okstra 0.167.0 → 0.169.0
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/README.md +6 -5
- package/docs/architecture/storage-model.md +57 -1
- package/docs/architecture.md +70 -2
- package/docs/cli.md +8 -4
- package/docs/for-ai/skills/okstra-code-review.md +3 -2
- package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
- package/docs/pr-template-usage.md +10 -6
- package/docs/project-structure-overview.md +14 -11
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -5
- package/runtime/agents/workers/report-writer-worker.md +9 -4
- package/runtime/agents/workers/translator-worker.md +6 -4
- package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
- package/runtime/prompts/coding-preflight/clean-code.md +13 -0
- package/runtime/prompts/duties/acceptance-critic.md +24 -0
- package/runtime/prompts/duties/acceptance-verifier.md +24 -0
- package/runtime/prompts/duties/analysis-worker.md +24 -0
- package/runtime/prompts/duties/code-reviewer.md +24 -0
- package/runtime/prompts/duties/common.md +35 -0
- package/runtime/prompts/duties/implementation-executor.md +24 -0
- package/runtime/prompts/duties/implementation-verifier.md +24 -0
- package/runtime/prompts/duties/lead.md +24 -0
- package/runtime/prompts/duties/report-writer.md +24 -0
- package/runtime/prompts/duties/reverification-worker.md +24 -0
- package/runtime/prompts/duties/schedule-verifier.md +24 -0
- package/runtime/prompts/duties/scope-critic.md +24 -0
- package/runtime/prompts/duties/translator.md +24 -0
- package/runtime/prompts/lead/convergence.md +51 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
- package/runtime/prompts/lead/plan-body-verification.md +16 -1
- package/runtime/prompts/lead/report-writer.md +20 -5
- package/runtime/prompts/lead/team-contract.md +13 -13
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
- package/runtime/prompts/profiles/implementation.md +4 -2
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
- package/runtime/python/okstra_ctl/context_cost.py +46 -5
- package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
- package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
- package/runtime/python/okstra_ctl/doctor.py +150 -16
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
- package/runtime/python/okstra_ctl/path_hints.py +26 -0
- package/runtime/python/okstra_ctl/paths.py +20 -0
- package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
- package/runtime/python/okstra_ctl/ports/host.py +3 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
- package/runtime/python/okstra_ctl/pr_template.py +3 -6
- package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
- package/runtime/python/okstra_ctl/render.py +217 -12
- package/runtime/python/okstra_ctl/report_finalize.py +44 -0
- package/runtime/python/okstra_ctl/run.py +368 -51
- package/runtime/python/okstra_ctl/session.py +16 -12
- package/runtime/python/okstra_ctl/team.py +11 -11
- package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
- package/runtime/skills/okstra-code-review/SKILL.md +22 -3
- package/runtime/skills/okstra-run/SKILL.md +16 -1
- package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
- package/runtime/templates/implementation-worker-preamble.md +0 -10
- package/runtime/templates/report-writer-prompt-preamble.md +0 -9
- package/runtime/templates/reports/settings.template.json +0 -11
- package/runtime/templates/worker-prompt-preamble.md +0 -10
- package/runtime/validators/lib/fixtures.sh +93 -0
- package/runtime/validators/lib/validate-assets.sh +0 -8
- package/runtime/validators/validate-run.py +182 -0
- package/src/cli-registry.mjs +14 -0
- package/src/commands/execute/agent-prompt.mjs +25 -0
- package/src/commands/execute/codex-dispatch.mjs +6 -63
- package/src/commands/execute/worker-dispatch.mjs +76 -0
- package/src/commands/lifecycle/doctor.mjs +18 -3
- package/src/commands/lifecycle/install.mjs +33 -15
- package/src/commands/lifecycle/uninstall.mjs +4 -3
- package/src/lib/install-assets.mjs +9 -0
- package/runtime/agents/workers/antigravity-worker.md +0 -259
- package/runtime/agents/workers/codex-worker.md +0 -259
- package/runtime/agents/workers/grok-worker.md +0 -259
- package/runtime/agents/workers/kimi-worker.md +0 -259
- package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
- package/runtime/templates/operating-standard.md +0 -22
- package/src/lib/worker-agent-render.mjs +0 -50
- /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
"""Materialize and verify one auditable LLM invocation specification."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from pathlib import PurePosixPath
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
from typing import Any, Mapping, get_args
|
|
14
|
+
|
|
15
|
+
from .agent_invocation import (
|
|
16
|
+
AgentInstruction,
|
|
17
|
+
AgentInstructionSource,
|
|
18
|
+
AgentAudience,
|
|
19
|
+
AgentInvocationError,
|
|
20
|
+
AgentInvocationRequest,
|
|
21
|
+
AgentModelAssignment,
|
|
22
|
+
PreparedAgentInvocation,
|
|
23
|
+
agent_model_assignment_from_payload,
|
|
24
|
+
digest_duty_catalog,
|
|
25
|
+
materialize_standalone_result,
|
|
26
|
+
prepare_agent_invocation,
|
|
27
|
+
publish_standalone_completion,
|
|
28
|
+
verify_standalone_completion,
|
|
29
|
+
verify_agent_invocation,
|
|
30
|
+
)
|
|
31
|
+
from .application.resolve_assignment import resolve_assignment_runner
|
|
32
|
+
from .model_discovery import normalize_execution_for_dispatch
|
|
33
|
+
from .models import provider_default_model, resolve_model_metadata
|
|
34
|
+
from .path_hints import hydrate_active_run_context
|
|
35
|
+
from .ports.host_model import HostModelBindingRequest
|
|
36
|
+
from .registry.host_registry import default_host_registry
|
|
37
|
+
from .registry.provider_registry import default_provider_registry
|
|
38
|
+
from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
|
|
39
|
+
from .worker_prompt_policy import resolve_prompt_plan_for_manifest
|
|
40
|
+
from .dispatch_state import (
|
|
41
|
+
DispatchError,
|
|
42
|
+
link_agent_dispatch_result,
|
|
43
|
+
record_verified_agent_dispatch,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_AUDIENCES = frozenset(get_args(AgentAudience))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AgentPromptCliError(RuntimeError):
|
|
51
|
+
"""Raised when CLI input cannot become a valid invocation request."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parser() -> argparse.ArgumentParser:
|
|
55
|
+
parser = argparse.ArgumentParser(prog="okstra agent-prompt")
|
|
56
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
57
|
+
materialize = commands.add_parser("materialize")
|
|
58
|
+
_common_paths(materialize)
|
|
59
|
+
materialize.add_argument("--invocation-id", required=True)
|
|
60
|
+
materialize.add_argument("--audience", required=True)
|
|
61
|
+
materialize.add_argument("--instruction", required=True)
|
|
62
|
+
materialize.add_argument("--prompt", required=True)
|
|
63
|
+
materialize.add_argument("--run-manifest")
|
|
64
|
+
materialize.add_argument("--worker-id")
|
|
65
|
+
materialize.add_argument("--dispatch-kind")
|
|
66
|
+
materialize.add_argument("--assignment-ref")
|
|
67
|
+
materialize.add_argument("--result")
|
|
68
|
+
materialize.add_argument("--audit-source")
|
|
69
|
+
materialize.add_argument("--host-runtime")
|
|
70
|
+
materialize.add_argument("--provider")
|
|
71
|
+
materialize.add_argument("--model-role")
|
|
72
|
+
materialize.add_argument("--model", default="")
|
|
73
|
+
materialize.add_argument("--purpose")
|
|
74
|
+
materialize.add_argument("--json", action="store_true")
|
|
75
|
+
|
|
76
|
+
verify = commands.add_parser("verify")
|
|
77
|
+
_common_paths(verify)
|
|
78
|
+
verify.add_argument("--run-manifest")
|
|
79
|
+
verify.add_argument("--metadata", required=True)
|
|
80
|
+
verify.add_argument("--json", action="store_true")
|
|
81
|
+
|
|
82
|
+
materialize_result = commands.add_parser("materialize-result")
|
|
83
|
+
_standalone_completion_args(materialize_result)
|
|
84
|
+
materialize_result.add_argument("--returned-body-file", required=True)
|
|
85
|
+
|
|
86
|
+
complete = commands.add_parser("complete")
|
|
87
|
+
_standalone_completion_args(complete)
|
|
88
|
+
|
|
89
|
+
verify_completion = commands.add_parser("verify-completion")
|
|
90
|
+
_common_paths(verify_completion)
|
|
91
|
+
verify_completion.add_argument("--purpose", required=True)
|
|
92
|
+
verify_completion.add_argument("--completion", required=True)
|
|
93
|
+
verify_completion.add_argument("--json", action="store_true")
|
|
94
|
+
|
|
95
|
+
record_dispatch = commands.add_parser("record-dispatch")
|
|
96
|
+
_common_paths(record_dispatch)
|
|
97
|
+
record_dispatch.add_argument("--run-manifest", required=True)
|
|
98
|
+
record_dispatch.add_argument("--metadata", required=True)
|
|
99
|
+
record_dispatch.add_argument(
|
|
100
|
+
"--enforcement-mode",
|
|
101
|
+
required=True,
|
|
102
|
+
choices=("core-pre-dispatch", "host-native-spec-link-gate"),
|
|
103
|
+
)
|
|
104
|
+
record_dispatch.add_argument("--json", action="store_true")
|
|
105
|
+
|
|
106
|
+
link_result = commands.add_parser("link-result")
|
|
107
|
+
_common_paths(link_result)
|
|
108
|
+
link_result.add_argument("--run-manifest", required=True)
|
|
109
|
+
link_result.add_argument("--dispatch-id", required=True)
|
|
110
|
+
link_result.add_argument("--result", required=True)
|
|
111
|
+
link_result.add_argument("--json", action="store_true")
|
|
112
|
+
return parser
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _common_paths(parser: argparse.ArgumentParser) -> None:
|
|
116
|
+
parser.add_argument("--project-root", required=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _standalone_completion_args(parser: argparse.ArgumentParser) -> None:
|
|
120
|
+
_common_paths(parser)
|
|
121
|
+
parser.add_argument("--purpose", required=True)
|
|
122
|
+
parser.add_argument("--metadata", required=True)
|
|
123
|
+
parser.add_argument("--json", action="store_true")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(argv: list[str] | None = None) -> int:
|
|
127
|
+
try:
|
|
128
|
+
args = _parser().parse_args(argv)
|
|
129
|
+
if args.command == "materialize":
|
|
130
|
+
prepared = _materialize(args)
|
|
131
|
+
_emit(_prepared_payload(prepared, args.audience), args.json)
|
|
132
|
+
return 0
|
|
133
|
+
if args.command == "verify":
|
|
134
|
+
_verify(args)
|
|
135
|
+
_emit(
|
|
136
|
+
{"ok": True, "metadataPath": str(Path(args.metadata).resolve())},
|
|
137
|
+
args.json,
|
|
138
|
+
)
|
|
139
|
+
return 0
|
|
140
|
+
if args.command == "materialize-result":
|
|
141
|
+
_materialize_result(args)
|
|
142
|
+
return 0
|
|
143
|
+
if args.command == "complete":
|
|
144
|
+
_complete(args)
|
|
145
|
+
return 0
|
|
146
|
+
if args.command == "verify-completion":
|
|
147
|
+
_verify_completion(args)
|
|
148
|
+
return 0
|
|
149
|
+
if args.command == "record-dispatch":
|
|
150
|
+
_record_dispatch(args)
|
|
151
|
+
return 0
|
|
152
|
+
_link_result(args)
|
|
153
|
+
return 0
|
|
154
|
+
except (
|
|
155
|
+
AgentPromptCliError,
|
|
156
|
+
AgentInvocationError,
|
|
157
|
+
DispatchError,
|
|
158
|
+
WorkerPromptHeaderError,
|
|
159
|
+
OSError,
|
|
160
|
+
ValueError,
|
|
161
|
+
) as exc:
|
|
162
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
163
|
+
return 2
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _materialize(args: argparse.Namespace) -> PreparedAgentInvocation:
|
|
167
|
+
project_root = _project_root(args.project_root)
|
|
168
|
+
if args.run_manifest:
|
|
169
|
+
return _materialize_run(args, project_root)
|
|
170
|
+
return _materialize_standalone(args, project_root)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _record_dispatch(args: argparse.Namespace) -> None:
|
|
174
|
+
project_root = _project_root(args.project_root)
|
|
175
|
+
record = record_verified_agent_dispatch(
|
|
176
|
+
project_root=project_root,
|
|
177
|
+
run_manifest_path=_project_input(
|
|
178
|
+
project_root, args.run_manifest, "run manifest"
|
|
179
|
+
),
|
|
180
|
+
metadata_path=_project_input(
|
|
181
|
+
project_root, args.metadata, "agent invocation metadata"
|
|
182
|
+
),
|
|
183
|
+
enforcement_mode=args.enforcement_mode,
|
|
184
|
+
)
|
|
185
|
+
_emit(record, args.json)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _link_result(args: argparse.Namespace) -> None:
|
|
189
|
+
project_root = _project_root(args.project_root)
|
|
190
|
+
link = link_agent_dispatch_result(
|
|
191
|
+
project_root=project_root,
|
|
192
|
+
run_manifest_path=_project_input(
|
|
193
|
+
project_root, args.run_manifest, "run manifest"
|
|
194
|
+
),
|
|
195
|
+
dispatch_id=args.dispatch_id,
|
|
196
|
+
result_path=_project_input(project_root, args.result, "agent result"),
|
|
197
|
+
)
|
|
198
|
+
_emit(link, args.json)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _materialize_run(
|
|
202
|
+
args: argparse.Namespace,
|
|
203
|
+
project_root: Path,
|
|
204
|
+
) -> PreparedAgentInvocation:
|
|
205
|
+
required = {
|
|
206
|
+
"worker-id": args.worker_id,
|
|
207
|
+
"dispatch-kind": args.dispatch_kind,
|
|
208
|
+
"assignment-ref": args.assignment_ref,
|
|
209
|
+
"result": args.result,
|
|
210
|
+
}
|
|
211
|
+
missing = [name for name, value in required.items() if not value]
|
|
212
|
+
if missing:
|
|
213
|
+
raise AgentPromptCliError(
|
|
214
|
+
f"run materialization requires: {', '.join('--' + name for name in missing)}"
|
|
215
|
+
)
|
|
216
|
+
forbidden = {
|
|
217
|
+
"host-runtime": args.host_runtime,
|
|
218
|
+
"provider": args.provider,
|
|
219
|
+
"model-role": args.model_role,
|
|
220
|
+
"model": args.model,
|
|
221
|
+
"purpose": args.purpose,
|
|
222
|
+
}
|
|
223
|
+
present = [name for name, value in forbidden.items() if value]
|
|
224
|
+
if present:
|
|
225
|
+
raise AgentPromptCliError(
|
|
226
|
+
f"run materialization forbids: {', '.join('--' + name for name in present)}"
|
|
227
|
+
)
|
|
228
|
+
manifest_path = _project_input(project_root, args.run_manifest, "run manifest")
|
|
229
|
+
manifest = _read_json_object(manifest_path, "run manifest")
|
|
230
|
+
contract = _mapping(manifest.get("agentContract"), "run agent contract")
|
|
231
|
+
authorized = _mapping(contract.get("authorizedPaths"), "authorized paths")
|
|
232
|
+
|
|
233
|
+
instruction_path = _authorized_path(
|
|
234
|
+
project_root,
|
|
235
|
+
args.instruction,
|
|
236
|
+
authorized.get("instructionRoots"),
|
|
237
|
+
"instruction",
|
|
238
|
+
must_exist=True,
|
|
239
|
+
)
|
|
240
|
+
prompt_path = _authorized_path(
|
|
241
|
+
project_root,
|
|
242
|
+
args.prompt,
|
|
243
|
+
authorized.get("promptRoots"),
|
|
244
|
+
"prompt",
|
|
245
|
+
must_exist=False,
|
|
246
|
+
)
|
|
247
|
+
result_path = _authorized_path(
|
|
248
|
+
project_root,
|
|
249
|
+
args.result,
|
|
250
|
+
authorized.get("resultRoots"),
|
|
251
|
+
"result",
|
|
252
|
+
must_exist=False,
|
|
253
|
+
)
|
|
254
|
+
audit_source_path = (
|
|
255
|
+
_authorized_path(
|
|
256
|
+
project_root,
|
|
257
|
+
args.audit_source,
|
|
258
|
+
authorized.get("resultRoots"),
|
|
259
|
+
"audit source",
|
|
260
|
+
must_exist=False,
|
|
261
|
+
)
|
|
262
|
+
if args.audit_source
|
|
263
|
+
else result_path
|
|
264
|
+
)
|
|
265
|
+
_validate_run_identity(
|
|
266
|
+
manifest,
|
|
267
|
+
worker_id=args.worker_id,
|
|
268
|
+
dispatch_kind=args.dispatch_kind,
|
|
269
|
+
assignment_ref=args.assignment_ref,
|
|
270
|
+
audience=args.audience,
|
|
271
|
+
)
|
|
272
|
+
assignments = _mapping(
|
|
273
|
+
manifest.get("invocationAssignments"),
|
|
274
|
+
"run invocation assignments",
|
|
275
|
+
)
|
|
276
|
+
if args.assignment_ref not in assignments:
|
|
277
|
+
raise AgentPromptCliError(
|
|
278
|
+
f"assignment reference is missing from run manifest: {args.assignment_ref}"
|
|
279
|
+
)
|
|
280
|
+
assignment = agent_model_assignment_from_payload(assignments[args.assignment_ref])
|
|
281
|
+
duty_root = _project_manifest_path(
|
|
282
|
+
project_root,
|
|
283
|
+
contract.get("dutyRootPath"),
|
|
284
|
+
"duty root",
|
|
285
|
+
must_exist=True,
|
|
286
|
+
)
|
|
287
|
+
if contract.get("catalogDigest") != digest_duty_catalog(duty_root):
|
|
288
|
+
raise AgentPromptCliError("run duty snapshot catalog digest does not match")
|
|
289
|
+
active_context_path = _project_manifest_path(
|
|
290
|
+
project_root,
|
|
291
|
+
manifest.get("activeRunContextPath"),
|
|
292
|
+
"active run context",
|
|
293
|
+
must_exist=True,
|
|
294
|
+
)
|
|
295
|
+
active_context = hydrate_active_run_context(
|
|
296
|
+
_read_json_object(active_context_path, "active run context")
|
|
297
|
+
)
|
|
298
|
+
prompt_rel = _relative(project_root, prompt_path)
|
|
299
|
+
result_rel = _relative(project_root, result_path)
|
|
300
|
+
audit_rel = _relative(project_root, audit_source_path)
|
|
301
|
+
anchor_lines = tuple(worker_prompt_headers(
|
|
302
|
+
project_root=project_root,
|
|
303
|
+
prompt_rel=prompt_rel,
|
|
304
|
+
result_rel=result_rel,
|
|
305
|
+
audit_source_rel=audit_rel,
|
|
306
|
+
worker_id=args.worker_id,
|
|
307
|
+
dispatch_kind=args.dispatch_kind,
|
|
308
|
+
manifest=manifest,
|
|
309
|
+
active_context=active_context,
|
|
310
|
+
))
|
|
311
|
+
return prepare_agent_invocation(AgentInvocationRequest(
|
|
312
|
+
invocation_id=args.invocation_id,
|
|
313
|
+
worker_id=args.worker_id,
|
|
314
|
+
audience=args.audience,
|
|
315
|
+
assignment_ref=args.assignment_ref,
|
|
316
|
+
purpose=None,
|
|
317
|
+
assignment=assignment,
|
|
318
|
+
instruction=AgentInstruction(
|
|
319
|
+
anchor_lines=anchor_lines,
|
|
320
|
+
body=instruction_path.read_text(encoding="utf-8"),
|
|
321
|
+
source_paths=(AgentInstructionSource(
|
|
322
|
+
kind="project",
|
|
323
|
+
path=_relative(project_root, instruction_path),
|
|
324
|
+
),),
|
|
325
|
+
),
|
|
326
|
+
project_root=project_root,
|
|
327
|
+
run_manifest_path=manifest_path,
|
|
328
|
+
duty_root=duty_root,
|
|
329
|
+
prompt_path=prompt_path,
|
|
330
|
+
metadata_path=prompt_path.with_name(prompt_path.name + ".meta.json"),
|
|
331
|
+
dispatch_kind=args.dispatch_kind,
|
|
332
|
+
))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _validate_run_identity(
|
|
336
|
+
manifest: Mapping[str, Any],
|
|
337
|
+
*,
|
|
338
|
+
worker_id: str,
|
|
339
|
+
dispatch_kind: str,
|
|
340
|
+
assignment_ref: str,
|
|
341
|
+
audience: str,
|
|
342
|
+
) -> None:
|
|
343
|
+
expected: str
|
|
344
|
+
if assignment_ref == "lead":
|
|
345
|
+
expected = "lead"
|
|
346
|
+
if worker_id != "lead":
|
|
347
|
+
raise AgentPromptCliError("lead assignment requires worker ID 'lead'")
|
|
348
|
+
elif assignment_ref == "translator":
|
|
349
|
+
expected = "translator"
|
|
350
|
+
if worker_id != "translator":
|
|
351
|
+
raise AgentPromptCliError("translator assignment requires worker ID 'translator'")
|
|
352
|
+
elif assignment_ref.startswith("critic/"):
|
|
353
|
+
scope = assignment_ref.split("/", 1)[1]
|
|
354
|
+
expected = {"scope": "scope-critic", "acceptance": "acceptance-critic"}.get(
|
|
355
|
+
scope,
|
|
356
|
+
"",
|
|
357
|
+
)
|
|
358
|
+
if not expected or dispatch_kind != "critic":
|
|
359
|
+
raise AgentPromptCliError("critic assignment identity is invalid")
|
|
360
|
+
convergence = manifest.get("convergence")
|
|
361
|
+
critic = convergence.get("critic") if isinstance(convergence, Mapping) else None
|
|
362
|
+
provider = critic.get("provider") if isinstance(critic, Mapping) else None
|
|
363
|
+
if provider and provider != worker_id:
|
|
364
|
+
raise AgentPromptCliError("critic worker does not match manifest provider")
|
|
365
|
+
else:
|
|
366
|
+
try:
|
|
367
|
+
expected = resolve_prompt_plan_for_manifest(
|
|
368
|
+
manifest=manifest,
|
|
369
|
+
worker_id=worker_id,
|
|
370
|
+
dispatch_kind=dispatch_kind,
|
|
371
|
+
).duty_audience
|
|
372
|
+
except ValueError as exc:
|
|
373
|
+
raise AgentPromptCliError(str(exc)) from exc
|
|
374
|
+
if audience != expected:
|
|
375
|
+
raise AgentPromptCliError(
|
|
376
|
+
f"audience {audience!r} does not match required audience {expected!r}"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _materialize_standalone(
|
|
381
|
+
args: argparse.Namespace,
|
|
382
|
+
project_root: Path,
|
|
383
|
+
) -> PreparedAgentInvocation:
|
|
384
|
+
required = {
|
|
385
|
+
"host-runtime": args.host_runtime,
|
|
386
|
+
"provider": args.provider,
|
|
387
|
+
"model-role": args.model_role,
|
|
388
|
+
"purpose": args.purpose,
|
|
389
|
+
}
|
|
390
|
+
missing = [name for name, value in required.items() if not value]
|
|
391
|
+
if missing:
|
|
392
|
+
raise AgentPromptCliError(
|
|
393
|
+
"standalone materialization requires: "
|
|
394
|
+
+ ", ".join("--" + name for name in missing)
|
|
395
|
+
)
|
|
396
|
+
forbidden = {
|
|
397
|
+
"worker-id": args.worker_id,
|
|
398
|
+
"dispatch-kind": args.dispatch_kind,
|
|
399
|
+
"assignment-ref": args.assignment_ref,
|
|
400
|
+
"result": args.result,
|
|
401
|
+
"audit-source": args.audit_source,
|
|
402
|
+
}
|
|
403
|
+
present = [name for name, value in forbidden.items() if value]
|
|
404
|
+
if present:
|
|
405
|
+
raise AgentPromptCliError(
|
|
406
|
+
"standalone materialization forbids: "
|
|
407
|
+
+ ", ".join("--" + name for name in present)
|
|
408
|
+
)
|
|
409
|
+
if not _is_slug(args.purpose) or not _is_slug(args.invocation_id):
|
|
410
|
+
raise AgentPromptCliError("standalone purpose and invocation ID must be slugs")
|
|
411
|
+
if args.audience not in _AUDIENCES:
|
|
412
|
+
raise AgentPromptCliError(f"unknown duty audience: {args.audience}")
|
|
413
|
+
root = project_root / ".okstra" / "agent-invocations" / args.purpose
|
|
414
|
+
if not root.is_dir():
|
|
415
|
+
raise AgentPromptCliError(f"standalone invocation root not found: {root}")
|
|
416
|
+
resolved_root = root.resolve(strict=True)
|
|
417
|
+
if not _is_relative_to(resolved_root, project_root):
|
|
418
|
+
raise AgentPromptCliError("standalone invocation root escapes project root")
|
|
419
|
+
root = resolved_root
|
|
420
|
+
expected_instruction = root / f"{args.invocation_id}.instructions.md"
|
|
421
|
+
expected_prompt = root / f"{args.invocation_id}.prompt.md"
|
|
422
|
+
instruction_path = _exact_path(args.instruction, expected_instruction, True, "instruction")
|
|
423
|
+
prompt_path = _exact_path(args.prompt, expected_prompt, False, "prompt")
|
|
424
|
+
assignment = _standalone_assignment(
|
|
425
|
+
host_runtime=args.host_runtime,
|
|
426
|
+
provider=args.provider,
|
|
427
|
+
model_role=args.model_role,
|
|
428
|
+
model=args.model,
|
|
429
|
+
)
|
|
430
|
+
duty_root = root / f"{args.invocation_id}.duty-contracts"
|
|
431
|
+
_snapshot_standalone_duties(duty_root)
|
|
432
|
+
return prepare_agent_invocation(AgentInvocationRequest(
|
|
433
|
+
invocation_id=args.invocation_id,
|
|
434
|
+
worker_id=None,
|
|
435
|
+
audience=args.audience,
|
|
436
|
+
assignment_ref=None,
|
|
437
|
+
purpose=args.purpose,
|
|
438
|
+
assignment=assignment,
|
|
439
|
+
instruction=AgentInstruction(
|
|
440
|
+
anchor_lines=(),
|
|
441
|
+
body=instruction_path.read_text(encoding="utf-8"),
|
|
442
|
+
source_paths=(AgentInstructionSource(
|
|
443
|
+
kind="project",
|
|
444
|
+
path=_relative(project_root, instruction_path),
|
|
445
|
+
),),
|
|
446
|
+
),
|
|
447
|
+
project_root=project_root,
|
|
448
|
+
run_manifest_path=None,
|
|
449
|
+
duty_root=duty_root,
|
|
450
|
+
prompt_path=prompt_path,
|
|
451
|
+
metadata_path=prompt_path.with_name(prompt_path.name + ".meta.json"),
|
|
452
|
+
dispatch_kind="standalone",
|
|
453
|
+
))
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _standalone_assignment(
|
|
457
|
+
*, host_runtime: str, provider: str, model_role: str, model: str,
|
|
458
|
+
) -> AgentModelAssignment:
|
|
459
|
+
provider_registry = default_provider_registry()
|
|
460
|
+
provider_spec = provider_registry.resolve(provider)
|
|
461
|
+
default_model = provider_default_model(provider, model_role)
|
|
462
|
+
if not default_model:
|
|
463
|
+
raise AgentPromptCliError(
|
|
464
|
+
f"provider {provider!r} has no default model for role {model_role!r}"
|
|
465
|
+
)
|
|
466
|
+
metadata = resolve_model_metadata(
|
|
467
|
+
provider=provider,
|
|
468
|
+
raw_value=model,
|
|
469
|
+
default_display=default_model,
|
|
470
|
+
default_execution=default_model,
|
|
471
|
+
)
|
|
472
|
+
host_adapter = default_host_registry(provider_registry).resolve(host_runtime)
|
|
473
|
+
runner = resolve_assignment_runner(
|
|
474
|
+
host=host_adapter.descriptor,
|
|
475
|
+
provider=provider_spec,
|
|
476
|
+
role=model_role,
|
|
477
|
+
).runner
|
|
478
|
+
execution = (
|
|
479
|
+
normalize_execution_for_dispatch(
|
|
480
|
+
provider=provider,
|
|
481
|
+
execution=metadata.execution,
|
|
482
|
+
role=model_role,
|
|
483
|
+
)
|
|
484
|
+
if runner == "cli-wrapper"
|
|
485
|
+
else metadata.execution
|
|
486
|
+
)
|
|
487
|
+
host_model = host_adapter.host_model().resolve(HostModelBindingRequest(
|
|
488
|
+
host_runtime=host_runtime,
|
|
489
|
+
provider=provider,
|
|
490
|
+
model=metadata.display,
|
|
491
|
+
model_execution_value=execution,
|
|
492
|
+
runner=runner,
|
|
493
|
+
))
|
|
494
|
+
return AgentModelAssignment(
|
|
495
|
+
provider=provider,
|
|
496
|
+
model=metadata.display,
|
|
497
|
+
model_execution_value=execution,
|
|
498
|
+
runner=runner,
|
|
499
|
+
host_runtime=host_runtime,
|
|
500
|
+
host_model_value=host_model,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _snapshot_standalone_duties(destination: Path) -> None:
|
|
505
|
+
source = Path(__file__).resolve().parents[2] / "prompts" / "duties"
|
|
506
|
+
if not source.is_dir():
|
|
507
|
+
raise AgentPromptCliError(f"duty contract source not found: {source}")
|
|
508
|
+
if destination.exists():
|
|
509
|
+
if not destination.is_dir() or digest_duty_catalog(destination) != digest_duty_catalog(source):
|
|
510
|
+
raise AgentPromptCliError("standalone duty snapshot conflicts with existing files")
|
|
511
|
+
return
|
|
512
|
+
temporary = Path(tempfile.mkdtemp(prefix=f".{destination.name}.", dir=destination.parent))
|
|
513
|
+
try:
|
|
514
|
+
shutil.copytree(source, temporary, dirs_exist_ok=True)
|
|
515
|
+
try:
|
|
516
|
+
os.rename(temporary, destination)
|
|
517
|
+
except FileExistsError:
|
|
518
|
+
if digest_duty_catalog(destination) != digest_duty_catalog(source):
|
|
519
|
+
raise AgentPromptCliError("standalone duty snapshot publication conflict")
|
|
520
|
+
finally:
|
|
521
|
+
if temporary.exists():
|
|
522
|
+
shutil.rmtree(temporary)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _verify(args: argparse.Namespace) -> None:
|
|
526
|
+
project_root = _project_root(args.project_root)
|
|
527
|
+
metadata_path = _project_input(project_root, args.metadata, "metadata")
|
|
528
|
+
metadata = _read_json_object(metadata_path, "metadata")
|
|
529
|
+
contract_source = _mapping(metadata.get("contractSource"), "contract source")
|
|
530
|
+
mode = contract_source.get("mode")
|
|
531
|
+
expected_manifest: Path | None = None
|
|
532
|
+
expected_assignment: AgentModelAssignment | None = None
|
|
533
|
+
if mode == "run":
|
|
534
|
+
if not args.run_manifest:
|
|
535
|
+
raise AgentPromptCliError("run invocation verification requires --run-manifest")
|
|
536
|
+
expected_manifest = _project_input(project_root, args.run_manifest, "run manifest")
|
|
537
|
+
manifest = _read_json_object(expected_manifest, "run manifest")
|
|
538
|
+
assignments = _mapping(manifest.get("invocationAssignments"), "run assignments")
|
|
539
|
+
reference = metadata.get("assignmentRef")
|
|
540
|
+
if not isinstance(reference, str) or reference not in assignments:
|
|
541
|
+
raise AgentPromptCliError("assignment reference is missing from run manifest")
|
|
542
|
+
expected_assignment = agent_model_assignment_from_payload(assignments[reference])
|
|
543
|
+
elif mode == "standalone":
|
|
544
|
+
if args.run_manifest:
|
|
545
|
+
raise AgentPromptCliError("standalone invocation verification forbids --run-manifest")
|
|
546
|
+
else:
|
|
547
|
+
raise AgentPromptCliError("contract source mode is invalid")
|
|
548
|
+
errors = verify_agent_invocation(
|
|
549
|
+
metadata_path,
|
|
550
|
+
project_root=project_root,
|
|
551
|
+
expected_run_manifest_path=expected_manifest,
|
|
552
|
+
expected_assignment=expected_assignment,
|
|
553
|
+
)
|
|
554
|
+
if errors:
|
|
555
|
+
raise AgentPromptCliError("; ".join(errors))
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _materialize_result(args: argparse.Namespace) -> None:
|
|
559
|
+
project_root = _project_root(args.project_root)
|
|
560
|
+
metadata_path = _project_input(project_root, args.metadata, "metadata")
|
|
561
|
+
root = project_root / ".okstra" / "agent-invocations" / args.purpose
|
|
562
|
+
temporary_root = root / ".tmp"
|
|
563
|
+
returned_body_path = _project_input(
|
|
564
|
+
project_root,
|
|
565
|
+
args.returned_body_file,
|
|
566
|
+
"returned body",
|
|
567
|
+
)
|
|
568
|
+
if not temporary_root.is_dir() or not _is_relative_to(
|
|
569
|
+
returned_body_path,
|
|
570
|
+
temporary_root.resolve(strict=True),
|
|
571
|
+
):
|
|
572
|
+
raise AgentPromptCliError(
|
|
573
|
+
"returned body file must be inside the standalone purpose .tmp directory"
|
|
574
|
+
)
|
|
575
|
+
result = materialize_standalone_result(
|
|
576
|
+
project_root=project_root,
|
|
577
|
+
purpose=args.purpose,
|
|
578
|
+
metadata_path=metadata_path,
|
|
579
|
+
returned_body=returned_body_path.read_bytes(),
|
|
580
|
+
)
|
|
581
|
+
_emit({
|
|
582
|
+
"ok": True,
|
|
583
|
+
"purpose": result.purpose,
|
|
584
|
+
"invocationId": result.invocation_id,
|
|
585
|
+
"metadataPath": str(result.prompt_metadata_path),
|
|
586
|
+
"resultPath": str(result.result_path),
|
|
587
|
+
"resultEnvelopeDigest": result.result_envelope_digest,
|
|
588
|
+
}, args.json)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _complete(args: argparse.Namespace) -> None:
|
|
592
|
+
project_root = _project_root(args.project_root)
|
|
593
|
+
metadata_path = _project_input(project_root, args.metadata, "metadata")
|
|
594
|
+
completion = publish_standalone_completion(
|
|
595
|
+
project_root=project_root,
|
|
596
|
+
purpose=args.purpose,
|
|
597
|
+
metadata_path=metadata_path,
|
|
598
|
+
completed_at=datetime.now(timezone.utc),
|
|
599
|
+
)
|
|
600
|
+
_emit({
|
|
601
|
+
"ok": True,
|
|
602
|
+
"purpose": args.purpose,
|
|
603
|
+
"metadataPath": str(metadata_path),
|
|
604
|
+
"completionPath": str(completion),
|
|
605
|
+
}, args.json)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _verify_completion(args: argparse.Namespace) -> None:
|
|
609
|
+
project_root = _project_root(args.project_root)
|
|
610
|
+
completion_path = _project_input(
|
|
611
|
+
project_root,
|
|
612
|
+
args.completion,
|
|
613
|
+
"completion",
|
|
614
|
+
)
|
|
615
|
+
verified = verify_standalone_completion(
|
|
616
|
+
completion_path,
|
|
617
|
+
project_root=project_root,
|
|
618
|
+
expected_purpose=args.purpose,
|
|
619
|
+
)
|
|
620
|
+
_emit({
|
|
621
|
+
"ok": True,
|
|
622
|
+
"purpose": verified.purpose,
|
|
623
|
+
"invocationId": verified.invocation_id,
|
|
624
|
+
"metadataPath": str(verified.prompt_metadata_path),
|
|
625
|
+
"resultPath": str(verified.result_path),
|
|
626
|
+
"completionPath": str(verified.completion_path),
|
|
627
|
+
"resultEnvelopeDigest": verified.result_envelope_digest,
|
|
628
|
+
"returnedBody": verified.returned_body,
|
|
629
|
+
}, args.json)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _authorized_path(
|
|
633
|
+
project_root: Path,
|
|
634
|
+
raw_path: str,
|
|
635
|
+
raw_roots: object,
|
|
636
|
+
label: str,
|
|
637
|
+
*,
|
|
638
|
+
must_exist: bool,
|
|
639
|
+
) -> Path:
|
|
640
|
+
if not isinstance(raw_roots, list) or not raw_roots:
|
|
641
|
+
raise AgentPromptCliError(f"run manifest has no authorized {label} roots")
|
|
642
|
+
path = _candidate(project_root, raw_path, must_exist=must_exist, label=label)
|
|
643
|
+
roots = [
|
|
644
|
+
_project_manifest_path(project_root, value, f"{label} root", must_exist=True)
|
|
645
|
+
for value in raw_roots
|
|
646
|
+
]
|
|
647
|
+
if not any(_is_relative_to(path, root) for root in roots):
|
|
648
|
+
raise AgentPromptCliError(f"{label} path is outside authorized roots: {path}")
|
|
649
|
+
return path
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _candidate(project_root: Path, raw: str, *, must_exist: bool, label: str) -> Path:
|
|
653
|
+
candidate = Path(raw)
|
|
654
|
+
if not candidate.is_absolute():
|
|
655
|
+
candidate = project_root / candidate
|
|
656
|
+
try:
|
|
657
|
+
if must_exist or candidate.exists() or candidate.is_symlink():
|
|
658
|
+
return candidate.resolve(strict=True)
|
|
659
|
+
return candidate.parent.resolve(strict=True) / candidate.name
|
|
660
|
+
except OSError as exc:
|
|
661
|
+
raise AgentPromptCliError(f"{label} path is invalid: {candidate}") from exc
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _project_input(project_root: Path, raw: str, label: str) -> Path:
|
|
665
|
+
path = _candidate(project_root, raw, must_exist=True, label=label)
|
|
666
|
+
if not _is_relative_to(path, project_root):
|
|
667
|
+
raise AgentPromptCliError(f"{label} path escapes project root: {path}")
|
|
668
|
+
return path
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _project_manifest_path(
|
|
672
|
+
project_root: Path,
|
|
673
|
+
value: object,
|
|
674
|
+
label: str,
|
|
675
|
+
*,
|
|
676
|
+
must_exist: bool,
|
|
677
|
+
) -> Path:
|
|
678
|
+
if not isinstance(value, str) or not value:
|
|
679
|
+
raise AgentPromptCliError(f"{label} path is invalid")
|
|
680
|
+
pure = PurePosixPath(value)
|
|
681
|
+
if pure.is_absolute() or any(part in {"", ".", ".."} for part in pure.parts):
|
|
682
|
+
raise AgentPromptCliError(f"{label} path is invalid")
|
|
683
|
+
path = _candidate(project_root, value, must_exist=must_exist, label=label)
|
|
684
|
+
if not _is_relative_to(path, project_root):
|
|
685
|
+
raise AgentPromptCliError(f"{label} path escapes project root: {path}")
|
|
686
|
+
return path
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _exact_path(raw: str, expected: Path, must_exist: bool, label: str) -> Path:
|
|
690
|
+
actual = (
|
|
691
|
+
Path(raw).resolve(strict=True)
|
|
692
|
+
if must_exist
|
|
693
|
+
else Path(raw).parent.resolve(strict=True) / Path(raw).name
|
|
694
|
+
)
|
|
695
|
+
expected_actual = (
|
|
696
|
+
expected.resolve(strict=True)
|
|
697
|
+
if must_exist
|
|
698
|
+
else expected.parent.resolve(strict=True) / expected.name
|
|
699
|
+
)
|
|
700
|
+
if actual != expected_actual:
|
|
701
|
+
raise AgentPromptCliError(f"{label} does not use canonical standalone path")
|
|
702
|
+
return actual
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _project_root(value: str) -> Path:
|
|
706
|
+
try:
|
|
707
|
+
return Path(value).resolve(strict=True)
|
|
708
|
+
except OSError as exc:
|
|
709
|
+
raise AgentPromptCliError(f"project root not found: {value}") from exc
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _relative(project_root: Path, path: Path) -> str:
|
|
713
|
+
try:
|
|
714
|
+
return path.relative_to(project_root).as_posix()
|
|
715
|
+
except ValueError as exc:
|
|
716
|
+
raise AgentPromptCliError(f"path escapes project root: {path}") from exc
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _read_json_object(path: Path, label: str) -> dict[str, Any]:
|
|
720
|
+
try:
|
|
721
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
722
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
723
|
+
raise AgentPromptCliError(f"{label} is invalid: {path}") from exc
|
|
724
|
+
if not isinstance(value, dict):
|
|
725
|
+
raise AgentPromptCliError(f"{label} must be a JSON object: {path}")
|
|
726
|
+
return value
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def _mapping(value: object, label: str) -> Mapping[str, Any]:
|
|
730
|
+
if not isinstance(value, Mapping):
|
|
731
|
+
raise AgentPromptCliError(f"{label} is missing or invalid")
|
|
732
|
+
return value
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _is_relative_to(path: Path, root: Path) -> bool:
|
|
736
|
+
try:
|
|
737
|
+
path.relative_to(root)
|
|
738
|
+
except ValueError:
|
|
739
|
+
return False
|
|
740
|
+
return True
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _is_slug(value: str) -> bool:
|
|
744
|
+
if not value or value.startswith("-") or value.endswith("-"):
|
|
745
|
+
return False
|
|
746
|
+
return all(part.isalnum() and part.lower() == part for part in value.split("-"))
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _prepared_payload(
|
|
750
|
+
prepared: PreparedAgentInvocation,
|
|
751
|
+
audience: str,
|
|
752
|
+
) -> dict[str, object]:
|
|
753
|
+
assignment = prepared.assignment
|
|
754
|
+
return {
|
|
755
|
+
"ok": True,
|
|
756
|
+
"invocationId": prepared.invocation_id,
|
|
757
|
+
"workerId": prepared.worker_id,
|
|
758
|
+
"assignmentRef": prepared.assignment_ref,
|
|
759
|
+
"audience": audience,
|
|
760
|
+
"promptPath": str(prepared.prompt_path),
|
|
761
|
+
"metadataPath": str(prepared.metadata_path),
|
|
762
|
+
"modelAssignment": {
|
|
763
|
+
"provider": assignment.provider,
|
|
764
|
+
"model": assignment.model,
|
|
765
|
+
"modelExecutionValue": assignment.model_execution_value,
|
|
766
|
+
"runner": assignment.runner,
|
|
767
|
+
"hostRuntime": assignment.host_runtime,
|
|
768
|
+
"hostModelValue": assignment.host_model_value,
|
|
769
|
+
},
|
|
770
|
+
"digests": {
|
|
771
|
+
"catalogDigest": prepared.catalog_digest,
|
|
772
|
+
"assignmentDigest": prepared.assignment_digest,
|
|
773
|
+
"dutyDigest": prepared.duty_digest,
|
|
774
|
+
"instructionDigest": prepared.instruction_digest,
|
|
775
|
+
"promptDigest": prepared.prompt_digest,
|
|
776
|
+
},
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _emit(payload: Mapping[str, object], as_json: bool) -> None:
|
|
781
|
+
if as_json:
|
|
782
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
783
|
+
else:
|
|
784
|
+
print(payload.get("promptPath") or payload.get("metadataPath") or "ok")
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
if __name__ == "__main__":
|
|
788
|
+
raise SystemExit(main())
|