okstra 0.180.0 → 0.183.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-registry.mjs +16 -2
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.d.mts +4 -2
- package/dist/commands/execute/render-bundle.mjs +46 -5
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +11 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/inspect/model-io.d.mts +1 -0
- package/dist/commands/inspect/model-io.mjs +25 -0
- package/dist/commands/inspect/model-io.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.mjs +29 -8
- package/dist/commands/inspect/stage-map.mjs.map +1 -1
- package/dist/commands/inspect/task-list.mjs +52 -6
- package/dist/commands/inspect/task-list.mjs.map +1 -1
- package/dist/commands/inspect/user-response.mjs +14 -4
- package/dist/commands/inspect/user-response.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.d.mts +1 -0
- package/dist/commands/lifecycle/check-project.mjs +69 -50
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/contract-check.d.mts +1 -0
- package/dist/commands/lifecycle/contract-check.mjs +18 -0
- package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.mjs +154 -51
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/pr/pr.d.mts +1 -0
- package/dist/commands/pr/pr.mjs +19 -1
- package/dist/commands/pr/pr.mjs.map +1 -1
- package/dist/commands/report/agent-activity.mjs +2 -2
- package/dist/commands/report/translate.mjs +3 -0
- package/dist/commands/report/translate.mjs.map +1 -1
- package/dist/lib/host-registry-client.mjs +13 -9
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/docs/architecture.md +11 -0
- package/docs/cli.md +26 -13
- package/docs/container.md +6 -4
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
- package/docs/for-ai/skills/okstra-code-review.md +4 -4
- package/docs/for-ai/skills/okstra-container-build.md +20 -17
- package/docs/for-ai/skills/okstra-inspect.md +20 -23
- package/docs/for-ai/skills/okstra-manager.md +19 -18
- package/docs/for-ai/skills/okstra-memory.md +2 -2
- package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
- package/docs/for-ai/skills/okstra-rollup.md +14 -13
- package/docs/for-ai/skills/okstra-run.md +7 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
- package/docs/for-ai/skills/okstra-setup.md +7 -7
- package/docs/for-ai/skills/okstra-usage.md +5 -4
- package/docs/for-ai/skills/okstra-user-response.md +50 -32
- package/docs/project-structure-overview.md +29 -26
- package/docs/task-process/implementation.md +7 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/report-writer-worker.md +3 -3
- package/runtime/agents/workers/translator-worker.md +5 -13
- package/runtime/bin/okstra-error-log.py +51 -11
- package/runtime/bin/okstra-report-translate.py +210 -23
- package/runtime/prompts/host-orchestration/implementation.md +1 -1
- package/runtime/prompts/launch.template.md +3 -7
- package/runtime/prompts/lead/context-loader.md +41 -141
- package/runtime/prompts/lead/convergence.md +8 -6
- package/runtime/prompts/lead/okstra-lead-contract.md +25 -35
- package/runtime/prompts/lead/plan-body-verification.md +187 -14
- package/runtime/prompts/lead/report-writer.md +20 -1
- package/runtime/prompts/lead/team-contract.md +8 -53
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +8 -2
- package/runtime/prompts/profiles/release-handoff.md +0 -1
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +6 -1
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
- package/runtime/python/okstra_ctl/agent_activity.py +118 -35
- package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +65 -18
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
- package/runtime/python/okstra_ctl/analysis_packet.py +81 -1
- package/runtime/python/okstra_ctl/approval_decisions.py +3 -2
- package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
- package/runtime/python/okstra_ctl/backfill.py +13 -10
- package/runtime/python/okstra_ctl/batch.py +2 -4
- package/runtime/python/okstra_ctl/build_tools.py +6 -3
- package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
- package/runtime/python/okstra_ctl/clarification_items.py +27 -13
- package/runtime/python/okstra_ctl/cmux.py +130 -52
- package/runtime/python/okstra_ctl/code_review_target.py +34 -8
- package/runtime/python/okstra_ctl/consumers.py +5 -4
- package/runtime/python/okstra_ctl/container.py +103 -8
- package/runtime/python/okstra_ctl/context_cost.py +2 -1
- package/runtime/python/okstra_ctl/contract_graph.py +497 -0
- package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
- package/runtime/python/okstra_ctl/convergence.py +338 -17
- package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
- package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
- package/runtime/python/okstra_ctl/convergence_store.py +55 -34
- package/runtime/python/okstra_ctl/design_prep.py +7 -4
- package/runtime/python/okstra_ctl/dispatch_core.py +35 -65
- package/runtime/python/okstra_ctl/dispatch_state.py +134 -59
- package/runtime/python/okstra_ctl/doctor.py +6 -3
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
- package/runtime/python/okstra_ctl/error_log_write.py +35 -30
- package/runtime/python/okstra_ctl/error_report.py +26 -1
- package/runtime/python/okstra_ctl/error_zip.py +27 -5
- package/runtime/python/okstra_ctl/execution_identity.py +3 -2
- package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
- package/runtime/python/okstra_ctl/final_report_schema.py +2 -2
- package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
- package/runtime/python/okstra_ctl/fixed_text.py +39 -0
- package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
- package/runtime/python/okstra_ctl/handoff.py +5 -4
- package/runtime/python/okstra_ctl/i18n.py +4 -2
- package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
- package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
- package/runtime/python/okstra_ctl/incremental_carry.py +2 -1
- package/runtime/python/okstra_ctl/incremental_scope.py +8 -3
- package/runtime/python/okstra_ctl/index.py +8 -11
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
- package/runtime/python/okstra_ctl/invocation.py +3 -6
- package/runtime/python/okstra_ctl/json_boundary.py +366 -0
- package/runtime/python/okstra_ctl/json_registry.py +10 -12
- package/runtime/python/okstra_ctl/jsonl.py +19 -2
- package/runtime/python/okstra_ctl/lead_events.py +33 -1
- package/runtime/python/okstra_ctl/listing.py +3 -3
- package/runtime/python/okstra_ctl/log_report.py +24 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -7
- package/runtime/python/okstra_ctl/manager_store.py +12 -10
- package/runtime/python/okstra_ctl/material.py +5 -1
- package/runtime/python/okstra_ctl/migrate.py +29 -25
- package/runtime/python/okstra_ctl/model_cli.py +3 -15
- package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
- package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
- package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
- package/runtime/python/okstra_ctl/paths.py +9 -0
- package/runtime/python/okstra_ctl/plan_items.py +115 -4
- package/runtime/python/okstra_ctl/plan_items_cli.py +513 -18
- package/runtime/python/okstra_ctl/pr_template.py +3 -2
- package/runtime/python/okstra_ctl/project_meta.py +5 -7
- package/runtime/python/okstra_ctl/recap.py +5 -4
- package/runtime/python/okstra_ctl/reconcile.py +21 -27
- package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
- package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
- package/runtime/python/okstra_ctl/render.py +26 -15
- package/runtime/python/okstra_ctl/render_final_report.py +3 -2
- package/runtime/python/okstra_ctl/report_assembly.py +172 -17
- package/runtime/python/okstra_ctl/report_finalize.py +7 -10
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_language.py +3 -2
- package/runtime/python/okstra_ctl/report_markdown.py +13 -1
- package/runtime/python/okstra_ctl/report_narrative.py +40 -8
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
- package/runtime/python/okstra_ctl/report_views.py +3 -2
- package/runtime/python/okstra_ctl/rollup.py +65 -4
- package/runtime/python/okstra_ctl/run.py +159 -56
- package/runtime/python/okstra_ctl/run_audit.py +3 -2
- package/runtime/python/okstra_ctl/run_context.py +6 -9
- package/runtime/python/okstra_ctl/run_index_row.py +2 -8
- package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
- package/runtime/python/okstra_ctl/schema_excerpt.py +4 -2
- package/runtime/python/okstra_ctl/session_transcript.py +27 -1
- package/runtime/python/okstra_ctl/set_work_status.py +64 -38
- package/runtime/python/okstra_ctl/stage_fix_carry.py +4 -2
- package/runtime/python/okstra_ctl/stage_map.py +26 -6
- package/runtime/python/okstra_ctl/stage_targets.py +3 -4
- package/runtime/python/okstra_ctl/team.py +2 -1
- package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
- package/runtime/python/okstra_ctl/time_report.py +51 -4
- package/runtime/python/okstra_ctl/usage_identity.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +58 -4
- package/runtime/python/okstra_ctl/user_response.py +1431 -66
- package/runtime/python/okstra_ctl/wizard.py +11 -44
- package/runtime/python/okstra_ctl/work_categories.py +3 -2
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -7
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
- package/runtime/python/okstra_ctl/worker_runner.py +14 -12
- package/runtime/python/okstra_ctl/workflow.py +2 -1
- package/runtime/python/okstra_ctl/worktree.py +3 -2
- package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
- package/runtime/python/okstra_ctl/write_policy.py +4 -2
- package/runtime/python/okstra_token_usage/antigravity.py +39 -12
- package/runtime/python/okstra_token_usage/collect.py +90 -38
- package/runtime/python/okstra_token_usage/grok.py +127 -0
- package/runtime/schemas/final-report-v2.0.schema.json +9 -0
- package/runtime/schemas/final-report-v3.0.schema.json +9 -0
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
- package/runtime/skills/okstra-code-review/SKILL.md +21 -11
- package/runtime/skills/okstra-container-build/SKILL.md +18 -18
- package/runtime/skills/okstra-inspect/SKILL.md +12 -11
- package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
- package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
- package/runtime/skills/okstra-inspect/facets/history.md +9 -14
- package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
- package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
- package/runtime/skills/okstra-inspect/facets/report.md +6 -10
- package/runtime/skills/okstra-inspect/facets/status.md +9 -8
- package/runtime/skills/okstra-inspect/facets/time.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +16 -14
- package/runtime/skills/okstra-memory/SKILL.md +3 -3
- package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
- package/runtime/skills/okstra-rollup/SKILL.md +6 -16
- package/runtime/skills/okstra-run/SKILL.md +7 -7
- package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
- package/runtime/skills/okstra-setup/SKILL.md +21 -13
- package/runtime/skills/okstra-setup/references/project-config.md +2 -2
- package/runtime/skills/okstra-usage/SKILL.md +10 -10
- package/runtime/skills/okstra-user-response/SKILL.md +78 -107
- package/runtime/templates/report-writer-prompt-preamble.md +17 -1
- package/runtime/templates/reports/schedule.template.md +4 -4
- package/runtime/templates/worker-error-contract.md +17 -29
- package/runtime/validators/validate-run.py +379 -63
- package/runtime/validators/validate_session_conformance.py +43 -7
|
@@ -10,7 +10,7 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `runtime` | `codex` |
|
|
12
12
|
| `leadRoleLabel` | `Codex lead` |
|
|
13
|
-
| `userPromptMode` | `
|
|
13
|
+
| `userPromptMode` | `native-question` |
|
|
14
14
|
| `workerDispatchBackend` | `mixed` |
|
|
15
15
|
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
16
|
| `sessionAccounting` | `artifact-only` |
|
|
@@ -20,14 +20,57 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
|
|
|
20
20
|
|
|
21
21
|
## Wizard interaction relay
|
|
22
22
|
|
|
23
|
-
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host.
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it. Codex `request_user_input` is single-select only (2–3 mutually exclusive choices, 1–3 questions); it has no native multi-select.
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"schemaVersion": 1,
|
|
28
28
|
"runtime": "codex",
|
|
29
|
-
"semanticFunctions": [
|
|
29
|
+
"semanticFunctions": [
|
|
30
|
+
"plain_text_input",
|
|
31
|
+
"native_single_select",
|
|
32
|
+
"native_question_group"
|
|
33
|
+
],
|
|
34
|
+
"nativeLimits": {
|
|
35
|
+
"minOptions": 2,
|
|
36
|
+
"maxOptions": 3,
|
|
37
|
+
"maxQuestions": 3
|
|
38
|
+
},
|
|
30
39
|
"interactions": {
|
|
40
|
+
"native-single": {
|
|
41
|
+
"function": "request_user_input",
|
|
42
|
+
"input": {
|
|
43
|
+
"callCount": 1,
|
|
44
|
+
"questions": "one",
|
|
45
|
+
"id": "prompt.step",
|
|
46
|
+
"header": "Q1",
|
|
47
|
+
"question": "label-with-progress",
|
|
48
|
+
"options": "all-in-original-order-as-label-description"
|
|
49
|
+
},
|
|
50
|
+
"response": {
|
|
51
|
+
"envelope": "answers",
|
|
52
|
+
"key": "question-id",
|
|
53
|
+
"selection": "answers-array",
|
|
54
|
+
"submit": "matching-option-value"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"native-group": {
|
|
58
|
+
"function": "request_user_input",
|
|
59
|
+
"input": {
|
|
60
|
+
"callCount": 1,
|
|
61
|
+
"questions": "all-in-original-order",
|
|
62
|
+
"id": "questions[].step",
|
|
63
|
+
"header": "Q<one-based-question-position>",
|
|
64
|
+
"question": "label-with-progress",
|
|
65
|
+
"options": "all-in-original-order-as-label-description"
|
|
66
|
+
},
|
|
67
|
+
"response": {
|
|
68
|
+
"envelope": "answers",
|
|
69
|
+
"key": "question-id",
|
|
70
|
+
"selection": "answers-array",
|
|
71
|
+
"submit": "compact-step-json-values"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
31
74
|
"numbered-single": {
|
|
32
75
|
"function": "host-text",
|
|
33
76
|
"input": {
|
|
@@ -67,7 +110,9 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
67
110
|
}
|
|
68
111
|
```
|
|
69
112
|
|
|
70
|
-
|
|
113
|
+
For `request_user_input`, send one to three questions. Each question carries `id` (`prompt.step` or `questions[].step`), a header of at most 12 characters (`Q1`…`Q3`), the rendered question text including the progress suffix, and every wizard option as `{label, description}` in original order — the tool has no option `value` field. Do not include an `Other` option; the client adds a free-form row. Native plans are emitted only when the prompt fits `nativeLimits` (unique labels, two or three options, one to three questions) and no grouped question is multi-select. Other prompts use the text mapping so no option is dropped. Look up each answer by question `id`. The selected strings are in `answers[id].answers`. Match those strings to option labels, emit their `value` fields in original option order, and join with `,` when more than one is present. If the typed Other text matches no label, submit it unchanged.
|
|
114
|
+
|
|
115
|
+
For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.
|
|
71
116
|
|
|
72
117
|
|
|
73
118
|
## Semantic operation mapping
|
|
@@ -76,7 +121,7 @@ Render every numbered item as its option label followed by its description verba
|
|
|
76
121
|
|---|---|
|
|
77
122
|
| `read_artifacts` | Read the manifest-provided paths through the current host's file interface. |
|
|
78
123
|
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
79
|
-
| `prompt_user` |
|
|
124
|
+
| `prompt_user` | Use `request_user_input` for approvals and clarifications that fit two or three options; otherwise ask through host text and stop until an explicit answer arrives. |
|
|
80
125
|
| `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` with the current Codex host's primitive, the returned `promptPath`, and `hostModelValue`. Pass `runner=cli-wrapper` assignments to `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when required. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
|
|
81
126
|
| `await_workers` | Await native host workers through the host primitive and CLI workers through synchronous dispatch, then verify team-state terminal records and Result Paths for both. |
|
|
82
127
|
| `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or `okstra worker-dispatch` attempt according to the persisted runner. |
|
|
@@ -11,7 +11,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
|
11
11
|
PENDING_HOST_PORT,
|
|
12
12
|
CapabilityHostAdapter,
|
|
13
13
|
no_automatic_claim,
|
|
14
|
-
|
|
14
|
+
relay_interaction_port,
|
|
15
15
|
)
|
|
16
16
|
from okstra_ctl.domain.host import HostDescriptor
|
|
17
17
|
from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
|
|
@@ -54,7 +54,7 @@ def create_adapter(
|
|
|
54
54
|
interaction_port=(
|
|
55
55
|
interaction_port
|
|
56
56
|
if interaction_port is not None
|
|
57
|
-
else
|
|
57
|
+
else relay_interaction_port(DESCRIPTOR.relay_contract)
|
|
58
58
|
),
|
|
59
59
|
lead_session_port=lead_session_port,
|
|
60
60
|
worker_dispatch_port=worker_dispatch_port,
|
|
@@ -10,7 +10,7 @@ This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only w
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `runtime` | `grok` |
|
|
12
12
|
| `leadRoleLabel` | `Grok lead` |
|
|
13
|
-
| `userPromptMode` | `
|
|
13
|
+
| `userPromptMode` | `native-question` |
|
|
14
14
|
| `workerDispatchBackend` | `mixed` |
|
|
15
15
|
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
16
|
| `sessionAccounting` | `artifact-only` |
|
|
@@ -20,14 +20,73 @@ This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only w
|
|
|
20
20
|
|
|
21
21
|
## Wizard interaction relay
|
|
22
22
|
|
|
23
|
-
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host.
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it.
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"schemaVersion": 1,
|
|
28
28
|
"runtime": "grok",
|
|
29
|
-
"semanticFunctions": [
|
|
29
|
+
"semanticFunctions": [
|
|
30
|
+
"plain_text_input",
|
|
31
|
+
"native_single_select",
|
|
32
|
+
"native_multi_select",
|
|
33
|
+
"native_question_group"
|
|
34
|
+
],
|
|
35
|
+
"nativeLimits": {
|
|
36
|
+
"minOptions": 2,
|
|
37
|
+
"maxOptions": 15,
|
|
38
|
+
"maxQuestions": 8
|
|
39
|
+
},
|
|
30
40
|
"interactions": {
|
|
41
|
+
"native-single": {
|
|
42
|
+
"function": "ask_user_question",
|
|
43
|
+
"input": {
|
|
44
|
+
"callCount": 1,
|
|
45
|
+
"questions": "one",
|
|
46
|
+
"question": "label-with-progress",
|
|
47
|
+
"options": "all-in-original-order-as-label-description",
|
|
48
|
+
"multiSelect": false
|
|
49
|
+
},
|
|
50
|
+
"response": {
|
|
51
|
+
"envelope": "answers",
|
|
52
|
+
"key": "rendered-question-text",
|
|
53
|
+
"selection": "selected-label",
|
|
54
|
+
"submit": "matching-option-value"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"native-multi": {
|
|
58
|
+
"function": "ask_user_question",
|
|
59
|
+
"input": {
|
|
60
|
+
"callCount": 1,
|
|
61
|
+
"questions": "one",
|
|
62
|
+
"question": "label-with-progress",
|
|
63
|
+
"options": "all-in-original-order-as-label-description",
|
|
64
|
+
"multiSelect": true
|
|
65
|
+
},
|
|
66
|
+
"response": {
|
|
67
|
+
"envelope": "answers",
|
|
68
|
+
"key": "rendered-question-text",
|
|
69
|
+
"selection": "selected-labels-joined-comma-space",
|
|
70
|
+
"submit": "csv-matching-option-values-in-option-order"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"native-group": {
|
|
74
|
+
"function": "ask_user_question",
|
|
75
|
+
"input": {
|
|
76
|
+
"callCount": 1,
|
|
77
|
+
"questions": "all-in-original-order",
|
|
78
|
+
"question": "label-with-progress",
|
|
79
|
+
"options": "all-in-original-order-as-label-description",
|
|
80
|
+
"multiSelect": "questions[].multi"
|
|
81
|
+
},
|
|
82
|
+
"response": {
|
|
83
|
+
"envelope": "answers",
|
|
84
|
+
"key": "rendered-question-text",
|
|
85
|
+
"selection": "selected-label-or-labels-joined-comma-space",
|
|
86
|
+
"multiValue": "csv-matching-option-values-in-option-order",
|
|
87
|
+
"submit": "compact-step-json-values"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
31
90
|
"numbered-single": {
|
|
32
91
|
"function": "host-text",
|
|
33
92
|
"input": {
|
|
@@ -67,7 +126,9 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
67
126
|
}
|
|
68
127
|
```
|
|
69
128
|
|
|
70
|
-
|
|
129
|
+
For `ask_user_question`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Do not send `preview`. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only when the prompt fits `nativeLimits` (unique option labels, two to fifteen options, one to eight questions); other prompts use the text mapping so no option is dropped. The host adds a free-text row on every question. Do not drop a wizard option to make room for it. If the user types in that row and the text matches no option label, submit the typed text unchanged. For a group, key the compact JSON object by the corresponding `questions[].step`; a multi-select question stores its ordered value CSV as one string, not a JSON array.
|
|
130
|
+
|
|
131
|
+
For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer. Do not translate numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.
|
|
71
132
|
|
|
72
133
|
## Semantic operation mapping
|
|
73
134
|
|
|
@@ -75,7 +136,7 @@ Render every numbered item as its option label followed by its description verba
|
|
|
75
136
|
|---|---|
|
|
76
137
|
| `read_artifacts` | Read the manifest-provided paths through the current Grok host file interface. |
|
|
77
138
|
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
78
|
-
| `prompt_user` |
|
|
139
|
+
| `prompt_user` | Use `ask_user_question` for approvals and clarifications; do not infer an answer from silence. |
|
|
79
140
|
| `dispatch_worker` | Verify the materialized invocation. Use the host primitive with `promptPath` and `hostModelValue` for `native-session`; use deterministic `okstra worker-dispatch` with `modelExecutionValue` for `cli-wrapper`. **Not in a cmux run:** the cmux adapter overrides this row. |
|
|
80
141
|
| `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
|
|
81
142
|
| `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
|
|
@@ -16,7 +16,8 @@ from okstra_ctl.domain.worker_exec import (
|
|
|
16
16
|
PolicySupport,
|
|
17
17
|
WorkerExecRequest,
|
|
18
18
|
)
|
|
19
|
-
from okstra_ctl.domain.worker_presentation import
|
|
19
|
+
from okstra_ctl.domain.worker_presentation import JsonEvents
|
|
20
|
+
from okstra_ctl.domain.worker_stream import StreamEvent, Text, ToolCall, ToolResult
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
GROK = {
|
|
@@ -108,6 +109,65 @@ def observe_served_model(event: Mapping[str, Any]) -> str | None:
|
|
|
108
109
|
return names[0]
|
|
109
110
|
return None
|
|
110
111
|
|
|
112
|
+
|
|
113
|
+
def stream_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
114
|
+
"""grok `--output-format streaming-json` 한 줄을 공통 이벤트로 옮긴다.
|
|
115
|
+
|
|
116
|
+
이 CLI 의 `-p` 기본 `plain` 은 종료 시에만 stdout 에 쓴다. 파이프에 붙은
|
|
117
|
+
워커는 그 동안 pane 과 로그가 0바이트다(실측 2026-08-22). 같은 호출에
|
|
118
|
+
`streaming-json` 을 주면 ACP 세션 업데이트가 NDJSON 으로 흐른다. 키는
|
|
119
|
+
`type=text|tool_call|tool_call_update|end` 이고 Claude 의
|
|
120
|
+
`message.content` 가 아니다 — 그 스키마로 읽으면 이벤트는 전부 버려진다.
|
|
121
|
+
"""
|
|
122
|
+
kind = event.get("type")
|
|
123
|
+
if kind == "text":
|
|
124
|
+
data = event.get("data")
|
|
125
|
+
# 공백만 있는 토큰도 살린다. 버리면 붙인 문장에서 단어가 붙는다.
|
|
126
|
+
return (Text(body=data),) if isinstance(data, str) and data else ()
|
|
127
|
+
if kind == "tool_call":
|
|
128
|
+
name = str(event.get("toolName") or event.get("title") or "tool")
|
|
129
|
+
return (ToolCall(name=name, detail=_call_detail(event)),)
|
|
130
|
+
if kind == "tool_call_update":
|
|
131
|
+
return _completed_tool(event)
|
|
132
|
+
if kind == "error":
|
|
133
|
+
message = event.get("message")
|
|
134
|
+
return (Text(body=message),) if isinstance(message, str) and message.strip() else ()
|
|
135
|
+
return ()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _call_detail(event: Mapping[str, Any]) -> str:
|
|
139
|
+
payload = event.get("rawInput")
|
|
140
|
+
if isinstance(payload, Mapping):
|
|
141
|
+
for key in ("command", "file_path", "path", "pattern", "query"):
|
|
142
|
+
value = payload.get(key)
|
|
143
|
+
if value:
|
|
144
|
+
return str(value)
|
|
145
|
+
return next(
|
|
146
|
+
(str(value) for value in payload.values() if isinstance(value, str) and value),
|
|
147
|
+
"",
|
|
148
|
+
)
|
|
149
|
+
title = event.get("title")
|
|
150
|
+
return str(title) if isinstance(title, str) else ""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _completed_tool(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
154
|
+
if event.get("status") != "completed":
|
|
155
|
+
return ()
|
|
156
|
+
body = _output_body(event.get("rawOutput"))
|
|
157
|
+
return (ToolResult(body=body, size_bytes=len(body.encode("utf-8"))),)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _output_body(output: Any) -> str:
|
|
161
|
+
if isinstance(output, str):
|
|
162
|
+
return output
|
|
163
|
+
if isinstance(output, Mapping):
|
|
164
|
+
text = next(
|
|
165
|
+
(str(value) for value in output.values() if isinstance(value, str) and value),
|
|
166
|
+
"",
|
|
167
|
+
)
|
|
168
|
+
return text or json.dumps(output, ensure_ascii=False)
|
|
169
|
+
return "" if output is None else str(output)
|
|
170
|
+
|
|
111
171
|
GROK_LEAD_LAUNCH = LeadLaunchSpec(
|
|
112
172
|
executable="grok",
|
|
113
173
|
model_flag="--model",
|
|
@@ -125,6 +185,8 @@ class GrokExecution:
|
|
|
125
185
|
|
|
126
186
|
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
127
187
|
cwd = request.worktree_path or request.project_root
|
|
188
|
+
# `-p` 기본 `plain` 은 종료 시에만 stdout 에 쓴다. 러너는 파이프라
|
|
189
|
+
# TUI 도 열리지 않고, `--no-alt-screen` 도 그 침묵을 바꾸지 않는다.
|
|
128
190
|
return ExecCommand(
|
|
129
191
|
argv=(
|
|
130
192
|
"grok",
|
|
@@ -134,10 +196,16 @@ class GrokExecution:
|
|
|
134
196
|
request.model,
|
|
135
197
|
"--cwd",
|
|
136
198
|
str(cwd),
|
|
199
|
+
"--output-format",
|
|
200
|
+
"streaming-json",
|
|
137
201
|
),
|
|
138
202
|
stdin_text=None,
|
|
139
203
|
cwd=cwd,
|
|
140
|
-
presentation=
|
|
204
|
+
presentation=JsonEvents(
|
|
205
|
+
normalise=stream_events,
|
|
206
|
+
observe=observe_served_model,
|
|
207
|
+
join_adjacent_text=True,
|
|
208
|
+
),
|
|
141
209
|
)
|
|
142
210
|
|
|
143
211
|
def policy_support(self) -> PolicySupport:
|
|
@@ -3,10 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import json
|
|
6
|
-
import os
|
|
7
6
|
import re
|
|
8
7
|
import sys
|
|
9
|
-
import tempfile
|
|
10
8
|
from collections.abc import Mapping, Sequence
|
|
11
9
|
from datetime import datetime, timezone
|
|
12
10
|
from pathlib import Path
|
|
@@ -22,6 +20,11 @@ from okstra_ctl.worker_audit_ledger import (
|
|
|
22
20
|
parse_evidence_commands,
|
|
23
21
|
read_evidence_commands,
|
|
24
22
|
)
|
|
23
|
+
from okstra_ctl.json_boundary import (
|
|
24
|
+
JsonBoundaryError,
|
|
25
|
+
load_owned_object,
|
|
26
|
+
mutate_owned_object_atomic,
|
|
27
|
+
)
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
ACTIVITY_KINDS = frozenset({
|
|
@@ -59,12 +62,9 @@ class ActivityProjectionError(ValueError):
|
|
|
59
62
|
|
|
60
63
|
def _read_json_object(path: Path) -> dict[str, Any]:
|
|
61
64
|
try:
|
|
62
|
-
|
|
63
|
-
except
|
|
64
|
-
raise ActivityProjectionError(
|
|
65
|
-
if not isinstance(payload, dict):
|
|
66
|
-
raise ActivityProjectionError(f"JSON value must be an object: {path}")
|
|
67
|
-
return payload
|
|
65
|
+
return load_owned_object(path, artifact="agent activity artifact")
|
|
66
|
+
except JsonBoundaryError as exc:
|
|
67
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def _resolve_project_path(project_root: Path, value: str | Path) -> Path:
|
|
@@ -88,6 +88,50 @@ def _manifest_run_seq(manifest: Mapping[str, Any]) -> str:
|
|
|
88
88
|
return _require_string(sequences, "manifests")
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _validate_activity_ownership(
|
|
92
|
+
project_root: Path,
|
|
93
|
+
manifest: Mapping[str, Any],
|
|
94
|
+
details: Mapping[str, Any],
|
|
95
|
+
) -> None:
|
|
96
|
+
executions = manifest.get("roleExecutions")
|
|
97
|
+
assignments = manifest.get("workerAssignments")
|
|
98
|
+
if isinstance(assignments, list):
|
|
99
|
+
assigned = {
|
|
100
|
+
row["workerId"] for row in assignments
|
|
101
|
+
if isinstance(row, Mapping) and isinstance(row.get("workerId"), str)
|
|
102
|
+
}
|
|
103
|
+
elif isinstance(executions, list):
|
|
104
|
+
assigned = {
|
|
105
|
+
f"{row['provider']}-worker"
|
|
106
|
+
for row in executions
|
|
107
|
+
if isinstance(row, Mapping) and isinstance(row.get("provider"), str)
|
|
108
|
+
}
|
|
109
|
+
else:
|
|
110
|
+
assigned = set()
|
|
111
|
+
if isinstance(assignments, list) or isinstance(executions, list):
|
|
112
|
+
assigned.update({"okstra-lead", f"{manifest.get('leadRuntime', '')}-lead"})
|
|
113
|
+
agent = _require_string(details, "agent")
|
|
114
|
+
if agent not in assigned:
|
|
115
|
+
raise ActivityProjectionError(
|
|
116
|
+
f"agent `{agent}` is not assigned to this run"
|
|
117
|
+
)
|
|
118
|
+
state_value = manifest.get("planBodyVerificationPath")
|
|
119
|
+
if not isinstance(state_value, str) or not details.get("planItemIds"):
|
|
120
|
+
return
|
|
121
|
+
state = _read_json_object(_resolve_project_path(project_root, state_value))
|
|
122
|
+
verification = state.get("planBodyVerification")
|
|
123
|
+
items = verification.get("planItems") if isinstance(verification, Mapping) else []
|
|
124
|
+
known = {
|
|
125
|
+
row.get("id") for row in items
|
|
126
|
+
if isinstance(row, Mapping) and isinstance(row.get("id"), str)
|
|
127
|
+
}
|
|
128
|
+
unknown = sorted(set(details["planItemIds"]) - known)
|
|
129
|
+
if unknown:
|
|
130
|
+
raise ActivityProjectionError(
|
|
131
|
+
"plan item(s) not in the current plan state: " + ", ".join(unknown)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
91
135
|
def _utc_now() -> str:
|
|
92
136
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
93
137
|
|
|
@@ -139,27 +183,6 @@ def _validate_activity_order(rows: Sequence[Mapping[str, Any]]) -> None:
|
|
|
139
183
|
)
|
|
140
184
|
|
|
141
185
|
|
|
142
|
-
def _atomic_write_json(path: Path, payload: Mapping[str, Any]) -> None:
|
|
143
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
-
temporary_path: Path | None = None
|
|
145
|
-
try:
|
|
146
|
-
with tempfile.NamedTemporaryFile(
|
|
147
|
-
mode="w",
|
|
148
|
-
encoding="utf-8",
|
|
149
|
-
dir=path.parent,
|
|
150
|
-
prefix=f".{path.name}.",
|
|
151
|
-
suffix=".tmp",
|
|
152
|
-
delete=False,
|
|
153
|
-
) as handle:
|
|
154
|
-
temporary_path = Path(handle.name)
|
|
155
|
-
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
156
|
-
handle.write("\n")
|
|
157
|
-
os.replace(temporary_path, path)
|
|
158
|
-
finally:
|
|
159
|
-
if temporary_path is not None and temporary_path.exists():
|
|
160
|
-
temporary_path.unlink()
|
|
161
|
-
|
|
162
|
-
|
|
163
186
|
def record_activity(
|
|
164
187
|
project_root: Path,
|
|
165
188
|
run_manifest_path: Path,
|
|
@@ -172,6 +195,7 @@ def record_activity(
|
|
|
172
195
|
raise ActivityProjectionError(
|
|
173
196
|
"run manifest does not declare activityContractVersion 1"
|
|
174
197
|
)
|
|
198
|
+
_validate_activity_ownership(project_root, manifest, details)
|
|
175
199
|
event = LeadEvent(
|
|
176
200
|
event_type="activity",
|
|
177
201
|
lead_runtime=_require_string(manifest, "leadRuntime"),
|
|
@@ -184,7 +208,10 @@ def record_activity(
|
|
|
184
208
|
events_path = _resolve_project_path(
|
|
185
209
|
project_root, _require_string(manifest, "leadEventsPath")
|
|
186
210
|
)
|
|
187
|
-
|
|
211
|
+
try:
|
|
212
|
+
return append_activity_event(events_path, event)
|
|
213
|
+
except ValueError as exc:
|
|
214
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
188
215
|
|
|
189
216
|
|
|
190
217
|
def agent_activity_rows(
|
|
@@ -224,9 +251,14 @@ def project_agent_activity(
|
|
|
224
251
|
manifest = _read_json_object(run_manifest_path)
|
|
225
252
|
if manifest.get("activityContractVersion") != 1:
|
|
226
253
|
return ()
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
254
|
+
try:
|
|
255
|
+
mutate_owned_object_atomic(
|
|
256
|
+
data_path,
|
|
257
|
+
lambda data: {**data, "agentActivity": list(rows)},
|
|
258
|
+
artifact="final report record",
|
|
259
|
+
)
|
|
260
|
+
except JsonBoundaryError as exc:
|
|
261
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
230
262
|
return rows
|
|
231
263
|
|
|
232
264
|
|
|
@@ -242,6 +274,9 @@ def _parse_command_records(raw_records: Sequence[str]) -> tuple[EvidenceCommand,
|
|
|
242
274
|
|
|
243
275
|
def _activity_commands(args: argparse.Namespace) -> tuple[EvidenceCommand, ...]:
|
|
244
276
|
commands = list(_parse_command_records(args.command_record))
|
|
277
|
+
typed = _typed_command(args)
|
|
278
|
+
if typed is not None:
|
|
279
|
+
commands.append(typed)
|
|
245
280
|
if args.audit_sidecar is not None:
|
|
246
281
|
audit_commands, failures = read_evidence_commands(args.audit_sidecar)
|
|
247
282
|
if failures:
|
|
@@ -250,6 +285,45 @@ def _activity_commands(args: argparse.Namespace) -> tuple[EvidenceCommand, ...]:
|
|
|
250
285
|
return tuple(commands)
|
|
251
286
|
|
|
252
287
|
|
|
288
|
+
def _typed_command(args: argparse.Namespace) -> EvidenceCommand | None:
|
|
289
|
+
values = (
|
|
290
|
+
args.evidence_command,
|
|
291
|
+
args.command_cwd,
|
|
292
|
+
args.command_exit_code,
|
|
293
|
+
args.command_output_file,
|
|
294
|
+
)
|
|
295
|
+
if not any(value is not None for value in values):
|
|
296
|
+
return None
|
|
297
|
+
if any(value is None for value in values):
|
|
298
|
+
raise ActivityProjectionError(
|
|
299
|
+
"--command requires --command-cwd, --command-exit-code, and "
|
|
300
|
+
"--command-output-file"
|
|
301
|
+
)
|
|
302
|
+
try:
|
|
303
|
+
output_summary = args.command_output_file.read_text(encoding="utf-8")
|
|
304
|
+
except (OSError, UnicodeError) as exc:
|
|
305
|
+
raise ActivityProjectionError(
|
|
306
|
+
f"cannot read command output {args.command_output_file}: {exc}"
|
|
307
|
+
) from exc
|
|
308
|
+
return EvidenceCommand(
|
|
309
|
+
command=args.evidence_command,
|
|
310
|
+
cwd=args.command_cwd,
|
|
311
|
+
exit_code=args.command_exit_code,
|
|
312
|
+
output_summary=output_summary,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _activity_summary(args: argparse.Namespace) -> str:
|
|
317
|
+
if args.summary is not None:
|
|
318
|
+
return args.summary
|
|
319
|
+
try:
|
|
320
|
+
return args.summary_file.read_text(encoding="utf-8").strip()
|
|
321
|
+
except (OSError, UnicodeError) as exc:
|
|
322
|
+
raise ActivityProjectionError(
|
|
323
|
+
f"cannot read activity summary {args.summary_file}: {exc}"
|
|
324
|
+
) from exc
|
|
325
|
+
|
|
326
|
+
|
|
253
327
|
def _conversation_activity_line(details: Mapping[str, Any]) -> str:
|
|
254
328
|
plan_items = ",".join(details["planItemIds"]) or "<none>"
|
|
255
329
|
result_path = str(details["resultPath"] or "<none>")
|
|
@@ -266,13 +340,15 @@ def _append(args: argparse.Namespace) -> int:
|
|
|
266
340
|
details = {
|
|
267
341
|
"kind": args.kind,
|
|
268
342
|
"agent": args.agent,
|
|
269
|
-
"summary": args
|
|
343
|
+
"summary": _activity_summary(args),
|
|
270
344
|
"planItemIds": args.plan_item_id,
|
|
271
345
|
"resultPath": args.result_path,
|
|
272
346
|
"commands": [command.to_record() for command in commands],
|
|
273
347
|
"evidenceRefs": args.evidence_ref,
|
|
274
348
|
"outcome": args.outcome,
|
|
275
349
|
}
|
|
350
|
+
if args.request_ref is not None:
|
|
351
|
+
details["activityRequestRef"] = args.request_ref
|
|
276
352
|
event = record_activity(args.project_root, args.run_manifest, details)
|
|
277
353
|
payload = dict(event.details)
|
|
278
354
|
payload["ok"] = True
|
|
@@ -301,15 +377,22 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
301
377
|
"--kind", choices=sorted(ACTIVITY_KINDS), required=True
|
|
302
378
|
)
|
|
303
379
|
append_parser.add_argument("--agent", required=True)
|
|
304
|
-
append_parser.
|
|
380
|
+
summary = append_parser.add_mutually_exclusive_group(required=True)
|
|
381
|
+
summary.add_argument("--summary")
|
|
382
|
+
summary.add_argument("--summary-file", type=Path)
|
|
305
383
|
append_parser.add_argument(
|
|
306
384
|
"--outcome", choices=sorted(ACTIVITY_OUTCOMES), required=True
|
|
307
385
|
)
|
|
308
386
|
append_parser.add_argument("--plan-item-id", action="append", default=[])
|
|
309
387
|
append_parser.add_argument("--evidence-ref", action="append", default=[])
|
|
310
388
|
append_parser.add_argument("--command-record", action="append", default=[])
|
|
389
|
+
append_parser.add_argument("--command", dest="evidence_command")
|
|
390
|
+
append_parser.add_argument("--command-cwd")
|
|
391
|
+
append_parser.add_argument("--command-exit-code", type=int)
|
|
392
|
+
append_parser.add_argument("--command-output-file", type=Path)
|
|
311
393
|
append_parser.add_argument("--result-path", default="")
|
|
312
394
|
append_parser.add_argument("--audit-sidecar", type=Path)
|
|
395
|
+
append_parser.add_argument("--request-ref")
|
|
313
396
|
project_parser = subparsers.add_parser("project")
|
|
314
397
|
project_parser.add_argument("--project-root", type=Path, required=True)
|
|
315
398
|
project_parser.add_argument("--run-manifest", type=Path, required=True)
|
|
@@ -15,6 +15,12 @@ import tempfile
|
|
|
15
15
|
from typing import Iterator, Literal, Mapping, get_args
|
|
16
16
|
|
|
17
17
|
from .domain.role import RoleCatalogError, role_for_duty
|
|
18
|
+
from .json_boundary import (
|
|
19
|
+
JsonBoundaryError,
|
|
20
|
+
external_invocation_json_source,
|
|
21
|
+
load_external_json,
|
|
22
|
+
load_owned_object,
|
|
23
|
+
)
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
AgentAudience = Literal[
|
|
@@ -935,8 +941,15 @@ def _verified_result_envelope(
|
|
|
935
941
|
) -> tuple[bytes, str]:
|
|
936
942
|
try:
|
|
937
943
|
body = result_path.read_bytes()
|
|
938
|
-
envelope =
|
|
939
|
-
|
|
944
|
+
envelope = load_external_json(
|
|
945
|
+
external_invocation_json_source(
|
|
946
|
+
result_path,
|
|
947
|
+
project_root=project_root,
|
|
948
|
+
purpose=purpose,
|
|
949
|
+
),
|
|
950
|
+
artifact="standalone worker result",
|
|
951
|
+
)
|
|
952
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError, JsonBoundaryError) as exc:
|
|
940
953
|
raise AgentInvocationError(
|
|
941
954
|
f"standalone result envelope is missing or invalid: {result_path}"
|
|
942
955
|
) from exc
|
|
@@ -1494,8 +1507,8 @@ def _metadata_difference(
|
|
|
1494
1507
|
so they are reported by name too.
|
|
1495
1508
|
"""
|
|
1496
1509
|
try:
|
|
1497
|
-
existing =
|
|
1498
|
-
except
|
|
1510
|
+
existing = load_owned_object(metadata, artifact="agent invocation metadata")
|
|
1511
|
+
except JsonBoundaryError as exc:
|
|
1499
1512
|
return f"existing metadata is unreadable: {exc}"
|
|
1500
1513
|
composed = materialized.metadata
|
|
1501
1514
|
if not isinstance(existing, dict):
|
|
@@ -2240,8 +2253,8 @@ def _load_json_object(path: Path, label: str) -> dict[str, object]:
|
|
|
2240
2253
|
|
|
2241
2254
|
def _read_json_if_object(path: Path) -> dict[str, object] | None:
|
|
2242
2255
|
try:
|
|
2243
|
-
value =
|
|
2244
|
-
except
|
|
2256
|
+
value = load_owned_object(path, artifact="agent invocation metadata")
|
|
2257
|
+
except JsonBoundaryError:
|
|
2245
2258
|
return None
|
|
2246
2259
|
return value if isinstance(value, dict) else None
|
|
2247
2260
|
|