oh-my-codex 0.18.7 → 0.18.8
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 +5 -5
- package/crates/omx-sparkshell/tests/execution.rs +1 -1
- package/dist/agents/__tests__/native-config.test.js +42 -1
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +8 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +1 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +17 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +61 -5
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +14 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +65 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +21 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/update.test.js +110 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +8 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +11 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +108 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +14 -2
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +62 -15
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +2 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +4 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +166 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +8 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +16 -0
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +47 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +1 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +2 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +2 -2
- package/dist/config/generator.js.map +1 -1
- package/dist/config/team-mode.d.ts +12 -0
- package/dist/config/team-mode.d.ts.map +1 -0
- package/dist/config/team-mode.js +91 -0
- package/dist/config/team-mode.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +36 -50
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +17 -21
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +258 -12
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +1 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +435 -32
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +42 -0
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +521 -15
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +61 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +132 -4
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +180 -21
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts +5 -0
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +324 -28
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +3 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +42 -19
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -3
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +128 -19
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +35 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +61 -62
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +24 -6
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +136 -38
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +11 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +71 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +32 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +113 -17
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/scripts/__tests__/codex-native-hook.test.js +593 -11
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js +74 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +88 -31
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +3 -1
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +62 -38
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +24 -18
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +75 -11
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +193 -22
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +61 -3
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +58 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +113 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +3 -16
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +25 -0
- 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 +57 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +7 -39
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +10 -14
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +1 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +9 -4
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +195 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +3 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +142 -12
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +8 -8
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
- package/plugins/oh-my-codex/hooks/hooks.json +1 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
- package/skills/autopilot/SKILL.md +3 -1
- package/skills/code-review/SKILL.md +7 -7
- package/skills/ralph/SKILL.md +22 -22
- package/skills/ultraqa/SKILL.md +9 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +686 -13
- package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
- package/src/scripts/__tests__/run-test-files.test.ts +102 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
- package/src/scripts/codex-native-hook.ts +105 -28
- package/src/scripts/demo-team-e2e.sh +10 -7
- package/src/scripts/eval/eval-parity-smoke.ts +1 -1
- package/src/scripts/notify-hook/auto-nudge.ts +3 -1
- package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
- package/src/scripts/notify-hook/state-io.ts +75 -37
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
- package/src/scripts/notify-hook/tmux-injection.ts +35 -19
- package/src/scripts/notify-hook.ts +91 -4
- package/src/scripts/run-test-files.ts +192 -22
- package/src/scripts/sync-plugin-mirror.ts +98 -9
- package/src/scripts/verify-native-agents.ts +65 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
-
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
|
|
@@ -23,6 +23,54 @@ async function withIsolatedHome(prefix, run) {
|
|
|
23
23
|
await rm(homeDir, { recursive: true, force: true });
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
const AUTOPILOT_TEST_NOW = '2026-05-30T00:00:00.000Z';
|
|
27
|
+
const AUTOPILOT_TEST_STARTED_AT = '2026-05-29T00:00:00.000Z';
|
|
28
|
+
const AUTOPILOT_TEST_UPDATED_AT = '2026-05-29T00:10:00.000Z';
|
|
29
|
+
async function writeActiveAutopilotSkillState(stateDir, sessionId, phase = 'ralplan') {
|
|
30
|
+
await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
|
|
31
|
+
await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
32
|
+
version: 1,
|
|
33
|
+
active: true,
|
|
34
|
+
skill: 'autopilot',
|
|
35
|
+
keyword: '$autopilot',
|
|
36
|
+
phase,
|
|
37
|
+
activated_at: AUTOPILOT_TEST_STARTED_AT,
|
|
38
|
+
updated_at: AUTOPILOT_TEST_UPDATED_AT,
|
|
39
|
+
session_id: sessionId,
|
|
40
|
+
active_skills: [{ skill: 'autopilot', active: true, phase, session_id: sessionId }],
|
|
41
|
+
}, null, 2));
|
|
42
|
+
}
|
|
43
|
+
async function readAutopilotModeState(stateDir, sessionId) {
|
|
44
|
+
return JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
async function continueAutopilotTestState(stateDir, cwd, sessionId, suffix, text = 'continue') {
|
|
47
|
+
await recordSkillActivation({
|
|
48
|
+
stateDir,
|
|
49
|
+
sourceCwd: cwd,
|
|
50
|
+
text,
|
|
51
|
+
sessionId,
|
|
52
|
+
threadId: `thread-${suffix}`,
|
|
53
|
+
turnId: `turn-${suffix}`,
|
|
54
|
+
nowIso: AUTOPILOT_TEST_NOW,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async function assertAutopilotRecoverySnapshot(cwd, modeState, expectedPath, expectedReason) {
|
|
58
|
+
const snapshotPath = modeState.state?.handoff_artifacts?.context_snapshot_path ?? '';
|
|
59
|
+
if (typeof expectedPath === 'string')
|
|
60
|
+
assert.equal(snapshotPath, expectedPath);
|
|
61
|
+
else
|
|
62
|
+
assert.match(snapshotPath, expectedPath);
|
|
63
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.kind, 'recovery');
|
|
64
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.recovery?.reason, expectedReason);
|
|
65
|
+
assert.equal(modeState.state?.context_snapshot_recovery?.status, 'degraded');
|
|
66
|
+
assert.equal(modeState.state?.context_snapshot_recovery?.reason, expectedReason);
|
|
67
|
+
const recoverySnapshot = await readFile(join(cwd, snapshotPath), 'utf-8');
|
|
68
|
+
assert.match(recoverySnapshot, /recovery status: degraded/);
|
|
69
|
+
assert.match(recoverySnapshot, new RegExp(`recovery reason: ${expectedReason}`));
|
|
70
|
+
assert.match(recoverySnapshot, /do not treat the continuation input as the task seed/);
|
|
71
|
+
assert.doesNotMatch(recoverySnapshot, /task seed: continue/);
|
|
72
|
+
return snapshotPath;
|
|
73
|
+
}
|
|
26
74
|
describe('keyword detector team compatibility', () => {
|
|
27
75
|
it('keeps explicit $skill order in detectKeywords results (left-to-right)', () => {
|
|
28
76
|
const matches = detectKeywords('$analyze $ultraqa $code-review now');
|
|
@@ -496,6 +544,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
496
544
|
rationale: 'Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.',
|
|
497
545
|
});
|
|
498
546
|
assert.deepEqual(modeState.state.handoff_artifacts, {
|
|
547
|
+
context_snapshot_path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
|
|
548
|
+
context_snapshot: {
|
|
549
|
+
path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
|
|
550
|
+
kind: 'canonical',
|
|
551
|
+
original_task_status: 'activation-prompt',
|
|
552
|
+
},
|
|
499
553
|
deep_interview: null,
|
|
500
554
|
ralplan: null,
|
|
501
555
|
ralplan_consensus_gate: {
|
|
@@ -514,6 +568,294 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
514
568
|
assert.equal(modeState.state.review_verdict, null);
|
|
515
569
|
assert.equal(modeState.state.qa_verdict, null);
|
|
516
570
|
assert.equal(modeState.state.return_to_ralplan_reason, null);
|
|
571
|
+
const snapshot = await readFile(join(cwd, '.omx', 'context', 'please-run-and-keep-going-20260225T000000Z.md'), 'utf-8');
|
|
572
|
+
assert.match(snapshot, /activation prompt \/ task seed: please run \$autopilot and keep going/);
|
|
573
|
+
assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
await rm(cwd, { recursive: true, force: true });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
it('migrates legacy Autopilot context snapshot paths into handoff artifacts', async () => {
|
|
580
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-legacy-context-'));
|
|
581
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
582
|
+
const sessionId = 'sess-autopilot-legacy-context';
|
|
583
|
+
try {
|
|
584
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
|
|
585
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
586
|
+
active: true,
|
|
587
|
+
mode: 'autopilot',
|
|
588
|
+
current_phase: 'deep-interview',
|
|
589
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
590
|
+
context_snapshot_path: '.omx/context/legacy-task-20260529T000000Z.md',
|
|
591
|
+
state: { handoff_artifacts: { deep_interview: null } },
|
|
592
|
+
}, null, 2));
|
|
593
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
594
|
+
await writeFile(join(cwd, '.omx', 'context', 'legacy-task-20260529T000000Z.md'), '# legacy task');
|
|
595
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'legacy');
|
|
596
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
597
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/legacy-task-20260529T000000Z.md');
|
|
598
|
+
assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
|
|
599
|
+
path: '.omx/context/legacy-task-20260529T000000Z.md',
|
|
600
|
+
kind: 'legacy',
|
|
601
|
+
original_task_status: 'legacy-unverified',
|
|
602
|
+
});
|
|
603
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
await rm(cwd, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
it('rejects unsafe legacy Autopilot context snapshot paths without writing outside .omx/context', async () => {
|
|
610
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-unsafe-context-'));
|
|
611
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
612
|
+
const sessionId = 'sess-autopilot-unsafe-context';
|
|
613
|
+
try {
|
|
614
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
|
|
615
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
616
|
+
active: true,
|
|
617
|
+
mode: 'autopilot',
|
|
618
|
+
current_phase: 'deep-interview',
|
|
619
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
620
|
+
context_snapshot_path: '.omx/context/../../escape.md',
|
|
621
|
+
state: { handoff_artifacts: { deep_interview: null } },
|
|
622
|
+
}, null, 2));
|
|
623
|
+
const result = await recordSkillActivation({
|
|
624
|
+
stateDir,
|
|
625
|
+
text: 'continue',
|
|
626
|
+
sessionId,
|
|
627
|
+
threadId: 'thread-unsafe',
|
|
628
|
+
turnId: 'turn-unsafe',
|
|
629
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
630
|
+
});
|
|
631
|
+
assert.ok(result);
|
|
632
|
+
assert.equal(existsSync(join(cwd, '.omx', 'escape.md')), false);
|
|
633
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
634
|
+
assert.equal(modeState.context_snapshot_path, undefined);
|
|
635
|
+
await assertAutopilotRecoverySnapshot(cwd, modeState, '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
await rm(cwd, { recursive: true, force: true });
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
it('does not snapshot bare continuation text when active Autopilot mode state is corrupt', async () => {
|
|
642
|
+
const expectedReasons = {
|
|
643
|
+
'missing-current-phase': 'nonpreservable-autopilot-mode-state-missing-current-phase',
|
|
644
|
+
'malformed-json': 'malformed-autopilot-mode-state',
|
|
645
|
+
'array-json': 'malformed-autopilot-mode-state',
|
|
646
|
+
};
|
|
647
|
+
for (const fixture of ['missing-current-phase', 'malformed-json', 'array-json']) {
|
|
648
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-autopilot-corrupt-continuation-${fixture}-`));
|
|
649
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
650
|
+
const sessionId = `sess-autopilot-corrupt-continuation-${fixture}`;
|
|
651
|
+
try {
|
|
652
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
653
|
+
const modeStatePath = join(stateDir, 'sessions', sessionId, 'autopilot-state.json');
|
|
654
|
+
if (fixture === 'missing-current-phase') {
|
|
655
|
+
await writeFile(modeStatePath, JSON.stringify({
|
|
656
|
+
active: true,
|
|
657
|
+
mode: 'autopilot',
|
|
658
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
659
|
+
state: { handoff_artifacts: {} },
|
|
660
|
+
}, null, 2));
|
|
661
|
+
}
|
|
662
|
+
else if (fixture === 'malformed-json') {
|
|
663
|
+
await writeFile(modeStatePath, '{ "active": true, "mode": "autopilot",');
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
await writeFile(modeStatePath, '[]');
|
|
667
|
+
}
|
|
668
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, fixture);
|
|
669
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
|
|
670
|
+
await assertAutopilotRecoverySnapshot(cwd, JSON.parse(await readFile(modeStatePath, 'utf-8')), /^\.omx\/context\/autopilot-recovery-20260530T000000Z(?:-\d+)?\.md$/, expectedReasons[fixture]);
|
|
671
|
+
}
|
|
672
|
+
finally {
|
|
673
|
+
await rm(cwd, { recursive: true, force: true });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
it('rejects nested symlink Autopilot context snapshot candidates during reuse', async () => {
|
|
678
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-context-'));
|
|
679
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-outside-'));
|
|
680
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
681
|
+
const sessionId = 'sess-autopilot-nested-symlink-context';
|
|
682
|
+
try {
|
|
683
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
684
|
+
await symlink(outside, join(cwd, '.omx', 'context', 'link'));
|
|
685
|
+
await writeFile(join(outside, 'exfil.md'), '# outside context');
|
|
686
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
687
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
688
|
+
active: true,
|
|
689
|
+
mode: 'autopilot',
|
|
690
|
+
current_phase: 'ralplan',
|
|
691
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
692
|
+
state: { handoff_artifacts: { context_snapshot_path: '.omx/context/link/exfil.md' } },
|
|
693
|
+
}, null, 2));
|
|
694
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'nested-symlink');
|
|
695
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
696
|
+
assert.equal(existsSync(join(outside, 'exfil.md')), true);
|
|
697
|
+
}
|
|
698
|
+
finally {
|
|
699
|
+
await rm(cwd, { recursive: true, force: true });
|
|
700
|
+
await rm(outside, { recursive: true, force: true });
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
it('rejects typed canonical Autopilot recovery snapshot candidates during reuse', async () => {
|
|
704
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-typed-recovery-context-'));
|
|
705
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
706
|
+
const sessionId = 'sess-autopilot-typed-recovery-context';
|
|
707
|
+
try {
|
|
708
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
709
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# stale degraded recovery');
|
|
710
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
711
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
712
|
+
active: true,
|
|
713
|
+
mode: 'autopilot',
|
|
714
|
+
current_phase: 'ralplan',
|
|
715
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
716
|
+
state: {
|
|
717
|
+
handoff_artifacts: {
|
|
718
|
+
context_snapshot: {
|
|
719
|
+
path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
720
|
+
kind: 'canonical',
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
}, null, 2));
|
|
725
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'typed-recovery');
|
|
726
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
727
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md')), true);
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
await rm(cwd, { recursive: true, force: true });
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
it('rejects oversized Autopilot context snapshot candidates during reuse', async () => {
|
|
734
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-oversized-context-'));
|
|
735
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
736
|
+
const sessionId = 'sess-autopilot-oversized-context';
|
|
737
|
+
try {
|
|
738
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
739
|
+
await writeFile(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md'), 'x'.repeat((1024 * 1024) + 1));
|
|
740
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId);
|
|
741
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
742
|
+
active: true,
|
|
743
|
+
mode: 'autopilot',
|
|
744
|
+
current_phase: 'ralplan',
|
|
745
|
+
started_at: AUTOPILOT_TEST_STARTED_AT,
|
|
746
|
+
state: {
|
|
747
|
+
handoff_artifacts: {
|
|
748
|
+
context_snapshot_path: '.omx/context/oversized-legacy-20260529T000000Z.md',
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
}, null, 2));
|
|
752
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'oversized-context');
|
|
753
|
+
await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
|
|
754
|
+
assert.equal(existsSync(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md')), true);
|
|
755
|
+
}
|
|
756
|
+
finally {
|
|
757
|
+
await rm(cwd, { recursive: true, force: true });
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
it('does not promote degraded recovery snapshots to canonical context on reactivation', async () => {
|
|
761
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-recovery-reactivation-'));
|
|
762
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
763
|
+
const sessionId = 'sess-autopilot-recovery-reactivation';
|
|
764
|
+
try {
|
|
765
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
766
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# degraded recovery');
|
|
767
|
+
await writeActiveAutopilotSkillState(stateDir, sessionId, 'complete');
|
|
768
|
+
await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
|
|
769
|
+
active: true,
|
|
770
|
+
mode: 'autopilot',
|
|
771
|
+
current_phase: 'complete',
|
|
772
|
+
completed_at: AUTOPILOT_TEST_UPDATED_AT,
|
|
773
|
+
state: {
|
|
774
|
+
handoff_artifacts: {
|
|
775
|
+
context_snapshot_path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
776
|
+
context_snapshot: {
|
|
777
|
+
path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
|
|
778
|
+
kind: 'recovery',
|
|
779
|
+
recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
context_snapshot_recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
|
|
783
|
+
},
|
|
784
|
+
}, null, 2));
|
|
785
|
+
await continueAutopilotTestState(stateDir, cwd, sessionId, 'recovery-reactivation', '$autopilot implement the real task');
|
|
786
|
+
const modeState = await readAutopilotModeState(stateDir, sessionId);
|
|
787
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/implement-the-real-task-20260530T000000Z.md');
|
|
788
|
+
assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
|
|
789
|
+
path: '.omx/context/implement-the-real-task-20260530T000000Z.md',
|
|
790
|
+
kind: 'canonical',
|
|
791
|
+
original_task_status: 'activation-prompt',
|
|
792
|
+
});
|
|
793
|
+
assert.equal(modeState.state?.context_snapshot_recovery, undefined);
|
|
794
|
+
const snapshot = await readFile(join(cwd, '.omx', 'context', 'implement-the-real-task-20260530T000000Z.md'), 'utf-8');
|
|
795
|
+
assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement the real task/);
|
|
796
|
+
}
|
|
797
|
+
finally {
|
|
798
|
+
await rm(cwd, { recursive: true, force: true });
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
it('does not follow symlinked Autopilot context directories when writing snapshots', async () => {
|
|
802
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-context-'));
|
|
803
|
+
const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-outside-'));
|
|
804
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
805
|
+
try {
|
|
806
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
807
|
+
await symlink(outside, join(cwd, '.omx', 'context'));
|
|
808
|
+
await mkdir(stateDir, { recursive: true });
|
|
809
|
+
const warnings = [];
|
|
810
|
+
mock.method(console, 'warn', (...args) => {
|
|
811
|
+
warnings.push(args);
|
|
812
|
+
});
|
|
813
|
+
await recordSkillActivation({
|
|
814
|
+
stateDir,
|
|
815
|
+
sourceCwd: cwd,
|
|
816
|
+
text: '$autopilot symlink escape',
|
|
817
|
+
sessionId: 'sess-autopilot-symlink-context',
|
|
818
|
+
threadId: 'thread-symlink-context',
|
|
819
|
+
turnId: 'turn-symlink-context',
|
|
820
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
821
|
+
});
|
|
822
|
+
assert.equal(warnings.length, 1);
|
|
823
|
+
assert.match(String(warnings[0][1]), /symbolic link/);
|
|
824
|
+
assert.equal(existsSync(join(outside, 'symlink-escape-20260530T000000Z.md')), false);
|
|
825
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-symlink-context', 'autopilot-state.json')), false);
|
|
826
|
+
}
|
|
827
|
+
finally {
|
|
828
|
+
await rm(cwd, { recursive: true, force: true });
|
|
829
|
+
await rm(outside, { recursive: true, force: true });
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
it('allocates unique Autopilot context snapshot paths for same-second matching slugs', async () => {
|
|
833
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-context-collision-'));
|
|
834
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
835
|
+
try {
|
|
836
|
+
await mkdir(stateDir, { recursive: true });
|
|
837
|
+
await recordSkillActivation({
|
|
838
|
+
stateDir,
|
|
839
|
+
sourceCwd: cwd,
|
|
840
|
+
text: '$autopilot same task',
|
|
841
|
+
sessionId: 'sess-autopilot-collision-a',
|
|
842
|
+
threadId: 'thread-collision',
|
|
843
|
+
turnId: 'turn-collision-a',
|
|
844
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
845
|
+
});
|
|
846
|
+
await recordSkillActivation({
|
|
847
|
+
stateDir,
|
|
848
|
+
sourceCwd: cwd,
|
|
849
|
+
text: '$autopilot same task',
|
|
850
|
+
sessionId: 'sess-autopilot-collision-b',
|
|
851
|
+
threadId: 'thread-collision',
|
|
852
|
+
turnId: 'turn-collision-b',
|
|
853
|
+
nowIso: '2026-05-30T00:00:00.000Z',
|
|
854
|
+
});
|
|
855
|
+
const first = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-a', 'autopilot-state.json'), 'utf-8'));
|
|
856
|
+
const second = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-b', 'autopilot-state.json'), 'utf-8'));
|
|
857
|
+
assert.equal(first.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z.md');
|
|
858
|
+
assert.equal(second.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z-2.md');
|
|
517
859
|
}
|
|
518
860
|
finally {
|
|
519
861
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -589,6 +931,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
589
931
|
assert.equal(modeState.run_outcome, undefined);
|
|
590
932
|
assert.equal(modeState.handoff_artifacts, undefined);
|
|
591
933
|
assert.deepEqual(modeState.state?.handoff_artifacts, {
|
|
934
|
+
context_snapshot_path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
|
|
935
|
+
context_snapshot: {
|
|
936
|
+
path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
|
|
937
|
+
kind: 'canonical',
|
|
938
|
+
original_task_status: 'activation-prompt',
|
|
939
|
+
},
|
|
592
940
|
deep_interview: null,
|
|
593
941
|
ralplan: null,
|
|
594
942
|
ralplan_consensus_gate: {
|
|
@@ -1167,6 +1515,72 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
1167
1515
|
await rm(cwd, { recursive: true, force: true });
|
|
1168
1516
|
}
|
|
1169
1517
|
});
|
|
1518
|
+
it('does not activate team state when persisted Team mode is disabled', async () => {
|
|
1519
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-'));
|
|
1520
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1521
|
+
try {
|
|
1522
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1523
|
+
await mkdir(stateDir, { recursive: true });
|
|
1524
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1525
|
+
const result = await recordSkillActivation({
|
|
1526
|
+
stateDir,
|
|
1527
|
+
text: '$team coordinate the hotfix',
|
|
1528
|
+
sessionId: 'sess-team-disabled',
|
|
1529
|
+
nowIso: '2026-04-08T00:00:00.000Z',
|
|
1530
|
+
});
|
|
1531
|
+
assert.equal(result, null);
|
|
1532
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1533
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled', SKILL_ACTIVE_STATE_FILE)), false);
|
|
1534
|
+
}
|
|
1535
|
+
finally {
|
|
1536
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
it('ignores disabled Team when selecting the primary workflow', async () => {
|
|
1540
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-primary-'));
|
|
1541
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1542
|
+
try {
|
|
1543
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1544
|
+
await mkdir(stateDir, { recursive: true });
|
|
1545
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1546
|
+
const result = await recordSkillActivation({
|
|
1547
|
+
stateDir,
|
|
1548
|
+
text: '$team $ralph ship this fix',
|
|
1549
|
+
sessionId: 'sess-team-disabled-primary',
|
|
1550
|
+
nowIso: '2026-04-10T01:00:00.000Z',
|
|
1551
|
+
});
|
|
1552
|
+
assert.equal(result?.skill, 'ralph');
|
|
1553
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralph']);
|
|
1554
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1555
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-primary', 'ralph-state.json')), true);
|
|
1556
|
+
}
|
|
1557
|
+
finally {
|
|
1558
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
it('filters deferred team handoffs when persisted Team mode is disabled', async () => {
|
|
1562
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-deferred-'));
|
|
1563
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1564
|
+
try {
|
|
1565
|
+
await mkdir(join(cwd, '.omx'), { recursive: true });
|
|
1566
|
+
await mkdir(stateDir, { recursive: true });
|
|
1567
|
+
await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
|
|
1568
|
+
const result = await recordSkillActivation({
|
|
1569
|
+
stateDir,
|
|
1570
|
+
text: '$ralplan $team $ralph ship this fix',
|
|
1571
|
+
sessionId: 'sess-team-disabled-deferred',
|
|
1572
|
+
nowIso: '2026-04-10T00:00:00.000Z',
|
|
1573
|
+
});
|
|
1574
|
+
assert.equal(result?.skill, 'ralplan');
|
|
1575
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralplan']);
|
|
1576
|
+
assert.deepEqual(result?.deferred_skills, ['ralph']);
|
|
1577
|
+
assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
|
|
1578
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-deferred', 'team-state.json')), false);
|
|
1579
|
+
}
|
|
1580
|
+
finally {
|
|
1581
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1170
1584
|
it('preserves active team root state when $team is re-entered from prompt routing', async () => {
|
|
1171
1585
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-preserve-'));
|
|
1172
1586
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -2084,6 +2498,8 @@ deepMaxRounds = 21
|
|
|
2084
2498
|
session_id: 'sess-autopilot',
|
|
2085
2499
|
state: { context_snapshot_path: '.omx/context/existing.md' },
|
|
2086
2500
|
}));
|
|
2501
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
2502
|
+
await writeFile(join(cwd, '.omx', 'context', 'existing.md'), '# existing context');
|
|
2087
2503
|
const result = await recordSkillActivation({
|
|
2088
2504
|
stateDir,
|
|
2089
2505
|
text: 'autopilot keep going',
|
|
@@ -2097,7 +2513,8 @@ deepMaxRounds = 21
|
|
|
2097
2513
|
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot', 'autopilot-state.json'), 'utf-8'));
|
|
2098
2514
|
assert.equal(modeState.current_phase, 'code-review');
|
|
2099
2515
|
assert.equal(modeState.started_at, '2026-02-25T00:00:00.000Z');
|
|
2100
|
-
assert.equal(modeState.state?.context_snapshot_path,
|
|
2516
|
+
assert.equal(modeState.state?.context_snapshot_path, undefined);
|
|
2517
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/existing.md');
|
|
2101
2518
|
}
|
|
2102
2519
|
finally {
|
|
2103
2520
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -2267,6 +2684,8 @@ deepMaxRounds = 21
|
|
|
2267
2684
|
session_id: 'sess-autopilot-bare',
|
|
2268
2685
|
state: { context_snapshot_path: '.omx/context/autopilot.md' },
|
|
2269
2686
|
}, null, 2));
|
|
2687
|
+
await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
|
|
2688
|
+
await writeFile(join(cwd, '.omx', 'context', 'autopilot.md'), '# autopilot context');
|
|
2270
2689
|
const result = await recordSkillActivation({
|
|
2271
2690
|
stateDir,
|
|
2272
2691
|
text: '\\ keep going now',
|
|
@@ -2279,7 +2698,8 @@ deepMaxRounds = 21
|
|
|
2279
2698
|
assert.equal(result.transition_error, undefined);
|
|
2280
2699
|
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-bare', 'autopilot-state.json'), 'utf-8'));
|
|
2281
2700
|
assert.equal(modeState.current_phase, 'code-review');
|
|
2282
|
-
assert.equal(modeState.state?.context_snapshot_path,
|
|
2701
|
+
assert.equal(modeState.state?.context_snapshot_path, undefined);
|
|
2702
|
+
assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/autopilot.md');
|
|
2283
2703
|
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-bare', 'ralph-state.json')), false);
|
|
2284
2704
|
}
|
|
2285
2705
|
finally {
|