Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions argus_skill/apps/_runtime_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class _Outcome:
final_review_reason: str = ""
final_review_next_action: str = ""
summary: str = ""
# Full operator-facing Engineer handoff. ``summary`` stays compact for
# status surfaces, while this field preserves long-form deliverables.
final_output: str = ""
research_result: dict | None = None
final_planner_report: dict = field(default_factory=dict)
plan_challenge: dict = field(default_factory=dict)
Expand Down
2 changes: 2 additions & 0 deletions argus_skill/apps/_runtime_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,7 @@ def _build_execute_outcome(self, ex_state: "_ExecuteState") -> _Outcome:
continue
summary_lines.append(cleaned.lstrip("#").strip())
summary = " ".join(summary_lines)[:1200]
final_output = visible_engineer_message.strip()
return _Outcome(
success=bool(outcome.successful and ex_state.effective_status == "done"),
status=ex_state.effective_status,
Expand All @@ -1284,6 +1285,7 @@ def _build_execute_outcome(self, ex_state: "_ExecuteState") -> _Outcome:
),
final_review_next_action=ex_state.final_review_next_action,
summary=summary,
final_output=final_output,
research_result=(
getattr(
rounds[-1].review,
Expand Down
1 change: 1 addition & 0 deletions argus_skill/core/event_payload_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
"scope": {"type": "string"},
"status": {"type": "string", "minLength": 1},
"summary": {"type": "string"},
"final_output": {"type": "string"},
"outcome_class": {
"type": "string",
"enum": [
Expand Down
15 changes: 15 additions & 0 deletions argus_skill/core/mission_view/_reduce_mission.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from ...life.mission_outcome import mission_outcome_class, mission_outcome_dimensions
from ..event_catalog import EventType
from ..role_reply import strip_named_lines
from ._reduce_helpers import (
_PROGRESS_LABELS,
_integer,
Expand Down Expand Up @@ -79,6 +80,7 @@ def reduce_mission_lifecycle_event(
"title": _text(event, "title", 240),
"objective": _text(event, "objective", 2000),
"summary": "",
"final_output": "",
"status": "working",
"started_at": ts,
"completed_at": None,
Expand Down Expand Up @@ -109,11 +111,13 @@ def reduce_mission_lifecycle_event(
event,
event_type,
)
final_output = str(event.get("final_output") or "").strip()
mission.update({
"id": _text(event, "item_id") or mission.get("id", ""),
"title": _text(event, "title", 240) or mission.get("title", ""),
"objective": _text(event, "objective", 2000) or mission.get("objective", ""),
"summary": _text(event, "summary", 1200),
"final_output": final_output or mission.get("final_output", ""),
"status": mission_status,
"completed_at": ts,
})
Expand Down Expand Up @@ -236,6 +240,17 @@ def reduce_round_event(
kind = _text(event, "kind")
label = _PROGRESS_LABELS.get(kind, "Working")
_set_role(view, role, "active", label, ts)
if role == "engineer" and kind in {
"assistant_message",
"agent_message",
"message",
}:
candidate = strip_named_lines(
str(event.get("text") or ""),
("MILESTONE_STATUS", "OPERATOR_QUESTION", "OPERATOR_OPTIONS"),
).strip()
if candidate:
mission["final_output"] = candidate
detail = (
_text(event, "action_summary", 4000)
or _text(event, "text", 4000)
Expand Down
2 changes: 2 additions & 0 deletions argus_skill/core/mission_view/_view_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def empty_mission_view() -> dict[str, Any]:
"title": "",
"objective": "",
"summary": "",
"final_output": "",
"status": "idle",
"started_at": None,
"completed_at": None,
Expand Down Expand Up @@ -152,6 +153,7 @@ def _read_unlocked(root: Path) -> dict[str, Any]:
skill.pop("content_truncated", None)
mission = payload.setdefault("mission", {})
mission.setdefault("summary", "")
mission.setdefault("final_output", "")
mission.setdefault("campaign_started_at", None)
mission.setdefault("campaign_elapsed_seconds", 0.0)
for role in payload.setdefault("roles", []):
Expand Down
6 changes: 6 additions & 0 deletions argus_skill/life/supervisor/_mission_execution_settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ def _emit_mission_outcome_and_build_result(
or ""
).split()
)[:1200]
final_output = str(
getattr(outcome, "final_output", "")
or getattr(outcome, "final_message", "")
or ""
).strip()
try:
from ...core.metrics import metrics_root_for_project, record_metric

Expand Down Expand Up @@ -789,6 +794,7 @@ def _emit_mission_outcome_and_build_result(
"success": success,
"status": status,
"summary": mission_summary,
"final_output": final_output,
"outcome_class": mission_outcome_class(status=status, success=success),
"outcome": state.outcome_dimensions,
"planner_report": planner_report,
Expand Down
4 changes: 2 additions & 2 deletions argus_skill/release_manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package_version": "0.1.1",
"release_id": "0.1.1+e49bd9f10086a3df",
"release_id": "0.1.1+75ef08fcb72bd7b4",
"schema_version": 1,
"source_digest": "e49bd9f10086a3df711c1be3594b60b70d593a0430c71f0e8f3488d29fe7644c"
"source_digest": "75ef08fcb72bd7b46f675e9784483fb0cce320ad95b5ec1d7fccfb8638bea868"
}
1 change: 1 addition & 0 deletions frontend/core/src/eventPayloads.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export interface LifeMissionCompletedEvent extends EventMsg {
"scope"?: string;
"status": string;
"summary"?: string;
"final_output"?: string;
"outcome_class"?: "completed" | "incomplete" | "stalled" | "blocked" | "failed" | "ended";
"outcome"?: { "execution_status": string; "review_status": string; "stage_certification": string; "interruption_kind": string; "resumable": boolean; };
"success"?: boolean;
Expand Down
64 changes: 63 additions & 1 deletion frontend/core/src/missionView.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { canonicalEventType, EVENT_TYPES } from './eventCatalog.js';
import { eventKey, isReasoning, isStructuredAgentPayload } from './events.js';
import {
eventKey,
isReasoning,
isStructuredAgentPayload,
visibleAgentText,
} from './events.js';
import {
missionOutcomeDimensions,
missionOutcomePresentation,
Expand Down Expand Up @@ -40,6 +45,7 @@ export function emptyMissionView(): MissionView {
title: '',
objective: '',
summary: '',
final_output: '',
status: 'idle',
started_at: null,
completed_at: null,
Expand Down Expand Up @@ -290,6 +296,7 @@ export function reduceMissionViewEvent(view: MissionView, event: EventMsg): Miss
title: S(event, 'title'),
objective: S(event, 'objective'),
summary: '',
final_output: '',
status: 'working',
started_at: ts,
completed_at: null,
Expand All @@ -308,6 +315,13 @@ export function reduceMissionViewEvent(view: MissionView, event: EventMsg): Miss
const kind = S(event, 'kind');
const label = PROGRESS_LABELS[kind] ?? 'Working';
setRole(view, role, 'active', label, ts);
if (
role === 'engineer'
&& ['assistant_message', 'agent_message', 'message'].includes(kind)
) {
const candidate = visibleAgentText(event.text);
if (candidate) view.mission.final_output = candidate;
}
const detail = S(event, 'action_summary') || S(event, 'text');
if (detail && !isReasoning(event) && !isStructuredAgentPayload(event)) {
addRoleWork(view, event, role, kind || 'progress', label, detail, 'active');
Expand Down Expand Up @@ -479,6 +493,9 @@ export function reduceMissionViewEvent(view: MissionView, event: EventMsg): Miss
view.mission.title = S(event, 'title') || view.mission.title;
view.mission.objective = S(event, 'objective') || view.mission.objective;
view.mission.summary = S(event, 'summary');
view.mission.final_output = visibleAgentText(event.final_output)
|| view.mission.final_output
|| '';
view.mission.status = presentation.missionStatus;
view.mission.completed_at = ts;
view.outcome = missionOutcomeDimensions(event);
Expand Down Expand Up @@ -665,10 +682,55 @@ export function projectMissionView(
.filter((event) => event.ts == null || Number(event.ts) > seedTs)
.sort((left, right) => Number(left.ts ?? 0) - Number(right.ts ?? 0))
.forEach((event) => reduceMissionViewEvent(view, event));
if (!view.mission.final_output) {
view.mission.final_output = recoverMissionFinalOutput(events, view.mission.id);
}
finalizeSnapshot(view, snapshot, artifacts, missionContext);
return view;
}

/** Recover full handoffs produced before ``life.mission.completed`` gained a
* dedicated ``final_output`` field. The compact snapshot can be newer than
* the REST event window, so inspect that window explicitly instead of relying
* on the incremental reducer to replay older rows. */
function recoverMissionFinalOutput(events: EventMsg[], missionId: string): string {
let completionIndex = -1;
for (let index = events.length - 1; index >= 0; index -= 1) {
const event = events[index];
if (canonicalEventType(event.type) !== EVENT_TYPES.LIFE_MISSION_COMPLETED) continue;
if (missionId && S(event, 'item_id') !== missionId) continue;
completionIndex = index;
break;
}
if (completionIndex < 0) return '';

let startIndex = -1;
for (let index = completionIndex - 1; index >= 0; index -= 1) {
const event = events[index];
if (canonicalEventType(event.type) !== EVENT_TYPES.LIFE_MISSION_STARTED) continue;
if (missionId && S(event, 'item_id') !== missionId) continue;
startIndex = index + 1;
break;
}
if (startIndex < 0) return '';

let output = '';
let finalOutput = '';
for (let index = startIndex; index < completionIndex; index += 1) {
const event = events[index];
if (canonicalEventType(event.type) !== EVENT_TYPES.ENGINEER_PROGRESS) continue;
const role = S(event, 'agent_layer') || S(event, 'actor');
const kind = S(event, 'kind');
if (!['engineer', 'main'].includes(role)) continue;
if (!['assistant_message', 'agent_message', 'message'].includes(kind)) continue;
const candidate = visibleAgentText(event.text);
if (!candidate) continue;
output = candidate;
if (event.final_delivery === true) finalOutput = candidate;
}
return finalOutput || output;
}

/**
* Decode only presentation-layer escaping introduced by Markdown/JSON
* transport. The persisted objective remains byte-for-byte authoritative.
Expand Down
4 changes: 2 additions & 2 deletions frontend/core/src/release.generated.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Generated by argus_skill.release_tools.generate_manifest. Do not edit.
export const RELEASE_ID = "0.1.1+e49bd9f10086a3df";
export const RELEASE_SOURCE_DIGEST = "e49bd9f10086a3df711c1be3594b60b70d593a0430c71f0e8f3488d29fe7644c";
export const RELEASE_ID = "0.1.1+75ef08fcb72bd7b4";
export const RELEASE_SOURCE_DIGEST = "75ef08fcb72bd7b46f675e9784483fb0cce320ad95b5ec1d7fccfb8638bea868";
1 change: 1 addition & 0 deletions frontend/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ export interface MissionView {
title: string;
objective: string;
summary: string;
final_output?: string;
status: string;
started_at: number | null;
completed_at: number | null;
Expand Down
170 changes: 85 additions & 85 deletions frontend/tui/bundle/argus.mjs

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading