oh-my-codex 0.18.6 → 0.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +56 -7
- package/dist/agents/__tests__/definitions.test.js +11 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +14 -5
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +2 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +4 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.js +2 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
- package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/fsm.test.js +75 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
- package/dist/autopilot/deep-interview-gate.d.ts +18 -0
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
- package/dist/autopilot/deep-interview-gate.js +256 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -0
- package/dist/autopilot/fsm.d.ts +13 -0
- package/dist/autopilot/fsm.d.ts.map +1 -0
- package/dist/autopilot/fsm.js +70 -0
- package/dist/autopilot/fsm.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts +17 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
- package/dist/autopilot/ralplan-gate.js +61 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +24 -4
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +175 -6
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +100 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +18 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +191 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +36 -5
- package/dist/cli/question.js.map +1 -1
- package/dist/config/__tests__/deep-interview.test.js +7 -6
- package/dist/config/__tests__/deep-interview.test.js.map +1 -1
- package/dist/config/deep-interview.d.ts.map +1 -1
- package/dist/config/deep-interview.js +14 -4
- package/dist/config/deep-interview.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +649 -11
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +63 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +25 -0
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/deep-interview-config-instruction.js +1 -1
- package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +171 -21
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/session.d.ts +2 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +13 -5
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +35 -0
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +168 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +67 -13
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +80 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +134 -1
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +13 -2
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +17 -0
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +64 -10
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.js +1 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +16 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +2 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +39 -2
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +13 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +12 -2
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +83 -9
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +130 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.js +1 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +1 -0
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +14 -5
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +160 -2
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/policy.test.js +63 -3
- package/dist/question/__tests__/policy.test.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +191 -2
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/__tests__/state.test.js +94 -3
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +4 -0
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts +12 -2
- package/dist/question/autopilot-wait.d.ts.map +1 -1
- package/dist/question/autopilot-wait.js +158 -47
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +22 -6
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/policy.d.ts.map +1 -1
- package/dist/question/policy.js +2 -5
- package/dist/question/policy.js.map +1 -1
- package/dist/question/renderer.d.ts +12 -0
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +87 -3
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts +8 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +54 -14
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -1
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/ui.d.ts +1 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +1 -0
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +191 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +9 -1
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +84 -2
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +9 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +32 -11
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1487 -34
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +356 -38
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +79 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts +9 -0
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
- package/dist/scripts/hook-payload-guard.js +111 -0
- package/dist/scripts/hook-payload-guard.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +8 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook.js +11 -2
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +1012 -1
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +73 -7
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +102 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +33 -3
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +6 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +28 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +10 -3
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +139 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +3 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +41 -4
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
- package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
- package/dist/team/__tests__/coordination-protocol.test.js +173 -0
- package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +51 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +83 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +45 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/coordination-protocol.d.ts +14 -0
- package/dist/team/coordination-protocol.d.ts.map +1 -0
- package/dist/team/coordination-protocol.js +244 -0
- package/dist/team/coordination-protocol.js.map +1 -0
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +19 -3
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +24 -0
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state/types.d.ts +21 -1
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +17 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +12 -5
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +19 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +63 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +4 -2
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +3 -0
- package/dist/utils/agents-model-table.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
- package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
- package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
- package/skills/autopilot/SKILL.md +10 -5
- package/skills/deep-interview/SKILL.md +9 -4
- package/skills/ralplan/SKILL.md +12 -0
- package/skills/team/SKILL.md +16 -0
- package/skills/worker/SKILL.md +14 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +2202 -523
- package/src/scripts/codex-native-hook.ts +444 -36
- package/src/scripts/codex-native-pre-post.ts +80 -0
- package/src/scripts/hook-payload-guard.ts +113 -0
- package/src/scripts/notify-fallback-watcher.ts +8 -1
- package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
- package/src/scripts/notify-hook.ts +14 -2
|
@@ -133,6 +133,28 @@ describe('keyword detector team compatibility', () => {
|
|
|
133
133
|
const pathOnly = detectPrimaryKeyword('inspect .omx/ultragoal/goals.json');
|
|
134
134
|
assert.notEqual(pathOnly?.skill, 'ultragoal');
|
|
135
135
|
});
|
|
136
|
+
it('maps bare and command-style autopilot invocations to autopilot', () => {
|
|
137
|
+
for (const prompt of ['autopilot', 'run autopilot', 'autopilot this', 'autopilot mode']) {
|
|
138
|
+
const match = detectPrimaryKeyword(prompt);
|
|
139
|
+
assert.ok(match, `expected autopilot match for ${prompt}`);
|
|
140
|
+
assert.equal(match.skill, 'autopilot');
|
|
141
|
+
assert.equal(match.keyword.toLowerCase(), 'autopilot');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
it('does not trigger autopilot from management/debug prose mentions', () => {
|
|
145
|
+
assert.equal(detectPrimaryKeyword('inspect autopilot state before continuing'), null);
|
|
146
|
+
assert.equal(detectPrimaryKeyword('fix the autopilot bug in the detector'), null);
|
|
147
|
+
assert.equal(detectPrimaryKeyword('why did autopilot fail?'), null);
|
|
148
|
+
assert.equal(detectPrimaryKeyword('run autopilot tests'), null);
|
|
149
|
+
assert.equal(detectPrimaryKeyword('run autopilot regression tests'), null);
|
|
150
|
+
assert.equal(detectPrimaryKeyword('continue autopilot debugging'), null);
|
|
151
|
+
assert.equal(detectPrimaryKeyword('start autopilot bug investigation'), null);
|
|
152
|
+
});
|
|
153
|
+
it('keeps higher-priority workflow keywords ahead of autopilot mentions', () => {
|
|
154
|
+
const match = detectPrimaryKeyword('autopilot this after consensus plan');
|
|
155
|
+
assert.ok(match);
|
|
156
|
+
assert.equal(match.skill, 'ralplan');
|
|
157
|
+
});
|
|
136
158
|
it('maps code-review keyword variants to code-review skill', () => {
|
|
137
159
|
const hyphen = detectPrimaryKeyword('run $code-review before merge');
|
|
138
160
|
assert.ok(hyphen);
|
|
@@ -333,6 +355,7 @@ describe('keyword registry coverage', () => {
|
|
|
333
355
|
assert.ok(registryKeywords.has('$ultragoal'));
|
|
334
356
|
assert.ok(registryKeywords.has('$prometheus-strict'));
|
|
335
357
|
assert.ok(registryKeywords.has('ultragoal'));
|
|
358
|
+
assert.ok(registryKeywords.has('autopilot'));
|
|
336
359
|
});
|
|
337
360
|
});
|
|
338
361
|
describe('keyword detector skill-active-state lifecycle', () => {
|
|
@@ -496,6 +519,146 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
496
519
|
await rm(cwd, { recursive: true, force: true });
|
|
497
520
|
}
|
|
498
521
|
});
|
|
522
|
+
it('fully resets terminal Autopilot mode state when reactivated', async () => {
|
|
523
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-terminal-reset-'));
|
|
524
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
525
|
+
const sessionId = 'sess-autopilot-terminal-reset';
|
|
526
|
+
try {
|
|
527
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
528
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
529
|
+
version: 1,
|
|
530
|
+
active: true,
|
|
531
|
+
skill: 'autopilot',
|
|
532
|
+
keyword: '$autopilot',
|
|
533
|
+
phase: 'complete',
|
|
534
|
+
activated_at: '2026-05-29T00:00:00.000Z',
|
|
535
|
+
updated_at: '2026-05-29T00:00:00.000Z',
|
|
536
|
+
session_id: sessionId,
|
|
537
|
+
active_skills: [{ skill: 'autopilot', active: true, phase: 'complete', session_id: sessionId }],
|
|
538
|
+
}, null, 2));
|
|
539
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
540
|
+
active: true,
|
|
541
|
+
mode: 'autopilot',
|
|
542
|
+
current_phase: 'complete',
|
|
543
|
+
started_at: '2026-05-29T00:00:00.000Z',
|
|
544
|
+
completed_at: '2026-05-29T00:10:00.000Z',
|
|
545
|
+
iteration: 10,
|
|
546
|
+
max_iterations: 10,
|
|
547
|
+
review_cycle: 3,
|
|
548
|
+
lifecycle_outcome: 'finished',
|
|
549
|
+
run_outcome: 'finish',
|
|
550
|
+
handoff_artifacts: {
|
|
551
|
+
code_review: { verdict: 'APPROVE / CLEAR' },
|
|
552
|
+
ultraqa: { verdict: 'pass' },
|
|
553
|
+
},
|
|
554
|
+
state: {
|
|
555
|
+
handoff_artifacts: {
|
|
556
|
+
ralplan_consensus_gate: { complete: false },
|
|
557
|
+
code_review: { verdict: 'stale' },
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
}, null, 2));
|
|
561
|
+
const result = await recordSkillActivation({
|
|
562
|
+
stateDir,
|
|
563
|
+
text: '$autopilot investigate the next issue',
|
|
564
|
+
sessionId,
|
|
565
|
+
threadId: 'thread-reactivated',
|
|
566
|
+
turnId: 'turn-reactivated',
|
|
567
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
568
|
+
});
|
|
569
|
+
assert.ok(result);
|
|
570
|
+
assert.equal(result.skill, 'autopilot');
|
|
571
|
+
assert.equal(result.phase, 'deep-interview');
|
|
572
|
+
assert.equal(result.activated_at, '2026-05-30T00:00:00.000Z');
|
|
573
|
+
assert.equal(result.active_skills?.[0]?.phase, 'deep-interview');
|
|
574
|
+
assert.equal(result.active_skills?.[0]?.activated_at, '2026-05-30T00:00:00.000Z');
|
|
575
|
+
const skillState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), 'utf-8'));
|
|
576
|
+
assert.equal(skillState.phase, 'deep-interview');
|
|
577
|
+
assert.equal(skillState.activated_at, '2026-05-30T00:00:00.000Z');
|
|
578
|
+
assert.equal(skillState.active_skills?.[0]?.phase, 'deep-interview');
|
|
579
|
+
assert.equal(skillState.active_skills?.[0]?.activated_at, '2026-05-30T00:00:00.000Z');
|
|
580
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
581
|
+
assert.equal(modeState.active, true);
|
|
582
|
+
assert.equal(modeState.current_phase, 'deep-interview');
|
|
583
|
+
assert.equal(modeState.started_at, '2026-05-30T00:00:00.000Z');
|
|
584
|
+
assert.equal(modeState.completed_at, undefined);
|
|
585
|
+
assert.equal(modeState.iteration, 1);
|
|
586
|
+
assert.equal(modeState.max_iterations, 10);
|
|
587
|
+
assert.equal(modeState.review_cycle, 0);
|
|
588
|
+
assert.equal(modeState.lifecycle_outcome, undefined);
|
|
589
|
+
assert.equal(modeState.run_outcome, undefined);
|
|
590
|
+
assert.equal(modeState.handoff_artifacts, undefined);
|
|
591
|
+
assert.deepEqual(modeState.state?.handoff_artifacts, {
|
|
592
|
+
deep_interview: null,
|
|
593
|
+
ralplan: null,
|
|
594
|
+
ralplan_consensus_gate: {
|
|
595
|
+
required: true,
|
|
596
|
+
sequence: ['architect-review', 'critic-review'],
|
|
597
|
+
planning_artifacts_are_not_consensus: true,
|
|
598
|
+
required_review_roles: ['architect', 'critic'],
|
|
599
|
+
ralplan_architect_review: null,
|
|
600
|
+
ralplan_critic_review: null,
|
|
601
|
+
complete: false,
|
|
602
|
+
},
|
|
603
|
+
ultragoal: null,
|
|
604
|
+
code_review: null,
|
|
605
|
+
ultraqa: null,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
await rm(cwd, { recursive: true, force: true });
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
it('resets stopped Autopilot mode state when reactivated', async () => {
|
|
613
|
+
for (const phase of ['stopped', 'user-stopped']) {
|
|
614
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-autopilot-${phase}-reset-`));
|
|
615
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
616
|
+
const sessionId = `sess-autopilot-${phase}-reset`;
|
|
617
|
+
try {
|
|
618
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
619
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
620
|
+
version: 1,
|
|
621
|
+
active: true,
|
|
622
|
+
skill: 'autopilot',
|
|
623
|
+
keyword: '$autopilot',
|
|
624
|
+
phase,
|
|
625
|
+
activated_at: '2026-05-29T00:00:00.000Z',
|
|
626
|
+
updated_at: '2026-05-29T00:00:00.000Z',
|
|
627
|
+
source: 'keyword-detector',
|
|
628
|
+
session_id: sessionId,
|
|
629
|
+
active_skills: [{ skill: 'autopilot', active: true, phase, session_id: sessionId }],
|
|
630
|
+
}, null, 2));
|
|
631
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
632
|
+
active: true,
|
|
633
|
+
mode: 'autopilot',
|
|
634
|
+
current_phase: phase,
|
|
635
|
+
started_at: '2026-05-29T00:00:00.000Z',
|
|
636
|
+
completed_at: '2026-05-29T00:10:00.000Z',
|
|
637
|
+
iteration: 10,
|
|
638
|
+
max_iterations: 10,
|
|
639
|
+
review_cycle: 3,
|
|
640
|
+
state: { handoff_artifacts: { code_review: { verdict: 'stale' } } },
|
|
641
|
+
}, null, 2));
|
|
642
|
+
const result = await recordSkillActivation({
|
|
643
|
+
stateDir,
|
|
644
|
+
text: '$autopilot new task after stop',
|
|
645
|
+
sessionId,
|
|
646
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
647
|
+
});
|
|
648
|
+
assert.ok(result);
|
|
649
|
+
assert.equal(result.phase, 'deep-interview');
|
|
650
|
+
assert.equal(result.activated_at, '2026-05-30T00:00:00.000Z');
|
|
651
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
652
|
+
assert.equal(modeState.current_phase, 'deep-interview');
|
|
653
|
+
assert.equal(modeState.iteration, 1);
|
|
654
|
+
assert.equal(modeState.review_cycle, 0);
|
|
655
|
+
assert.equal(modeState.state?.handoff_artifacts?.code_review, null);
|
|
656
|
+
}
|
|
657
|
+
finally {
|
|
658
|
+
await rm(cwd, { recursive: true, force: true });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
});
|
|
499
662
|
it('adds approved workflow overlaps without deleting the existing canonical state', async () => {
|
|
500
663
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-overlap-'));
|
|
501
664
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -864,7 +1027,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
864
1027
|
await rm(cwd, { recursive: true, force: true });
|
|
865
1028
|
}
|
|
866
1029
|
});
|
|
867
|
-
it('
|
|
1030
|
+
it('denies ralplan handoff from deep-interview without completion or explicit skip evidence', async () => {
|
|
868
1031
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-handoff-'));
|
|
869
1032
|
const stateDir = join(cwd, '.omx', 'state');
|
|
870
1033
|
try {
|
|
@@ -884,10 +1047,49 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
884
1047
|
sessionId: 'sess-ralplan-handoff',
|
|
885
1048
|
nowIso: '2026-04-10T00:00:00.000Z',
|
|
886
1049
|
});
|
|
1050
|
+
assert.equal(result?.skill, 'deep-interview');
|
|
1051
|
+
assert.match(String(result?.transition_error), /missing deep-interview completion\/skip gate/i);
|
|
1052
|
+
const preserved = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), 'utf-8'));
|
|
1053
|
+
assert.equal(preserved.active, true);
|
|
1054
|
+
assert.equal(preserved.current_phase, 'intent-first');
|
|
1055
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'ralplan-state.json')), false);
|
|
1056
|
+
}
|
|
1057
|
+
finally {
|
|
1058
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
it('allows ralplan handoff from deep-interview with a durable completion gate', async () => {
|
|
1062
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-handoff-complete-'));
|
|
1063
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1064
|
+
try {
|
|
1065
|
+
await mkdir(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete'), { recursive: true });
|
|
1066
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1067
|
+
version: 1,
|
|
1068
|
+
active: true,
|
|
1069
|
+
skill: 'deep-interview',
|
|
1070
|
+
phase: 'planning',
|
|
1071
|
+
session_id: 'sess-ralplan-handoff-complete',
|
|
1072
|
+
active_skills: [{ skill: 'deep-interview', phase: 'planning', active: true, session_id: 'sess-ralplan-handoff-complete' }],
|
|
1073
|
+
}, null, 2));
|
|
1074
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', 'deep-interview-state.json'), JSON.stringify({
|
|
1075
|
+
active: true,
|
|
1076
|
+
mode: 'deep-interview',
|
|
1077
|
+
current_phase: 'intent-first',
|
|
1078
|
+
deep_interview_gate: {
|
|
1079
|
+
status: 'complete',
|
|
1080
|
+
rationale: 'Requirements are clarified and ready for ralplan consensus.',
|
|
1081
|
+
},
|
|
1082
|
+
}, null, 2));
|
|
1083
|
+
const result = await recordSkillActivation({
|
|
1084
|
+
stateDir,
|
|
1085
|
+
text: '$ralplan implement the approved contract',
|
|
1086
|
+
sessionId: 'sess-ralplan-handoff-complete',
|
|
1087
|
+
nowIso: '2026-04-10T00:00:00.000Z',
|
|
1088
|
+
});
|
|
887
1089
|
assert.equal(result?.transition_error, undefined);
|
|
888
1090
|
assert.equal(result?.skill, 'ralplan');
|
|
889
1091
|
assert.equal(result?.transition_message, 'mode transiting: deep-interview -> ralplan');
|
|
890
|
-
const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), 'utf-8'));
|
|
1092
|
+
const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff-complete', 'deep-interview-state.json'), 'utf-8'));
|
|
891
1093
|
assert.equal(completed.active, false);
|
|
892
1094
|
assert.equal(completed.current_phase, 'completed');
|
|
893
1095
|
}
|
|
@@ -1034,6 +1236,57 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
1034
1236
|
await rm(cwd, { recursive: true, force: true });
|
|
1035
1237
|
}
|
|
1036
1238
|
});
|
|
1239
|
+
it('emits terminal ralplan state before explicit ultragoal execution handoff', async () => {
|
|
1240
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-ultragoal-handoff-'));
|
|
1241
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1242
|
+
try {
|
|
1243
|
+
await mkdir(join(stateDir, 'sessions', 'sess-ralplan-ultragoal'), { recursive: true });
|
|
1244
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1245
|
+
version: 1,
|
|
1246
|
+
active: true,
|
|
1247
|
+
skill: 'ralplan',
|
|
1248
|
+
keyword: '$ralplan',
|
|
1249
|
+
phase: 'planning',
|
|
1250
|
+
session_id: 'sess-ralplan-ultragoal',
|
|
1251
|
+
active_skills: [{ skill: 'ralplan', phase: 'planning', active: true, session_id: 'sess-ralplan-ultragoal' }],
|
|
1252
|
+
}, null, 2));
|
|
1253
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ralplan-state.json'), JSON.stringify({
|
|
1254
|
+
active: true,
|
|
1255
|
+
mode: 'ralplan',
|
|
1256
|
+
current_phase: 'complete',
|
|
1257
|
+
planning_complete: true,
|
|
1258
|
+
ralplan_consensus_gate: {
|
|
1259
|
+
complete: true,
|
|
1260
|
+
sequence: ['architect-review', 'critic-review'],
|
|
1261
|
+
ralplan_architect_review: { agent_role: 'architect', verdict: 'approve', approved: true },
|
|
1262
|
+
ralplan_critic_review: { agent_role: 'critic', verdict: 'approve', approved: true },
|
|
1263
|
+
},
|
|
1264
|
+
}, null, 2));
|
|
1265
|
+
const result = await recordSkillActivation({
|
|
1266
|
+
stateDir,
|
|
1267
|
+
sourceCwd: cwd,
|
|
1268
|
+
text: '$ultragoal execute the approved ralplan',
|
|
1269
|
+
sessionId: 'sess-ralplan-ultragoal',
|
|
1270
|
+
nowIso: '2026-04-10T00:20:00.000Z',
|
|
1271
|
+
});
|
|
1272
|
+
assert.equal(result?.transition_error, undefined);
|
|
1273
|
+
assert.equal(result?.skill, 'ultragoal');
|
|
1274
|
+
assert.equal(result?.transition_message, 'mode transiting: ralplan -> ultragoal');
|
|
1275
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ultragoal']);
|
|
1276
|
+
const ralplan = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ralplan-state.json'), 'utf-8'));
|
|
1277
|
+
assert.equal(ralplan.active, false);
|
|
1278
|
+
assert.equal(ralplan.current_phase, 'completed');
|
|
1279
|
+
assert.equal(ralplan.auto_completed_reason, 'mode transiting: ralplan -> ultragoal');
|
|
1280
|
+
assert.ok(ralplan.completed_at);
|
|
1281
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-ultragoal', 'ultragoal-state.json'), 'utf-8'));
|
|
1282
|
+
assert.equal(ultragoal.active, true);
|
|
1283
|
+
assert.equal(ultragoal.mode, 'ultragoal');
|
|
1284
|
+
assert.equal(ultragoal.current_phase, 'planning');
|
|
1285
|
+
}
|
|
1286
|
+
finally {
|
|
1287
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1037
1290
|
it('keeps root team state out of the session-scoped Ralph canonical state', async () => {
|
|
1038
1291
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-ralph-'));
|
|
1039
1292
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -1523,6 +1776,211 @@ deepMaxRounds = 21
|
|
|
1523
1776
|
await rm(cwd, { recursive: true, force: true });
|
|
1524
1777
|
}
|
|
1525
1778
|
});
|
|
1779
|
+
it('keeps Autopilot visible when a supervised code-review child keyword appears', async () => {
|
|
1780
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-code-review-'));
|
|
1781
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1782
|
+
const sessionId = 'sess-autopilot-child-code-review';
|
|
1783
|
+
try {
|
|
1784
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
1785
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1786
|
+
version: 1,
|
|
1787
|
+
active: true,
|
|
1788
|
+
skill: 'autopilot',
|
|
1789
|
+
keyword: '$autopilot',
|
|
1790
|
+
phase: 'ralplan',
|
|
1791
|
+
activated_at: '2026-05-30T00:00:00.000Z',
|
|
1792
|
+
updated_at: '2026-05-30T00:01:00.000Z',
|
|
1793
|
+
source: 'keyword-detector',
|
|
1794
|
+
session_id: sessionId,
|
|
1795
|
+
active_skills: [{ skill: 'autopilot', phase: 'ralplan', active: true, session_id: sessionId }],
|
|
1796
|
+
}, null, 2));
|
|
1797
|
+
const result = await recordSkillActivation({
|
|
1798
|
+
stateDir,
|
|
1799
|
+
text: 'CODE REVIEW the current diff before continuing',
|
|
1800
|
+
sessionId,
|
|
1801
|
+
threadId: 'thread-autopilot-child-code-review',
|
|
1802
|
+
turnId: 'turn-autopilot-child-code-review',
|
|
1803
|
+
nowIso: '2026-05-30T00:02:00.000Z',
|
|
1804
|
+
});
|
|
1805
|
+
assert.ok(result);
|
|
1806
|
+
assert.equal(result.skill, 'autopilot');
|
|
1807
|
+
assert.equal(result.phase, 'ralplan');
|
|
1808
|
+
assert.equal(result.supervised_child_skill, 'code-review');
|
|
1809
|
+
const persisted = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), 'utf-8'));
|
|
1810
|
+
assert.equal(persisted.skill, 'autopilot');
|
|
1811
|
+
assert.equal(persisted.phase, 'ralplan');
|
|
1812
|
+
assert.deepEqual(persisted.active_skills?.map((entry) => entry.skill), ['autopilot']);
|
|
1813
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'code-review-state.json')), false);
|
|
1814
|
+
}
|
|
1815
|
+
finally {
|
|
1816
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
it('keeps tracked Autopilot child keywords supervised and completes stale child mode state', async () => {
|
|
1820
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-ultraqa-'));
|
|
1821
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1822
|
+
const sessionId = 'sess-autopilot-child-ultraqa';
|
|
1823
|
+
try {
|
|
1824
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
1825
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1826
|
+
version: 1,
|
|
1827
|
+
active: true,
|
|
1828
|
+
skill: 'autopilot',
|
|
1829
|
+
keyword: '$autopilot',
|
|
1830
|
+
phase: 'ultraqa',
|
|
1831
|
+
activated_at: '2026-05-30T00:00:00.000Z',
|
|
1832
|
+
updated_at: '2026-05-30T00:01:00.000Z',
|
|
1833
|
+
source: 'keyword-detector',
|
|
1834
|
+
session_id: sessionId,
|
|
1835
|
+
active_skills: [{ skill: 'autopilot', phase: 'ultraqa', active: true, session_id: sessionId }],
|
|
1836
|
+
}, null, 2));
|
|
1837
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), JSON.stringify({
|
|
1838
|
+
active: true,
|
|
1839
|
+
mode: 'ultragoal',
|
|
1840
|
+
current_phase: 'planning',
|
|
1841
|
+
session_id: sessionId,
|
|
1842
|
+
started_at: '2026-05-29T23:00:00.000Z',
|
|
1843
|
+
updated_at: '2026-05-29T23:05:00.000Z',
|
|
1844
|
+
}, null, 2));
|
|
1845
|
+
const result = await recordSkillActivation({
|
|
1846
|
+
stateDir,
|
|
1847
|
+
text: '$ultraqa run adversarial checks',
|
|
1848
|
+
sessionId,
|
|
1849
|
+
threadId: 'thread-autopilot-child-ultraqa',
|
|
1850
|
+
turnId: 'turn-autopilot-child-ultraqa',
|
|
1851
|
+
nowIso: '2026-05-30T00:02:00.000Z',
|
|
1852
|
+
});
|
|
1853
|
+
assert.ok(result);
|
|
1854
|
+
assert.equal(result.skill, 'autopilot');
|
|
1855
|
+
assert.equal(result.phase, 'ultraqa');
|
|
1856
|
+
assert.equal(result.supervised_child_skill, 'ultraqa');
|
|
1857
|
+
assert.equal(result.transition_error, undefined);
|
|
1858
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'ultraqa-state.json')), false);
|
|
1859
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), 'utf-8'));
|
|
1860
|
+
assert.equal(ultragoal.active, false);
|
|
1861
|
+
assert.equal(ultragoal.current_phase, 'completed');
|
|
1862
|
+
assert.match(ultragoal.auto_completed_reason || '', /mode transiting: ultragoal -> ultraqa/);
|
|
1863
|
+
}
|
|
1864
|
+
finally {
|
|
1865
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
it('denies supervised Autopilot child rollback without clearing stale execution state', async () => {
|
|
1869
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-rollback-'));
|
|
1870
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1871
|
+
const sessionId = 'sess-autopilot-child-rollback';
|
|
1872
|
+
try {
|
|
1873
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
1874
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1875
|
+
version: 1,
|
|
1876
|
+
active: true,
|
|
1877
|
+
skill: 'autopilot',
|
|
1878
|
+
keyword: '$autopilot',
|
|
1879
|
+
phase: 'ultragoal',
|
|
1880
|
+
session_id: sessionId,
|
|
1881
|
+
active_skills: [{ skill: 'autopilot', phase: 'ultragoal', active: true, session_id: sessionId }],
|
|
1882
|
+
}, null, 2));
|
|
1883
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), JSON.stringify({
|
|
1884
|
+
active: true,
|
|
1885
|
+
mode: 'ultragoal',
|
|
1886
|
+
current_phase: 'executing',
|
|
1887
|
+
session_id: sessionId,
|
|
1888
|
+
}, null, 2));
|
|
1889
|
+
const result = await recordSkillActivation({
|
|
1890
|
+
stateDir,
|
|
1891
|
+
text: '$deep-interview go back and re-plan',
|
|
1892
|
+
sessionId,
|
|
1893
|
+
nowIso: '2026-05-30T00:03:00.000Z',
|
|
1894
|
+
});
|
|
1895
|
+
assert.equal(result?.skill, 'autopilot');
|
|
1896
|
+
assert.match(String(result?.transition_error), /Execution-to-planning rollback auto-complete is not allowed/i);
|
|
1897
|
+
assert.equal(result?.supervised_child_skill, undefined);
|
|
1898
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json')), false);
|
|
1899
|
+
const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ultragoal-state.json'), 'utf-8'));
|
|
1900
|
+
assert.equal(ultragoal.active, true);
|
|
1901
|
+
assert.equal(ultragoal.current_phase, 'executing');
|
|
1902
|
+
}
|
|
1903
|
+
finally {
|
|
1904
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
it('surfaces supervised Autopilot deep-interview to ralplan gate failures', async () => {
|
|
1908
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-gate-'));
|
|
1909
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1910
|
+
const sessionId = 'sess-autopilot-child-gate';
|
|
1911
|
+
try {
|
|
1912
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
1913
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1914
|
+
version: 1,
|
|
1915
|
+
active: true,
|
|
1916
|
+
skill: 'autopilot',
|
|
1917
|
+
keyword: '$autopilot',
|
|
1918
|
+
phase: 'deep-interview',
|
|
1919
|
+
session_id: sessionId,
|
|
1920
|
+
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
1921
|
+
}, null, 2));
|
|
1922
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json'), JSON.stringify({
|
|
1923
|
+
active: true,
|
|
1924
|
+
mode: 'deep-interview',
|
|
1925
|
+
current_phase: 'intent-first',
|
|
1926
|
+
session_id: sessionId,
|
|
1927
|
+
}, null, 2));
|
|
1928
|
+
const result = await recordSkillActivation({
|
|
1929
|
+
stateDir,
|
|
1930
|
+
text: '$ralplan continue without interview completion evidence',
|
|
1931
|
+
sessionId,
|
|
1932
|
+
nowIso: '2026-05-30T00:04:00.000Z',
|
|
1933
|
+
});
|
|
1934
|
+
assert.equal(result?.skill, 'autopilot');
|
|
1935
|
+
assert.match(String(result?.transition_error), /missing deep-interview completion\/skip gate/i);
|
|
1936
|
+
assert.equal(result?.supervised_child_skill, undefined);
|
|
1937
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'ralplan-state.json')), false);
|
|
1938
|
+
const deepInterview = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json'), 'utf-8'));
|
|
1939
|
+
assert.equal(deepInterview.active, true);
|
|
1940
|
+
assert.equal(deepInterview.current_phase, 'intent-first');
|
|
1941
|
+
}
|
|
1942
|
+
finally {
|
|
1943
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
it('ignores stale root child mode state during session-scoped Autopilot child reconciliation', async () => {
|
|
1947
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-child-session-root-'));
|
|
1948
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1949
|
+
const sessionId = 'sess-autopilot-child-session-root';
|
|
1950
|
+
try {
|
|
1951
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
1952
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1953
|
+
version: 1,
|
|
1954
|
+
active: true,
|
|
1955
|
+
skill: 'autopilot',
|
|
1956
|
+
keyword: '$autopilot',
|
|
1957
|
+
phase: 'deep-interview',
|
|
1958
|
+
session_id: sessionId,
|
|
1959
|
+
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
1960
|
+
}, null, 2));
|
|
1961
|
+
await writeFile(join(stateDir, 'ultragoal-state.json'), JSON.stringify({
|
|
1962
|
+
active: true,
|
|
1963
|
+
mode: 'ultragoal',
|
|
1964
|
+
current_phase: 'executing',
|
|
1965
|
+
}, null, 2));
|
|
1966
|
+
const result = await recordSkillActivation({
|
|
1967
|
+
stateDir,
|
|
1968
|
+
text: '$deep-interview continue scoped interview',
|
|
1969
|
+
sessionId,
|
|
1970
|
+
nowIso: '2026-05-30T00:05:00.000Z',
|
|
1971
|
+
});
|
|
1972
|
+
assert.equal(result?.skill, 'autopilot');
|
|
1973
|
+
assert.equal(result?.supervised_child_skill, 'deep-interview');
|
|
1974
|
+
assert.equal(result?.transition_error, undefined);
|
|
1975
|
+
const rootUltragoal = JSON.parse(await readFile(join(stateDir, 'ultragoal-state.json'), 'utf-8'));
|
|
1976
|
+
assert.equal(rootUltragoal.active, true);
|
|
1977
|
+
assert.equal(rootUltragoal.current_phase, 'executing');
|
|
1978
|
+
assert.equal(existsSync(join(stateDir, 'sessions', sessionId, 'deep-interview-state.json')), false);
|
|
1979
|
+
}
|
|
1980
|
+
finally {
|
|
1981
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1526
1984
|
it('records ultragoal as a prompt skill with first-class mode state', async () => {
|
|
1527
1985
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ultragoal-'));
|
|
1528
1986
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -1547,19 +2005,27 @@ deepMaxRounds = 21
|
|
|
1547
2005
|
}
|
|
1548
2006
|
});
|
|
1549
2007
|
it('emits a warning when skill-active-state persistence fails', async () => {
|
|
2008
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-persist-fail-'));
|
|
1550
2009
|
const warnings = [];
|
|
1551
2010
|
mock.method(console, 'warn', (...args) => {
|
|
1552
2011
|
warnings.push(args);
|
|
1553
2012
|
});
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
2013
|
+
try {
|
|
2014
|
+
const blockingFile = join(cwd, 'state-root-file');
|
|
2015
|
+
await writeFile(blockingFile, 'not a directory');
|
|
2016
|
+
const result = await recordSkillActivation({
|
|
2017
|
+
stateDir: join(blockingFile, 'nested', 'state-dir'),
|
|
2018
|
+
text: 'please run $autopilot',
|
|
2019
|
+
nowIso: '2026-02-25T00:00:00.000Z',
|
|
2020
|
+
});
|
|
2021
|
+
assert.ok(result);
|
|
2022
|
+
assert.equal(result.skill, 'autopilot');
|
|
2023
|
+
assert.equal(warnings.length, 1);
|
|
2024
|
+
assert.match(String(warnings[0][0]), /failed to persist keyword activation state/);
|
|
2025
|
+
}
|
|
2026
|
+
finally {
|
|
2027
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2028
|
+
}
|
|
1563
2029
|
});
|
|
1564
2030
|
it('preserves activated_at for same-skill continuation', async () => {
|
|
1565
2031
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-continuation-'));
|
|
@@ -1820,6 +2286,141 @@ deepMaxRounds = 21
|
|
|
1820
2286
|
await rm(cwd, { recursive: true, force: true });
|
|
1821
2287
|
}
|
|
1822
2288
|
});
|
|
2289
|
+
it('preserves active Autopilot question-wait state on bare continuation', async () => {
|
|
2290
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-question-wait-'));
|
|
2291
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2292
|
+
const sessionId = 'sess-autopilot-question-wait';
|
|
2293
|
+
try {
|
|
2294
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2295
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2296
|
+
version: 1,
|
|
2297
|
+
active: true,
|
|
2298
|
+
skill: 'autopilot',
|
|
2299
|
+
keyword: '$autopilot',
|
|
2300
|
+
phase: 'waiting-for-user',
|
|
2301
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2302
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2303
|
+
source: 'keyword-detector',
|
|
2304
|
+
session_id: sessionId,
|
|
2305
|
+
active_skills: [
|
|
2306
|
+
{
|
|
2307
|
+
skill: 'autopilot',
|
|
2308
|
+
phase: 'waiting-for-user',
|
|
2309
|
+
active: true,
|
|
2310
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2311
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2312
|
+
session_id: sessionId,
|
|
2313
|
+
},
|
|
2314
|
+
],
|
|
2315
|
+
}, null, 2));
|
|
2316
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
2317
|
+
active: true,
|
|
2318
|
+
mode: 'autopilot',
|
|
2319
|
+
current_phase: 'waiting-for-user',
|
|
2320
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
2321
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2322
|
+
session_id: sessionId,
|
|
2323
|
+
iteration: 4,
|
|
2324
|
+
max_iterations: 10,
|
|
2325
|
+
review_cycle: 2,
|
|
2326
|
+
run_outcome: 'blocked_on_user',
|
|
2327
|
+
lifecycle_outcome: 'askuserQuestion',
|
|
2328
|
+
state: {
|
|
2329
|
+
deep_interview_question: {
|
|
2330
|
+
status: 'waiting_for_user',
|
|
2331
|
+
obligation_id: 'obligation-question-wait',
|
|
2332
|
+
previous_phase: 'deep-interview',
|
|
2333
|
+
},
|
|
2334
|
+
},
|
|
2335
|
+
}, null, 2));
|
|
2336
|
+
const result = await recordSkillActivation({
|
|
2337
|
+
stateDir,
|
|
2338
|
+
text: '\\ keep going now',
|
|
2339
|
+
sessionId,
|
|
2340
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
2341
|
+
});
|
|
2342
|
+
assert.ok(result);
|
|
2343
|
+
assert.equal(result.skill, 'autopilot');
|
|
2344
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
2345
|
+
assert.equal(modeState.current_phase, 'waiting-for-user');
|
|
2346
|
+
assert.equal(modeState.iteration, 4);
|
|
2347
|
+
assert.equal(modeState.max_iterations, 10);
|
|
2348
|
+
assert.equal(modeState.review_cycle, 2);
|
|
2349
|
+
assert.equal(modeState.lifecycle_outcome, 'askuserQuestion');
|
|
2350
|
+
assert.equal(modeState.state?.deep_interview_question?.status, 'waiting_for_user');
|
|
2351
|
+
assert.equal(modeState.state?.deep_interview_question?.obligation_id, 'obligation-question-wait');
|
|
2352
|
+
}
|
|
2353
|
+
finally {
|
|
2354
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
it('resets terminal Ralph blocked_on_user state when reactivated', async () => {
|
|
2358
|
+
const cases = [
|
|
2359
|
+
{ name: 'phase', phase: 'blocked_on_user', run_outcome: undefined },
|
|
2360
|
+
{ name: 'outcome', phase: 'executing', run_outcome: 'blocked_on_user' },
|
|
2361
|
+
];
|
|
2362
|
+
for (const testCase of cases) {
|
|
2363
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-state-ralph-terminal-${testCase.name}-reactivation-`));
|
|
2364
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2365
|
+
const sessionId = `sess-ralph-terminal-${testCase.name}`;
|
|
2366
|
+
try {
|
|
2367
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
2368
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
2369
|
+
version: 1,
|
|
2370
|
+
active: true,
|
|
2371
|
+
skill: 'ralph',
|
|
2372
|
+
keyword: '$ralph',
|
|
2373
|
+
phase: testCase.phase,
|
|
2374
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2375
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2376
|
+
source: 'keyword-detector',
|
|
2377
|
+
session_id: sessionId,
|
|
2378
|
+
active_skills: [
|
|
2379
|
+
{
|
|
2380
|
+
skill: 'ralph',
|
|
2381
|
+
phase: testCase.phase,
|
|
2382
|
+
active: true,
|
|
2383
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
2384
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
2385
|
+
session_id: sessionId,
|
|
2386
|
+
},
|
|
2387
|
+
],
|
|
2388
|
+
}, null, 2));
|
|
2389
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'ralph-state.json'), JSON.stringify({
|
|
2390
|
+
active: false,
|
|
2391
|
+
mode: 'ralph',
|
|
2392
|
+
current_phase: testCase.phase,
|
|
2393
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
2394
|
+
completed_at: '2026-04-19T00:10:00.000Z',
|
|
2395
|
+
iteration: 50,
|
|
2396
|
+
max_iterations: 50,
|
|
2397
|
+
...(testCase.run_outcome ? { run_outcome: testCase.run_outcome } : {}),
|
|
2398
|
+
}, null, 2));
|
|
2399
|
+
const result = await recordSkillActivation({
|
|
2400
|
+
stateDir,
|
|
2401
|
+
text: '\\ keep going now',
|
|
2402
|
+
sessionId,
|
|
2403
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
2404
|
+
});
|
|
2405
|
+
assert.ok(result);
|
|
2406
|
+
assert.equal(result.skill, 'ralph');
|
|
2407
|
+
assert.equal(result.phase, 'planning');
|
|
2408
|
+
assert.equal(result.activated_at, '2026-04-19T00:15:00.000Z');
|
|
2409
|
+
assert.equal(result.active_skills?.[0]?.phase, 'planning');
|
|
2410
|
+
assert.equal(result.active_skills?.[0]?.activated_at, '2026-04-19T00:15:00.000Z');
|
|
2411
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'ralph-state.json'), 'utf-8'));
|
|
2412
|
+
assert.equal(modeState.active, true);
|
|
2413
|
+
assert.equal(modeState.current_phase, 'starting');
|
|
2414
|
+
assert.equal(modeState.started_at, '2026-04-19T00:15:00.000Z');
|
|
2415
|
+
assert.equal(modeState.completed_at, undefined);
|
|
2416
|
+
assert.equal(modeState.iteration, 0);
|
|
2417
|
+
assert.equal(modeState.max_iterations, 50);
|
|
2418
|
+
}
|
|
2419
|
+
finally {
|
|
2420
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
1823
2424
|
it('routes bare keep-going continuation to the active ralph skill instead of resetting through generic keep-going detection', async () => {
|
|
1824
2425
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ralph-bare-continuation-'));
|
|
1825
2426
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -2113,6 +2714,43 @@ describe('applyRalplanGate', () => {
|
|
|
2113
2714
|
await rm(cwd, { recursive: true, force: true });
|
|
2114
2715
|
}
|
|
2115
2716
|
});
|
|
2717
|
+
it('keeps native-proof execution follow-ups gated when consensus is artifact-only', async () => {
|
|
2718
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-native-required-'));
|
|
2719
|
+
try {
|
|
2720
|
+
const plansDir = join(cwd, '.omx', 'plans');
|
|
2721
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
2722
|
+
await mkdir(plansDir, { recursive: true });
|
|
2723
|
+
await mkdir(stateDir, { recursive: true });
|
|
2724
|
+
await writeFile(join(plansDir, 'prd-issue-833.md'), '# Approved plan\n\nLaunch hint: omx team 3:executor "Execute approved issue 833 plan"\n');
|
|
2725
|
+
await writeFile(join(plansDir, 'test-spec-issue-833.md'), '# Test spec\n');
|
|
2726
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
2727
|
+
current_phase: 'complete',
|
|
2728
|
+
planning_complete: true,
|
|
2729
|
+
ralplan_consensus_gate: {
|
|
2730
|
+
complete: true,
|
|
2731
|
+
sequence: ['architect-review', 'critic-review'],
|
|
2732
|
+
ralplan_architect_review: {
|
|
2733
|
+
agent_role: 'architect',
|
|
2734
|
+
verdict: 'approve',
|
|
2735
|
+
iteration: 1,
|
|
2736
|
+
provenance_kind: 'codex_exec',
|
|
2737
|
+
},
|
|
2738
|
+
ralplan_critic_review: {
|
|
2739
|
+
agent_role: 'critic',
|
|
2740
|
+
verdict: 'approve',
|
|
2741
|
+
iteration: 1,
|
|
2742
|
+
provenance_kind: 'codex_exec',
|
|
2743
|
+
},
|
|
2744
|
+
},
|
|
2745
|
+
}));
|
|
2746
|
+
const result = applyRalplanGate(['team'], 'team', { cwd, requireNativeSubagents: true });
|
|
2747
|
+
assert.equal(result.gateApplied, true);
|
|
2748
|
+
assert.deepEqual(result.keywords, ['ralplan']);
|
|
2749
|
+
}
|
|
2750
|
+
finally {
|
|
2751
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2116
2754
|
it('does not re-enter ralplan for a short approved ralph follow-up with durable consensus', async () => {
|
|
2117
2755
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-followup-ralph-'));
|
|
2118
2756
|
try {
|