omnilane 0.34.0 → 0.42.1

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.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/CHANGELOG.md +71 -1
  4. package/README.ja.md +63 -33
  5. package/README.ko.md +63 -32
  6. package/README.md +147 -86
  7. package/README.zh-CN.md +61 -30
  8. package/README.zh-TW.md +124 -75
  9. package/VERSION +1 -1
  10. package/config/aa-model-policy.json +3046 -0
  11. package/docs/aa-model-coverage-2026-09-05.json +29204 -0
  12. package/docs/completion-wakeup.md +126 -0
  13. package/docs/model-capabilities-2026-09.md +380 -0
  14. package/docs/native-executor.md +264 -0
  15. package/docs/release-notes-0.42.1.md +32 -0
  16. package/hooks/routing-instruction.md +101 -40
  17. package/package.json +8 -2
  18. package/plugin.json +2 -2
  19. package/routing.local.yaml.example +8 -3
  20. package/routing.yaml +16 -16
  21. package/scripts/completion-wakeup.py +390 -0
  22. package/scripts/configure.sh +4 -4
  23. package/scripts/dispatch.sh +323 -32
  24. package/scripts/doctor.sh +55 -1
  25. package/scripts/jobs.sh +64 -17
  26. package/scripts/lib/aa_policy.py +473 -0
  27. package/scripts/lib/aa_retry.py +77 -0
  28. package/scripts/lib/common.sh +106 -1
  29. package/scripts/lib/job-worker.sh +314 -20
  30. package/scripts/lib/live-protocol.sh +147 -2
  31. package/scripts/lib/native.py +507 -0
  32. package/scripts/lib/normalize-claude-stream.py +72 -0
  33. package/scripts/lib/prepare-agy-mode.py +374 -0
  34. package/scripts/release-audit.sh +103 -0
  35. package/scripts/runners/run-claude.sh +81 -47
  36. package/scripts/runners/run-codex-live.py +462 -0
  37. package/scripts/runners/run-codex.sh +62 -3
  38. package/scripts/runners/run-gemini.sh +85 -10
  39. package/scripts/runners/run-grok-live.py +426 -0
  40. package/scripts/runners/run-grok.sh +117 -6
  41. package/scripts/runners/run-vote.sh +6 -3
  42. package/skills/omnilane/SKILL.md +217 -81
@@ -0,0 +1,507 @@
1
+ #!/usr/bin/env python3
2
+ """Caller-owned native handoff protocol. No provider discovery or agent spawning.
3
+
4
+ The shell resolves a routing row before invoking this module. Exit 10 from
5
+ route means auto chose CLI; all other errors fail closed. Native state is a
6
+ single atomically replaced JSON record, serialized by a per-job advisory lock.
7
+ """
8
+
9
+ import argparse
10
+ import datetime
11
+ import fcntl
12
+ import json
13
+ import os
14
+ from pathlib import Path
15
+ import re
16
+ import secrets
17
+ import stat
18
+ import sys
19
+
20
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
21
+ import aa_policy # noqa: E402
22
+
23
+
24
+ MAX_BYTES = 262144
25
+ JOB_ID = re.compile(r"[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+\Z")
26
+ IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}\Z")
27
+
28
+
29
+ def check(condition, message):
30
+ if not condition:
31
+ raise ValueError(message)
32
+
33
+
34
+ def text_value(value, label, limit=4096, multiline=False):
35
+ check(isinstance(value, str) and bool(value.strip()) and len(value) <= limit,
36
+ f"invalid {label}")
37
+ check(all(ord(c) >= 32 or (multiline and c in "\n\t") for c in value)
38
+ and "\x7f" not in value, f"control character in {label}")
39
+ return value
40
+
41
+
42
+ def identifier(value, label):
43
+ text_value(value, label, 256)
44
+ check(IDENTIFIER.fullmatch(value), f"invalid {label}")
45
+ return value
46
+
47
+
48
+ def fields(value, required, optional=()):
49
+ check(isinstance(value, dict), "expected JSON object")
50
+ check(set(required) <= value.keys() and value.keys() <= set(required) | set(optional),
51
+ "missing or unknown JSON fields")
52
+
53
+
54
+ def string_list(value, label, nonempty=True):
55
+ check(isinstance(value, list) and len(value) <= 128 and (value or not nonempty),
56
+ f"invalid {label}")
57
+ for item in value:
58
+ text_value(item, label)
59
+ check(len(set(value)) == len(value), f"duplicate {label}")
60
+ return value
61
+
62
+
63
+ def unique_object(pairs):
64
+ result = {}
65
+ for key, value in pairs:
66
+ check(key not in result, "duplicate JSON key")
67
+ result[key] = value
68
+ return result
69
+
70
+
71
+ def read_json(path):
72
+ # Never follow a submitted symlink (context, completion, or job state).
73
+ fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
74
+ with os.fdopen(fd, "r", encoding="utf-8") as stream:
75
+ info = os.fstat(stream.fileno())
76
+ check(stat.S_ISREG(info.st_mode) and info.st_size <= MAX_BYTES, "invalid JSON file")
77
+ raw = stream.read(MAX_BYTES + 1)
78
+ check(len(raw.encode("utf-8")) <= MAX_BYTES, "JSON file too large")
79
+ return json.loads(raw, object_pairs_hook=unique_object,
80
+ parse_constant=lambda _: (_ for _ in ()).throw(ValueError("invalid JSON number")))
81
+
82
+
83
+ def canonical_dir(value):
84
+ text_value(value, "workdir")
85
+ path = Path(value)
86
+ check(path.is_absolute() and path.is_dir(), "workdir must be an existing absolute directory")
87
+ return str(path.resolve(strict=True))
88
+
89
+
90
+ def capability_context(path):
91
+ ctx = read_json(path)
92
+ fields(ctx, ("schema_version", "harness", "vendor", "capabilities", "requirements"),
93
+ ("current_model", "current_effort", "agent_strategy", "existing_agent",
94
+ "preserve_existing_context", "new_agent_capacity"))
95
+ check(type(ctx["schema_version"]) is int and ctx["schema_version"] == 1, "unsupported context version")
96
+ identifier(ctx["harness"], "harness")
97
+ identifier(ctx["vendor"], "vendor")
98
+ if "current_model" in ctx:
99
+ text_value(ctx["current_model"], "current_model", 256)
100
+ if "current_effort" in ctx:
101
+ text_value(ctx["current_effort"], "current_effort", 256)
102
+ check(ctx.get("agent_strategy", "new") in ("new", "reuse"), "invalid agent strategy")
103
+ check(ctx.get("new_agent_capacity", "unknown") in ("available", "exhausted", "unknown"),
104
+ "invalid new agent capacity")
105
+ if "preserve_existing_context" in ctx:
106
+ check(type(ctx["preserve_existing_context"]) is bool, "invalid preserve_existing_context")
107
+ if "existing_agent" in ctx:
108
+ agent = ctx["existing_agent"]
109
+ fields(agent, ("agent_id", "vendor", "model", "effort", "harness", "state", "observed_by", "evidence"))
110
+ text_value(agent["agent_id"], "existing agent id", 256)
111
+ check(re.fullmatch(r"[A-Za-z0-9_/][A-Za-z0-9._:/-]{0,255}", agent["agent_id"]),
112
+ "invalid existing agent id")
113
+ for key in ("vendor", "model", "effort", "harness"):
114
+ text_value(agent[key], "existing agent " + key, 256)
115
+ check(agent["state"] in ("idle", "busy", "unknown"), "invalid existing agent state")
116
+ check(agent["observed_by"] == "caller", "existing agent must be caller-observed")
117
+ string_list(agent["evidence"], "existing agent evidence")
118
+ req = ctx["requirements"]
119
+ fields(req, ("tools", "isolation", "lifecycle"))
120
+ string_list(req["tools"], "required tools", nonempty=False)
121
+ identifier(req["isolation"], "isolation")
122
+ identifier(req["lifecycle"], "lifecycle")
123
+ caps = ctx["capabilities"]
124
+ check(isinstance(caps, list) and 0 < len(caps) <= 128, "invalid capabilities")
125
+ for cap in caps:
126
+ fields(cap, ("model", "efforts", "modes", "workdirs", "tools", "isolations", "lifecycles"),
127
+ ("agent_strategy", "existing_agent_id"))
128
+ check(cap.get("agent_strategy", "new") in ("new", "reuse"), "invalid capability strategy")
129
+ if "existing_agent_id" in cap:
130
+ text_value(cap["existing_agent_id"], "capability existing agent id", 256)
131
+ text_value(cap["model"], "model", 256)
132
+ for key in ("efforts", "modes", "workdirs", "isolations", "lifecycles"):
133
+ string_list(cap[key], key)
134
+ string_list(cap["tools"], "tools", nonempty=False)
135
+ cap["workdirs"] = [canonical_dir(d) for d in cap["workdirs"]]
136
+ return ctx
137
+
138
+
139
+ def choose(args, ctx):
140
+ """Exact match on one capability row, never unions across different rows."""
141
+ if args.executor == "cli":
142
+ return "cli", "forced-cli", args.model
143
+ if ctx is None:
144
+ return "cli", "no-native-context", args.model
145
+ if args.vendor in ("vote", "exec", "off"):
146
+ return "cli", "cli-only-routing-kind", args.model
147
+ if args.background or args.session != "auto" or args.thread:
148
+ return "cli", "cli-session-lifecycle", args.model
149
+ if args.job_timeout or args.idle_timeout:
150
+ return "cli", "cli-watchdog-required", args.model
151
+ if args.mode == "sysops":
152
+ return "cli", "sysops-requires-cli", args.model
153
+ if ctx["vendor"] != args.vendor:
154
+ return "cli", "vendor-mismatch", args.model
155
+ model = args.model
156
+ if model in ("", "-"):
157
+ model = ctx.get("current_model", "")
158
+ if not model:
159
+ return "cli", "unknown-current-model", args.model
160
+ caps = [cap for cap in ctx["capabilities"] if cap["model"] == model]
161
+ if not caps:
162
+ return "cli", "model-mismatch", model
163
+ if args.effort in ("", "-"):
164
+ return "cli", "unknown-effort", model
165
+ req = ctx["requirements"]
166
+ # collaboration.spawn_agent inherits the caller's tools and filesystem.
167
+ # advise/work express task intent; neither creates an OS sandbox here.
168
+ isolation = req["isolation"]
169
+ if isolation != "shared-inherited" or req["lifecycle"] != "single-shot":
170
+ return "cli", "unsupported-isolation-or-lifecycle", model
171
+ strategy = ctx.get("agent_strategy", "new")
172
+ if strategy == "reuse":
173
+ if ctx.get("preserve_existing_context") is not True:
174
+ return "cli", "reuse-preserve-context-required", model
175
+ agent = ctx.get("existing_agent")
176
+ if agent is None:
177
+ return "cli", "reuse-existing-agent-required", model
178
+ if agent["state"] != "idle":
179
+ return "cli", "reuse-agent-not-idle", model
180
+ if args.vendor != "codex":
181
+ return "cli", "reuse-backend-unsupported", model
182
+ if ctx.get("current_model") != model or ctx.get("current_effort") != args.effort:
183
+ return "cli", "reuse-current-runtime-mismatch", model
184
+ expected = {"vendor": args.vendor, "model": model, "effort": args.effort, "harness": ctx["harness"]}
185
+ if any(agent[key] != value for key, value in expected.items()):
186
+ return "cli", "reuse-agent-runtime-mismatch", model
187
+ caps = [cap for cap in caps if cap.get("agent_strategy", "new") == "reuse"
188
+ and cap.get("existing_agent_id") == agent["agent_id"]]
189
+ else:
190
+ if "existing_agent" in ctx or ctx.get("preserve_existing_context") is True:
191
+ return "cli", "reuse-strategy-required", model
192
+ if ctx.get("new_agent_capacity") == "exhausted":
193
+ return "cli", "new-agent-capacity-exhausted", model
194
+ caps = [cap for cap in caps if cap.get("agent_strategy", "new") == "new"
195
+ and "existing_agent_id" not in cap]
196
+ if not caps:
197
+ return "cli", "agent-strategy-capability-mismatch", model
198
+ checks = (
199
+ ("effort-mismatch", lambda c: args.effort in c["efforts"]),
200
+ ("mode-mismatch", lambda c: args.mode in c["modes"]),
201
+ ("workdir-mismatch", lambda c: args.workdir in c["workdirs"]),
202
+ ("tools-mismatch", lambda c: set(req["tools"]) <= set(c["tools"])),
203
+ ("isolation-mismatch", lambda c: isolation in c["isolations"]),
204
+ ("lifecycle-mismatch", lambda c: "single-shot" in c["lifecycles"]),
205
+ )
206
+ for reason, predicate in checks:
207
+ caps = [cap for cap in caps if predicate(cap)]
208
+ if not caps:
209
+ return "cli", reason, model
210
+ return "native", "exact-idle-reuse-match" if strategy == "reuse" else "exact-capability-match", model
211
+
212
+
213
+ def stamp():
214
+ return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
215
+
216
+
217
+ def write_new(path, content):
218
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
219
+ with os.fdopen(fd, "w", encoding="utf-8") as stream:
220
+ stream.write(content)
221
+ stream.flush()
222
+ os.fsync(stream.fileno())
223
+
224
+
225
+ def json_text(value):
226
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":")) + "\n"
227
+
228
+
229
+ def atomic_json(path, value):
230
+ content = json_text(value)
231
+ check(len(content.encode("utf-8")) <= MAX_BYTES, "public record too large")
232
+ temp = path.with_name("." + path.name + "." + secrets.token_hex(8))
233
+ write_new(temp, content)
234
+ try:
235
+ os.replace(temp, path)
236
+ finally:
237
+ if temp.exists():
238
+ temp.unlink()
239
+
240
+
241
+ def cleanup_staging_dir(path):
242
+ """Remove only files from a staging directory created by this process."""
243
+ try:
244
+ children = list(path.iterdir())
245
+ except FileNotFoundError:
246
+ return
247
+ for child in children:
248
+ # Never recurse or follow links during cleanup. A surprising entry is
249
+ # left hidden rather than risking deletion of unrelated state.
250
+ if child.is_symlink() or child.is_file():
251
+ child.unlink()
252
+ else:
253
+ return
254
+ path.rmdir()
255
+
256
+
257
+ def publish_job(root, job_id, plan, metadata, registry=None, caller=None, registry_source=None):
258
+ """Initialize privately, then publish one complete job directory."""
259
+ job = root / job_id
260
+ staging = root / (".native-stage-" + job_id + "-" + secrets.token_hex(8))
261
+ staging.mkdir(mode=0o700)
262
+ try:
263
+ if registry is not None:
264
+ aa_policy.publish_context(staging, registry, caller, plan["aa_policy"], registry_source)
265
+ write_new(staging / "native.lock", "")
266
+ write_new(staging / "task.txt", plan["task"])
267
+ write_new(staging / "meta.json", json_text(metadata))
268
+ write_new(staging / "native.json", json_text(plan))
269
+
270
+ publish_lock_path = root / ".native-publish.lock"
271
+ publish_fd = os.open(
272
+ publish_lock_path,
273
+ os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK,
274
+ 0o600,
275
+ )
276
+ with os.fdopen(publish_fd, "r+") as publish_lock:
277
+ check(stat.S_ISREG(os.fstat(publish_lock.fileno()).st_mode),
278
+ "invalid native publication lock")
279
+ fcntl.flock(publish_lock, fcntl.LOCK_EX)
280
+ check(not job.exists() and not job.is_symlink(), "job ID collision")
281
+ os.rename(staging, job)
282
+ finally:
283
+ cleanup_staging_dir(staging)
284
+
285
+
286
+ def jobs_root(home, create=False):
287
+ home = Path(home).absolute()
288
+ root = home / "jobs"
289
+ check(not home.is_symlink() and not root.is_symlink(), "unsafe jobs store")
290
+ if create:
291
+ home.mkdir(mode=0o700, parents=True, exist_ok=True)
292
+ root.mkdir(mode=0o700, exist_ok=True)
293
+ check(root.is_dir(), "missing jobs store")
294
+ return root
295
+
296
+
297
+ def route(args):
298
+ check(not (args.caller_context and args.operator_asserted_human),
299
+ "caller context and operator assertion are mutually exclusive")
300
+ ctx = capability_context(args.context) if args.context else None
301
+ args.workdir = canonical_dir(args.workdir)
302
+ registry, registry_sha = aa_policy.load_registry(
303
+ args.policy, args.expected_registry_sha256
304
+ )
305
+ caller = None
306
+ caller_sha = None
307
+ if args.caller_context:
308
+ caller, caller_sha = aa_policy.load_caller(
309
+ args.caller_context, registry, args.expected_caller_sha256
310
+ )
311
+ policy_decision = aa_policy.decide(
312
+ registry,
313
+ registry_sha,
314
+ vendor=args.vendor,
315
+ model=args.model,
316
+ effort=None if args.effort in ("", "-") else args.effort,
317
+ caller=caller,
318
+ caller_sha256=caller_sha,
319
+ operator_asserted_human=args.operator_asserted_human,
320
+ target_config=args.target_config,
321
+ )
322
+ if not policy_decision["allowed"]:
323
+ print(aa_policy._json_line(policy_decision), end="", file=sys.stderr)
324
+ return 3
325
+ executor, reason, model = choose(args, ctx)
326
+ if executor == "cli":
327
+ check(args.executor != "native", "native capability rejected: " + reason)
328
+ # Preserve an inherited exact model on CLI fallback too.
329
+ print(json_text({"reason": reason, "model": model}).strip())
330
+ return 10
331
+ plan = {"schema_version": 1, "executor": executor, "executor_reason": reason,
332
+ "vendor": args.vendor, "model": model, "effort": args.effort,
333
+ "harness": ctx["harness"], "lane": args.lane, "mode": args.mode,
334
+ "workdir": args.workdir, "task": args.task,
335
+ "requirements": ctx["requirements"], "timeout": args.timeout,
336
+ "worker_contract": {
337
+ "no_nested_dispatch": True,
338
+ "isolation": ctx["requirements"]["isolation"],
339
+ "mode_is_task_intent": True,
340
+ "caller_enforces_deadline": True,
341
+ },
342
+ "aa_policy": policy_decision,
343
+ "state": "planned" if args.dry_run else "pending",
344
+ "job_id": None, "task_id": None, "agent_id": None,
345
+ "provider_invoked": False, "job_state_created": False}
346
+ strategy = ctx.get("agent_strategy", "new")
347
+ plan.update(agent_strategy=strategy, existing_agent_id=None,
348
+ preserve_existing_context=strategy == "reuse",
349
+ new_agent_capacity=ctx.get("new_agent_capacity", "unknown"))
350
+ if strategy == "reuse":
351
+ agent = ctx["existing_agent"]
352
+ plan.update(existing_agent_id=agent["agent_id"], agent_id=agent["agent_id"],
353
+ reuse_observation={"state": agent["state"], "observed_by": "caller",
354
+ "evidence": agent["evidence"]})
355
+ plan["worker_contract"].update(backend="collaboration.followup_task",
356
+ preserve_existing_context=True,
357
+ caller_rechecks_idle_before_followup=True)
358
+ if args.dry_run:
359
+ print(json_text(plan), end="")
360
+ return 0
361
+ if args.task == "-":
362
+ plan["task"] = sys.stdin.read(MAX_BYTES + 1)
363
+ text_value(plan["task"], "task", 65536, multiline=True)
364
+ job_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + f"-{os.getpid()}-{secrets.randbelow(10**9)}"
365
+ plan.update(job_id=job_id, task_id=job_id, created=stamp(), job_state_created=True)
366
+ # Validate size before creating any job state.
367
+ check(len(json_text(plan).encode("utf-8")) <= MAX_BYTES, "handoff too large")
368
+ metadata = {k: plan[k] for k in ("lane", "vendor", "model", "effort", "mode", "workdir", "executor", "executor_reason")}
369
+ metadata.update(aa_policy_code=policy_decision["code"],
370
+ aa_effective_ceiling=policy_decision["effective_ceiling"])
371
+ metadata.update(started=plan["created"], session_mode="native")
372
+ check(len(json_text(metadata).encode("utf-8")) <= 4096, "metadata too large")
373
+ root = jobs_root(args.home, create=True)
374
+ if policy_decision.get("child_context") is not None:
375
+ plan["worker_contract"]["caller_context_path"] = str(root / job_id / "aa-child-context.json")
376
+ plan["worker_contract"]["caller_context"] = policy_decision["child_context"]
377
+ publish_job(root, job_id, plan, metadata, registry, caller, args.policy)
378
+ print(json_text(plan), end="")
379
+ return 0
380
+
381
+
382
+ def validate_completion(value, state):
383
+ fields(value, ("schema_version", "job_id", "agent_id", "runtime", "outcome", "result", "evidence"),
384
+ ("agent_strategy",))
385
+ check(type(value["schema_version"]) is int and value["schema_version"] == 1, "unsupported completion version")
386
+ check(value["job_id"] == state["job_id"], "completion job ID mismatch")
387
+ # Hosts may expose a canonical task name such as /root/reviewer as the
388
+ # agent identifier. It is public metadata only, never a path or a PID.
389
+ text_value(value["agent_id"], "agent_id", 256)
390
+ check(re.fullmatch(r"[A-Za-z0-9_/][A-Za-z0-9._:/-]{0,255}", value["agent_id"]), "invalid agent_id")
391
+ runtime = value["runtime"]
392
+ fields(runtime, ("vendor", "model", "effort", "harness", "backend"))
393
+ for key in ("vendor", "model", "effort", "harness", "backend"):
394
+ text_value(runtime[key], "runtime " + key, 256)
395
+ for key in ("vendor", "model", "effort", "harness"):
396
+ check(runtime[key] == state[key], "runtime " + key + " mismatch")
397
+ strategy = state.get("agent_strategy", "new")
398
+ if strategy == "reuse":
399
+ check(value.get("agent_strategy") == "reuse", "reuse completion strategy required")
400
+ check(value["agent_id"] == state["existing_agent_id"], "reuse agent ID mismatch")
401
+ check(runtime["backend"] == "collaboration.followup_task", "reuse backend mismatch")
402
+ else:
403
+ check(value.get("agent_strategy", "new") == "new", "new-agent completion strategy mismatch")
404
+ check(runtime["backend"] != "collaboration.followup_task", "reuse backend on new-agent job")
405
+ check(value["outcome"] in ("success", "failure"), "invalid outcome")
406
+ text_value(value["result"], "result", 65536, multiline=True)
407
+ string_list(value["evidence"], "evidence")
408
+ return value
409
+
410
+
411
+ def job_command(args):
412
+ check(JOB_ID.fullmatch(args.job_id), "invalid job ID")
413
+ job = jobs_root(args.home) / args.job_id
414
+ check(job.is_dir() and not job.is_symlink(), "invalid job directory")
415
+ lock_fd = os.open(job / "native.lock", os.O_RDWR | os.O_NOFOLLOW | os.O_NONBLOCK)
416
+ with os.fdopen(lock_fd, "r+") as lock:
417
+ check(stat.S_ISREG(os.fstat(lock.fileno()).st_mode), "invalid native lock")
418
+ fcntl.flock(lock, fcntl.LOCK_EX)
419
+ state = read_json(job / "native.json")
420
+ check(isinstance(state, dict) and state.get("job_id") == args.job_id
421
+ and state.get("executor") == "native"
422
+ and state.get("state") in ("pending", "completed", "cancelled"), "invalid native state")
423
+ if args.action in ("complete-native", "cancel"):
424
+ check(state["state"] == "pending", "native job is already terminal")
425
+ if args.action == "complete-native":
426
+ check(args.input is not None, "completion input file required")
427
+ value = validate_completion(read_json(args.input), state)
428
+ state.update(state="completed", agent_id=value["agent_id"], completion=value,
429
+ exit_code=0 if value["outcome"] == "success" else 1, finished=stamp())
430
+ else:
431
+ check(args.input is None, "unexpected cancel input")
432
+ # There is deliberately no PID or signal here. The caller owns
433
+ # any agent already spawned and must stop it with its own tool.
434
+ state.update(state="cancelled", exit_code=143, finished=stamp())
435
+ atomic_json(job / "native.json", state)
436
+ pending = state["state"] == "pending"
437
+ public_state = "pending" if pending else "cancelled" if state["state"] == "cancelled" else "done"
438
+ if args.action == "list-state":
439
+ print(public_state)
440
+ return 0
441
+ if args.action == "result":
442
+ check(not pending, "native job pending; ingest caller result first")
443
+ summary = {"id": args.job_id, "state": public_state, "native_state": state["state"],
444
+ "executor": "native", "executor_reason": state["executor_reason"],
445
+ "exit_code": state.get("exit_code"), "agent_id": state["agent_id"],
446
+ "vendor": state["vendor"], "model": state["model"], "effort": state["effort"],
447
+ "harness": state["harness"]}
448
+ summary.update(agent_strategy=state.get("agent_strategy", "new"),
449
+ existing_agent_id=state.get("existing_agent_id"),
450
+ preserve_existing_context=state.get("preserve_existing_context", False))
451
+ if args.action == "result":
452
+ summary["completion"] = state.get("completion")
453
+ if args.json:
454
+ print(json_text({"schema_version": 1, "command": args.action, "ok": True, "job": summary}), end="")
455
+ elif args.action == "result":
456
+ print(state.get("completion", {}).get("result", "cancelled by caller"))
457
+ else:
458
+ print(json_text(summary), end="")
459
+ return state.get("exit_code", 0) if args.action == "result" else 0
460
+
461
+
462
+ def main():
463
+ parser = argparse.ArgumentParser(description=__doc__)
464
+ sub = parser.add_subparsers(dest="command", required=True)
465
+ p = sub.add_parser("route")
466
+ for key in ("home", "lane", "vendor", "model", "effort", "workdir", "task"):
467
+ p.add_argument("--" + key, required=True)
468
+ p.add_argument("--executor", choices=("auto", "native", "cli"), required=True)
469
+ p.add_argument("--mode", choices=("advise", "work", "sysops"), required=True)
470
+ p.add_argument("--context")
471
+ p.add_argument("--policy", required=True)
472
+ p.add_argument("--caller-context")
473
+ p.add_argument("--operator-asserted-human", action="store_true")
474
+ p.add_argument("--expected-registry-sha256")
475
+ p.add_argument("--expected-caller-sha256")
476
+ p.add_argument("--target-config")
477
+ p.add_argument("--session", default="auto")
478
+ p.add_argument("--thread", default="")
479
+ p.add_argument("--timeout", type=int, default=600)
480
+ p.add_argument("--job-timeout", default="")
481
+ p.add_argument("--idle-timeout", default="")
482
+ p.add_argument("--background", action="store_true")
483
+ p.add_argument("--dry-run", action="store_true")
484
+ p = sub.add_parser("job")
485
+ p.add_argument("--home", required=True)
486
+ p.add_argument("--json", action="store_true")
487
+ p.add_argument("action", choices=("status", "result", "cancel", "complete-native", "list-state"))
488
+ p.add_argument("job_id")
489
+ p.add_argument("input", nargs="?")
490
+ args = parser.parse_args()
491
+ try:
492
+ return route(args) if args.command == "route" else job_command(args)
493
+ except (ValueError, OSError, RecursionError, TypeError, KeyError) as error:
494
+ # Avoid echoing submitted JSON, task text, result text, or capability data.
495
+ if isinstance(error, (OSError, UnicodeError, json.JSONDecodeError, RecursionError, TypeError, KeyError)):
496
+ message = "invalid or inaccessible native protocol input/state"
497
+ else:
498
+ message = str(error)
499
+ if getattr(args, "json", False):
500
+ print(json_text({"schema_version": 1, "command": args.action, "ok": False, "error": message}), end="")
501
+ else:
502
+ print("omnilane: " + message, file=sys.stderr)
503
+ return 2
504
+
505
+
506
+ if __name__ == "__main__":
507
+ sys.exit(main())
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """Normalize a successful Claude stream into Omnilane's result contract."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+
10
+
11
+ def main() -> int:
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument("events", type=Path)
14
+ parser.add_argument("output", type=Path)
15
+ args = parser.parse_args()
16
+
17
+ last_result: str | None = None
18
+ last_result_error = False
19
+ last_top_level_text: str | None = None
20
+ with args.events.open(encoding="utf-8") as stream:
21
+ for raw_line in stream:
22
+ try:
23
+ event = json.loads(raw_line)
24
+ except json.JSONDecodeError:
25
+ continue
26
+ if not isinstance(event, dict):
27
+ continue
28
+ if event.get("type") == "result" and isinstance(event.get("result"), str):
29
+ last_result = event["result"]
30
+ last_result_error = event.get("is_error") is True
31
+ continue
32
+ if event.get("type") != "assistant" or event.get("parent_tool_use_id"):
33
+ continue
34
+ message = event.get("message")
35
+ if not isinstance(message, dict):
36
+ continue
37
+ content = message.get("content")
38
+ if not isinstance(content, list):
39
+ continue
40
+ text_blocks = [
41
+ block["text"]
42
+ for block in content
43
+ if isinstance(block, dict)
44
+ and block.get("type") == "text"
45
+ and isinstance(block.get("text"), str)
46
+ ]
47
+ if text_blocks:
48
+ last_top_level_text = "\n".join(text_blocks)
49
+
50
+ if last_result is not None:
51
+ if last_result_error:
52
+ return 1
53
+ elif last_top_level_text is None:
54
+ return 1
55
+ else:
56
+ last_result = last_top_level_text
57
+ canonical = {
58
+ "type": "result",
59
+ "is_error": False,
60
+ "result": last_result,
61
+ "normalized_by": "omnilane",
62
+ }
63
+ with args.events.open("a", encoding="utf-8") as stream:
64
+ stream.write(json.dumps(canonical, ensure_ascii=False, separators=(",", ":")))
65
+ stream.write("\n")
66
+
67
+ args.output.write_text(last_result.rstrip("\n") + "\n", encoding="utf-8")
68
+ return 0
69
+
70
+
71
+ if __name__ == "__main__":
72
+ raise SystemExit(main())