Created
July 29, 2026 06:30
-
-
Save arikon/91476f67e6a3a30dcb2f6ceae8e70802 to your computer and use it in GitHub Desktop.
OMX #3354: realistic native Ultragoal receipt transport for Codex App (dev@73cb50c1..96bdd023)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/docs/codex-native-hooks.md b/docs/codex-native-hooks.md | |
| index 75f72bcc..3f95f503 100644 | |
| --- a/docs/codex-native-hooks.md | |
| +++ b/docs/codex-native-hooks.md | |
| @@ -120,8 +120,20 @@ while the Conductor boundary is active. | |
| Planning boundaries (`ralplan`, `deep-interview`) remain fail-closed for mutation | |
| transports: only their documented planning artifact paths and non-deactivating | |
| -state operations are allowed. No assignment-backed grant, prompt-derived scope, | |
| -or child-write allowance exists in this Option C implementation. | |
| +state operations are allowed. They do not accept an Ultragoal assignment-backed | |
| +grant, prompt-derived scope, or child-write allowance. | |
| + | |
| +Standalone Ultragoal on a native Codex surface outside tmux has three configured | |
| +backends: `off`, `host`, and `local-experimental`. `off` preserves the no-owner | |
| +activation block. `host` is the production-safe integration point, but it fails | |
| +closed because Codex does not currently expose a documented host-issued native | |
| +executor receipt API. `local-experimental` requires an ordered bootstrap: | |
| +typed executor spawn, root authorization through | |
| +`omx ultragoal native-bootstrap authorize`, then activation. The assignment is | |
| +a short-lived same-user guardrail, not host authentication or an ADR 3212 | |
| +hostile-child boundary. See | |
| +[`native-subagent-assignments.md`](native-subagent-assignments.md) for exact | |
| +configuration, lifecycle, reconciliation, and recovery behavior. | |
| ### Exact direct-cancel recovery exception | |
| diff --git a/docs/native-subagent-assignments.md b/docs/native-subagent-assignments.md | |
| new file mode 100644 | |
| index 00000000..01445224 | |
| --- /dev/null | |
| +++ b/docs/native-subagent-assignments.md | |
| @@ -0,0 +1,211 @@ | |
| +# Native subagent assignments | |
| + | |
| +The Codex native hook keeps typed native children read-only unless the active | |
| +root Conductor establishes an exact, session-scoped assignment. This transport | |
| +does not grant Team-worker identity or authority. | |
| + | |
| +## Backends | |
| + | |
| +Configure the transport in `~/.omx/config.json`: | |
| + | |
| +```json | |
| +{ | |
| + "nativeUltragoal": { | |
| + "backend": "local-experimental", | |
| + "assignmentTtlSeconds": 900 | |
| + } | |
| +} | |
| +``` | |
| + | |
| +`assignmentTtlSeconds` accepts an integer from 30 through 3600 and defaults to | |
| +900. `OMX_NATIVE_ULTRAGOAL_BACKEND` overrides the configured backend. The | |
| +override is fail-closed: a present non-empty invalid value resolves to `off` | |
| +instead of falling back to a configured authority backend. The | |
| +legacy `OMX_NATIVE_SUBAGENT_ASSIGNMENTS=on` setting maps to | |
| +`local-experimental` only when neither the new environment override nor an | |
| +explicit configured backend is present. | |
| + | |
| +The available backends are: | |
| + | |
| +- `off` (default): native Ultragoal activation outside tmux remains blocked | |
| + because no authorized executor is available. | |
| +- `host`: reserved for a documented, unforgeable receipt issued by the Codex | |
| + host. OMX has no such host receipt API today, so this backend fails closed | |
| + with `OMX-ULTRAGOAL-HOST-RECEIPT-UNAVAILABLE`. | |
| +- `local-experimental`: enables short-lived local assignments after OMX checks | |
| + the native tracker. This is a bounded same-user workflow guardrail, not host | |
| + authentication and not an ADR 3212 hostile-child boundary. | |
| + | |
| +## Ordered bootstrap | |
| + | |
| +On Codex native surfaces outside tmux, a fresh standalone `$ultragoal` | |
| +activation stays inactive until the executor assignment is ready. Use this | |
| +order: | |
| + | |
| +1. Invoke `$ultragoal`. The hook verifies typed `spawn_agent` support, creates | |
| + a session bootstrap, and returns `OMX-ULTRAGOAL-BOOTSTRAP-REQUIRED` without | |
| + activating Conductor mode. | |
| +2. Spawn one direct native child with `agent_type: "executor"` and retain its | |
| + canonical child ID. | |
| +3. Authorize only the required paths and actions: | |
| + | |
| + ```sh | |
| + omx ultragoal native-bootstrap authorize \ | |
| + --session-id <session-id> \ | |
| + --root-thread-id <root-thread-id> \ | |
| + --bootstrap-generation <generation-from-bootstrap-required> \ | |
| + --child-id <child-agent-id> \ | |
| + --path-prefix <repo-relative-path> \ | |
| + --json | |
| + ``` | |
| + | |
| + Repeat `--path-prefix` for additional scopes. Add `--path-write-only` to | |
| + omit `bash-write`. Use `--ttl-seconds <30-3600>` only when the configured | |
| + TTL is unsuitable. Every `native-bootstrap` action requires an explicit | |
| + `--session-id` equal to the canonical current runtime session; pointer-only | |
| + or cross-session commands fail closed. `--bootstrap-generation` is the exact | |
| + generation printed by the bootstrap-required response; stale commands cannot | |
| + authorize a replacement generation. | |
| +4. Retry the canonical Ultragoal activation. `PreToolUse` moves the receipt to | |
| + `activating`; only a successful activation `PostToolUse` publishes `active`. | |
| + The hook proceeds only after the | |
| + bootstrap generation, assignment, tracker session, root thread, exact child | |
| + ID, direct parent, role, status, lifetime, and path scope all validate. | |
| + | |
| +The child remains read-only until activation is confirmed. A crash before the | |
| +activation tool runs leaves `ready`; a rejected or interrupted activation leaves | |
| +`activating`. Both are safe and retryable while the assignment and tracked | |
| +executor remain valid. If the receipt is revoked, expires, or loses tracker | |
| +authority before confirmation, the hook revokes the grant and terminalizes the | |
| +new Ultragoal state as `failed` instead of leaving Conductor active without an | |
| +executor. | |
| + | |
| +## Location | |
| + | |
| +The bootstrap for root session `<session-id>` is stored at: | |
| + | |
| +```text | |
| +.omx/state/sessions/<session-id>/native-ultragoal-bootstrap.json | |
| +``` | |
| + | |
| +The assignment for child `<child-agent-id>` is stored at: | |
| + | |
| +```text | |
| +.omx/state/sessions/<session-id>/native-assignments/<encodeURIComponent(child-agent-id)>.json | |
| +``` | |
| + | |
| +`omx ultragoal native-bootstrap authorize` is the supported writer. The native | |
| +hook rejects the canonical command when a tracked child invokes it and rejects | |
| +child writes to bootstrap or `native-assignments` paths, even when ordinary | |
| +path scope would otherwise match. This is still a same-user experimental | |
| +boundary: the CLI cannot authenticate an arbitrary local OS process as | |
| +Main-root without a host-issued receipt. | |
| + | |
| +## Active assignment schema | |
| + | |
| +The JSON object must contain exactly these keys: | |
| + | |
| +```json | |
| +{ | |
| + "schema_version": 1, | |
| + "kind": "native-subagent-assignment", | |
| + "lifecycle": "active", | |
| + "root_session_id": "exact-root-session-id", | |
| + "root_thread_id": "exact-root-thread-id", | |
| + "child_agent_id": "exact-canonical-child-agent-id", | |
| + "agent_type": "executor", | |
| + "allowed_actions": ["path-write", "bash-write"], | |
| + "path_prefixes": ["tmp/native-probe"], | |
| + "issued_at": "2026-07-23T20:00:00.000Z", | |
| + "expires_at": "2026-07-23T20:10:00.000Z", | |
| + "bootstrap_generation": "exact-bootstrap-generation" | |
| +} | |
| +``` | |
| + | |
| +The hook checks the root session and leader thread against the active session | |
| +and native subagent tracker. The first version accepts only a directly attested | |
| +`executor` child. Its tracker entry must be `kind: "subagent"`, carry | |
| +`mode: "executor"`, `provenance_kind: "native_subagent"`, and exact | |
| +`direct_child_parent_id` / `direct_child_root_id` bindings to Main-root; revoked | |
| +reopen authority, completion, or a non-available explicit status fails closed. | |
| +The live tool payload must independently provide one unambiguous top-level | |
| +`executor` role through the native `agent_role`/`agent_type` aliases. | |
| +Nested-only role evidence, missing tracker attestation, or conflicting aliases | |
| +cannot authorize an assignment. The filename must encode that same canonical | |
| +child ID, and `bootstrap_generation` must match the current session bootstrap. | |
| + | |
| +Assignments are fail-closed when unreadable, malformed, expired, issued too far | |
| +in the future, longer than 24 hours, symlinked, hard-linked, larger than 64 KiB, | |
| +or writable by group/other. Unknown keys, duplicate actions or prefixes, | |
| +ambiguous paths, symlink traversal, `.omx`, and `.git` scopes are rejected. | |
| +Deleting the file or changing `lifecycle` away from `active` revokes the grant. | |
| + | |
| +## Lifecycle, revocation, and recovery | |
| + | |
| +Inspect and reconcile the current bootstrap with: | |
| + | |
| +```sh | |
| +omx ultragoal native-bootstrap status --session-id <session-id> --json | |
| +``` | |
| + | |
| +Status reconciliation returns readiness only while the exact assignment and | |
| +tracked direct executor remain valid. An expired assignment cannot authorize a | |
| +mutation. Reconciliation revokes an active assignment when the tracked child is | |
| +missing, completed, unavailable, foreign to the root, or has the wrong role. | |
| +Fresh activation also reconciles before it proceeds. | |
| + | |
| +All lifecycle mutations for one session are serialized by a session-scoped | |
| +lock. Activation revalidates tracker authority, generation, file identity, | |
| +schema, scope, and expiry under that lock and returns a positive receipt before | |
| +the hook permits state activation. Concurrent revoke cannot be overwritten by | |
| +a stale authorize or activation write. Revoked and expired generations require | |
| +an explicit new bootstrap before another authorization; authorization never | |
| +rotates a generation implicitly. An already-active receipt is idempotently | |
| +revalidated so a rejected or interrupted state write can be retried safely. | |
| + | |
| +Explicitly revoke the bootstrap and its assignment with: | |
| + | |
| +```sh | |
| +omx ultragoal native-bootstrap revoke \ | |
| + --session-id <session-id> \ | |
| + --child-id <child-agent-id> \ | |
| + --reason <reason> \ | |
| + --json | |
| +``` | |
| + | |
| +`--child-id` is optional when the bootstrap already records the child. After | |
| +revocation, expiry, or tracker invalidation, the child returns to the normal | |
| +read-only policy. Re-run `authorize` with the explicit current `--session-id`, | |
| +root thread, and a live strictly tracked executor to renew or replace the lease; | |
| +when standalone Ultragoal state is still active, the CLI restores bootstrap | |
| +status to `active` only after revalidation. Terminal `Stop`, exact root | |
| +cancellation, and a new fresh run revoke or rotate the old generation. An | |
| +assignment from an older generation cannot be reused. | |
| + | |
| +## Actions | |
| + | |
| +- `path-write`: direct file-edit tools within `path_prefixes`. | |
| +- `bash-write`: one statically analyzable shell mutation within | |
| + `path_prefixes`. | |
| + | |
| +Assignments never authorize VCS, PR, network, deployment, state, orchestration, | |
| +goal, assignment, or Team-worker operations. A `"."` prefix still excludes | |
| +`.git` and `.omx`. Shell substitution, | |
| +unresolved arithmetic, dynamic nested execution, unsafe heredocs, unsafe | |
| +nameref/allexport state, arbitrary PATH mutation, unresolved interpreter | |
| +writes, and path/symlink escapes remain blocked. | |
| + | |
| +Without a valid assignment, typed native children continue to receive | |
| +`OWNER_CONFIRMATION_REQUIRED`. | |
| + | |
| +## Root orchestration transports | |
| + | |
| +An active, session-bound Main-root Conductor may write the existing bounded | |
| +metadata roots, including `.omx/state`, `.omx/ultragoal`, `.omx/ralph`, | |
| +`.omx/team`, `.omx/mailbox`, `.omx/handoff`, `.omx/handoffs`, `.omx/goals`, | |
| +`.omx/notepad`, `.omx/wiki`, and `.beads`. | |
| + | |
| +The hook structurally recognizes canonical `omx state read`, `omx state write`, | |
| +`omx state clear`, and `omx ultragoal checkpoint` commands before generic shell | |
| +mutation analysis. Exact session/cwd/mode binding and all existing injection, | |
| +PATH, symlink, and protected-state-gate checks still apply. | |
| diff --git a/plugins/oh-my-codex/skills/ultragoal/SKILL.md b/plugins/oh-my-codex/skills/ultragoal/SKILL.md | |
| index 3a22a0ed..7010d328 100644 | |
| --- a/plugins/oh-my-codex/skills/ultragoal/SKILL.md | |
| +++ b/plugins/oh-my-codex/skills/ultragoal/SKILL.md | |
| @@ -24,6 +24,7 @@ Ultragoal is both a tracked workflow skill and the Autopilot durable-implementat | |
| - **Standalone `$ultragoal` activation**: ensure `.omx/state[/sessions/<session>]/ultragoal-state.json` exists with `mode:"ultragoal"`, `active:true`, and a non-empty `current_phase` such as `planning` before or while goals are created. This state is a lightweight HUD/runtime declaration; `.omx/ultragoal/goals.json` and `ledger.jsonl` remain the durable goal source of truth. | |
| - **During execution**: update `current_phase` to the smallest accurate phase (`planning`, `executing`, `verifying`, `reviewing`, `checkpointing`, or `blocked`) when the visible workflow phase changes. | |
| +- **Codex App outside tmux**: `nativeUltragoal.backend` defaults to `off`. `host` remains fail-closed until Codex exposes a documented host-issued native executor receipt API. `local-experimental` is an explicit same-user guardrail, not host authentication or an ADR 3212 hostile-child boundary. When typed `spawn_agent` support is advertised, keep Ultragoal inactive while Main-root follows the ordered bootstrap: invoke `$ultragoal`, spawn one exact typed `executor`, run `omx ultragoal native-bootstrap authorize --session-id <session-id> --root-thread-id <root-thread-id> --bootstrap-generation <generation> --child-id <child-id> --path-prefix <repo-relative-path> --json`, then retry the canonical Ultragoal activation. Keep paths/actions minimal; use `omx ultragoal native-bootstrap status` to reconcile/recover and `omx ultragoal native-bootstrap revoke` when the executor is no longer needed. Follow `docs/native-subagent-assignments.md` for configuration and lifecycle details. | |
| - **Inside active Autopilot**: keep `mode:"autopilot"` active and set the supervised phase to `current_phase:"ultragoal"`; do not start a peer Autopilot replacement. Ultragoal's own mode state may still exist as child-phase detail, but Autopilot owns the parent phase. | |
| - **On handoff to code-review**: persist implementation/test/ledger evidence under Autopilot `handoff_artifacts.ultragoal`, then set Autopilot `current_phase:"code-review"`. | |
| - **On completion/blocker**: set standalone Ultragoal `active:false,current_phase:"complete"` only when all durable goals are complete; otherwise keep it active with a blocker/review-blocked phase and ledger evidence. | |
| diff --git a/skills/ultragoal/SKILL.md b/skills/ultragoal/SKILL.md | |
| index 3a22a0ed..7010d328 100644 | |
| --- a/skills/ultragoal/SKILL.md | |
| +++ b/skills/ultragoal/SKILL.md | |
| @@ -24,6 +24,7 @@ Ultragoal is both a tracked workflow skill and the Autopilot durable-implementat | |
| - **Standalone `$ultragoal` activation**: ensure `.omx/state[/sessions/<session>]/ultragoal-state.json` exists with `mode:"ultragoal"`, `active:true`, and a non-empty `current_phase` such as `planning` before or while goals are created. This state is a lightweight HUD/runtime declaration; `.omx/ultragoal/goals.json` and `ledger.jsonl` remain the durable goal source of truth. | |
| - **During execution**: update `current_phase` to the smallest accurate phase (`planning`, `executing`, `verifying`, `reviewing`, `checkpointing`, or `blocked`) when the visible workflow phase changes. | |
| +- **Codex App outside tmux**: `nativeUltragoal.backend` defaults to `off`. `host` remains fail-closed until Codex exposes a documented host-issued native executor receipt API. `local-experimental` is an explicit same-user guardrail, not host authentication or an ADR 3212 hostile-child boundary. When typed `spawn_agent` support is advertised, keep Ultragoal inactive while Main-root follows the ordered bootstrap: invoke `$ultragoal`, spawn one exact typed `executor`, run `omx ultragoal native-bootstrap authorize --session-id <session-id> --root-thread-id <root-thread-id> --bootstrap-generation <generation> --child-id <child-id> --path-prefix <repo-relative-path> --json`, then retry the canonical Ultragoal activation. Keep paths/actions minimal; use `omx ultragoal native-bootstrap status` to reconcile/recover and `omx ultragoal native-bootstrap revoke` when the executor is no longer needed. Follow `docs/native-subagent-assignments.md` for configuration and lifecycle details. | |
| - **Inside active Autopilot**: keep `mode:"autopilot"` active and set the supervised phase to `current_phase:"ultragoal"`; do not start a peer Autopilot replacement. Ultragoal's own mode state may still exist as child-phase detail, but Autopilot owns the parent phase. | |
| - **On handoff to code-review**: persist implementation/test/ledger evidence under Autopilot `handoff_artifacts.ultragoal`, then set Autopilot `current_phase:"code-review"`. | |
| - **On completion/blocker**: set standalone Ultragoal `active:false,current_phase:"complete"` only when all durable goals are complete; otherwise keep it active with a blocker/review-blocked phase and ledger evidence. | |
| diff --git a/src/cli/__tests__/ultragoal.test.ts b/src/cli/__tests__/ultragoal.test.ts | |
| index 74c6d1f2..961c0a61 100644 | |
| --- a/src/cli/__tests__/ultragoal.test.ts | |
| +++ b/src/cli/__tests__/ultragoal.test.ts | |
| @@ -14,6 +14,10 @@ import { | |
| NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_FILE, | |
| writeRoleRoutingMarker, | |
| } from '../../subagents/role-routing-marker.js'; | |
| +import { | |
| + beginNativeUltragoalBootstrap, | |
| + nativeExecutorAssignmentPath, | |
| +} from '../../ultragoal/native-bootstrap.js'; | |
| import { ultragoalCommand, ULTRAGOAL_HELP } from '../ultragoal.js'; | |
| async function withCwd<T>(run: (cwd: string) => Promise<T>): Promise<T> { | |
| @@ -144,6 +148,7 @@ describe('cli/ultragoal', () => { | |
| ['next'], | |
| ['start-next'], | |
| ['checkpoint', '--goal-id', 'G001-first', '--status', 'complete', '--evidence', 'worker evidence'], | |
| + ['native-bootstrap', 'revoke', '--session-id', 'worker-session'], | |
| ]; | |
| const envCases: Array<[string, string]> = [ | |
| ['OMX_TEAM_WORKER', 'display-team/worker-1'], | |
| @@ -214,6 +219,127 @@ describe('cli/ultragoal', () => { | |
| assert.match(ULTRAGOAL_HELP, /quality-gate-json/); | |
| assert.match(ULTRAGOAL_HELP, /ai-slop-cleaner/); | |
| assert.match(ULTRAGOAL_HELP, /code-review/); | |
| + assert.match(ULTRAGOAL_HELP, /native-bootstrap <authorize\|status\|revoke>/); | |
| + }); | |
| + | |
| + it('authorizes, reports, and revokes an experimental native executor bootstrap', async () => { | |
| + await withCwd(async (cwd) => { | |
| + const previousBackend = process.env.OMX_NATIVE_ULTRAGOAL_BACKEND; | |
| + process.env.OMX_NATIVE_ULTRAGOAL_BACKEND = 'local-experimental'; | |
| + const stateDir = join(cwd, '.omx', 'state'); | |
| + const sessionId = 'native-cli-session'; | |
| + const rootThreadId = 'native-cli-root'; | |
| + const childAgentId = 'native-cli-executor'; | |
| + try { | |
| + await mkdir(stateDir, { recursive: true }); | |
| + await writeFile(join(stateDir, 'session.json'), `${JSON.stringify({ | |
| + session_id: sessionId, | |
| + native_session_id: rootThreadId, | |
| + cwd, | |
| + })}\n`); | |
| + await writeFile(join(stateDir, 'subagent-tracking.json'), `${JSON.stringify({ | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: rootThreadId, | |
| + updated_at: new Date().toISOString(), | |
| + threads: { | |
| + [rootThreadId]: { thread_id: rootThreadId, kind: 'leader' }, | |
| + [childAgentId]: { | |
| + thread_id: childAgentId, | |
| + kind: 'subagent', | |
| + mode: 'executor', | |
| + status: 'available', | |
| + provenance_kind: 'native_subagent', | |
| + direct_child_parent_id: rootThreadId, | |
| + direct_child_root_id: rootThreadId, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }, null, 2)}\n`); | |
| + const initialBootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + }); | |
| + | |
| + const crossSession = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'status', '--session-id', 'foreign-session', '--json', | |
| + ])); | |
| + assert.equal(crossSession.exitCode, 1); | |
| + assert.match(crossSession.stderr.join('\n'), /does not match the canonical current runtime session/); | |
| + const omittedSession = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'status', '--json', | |
| + ])); | |
| + assert.equal(omittedSession.exitCode, 1); | |
| + assert.match(omittedSession.stderr.join('\n'), /require an explicit --session-id/); | |
| + | |
| + const authorized = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'authorize', | |
| + '--session-id', sessionId, | |
| + '--root-thread-id', rootThreadId, | |
| + '--child-id', childAgentId, | |
| + '--bootstrap-generation', initialBootstrap.generation, | |
| + '--path-prefix', 'src', | |
| + '--json', | |
| + ])); | |
| + assert.equal(authorized.exitCode, undefined); | |
| + assert.equal(JSON.parse(authorized.stdout.join('\n')).assignment.child_agent_id, childAgentId); | |
| + assert.equal(existsSync(nativeExecutorAssignmentPath(stateDir, sessionId, childAgentId)), true); | |
| + | |
| + const status = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'status', '--session-id', sessionId, '--json', | |
| + ])); | |
| + const statusPayload = JSON.parse(status.stdout.join('\n')) as { | |
| + bootstrap: { status: string }; | |
| + readiness: { ready: boolean }; | |
| + }; | |
| + assert.equal(statusPayload.bootstrap.status, 'ready'); | |
| + assert.equal(statusPayload.readiness.ready, true); | |
| + | |
| + const revoked = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'revoke', '--session-id', sessionId, '--json', | |
| + ])); | |
| + assert.equal(revoked.exitCode, undefined); | |
| + assert.equal(JSON.parse(revoked.stdout.join('\n')).status, 'revoked'); | |
| + | |
| + await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true }); | |
| + await writeFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), `${JSON.stringify({ | |
| + mode: 'ultragoal', | |
| + active: true, | |
| + current_phase: 'executing', | |
| + session_id: sessionId, | |
| + })}\n`); | |
| + const recoveryBootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + }); | |
| + const recovered = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'authorize', | |
| + '--session-id', sessionId, | |
| + '--root-thread-id', rootThreadId, | |
| + '--child-id', childAgentId, | |
| + '--bootstrap-generation', recoveryBootstrap.generation, | |
| + '--path-prefix', 'src', | |
| + '--json', | |
| + ])); | |
| + assert.equal(recovered.exitCode, undefined); | |
| + const recoveredStatus = await capture(() => ultragoalCommand([ | |
| + 'native-bootstrap', 'status', '--session-id', sessionId, '--json', | |
| + ])); | |
| + assert.equal(JSON.parse(recoveredStatus.stdout.join('\n')).bootstrap.status, 'active'); | |
| + } finally { | |
| + if (typeof previousBackend === 'string') process.env.OMX_NATIVE_ULTRAGOAL_BACKEND = previousBackend; | |
| + else delete process.env.OMX_NATIVE_ULTRAGOAL_BACKEND; | |
| + } | |
| + }); | |
| }); | |
| it('creates and starts goals through the command surface', async () => { | |
| diff --git a/src/cli/ultragoal.ts b/src/cli/ultragoal.ts | |
| index 64d2e356..c781ed62 100644 | |
| --- a/src/cli/ultragoal.ts | |
| +++ b/src/cli/ultragoal.ts | |
| @@ -12,6 +12,7 @@ import { | |
| resolveNativeSubagentSupportStatus, | |
| } from '../leader/contract.js'; | |
| import { resolveRuntimeStateScope } from '../mcp/state-paths.js'; | |
| +import { resolveNativeUltragoalConfig } from '../config/native-ultragoal.js'; | |
| import { readRoleRoutingMarker } from '../subagents/role-routing-marker.js'; | |
| import { | |
| addUltragoalGoal, | |
| @@ -33,6 +34,31 @@ import { | |
| ULTRAGOAL_STEERING_SOURCES, | |
| UltragoalError, | |
| } from '../ultragoal/artifacts.js'; | |
| +import { | |
| + issueLocalNativeExecutorAssignment, | |
| + markNativeUltragoalBootstrapActive, | |
| + prepareNativeUltragoalBootstrapActivation, | |
| + readNativeUltragoalBootstrap, | |
| + reconcileLocalNativeExecutorAssignments, | |
| + revokeLocalNativeExecutorAssignment, | |
| +} from '../ultragoal/native-bootstrap.js'; | |
| + | |
| +async function sessionHasActiveStandaloneUltragoal(stateDir: string, sessionId: string): Promise<boolean> { | |
| + try { | |
| + const parsed = JSON.parse(await readFile( | |
| + join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), | |
| + 'utf-8', | |
| + )) as Record<string, unknown>; | |
| + const phase = String(parsed.current_phase ?? parsed.currentPhase ?? '').trim().toLowerCase(); | |
| + return parsed.mode === 'ultragoal' | |
| + && parsed.active === true | |
| + && parsed.session_id === sessionId | |
| + && phase !== '' | |
| + && !['complete', 'completed', 'cancelled', 'failed'].includes(phase); | |
| + } catch { | |
| + return false; | |
| + } | |
| +} | |
| export const ULTRAGOAL_HELP = `omx ultragoal - Durable repo-native multi-goal workflow over Codex goal mode | |
| @@ -46,6 +72,7 @@ Usage: | |
| omx ultragoal steer --directive-json <json-or-path> [--json] | |
| omx ultragoal checkpoint --goal-id <id> --status <complete|failed|blocked> [--evidence <text>] [--codex-goal-json <json-or-path>] [--quality-gate-json <json-or-path>] [--json] | |
| omx ultragoal status [--codex-goal-json <json-or-path>] [--json] | |
| + omx ultragoal native-bootstrap <authorize|status|revoke> [options] [--json] | |
| Aliases: | |
| create -> create-goals, complete|next|start-next -> complete-goals | |
| @@ -336,6 +363,7 @@ const ULTRAGOAL_MUTATING_COMMANDS = new Set([ | |
| 'next', | |
| 'start-next', | |
| 'checkpoint', | |
| + 'native-bootstrap', | |
| ]); | |
| function readTeamWorkerIdentity(env: NodeJS.ProcessEnv = process.env): string | null { | |
| @@ -374,6 +402,110 @@ export async function ultragoalCommand(args: string[], deps: UltragoalCommandDep | |
| assertUltragoalMutationAllowedFromCurrentProcess(command); | |
| + if (command === 'native-bootstrap') { | |
| + const action = rest[0] ?? 'status'; | |
| + const actionArgs = rest.slice(1); | |
| + const scope = await resolveRuntimeStateScope(cwd); | |
| + const config = resolveNativeUltragoalConfig(); | |
| + const requestedSessionId = readValue(actionArgs, '--session-id'); | |
| + if (!requestedSessionId) { | |
| + throw new UltragoalError('Native bootstrap commands require an explicit --session-id.'); | |
| + } | |
| + if (!scope.sessionId) { | |
| + throw new UltragoalError('Native bootstrap requires a canonical current runtime session.'); | |
| + } | |
| + if (requestedSessionId !== scope.sessionId) { | |
| + throw new UltragoalError('Requested --session-id does not match the canonical current runtime session.'); | |
| + } | |
| + const sessionId = scope.sessionId; | |
| + const rootThreadId = readValue(actionArgs, '--root-thread-id'); | |
| + | |
| + if (action === 'authorize') { | |
| + if (config.backend !== 'local-experimental') { | |
| + throw new UltragoalError('native-bootstrap authorize requires nativeUltragoal.backend="local-experimental".'); | |
| + } | |
| + const childAgentId = readValue(actionArgs, '--child-id'); | |
| + const bootstrapGeneration = readValue(actionArgs, '--bootstrap-generation'); | |
| + const pathPrefixes = readRepeated(actionArgs, '--path-prefix'); | |
| + if (!rootThreadId) throw new UltragoalError('Missing --root-thread-id.'); | |
| + if (!childAgentId) throw new UltragoalError('Missing --child-id.'); | |
| + if (!bootstrapGeneration) throw new UltragoalError('Missing --bootstrap-generation.'); | |
| + if (pathPrefixes.length === 0) throw new UltragoalError('Pass at least one --path-prefix.'); | |
| + const ttlRaw = readValue(actionArgs, '--ttl-seconds'); | |
| + const ttlSeconds = ttlRaw === undefined ? config.assignmentTtlSeconds : Number(ttlRaw); | |
| + try { | |
| + const assignment = await issueLocalNativeExecutorAssignment({ | |
| + cwd: scope.cwd, | |
| + stateDir: scope.baseStateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + childAgentId, | |
| + expectedGeneration: bootstrapGeneration, | |
| + pathPrefixes, | |
| + ttlSeconds, | |
| + allowedActions: hasFlag(actionArgs, '--path-write-only') | |
| + ? ['path-write'] | |
| + : ['path-write', 'bash-write'], | |
| + }); | |
| + if (await sessionHasActiveStandaloneUltragoal(scope.baseStateDir, sessionId)) { | |
| + const currentBootstrap = await readNativeUltragoalBootstrap(scope.baseStateDir, sessionId); | |
| + if (currentBootstrap?.status !== 'active') { | |
| + const prepared = await prepareNativeUltragoalBootstrapActivation({ | |
| + cwd: scope.cwd, | |
| + stateDir: scope.baseStateDir, | |
| + sessionId, | |
| + childAgentId, | |
| + }); | |
| + if (!prepared.ok) throw new UltragoalError('native_ultragoal_activation_receipt_invalid'); | |
| + const confirmed = await markNativeUltragoalBootstrapActive({ | |
| + cwd: scope.cwd, | |
| + stateDir: scope.baseStateDir, | |
| + sessionId, | |
| + childAgentId, | |
| + }); | |
| + if (!confirmed.ok) throw new UltragoalError('native_ultragoal_activation_receipt_invalid'); | |
| + } | |
| + } | |
| + if (json) printJson({ ok: true, backend: config.backend, assignment }); | |
| + else console.log(`native Ultragoal executor authorized: ${assignment.child_agent_id}`); | |
| + } catch (error) { | |
| + throw new UltragoalError(error instanceof Error ? error.message : String(error)); | |
| + } | |
| + return; | |
| + } | |
| + | |
| + if (action === 'revoke') { | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir: scope.baseStateDir, | |
| + sessionId, | |
| + childAgentId: readValue(actionArgs, '--child-id'), | |
| + reason: readValue(actionArgs, '--reason') ?? 'root_requested_revoke', | |
| + }); | |
| + if (json) printJson({ ok: true, sessionId, status: 'revoked' }); | |
| + else console.log(`native Ultragoal bootstrap revoked: ${sessionId}`); | |
| + return; | |
| + } | |
| + | |
| + if (action === 'status') { | |
| + const bootstrap = await readNativeUltragoalBootstrap(scope.baseStateDir, sessionId); | |
| + const readiness = bootstrap?.root_thread_id | |
| + ? await reconcileLocalNativeExecutorAssignments({ | |
| + cwd: scope.cwd, | |
| + stateDir: scope.baseStateDir, | |
| + sessionId, | |
| + rootThreadId: bootstrap.root_thread_id, | |
| + }) | |
| + : { ready: false }; | |
| + if (json) printJson({ backend: config.backend, bootstrap, readiness }); | |
| + else console.log(bootstrap | |
| + ? `native Ultragoal bootstrap: ${bootstrap.status}${readiness.ready ? ' (ready)' : ''}` | |
| + : 'native Ultragoal bootstrap: absent'); | |
| + return; | |
| + } | |
| + | |
| + throw new UltragoalError('Invalid native-bootstrap action; expected authorize, status, or revoke.'); | |
| + } | |
| + | |
| if (command === 'create' || command === 'create-goals') { | |
| const briefFile = readValue(rest, '--brief-file'); | |
| const brief = readValue(rest, '--brief') | |
| diff --git a/src/config/__tests__/native-ultragoal.test.ts b/src/config/__tests__/native-ultragoal.test.ts | |
| new file mode 100644 | |
| index 00000000..e4bb6532 | |
| --- /dev/null | |
| +++ b/src/config/__tests__/native-ultragoal.test.ts | |
| @@ -0,0 +1,95 @@ | |
| +import assert from 'node:assert/strict'; | |
| +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; | |
| +import { tmpdir } from 'node:os'; | |
| +import { join } from 'node:path'; | |
| +import { describe, it } from 'node:test'; | |
| +import { resolveNativeUltragoalConfig } from '../native-ultragoal.js'; | |
| + | |
| +describe('native Ultragoal config', () => { | |
| + it('defaults off and reads the explicit global config', async () => { | |
| + const homeDir = await mkdtemp(join(tmpdir(), 'omx-native-ultragoal-config-')); | |
| + try { | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ homeDir, env: {} }), { | |
| + backend: 'off', | |
| + assignmentTtlSeconds: 900, | |
| + source: 'default', | |
| + }); | |
| + await mkdir(join(homeDir, '.omx'), { recursive: true }); | |
| + await writeFile(join(homeDir, '.omx', 'config.json'), JSON.stringify({ | |
| + nativeUltragoal: { | |
| + backend: 'local-experimental', | |
| + assignmentTtlSeconds: 300, | |
| + }, | |
| + })); | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ homeDir, env: {} }), { | |
| + backend: 'local-experimental', | |
| + assignmentTtlSeconds: 300, | |
| + source: 'config', | |
| + }); | |
| + } finally { | |
| + await rm(homeDir, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('keeps invalid config fail-closed and preserves explicit env precedence', async () => { | |
| + const homeDir = await mkdtemp(join(tmpdir(), 'omx-native-ultragoal-config-invalid-')); | |
| + try { | |
| + await mkdir(join(homeDir, '.omx'), { recursive: true }); | |
| + await writeFile(join(homeDir, '.omx', 'config.json'), JSON.stringify({ | |
| + nativeUltragoal: { backend: 'local', assignmentTtlSeconds: 1 }, | |
| + })); | |
| + assert.equal(resolveNativeUltragoalConfig({ homeDir, env: {} }).backend, 'off'); | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ | |
| + homeDir, | |
| + env: { OMX_NATIVE_ULTRAGOAL_BACKEND: 'host' }, | |
| + }), { | |
| + backend: 'host', | |
| + assignmentTtlSeconds: 900, | |
| + source: 'env', | |
| + }); | |
| + assert.equal(resolveNativeUltragoalConfig({ | |
| + homeDir, | |
| + env: { OMX_NATIVE_SUBAGENT_ASSIGNMENTS: 'on' }, | |
| + }).backend, 'off'); | |
| + await writeFile(join(homeDir, '.omx', 'config.json'), '{ malformed'); | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ | |
| + homeDir, | |
| + env: { OMX_NATIVE_SUBAGENT_ASSIGNMENTS: 'on' }, | |
| + }), { | |
| + backend: 'off', | |
| + assignmentTtlSeconds: 900, | |
| + source: 'config', | |
| + }); | |
| + } finally { | |
| + await rm(homeDir, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('keeps explicit new config ahead of the legacy assignment opt-in', async () => { | |
| + const homeDir = await mkdtemp(join(tmpdir(), 'omx-native-ultragoal-config-legacy-')); | |
| + try { | |
| + await mkdir(join(homeDir, '.omx'), { recursive: true }); | |
| + await writeFile(join(homeDir, '.omx', 'config.json'), JSON.stringify({ | |
| + nativeUltragoal: { backend: 'host', assignmentTtlSeconds: 120 }, | |
| + })); | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ | |
| + homeDir, | |
| + env: { OMX_NATIVE_SUBAGENT_ASSIGNMENTS: 'on' }, | |
| + }), { | |
| + backend: 'host', | |
| + assignmentTtlSeconds: 120, | |
| + source: 'config', | |
| + }); | |
| + assert.deepEqual(resolveNativeUltragoalConfig({ | |
| + homeDir, | |
| + env: { OMX_NATIVE_ULTRAGOAL_BACKEND: 'local' }, | |
| + }), { | |
| + backend: 'off', | |
| + assignmentTtlSeconds: 120, | |
| + source: 'env', | |
| + }); | |
| + } finally { | |
| + await rm(homeDir, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| +}); | |
| diff --git a/src/config/native-ultragoal.ts b/src/config/native-ultragoal.ts | |
| new file mode 100644 | |
| index 00000000..83da9374 | |
| --- /dev/null | |
| +++ b/src/config/native-ultragoal.ts | |
| @@ -0,0 +1,125 @@ | |
| +import { existsSync, readFileSync } from 'node:fs'; | |
| +import { homedir } from 'node:os'; | |
| +import { join } from 'node:path'; | |
| + | |
| +export type NativeUltragoalBackend = 'off' | 'host' | 'local-experimental'; | |
| + | |
| +export interface NativeUltragoalRuntimeConfig { | |
| + backend: NativeUltragoalBackend; | |
| + assignmentTtlSeconds: number; | |
| + source: 'default' | 'config' | 'env' | 'legacy-env'; | |
| +} | |
| + | |
| +interface NativeUltragoalConfigFile { | |
| + nativeUltragoal?: { | |
| + backend?: unknown; | |
| + assignmentTtlSeconds?: unknown; | |
| + }; | |
| +} | |
| + | |
| +const DEFAULT_ASSIGNMENT_TTL_SECONDS = 15 * 60; | |
| +const MIN_ASSIGNMENT_TTL_SECONDS = 30; | |
| +const MAX_ASSIGNMENT_TTL_SECONDS = 60 * 60; | |
| + | |
| +function normalizeBackend(value: unknown): NativeUltragoalBackend | null { | |
| + if (typeof value !== 'string') return null; | |
| + const normalized = value.trim().toLowerCase(); | |
| + if (normalized === 'off' || normalized === 'host' || normalized === 'local-experimental') return normalized; | |
| + return null; | |
| +} | |
| + | |
| +function normalizeAssignmentTtlSeconds(value: unknown): number | null { | |
| + if (typeof value !== 'number' || !Number.isInteger(value)) return null; | |
| + if (value < MIN_ASSIGNMENT_TTL_SECONDS || value > MAX_ASSIGNMENT_TTL_SECONDS) return null; | |
| + return value; | |
| +} | |
| + | |
| +function readConfigFile(homeDir: string): | |
| + | { status: 'absent' } | |
| + | { status: 'invalid' } | |
| + | { status: 'valid'; value: NativeUltragoalConfigFile } { | |
| + const path = join(homeDir, '.omx', 'config.json'); | |
| + if (!existsSync(path)) return { status: 'absent' }; | |
| + try { | |
| + const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown; | |
| + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) | |
| + ? { status: 'valid', value: parsed as NativeUltragoalConfigFile } | |
| + : { status: 'invalid' }; | |
| + } catch { | |
| + return { status: 'invalid' }; | |
| + } | |
| +} | |
| + | |
| +export function resolveNativeUltragoalConfig(options: { | |
| + homeDir?: string; | |
| + env?: NodeJS.ProcessEnv; | |
| +} = {}): NativeUltragoalRuntimeConfig { | |
| + const env = options.env ?? process.env; | |
| + const configResult = readConfigFile(options.homeDir ?? homedir()); | |
| + const config = configResult.status === 'valid' ? configResult.value : null; | |
| + const hasExplicitConfiguredBackend = config?.nativeUltragoal?.backend !== undefined; | |
| + const configuredBackend = normalizeBackend(config?.nativeUltragoal?.backend); | |
| + const assignmentTtlSeconds = normalizeAssignmentTtlSeconds( | |
| + config?.nativeUltragoal?.assignmentTtlSeconds, | |
| + ) ?? DEFAULT_ASSIGNMENT_TTL_SECONDS; | |
| + const rawEnvBackend = env.OMX_NATIVE_ULTRAGOAL_BACKEND; | |
| + const explicitEnvBackend = normalizeBackend(rawEnvBackend); | |
| + if (explicitEnvBackend) { | |
| + return { | |
| + backend: explicitEnvBackend, | |
| + assignmentTtlSeconds, | |
| + source: 'env', | |
| + }; | |
| + } | |
| + if (typeof rawEnvBackend === 'string' && rawEnvBackend.trim() !== '') { | |
| + return { | |
| + backend: 'off', | |
| + assignmentTtlSeconds, | |
| + source: 'env', | |
| + }; | |
| + } | |
| + | |
| + if (configResult.status === 'invalid') { | |
| + return { | |
| + backend: 'off', | |
| + assignmentTtlSeconds, | |
| + source: 'config', | |
| + }; | |
| + } | |
| + | |
| + if (configuredBackend) { | |
| + return { | |
| + backend: configuredBackend, | |
| + assignmentTtlSeconds, | |
| + source: 'config', | |
| + }; | |
| + } | |
| + | |
| + if (hasExplicitConfiguredBackend) { | |
| + return { | |
| + backend: 'off', | |
| + assignmentTtlSeconds, | |
| + source: 'config', | |
| + }; | |
| + } | |
| + | |
| + if (String(env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS ?? '').trim().toLowerCase() === 'on') { | |
| + return { | |
| + backend: 'local-experimental', | |
| + assignmentTtlSeconds, | |
| + source: 'legacy-env', | |
| + }; | |
| + } | |
| + | |
| + return { | |
| + backend: 'off', | |
| + assignmentTtlSeconds, | |
| + source: 'default', | |
| + }; | |
| +} | |
| + | |
| +export function nativeUltragoalBackendIsLocalExperimental( | |
| + options: Parameters<typeof resolveNativeUltragoalConfig>[0] = {}, | |
| +): boolean { | |
| + return resolveNativeUltragoalConfig(options).backend === 'local-experimental'; | |
| +} | |
| diff --git a/src/scripts/__tests__/codex-native-assignment.test.ts b/src/scripts/__tests__/codex-native-assignment.test.ts | |
| new file mode 100644 | |
| index 00000000..20c579dd | |
| --- /dev/null | |
| +++ b/src/scripts/__tests__/codex-native-assignment.test.ts | |
| @@ -0,0 +1,461 @@ | |
| +import assert from "node:assert/strict"; | |
| +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; | |
| +import { tmpdir } from "node:os"; | |
| +import { dirname, join, resolve } from "node:path"; | |
| +import { afterEach, beforeEach, describe, it } from "node:test"; | |
| +import { dispatchCodexNativeHook } from "../codex-native-hook.js"; | |
| + | |
| +async function writeJson(path: string, value: unknown): Promise<void> { | |
| + await mkdir(dirname(path), { recursive: true }); | |
| + await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8"); | |
| +} | |
| + | |
| +async function createActiveConductorFixture(phase = "executing"): Promise<{ | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + leaderThreadId: string; | |
| + childAgentId: string; | |
| +}> { | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-assignment-")); | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-native-assignment"; | |
| + const leaderThreadId = "thread-native-assignment-leader"; | |
| + const childAgentId = "agent-native-assignment-executor"; | |
| + const now = new Date().toISOString(); | |
| + const bootstrapGeneration = "bootstrap-generation-native-assignment"; | |
| + | |
| + await writeJson(join(stateDir, "session.json"), { | |
| + session_id: sessionId, | |
| + native_session_id: leaderThreadId, | |
| + cwd, | |
| + }); | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: now, | |
| + threads: { | |
| + [leaderThreadId]: { | |
| + thread_id: leaderThreadId, | |
| + kind: "leader", | |
| + first_seen_at: now, | |
| + last_seen_at: now, | |
| + turn_count: 1, | |
| + }, | |
| + [childAgentId]: { | |
| + thread_id: childAgentId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + status: "available", | |
| + provenance_kind: "native_subagent", | |
| + direct_child_parent_id: leaderThreadId, | |
| + direct_child_root_id: leaderThreadId, | |
| + leader_thread_id: leaderThreadId, | |
| + first_seen_at: now, | |
| + last_seen_at: now, | |
| + turn_count: 1, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { | |
| + active: true, | |
| + skill: "ultragoal", | |
| + phase, | |
| + session_id: sessionId, | |
| + active_skills: [ | |
| + { skill: "ultragoal", phase, active: true, session_id: sessionId }, | |
| + ], | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { | |
| + active: true, | |
| + mode: "ultragoal", | |
| + current_phase: phase, | |
| + session_id: sessionId, | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "native-ultragoal-bootstrap.json"), { | |
| + schema_version: 1, | |
| + kind: "native-ultragoal-bootstrap", | |
| + generation: bootstrapGeneration, | |
| + backend: "local-experimental", | |
| + status: "active", | |
| + session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + created_at: now, | |
| + updated_at: now, | |
| + expires_at: new Date(Date.now() + 10 * 60_000).toISOString(), | |
| + }); | |
| + | |
| + return { cwd, stateDir, sessionId, leaderThreadId, childAgentId }; | |
| +} | |
| + | |
| +function preToolUse( | |
| + cwd: string, | |
| + sessionId: string, | |
| + identity: Record<string, unknown>, | |
| + toolName: string, | |
| + toolInput: Record<string, unknown>, | |
| +) { | |
| + return dispatchCodexNativeHook({ | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + ...identity, | |
| + tool_name: toolName, | |
| + tool_input: toolInput, | |
| + }, { cwd }); | |
| +} | |
| + | |
| +describe("native Conductor assignment transport", () => { | |
| + const originalAssignmentOptIn = process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS; | |
| + | |
| + beforeEach(() => { | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + }); | |
| + | |
| + afterEach(() => { | |
| + if (originalAssignmentOptIn === undefined) delete process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS; | |
| + else process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = originalAssignmentOptIn; | |
| + }); | |
| + | |
| + it("requires typed tracker authority and rejects missing or conflicting role evidence", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture; | |
| + const assignmentPath = join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + "native-assignments", | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| + const issuedAt = new Date(); | |
| + const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000); | |
| + const trackingPath = join(stateDir, "subagent-tracking.json"); | |
| + const trackedChild = { | |
| + thread_id: childAgentId, | |
| + kind: "subagent", | |
| + leader_thread_id: leaderThreadId, | |
| + first_seen_at: issuedAt.toISOString(), | |
| + last_seen_at: issuedAt.toISOString(), | |
| + turn_count: 1, | |
| + }; | |
| + | |
| + try { | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + bootstrap_generation: "bootstrap-generation-native-assignment", | |
| + }); | |
| + await writeJson(trackingPath, { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: issuedAt.toISOString(), | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childAgentId]: trackedChild, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + | |
| + const realTypedChild = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_type: "executor" }, | |
| + "Write", | |
| + { file_path: "src/assigned/real-typed-child.txt", content: "ok\n" }, | |
| + ); | |
| + assert.equal(realTypedChild.outputJson?.decision, "block"); | |
| + assert.match(String(realTypedChild.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + const missingTypedRole = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { | |
| + agent_id: childAgentId, | |
| + source: { subagent: { thread_spawn: { agent_role: "executor" } } }, | |
| + }, | |
| + "Write", | |
| + { file_path: "src/assigned/missing-role.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(missingTypedRole.outputJson?.decision, "block"); | |
| + assert.match(String(missingTypedRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + const conflictingTypedAliases = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_role: "executor", agent_type: "writer" }, | |
| + "Write", | |
| + { file_path: "src/assigned/conflicting-aliases.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(conflictingTypedAliases.outputJson?.decision, "block"); | |
| + assert.match(String(conflictingTypedAliases.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await writeJson(trackingPath, { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: issuedAt.toISOString(), | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childAgentId]: { ...trackedChild, mode: "writer" }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + const conflictingTrackerRole = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_role: "executor" }, | |
| + "Write", | |
| + { file_path: "src/assigned/conflicting-role.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(conflictingTrackerRole.outputJson?.decision, "block"); | |
| + assert.match(String(conflictingTrackerRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("allows only an exact live typed-child path grant and keeps all negative contracts fail-closed", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture; | |
| + const assignmentPath = join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + "native-assignments", | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| + const issuedAt = new Date(); | |
| + const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000); | |
| + | |
| + try { | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + bootstrap_generation: "bootstrap-generation-native-assignment", | |
| + }); | |
| + | |
| + const exactChild = { agent_id: childAgentId, agent_role: "executor" }; | |
| + const allowed = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/result.txt", content: "ok\n" }, | |
| + ); | |
| + assert.equal(allowed.outputJson, null); | |
| + | |
| + for (const [name, identity, filePath] of [ | |
| + ["outside-scope", exactChild, "src/outside.txt"], | |
| + ["assignment-self-mutation", exactChild, assignmentPath], | |
| + ["foreign-child", { agent_id: "agent-native-assignment-foreign", agent_role: "executor" }, "src/assigned/result.txt"], | |
| + ["role-mismatch", { agent_id: childAgentId, agent_role: "writer" }, "src/assigned/result.txt"], | |
| + ["ungranted-writer", { agent_id: "agent-native-assignment-writer", agent_role: "writer" }, "src/assigned/result.txt"], | |
| + ] as const) { | |
| + const denied = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + identity, | |
| + "Write", | |
| + { file_path: filePath, content: `${name}\n` }, | |
| + ); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name); | |
| + } | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: "foreign-session", | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + bootstrap_generation: "bootstrap-generation-native-assignment", | |
| + unexpected_scope_expansion: true, | |
| + }); | |
| + const malformed = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/malformed.txt", content: "malformed\n" }, | |
| + ); | |
| + assert.equal(malformed.outputJson?.decision, "block"); | |
| + assert.match(String(malformed.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await mkdir(join(cwd, "src", "assigned"), { recursive: true }); | |
| + const escapedTarget = await mkdtemp(join(tmpdir(), "omx-native-assignment-escape-")); | |
| + await symlink(escapedTarget, join(cwd, "src", "assigned", "link")); | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write", "bash-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + bootstrap_generation: "bootstrap-generation-native-assignment", | |
| + }); | |
| + const allowedBash = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Bash", | |
| + { command: "printf ok > src/assigned/bash.txt" }, | |
| + ); | |
| + assert.equal(allowedBash.outputJson, null); | |
| + for (const [name, command] of [ | |
| + ["bash-outside-scope", "printf no > src/outside-bash.txt"], | |
| + ["bash-symlink-escape", "printf no > src/assigned/link/escaped.txt"], | |
| + ["bash-substitution-poison", "printf \"$(printf no)\" > src/assigned/poison.txt"], | |
| + ] as const) { | |
| + const denied = await preToolUse(cwd, sessionId, exactChild, "Bash", { command }); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name); | |
| + } | |
| + await rm(escapedTarget, { recursive: true, force: true }); | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: new Date(issuedAt.getTime() - 20 * 60_000).toISOString(), | |
| + expires_at: new Date(issuedAt.getTime() - 10 * 60_000).toISOString(), | |
| + }); | |
| + const stale = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/stale.txt", content: "stale\n" }, | |
| + ); | |
| + assert.equal(stale.outputJson?.decision, "block"); | |
| + assert.match(String(stale.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write", "bash-write"], | |
| + path_prefixes: ["."], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + bootstrap_generation: "bootstrap-generation-native-assignment", | |
| + }); | |
| + for (const [name, toolName, toolInput] of [ | |
| + ["git-path", "Write", { file_path: ".git/config", content: "must-not-write\n" }], | |
| + ["omx-goal-path", "Write", { file_path: ".omx/ultragoal/goals.json", content: "{}\n" }], | |
| + ["git-bash", "Bash", { command: "printf no > .git/config" }], | |
| + ["omx-goal-bash", "Bash", { command: "printf no > .omx/ultragoal/goals.json" }], | |
| + ] as const) { | |
| + const denied = await preToolUse(cwd, sessionId, exactChild, toolName, toolInput); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name); | |
| + } | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + }); | |
| + await chmod(assignmentPath, 0o666); | |
| + const writableByOthers = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/world-writable-grant.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(writableByOthers.outputJson?.decision, "block"); | |
| + assert.match(String(writableByOthers.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("allows only Main-root to issue an assignment in the exact active session scope", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, sessionId, leaderThreadId } = fixture; | |
| + | |
| + try { | |
| + const root = { agent_id: leaderThreadId, thread_id: leaderThreadId }; | |
| + | |
| + const rootAssignment = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: `.omx/state/sessions/${sessionId}/native-assignments/root-created.json`, | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(rootAssignment.outputJson, null, "root-assignment-create"); | |
| + const foreignSessionAssignment = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: ".omx/state/sessions/foreign-session/native-assignments/root-created.json", | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(foreignSessionAssignment.outputJson?.decision, "block", "foreign-session-assignment"); | |
| + const protectedGate = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: `.omx/state/sessions/${sessionId}/ultragoal-state.json`, | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(protectedGate.outputJson?.decision, "block", "protected-state-gate"); | |
| + | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| +}); | |
| diff --git a/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts b/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts | |
| index bc832ed9..feb42459 100644 | |
| --- a/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts | |
| +++ b/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts | |
| @@ -5,14 +5,19 @@ import { tmpdir } from "node:os"; | |
| import { dirname, join } from "node:path"; | |
| import { afterEach, describe, it } from "node:test"; | |
| import { dispatchCodexNativeHook } from "../codex-native-hook.js"; | |
| +import { | |
| + issueLocalNativeExecutorAssignment, | |
| + readNativeUltragoalBootstrap, | |
| + revokeLocalNativeExecutorAssignment, | |
| +} from "../../ultragoal/native-bootstrap.js"; | |
| // Regression coverage for OMX #3311: on native Codex App / native-hook | |
| // surfaces outside tmux, standalone Ultragoal must not activate an | |
| // execution-blocking Main-root Conductor state when no authorized executor | |
| -// will ever be reachable (Team is tmux-only; native child/descendant | |
| -// provenance intentionally grants no write authority per #3127). The guard | |
| -// under test refuses the *activation* write itself; it must never affect | |
| -// already-active sessions, non-ultragoal modes, non-native launchers, or | |
| +// will ever be reachable. Team is tmux-only; native child/descendant provenance | |
| +// grants no write authority unless the root issues an exact scoped assignment. | |
| +// The guard under test refuses the *activation* write itself; it must never | |
| +// affect already-active sessions, non-ultragoal modes, non-native launchers, or | |
| // attached-tmux transport. | |
| async function writeJson(path: string, value: unknown): Promise<void> { | |
| @@ -35,6 +40,8 @@ async function writeLeaderSessionFixture( | |
| await writeJson(join(stateDir, "session.json"), { | |
| session_id: sessionId, | |
| native_session_id: leaderThreadId, | |
| + owner_codex_session_id: sessionId, | |
| + target_session_id: sessionId, | |
| cwd, | |
| }); | |
| await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| @@ -53,10 +60,13 @@ async function writeLeaderSessionFixture( | |
| describe("issue-3311: standalone Ultragoal native-App outside-tmux activation guard", () => { | |
| const originalTmux = process.env.TMUX; | |
| + const originalAssignmentOptIn = process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS; | |
| afterEach(() => { | |
| if (originalTmux === undefined) delete process.env.TMUX; | |
| else process.env.TMUX = originalTmux; | |
| + if (originalAssignmentOptIn === undefined) delete process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS; | |
| + else process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = originalAssignmentOptIn; | |
| }); | |
| it("blocks fresh standalone-ultragoal Conductor activation on native App outside tmux (Bash state write)", async () => { | |
| @@ -88,8 +98,71 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null) | |
| ?.hookSpecificOutput?.additionalContext || "", | |
| ); | |
| - assert.match(context, /native child\/descendant provenance does not grant write authority/); | |
| - assert.doesNotMatch(context, /write-assignment|assignment-backed|scoped native .*grant/i); | |
| + assert.match(context, /typed native spawn surface backed by scoped write assignments/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("keeps fresh activation inactive until the advertised executor is authorized", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-3319-supported-bash-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-3319-supported-bash"; | |
| + const leaderThreadId = "thread-3319-supported-bash"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, leaderThreadId, cwd); | |
| + | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: leaderThreadId, | |
| + agent_id: leaderThreadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + tool_name: "Bash", | |
| + tool_input: { command: `omx state write --input '${ACTIVATION_INPUT}' --json` }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.equal(result.outputJson?.decision, "block"); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /OMX-ULTRAGOAL-BOOTSTRAP-REQUIRED/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("keeps fresh activation blocked when native subagents are advertised without typed role routing", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-3319-untyped-bash-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-3319-untyped-bash"; | |
| + const leaderThreadId = "thread-3319-untyped-bash"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, leaderThreadId, cwd); | |
| + | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: leaderThreadId, | |
| + agent_id: leaderThreadId, | |
| + source: "native", | |
| + capabilities: { native_subagents: true }, | |
| + tool_name: "Bash", | |
| + tool_input: { command: `omx state write --input '${ACTIVATION_INPUT}' --json` }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.equal(result.outputJson?.decision, "block"); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /OMX-ULTRAGOAL-(?:NO-OWNER|BOOTSTRAP-REQUIRED)/); | |
| } finally { | |
| await rm(cwd, { recursive: true, force: true }); | |
| } | |
| @@ -124,8 +197,7 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null) | |
| ?.hookSpecificOutput?.additionalContext || "", | |
| ); | |
| - assert.match(context, /native child\/descendant provenance does not grant write authority/); | |
| - assert.doesNotMatch(context, /write-assignment|assignment-backed|scoped native .*grant/i); | |
| + assert.match(context, /typed native spawn surface backed by scoped write assignments/); | |
| } finally { | |
| await rm(cwd, { recursive: true, force: true }); | |
| } | |
| @@ -163,8 +235,7 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null) | |
| ?.hookSpecificOutput?.additionalContext || "", | |
| ); | |
| - assert.match(context, /native child\/descendant provenance does not grant write authority/); | |
| - assert.doesNotMatch(context, /write-assignment|assignment-backed|scoped native .*grant/i); | |
| + assert.match(context, /typed native spawn surface backed by scoped write assignments/); | |
| } finally { | |
| await rm(cwd, { recursive: true, force: true }); | |
| } | |
| @@ -336,6 +407,129 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| } | |
| }); | |
| + it("does not let a tracked native child authorize its own bootstrap assignment", async () => { | |
| + delete process.env.TMUX; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-bootstrap-self-auth-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-bootstrap-self-auth"; | |
| + const leaderThreadId = "thread-bootstrap-self-auth-root"; | |
| + const childThreadId = "thread-bootstrap-self-auth-child"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, leaderThreadId, cwd); | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childThreadId]: { | |
| + thread_id: childThreadId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + status: "available", | |
| + provenance_kind: "native_subagent", | |
| + direct_child_parent_id: leaderThreadId, | |
| + direct_child_root_id: leaderThreadId, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: childThreadId, | |
| + agent_id: childThreadId, | |
| + agent_type: "executor", | |
| + source: "native", | |
| + tool_name: "Bash", | |
| + tool_input: { | |
| + command: `omx ultragoal native-bootstrap authorize --session-id ${sessionId} --root-thread-id ${leaderThreadId} --child-id ${childThreadId} --path-prefix src`, | |
| + }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.equal(result.outputJson?.decision, "block"); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /root-owned/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("keeps the spawned executor read-only while the bootstrap is awaiting authorization", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-bootstrap-pending-child-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-bootstrap-pending-child"; | |
| + const leaderThreadId = "thread-bootstrap-pending-root"; | |
| + const childThreadId = "thread-bootstrap-pending-child"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, leaderThreadId, cwd); | |
| + await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "UserPromptSubmit", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: leaderThreadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + prompt: "$ultragoal execute this plan", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childThreadId]: { | |
| + thread_id: childThreadId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + status: "available", | |
| + provenance_kind: "native_subagent", | |
| + direct_child_parent_id: leaderThreadId, | |
| + direct_child_root_id: leaderThreadId, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: childThreadId, | |
| + agent_id: childThreadId, | |
| + agent_type: "executor", | |
| + source: "native", | |
| + tool_name: "Write", | |
| + tool_input: { file_path: join(cwd, "src", "pending-write.ts"), content: "blocked" }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.equal(result.outputJson?.decision, "block"); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /bootstrap is not active/); | |
| + assert.match(String(result.outputJson?.reason ?? ""), /remain read-only/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| it("does not block autopilot-supervised ultragoal activation (mode is autopilot, not standalone ultragoal)", async () => { | |
| delete process.env.TMUX; | |
| const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-3311-autopilot-")); | |
| @@ -427,6 +621,271 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| } | |
| }); | |
| + it("keeps '$ultragoal' prompt activation inactive until the advertised executor is authorized", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-3319-supported-prompt-")); | |
| + try { | |
| + await mkdir(join(cwd, ".omx", "state"), { recursive: true }); | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "UserPromptSubmit", | |
| + cwd, | |
| + session_id: "sess-3319-supported-prompt", | |
| + thread_id: "thread-3319-supported-prompt", | |
| + turn_id: "turn-3319-supported-prompt", | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + prompt: "$ultragoal split this launch into durable goals", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.notEqual(result.skillState?.initialized_mode, "ultragoal"); | |
| + assert.match(String(result.skillState?.transition_error ?? result.outputJson?.reason ?? ""), /BOOTSTRAP|inactive|authorization/i); | |
| + assert.equal( | |
| + existsSync(join(cwd, ".omx", "state", "sessions", "sess-3319-supported-prompt", "ultragoal-state.json")), | |
| + false, | |
| + ); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("completes prompt bootstrap through typed assignment, activation, and scoped executor write", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-bootstrap-e2e-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-bootstrap-e2e"; | |
| + const rootThreadId = "thread-bootstrap-e2e-root"; | |
| + const childThreadId = "thread-bootstrap-e2e-child"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, rootThreadId, cwd); | |
| + const firstPrompt = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "UserPromptSubmit", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + prompt: "$ultragoal execute the durable plan", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal(firstPrompt.skillState?.active, false); | |
| + const bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + assert.ok(bootstrap); | |
| + | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: rootThreadId, | |
| + threads: { | |
| + [rootThreadId]: { thread_id: rootThreadId, kind: "leader" }, | |
| + [childThreadId]: { | |
| + thread_id: childThreadId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + status: "available", | |
| + provenance_kind: "native_subagent", | |
| + direct_child_parent_id: rootThreadId, | |
| + direct_child_root_id: rootThreadId, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + await issueLocalNativeExecutorAssignment({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + childAgentId: childThreadId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ["src/native-flow"], | |
| + ttlSeconds: 300, | |
| + }); | |
| + | |
| + const activated = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "UserPromptSubmit", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + prompt: "$ultragoal execute the durable plan", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal(activated.skillState?.active, true); | |
| + assert.equal(activated.skillState?.initialized_mode, "ultragoal"); | |
| + | |
| + const write = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: childThreadId, | |
| + agent_id: childThreadId, | |
| + agent_type: "executor", | |
| + source: "native", | |
| + tool_name: "Write", | |
| + tool_input: { file_path: join(cwd, "src", "native-flow", "result.ts"), content: "ok" }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal(write.outputJson, null); | |
| + | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { | |
| + mode: "ultragoal", | |
| + active: false, | |
| + current_phase: "complete", | |
| + session_id: sessionId, | |
| + }); | |
| + const terminalWrite = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: childThreadId, | |
| + agent_id: childThreadId, | |
| + agent_type: "executor", | |
| + source: "native", | |
| + tool_name: "Write", | |
| + tool_input: { file_path: join(cwd, "src", "native-flow", "late.ts"), content: "no" }, | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal(terminalWrite.outputJson?.decision, "block"); | |
| + assert.equal((await readNativeUltragoalBootstrap(stateDir, sessionId))?.status, "revoked"); | |
| + await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "Stop", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + source: "native", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal((await readNativeUltragoalBootstrap(stateDir, sessionId))?.status, "revoked"); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("keeps the executor read-only until successful activation PostToolUse", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-bootstrap-two-phase-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-bootstrap-two-phase"; | |
| + const rootThreadId = "thread-bootstrap-two-phase-root"; | |
| + const childThreadId = "thread-bootstrap-two-phase-child"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, rootThreadId, cwd); | |
| + const activationPayload = { | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + agent_id: rootThreadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + tool_name: "Bash", | |
| + tool_input: { command: `omx state write --input '${ACTIVATION_INPUT}' --json` }, | |
| + }; | |
| + await dispatchCodexNativeHook({ hook_event_name: "PreToolUse", ...activationPayload }, { cwd }); | |
| + const bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + assert.ok(bootstrap); | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: rootThreadId, | |
| + threads: { | |
| + [rootThreadId]: { thread_id: rootThreadId, kind: "leader" }, | |
| + [childThreadId]: { | |
| + thread_id: childThreadId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + status: "available", | |
| + provenance_kind: "native_subagent", | |
| + direct_child_parent_id: rootThreadId, | |
| + direct_child_root_id: rootThreadId, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + await issueLocalNativeExecutorAssignment({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + childAgentId: childThreadId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ["src/native-flow"], | |
| + ttlSeconds: 300, | |
| + }); | |
| + | |
| + const prepared = await dispatchCodexNativeHook( | |
| + { hook_event_name: "PreToolUse", ...activationPayload }, | |
| + { cwd }, | |
| + ); | |
| + assert.equal(prepared.outputJson, null); | |
| + assert.equal((await readNativeUltragoalBootstrap(stateDir, sessionId))?.status, "activating"); | |
| + const prematureWrite = await dispatchCodexNativeHook({ | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: childThreadId, | |
| + agent_id: childThreadId, | |
| + agent_type: "executor", | |
| + source: "native", | |
| + tool_name: "Write", | |
| + tool_input: { file_path: join(cwd, "src", "native-flow", "premature.ts"), content: "no" }, | |
| + }, { cwd }); | |
| + assert.equal(prematureWrite.outputJson?.decision, "block"); | |
| + | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { | |
| + mode: "ultragoal", | |
| + active: true, | |
| + current_phase: "executing", | |
| + session_id: sessionId, | |
| + }); | |
| + await dispatchCodexNativeHook({ hook_event_name: "PostToolUse", ...activationPayload }, { cwd }); | |
| + assert.equal((await readNativeUltragoalBootstrap(stateDir, sessionId))?.status, "active"); | |
| + | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir, | |
| + sessionId, | |
| + reason: "activation_receipt_race", | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { | |
| + mode: "ultragoal", | |
| + active: true, | |
| + current_phase: "executing", | |
| + session_id: sessionId, | |
| + }); | |
| + await dispatchCodexNativeHook({ hook_event_name: "PostToolUse", ...activationPayload }, { cwd }); | |
| + const failedState = JSON.parse(readFileSync( | |
| + join(stateDir, "sessions", sessionId, "ultragoal-state.json"), | |
| + "utf-8", | |
| + )) as { active: boolean; current_phase: string; error?: string }; | |
| + assert.equal(failedState.active, false); | |
| + assert.equal(failedState.current_phase, "failed"); | |
| + assert.equal(failedState.error, "native_ultragoal_activation_receipt_invalid"); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| it("allows the primary '$ultragoal' UserPromptSubmit activation when attached to tmux (Team is reachable)", async () => { | |
| process.env.TMUX = "/tmp/tmux-3311-prompt,12345,0"; | |
| const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-3311-prompt-tmux-")); | |
| @@ -588,4 +1047,62 @@ describe("issue-3311: standalone Ultragoal native-App outside-tmux activation gu | |
| await rm(cwd, { recursive: true, force: true }); | |
| } | |
| }); | |
| + | |
| + it("keeps a completed standalone Ralplan to Ultragoal handoff inactive until executor authorization", async () => { | |
| + delete process.env.TMUX; | |
| + process.env.OMX_NATIVE_SUBAGENT_ASSIGNMENTS = "on"; | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-ultragoal-bootstrap-")); | |
| + try { | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-ralplan-ultragoal-bootstrap"; | |
| + const threadId = "thread-ralplan-ultragoal-bootstrap"; | |
| + await writeLeaderSessionFixture(stateDir, sessionId, threadId, cwd); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { | |
| + active: false, | |
| + skill: "ralplan", | |
| + phase: "complete", | |
| + session_id: sessionId, | |
| + owner_codex_session_id: sessionId, | |
| + target_session_id: sessionId, | |
| + active_skills: [{ | |
| + skill: "ralplan", | |
| + phase: "complete", | |
| + active: false, | |
| + session_id: sessionId, | |
| + owner_codex_session_id: sessionId, | |
| + }], | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { | |
| + active: false, | |
| + mode: "ralplan", | |
| + current_phase: "complete", | |
| + session_id: sessionId, | |
| + owner_codex_session_id: sessionId, | |
| + target_session_id: sessionId, | |
| + }); | |
| + | |
| + const result = await dispatchCodexNativeHook( | |
| + { | |
| + hook_event_name: "UserPromptSubmit", | |
| + cwd, | |
| + session_id: sessionId, | |
| + thread_id: threadId, | |
| + source: "native", | |
| + available_tools: ["collaboration.spawn_agent"], | |
| + prompt: "$ultragoal execute the approved plan", | |
| + }, | |
| + { cwd }, | |
| + ); | |
| + | |
| + assert.equal(result.skillState?.active, false); | |
| + assert.equal(result.skillState?.skill, "ultragoal"); | |
| + assert.match(String(result.skillState?.transition_error ?? ""), /OMX-ULTRAGOAL-BOOTSTRAP-REQUIRED/); | |
| + assert.equal( | |
| + existsSync(join(stateDir, "sessions", sessionId, "ultragoal-state.json")), | |
| + false, | |
| + ); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| }); | |
| diff --git a/src/scripts/codex-native-hook.ts b/src/scripts/codex-native-hook.ts | |
| index 8c74a5d4..a40aa6a8 100644 | |
| --- a/src/scripts/codex-native-hook.ts | |
| +++ b/src/scripts/codex-native-hook.ts | |
| @@ -97,6 +97,7 @@ import { | |
| } from "../hooks/keyword-detector.js"; | |
| import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js"; | |
| import { readTeamModeConfig } from "../config/team-mode.js"; | |
| +import { resolveNativeUltragoalConfig } from "../config/native-ultragoal.js"; | |
| import { | |
| detectNativeStopStallPattern, | |
| loadAutoNudgeConfig, | |
| @@ -165,6 +166,19 @@ import { | |
| steerUltragoal, | |
| type UltragoalSteeringProposal, | |
| } from "../ultragoal/artifacts.js"; | |
| +import { | |
| + beginNativeUltragoalBootstrap, | |
| + DOCUMENTED_HOST_RECEIPT_UNAVAILABLE, | |
| + findReadyLocalNativeExecutorAssignment, | |
| + markNativeUltragoalBootstrapActive, | |
| + NATIVE_ASSIGNMENT_DIRECTORY, | |
| + nativeExecutorAssignmentFileIsSafe, | |
| + parseActiveLocalNativeExecutorAssignment, | |
| + prepareNativeUltragoalBootstrapActivation, | |
| + readNativeUltragoalBootstrap, | |
| + reconcileLocalNativeExecutorAssignments, | |
| + revokeLocalNativeExecutorAssignment, | |
| +} from "../ultragoal/native-bootstrap.js"; | |
| import { triagePrompt } from "../hooks/triage-heuristic.js"; | |
| import { readTriageConfig } from "../hooks/triage-config.js"; | |
| import { | |
| @@ -2661,6 +2675,28 @@ function buildNativeOutsideTmuxTeamPromptBlockState( | |
| // of an already-active standalone Ultragoal session) are left untouched, so | |
| // resumption, prior-plan continuation, and non-standalone Ultragoal remain | |
| // exactly as before. | |
| +function nativeSubagentAssignmentsOptedIn(): boolean { | |
| + return resolveNativeUltragoalConfig().backend === "local-experimental"; | |
| +} | |
| + | |
| +function payloadAdvertisesTypedNativeSpawn(payload: CodexHookPayload): boolean { | |
| + const capabilities = safeObject(payload.omx_runtime_capabilities ?? payload.capabilities); | |
| + const nativeSubagents = capabilities?.native_subagents ?? capabilities?.nativeSubagents; | |
| + const multiAgent = capabilities?.multi_agent_v1 ?? capabilities?.multiAgentV1; | |
| + const roleRouting = capabilities?.role_routing ?? capabilities?.roleRouting; | |
| + if (roleRouting === true && (nativeSubagents === true || multiAgent === true)) return true; | |
| + | |
| + const availableTools = payload.available_tools ?? payload.availableTools ?? payload.tools; | |
| + if (!Array.isArray(availableTools)) return false; | |
| + return availableTools.some((tool) => { | |
| + const record = safeObject(tool); | |
| + const name = typeof tool === "string" | |
| + ? tool | |
| + : safeString(record?.name ?? record?.tool_name ?? record?.toolName).trim(); | |
| + return isNativeSubagentSpawnToolName(name); | |
| + }); | |
| +} | |
| + | |
| async function buildNativeOutsideTmuxUltragoalPromptBlockState( | |
| classification: KeywordInputClassification, | |
| cwd: string, | |
| @@ -2686,12 +2722,85 @@ async function buildNativeOutsideTmuxUltragoalPromptBlockState( | |
| // planning guards). Fall through unchanged to recordSkillActivation's own | |
| // transition/continuation/supervision logic. | |
| const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId); | |
| - const hasAnyActiveTrackedSkill = canonicalState | |
| - ? listActiveSkills(canonicalState).some((entry) => ( | |
| + const activeTrackedSkills = canonicalState | |
| + ? listActiveSkills(canonicalState).filter((entry) => ( | |
| matchesSkillStopContext(entry, canonicalState, sessionId, threadId ?? "") | |
| )) | |
| - : false; | |
| - if (hasAnyActiveTrackedSkill) return null; | |
| + : []; | |
| + if (activeTrackedSkills.length > 0) return null; | |
| + | |
| + const rootThreadId = safeString(threadId ?? readPayloadThreadId(payload)).trim(); | |
| + const nativeConfig = resolveNativeUltragoalConfig(); | |
| + const nativeSupport = resolveNativeSubagentSupportStatus({ | |
| + payload, | |
| + persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)), | |
| + persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, { cwd, sessionId }), | |
| + persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)), | |
| + cwd, | |
| + sessionId, | |
| + }); | |
| + if (nativeConfig.backend === "local-experimental" && rootThreadId) { | |
| + const ready = await reconcileLocalNativeExecutorAssignments({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + }); | |
| + if (ready.ready && ready.childAgentId) return null; | |
| + if (nativeSupport.status === "supported" && payloadAdvertisesTypedNativeSpawn(payload)) { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + backend: nativeConfig.backend, | |
| + ttlSeconds: nativeConfig.assignmentTtlSeconds, | |
| + }); | |
| + const nowIso = new Date().toISOString(); | |
| + return { | |
| + version: 1, | |
| + active: false, | |
| + skill: "ultragoal", | |
| + keyword: match.keyword, | |
| + phase: "bootstrap", | |
| + activated_at: nowIso, | |
| + updated_at: nowIso, | |
| + source: "keyword-detector", | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + turn_id: turnId, | |
| + active_skills: [], | |
| + transition_error: buildNativeUltragoalBootstrapRequiredReason( | |
| + sessionId, | |
| + rootThreadId, | |
| + bootstrap.generation, | |
| + ), | |
| + }; | |
| + } | |
| + } else if (nativeConfig.backend === "host" && rootThreadId) { | |
| + await beginNativeUltragoalBootstrap({ | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + backend: nativeConfig.backend, | |
| + ttlSeconds: nativeConfig.assignmentTtlSeconds, | |
| + }); | |
| + const nowIso = new Date().toISOString(); | |
| + return { | |
| + version: 1, | |
| + active: false, | |
| + skill: "ultragoal", | |
| + keyword: match.keyword, | |
| + phase: "bootstrap", | |
| + activated_at: nowIso, | |
| + updated_at: nowIso, | |
| + source: "keyword-detector", | |
| + session_id: sessionId, | |
| + thread_id: rootThreadId, | |
| + turn_id: turnId, | |
| + active_skills: [], | |
| + transition_error: ULTRAGOAL_HOST_RECEIPT_UNAVAILABLE_REASON, | |
| + }; | |
| + } | |
| const nowIso = new Date().toISOString(); | |
| return { | |
| @@ -2711,6 +2820,40 @@ async function buildNativeOutsideTmuxUltragoalPromptBlockState( | |
| }; | |
| } | |
| +async function revokeTerminalNativeUltragoalBootstrap( | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<void> { | |
| + if (!sessionId) return; | |
| + const bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + if (!bootstrap || bootstrap.status === "revoked") return; | |
| + const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, sessionId, stateDir); | |
| + const phase = safeString(state?.current_phase ?? state?.currentPhase).trim().toLowerCase(); | |
| + if (state?.active === false || TERMINAL_MODE_PHASES.has(phase)) { | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir, | |
| + sessionId, | |
| + reason: `ultragoal_${phase || "inactive"}`, | |
| + }); | |
| + } | |
| +} | |
| + | |
| +async function failNativeUltragoalActivation( | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| + reason: string, | |
| +): Promise<void> { | |
| + await revokeLocalNativeExecutorAssignment({ stateDir, sessionId, reason }); | |
| + await updateModeState("ultragoal", { | |
| + active: false, | |
| + current_phase: "failed", | |
| + error: reason, | |
| + last_turn_at: new Date().toISOString(), | |
| + }, cwd, sessionId); | |
| +} | |
| + | |
| function buildSkillStateCliInstruction(mode: string, statePath: string): string { | |
| return `skill: ${mode} activated and initial state initialized at ${statePath}; use CLI-first state updates via \`omx state write/read/clear --input '<json>' --json\`; use omx_state MCP only when explicit MCP compatibility is enabled.`; | |
| } | |
| @@ -4049,6 +4192,15 @@ function resolveConductorPolicyRoot(stateDir: string, fallbackCwd: string): Cond | |
| return { cwd: canonicalFallback, valid: !statePresent, statePresent, externalStateRoot: statePresent }; | |
| } | |
| +function readDirectPayloadAgentRole(payload: CodexHookPayload): string { | |
| + const roles = [...new Set([ | |
| + "agent_role", | |
| + "agentRole", | |
| + "agent_type", | |
| + "agentType", | |
| + ].map((key) => safeString(payload[key]).trim().toLowerCase()).filter(Boolean))]; | |
| + return roles.length === 1 ? roles[0] ?? "" : ""; | |
| +} | |
| function readPayloadAgentRole(payload: CodexHookPayload): string { | |
| const directRole = safeString( | |
| @@ -5487,7 +5639,6 @@ function classifyPreToolUseMutationTransport( | |
| return "goal-lifecycle"; | |
| } | |
| const canonicalToolName = canonicalizeNativeCollaborationToolName(toolName); | |
| - | |
| if ( | |
| CONDUCTOR_ORCHESTRATION_TOOL_NAMES.has(canonicalToolName) | |
| || canonicalToolName.startsWith("collaboration.") | |
| @@ -10528,15 +10679,26 @@ async function buildRalplanPreToolUseBoundaryOutput( | |
| }; | |
| } | |
| -function buildRawProtectedWorkflowStatePathOutput( | |
| +async function buildRawProtectedWorkflowStatePathOutput( | |
| payload: CodexHookPayload, | |
| cwd: string, | |
| stateDir: string, | |
| -): Record<string, unknown> | null { | |
| + authoritativeSessionId = "", | |
| + activeConductor = false, | |
| +): Promise<Record<string, unknown> | null> { | |
| const toolName = safeString(payload.tool_name).trim(); | |
| if (classifyPreToolUseMutationTransport(payload, toolName, cwd) !== "path") return null; | |
| const candidates = collectImplementationToolPathCandidates(payload, toolName, readPreToolUsePathCandidates(payload)); | |
| - const protectedPath = candidates.find((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate)); | |
| + const protectedCandidates = candidates.filter((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate)); | |
| + if ( | |
| + nativeSubagentAssignmentsOptedIn() | |
| + && activeConductor | |
| + && authoritativeSessionId | |
| + && protectedCandidates.length > 0 | |
| + && protectedCandidates.every((candidate) => isSessionBoundNativeAssignmentPath(cwd, candidate, authoritativeSessionId)) | |
| + && await resolvePreToolUseWriteActor(payload, cwd, stateDir, authoritativeSessionId) === "main-root" | |
| + ) return null; | |
| + const protectedPath = protectedCandidates[0]; | |
| if (protectedPath === undefined) return null; | |
| return { | |
| decision: "block", | |
| @@ -10799,6 +10961,104 @@ interface ActiveConductorState { | |
| type PreToolUseWriteActor = "main-root" | "native-child" | "provenance-conflict" | "team-worker"; | |
| +type NativeAssignmentAction = "path-write" | "bash-write"; | |
| + | |
| +interface NativeSubagentAssignment { | |
| + schema_version: 1; | |
| + kind: "native-subagent-assignment"; | |
| + lifecycle: "active"; | |
| + root_session_id: string; | |
| + root_thread_id: string; | |
| + child_agent_id: string; | |
| + agent_type: string; | |
| + allowed_actions: NativeAssignmentAction[]; | |
| + path_prefixes: string[]; | |
| + issued_at: string; | |
| + expires_at: string; | |
| + bootstrap_generation: string; | |
| +} | |
| + | |
| + | |
| +function nativeAssignmentPath(stateDir: string, sessionId: string, childAgentId: string): string { | |
| + return join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + NATIVE_ASSIGNMENT_DIRECTORY, | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| +} | |
| + | |
| +function isNativeAssignmentPath(stateDir: string, rawPath: string, executionCwd: string): boolean { | |
| + const absolute = isAbsolute(rawPath) ? resolve(rawPath) : resolve(executionCwd, rawPath); | |
| + const assignmentRoot = resolve(stateDir, "sessions"); | |
| + const relativePath = relative(assignmentRoot, absolute).replace(/\\/g, "/"); | |
| + return relativePath !== "" | |
| + && !relativePath.startsWith("../") | |
| + && /^.+\/native-assignments(?:\/|$)/.test(relativePath); | |
| +} | |
| + | |
| +async function readNativeSubagentAssignment( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<NativeSubagentAssignment | null> { | |
| + if (!nativeSubagentAssignmentsOptedIn()) return null; | |
| + const childAgentId = readPayloadAgentId(payload); | |
| + const agentType = readDirectPayloadAgentRole(payload); | |
| + if (!childAgentId || !agentType || resolveInstalledRoleName(agentType, undefined, cwd) === null) return null; | |
| + | |
| + const trackingState = await readSubagentTrackingState(cwd).catch(() => null); | |
| + const trackedSession = safeObject(trackingState?.sessions?.[sessionId]); | |
| + const leaderThreadId = safeString(trackedSession?.leader_thread_id).trim(); | |
| + const trackedThreads = safeObject(trackedSession?.threads); | |
| + const trackedChild = safeObject(trackedThreads?.[childAgentId]); | |
| + const trackedAgentType = safeString(trackedChild?.mode).trim().toLowerCase(); | |
| + const trackedParentId = safeString( | |
| + trackedChild?.direct_child_parent_id ?? trackedChild?.parent_thread_id, | |
| + ).trim(); | |
| + const trackedRootId = safeString(trackedChild?.direct_child_root_id).trim(); | |
| + const trackedStatus = safeString(trackedChild?.status).trim().toLowerCase(); | |
| + if ( | |
| + !leaderThreadId | |
| + || safeString(trackedChild?.kind).trim() !== "subagent" | |
| + || (trackedAgentType !== "" && trackedAgentType !== agentType) | |
| + || (trackedParentId !== "" && trackedParentId !== leaderThreadId) | |
| + || (trackedRootId !== "" && trackedRootId !== leaderThreadId) | |
| + || (trackedStatus !== "" && trackedStatus !== "available") | |
| + ) return null; | |
| + | |
| + const path = nativeAssignmentPath(stateDir, sessionId, childAgentId); | |
| + if (!await nativeExecutorAssignmentFileIsSafe(stateDir, path)) return null; | |
| + let raw: Record<string, unknown> | null = null; | |
| + try { | |
| + raw = safeObject(JSON.parse(readFileSync(path, "utf-8"))); | |
| + } catch { | |
| + return null; | |
| + } | |
| + const assignment = await parseActiveLocalNativeExecutorAssignment(raw, { | |
| + cwd, | |
| + sessionId, | |
| + rootThreadId: leaderThreadId, | |
| + childAgentId, | |
| + agentType, | |
| + }); | |
| + if (!assignment) return null; | |
| + const ready = await findReadyLocalNativeExecutorAssignment({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId: leaderThreadId, | |
| + }); | |
| + if ( | |
| + !ready | |
| + || ready.child_agent_id !== assignment.child_agent_id | |
| + || ready.bootstrap_generation !== assignment.bootstrap_generation | |
| + ) return null; | |
| + return assignment; | |
| +} | |
| + | |
| function hasSubagentThreadSpawnProvenance(payload: CodexHookPayload): boolean { | |
| const source = payload.source; | |
| if (!source || typeof source !== "object") return false; | |
| @@ -10930,6 +11190,23 @@ const ULTRAGOAL_NO_OWNER_DENY_REASON = | |
| + "is tmux-only, and native child/descendant provenance intentionally does not grant write authority (see #3127). " | |
| + "Activating Conductor mode here would deadlock with only `omx cancel` reachable."; | |
| +const ULTRAGOAL_HOST_RECEIPT_UNAVAILABLE_REASON = | |
| + `OMX-ULTRAGOAL-HOST-RECEIPT-UNAVAILABLE: ${DOCUMENTED_HOST_RECEIPT_UNAVAILABLE}. ` | |
| + + "Host-backed native executor authority remains fail-closed until Codex exposes a documented receipt verifier."; | |
| + | |
| +function buildNativeUltragoalBootstrapRequiredReason( | |
| + sessionId: string, | |
| + rootThreadId: string, | |
| + generation: string, | |
| +): string { | |
| + return "OMX-ULTRAGOAL-BOOTSTRAP-REQUIRED: typed native execution is available, but Ultragoal activation remains " | |
| + + "inactive until an exact executor is spawned and authorized. Spawn one typed `executor`, then run " | |
| + + "`omx ultragoal native-bootstrap authorize " | |
| + + `--session-id ${JSON.stringify(sessionId)} --root-thread-id ${JSON.stringify(rootThreadId)} ` | |
| + + `--bootstrap-generation ${JSON.stringify(generation)} ` | |
| + + "--child-id <spawned-child-id> --path-prefix . --json`, then retry the canonical Ultragoal state activation. "; | |
| +} | |
| + | |
| function collectUltragoalActivationCandidatePayloads( | |
| payload: CodexHookPayload, | |
| toolName: string, | |
| @@ -10996,21 +11273,154 @@ async function buildUltragoalNoOwnerActivationGuardOutput( | |
| }); | |
| if (executionSurface.launcher !== "native" || executionSurface.transport !== "outside-tmux") return null; | |
| + const rootThreadId = readPayloadThreadId(payload); | |
| + const nativeConfig = resolveNativeUltragoalConfig(); | |
| + if (nativeConfig.backend === "host") { | |
| + return { | |
| + decision: "block", | |
| + reason: ULTRAGOAL_HOST_RECEIPT_UNAVAILABLE_REASON, | |
| + hookSpecificOutput: { | |
| + hookEventName: "PreToolUse", | |
| + additionalContext: ULTRAGOAL_HOST_RECEIPT_UNAVAILABLE_REASON, | |
| + }, | |
| + }; | |
| + } | |
| + if (nativeConfig.backend === "local-experimental" && rootThreadId) { | |
| + let bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + if (!bootstrap) { | |
| + const nativeSupport = resolveNativeSubagentSupportStatus({ | |
| + payload, | |
| + persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)), | |
| + persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, { cwd, sessionId }), | |
| + persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)), | |
| + cwd, | |
| + sessionId, | |
| + }); | |
| + if (nativeSupport.status === "supported" && payloadAdvertisesTypedNativeSpawn(payload)) { | |
| + bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + backend: nativeConfig.backend, | |
| + ttlSeconds: nativeConfig.assignmentTtlSeconds, | |
| + }); | |
| + } | |
| + } | |
| + const ready = await findReadyLocalNativeExecutorAssignment({ | |
| + cwd: policyCwd, | |
| + stateDir, | |
| + sessionId, | |
| + rootThreadId, | |
| + }); | |
| + if (ready) { | |
| + const activation = await prepareNativeUltragoalBootstrapActivation({ | |
| + cwd: policyCwd, | |
| + stateDir, | |
| + sessionId, | |
| + childAgentId: ready.child_agent_id, | |
| + }); | |
| + if (activation.ok) return null; | |
| + } | |
| + const reason = bootstrap | |
| + ? buildNativeUltragoalBootstrapRequiredReason(sessionId, rootThreadId, bootstrap.generation) | |
| + : ULTRAGOAL_NO_OWNER_DENY_REASON; | |
| + return { | |
| + decision: "block", | |
| + reason, | |
| + hookSpecificOutput: { | |
| + hookEventName: "PreToolUse", | |
| + additionalContext: reason, | |
| + }, | |
| + }; | |
| + } | |
| + | |
| return { | |
| decision: "block", | |
| reason: ULTRAGOAL_NO_OWNER_DENY_REASON, | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| additionalContext: | |
| - "Standalone Ultragoal Conductor mode requires an attached tmux session (Team execution) on native Codex App " | |
| - + "outside tmux; native child/descendant provenance does not grant write authority. Do not set ultragoal " | |
| - + "active/current_phase here. Proceed with direct bounded implementation for this task without " | |
| + "Standalone Ultragoal Conductor mode requires either an attached tmux session (Team execution) or a positively " | |
| + + "advertised typed native spawn surface backed by scoped write assignments; neither is proven on this native " | |
| + + "Codex App outside-tmux event. Do not set " | |
| + + "ultragoal active/current_phase here. Proceed with direct bounded implementation for this task without " | |
| + "entering Ultragoal's Conductor-gated planning mode, or re-run from an attached tmux OMX CLI shell so " | |
| + "`omx team` is available. `omx cancel` remains available if a stuck Conductor state already exists.", | |
| }, | |
| }; | |
| } | |
| +async function buildNativeUltragoalBootstrapMutationGuardOutput( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<Record<string, unknown> | null> { | |
| + if (safeString(payload.tool_name).trim() !== "Bash") return null; | |
| + const command = readPreToolUseCommand(payload); | |
| + if (!/\bomx\s+ultragoal\s+native-bootstrap\b/.test(command)) return null; | |
| + const actor = await resolvePreToolUseWriteActor(payload, cwd, stateDir, sessionId); | |
| + if (actor === "main-root") return null; | |
| + const reason = actor === "native-child" | |
| + ? "OWNER_CONFIRMATION_REQUIRED: native Ultragoal bootstrap metadata is root-owned; a native child cannot authorize, revoke, or reconcile its own assignment." | |
| + : "PROVENANCE_DENIED: native Ultragoal bootstrap mutation requires exact Main-root session provenance."; | |
| + return { | |
| + decision: "block", | |
| + reason, | |
| + hookSpecificOutput: { | |
| + hookEventName: "PreToolUse", | |
| + additionalContext: reason, | |
| + }, | |
| + }; | |
| +} | |
| + | |
| +function isExactNativeUltragoalBootstrapCommand(command: string): boolean { | |
| + if (nativeAssignedBashHasUnsafeStructure(".", command)) return false; | |
| + const words = tokenizeConductorShellWords(command).map(shellWordLiteral); | |
| + return words.length >= 4 | |
| + && basename(words[0] ?? "") === "omx" | |
| + && words[1] === "ultragoal" | |
| + && words[2] === "native-bootstrap" | |
| + && ["authorize", "status", "revoke"].includes(words[3] ?? ""); | |
| +} | |
| + | |
| +async function buildPendingNativeUltragoalExecutorGuardOutput( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<Record<string, unknown> | null> { | |
| + if (!sessionId) return null; | |
| + let bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + if (!bootstrap || bootstrap.status === "revoked" || bootstrap.status === "expired") { | |
| + return null; | |
| + } | |
| + if (bootstrap.status === "active") { | |
| + const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, sessionId, stateDir); | |
| + if (isActiveConductorModeState(state, "ultragoal", sessionId)) return null; | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir, | |
| + sessionId, | |
| + reason: "ultragoal_state_not_active", | |
| + }); | |
| + bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionId); | |
| + } | |
| + const actor = await resolvePreToolUseWriteActor(payload, cwd, stateDir, sessionId); | |
| + if (actor !== "native-child") return null; | |
| + const toolName = safeString(payload.tool_name).trim(); | |
| + if (classifyPreToolUseMutationTransport(payload, toolName, cwd) === "read-only") return null; | |
| + if (await resolveAuthorizedSendMessageChildToLeader(payload, toolName, cwd, sessionId)) return null; | |
| + const reason = "OWNER_CONFIRMATION_REQUIRED: native Ultragoal bootstrap is not active; spawned executors remain read-only until the exact assignment/receipt validates and Main-root completes activation."; | |
| + return { | |
| + decision: "block", | |
| + reason, | |
| + hookSpecificOutput: { | |
| + hookEventName: "PreToolUse", | |
| + additionalContext: reason, | |
| + }, | |
| + }; | |
| +} | |
| + | |
| function normalizeRepoRelativePath(cwd: string, rawPath: string): string | null { | |
| const candidate = rawPath.trim(); | |
| if (!candidate || isUnresolvedVariableTarget(candidate)) return null; | |
| @@ -11071,6 +11481,14 @@ function conductorPathTraversesLink(cwd: string, relativePath: string): boolean | |
| return false; | |
| } | |
| +function isSessionBoundNativeAssignmentPath(cwd: string, rawPath: string, sessionId: string): boolean { | |
| + const relativePath = normalizeRepoRelativePath(cwd, rawPath); | |
| + const match = relativePath | |
| + ? /^\.omx\/state\/sessions\/([^/]+)\/native-assignments\/[^/]+\.json$/.exec(relativePath) | |
| + : null; | |
| + return sessionId !== "" && match?.[1] === sessionId; | |
| +} | |
| + | |
| function isAllowedConductorMetadataPath(cwd: string, rawPath: string): boolean { | |
| const relativePath = normalizeRepoRelativePath(cwd, rawPath); | |
| if (!relativePath || conductorPathnameExpansionIsAmbiguous(relativePath)) return false; | |
| @@ -19822,6 +20240,85 @@ function conductorStatePayloadPreservesActiveGuard( | |
| return phase !== "" && isNonTerminalPhase(phase); | |
| } | |
| +function nativeAssignmentTargetIsWithinScope( | |
| + cwd: string, | |
| + rawTarget: string, | |
| + pathPrefixes: readonly string[], | |
| +): boolean { | |
| + const relativeTarget = normalizePlanningArtifactRelativePath(cwd, rawTarget); | |
| + if (!relativeTarget || conductorPathnameExpansionIsAmbiguous(relativeTarget)) return false; | |
| + if (conductorPathTraversesLink(cwd, relativeTarget)) return false; | |
| + if ( | |
| + relativeTarget === ".git" | |
| + || relativeTarget.startsWith(".git/") | |
| + || relativeTarget === ".omx" | |
| + || relativeTarget.startsWith(".omx/") | |
| + ) return false; | |
| + return pathPrefixes.some((prefix) => ( | |
| + prefix === "." | |
| + || relativeTarget === prefix | |
| + || relativeTarget.startsWith(`${prefix}/`) | |
| + )); | |
| +} | |
| + | |
| +function nativeAssignedBashHasUnsafeStructure(cwd: string, command: string): boolean { | |
| + const normalized = normalizeShellLineContinuations(command); | |
| + if ( | |
| + splitConductorShellSegments(stripHeredocBodiesForCommandScan(normalized)).length !== 1 | |
| + || hasUnresolvedShellArithmeticExpansion(normalized) | |
| + || hasUnquotedShellSubstitution(normalized) | |
| + || hasDynamicNestedShellExecution(normalized) | |
| + || hasUnsafeUnquotedHeredocExpansion(normalized) | |
| + || commandHasUnsafeConductorShellState(normalized, cwd) | |
| + || conductorCommandMayMutatePathResolution(normalized) | |
| + ) return true; | |
| + const interpreterWrites = extractConductorInterpreterWrites(normalized); | |
| + return interpreterWrites.some((write) => write.unresolved || write.targets.length === 0); | |
| +} | |
| + | |
| +async function nativeChildAssignmentAllowsMutation( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| + toolName: string, | |
| + mutationTransport: PreToolUseMutationTransport, | |
| +): Promise<boolean> { | |
| + if ( | |
| + mutationTransport === "state" | |
| + || mutationTransport === "orchestration" | |
| + || mutationTransport === "goal-lifecycle" | |
| + || mutationTransport === "unknown" | |
| + ) { | |
| + return false; | |
| + } | |
| + const assignment = await readNativeSubagentAssignment(payload, cwd, stateDir, sessionId); | |
| + if (!assignment) return false; | |
| + | |
| + if (mutationTransport === "path") { | |
| + if (!assignment.allowed_actions.includes("path-write")) return false; | |
| + const candidates = collectImplementationToolPathCandidates( | |
| + payload, | |
| + toolName, | |
| + readPreToolUsePathCandidates(payload), | |
| + ); | |
| + return candidates.length > 0 | |
| + && candidates.every((candidate) => ( | |
| + !isNativeAssignmentPath(stateDir, candidate, cwd) | |
| + && nativeAssignmentTargetIsWithinScope(cwd, candidate, assignment.path_prefixes) | |
| + )); | |
| + } | |
| + if (mutationTransport !== "bash") return false; | |
| + | |
| + const command = readPreToolUseCommand(payload); | |
| + if (nativeAssignedBashHasUnsafeStructure(cwd, command)) return false; | |
| + if (!assignment.allowed_actions.includes("bash-write")) return false; | |
| + const targets = extractDeepInterviewCommandWriteTargets(command, cwd, cwd); | |
| + if (targets.some((target) => isNativeAssignmentPath(stateDir, target, cwd))) return false; | |
| + return targets.length > 0 && targets.every((target) => ( | |
| + nativeAssignmentTargetIsWithinScope(cwd, target, assignment.path_prefixes) | |
| + )); | |
| +} | |
| function buildConductorSessionProvenanceDeny( | |
| activeState: ActiveConductorState, | |
| @@ -19872,6 +20369,12 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| const pathCandidates = readPreToolUsePathCandidates(payload); | |
| const mutationTransport = classifyPreToolUseMutationTransport(payload, toolName, cwd); | |
| + if ( | |
| + writeActor === "main-root" | |
| + && toolName === "Bash" | |
| + && isExactNativeUltragoalBootstrapCommand(command) | |
| + ) return null; | |
| + | |
| let blocked = false; | |
| let blockedDetail = "Main-root Conductor write is not delegated"; | |
| let nativeChildMutationAttempt = false; | |
| @@ -19925,7 +20428,10 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| blocked = true; | |
| blockedDetail = describeConductorBlockedWrite(toolName, undefined, toolPathCandidates.length); | |
| } else { | |
| - const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataExecutionPath(cwd, policyCwd, candidate)); | |
| + const blockedPath = toolPathCandidates.find((candidate) => ( | |
| + !isSessionBoundNativeAssignmentPath(policyCwd, candidate, sessionId) | |
| + && !isAllowedConductorMetadataExecutionPath(cwd, policyCwd, candidate) | |
| + )); | |
| blocked = blockedPath !== undefined; | |
| if (blockedPath !== undefined) { | |
| blockedDetail = describeConductorBlockedWrite(toolName, blockedPath, toolPathCandidates.length); | |
| @@ -19945,11 +20451,23 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| stateDir, | |
| policyCwd, | |
| ); | |
| - if (writeActor === "team-worker" && !teamWorkerProtectedStateTarget) return null; | |
| + if (writeActor === "team-worker" && mutationTransport !== "goal-lifecycle" && !teamWorkerProtectedStateTarget) return null; | |
| if (writeActor === "team-worker" && teamWorkerProtectedStateTarget && !blocked) { | |
| blocked = true; | |
| blockedDetail = "Bash targets protected workflow state outside authorized Team-worker scope"; | |
| } | |
| + if ( | |
| + writeActor === "native-child" | |
| + && nativeChildMutationAttempt | |
| + && await nativeChildAssignmentAllowsMutation( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + sessionId, | |
| + toolName, | |
| + mutationTransport, | |
| + ) | |
| + ) return null; | |
| if (!blocked && (writeActor !== "native-child" || !nativeChildMutationAttempt)) return null; | |
| if (!blocked && nativeChildMutationAttempt && writeActor === "native-child") { | |
| blockedDetail = toolName === "Bash" | |
| @@ -21963,6 +22481,28 @@ export async function dispatchCodexNativeHook( | |
| }, | |
| }); | |
| } | |
| + if ( | |
| + skillState?.active === true | |
| + && skillState.initialized_mode === "ultragoal" | |
| + && sessionIdForState | |
| + ) { | |
| + const bootstrap = await readNativeUltragoalBootstrap(stateDir, sessionIdForState); | |
| + let receiptConfirmed = bootstrap?.status === "active"; | |
| + if (bootstrap && ["ready", "activating"].includes(bootstrap.status) && bootstrap.child_agent_id) { | |
| + const confirmed = await markNativeUltragoalBootstrapActive({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId: sessionIdForState, | |
| + childAgentId: bootstrap.child_agent_id, | |
| + }); | |
| + receiptConfirmed = confirmed.ok; | |
| + } | |
| + if (!receiptConfirmed && resolveNativeUltragoalConfig().backend === "local-experimental") { | |
| + const reason = "native_ultragoal_activation_receipt_invalid"; | |
| + await failNativeUltragoalActivation(cwd, stateDir, sessionIdForState, reason); | |
| + skillState = { ...skillState, active: false, transition_error: reason }; | |
| + } | |
| + } | |
| // --- Triage classifier (advisory-only, non-keyword prompts) --- | |
| if ( | |
| prompt | |
| @@ -22204,7 +22744,16 @@ export async function dispatchCodexNativeHook( | |
| ? (await readStopSessionPinnedState("autopilot-state.json", policyCwd, sessionBinding.canonicalSessionId, stateDir) ?? {}) | |
| : {}, | |
| }); | |
| - if (directCancel.kind !== "not-direct-cancel") outputJson = directCancel.output; | |
| + if (directCancel.kind !== "not-direct-cancel") { | |
| + if (directCancel.kind === "handled" && sessionBinding.canonicalSessionId) { | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir, | |
| + sessionId: sessionBinding.canonicalSessionId, | |
| + reason: "root_cancelled", | |
| + }); | |
| + } | |
| + outputJson = directCancel.output; | |
| + } | |
| } | |
| if (!outputJson && !policyRoot.valid && policyRoot.statePresent && mutationTransport !== "read-only") { | |
| outputJson = buildConductorSessionProvenanceDeny( | |
| @@ -22326,6 +22875,12 @@ export async function dispatchCodexNativeHook( | |
| })(); | |
| outputJson = nativePreToolUseSpecificDeny | |
| ?? buildNativeUnknownRolePreToolUseOutput(payload, policyCwd) | |
| + ?? await buildPendingNativeUltragoalExecutorGuardOutput( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + preToolUseSessionId, | |
| + ) | |
| ?? await buildDeepInterviewPreToolUseBoundaryOutput( | |
| payload, | |
| policyCwd, | |
| @@ -22346,6 +22901,12 @@ export async function dispatchCodexNativeHook( | |
| stateDir, | |
| rootPointerConflict, | |
| ) | |
| + ?? await buildNativeUltragoalBootstrapMutationGuardOutput( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + preToolUseSessionId, | |
| + ) | |
| ?? await buildUltragoalNoOwnerActivationGuardOutput( | |
| payload, | |
| cwd, | |
| @@ -22360,7 +22921,13 @@ export async function dispatchCodexNativeHook( | |
| preToolUseSessionId, | |
| policyCwd, | |
| ) | |
| - ?? buildRawProtectedWorkflowStatePathOutput(payload, policyCwd, stateDir) | |
| + ?? await buildRawProtectedWorkflowStatePathOutput( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + preToolUseSessionId, | |
| + guardedConductorState !== null, | |
| + ) | |
| ?? await buildNativeSubagentCapacityCloseGuardOutput(payload, policyCwd, stateDir) | |
| ?? buildMalformedPreToolUseBlockTestOutput(payload) | |
| ?? buildNativePreToolUseOutput(payload); | |
| @@ -22375,6 +22942,34 @@ export async function dispatchCodexNativeHook( | |
| } | |
| await handleTeamWorkerPostToolUseSuccess(payload, cwd); | |
| } | |
| + if (canonicalSessionId) { | |
| + const toolName = safeString(payload.tool_name).trim(); | |
| + const activationCandidates = collectUltragoalActivationCandidatePayloads(payload, toolName, cwd); | |
| + if (activationCandidates.some((candidate) => isUltragoalConductorActivationPayload(candidate))) { | |
| + const bootstrap = await readNativeUltragoalBootstrap(stateDir, canonicalSessionId); | |
| + const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, canonicalSessionId, stateDir); | |
| + if (isActiveConductorModeState(state, "ultragoal", canonicalSessionId)) { | |
| + let receiptConfirmed = bootstrap?.status === "active"; | |
| + if (bootstrap?.status === "activating" && bootstrap.child_agent_id) { | |
| + const confirmed = await markNativeUltragoalBootstrapActive({ | |
| + cwd, | |
| + stateDir, | |
| + sessionId: canonicalSessionId, | |
| + childAgentId: bootstrap.child_agent_id, | |
| + }); | |
| + receiptConfirmed = confirmed.ok; | |
| + } | |
| + if (!receiptConfirmed && resolveNativeUltragoalConfig().backend === "local-experimental") { | |
| + await failNativeUltragoalActivation( | |
| + cwd, | |
| + stateDir, | |
| + canonicalSessionId, | |
| + "native_ultragoal_activation_receipt_invalid", | |
| + ); | |
| + } | |
| + } | |
| + } | |
| + } | |
| outputJson = buildNativePostToolUseOutput(payload); | |
| } else if (hookEventName === "Stop") { | |
| if (declaredTeamWorkerStopOnly) { | |
| @@ -22413,6 +23008,9 @@ export async function dispatchCodexNativeHook( | |
| }; | |
| } | |
| } | |
| + if (!declaredTeamWorkerStopOnly && canonicalSessionId) { | |
| + await revokeTerminalNativeUltragoalBootstrap(policyCwd, stateDir, canonicalSessionId); | |
| + } | |
| } | |
| return { | |
| diff --git a/src/subagents/__tests__/tracker.test.ts b/src/subagents/__tests__/tracker.test.ts | |
| index f5ace531..bedb9e6b 100644 | |
| --- a/src/subagents/__tests__/tracker.test.ts | |
| +++ b/src/subagents/__tests__/tracker.test.ts | |
| @@ -5,7 +5,7 @@ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; | |
| import { hostname, tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { describe, it } from 'node:test'; | |
| -import { __setCrossProcessPublishBarrierForTest, __setCrossProcessQuarantineBarrierForTest, consumeDirectChildReopenContext, CrossProcessLockLostError, buildSubagentResumeLedger, CROSS_PROCESS_LOCK_ARTIFACT_SWEEP_CAP, CROSS_PROCESS_LOCK_LEASE_MS, createSubagentTrackingState, crossProcessLockPath, DESCRIPTIVE_ADAPTED_PROVENANCE, fenceNativeSubagentAuthorities, forceGlobalAuthorityDenial, recordNativeSubagentAuthorityObservation, recordSubagentTurn, recordSubagentTurnForSession, NATIVE_SUBAGENT_PROVENANCE, readProcessStartIdentity, readSubagentTrackingState, readSubagentTrackingStateStrict, repairPersistedRootIdentity, revokeNativeSubagentAuthorities, selectReusableSubagentEntry, subagentTrackingPath, summarizeSubagentSession, withCrossProcessFileLockSync, writeSubagentTrackingState } from '../tracker.js'; | |
| +import { acquireCrossProcessFileLock, __setCrossProcessPublishBarrierForTest, __setCrossProcessQuarantineBarrierForTest, consumeDirectChildReopenContext, CrossProcessLockLostError, buildSubagentResumeLedger, CROSS_PROCESS_LOCK_ARTIFACT_SWEEP_CAP, CROSS_PROCESS_LOCK_LEASE_MS, createSubagentTrackingState, crossProcessLockPath, DESCRIPTIVE_ADAPTED_PROVENANCE, fenceNativeSubagentAuthorities, forceGlobalAuthorityDenial, recordNativeSubagentAuthorityObservation, recordSubagentTurn, recordSubagentTurnForSession, NATIVE_SUBAGENT_PROVENANCE, readProcessStartIdentity, readSubagentTrackingState, readSubagentTrackingStateStrict, repairPersistedRootIdentity, revokeNativeSubagentAuthorities, selectReusableSubagentEntry, subagentTrackingPath, summarizeSubagentSession, withCrossProcessFileLockSync, writeSubagentTrackingState } from '../tracker.js'; | |
| import { NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_FILE, readRoleRoutingMarker, writeRoleRoutingMarker } from '../role-routing-marker.js'; | |
| const CROSS_PROCESS_LOCK_HOLDER_SOURCE = ` | |
| @@ -1880,6 +1880,31 @@ describe('subagents/tracker', () => { | |
| } | |
| }); | |
| + it('can preserve a live local async owner beyond the legacy lease', async () => { | |
| + const cwd = await mkdtemp(join(tmpdir(), 'omx-subagent-tracker-')); | |
| + const resourcePath = join(cwd, 'lock-resource'); | |
| + const lockPath = crossProcessLockPath(resourcePath); | |
| + try { | |
| + const liveClaim = crossProcessLockClaim( | |
| + 'live-async-owner', | |
| + process.pid, | |
| + Date.now() - CROSS_PROCESS_LOCK_LEASE_MS - 1, | |
| + ); | |
| + await writeFile(lockPath, liveClaim); | |
| + await assert.rejects( | |
| + acquireCrossProcessFileLock(resourcePath, { | |
| + maxAttempts: 1, | |
| + retryMs: 1, | |
| + preserveLiveLocalOwner: true, | |
| + }), | |
| + /Timed out waiting for cross-process lock/, | |
| + ); | |
| + assert.equal(readFileSync(lockPath, 'utf-8'), liveClaim); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| it('does not steal a live same-host identity-matched claim after its lease or mistake its token for ours', async () => { | |
| const cwd = await mkdtemp(join(tmpdir(), 'omx-subagent-tracker-')); | |
| const resourcePath = join(cwd, 'lock-resource'); | |
| diff --git a/src/subagents/tracker.ts b/src/subagents/tracker.ts | |
| index 7823b252..986516cd 100644 | |
| --- a/src/subagents/tracker.ts | |
| +++ b/src/subagents/tracker.ts | |
| @@ -567,12 +567,16 @@ function isCrossProcessLockOlderThanLease(acquiredAtMs: number, nowMs: number): | |
| return acquiredAtMs < nowMs - CROSS_PROCESS_LOCK_LEASE_MS; | |
| } | |
| -function isCrossProcessLockReclaimable(claim: CrossProcessLockClaim): boolean { | |
| +function isCrossProcessLockReclaimable( | |
| + claim: CrossProcessLockClaim, | |
| + options: { preserveLiveLocalOwner?: boolean } = {}, | |
| +): boolean { | |
| if (claim.host !== hostname()) return isCrossProcessLockOlderThanLease(claim.acquiredAtMs, Date.now()); | |
| if (isCrossProcessLockOwnerDead(claim.pid)) return true; | |
| const currentPidStartId = readProcessStartIdentity(claim.pid); | |
| if (claim.pidStartId && currentPidStartId) return currentPidStartId !== claim.pidStartId; | |
| + if (options.preserveLiveLocalOwner) return false; | |
| return isCrossProcessLockOlderThanLease(claim.acquiredAtMs, Date.now()); | |
| } | |
| @@ -624,9 +628,13 @@ function restoreQuarantinedCrossProcessLock(lockPath: string, quarantinedPath: s | |
| removeCrossProcessLockFile(quarantinedPath); | |
| } | |
| -function recoverCrossProcessFileLock(lockPath: string, observed: CrossProcessLockState): boolean { | |
| +function recoverCrossProcessFileLock( | |
| + lockPath: string, | |
| + observed: CrossProcessLockState, | |
| + options: { preserveLiveLocalOwner?: boolean } = {}, | |
| +): boolean { | |
| if (observed.kind === 'missing') return true; | |
| - if (observed.kind === 'claim' && !isCrossProcessLockReclaimable(observed.claim)) return false; | |
| + if (observed.kind === 'claim' && !isCrossProcessLockReclaimable(observed.claim, options)) return false; | |
| const quarantinedPath = `${lockPath}.${Date.now()}.${randomUUID()}.quarantine`; | |
| try { | |
| @@ -644,7 +652,11 @@ function recoverCrossProcessFileLock(lockPath: string, observed: CrossProcessLoc | |
| && captured.kind === 'claim' | |
| && captured.claim.token === observed.claim.token; | |
| const capturedRecoverable = captured.kind === 'malformed' | |
| - || (capturedExpectedClaim && captured.kind === 'claim' && isCrossProcessLockReclaimable(captured.claim)); | |
| + || ( | |
| + capturedExpectedClaim | |
| + && captured.kind === 'claim' | |
| + && isCrossProcessLockReclaimable(captured.claim, options) | |
| + ); | |
| if (!capturedRecoverable) { | |
| restoreQuarantinedCrossProcessLock(lockPath, quarantinedPath); | |
| return true; | |
| @@ -742,6 +754,47 @@ export function __setCrossProcessQuarantineBarrierForTest( | |
| crossProcessQuarantineBarrier = barrier; | |
| } | |
| +export type CrossProcessFileLockHandle = { | |
| + assertOwnership(): void; | |
| + release(): void; | |
| +}; | |
| + | |
| +/** Acquire the shared recoverable lock for an async caller that owns its writes. */ | |
| +export async function acquireCrossProcessFileLock( | |
| + resourcePath: string, | |
| + options: { maxAttempts?: number; retryMs?: number; preserveLiveLocalOwner?: boolean } = {}, | |
| +): Promise<CrossProcessFileLockHandle> { | |
| + const lockPath = crossProcessLockPath(resourcePath); | |
| + const maxAttempts = typeof options.maxAttempts === 'number' && Number.isFinite(options.maxAttempts) | |
| + ? Math.max(1, Math.floor(options.maxAttempts)) | |
| + : DEFAULT_CROSS_PROCESS_LOCK_MAX_ATTEMPTS; | |
| + const retryMs = typeof options.retryMs === 'number' && Number.isFinite(options.retryMs) | |
| + ? Math.max(1, Math.floor(options.retryMs)) | |
| + : DEFAULT_CROSS_PROCESS_LOCK_RETRY_MS; | |
| + mkdirSync(dirname(lockPath), { recursive: true }); | |
| + const token = randomUUID(); | |
| + for (let attempt = 0; attempt < maxAttempts; attempt += 1) { | |
| + if ( | |
| + tryAcquireCrossProcessFileLock(lockPath, token) | |
| + || ( | |
| + recoverCrossProcessFileLock(lockPath, readCrossProcessLockState(lockPath), options) | |
| + && tryAcquireCrossProcessFileLock(lockPath, token) | |
| + ) | |
| + ) { | |
| + return { | |
| + assertOwnership: () => assertCrossProcessFileLockOwnership(lockPath, token), | |
| + release: () => releaseCrossProcessFileLock(lockPath, token), | |
| + }; | |
| + } | |
| + if (attempt < maxAttempts - 1) { | |
| + await new Promise((resolveDelay) => { | |
| + setTimeout(resolveDelay, Math.min(25, retryMs * 2 ** Math.min(attempt, 4))); | |
| + }); | |
| + } | |
| + } | |
| + throw new Error(`Timed out waiting for cross-process lock at ${lockPath}`); | |
| +} | |
| + | |
| export function withCrossProcessFileLockSync<T>( | |
| resourcePath: string, | |
| operation: (context: CrossProcessFileLockContext) => T, | |
| @@ -1182,6 +1235,12 @@ function readAuthorityFence(cwd: string): { ok: boolean; ids: Set<string> } { | |
| } | |
| } | |
| +/** True when durable authority state requires fail-closed denial for this child. */ | |
| +export function isNativeSubagentAuthorityFenced(cwd: string, childThreadId: string): boolean { | |
| + const fence = readAuthorityFence(cwd); | |
| + return !fence.ok || fence.ids.has(childThreadId.trim()); | |
| +} | |
| + | |
| /** Upper bound on fenced ids, so repeated failures cannot grow the fence without limit. */ | |
| export const AUTHORITY_FENCE_MAX_IDS = 512; | |
| diff --git a/src/ultragoal/__tests__/native-bootstrap.test.ts b/src/ultragoal/__tests__/native-bootstrap.test.ts | |
| new file mode 100644 | |
| index 00000000..e25ac483 | |
| --- /dev/null | |
| +++ b/src/ultragoal/__tests__/native-bootstrap.test.ts | |
| @@ -0,0 +1,454 @@ | |
| +import assert from 'node:assert/strict'; | |
| +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; | |
| +import { tmpdir } from 'node:os'; | |
| +import { dirname, join } from 'node:path'; | |
| +import { describe, it } from 'node:test'; | |
| +import { | |
| + beginNativeUltragoalBootstrap, | |
| + DOCUMENTED_HOST_RECEIPT_UNAVAILABLE, | |
| + findReadyLocalNativeExecutorAssignment, | |
| + issueLocalNativeExecutorAssignment, | |
| + markNativeUltragoalBootstrapActive, | |
| + nativeExecutorAssignmentPath, | |
| + prepareNativeUltragoalBootstrapActivation, | |
| + readNativeUltragoalBootstrap, | |
| + reconcileLocalNativeExecutorAssignments, | |
| + revokeLocalNativeExecutorAssignment, | |
| + unavailableHostNativeExecutorReceiptVerifier, | |
| +} from '../native-bootstrap.js'; | |
| + | |
| +async function writeJson(path: string, value: unknown): Promise<void> { | |
| + await mkdir(dirname(path), { recursive: true }); | |
| + await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); | |
| +} | |
| + | |
| +async function fixture() { | |
| + const cwd = await mkdtemp(join(tmpdir(), 'omx-native-bootstrap-')); | |
| + const stateDir = join(cwd, '.omx', 'state'); | |
| + const sessionId = 'session-native-bootstrap'; | |
| + const rootThreadId = 'root-native-bootstrap'; | |
| + const childAgentId = 'child-native-bootstrap'; | |
| + const now = new Date('2026-07-28T12:00:00.000Z'); | |
| + await writeJson(join(stateDir, 'subagent-tracking.json'), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: rootThreadId, | |
| + updated_at: now.toISOString(), | |
| + threads: { | |
| + [rootThreadId]: { | |
| + thread_id: rootThreadId, | |
| + kind: 'leader', | |
| + first_seen_at: now.toISOString(), | |
| + last_seen_at: now.toISOString(), | |
| + turn_count: 1, | |
| + }, | |
| + [childAgentId]: { | |
| + thread_id: childAgentId, | |
| + kind: 'subagent', | |
| + mode: 'executor', | |
| + status: 'available', | |
| + provenance_kind: 'native_subagent', | |
| + direct_child_parent_id: rootThreadId, | |
| + direct_child_root_id: rootThreadId, | |
| + first_seen_at: now.toISOString(), | |
| + last_seen_at: now.toISOString(), | |
| + turn_count: 1, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + return { cwd, stateDir, sessionId, rootThreadId, childAgentId, now }; | |
| +} | |
| + | |
| +describe('native Ultragoal bootstrap', () => { | |
| + it('keeps the host backend explicitly unavailable without a documented verifier', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const state = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'host', | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(state.status, 'host-receipt-unavailable'); | |
| + assert.equal(state.reason, DOCUMENTED_HOST_RECEIPT_UNAVAILABLE); | |
| + assert.deepEqual(await unavailableHostNativeExecutorReceiptVerifier.verifyActivationReceipt({ | |
| + cwd: f.cwd, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + }), { ok: false, reason: DOCUMENTED_HOST_RECEIPT_UNAVAILABLE }); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('orders local bootstrap before assignment and activation', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(bootstrap.status, 'awaiting-executor'); | |
| + assert.equal(await findReadyLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + now: f.now, | |
| + }), null); | |
| + | |
| + await assert.rejects( | |
| + issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['$UNRESOLVED/*'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), | |
| + /invalid_native_assignment_path_prefixes/, | |
| + ); | |
| + | |
| + const assignment = await issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src', 'tests'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(assignment.bootstrap_generation, bootstrap.generation); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'ready'); | |
| + assert.equal((await findReadyLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + now: f.now, | |
| + }))?.child_agent_id, f.childAgentId); | |
| + | |
| + const prepared = await prepareNativeUltragoalBootstrapActivation({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + childAgentId: f.childAgentId, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(prepared.ok, true); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'activating'); | |
| + const activation = await markNativeUltragoalBootstrapActive({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + childAgentId: f.childAgentId, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(activation.ok, true); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'active'); | |
| + assert.equal((await markNativeUltragoalBootstrapActive({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + childAgentId: f.childAgentId, | |
| + now: f.now, | |
| + })).ok, true); | |
| + | |
| + const renewed = await issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 600, | |
| + now: new Date(f.now.getTime() + 60_000), | |
| + }); | |
| + assert.equal(renewed.bootstrap_generation, bootstrap.generation); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'active'); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('revokes a local lease when tracker lifecycle closes and supports explicit revoke', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + await issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }); | |
| + const trackerPath = join(f.stateDir, 'subagent-tracking.json'); | |
| + const tracker = JSON.parse(await readFile(trackerPath, 'utf-8')) as { | |
| + sessions: Record<string, { threads: Record<string, { status: string }> }>; | |
| + }; | |
| + tracker.sessions[f.sessionId].threads[f.childAgentId].status = 'closed'; | |
| + await writeJson(trackerPath, tracker); | |
| + assert.deepEqual(await reconcileLocalNativeExecutorAssignments({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + now: f.now, | |
| + }), { ready: false }); | |
| + const assignment = JSON.parse(await readFile( | |
| + nativeExecutorAssignmentPath(f.stateDir, f.sessionId, f.childAgentId), | |
| + 'utf-8', | |
| + )) as { lifecycle: string; revoke_reason?: string }; | |
| + assert.equal(assignment.lifecycle, 'revoked'); | |
| + assert.equal(assignment.revoke_reason, 'child_closed'); | |
| + | |
| + await revokeLocalNativeExecutorAssignment({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + reason: 'root_cancelled', | |
| + now: f.now, | |
| + }); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'revoked'); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('denies explicit and fail-closed durable authority fences', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + const fencePath = join(f.stateDir, 'subagent-tracking.json.authority-fence.json'); | |
| + await writeJson(fencePath, { ids: [f.childAgentId] }); | |
| + await assert.rejects(issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), /child_authority_fenced/); | |
| + | |
| + await writeFile(fencePath, '{ malformed'); | |
| + await assert.rejects(issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), /child_authority_fenced/); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('rotates expired bootstrap generations and cannot activate without the exact assignment file', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const first = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 30, | |
| + now: f.now, | |
| + }); | |
| + await issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: first.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 30, | |
| + now: f.now, | |
| + }); | |
| + const assignmentPath = nativeExecutorAssignmentPath(f.stateDir, f.sessionId, f.childAgentId); | |
| + await rm(assignmentPath); | |
| + const missingAssignmentActivation = await markNativeUltragoalBootstrapActive({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + childAgentId: f.childAgentId, | |
| + now: f.now, | |
| + }); | |
| + assert.equal(missingAssignmentActivation.ok, false); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'ready'); | |
| + | |
| + const second = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 30, | |
| + now: new Date(f.now.getTime() + 31_000), | |
| + }); | |
| + assert.notEqual(second.generation, first.generation); | |
| + assert.equal(second.status, 'awaiting-executor'); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('serializes authorize before revoke so stale readiness cannot resurrect authority', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + const concurrent = await Promise.allSettled([ | |
| + issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), | |
| + revokeLocalNativeExecutorAssignment({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + reason: 'concurrent_root_revoke', | |
| + now: f.now, | |
| + }), | |
| + ]); | |
| + if (concurrent[0].status === 'rejected') { | |
| + assert.match(String(concurrent[0].reason), /native_ultragoal_bootstrap_not_ready/); | |
| + } | |
| + assert.equal(concurrent[1].status, 'fulfilled'); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'revoked'); | |
| + const assignmentPath = nativeExecutorAssignmentPath(f.stateDir, f.sessionId, f.childAgentId); | |
| + if (concurrent[0].status === 'fulfilled') { | |
| + const assignment = JSON.parse(await readFile(assignmentPath, 'utf-8')) as { | |
| + lifecycle: string; | |
| + revoke_reason?: string; | |
| + }; | |
| + assert.equal(assignment.lifecycle, 'revoked'); | |
| + assert.equal(assignment.revoke_reason, 'concurrent_root_revoke'); | |
| + } else { | |
| + await assert.rejects(readFile(assignmentPath), /ENOENT/); | |
| + } | |
| + const replacement = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + await assert.rejects(issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), /native_ultragoal_bootstrap_not_ready/); | |
| + assert.notEqual(replacement.generation, bootstrap.generation); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it('recovers one dead lifecycle lock without letting concurrent mutations overlap', async () => { | |
| + const f = await fixture(); | |
| + try { | |
| + const bootstrap = await beginNativeUltragoalBootstrap({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + backend: 'local-experimental', | |
| + ttlSeconds: 900, | |
| + now: f.now, | |
| + }); | |
| + const lockPath = join( | |
| + f.stateDir, | |
| + 'sessions', | |
| + f.sessionId, | |
| + 'native-ultragoal-bootstrap.json.lock', | |
| + ); | |
| + await writeJson(lockPath, { pid: 2_147_483_647, token: 'dead-owner' }); | |
| + const concurrent = await Promise.allSettled([ | |
| + issueLocalNativeExecutorAssignment({ | |
| + cwd: f.cwd, | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + rootThreadId: f.rootThreadId, | |
| + childAgentId: f.childAgentId, | |
| + expectedGeneration: bootstrap.generation, | |
| + pathPrefixes: ['src'], | |
| + ttlSeconds: 300, | |
| + now: f.now, | |
| + }), | |
| + revokeLocalNativeExecutorAssignment({ | |
| + stateDir: f.stateDir, | |
| + sessionId: f.sessionId, | |
| + reason: 'dead_lock_concurrent_revoke', | |
| + now: f.now, | |
| + }), | |
| + ]); | |
| + if (concurrent[0].status === 'rejected') { | |
| + assert.match(String(concurrent[0].reason), /native_ultragoal_bootstrap_not_ready/); | |
| + } | |
| + assert.equal(concurrent[1].status, 'fulfilled'); | |
| + assert.equal((await readNativeUltragoalBootstrap(f.stateDir, f.sessionId))?.status, 'revoked'); | |
| + await assert.rejects(readFile(lockPath), /ENOENT/); | |
| + await assert.rejects(readFile(`${lockPath}.reaper`), /ENOENT/); | |
| + } finally { | |
| + await rm(f.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| +}); | |
| diff --git a/src/ultragoal/native-bootstrap.ts b/src/ultragoal/native-bootstrap.ts | |
| new file mode 100644 | |
| index 00000000..b9324f7f | |
| --- /dev/null | |
| +++ b/src/ultragoal/native-bootstrap.ts | |
| @@ -0,0 +1,757 @@ | |
| +import { randomUUID } from 'node:crypto'; | |
| +import { constants as fsConstants } from 'node:fs'; | |
| +import { access, chmod, lstat, mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises'; | |
| +import { dirname, isAbsolute, join, relative, resolve } from 'node:path'; | |
| +import { | |
| + acquireCrossProcessFileLock, | |
| + isNativeSubagentAuthorityFenced, | |
| + NATIVE_SUBAGENT_PROVENANCE, | |
| + readSubagentTrackingState, | |
| +} from '../subagents/tracker.js'; | |
| +import type { NativeUltragoalBackend } from '../config/native-ultragoal.js'; | |
| + | |
| +export const NATIVE_ULTRAGOAL_BOOTSTRAP_FILE = 'native-ultragoal-bootstrap.json'; | |
| +export const NATIVE_ASSIGNMENT_DIRECTORY = 'native-assignments'; | |
| +export const DOCUMENTED_HOST_RECEIPT_UNAVAILABLE = 'documented_host_native_executor_receipt_unavailable'; | |
| + | |
| +export type NativeUltragoalBootstrapStatus = | |
| + | 'awaiting-executor' | |
| + | 'ready' | |
| + | 'activating' | |
| + | 'active' | |
| + | 'revoked' | |
| + | 'expired' | |
| + | 'host-receipt-unavailable'; | |
| + | |
| +export interface NativeUltragoalBootstrapState { | |
| + schema_version: 1; | |
| + kind: 'native-ultragoal-bootstrap'; | |
| + generation: string; | |
| + backend: NativeUltragoalBackend; | |
| + status: NativeUltragoalBootstrapStatus; | |
| + session_id: string; | |
| + root_thread_id: string; | |
| + child_agent_id?: string; | |
| + created_at: string; | |
| + updated_at: string; | |
| + expires_at: string; | |
| + reason?: string; | |
| +} | |
| + | |
| +export interface LocalNativeExecutorAssignment { | |
| + schema_version: 1; | |
| + kind: 'native-subagent-assignment'; | |
| + lifecycle: 'active' | 'revoked' | 'expired'; | |
| + root_session_id: string; | |
| + root_thread_id: string; | |
| + child_agent_id: string; | |
| + agent_type: 'executor'; | |
| + allowed_actions: Array<'path-write' | 'bash-write'>; | |
| + path_prefixes: string[]; | |
| + issued_at: string; | |
| + expires_at: string; | |
| + bootstrap_generation: string; | |
| + revoked_at?: string; | |
| + revoke_reason?: string; | |
| +} | |
| + | |
| +export interface HostNativeExecutorReceiptVerifier { | |
| + readonly available: boolean; | |
| + verifyActivationReceipt(input: { | |
| + cwd: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + }): Promise<{ ok: true; childAgentId: string } | { ok: false; reason: string }>; | |
| +} | |
| + | |
| +const ACTIVE_ASSIGNMENT_SCHEMA_KEYS = new Set([ | |
| + 'schema_version', | |
| + 'kind', | |
| + 'lifecycle', | |
| + 'root_session_id', | |
| + 'root_thread_id', | |
| + 'child_agent_id', | |
| + 'agent_type', | |
| + 'allowed_actions', | |
| + 'path_prefixes', | |
| + 'issued_at', | |
| + 'expires_at', | |
| + 'bootstrap_generation', | |
| +]); | |
| +const MAX_ASSIGNMENT_LIFETIME_MS = 24 * 60 * 60 * 1000; | |
| + | |
| +export const unavailableHostNativeExecutorReceiptVerifier: HostNativeExecutorReceiptVerifier = { | |
| + available: false, | |
| + async verifyActivationReceipt() { | |
| + return { ok: false, reason: DOCUMENTED_HOST_RECEIPT_UNAVAILABLE }; | |
| + }, | |
| +}; | |
| + | |
| +function bootstrapPath(stateDir: string, sessionId: string): string { | |
| + return join(stateDir, 'sessions', sessionId, NATIVE_ULTRAGOAL_BOOTSTRAP_FILE); | |
| +} | |
| + | |
| +async function withNativeUltragoalLifecycleLock<T>( | |
| + stateDir: string, | |
| + sessionId: string, | |
| + operation: () => Promise<T>, | |
| +): Promise<T> { | |
| + const handle = await acquireCrossProcessFileLock(bootstrapPath(stateDir, sessionId), { | |
| + maxAttempts: 200, | |
| + retryMs: 25, | |
| + preserveLiveLocalOwner: true, | |
| + }); | |
| + try { | |
| + const result = await operation(); | |
| + handle.assertOwnership(); | |
| + return result; | |
| + } finally { | |
| + handle.release(); | |
| + } | |
| +} | |
| + | |
| +export function nativeExecutorAssignmentPath(stateDir: string, sessionId: string, childAgentId: string): string { | |
| + return join( | |
| + stateDir, | |
| + 'sessions', | |
| + sessionId, | |
| + NATIVE_ASSIGNMENT_DIRECTORY, | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| +} | |
| + | |
| +async function writeJsonAtomic(path: string, value: unknown, mode = 0o600): Promise<void> { | |
| + await mkdir(dirname(path), { recursive: true, mode: 0o700 }); | |
| + const tmpPath = `${path}.tmp.${process.pid}.${Date.now()}.${randomUUID()}`; | |
| + await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf-8', mode }); | |
| + try { | |
| + await chmod(tmpPath, mode); | |
| + await rename(tmpPath, path); | |
| + } catch (error) { | |
| + await unlink(tmpPath).catch(() => {}); | |
| + throw error; | |
| + } | |
| +} | |
| + | |
| +async function readJsonObject(path: string): Promise<Record<string, unknown> | null> { | |
| + try { | |
| + const parsed = JSON.parse(await readFile(path, 'utf-8')) as unknown; | |
| + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) | |
| + ? parsed as Record<string, unknown> | |
| + : null; | |
| + } catch { | |
| + return null; | |
| + } | |
| +} | |
| + | |
| +function readString(value: unknown): string { | |
| + return typeof value === 'string' ? value.trim() : ''; | |
| +} | |
| + | |
| +function normalizePrefix(cwd: string, rawPrefix: string): string | null { | |
| + const trimmed = rawPrefix.trim().replace(/\\/g, '/'); | |
| + if (trimmed === '.') return '.'; | |
| + if (!trimmed || isAbsolute(trimmed) || trimmed.includes('\0') || /[$`*?\[\]{}]/.test(trimmed)) return null; | |
| + const absolute = resolve(cwd, trimmed); | |
| + const normalized = relative(cwd, absolute).replace(/\\/g, '/'); | |
| + if (!normalized || normalized === '..' || normalized.startsWith('../')) return null; | |
| + if ( | |
| + normalized === '.git' | |
| + || normalized.startsWith('.git/') | |
| + || normalized === '.omx' | |
| + || normalized.startsWith('.omx/') | |
| + ) return null; | |
| + return normalized; | |
| +} | |
| + | |
| +async function pathPrefixTraversesUnsafeLink(cwd: string, relativePath: string): Promise<boolean> { | |
| + if (relativePath === '.') return false; | |
| + let current = resolve(cwd); | |
| + for (const segment of relativePath.split('/').filter(Boolean)) { | |
| + current = join(current, segment); | |
| + try { | |
| + const metadata = await lstat(current); | |
| + if (metadata.isSymbolicLink() || (!metadata.isDirectory() && metadata.nlink > 1)) return true; | |
| + } catch (error) { | |
| + if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue; | |
| + return true; | |
| + } | |
| + } | |
| + return false; | |
| +} | |
| + | |
| +export async function parseActiveLocalNativeExecutorAssignment( | |
| + raw: Record<string, unknown> | null, | |
| + input: { | |
| + cwd: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + childAgentId: string; | |
| + agentType: string; | |
| + nowMs?: number; | |
| + }, | |
| +): Promise<(LocalNativeExecutorAssignment & { lifecycle: 'active' }) | null> { | |
| + if (!raw || Object.keys(raw).some((key) => !ACTIVE_ASSIGNMENT_SCHEMA_KEYS.has(key))) return null; | |
| + if ( | |
| + raw.schema_version !== 1 | |
| + || raw.kind !== 'native-subagent-assignment' | |
| + || raw.lifecycle !== 'active' | |
| + || readString(raw.root_session_id) !== input.sessionId | |
| + || readString(raw.root_thread_id) !== input.rootThreadId | |
| + || readString(raw.child_agent_id) !== input.childAgentId | |
| + || readString(raw.agent_type).toLowerCase() !== 'executor' | |
| + || input.agentType.trim().toLowerCase() !== 'executor' | |
| + || !readString(raw.bootstrap_generation) | |
| + ) return null; | |
| + | |
| + const nowMs = input.nowMs ?? Date.now(); | |
| + const issuedAt = Date.parse(readString(raw.issued_at)); | |
| + const expiresAt = Date.parse(readString(raw.expires_at)); | |
| + if ( | |
| + !Number.isFinite(issuedAt) | |
| + || !Number.isFinite(expiresAt) | |
| + || issuedAt > nowMs + 30_000 | |
| + || expiresAt <= nowMs | |
| + || expiresAt <= issuedAt | |
| + || expiresAt - issuedAt > MAX_ASSIGNMENT_LIFETIME_MS | |
| + ) return null; | |
| + | |
| + const allowedActions = Array.isArray(raw.allowed_actions) | |
| + ? raw.allowed_actions.map((value) => readString(value)) | |
| + : []; | |
| + if ( | |
| + allowedActions.length === 0 | |
| + || allowedActions.length > 2 | |
| + || new Set(allowedActions).size !== allowedActions.length | |
| + || allowedActions.some((action) => action !== 'path-write' && action !== 'bash-write') | |
| + ) return null; | |
| + | |
| + const pathPrefixes = Array.isArray(raw.path_prefixes) | |
| + ? raw.path_prefixes.map((value) => normalizePrefix(input.cwd, readString(value))) | |
| + : []; | |
| + if ( | |
| + pathPrefixes.length === 0 | |
| + || pathPrefixes.length > 32 | |
| + || pathPrefixes.some((prefix) => prefix === null) | |
| + || new Set(pathPrefixes).size !== pathPrefixes.length | |
| + ) return null; | |
| + for (const prefix of pathPrefixes as string[]) { | |
| + if (await pathPrefixTraversesUnsafeLink(input.cwd, prefix)) return null; | |
| + } | |
| + | |
| + return { | |
| + schema_version: 1, | |
| + kind: 'native-subagent-assignment', | |
| + lifecycle: 'active', | |
| + root_session_id: input.sessionId, | |
| + root_thread_id: input.rootThreadId, | |
| + child_agent_id: input.childAgentId, | |
| + agent_type: 'executor', | |
| + allowed_actions: allowedActions as Array<'path-write' | 'bash-write'>, | |
| + path_prefixes: pathPrefixes as string[], | |
| + issued_at: new Date(issuedAt).toISOString(), | |
| + expires_at: new Date(expiresAt).toISOString(), | |
| + bootstrap_generation: readString(raw.bootstrap_generation), | |
| + }; | |
| +} | |
| + | |
| +function parseBootstrap(raw: Record<string, unknown> | null): NativeUltragoalBootstrapState | null { | |
| + if ( | |
| + raw?.schema_version !== 1 | |
| + || raw.kind !== 'native-ultragoal-bootstrap' | |
| + || !readString(raw.generation) | |
| + || !['off', 'host', 'local-experimental'].includes(readString(raw.backend)) | |
| + || ![ | |
| + 'awaiting-executor', 'ready', 'activating', 'active', 'revoked', 'expired', 'host-receipt-unavailable', | |
| + ].includes(readString(raw.status)) | |
| + || !readString(raw.session_id) | |
| + || !readString(raw.root_thread_id) | |
| + || !readString(raw.created_at) | |
| + || !readString(raw.updated_at) | |
| + || !readString(raw.expires_at) | |
| + || !Number.isFinite(Date.parse(readString(raw.expires_at))) | |
| + ) return null; | |
| + return raw as unknown as NativeUltragoalBootstrapState; | |
| +} | |
| + | |
| +function parseAssignment(raw: Record<string, unknown> | null): LocalNativeExecutorAssignment | null { | |
| + if ( | |
| + raw?.schema_version !== 1 | |
| + || raw.kind !== 'native-subagent-assignment' | |
| + || !['active', 'revoked', 'expired'].includes(readString(raw.lifecycle)) | |
| + || raw.agent_type !== 'executor' | |
| + || !Array.isArray(raw.allowed_actions) | |
| + || !Array.isArray(raw.path_prefixes) | |
| + || !readString(raw.root_session_id) | |
| + || !readString(raw.root_thread_id) | |
| + || !readString(raw.child_agent_id) | |
| + || !readString(raw.issued_at) | |
| + || !readString(raw.expires_at) | |
| + || !readString(raw.bootstrap_generation) | |
| + || !Number.isFinite(Date.parse(readString(raw.expires_at))) | |
| + ) return null; | |
| + return raw as unknown as LocalNativeExecutorAssignment; | |
| +} | |
| + | |
| +async function beginNativeUltragoalBootstrapUnlocked(input: { | |
| + stateDir: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + backend: NativeUltragoalBackend; | |
| + ttlSeconds: number; | |
| + now?: Date; | |
| +}): Promise<NativeUltragoalBootstrapState> { | |
| + const now = input.now ?? new Date(); | |
| + const existing = parseBootstrap(await readJsonObject(bootstrapPath(input.stateDir, input.sessionId))); | |
| + if ( | |
| + existing | |
| + && existing.session_id === input.sessionId | |
| + && existing.root_thread_id === input.rootThreadId | |
| + && existing.backend === input.backend | |
| + && Date.parse(existing.expires_at) > now.getTime() | |
| + && ['awaiting-executor', 'ready', 'activating', 'active'].includes(existing.status) | |
| + ) return existing; | |
| + | |
| + if (existing?.child_agent_id) { | |
| + const staleAssignmentPath = nativeExecutorAssignmentPath( | |
| + input.stateDir, | |
| + input.sessionId, | |
| + existing.child_agent_id, | |
| + ); | |
| + const staleAssignment = parseAssignment(await readJsonObject(staleAssignmentPath)); | |
| + if (staleAssignment?.lifecycle === 'active') { | |
| + await transitionAssignment( | |
| + staleAssignmentPath, | |
| + staleAssignment, | |
| + Date.parse(existing.expires_at) <= now.getTime() ? 'expired' : 'revoked', | |
| + 'bootstrap_replaced', | |
| + now, | |
| + ); | |
| + } | |
| + } | |
| + | |
| + const state: NativeUltragoalBootstrapState = { | |
| + schema_version: 1, | |
| + kind: 'native-ultragoal-bootstrap', | |
| + generation: randomUUID(), | |
| + backend: input.backend, | |
| + status: input.backend === 'host' ? 'host-receipt-unavailable' : 'awaiting-executor', | |
| + session_id: input.sessionId, | |
| + root_thread_id: input.rootThreadId, | |
| + created_at: now.toISOString(), | |
| + updated_at: now.toISOString(), | |
| + expires_at: new Date(now.getTime() + input.ttlSeconds * 1000).toISOString(), | |
| + ...(input.backend === 'host' ? { reason: DOCUMENTED_HOST_RECEIPT_UNAVAILABLE } : {}), | |
| + }; | |
| + await writeJsonAtomic(bootstrapPath(input.stateDir, input.sessionId), state); | |
| + return state; | |
| +} | |
| + | |
| +export async function beginNativeUltragoalBootstrap( | |
| + input: Parameters<typeof beginNativeUltragoalBootstrapUnlocked>[0], | |
| +): Promise<NativeUltragoalBootstrapState> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + beginNativeUltragoalBootstrapUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +export async function readNativeUltragoalBootstrap( | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<NativeUltragoalBootstrapState | null> { | |
| + return parseBootstrap(await readJsonObject(bootstrapPath(stateDir, sessionId))); | |
| +} | |
| + | |
| +async function validateTrackedExecutor(input: { | |
| + cwd: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + childAgentId: string; | |
| +}): Promise<{ ok: true } | { ok: false; reason: string }> { | |
| + const tracking = await readSubagentTrackingState(input.cwd).catch(() => null); | |
| + const session = tracking?.sessions?.[input.sessionId]; | |
| + const child = session?.threads?.[input.childAgentId]; | |
| + if (!session || session.leader_thread_id !== input.rootThreadId) { | |
| + return { ok: false, reason: 'leader_session_mismatch' }; | |
| + } | |
| + if (isNativeSubagentAuthorityFenced(input.cwd, input.childAgentId)) { | |
| + return { ok: false, reason: 'child_authority_fenced' }; | |
| + } | |
| + if (!child || child.kind !== 'subagent') return { ok: false, reason: 'child_not_tracked' }; | |
| + const role = readString(child.mode || child.role).toLowerCase(); | |
| + if (role !== 'executor') return { ok: false, reason: 'child_role_mismatch' }; | |
| + if (child.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE) { | |
| + return { ok: false, reason: 'child_native_authority_unproven' }; | |
| + } | |
| + if (child.reopen_authority_revoked === true) return { ok: false, reason: 'child_authority_revoked' }; | |
| + if (child.status && child.status !== 'available') return { ok: false, reason: `child_${child.status}` }; | |
| + if (child.completed_at) return { ok: false, reason: 'child_completed' }; | |
| + if (child.direct_child_parent_id !== input.rootThreadId) { | |
| + return { ok: false, reason: 'child_parent_mismatch' }; | |
| + } | |
| + if (child.direct_child_root_id !== input.rootThreadId) { | |
| + return { ok: false, reason: 'child_root_mismatch' }; | |
| + } | |
| + return { ok: true }; | |
| +} | |
| + | |
| +async function issueLocalNativeExecutorAssignmentUnlocked(input: { | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + childAgentId: string; | |
| + expectedGeneration: string; | |
| + pathPrefixes: string[]; | |
| + ttlSeconds: number; | |
| + allowedActions?: Array<'path-write' | 'bash-write'>; | |
| + now?: Date; | |
| +}): Promise<LocalNativeExecutorAssignment> { | |
| + const now = input.now ?? new Date(); | |
| + if (!Number.isInteger(input.ttlSeconds) || input.ttlSeconds < 30 || input.ttlSeconds > 3600) { | |
| + throw new Error('invalid_native_assignment_ttl'); | |
| + } | |
| + let bootstrap = await readNativeUltragoalBootstrap(input.stateDir, input.sessionId); | |
| + const wasActive = bootstrap?.status === 'active'; | |
| + if ( | |
| + !bootstrap | |
| + || bootstrap.backend !== 'local-experimental' | |
| + || bootstrap.session_id !== input.sessionId | |
| + || bootstrap.root_thread_id !== input.rootThreadId | |
| + || bootstrap.generation !== input.expectedGeneration | |
| + || !['awaiting-executor', 'ready', 'active'].includes(bootstrap.status) | |
| + || Date.parse(bootstrap.expires_at) <= now.getTime() | |
| + ) throw new Error('native_ultragoal_bootstrap_not_ready'); | |
| + | |
| + const tracked = await validateTrackedExecutor(input); | |
| + if (!tracked.ok) throw new Error(tracked.reason); | |
| + const normalizedPrefixes = input.pathPrefixes.map((prefix) => normalizePrefix(input.cwd, prefix)); | |
| + if ( | |
| + normalizedPrefixes.length === 0 | |
| + || normalizedPrefixes.length > 32 | |
| + || normalizedPrefixes.some((prefix) => prefix === null) | |
| + || new Set(normalizedPrefixes).size !== normalizedPrefixes.length | |
| + ) throw new Error('invalid_native_assignment_path_prefixes'); | |
| + for (const prefix of normalizedPrefixes as string[]) { | |
| + if (await pathPrefixTraversesUnsafeLink(input.cwd, prefix)) { | |
| + throw new Error('invalid_native_assignment_path_prefixes'); | |
| + } | |
| + } | |
| + const actions = input.allowedActions ?? ['path-write', 'bash-write']; | |
| + if ( | |
| + actions.length === 0 | |
| + || actions.length > 2 | |
| + || new Set(actions).size !== actions.length | |
| + || actions.some((action) => action !== 'path-write' && action !== 'bash-write') | |
| + ) throw new Error('invalid_native_assignment_actions'); | |
| + const requestedExpiry = now.getTime() + input.ttlSeconds * 1000; | |
| + const expiresAt = wasActive | |
| + ? requestedExpiry | |
| + : Math.min(requestedExpiry, Date.parse(bootstrap.expires_at)); | |
| + const assignment: LocalNativeExecutorAssignment = { | |
| + schema_version: 1, | |
| + kind: 'native-subagent-assignment', | |
| + lifecycle: 'active', | |
| + root_session_id: input.sessionId, | |
| + root_thread_id: input.rootThreadId, | |
| + child_agent_id: input.childAgentId, | |
| + agent_type: 'executor', | |
| + allowed_actions: actions, | |
| + path_prefixes: normalizedPrefixes as string[], | |
| + issued_at: now.toISOString(), | |
| + expires_at: new Date(expiresAt).toISOString(), | |
| + bootstrap_generation: bootstrap.generation, | |
| + }; | |
| + await writeJsonAtomic( | |
| + nativeExecutorAssignmentPath(input.stateDir, input.sessionId, input.childAgentId), | |
| + assignment, | |
| + ); | |
| + await writeJsonAtomic(bootstrapPath(input.stateDir, input.sessionId), { | |
| + ...bootstrap, | |
| + status: wasActive ? 'active' : 'ready', | |
| + child_agent_id: input.childAgentId, | |
| + updated_at: now.toISOString(), | |
| + ...(wasActive ? { expires_at: new Date(expiresAt).toISOString(), reason: undefined } : {}), | |
| + }); | |
| + return assignment; | |
| +} | |
| + | |
| +export async function issueLocalNativeExecutorAssignment( | |
| + input: Parameters<typeof issueLocalNativeExecutorAssignmentUnlocked>[0], | |
| +): Promise<LocalNativeExecutorAssignment> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + issueLocalNativeExecutorAssignmentUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +export async function nativeExecutorAssignmentFileIsSafe(stateDir: string, path: string): Promise<boolean> { | |
| + try { | |
| + const relativePath = relative(resolve(stateDir), resolve(path)).replace(/\\/g, '/'); | |
| + if (!relativePath || relativePath === '..' || relativePath.startsWith('../')) return false; | |
| + let current = resolve(stateDir); | |
| + for (const segment of relativePath.split('/').filter(Boolean)) { | |
| + current = join(current, segment); | |
| + const metadata = await lstat(current); | |
| + if (metadata.isSymbolicLink()) return false; | |
| + if (current === resolve(path)) { | |
| + if (!metadata.isFile() || metadata.nlink !== 1 || (metadata.mode & 0o022) !== 0 || metadata.size > 64 * 1024) { | |
| + return false; | |
| + } | |
| + } else if (!metadata.isDirectory()) return false; | |
| + } | |
| + await access(path, fsConstants.R_OK); | |
| + return true; | |
| + } catch { | |
| + return false; | |
| + } | |
| +} | |
| + | |
| +async function transitionAssignment( | |
| + path: string, | |
| + assignment: LocalNativeExecutorAssignment, | |
| + lifecycle: 'revoked' | 'expired', | |
| + reason: string, | |
| + now = new Date(), | |
| +): Promise<LocalNativeExecutorAssignment> { | |
| + const next: LocalNativeExecutorAssignment = { | |
| + ...assignment, | |
| + lifecycle, | |
| + revoked_at: now.toISOString(), | |
| + revoke_reason: reason, | |
| + }; | |
| + await writeJsonAtomic(path, next); | |
| + return next; | |
| +} | |
| + | |
| +async function findReadyLocalNativeExecutorAssignmentUnlocked(input: { | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + now?: Date; | |
| +}): Promise<LocalNativeExecutorAssignment | null> { | |
| + const now = input.now ?? new Date(); | |
| + const bootstrap = await readNativeUltragoalBootstrap(input.stateDir, input.sessionId); | |
| + if ( | |
| + !bootstrap | |
| + || bootstrap.backend !== 'local-experimental' | |
| + || bootstrap.root_thread_id !== input.rootThreadId | |
| + || !bootstrap.child_agent_id | |
| + || !['ready', 'activating', 'active'].includes(bootstrap.status) | |
| + || Date.parse(bootstrap.expires_at) <= now.getTime() | |
| + ) return null; | |
| + const path = nativeExecutorAssignmentPath(input.stateDir, input.sessionId, bootstrap.child_agent_id); | |
| + if (!await nativeExecutorAssignmentFileIsSafe(input.stateDir, path)) return null; | |
| + const rawAssignment = await readJsonObject(path); | |
| + const transitionableAssignment = parseAssignment(rawAssignment); | |
| + if ( | |
| + transitionableAssignment?.lifecycle === 'active' | |
| + && Date.parse(transitionableAssignment.expires_at) <= now.getTime() | |
| + ) { | |
| + await transitionAssignment(path, transitionableAssignment, 'expired', 'assignment_expired', now); | |
| + return null; | |
| + } | |
| + const assignment = await parseActiveLocalNativeExecutorAssignment(rawAssignment, { | |
| + cwd: input.cwd, | |
| + sessionId: input.sessionId, | |
| + rootThreadId: input.rootThreadId, | |
| + childAgentId: bootstrap.child_agent_id, | |
| + agentType: 'executor', | |
| + nowMs: now.getTime(), | |
| + }); | |
| + if (!assignment || assignment.bootstrap_generation !== bootstrap.generation) return null; | |
| + const tracked = await validateTrackedExecutor({ | |
| + cwd: input.cwd, | |
| + sessionId: input.sessionId, | |
| + rootThreadId: input.rootThreadId, | |
| + childAgentId: assignment.child_agent_id, | |
| + }); | |
| + if (!tracked.ok) { | |
| + await transitionAssignment(path, assignment, 'revoked', tracked.reason, now); | |
| + return null; | |
| + } | |
| + return assignment; | |
| +} | |
| + | |
| +export async function findReadyLocalNativeExecutorAssignment( | |
| + input: Parameters<typeof findReadyLocalNativeExecutorAssignmentUnlocked>[0], | |
| +): Promise<LocalNativeExecutorAssignment | null> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + findReadyLocalNativeExecutorAssignmentUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +async function validateNativeUltragoalActivationReceipt(input: { | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + childAgentId: string; | |
| + now?: Date; | |
| +}, allowedStatuses: NativeUltragoalBootstrapStatus[]): Promise<{ | |
| + ok: true; | |
| + assignment: LocalNativeExecutorAssignment; | |
| + bootstrap: NativeUltragoalBootstrapState; | |
| +} | { ok: false }> { | |
| + const bootstrap = await readNativeUltragoalBootstrap(input.stateDir, input.sessionId); | |
| + if ( | |
| + !bootstrap | |
| + || !allowedStatuses.includes(bootstrap.status) | |
| + || bootstrap.child_agent_id !== input.childAgentId | |
| + ) return { ok: false }; | |
| + const now = input.now ?? new Date(); | |
| + if (Date.parse(bootstrap.expires_at) <= now.getTime()) return { ok: false }; | |
| + const assignmentPath = nativeExecutorAssignmentPath(input.stateDir, input.sessionId, input.childAgentId); | |
| + if (!await nativeExecutorAssignmentFileIsSafe(input.stateDir, assignmentPath)) return { ok: false }; | |
| + const assignment = await parseActiveLocalNativeExecutorAssignment(await readJsonObject(assignmentPath), { | |
| + cwd: input.cwd, | |
| + sessionId: input.sessionId, | |
| + rootThreadId: bootstrap.root_thread_id, | |
| + childAgentId: input.childAgentId, | |
| + agentType: 'executor', | |
| + nowMs: now.getTime(), | |
| + }); | |
| + if ( | |
| + !assignment | |
| + || assignment.bootstrap_generation !== bootstrap.generation | |
| + ) return { ok: false }; | |
| + const tracked = await validateTrackedExecutor({ | |
| + cwd: input.cwd, | |
| + sessionId: input.sessionId, | |
| + rootThreadId: bootstrap.root_thread_id, | |
| + childAgentId: input.childAgentId, | |
| + }); | |
| + if (!tracked.ok) return { ok: false }; | |
| + return { ok: true, assignment, bootstrap }; | |
| +} | |
| + | |
| +async function prepareNativeUltragoalBootstrapActivationUnlocked(input: { | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + childAgentId: string; | |
| + now?: Date; | |
| +}): Promise<{ ok: true; assignment: LocalNativeExecutorAssignment } | { ok: false }> { | |
| + const validated = await validateNativeUltragoalActivationReceipt(input, ['ready', 'activating']); | |
| + if (!validated.ok) return validated; | |
| + const { assignment, bootstrap } = validated; | |
| + if (bootstrap.status !== 'activating') { | |
| + await writeJsonAtomic(bootstrapPath(input.stateDir, input.sessionId), { | |
| + ...bootstrap, | |
| + status: 'activating', | |
| + updated_at: (input.now ?? new Date()).toISOString(), | |
| + }); | |
| + } | |
| + return { ok: true, assignment }; | |
| +} | |
| + | |
| +export async function prepareNativeUltragoalBootstrapActivation( | |
| + input: Parameters<typeof prepareNativeUltragoalBootstrapActivationUnlocked>[0], | |
| +): Promise<{ ok: true; assignment: LocalNativeExecutorAssignment } | { ok: false }> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + prepareNativeUltragoalBootstrapActivationUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +async function markNativeUltragoalBootstrapActiveUnlocked( | |
| + input: Parameters<typeof prepareNativeUltragoalBootstrapActivationUnlocked>[0], | |
| +): Promise<{ ok: true; assignment: LocalNativeExecutorAssignment } | { ok: false }> { | |
| + const validated = await validateNativeUltragoalActivationReceipt(input, ['ready', 'activating', 'active']); | |
| + if (!validated.ok) return validated; | |
| + const { assignment, bootstrap } = validated; | |
| + if (bootstrap.status !== 'active') { | |
| + await writeJsonAtomic(bootstrapPath(input.stateDir, input.sessionId), { | |
| + ...bootstrap, | |
| + status: 'active', | |
| + updated_at: (input.now ?? new Date()).toISOString(), | |
| + }); | |
| + } | |
| + return { ok: true, assignment }; | |
| +} | |
| + | |
| +export async function markNativeUltragoalBootstrapActive( | |
| + input: Parameters<typeof markNativeUltragoalBootstrapActiveUnlocked>[0], | |
| +): Promise<{ ok: true; assignment: LocalNativeExecutorAssignment } | { ok: false }> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + markNativeUltragoalBootstrapActiveUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +async function revokeLocalNativeExecutorAssignmentUnlocked(input: { | |
| + stateDir: string; | |
| + sessionId: string; | |
| + childAgentId?: string; | |
| + reason: string; | |
| + now?: Date; | |
| +}): Promise<void> { | |
| + const now = input.now ?? new Date(); | |
| + const bootstrap = await readNativeUltragoalBootstrap(input.stateDir, input.sessionId); | |
| + const childAgentId = input.childAgentId ?? bootstrap?.child_agent_id; | |
| + if (childAgentId) { | |
| + const path = nativeExecutorAssignmentPath(input.stateDir, input.sessionId, childAgentId); | |
| + const assignment = parseAssignment(await readJsonObject(path)); | |
| + if (assignment?.lifecycle === 'active') { | |
| + await transitionAssignment(path, assignment, 'revoked', input.reason, now); | |
| + } | |
| + } | |
| + if (bootstrap) { | |
| + await writeJsonAtomic(bootstrapPath(input.stateDir, input.sessionId), { | |
| + ...bootstrap, | |
| + status: 'revoked', | |
| + updated_at: now.toISOString(), | |
| + reason: input.reason, | |
| + }); | |
| + } | |
| +} | |
| + | |
| +export async function revokeLocalNativeExecutorAssignment( | |
| + input: Parameters<typeof revokeLocalNativeExecutorAssignmentUnlocked>[0], | |
| +): Promise<void> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + revokeLocalNativeExecutorAssignmentUnlocked(input) | |
| + )); | |
| +} | |
| + | |
| +async function reconcileLocalNativeExecutorAssignmentsUnlocked(input: { | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + rootThreadId: string; | |
| + now?: Date; | |
| +}): Promise<{ ready: boolean; childAgentId?: string }> { | |
| + const bootstrap = await readNativeUltragoalBootstrap(input.stateDir, input.sessionId); | |
| + const directory = join(input.stateDir, 'sessions', input.sessionId, NATIVE_ASSIGNMENT_DIRECTORY); | |
| + const entries = await readdir(directory).catch(() => []); | |
| + for (const entry of entries) { | |
| + if (!entry.endsWith('.json')) continue; | |
| + const path = join(directory, entry); | |
| + const assignment = parseAssignment(await readJsonObject(path)); | |
| + if (!assignment || assignment.lifecycle !== 'active') continue; | |
| + if ( | |
| + !bootstrap | |
| + || assignment.bootstrap_generation !== bootstrap.generation | |
| + || assignment.child_agent_id !== bootstrap.child_agent_id | |
| + ) { | |
| + await transitionAssignment(path, assignment, 'revoked', 'bootstrap_replaced', input.now); | |
| + continue; | |
| + } | |
| + const tracked = await validateTrackedExecutor({ | |
| + cwd: input.cwd, | |
| + sessionId: input.sessionId, | |
| + rootThreadId: input.rootThreadId, | |
| + childAgentId: assignment.child_agent_id, | |
| + }); | |
| + if (!tracked.ok) await transitionAssignment(path, assignment, 'revoked', tracked.reason, input.now); | |
| + } | |
| + const assignment = await findReadyLocalNativeExecutorAssignmentUnlocked(input); | |
| + return assignment | |
| + ? { ready: true, childAgentId: assignment.child_agent_id } | |
| + : { ready: false }; | |
| +} | |
| + | |
| +export async function reconcileLocalNativeExecutorAssignments( | |
| + input: Parameters<typeof reconcileLocalNativeExecutorAssignmentsUnlocked>[0], | |
| +): Promise<{ ready: boolean; childAgentId?: string }> { | |
| + return withNativeUltragoalLifecycleLock(input.stateDir, input.sessionId, () => ( | |
| + reconcileLocalNativeExecutorAssignmentsUnlocked(input) | |
| + )); | |
| +} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment