oh-my-codex 0.18.16 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +5 -0
- package/crates/omx-sparkshell/tests/execution.rs +8 -1
- package/dist/agents/__tests__/definitions.test.js +5 -5
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +18 -14
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +2 -2
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +4 -4
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +1 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +18 -16
- package/dist/agents/native-config.js.map +1 -1
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +17 -4
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.js +2 -2
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +1 -1
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autopilot/fsm.js +1 -1
- package/dist/autopilot/fsm.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +78 -3
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -5
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +96 -8
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +135 -1
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +2 -4
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/mission.test.d.ts +2 -0
- package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
- package/dist/cli/__tests__/mission.test.js +239 -0
- package/dist/cli/__tests__/mission.test.js.map +1 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +14 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +8 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +229 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +113 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +37 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +22 -1
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/version.test.js +13 -1
- package/dist/cli/__tests__/version.test.js.map +1 -1
- package/dist/cli/auth.d.ts +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +49 -9
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/index.d.ts +27 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +263 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mission.d.ts +36 -0
- package/dist/cli/mission.d.ts.map +1 -0
- package/dist/cli/mission.js +443 -0
- package/dist/cli/mission.js.map +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +22 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +4 -0
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +3 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +7 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +12 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +267 -7
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +143 -30
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +121 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +141 -6
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +1 -1
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +281 -151
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +4 -2
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +51 -15
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +1 -4
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +5 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/leader/__tests__/contract.test.d.ts +2 -0
- package/dist/leader/__tests__/contract.test.d.ts.map +1 -0
- package/dist/leader/__tests__/contract.test.js +75 -0
- package/dist/leader/__tests__/contract.test.js.map +1 -0
- package/dist/leader/contract.d.ts +26 -0
- package/dist/leader/contract.d.ts.map +1 -0
- package/dist/leader/contract.js +97 -0
- package/dist/leader/contract.js.map +1 -0
- package/dist/mcp/__tests__/state-paths.test.js +32 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +4 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +6 -15
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +22 -7
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/dispatcher.test.js +16 -0
- package/dist/notifications/__tests__/dispatcher.test.js.map +1 -1
- package/dist/notifications/__tests__/profiles.test.js +87 -0
- package/dist/notifications/__tests__/profiles.test.js.map +1 -1
- package/dist/notifications/config.d.ts +1 -0
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +60 -22
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +6 -16
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +2 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
- package/dist/pipeline/stages/ultragoal.js +6 -1
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +58 -4
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/renderer.d.ts +1 -1
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +73 -5
- package/dist/question/renderer.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +202 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.js +24 -3
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +7 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +63 -4
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +6935 -2681
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +4442 -187
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-catalog-docs.js +1 -0
- package/dist/scripts/generate-catalog-docs.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +1424 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/planning-gate.test.js +410 -0
- package/dist/state/__tests__/planning-gate.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +59 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/operations.d.ts +10 -0
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +250 -14
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +4 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +8 -6
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +642 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +248 -1
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +60 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +225 -1
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +72 -4
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +44 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +127 -8
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +89 -0
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +3 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +42 -5
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +21 -11
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +13 -11
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +63 -13
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +21 -4
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +75 -5
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +2 -2
- package/dist/utils/__tests__/agents-model-table.test.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/hooks/codex-native-hook.mjs +19 -7
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +16 -0
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +23 -0
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +3 -2
- package/skills/code-review/SKILL.md +16 -0
- package/skills/ecomode/references/agent-tiers.md +1 -1
- package/skills/ultragoal/SKILL.md +23 -0
- package/skills/ultrawork/references/agent-tiers.md +3 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +14843 -9586
- package/src/scripts/codex-native-hook.ts +4520 -194
- package/src/scripts/generate-catalog-docs.ts +1 -0
|
@@ -6,6 +6,7 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { executeStateOperation } from '../operations.js';
|
|
8
8
|
import { subagentTrackingPath } from '../../subagents/tracker.js';
|
|
9
|
+
import { updateModeState } from '../../modes/base.js';
|
|
9
10
|
async function withAmbientTmuxEnv(env, run) {
|
|
10
11
|
const previousTmux = process.env.TMUX;
|
|
11
12
|
const previousTmuxPane = process.env.TMUX_PANE;
|
|
@@ -65,6 +66,45 @@ async function withOmxRootEnv(root, run) {
|
|
|
65
66
|
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
async function withStateRootEnv(env, run) {
|
|
70
|
+
const previousOmxRoot = process.env.OMX_ROOT;
|
|
71
|
+
const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
72
|
+
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
73
|
+
if (typeof env.OMX_ROOT === 'string')
|
|
74
|
+
process.env.OMX_ROOT = env.OMX_ROOT;
|
|
75
|
+
else
|
|
76
|
+
delete process.env.OMX_ROOT;
|
|
77
|
+
if (typeof env.OMX_STATE_ROOT === 'string')
|
|
78
|
+
process.env.OMX_STATE_ROOT = env.OMX_STATE_ROOT;
|
|
79
|
+
else
|
|
80
|
+
delete process.env.OMX_STATE_ROOT;
|
|
81
|
+
if (typeof env.OMX_TEAM_STATE_ROOT === 'string')
|
|
82
|
+
process.env.OMX_TEAM_STATE_ROOT = env.OMX_TEAM_STATE_ROOT;
|
|
83
|
+
else
|
|
84
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
85
|
+
try {
|
|
86
|
+
return await run();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
if (typeof previousOmxRoot === 'string')
|
|
90
|
+
process.env.OMX_ROOT = previousOmxRoot;
|
|
91
|
+
else
|
|
92
|
+
delete process.env.OMX_ROOT;
|
|
93
|
+
if (typeof previousOmxStateRoot === 'string')
|
|
94
|
+
process.env.OMX_STATE_ROOT = previousOmxStateRoot;
|
|
95
|
+
else
|
|
96
|
+
delete process.env.OMX_STATE_ROOT;
|
|
97
|
+
if (typeof previousTeamStateRoot === 'string')
|
|
98
|
+
process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
|
|
99
|
+
else
|
|
100
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function responsePayload(response) {
|
|
104
|
+
assert.equal(response.isError, undefined);
|
|
105
|
+
assert.ok(response.payload && typeof response.payload === 'object' && !Array.isArray(response.payload));
|
|
106
|
+
return response.payload;
|
|
107
|
+
}
|
|
68
108
|
function validExecutionContract(stride) {
|
|
69
109
|
const perStride = {
|
|
70
110
|
task: {
|
|
@@ -146,6 +186,10 @@ function ralplanConsensusGate(sessionId, provenanceKind, threadOverrides = {}) {
|
|
|
146
186
|
},
|
|
147
187
|
};
|
|
148
188
|
}
|
|
189
|
+
async function writeNativeRalplanConsensusGate(cwd, sessionId, threadOverrides = {}) {
|
|
190
|
+
await writeNativeSubagentTracking(cwd, sessionId);
|
|
191
|
+
return ralplanConsensusGate(sessionId, 'native_subagent', threadOverrides);
|
|
192
|
+
}
|
|
149
193
|
async function createFakeTmuxBin(wd) {
|
|
150
194
|
const fakeBin = join(wd, 'bin');
|
|
151
195
|
await mkdir(fakeBin, { recursive: true });
|
|
@@ -230,6 +274,123 @@ describe('state operations directory initialization', () => {
|
|
|
230
274
|
await rm(wd, { recursive: true, force: true });
|
|
231
275
|
}
|
|
232
276
|
});
|
|
277
|
+
it('writes and clears session state under OMX_TEAM_STATE_ROOT without creating cwd .omx', async () => {
|
|
278
|
+
const root = await mkdtemp(join(tmpdir(), 'omx-state-ops-team-root-'));
|
|
279
|
+
try {
|
|
280
|
+
const wd = join(root, 'workspace');
|
|
281
|
+
const teamStateRoot = join(root, 'team-state');
|
|
282
|
+
await mkdir(wd, { recursive: true });
|
|
283
|
+
await withStateRootEnv({ OMX_TEAM_STATE_ROOT: teamStateRoot }, async () => {
|
|
284
|
+
const writeResponse = await executeStateOperation('state_write', {
|
|
285
|
+
workingDirectory: wd,
|
|
286
|
+
session_id: 'sess-team-write',
|
|
287
|
+
mode: 'autoresearch',
|
|
288
|
+
active: true,
|
|
289
|
+
current_phase: 'running',
|
|
290
|
+
});
|
|
291
|
+
const writePayload = responsePayload(writeResponse);
|
|
292
|
+
assert.equal(writePayload.path, join(teamStateRoot, 'sessions', 'sess-team-write', 'autoresearch-state.json'));
|
|
293
|
+
assert.equal(existsSync(writePayload.path), true);
|
|
294
|
+
assert.equal(existsSync(join(teamStateRoot, 'sessions', 'sess-team-write', 'skill-active-state.json')), true);
|
|
295
|
+
assert.equal(existsSync(join(wd, '.omx')), false);
|
|
296
|
+
const clearResponse = await executeStateOperation('state_clear', {
|
|
297
|
+
workingDirectory: wd,
|
|
298
|
+
session_id: 'sess-team-write',
|
|
299
|
+
mode: 'autoresearch',
|
|
300
|
+
});
|
|
301
|
+
const clearPayload = responsePayload(clearResponse);
|
|
302
|
+
assert.equal(clearPayload.path, writePayload.path);
|
|
303
|
+
assert.equal(existsSync(writePayload.path), false);
|
|
304
|
+
assert.equal(existsSync(join(teamStateRoot, 'sessions', 'sess-team-write', 'skill-active-state.json')), true);
|
|
305
|
+
assert.equal(existsSync(join(wd, '.omx')), false);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
await rm(root, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
it('writes and clears session state under OMX_ROOT when cwd is filesystem root', async () => {
|
|
313
|
+
const boxRoot = await mkdtemp(join(tmpdir(), 'omx-state-ops-omx-root-'));
|
|
314
|
+
try {
|
|
315
|
+
await withStateRootEnv({ OMX_ROOT: boxRoot }, async () => {
|
|
316
|
+
const writeResponse = await executeStateOperation('state_write', {
|
|
317
|
+
workingDirectory: '/',
|
|
318
|
+
session_id: 'sess-omx-root',
|
|
319
|
+
mode: 'autoresearch',
|
|
320
|
+
active: true,
|
|
321
|
+
current_phase: 'running',
|
|
322
|
+
});
|
|
323
|
+
const writePayload = responsePayload(writeResponse);
|
|
324
|
+
const expectedPath = join(boxRoot, '.omx', 'state', 'sessions', 'sess-omx-root', 'autoresearch-state.json');
|
|
325
|
+
assert.equal(writePayload.path, expectedPath);
|
|
326
|
+
assert.equal(existsSync(expectedPath), true);
|
|
327
|
+
const clearResponse = await executeStateOperation('state_clear', {
|
|
328
|
+
workingDirectory: '/',
|
|
329
|
+
session_id: 'sess-omx-root',
|
|
330
|
+
mode: 'autoresearch',
|
|
331
|
+
});
|
|
332
|
+
const clearPayload = responsePayload(clearResponse);
|
|
333
|
+
assert.equal(clearPayload.path, expectedPath);
|
|
334
|
+
assert.equal(existsSync(expectedPath), false);
|
|
335
|
+
const workspace = join(boxRoot, 'workspace');
|
|
336
|
+
await mkdir(workspace, { recursive: true });
|
|
337
|
+
const workspaceResponse = await executeStateOperation('state_write', {
|
|
338
|
+
workingDirectory: workspace,
|
|
339
|
+
session_id: 'sess-omx-root-workspace',
|
|
340
|
+
mode: 'autoresearch',
|
|
341
|
+
active: true,
|
|
342
|
+
current_phase: 'running',
|
|
343
|
+
});
|
|
344
|
+
const workspacePayload = responsePayload(workspaceResponse);
|
|
345
|
+
assert.equal(workspacePayload.path, join(boxRoot, '.omx', 'state', 'sessions', 'sess-omx-root-workspace', 'autoresearch-state.json'));
|
|
346
|
+
assert.equal(existsSync(join(workspace, '.omx')), false);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
await rm(boxRoot, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
it('writes and clears session state under OMX_STATE_ROOT when cwd is filesystem root', async () => {
|
|
354
|
+
const stateRoot = await mkdtemp(join(tmpdir(), 'omx-state-ops-state-root-'));
|
|
355
|
+
try {
|
|
356
|
+
await withStateRootEnv({ OMX_STATE_ROOT: stateRoot }, async () => {
|
|
357
|
+
const writeResponse = await executeStateOperation('state_write', {
|
|
358
|
+
workingDirectory: '/',
|
|
359
|
+
session_id: 'sess-state-root',
|
|
360
|
+
mode: 'autoresearch',
|
|
361
|
+
active: true,
|
|
362
|
+
current_phase: 'running',
|
|
363
|
+
});
|
|
364
|
+
const writePayload = responsePayload(writeResponse);
|
|
365
|
+
const expectedPath = join(stateRoot, '.omx', 'state', 'sessions', 'sess-state-root', 'autoresearch-state.json');
|
|
366
|
+
assert.equal(writePayload.path, expectedPath);
|
|
367
|
+
assert.equal(existsSync(expectedPath), true);
|
|
368
|
+
const clearResponse = await executeStateOperation('state_clear', {
|
|
369
|
+
workingDirectory: '/',
|
|
370
|
+
session_id: 'sess-state-root',
|
|
371
|
+
mode: 'autoresearch',
|
|
372
|
+
});
|
|
373
|
+
const clearPayload = responsePayload(clearResponse);
|
|
374
|
+
assert.equal(clearPayload.path, expectedPath);
|
|
375
|
+
assert.equal(existsSync(expectedPath), false);
|
|
376
|
+
const workspace = join(stateRoot, 'workspace');
|
|
377
|
+
await mkdir(workspace, { recursive: true });
|
|
378
|
+
const workspaceResponse = await executeStateOperation('state_write', {
|
|
379
|
+
workingDirectory: workspace,
|
|
380
|
+
session_id: 'sess-state-root-workspace',
|
|
381
|
+
mode: 'autoresearch',
|
|
382
|
+
active: true,
|
|
383
|
+
current_phase: 'running',
|
|
384
|
+
});
|
|
385
|
+
const workspacePayload = responsePayload(workspaceResponse);
|
|
386
|
+
assert.equal(workspacePayload.path, join(stateRoot, '.omx', 'state', 'sessions', 'sess-state-root-workspace', 'autoresearch-state.json'));
|
|
387
|
+
assert.equal(existsSync(join(workspace, '.omx')), false);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
await rm(stateRoot, { recursive: true, force: true });
|
|
392
|
+
}
|
|
393
|
+
});
|
|
233
394
|
it('surfaces active ultragoal artifacts in list-active without mode state files', async () => {
|
|
234
395
|
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ultragoal-artifact-'));
|
|
235
396
|
try {
|
|
@@ -261,6 +422,57 @@ describe('state operations directory initialization', () => {
|
|
|
261
422
|
await rm(wd, { recursive: true, force: true });
|
|
262
423
|
}
|
|
263
424
|
});
|
|
425
|
+
it('reports reconciled task-scoped aggregate ultragoal artifacts as inactive in get-status', async () => {
|
|
426
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ultragoal-reconciled-'));
|
|
427
|
+
try {
|
|
428
|
+
await mkdir(join(wd, '.omx', 'ultragoal'), { recursive: true });
|
|
429
|
+
await writeFile(join(wd, '.omx', 'ultragoal', 'goals.json'), JSON.stringify({
|
|
430
|
+
aggregateCompletion: {
|
|
431
|
+
status: 'complete',
|
|
432
|
+
completedAt: '2026-06-01T12:00:00.000Z',
|
|
433
|
+
evidence: 'task-scoped Codex aggregate completed and active microgoal row was reconciled',
|
|
434
|
+
},
|
|
435
|
+
activeGoalId: 'G002',
|
|
436
|
+
goals: [{
|
|
437
|
+
id: 'G001',
|
|
438
|
+
title: 'Fix duplicate HUD panes',
|
|
439
|
+
objective: 'Keep one HUD renderer per leader.',
|
|
440
|
+
status: 'complete',
|
|
441
|
+
completedAt: '2026-06-01T12:00:00.000Z',
|
|
442
|
+
}, {
|
|
443
|
+
id: 'G002',
|
|
444
|
+
title: 'Still marked running',
|
|
445
|
+
objective: 'Progress-only row left running by the old aggregate path.',
|
|
446
|
+
status: 'in_progress',
|
|
447
|
+
}, {
|
|
448
|
+
id: 'G003',
|
|
449
|
+
title: 'Still marked pending',
|
|
450
|
+
objective: 'Progress-only row left pending by the old aggregate path.',
|
|
451
|
+
status: 'pending',
|
|
452
|
+
}],
|
|
453
|
+
}, null, 2));
|
|
454
|
+
const activeResponse = await executeStateOperation('state_list_active', {
|
|
455
|
+
workingDirectory: wd,
|
|
456
|
+
});
|
|
457
|
+
assert.deepEqual(activeResponse.payload, { active_modes: [] });
|
|
458
|
+
const statusResponse = await executeStateOperation('state_get_status', {
|
|
459
|
+
workingDirectory: wd,
|
|
460
|
+
mode: 'ultragoal',
|
|
461
|
+
});
|
|
462
|
+
const statuses = statusResponse.payload.statuses || {};
|
|
463
|
+
assert.equal(statuses.ultragoal?.active, false);
|
|
464
|
+
assert.equal(statuses.ultragoal?.phase, 'complete');
|
|
465
|
+
assert.equal(statuses.ultragoal?.path, join(wd, '.omx', 'ultragoal', 'goals.json'));
|
|
466
|
+
assert.equal(statuses.ultragoal?.source, 'ultragoal-artifacts');
|
|
467
|
+
assert.equal(statuses.ultragoal?.data?.activeGoal, undefined);
|
|
468
|
+
assert.equal(statuses.ultragoal?.data?.complete, 1);
|
|
469
|
+
assert.equal(statuses.ultragoal?.data?.inProgress, 1);
|
|
470
|
+
assert.equal(statuses.ultragoal?.data?.pending, 1);
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
await rm(wd, { recursive: true, force: true });
|
|
474
|
+
}
|
|
475
|
+
});
|
|
264
476
|
it('prefers active ultragoal artifacts over stale inactive mode state', async () => {
|
|
265
477
|
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ultragoal-stale-state-'));
|
|
266
478
|
try {
|
|
@@ -682,6 +894,1218 @@ describe('state operations directory initialization', () => {
|
|
|
682
894
|
await rm(wd, { recursive: true, force: true });
|
|
683
895
|
}
|
|
684
896
|
});
|
|
897
|
+
it('finalizes completed ralplan writes across root and current session state', async () => {
|
|
898
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-complete-'));
|
|
899
|
+
try {
|
|
900
|
+
const sessionId = 'sess-ralplan-complete';
|
|
901
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
902
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
903
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
904
|
+
await mkdir(sessionDir, { recursive: true });
|
|
905
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
906
|
+
session_id: sessionId,
|
|
907
|
+
cwd: wd,
|
|
908
|
+
}, null, 2));
|
|
909
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
910
|
+
mode: 'ralplan',
|
|
911
|
+
active: true,
|
|
912
|
+
current_phase: 'planning',
|
|
913
|
+
session_id: sessionId,
|
|
914
|
+
}, null, 2));
|
|
915
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
916
|
+
mode: 'ralplan',
|
|
917
|
+
active: true,
|
|
918
|
+
current_phase: 'planning',
|
|
919
|
+
session_id: sessionId,
|
|
920
|
+
}, null, 2));
|
|
921
|
+
const staleSkillState = {
|
|
922
|
+
version: 1,
|
|
923
|
+
active: true,
|
|
924
|
+
skill: 'ralplan',
|
|
925
|
+
phase: 'planning',
|
|
926
|
+
updated_at: '2026-06-30T00:00:00.000Z',
|
|
927
|
+
session_id: sessionId,
|
|
928
|
+
active_skills: [{
|
|
929
|
+
skill: 'ralplan',
|
|
930
|
+
phase: 'planning',
|
|
931
|
+
active: true,
|
|
932
|
+
session_id: sessionId,
|
|
933
|
+
}],
|
|
934
|
+
};
|
|
935
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify(staleSkillState, null, 2));
|
|
936
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify(staleSkillState, null, 2));
|
|
937
|
+
const response = await executeStateOperation('state_write', {
|
|
938
|
+
workingDirectory: wd,
|
|
939
|
+
mode: 'ralplan',
|
|
940
|
+
active: false,
|
|
941
|
+
current_phase: 'complete',
|
|
942
|
+
terminal_reason: 'consensus approved bounded no-op',
|
|
943
|
+
ralplan_architect_review: {
|
|
944
|
+
verdict: 'APPROVE',
|
|
945
|
+
artifact_path: '.omx/plans/architect.md',
|
|
946
|
+
},
|
|
947
|
+
ralplan_critic_review: {
|
|
948
|
+
verdict: 'APPROVE',
|
|
949
|
+
artifact_path: '.omx/plans/critic.md',
|
|
950
|
+
},
|
|
951
|
+
ralplan_consensus_gate: consensusGate,
|
|
952
|
+
});
|
|
953
|
+
assert.equal(response.isError, undefined);
|
|
954
|
+
const rootRalplan = JSON.parse(await readFile(join(stateDir, 'ralplan-state.json'), 'utf-8'));
|
|
955
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
956
|
+
for (const state of [rootRalplan, sessionRalplan]) {
|
|
957
|
+
assert.equal(state.active, false);
|
|
958
|
+
assert.equal(state.current_phase, 'complete');
|
|
959
|
+
assert.equal(state.status, 'complete');
|
|
960
|
+
assert.equal(state.terminal_reason, 'consensus approved bounded no-op');
|
|
961
|
+
assert.equal(state.ralplan_consensus_gate.complete, true);
|
|
962
|
+
assert.equal(state.session_id, sessionId);
|
|
963
|
+
}
|
|
964
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
965
|
+
const sessionSkill = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
966
|
+
for (const state of [rootSkill, sessionSkill]) {
|
|
967
|
+
assert.equal(state.active, false);
|
|
968
|
+
assert.equal(state.phase, 'complete');
|
|
969
|
+
assert.equal(state.terminal_reason, 'consensus approved bounded no-op');
|
|
970
|
+
assert.deepEqual(state.active_skills, []);
|
|
971
|
+
}
|
|
972
|
+
assert.equal(sessionSkill.session_id, sessionId);
|
|
973
|
+
const listed = await executeStateOperation('state_list_active', {
|
|
974
|
+
workingDirectory: wd,
|
|
975
|
+
});
|
|
976
|
+
assert.deepEqual(listed.payload, { active_modes: [] });
|
|
977
|
+
}
|
|
978
|
+
finally {
|
|
979
|
+
await rm(wd, { recursive: true, force: true });
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
it('finalizes ralplan when runtime tracker lags but workspace tracker has completed native reviews', async () => {
|
|
983
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-runtime-lag-complete-'));
|
|
984
|
+
const stateRoot = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-runtime-lag-root-'));
|
|
985
|
+
try {
|
|
986
|
+
await withStateRootEnv({ OMX_STATE_ROOT: stateRoot }, async () => {
|
|
987
|
+
const sessionId = 'sess-ralplan-runtime-lag-complete';
|
|
988
|
+
const runtimeStateDir = join(stateRoot, '.omx', 'state');
|
|
989
|
+
const sessionDir = join(runtimeStateDir, 'sessions', sessionId);
|
|
990
|
+
const workspaceStateDir = join(wd, '.omx', 'state');
|
|
991
|
+
await mkdir(sessionDir, { recursive: true });
|
|
992
|
+
await mkdir(workspaceStateDir, { recursive: true });
|
|
993
|
+
await writeFile(join(runtimeStateDir, 'session.json'), JSON.stringify({
|
|
994
|
+
session_id: sessionId,
|
|
995
|
+
native_session_id: 'thread-leader',
|
|
996
|
+
cwd: wd,
|
|
997
|
+
}, null, 2));
|
|
998
|
+
const consensusGate = ralplanConsensusGate(sessionId, 'native_subagent');
|
|
999
|
+
consensusGate.ralplan_architect_review.completed_at = '2026-07-07T04:30:00.000Z';
|
|
1000
|
+
consensusGate.ralplan_critic_review.completed_at = '2026-07-07T04:31:00.000Z';
|
|
1001
|
+
await writeFile(subagentTrackingPath(wd), JSON.stringify({
|
|
1002
|
+
schemaVersion: 1,
|
|
1003
|
+
sessions: {
|
|
1004
|
+
[sessionId]: {
|
|
1005
|
+
session_id: sessionId,
|
|
1006
|
+
leader_thread_id: 'thread-leader',
|
|
1007
|
+
updated_at: '2026-07-07T04:31:00.000Z',
|
|
1008
|
+
threads: {
|
|
1009
|
+
'thread-leader': { thread_id: 'thread-leader', kind: 'leader', first_seen_at: '2026-07-07T04:29:00.000Z', last_seen_at: '2026-07-07T04:29:00.000Z', turn_count: 1 },
|
|
1010
|
+
'thread-architect': { thread_id: 'thread-architect', kind: 'subagent', first_seen_at: '2026-07-07T04:30:00.000Z', last_seen_at: '2026-07-07T04:30:00.000Z', turn_count: 1 },
|
|
1011
|
+
'thread-critic': { thread_id: 'thread-critic', kind: 'subagent', first_seen_at: '2026-07-07T04:31:00.000Z', last_seen_at: '2026-07-07T04:31:00.000Z', turn_count: 1 },
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
}, null, 2));
|
|
1016
|
+
await writeFile(join(workspaceStateDir, 'subagent-tracking.json'), JSON.stringify({
|
|
1017
|
+
schemaVersion: 1,
|
|
1018
|
+
sessions: {
|
|
1019
|
+
[sessionId]: {
|
|
1020
|
+
session_id: sessionId,
|
|
1021
|
+
leader_thread_id: 'thread-leader',
|
|
1022
|
+
updated_at: '2026-07-07T04:31:00.000Z',
|
|
1023
|
+
threads: {
|
|
1024
|
+
'thread-leader': { thread_id: 'thread-leader', kind: 'leader', first_seen_at: '2026-07-07T04:29:00.000Z', last_seen_at: '2026-07-07T04:29:00.000Z', turn_count: 1 },
|
|
1025
|
+
'thread-architect': { thread_id: 'thread-architect', kind: 'subagent', first_seen_at: '2026-07-07T04:30:00.000Z', last_seen_at: '2026-07-07T04:30:00.000Z', completed_at: '2026-07-07T04:30:00.000Z', turn_count: 1 },
|
|
1026
|
+
'thread-critic': { thread_id: 'thread-critic', kind: 'subagent', first_seen_at: '2026-07-07T04:31:00.000Z', last_seen_at: '2026-07-07T04:31:00.000Z', completed_at: '2026-07-07T04:31:00.000Z', turn_count: 1 },
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
}, null, 2));
|
|
1031
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1032
|
+
mode: 'ralplan',
|
|
1033
|
+
active: true,
|
|
1034
|
+
current_phase: 'planning',
|
|
1035
|
+
session_id: sessionId,
|
|
1036
|
+
}, null, 2));
|
|
1037
|
+
const response = await executeStateOperation('state_write', {
|
|
1038
|
+
workingDirectory: wd,
|
|
1039
|
+
session_id: sessionId,
|
|
1040
|
+
mode: 'ralplan',
|
|
1041
|
+
active: false,
|
|
1042
|
+
current_phase: 'complete',
|
|
1043
|
+
planning_complete: true,
|
|
1044
|
+
latest_plan_path: '.omx/plans/prd-clickstack-otel-consumer-20260707T043000Z.md',
|
|
1045
|
+
terminal_reason: 'consensus approved despite runtime tracker lag',
|
|
1046
|
+
ralplan_consensus_gate: consensusGate,
|
|
1047
|
+
});
|
|
1048
|
+
assert.equal(response.isError, undefined);
|
|
1049
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1050
|
+
assert.equal(sessionRalplan.active, false);
|
|
1051
|
+
assert.equal(sessionRalplan.current_phase, 'complete');
|
|
1052
|
+
assert.equal(sessionRalplan.status, 'complete');
|
|
1053
|
+
assert.equal(sessionRalplan.terminal_reason, 'consensus approved despite runtime tracker lag');
|
|
1054
|
+
assert.equal(sessionRalplan.ralplan_consensus_gate.complete, true);
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
finally {
|
|
1058
|
+
await rm(wd, { recursive: true, force: true });
|
|
1059
|
+
await rm(stateRoot, { recursive: true, force: true });
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
it('rejects forged ralplan complete gates before mutating active session state', async () => {
|
|
1063
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-forged-complete-'));
|
|
1064
|
+
try {
|
|
1065
|
+
const sessionId = 'sess-ralplan-forged-complete';
|
|
1066
|
+
await writeNativeSubagentTracking(wd, sessionId);
|
|
1067
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1068
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1069
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1070
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1071
|
+
session_id: sessionId,
|
|
1072
|
+
cwd: wd,
|
|
1073
|
+
}, null, 2));
|
|
1074
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1075
|
+
mode: 'ralplan',
|
|
1076
|
+
active: true,
|
|
1077
|
+
current_phase: 'planning',
|
|
1078
|
+
session_id: sessionId,
|
|
1079
|
+
}, null, 2));
|
|
1080
|
+
const response = await executeStateOperation('state_write', {
|
|
1081
|
+
workingDirectory: wd,
|
|
1082
|
+
mode: 'ralplan',
|
|
1083
|
+
active: false,
|
|
1084
|
+
current_phase: 'complete',
|
|
1085
|
+
ralplan_consensus_gate: {
|
|
1086
|
+
complete: true,
|
|
1087
|
+
},
|
|
1088
|
+
});
|
|
1089
|
+
assert.equal(response.isError, true);
|
|
1090
|
+
assert.match(String(response.payload.error ?? ''), /tracker-backed native architect and critic consensus evidence/);
|
|
1091
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1092
|
+
assert.equal(sessionRalplan.active, true);
|
|
1093
|
+
assert.equal(sessionRalplan.current_phase, 'planning');
|
|
1094
|
+
}
|
|
1095
|
+
finally {
|
|
1096
|
+
await rm(wd, { recursive: true, force: true });
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
it('normalizes currentPhase before gating ralplan terminal writes', async () => {
|
|
1100
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-current-phase-alias-'));
|
|
1101
|
+
try {
|
|
1102
|
+
const sessionId = 'sess-ralplan-current-phase-alias';
|
|
1103
|
+
await writeNativeSubagentTracking(wd, sessionId);
|
|
1104
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1105
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1106
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1107
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1108
|
+
session_id: sessionId,
|
|
1109
|
+
cwd: wd,
|
|
1110
|
+
}, null, 2));
|
|
1111
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1112
|
+
mode: 'ralplan',
|
|
1113
|
+
active: true,
|
|
1114
|
+
current_phase: 'planning',
|
|
1115
|
+
session_id: sessionId,
|
|
1116
|
+
}, null, 2));
|
|
1117
|
+
const response = await executeStateOperation('state_write', {
|
|
1118
|
+
workingDirectory: wd,
|
|
1119
|
+
mode: 'ralplan',
|
|
1120
|
+
active: false,
|
|
1121
|
+
currentPhase: 'complete',
|
|
1122
|
+
});
|
|
1123
|
+
assert.equal(response.isError, true);
|
|
1124
|
+
assert.match(String(response.payload.error ?? ''), /tracker-backed native architect and critic consensus evidence/);
|
|
1125
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1126
|
+
assert.equal(sessionRalplan.active, true);
|
|
1127
|
+
assert.equal(sessionRalplan.current_phase, 'planning');
|
|
1128
|
+
assert.equal(sessionRalplan.currentPhase, undefined);
|
|
1129
|
+
}
|
|
1130
|
+
finally {
|
|
1131
|
+
await rm(wd, { recursive: true, force: true });
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
it('reuses existing tracker-backed ralplan consensus when terminal writes omit gate payload', async () => {
|
|
1135
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-existing-consensus-'));
|
|
1136
|
+
try {
|
|
1137
|
+
const sessionId = 'sess-ralplan-existing-consensus';
|
|
1138
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1139
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1140
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1141
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1142
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1143
|
+
session_id: sessionId,
|
|
1144
|
+
cwd: wd,
|
|
1145
|
+
}, null, 2));
|
|
1146
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1147
|
+
mode: 'ralplan',
|
|
1148
|
+
active: true,
|
|
1149
|
+
current_phase: 'planning',
|
|
1150
|
+
session_id: sessionId,
|
|
1151
|
+
ralplan_consensus_gate: consensusGate,
|
|
1152
|
+
}, null, 2));
|
|
1153
|
+
const response = await executeStateOperation('state_write', {
|
|
1154
|
+
workingDirectory: wd,
|
|
1155
|
+
mode: 'ralplan',
|
|
1156
|
+
active: false,
|
|
1157
|
+
current_phase: 'complete',
|
|
1158
|
+
terminal_reason: 'existing tracker-backed consensus complete',
|
|
1159
|
+
});
|
|
1160
|
+
assert.equal(response.isError, undefined);
|
|
1161
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1162
|
+
assert.equal(sessionRalplan.active, false);
|
|
1163
|
+
assert.equal(sessionRalplan.current_phase, 'complete');
|
|
1164
|
+
const finalGate = sessionRalplan.ralplan_consensus_gate;
|
|
1165
|
+
assert.equal(finalGate.complete, true);
|
|
1166
|
+
assert.deepEqual(finalGate.required_review_roles, ['architect', 'critic']);
|
|
1167
|
+
assert.equal(finalGate.ralplan_architect_review.provenance_kind, 'native_subagent');
|
|
1168
|
+
assert.equal(finalGate.ralplan_critic_review.provenance_kind, 'native_subagent');
|
|
1169
|
+
}
|
|
1170
|
+
finally {
|
|
1171
|
+
await rm(wd, { recursive: true, force: true });
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
it('finalizes completed ralplan updateModeState writes across root and current session state', async () => {
|
|
1175
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-complete-update-mode-'));
|
|
1176
|
+
try {
|
|
1177
|
+
const sessionId = 'sess-ralplan-complete-update-mode';
|
|
1178
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1179
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1180
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1181
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1182
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1183
|
+
session_id: sessionId,
|
|
1184
|
+
cwd: wd,
|
|
1185
|
+
}, null, 2));
|
|
1186
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1187
|
+
mode: 'ralplan',
|
|
1188
|
+
active: true,
|
|
1189
|
+
current_phase: 'planning',
|
|
1190
|
+
session_id: sessionId,
|
|
1191
|
+
}, null, 2));
|
|
1192
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1193
|
+
mode: 'ralplan',
|
|
1194
|
+
active: true,
|
|
1195
|
+
current_phase: 'planning',
|
|
1196
|
+
session_id: sessionId,
|
|
1197
|
+
}, null, 2));
|
|
1198
|
+
const staleSkillState = {
|
|
1199
|
+
version: 1,
|
|
1200
|
+
active: true,
|
|
1201
|
+
skill: 'ralplan',
|
|
1202
|
+
phase: 'planning',
|
|
1203
|
+
session_id: sessionId,
|
|
1204
|
+
active_skills: [{
|
|
1205
|
+
skill: 'ralplan',
|
|
1206
|
+
phase: 'planning',
|
|
1207
|
+
active: true,
|
|
1208
|
+
session_id: sessionId,
|
|
1209
|
+
}],
|
|
1210
|
+
};
|
|
1211
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify(staleSkillState, null, 2));
|
|
1212
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify(staleSkillState, null, 2));
|
|
1213
|
+
await updateModeState('ralplan', {
|
|
1214
|
+
active: false,
|
|
1215
|
+
current_phase: 'complete',
|
|
1216
|
+
terminal_reason: 'runtime consensus complete',
|
|
1217
|
+
ralplan_consensus_gate: consensusGate,
|
|
1218
|
+
}, wd);
|
|
1219
|
+
const rootRalplan = JSON.parse(await readFile(join(stateDir, 'ralplan-state.json'), 'utf-8'));
|
|
1220
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1221
|
+
for (const state of [rootRalplan, sessionRalplan]) {
|
|
1222
|
+
assert.equal(state.active, false);
|
|
1223
|
+
assert.equal(state.current_phase, 'complete');
|
|
1224
|
+
assert.equal(state.status, 'complete');
|
|
1225
|
+
assert.equal(state.terminal_reason, 'runtime consensus complete');
|
|
1226
|
+
assert.equal(state.ralplan_consensus_gate.complete, true);
|
|
1227
|
+
assert.equal(state.session_id, sessionId);
|
|
1228
|
+
}
|
|
1229
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
1230
|
+
const sessionSkill = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
1231
|
+
for (const state of [rootSkill, sessionSkill]) {
|
|
1232
|
+
assert.equal(state.active, false);
|
|
1233
|
+
assert.equal(state.phase, 'complete');
|
|
1234
|
+
assert.equal(state.terminal_reason, 'runtime consensus complete');
|
|
1235
|
+
assert.deepEqual(state.active_skills, []);
|
|
1236
|
+
}
|
|
1237
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1238
|
+
workingDirectory: wd,
|
|
1239
|
+
});
|
|
1240
|
+
assert.deepEqual(rootListed.payload, { active_modes: [] });
|
|
1241
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1242
|
+
workingDirectory: wd,
|
|
1243
|
+
session_id: sessionId,
|
|
1244
|
+
});
|
|
1245
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1246
|
+
}
|
|
1247
|
+
finally {
|
|
1248
|
+
await rm(wd, { recursive: true, force: true });
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
it('rejects forged ralplan complete gates from updateModeState before mutating active state', async () => {
|
|
1252
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-update-mode-forged-complete-'));
|
|
1253
|
+
try {
|
|
1254
|
+
const sessionId = 'sess-ralplan-update-mode-forged-complete';
|
|
1255
|
+
await writeNativeSubagentTracking(wd, sessionId);
|
|
1256
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1257
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1258
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1259
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1260
|
+
session_id: sessionId,
|
|
1261
|
+
cwd: wd,
|
|
1262
|
+
}, null, 2));
|
|
1263
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1264
|
+
mode: 'ralplan',
|
|
1265
|
+
active: true,
|
|
1266
|
+
current_phase: 'planning',
|
|
1267
|
+
session_id: sessionId,
|
|
1268
|
+
}, null, 2));
|
|
1269
|
+
await assert.rejects(updateModeState('ralplan', {
|
|
1270
|
+
active: false,
|
|
1271
|
+
current_phase: 'complete',
|
|
1272
|
+
ralplan_consensus_gate: {
|
|
1273
|
+
complete: true,
|
|
1274
|
+
},
|
|
1275
|
+
}, wd), /architect and critic consensus evidence/);
|
|
1276
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1277
|
+
assert.equal(sessionRalplan.active, true);
|
|
1278
|
+
assert.equal(sessionRalplan.current_phase, 'planning');
|
|
1279
|
+
}
|
|
1280
|
+
finally {
|
|
1281
|
+
await rm(wd, { recursive: true, force: true });
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
it('does not promote stale session metadata when ralplan terminalizes from root scope', async () => {
|
|
1285
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-stale-session-'));
|
|
1286
|
+
try {
|
|
1287
|
+
const staleSessionId = 'sess-ralplan-stale';
|
|
1288
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, staleSessionId);
|
|
1289
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1290
|
+
const sessionDir = join(stateDir, 'sessions', staleSessionId);
|
|
1291
|
+
await mkdir(stateDir, { recursive: true });
|
|
1292
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1293
|
+
session_id: staleSessionId,
|
|
1294
|
+
cwd: join(wd, 'different-project'),
|
|
1295
|
+
}, null, 2));
|
|
1296
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1297
|
+
mode: 'ralplan',
|
|
1298
|
+
active: true,
|
|
1299
|
+
current_phase: 'planning',
|
|
1300
|
+
session_id: staleSessionId,
|
|
1301
|
+
}, null, 2));
|
|
1302
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1303
|
+
version: 1,
|
|
1304
|
+
active: true,
|
|
1305
|
+
skill: 'ralplan',
|
|
1306
|
+
phase: 'planning',
|
|
1307
|
+
session_id: staleSessionId,
|
|
1308
|
+
active_skills: [
|
|
1309
|
+
{
|
|
1310
|
+
skill: 'ralplan',
|
|
1311
|
+
phase: 'planning',
|
|
1312
|
+
active: true,
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
skill: 'ralplan',
|
|
1316
|
+
phase: 'planning',
|
|
1317
|
+
active: true,
|
|
1318
|
+
session_id: staleSessionId,
|
|
1319
|
+
},
|
|
1320
|
+
],
|
|
1321
|
+
}, null, 2));
|
|
1322
|
+
const response = await executeStateOperation('state_write', {
|
|
1323
|
+
workingDirectory: wd,
|
|
1324
|
+
mode: 'ralplan',
|
|
1325
|
+
active: false,
|
|
1326
|
+
current_phase: 'complete',
|
|
1327
|
+
status: 'complete',
|
|
1328
|
+
terminal_reason: 'consensus approved bounded no-op',
|
|
1329
|
+
state: {
|
|
1330
|
+
session_id: staleSessionId,
|
|
1331
|
+
ralplan_consensus_gate: consensusGate,
|
|
1332
|
+
},
|
|
1333
|
+
});
|
|
1334
|
+
assert.equal(response.isError, undefined);
|
|
1335
|
+
const rootRalplan = JSON.parse(await readFile(join(stateDir, 'ralplan-state.json'), 'utf-8'));
|
|
1336
|
+
assert.equal(rootRalplan.active, false);
|
|
1337
|
+
assert.equal(rootRalplan.current_phase, 'complete');
|
|
1338
|
+
assert.equal(rootRalplan.status, 'complete');
|
|
1339
|
+
assert.equal(rootRalplan.session_id, undefined);
|
|
1340
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
1341
|
+
assert.equal(rootSkill.active, false);
|
|
1342
|
+
assert.equal(rootSkill.phase, 'complete');
|
|
1343
|
+
assert.deepEqual(rootSkill.active_skills, []);
|
|
1344
|
+
assert.equal(existsSync(join(sessionDir, 'ralplan-state.json')), false);
|
|
1345
|
+
assert.equal(existsSync(join(sessionDir, 'skill-active-state.json')), false);
|
|
1346
|
+
const listed = await executeStateOperation('state_list_active', {
|
|
1347
|
+
workingDirectory: wd,
|
|
1348
|
+
});
|
|
1349
|
+
assert.deepEqual(listed.payload, { active_modes: [] });
|
|
1350
|
+
}
|
|
1351
|
+
finally {
|
|
1352
|
+
await rm(wd, { recursive: true, force: true });
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
it('does not hide unrelated root detail state when ralplan terminalizes without canonical state', async () => {
|
|
1356
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-root-detail-only-'));
|
|
1357
|
+
try {
|
|
1358
|
+
const sessionId = 'sess-ralplan-root-detail-only';
|
|
1359
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1360
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1361
|
+
await mkdir(stateDir, { recursive: true });
|
|
1362
|
+
await writeFile(join(stateDir, 'team-state.json'), JSON.stringify({
|
|
1363
|
+
mode: 'team',
|
|
1364
|
+
active: true,
|
|
1365
|
+
current_phase: 'running',
|
|
1366
|
+
}, null, 2));
|
|
1367
|
+
const response = await executeStateOperation('state_write', {
|
|
1368
|
+
workingDirectory: wd,
|
|
1369
|
+
mode: 'ralplan',
|
|
1370
|
+
active: false,
|
|
1371
|
+
current_phase: 'complete',
|
|
1372
|
+
state: {
|
|
1373
|
+
session_id: sessionId,
|
|
1374
|
+
ralplan_consensus_gate: consensusGate,
|
|
1375
|
+
},
|
|
1376
|
+
});
|
|
1377
|
+
assert.equal(response.isError, undefined);
|
|
1378
|
+
assert.equal(existsSync(join(stateDir, 'skill-active-state.json')), false);
|
|
1379
|
+
const listed = await executeStateOperation('state_list_active', {
|
|
1380
|
+
workingDirectory: wd,
|
|
1381
|
+
});
|
|
1382
|
+
assert.deepEqual(listed.payload, { active_modes: ['team'] });
|
|
1383
|
+
}
|
|
1384
|
+
finally {
|
|
1385
|
+
await rm(wd, { recursive: true, force: true });
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
it('preserves an unrelated active root ralplan when a session ralplan terminalizes', async () => {
|
|
1389
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-preserve-root-'));
|
|
1390
|
+
try {
|
|
1391
|
+
const sessionId = 'sess-ralplan-preserve-root';
|
|
1392
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1393
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1394
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1395
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1396
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1397
|
+
mode: 'ralplan',
|
|
1398
|
+
active: true,
|
|
1399
|
+
current_phase: 'planning',
|
|
1400
|
+
}, null, 2));
|
|
1401
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1402
|
+
mode: 'ralplan',
|
|
1403
|
+
active: true,
|
|
1404
|
+
current_phase: 'planning',
|
|
1405
|
+
session_id: sessionId,
|
|
1406
|
+
}, null, 2));
|
|
1407
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1408
|
+
version: 1,
|
|
1409
|
+
active: true,
|
|
1410
|
+
skill: 'ralplan',
|
|
1411
|
+
phase: 'planning',
|
|
1412
|
+
active_skills: [
|
|
1413
|
+
{
|
|
1414
|
+
skill: 'ralplan',
|
|
1415
|
+
phase: 'planning',
|
|
1416
|
+
active: true,
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
skill: 'ralplan',
|
|
1420
|
+
phase: 'planning',
|
|
1421
|
+
active: true,
|
|
1422
|
+
session_id: sessionId,
|
|
1423
|
+
},
|
|
1424
|
+
],
|
|
1425
|
+
}, null, 2));
|
|
1426
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
|
|
1427
|
+
version: 1,
|
|
1428
|
+
active: true,
|
|
1429
|
+
skill: 'ralplan',
|
|
1430
|
+
phase: 'planning',
|
|
1431
|
+
session_id: sessionId,
|
|
1432
|
+
active_skills: [{
|
|
1433
|
+
skill: 'ralplan',
|
|
1434
|
+
phase: 'planning',
|
|
1435
|
+
active: true,
|
|
1436
|
+
session_id: sessionId,
|
|
1437
|
+
}],
|
|
1438
|
+
}, null, 2));
|
|
1439
|
+
const response = await executeStateOperation('state_write', {
|
|
1440
|
+
workingDirectory: wd,
|
|
1441
|
+
session_id: sessionId,
|
|
1442
|
+
mode: 'ralplan',
|
|
1443
|
+
active: false,
|
|
1444
|
+
current_phase: 'complete',
|
|
1445
|
+
status: 'complete',
|
|
1446
|
+
terminal_reason: 'consensus approved bounded no-op',
|
|
1447
|
+
ralplan_consensus_gate: consensusGate,
|
|
1448
|
+
});
|
|
1449
|
+
assert.equal(response.isError, undefined);
|
|
1450
|
+
const rootRalplan = JSON.parse(await readFile(join(stateDir, 'ralplan-state.json'), 'utf-8'));
|
|
1451
|
+
assert.equal(rootRalplan.active, true);
|
|
1452
|
+
assert.equal(rootRalplan.current_phase, 'planning');
|
|
1453
|
+
assert.equal(rootRalplan.session_id, undefined);
|
|
1454
|
+
const sessionRalplan = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
|
|
1455
|
+
assert.equal(sessionRalplan.active, false);
|
|
1456
|
+
assert.equal(sessionRalplan.current_phase, 'complete');
|
|
1457
|
+
assert.equal(sessionRalplan.session_id, sessionId);
|
|
1458
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
1459
|
+
assert.equal(rootSkill.active, true);
|
|
1460
|
+
assert.equal(rootSkill.skill, 'ralplan');
|
|
1461
|
+
assert.deepEqual(rootSkill.active_skills, [{ skill: 'ralplan', phase: 'planning', active: true }]);
|
|
1462
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1463
|
+
workingDirectory: wd,
|
|
1464
|
+
});
|
|
1465
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['ralplan'] });
|
|
1466
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1467
|
+
workingDirectory: wd,
|
|
1468
|
+
session_id: sessionId,
|
|
1469
|
+
});
|
|
1470
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1471
|
+
}
|
|
1472
|
+
finally {
|
|
1473
|
+
await rm(wd, { recursive: true, force: true });
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
it('does not hide a legacy active root ralplan detail state when a session ralplan terminalizes', async () => {
|
|
1477
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-legacy-root-detail-'));
|
|
1478
|
+
try {
|
|
1479
|
+
const sessionId = 'sess-ralplan-legacy-root-detail';
|
|
1480
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1481
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1482
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1483
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1484
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1485
|
+
mode: 'ralplan',
|
|
1486
|
+
active: true,
|
|
1487
|
+
current_phase: 'planning',
|
|
1488
|
+
}, null, 2));
|
|
1489
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1490
|
+
mode: 'ralplan',
|
|
1491
|
+
active: true,
|
|
1492
|
+
current_phase: 'planning',
|
|
1493
|
+
session_id: sessionId,
|
|
1494
|
+
}, null, 2));
|
|
1495
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
|
|
1496
|
+
version: 1,
|
|
1497
|
+
active: true,
|
|
1498
|
+
skill: 'ralplan',
|
|
1499
|
+
phase: 'planning',
|
|
1500
|
+
session_id: sessionId,
|
|
1501
|
+
active_skills: [{
|
|
1502
|
+
skill: 'ralplan',
|
|
1503
|
+
phase: 'planning',
|
|
1504
|
+
active: true,
|
|
1505
|
+
session_id: sessionId,
|
|
1506
|
+
}],
|
|
1507
|
+
}, null, 2));
|
|
1508
|
+
const response = await executeStateOperation('state_write', {
|
|
1509
|
+
workingDirectory: wd,
|
|
1510
|
+
session_id: sessionId,
|
|
1511
|
+
mode: 'ralplan',
|
|
1512
|
+
active: false,
|
|
1513
|
+
current_phase: 'complete',
|
|
1514
|
+
ralplan_consensus_gate: consensusGate,
|
|
1515
|
+
});
|
|
1516
|
+
assert.equal(response.isError, undefined);
|
|
1517
|
+
assert.equal(existsSync(join(stateDir, 'skill-active-state.json')), false);
|
|
1518
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1519
|
+
workingDirectory: wd,
|
|
1520
|
+
});
|
|
1521
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['ralplan'] });
|
|
1522
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1523
|
+
workingDirectory: wd,
|
|
1524
|
+
session_id: sessionId,
|
|
1525
|
+
});
|
|
1526
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1527
|
+
}
|
|
1528
|
+
finally {
|
|
1529
|
+
await rm(wd, { recursive: true, force: true });
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
it('removes an empty session-only root mirror when a session ralplan terminalizes', async () => {
|
|
1533
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-empty-root-mirror-'));
|
|
1534
|
+
try {
|
|
1535
|
+
const sessionId = 'sess-ralplan-empty-root-mirror';
|
|
1536
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1537
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1538
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1539
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1540
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1541
|
+
mode: 'ralplan',
|
|
1542
|
+
active: true,
|
|
1543
|
+
current_phase: 'planning',
|
|
1544
|
+
}, null, 2));
|
|
1545
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1546
|
+
mode: 'ralplan',
|
|
1547
|
+
active: true,
|
|
1548
|
+
current_phase: 'planning',
|
|
1549
|
+
session_id: sessionId,
|
|
1550
|
+
}, null, 2));
|
|
1551
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1552
|
+
version: 1,
|
|
1553
|
+
active: true,
|
|
1554
|
+
skill: 'ralplan',
|
|
1555
|
+
phase: 'planning',
|
|
1556
|
+
session_id: sessionId,
|
|
1557
|
+
active_skills: [{
|
|
1558
|
+
skill: 'ralplan',
|
|
1559
|
+
phase: 'planning',
|
|
1560
|
+
active: true,
|
|
1561
|
+
session_id: sessionId,
|
|
1562
|
+
}],
|
|
1563
|
+
}, null, 2));
|
|
1564
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
|
|
1565
|
+
version: 1,
|
|
1566
|
+
active: true,
|
|
1567
|
+
skill: 'ralplan',
|
|
1568
|
+
phase: 'planning',
|
|
1569
|
+
session_id: sessionId,
|
|
1570
|
+
active_skills: [{
|
|
1571
|
+
skill: 'ralplan',
|
|
1572
|
+
phase: 'planning',
|
|
1573
|
+
active: true,
|
|
1574
|
+
session_id: sessionId,
|
|
1575
|
+
}],
|
|
1576
|
+
}, null, 2));
|
|
1577
|
+
const response = await executeStateOperation('state_write', {
|
|
1578
|
+
workingDirectory: wd,
|
|
1579
|
+
session_id: sessionId,
|
|
1580
|
+
mode: 'ralplan',
|
|
1581
|
+
active: false,
|
|
1582
|
+
current_phase: 'complete',
|
|
1583
|
+
ralplan_consensus_gate: consensusGate,
|
|
1584
|
+
});
|
|
1585
|
+
assert.equal(response.isError, undefined);
|
|
1586
|
+
assert.equal(existsSync(join(stateDir, 'skill-active-state.json')), false);
|
|
1587
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1588
|
+
workingDirectory: wd,
|
|
1589
|
+
});
|
|
1590
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['ralplan'] });
|
|
1591
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1592
|
+
workingDirectory: wd,
|
|
1593
|
+
session_id: sessionId,
|
|
1594
|
+
});
|
|
1595
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1596
|
+
}
|
|
1597
|
+
finally {
|
|
1598
|
+
await rm(wd, { recursive: true, force: true });
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
it('preserves run_outcome-only root canonical tombstones when a session ralplan terminalizes', async () => {
|
|
1602
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-preserve-root-tombstone-'));
|
|
1603
|
+
try {
|
|
1604
|
+
const sessionId = 'sess-ralplan-root-tombstone';
|
|
1605
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1606
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1607
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1608
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1609
|
+
await writeFile(join(stateDir, 'team-state.json'), JSON.stringify({
|
|
1610
|
+
mode: 'team',
|
|
1611
|
+
active: true,
|
|
1612
|
+
current_phase: 'running',
|
|
1613
|
+
}, null, 2));
|
|
1614
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1615
|
+
mode: 'ralplan',
|
|
1616
|
+
active: true,
|
|
1617
|
+
current_phase: 'planning',
|
|
1618
|
+
}, null, 2));
|
|
1619
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1620
|
+
mode: 'ralplan',
|
|
1621
|
+
active: true,
|
|
1622
|
+
current_phase: 'planning',
|
|
1623
|
+
session_id: sessionId,
|
|
1624
|
+
}, null, 2));
|
|
1625
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1626
|
+
version: 1,
|
|
1627
|
+
active: true,
|
|
1628
|
+
skill: 'team',
|
|
1629
|
+
run_outcome: 'finish',
|
|
1630
|
+
active_skills: [{
|
|
1631
|
+
skill: 'team',
|
|
1632
|
+
active: true,
|
|
1633
|
+
}],
|
|
1634
|
+
}, null, 2));
|
|
1635
|
+
const before = await executeStateOperation('state_list_active', {
|
|
1636
|
+
workingDirectory: wd,
|
|
1637
|
+
});
|
|
1638
|
+
assert.deepEqual(before.payload, { active_modes: [] });
|
|
1639
|
+
const response = await executeStateOperation('state_write', {
|
|
1640
|
+
workingDirectory: wd,
|
|
1641
|
+
session_id: sessionId,
|
|
1642
|
+
mode: 'ralplan',
|
|
1643
|
+
active: false,
|
|
1644
|
+
current_phase: 'complete',
|
|
1645
|
+
ralplan_consensus_gate: consensusGate,
|
|
1646
|
+
});
|
|
1647
|
+
assert.equal(response.isError, undefined);
|
|
1648
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
1649
|
+
assert.equal(rootSkill.active, true);
|
|
1650
|
+
assert.equal(rootSkill.run_outcome, 'finish');
|
|
1651
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1652
|
+
workingDirectory: wd,
|
|
1653
|
+
});
|
|
1654
|
+
assert.deepEqual(rootListed.payload, { active_modes: [] });
|
|
1655
|
+
}
|
|
1656
|
+
finally {
|
|
1657
|
+
await rm(wd, { recursive: true, force: true });
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
it('removes terminal_reason-only active root canonical state when a session ralplan terminalizes', async () => {
|
|
1661
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-terminal-reason-only-'));
|
|
1662
|
+
try {
|
|
1663
|
+
const sessionId = 'sess-ralplan-terminal-reason-only';
|
|
1664
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1665
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1666
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1667
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1668
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1669
|
+
mode: 'ralplan',
|
|
1670
|
+
active: true,
|
|
1671
|
+
current_phase: 'planning',
|
|
1672
|
+
}, null, 2));
|
|
1673
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1674
|
+
mode: 'ralplan',
|
|
1675
|
+
active: true,
|
|
1676
|
+
current_phase: 'planning',
|
|
1677
|
+
session_id: sessionId,
|
|
1678
|
+
}, null, 2));
|
|
1679
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1680
|
+
version: 1,
|
|
1681
|
+
active: true,
|
|
1682
|
+
skill: 'ralplan',
|
|
1683
|
+
terminal_reason: 'stale reason without terminal marker',
|
|
1684
|
+
active_skills: [{
|
|
1685
|
+
skill: 'ralplan',
|
|
1686
|
+
active: true,
|
|
1687
|
+
session_id: sessionId,
|
|
1688
|
+
}],
|
|
1689
|
+
}, null, 2));
|
|
1690
|
+
const before = await executeStateOperation('state_list_active', {
|
|
1691
|
+
workingDirectory: wd,
|
|
1692
|
+
session_id: sessionId,
|
|
1693
|
+
});
|
|
1694
|
+
assert.deepEqual(before.payload, { active_modes: ['ralplan'] });
|
|
1695
|
+
const response = await executeStateOperation('state_write', {
|
|
1696
|
+
workingDirectory: wd,
|
|
1697
|
+
session_id: sessionId,
|
|
1698
|
+
mode: 'ralplan',
|
|
1699
|
+
active: false,
|
|
1700
|
+
current_phase: 'complete',
|
|
1701
|
+
ralplan_consensus_gate: consensusGate,
|
|
1702
|
+
});
|
|
1703
|
+
assert.equal(response.isError, undefined);
|
|
1704
|
+
assert.equal(existsSync(join(stateDir, 'skill-active-state.json')), false);
|
|
1705
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1706
|
+
workingDirectory: wd,
|
|
1707
|
+
});
|
|
1708
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['ralplan'] });
|
|
1709
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1710
|
+
workingDirectory: wd,
|
|
1711
|
+
session_id: sessionId,
|
|
1712
|
+
});
|
|
1713
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1714
|
+
}
|
|
1715
|
+
finally {
|
|
1716
|
+
await rm(wd, { recursive: true, force: true });
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
it('removes non-terminal lifecycle_outcome root canonical state when a session ralplan terminalizes', async () => {
|
|
1720
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-lifecycle-nonterminal-'));
|
|
1721
|
+
try {
|
|
1722
|
+
const sessionId = 'sess-ralplan-lifecycle-nonterminal';
|
|
1723
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1724
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1725
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1726
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1727
|
+
await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
|
|
1728
|
+
mode: 'ralplan',
|
|
1729
|
+
active: true,
|
|
1730
|
+
current_phase: 'planning',
|
|
1731
|
+
}, null, 2));
|
|
1732
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1733
|
+
mode: 'ralplan',
|
|
1734
|
+
active: true,
|
|
1735
|
+
current_phase: 'planning',
|
|
1736
|
+
session_id: sessionId,
|
|
1737
|
+
}, null, 2));
|
|
1738
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1739
|
+
version: 1,
|
|
1740
|
+
active: true,
|
|
1741
|
+
skill: 'ralplan',
|
|
1742
|
+
lifecycle_outcome: 'progress',
|
|
1743
|
+
active_skills: [{
|
|
1744
|
+
skill: 'ralplan',
|
|
1745
|
+
active: true,
|
|
1746
|
+
session_id: sessionId,
|
|
1747
|
+
}],
|
|
1748
|
+
}, null, 2));
|
|
1749
|
+
const before = await executeStateOperation('state_list_active', {
|
|
1750
|
+
workingDirectory: wd,
|
|
1751
|
+
session_id: sessionId,
|
|
1752
|
+
});
|
|
1753
|
+
assert.deepEqual(before.payload, { active_modes: ['ralplan'] });
|
|
1754
|
+
const response = await executeStateOperation('state_write', {
|
|
1755
|
+
workingDirectory: wd,
|
|
1756
|
+
session_id: sessionId,
|
|
1757
|
+
mode: 'ralplan',
|
|
1758
|
+
active: false,
|
|
1759
|
+
current_phase: 'complete',
|
|
1760
|
+
ralplan_consensus_gate: consensusGate,
|
|
1761
|
+
});
|
|
1762
|
+
assert.equal(response.isError, undefined);
|
|
1763
|
+
assert.equal(existsSync(join(stateDir, 'skill-active-state.json')), false);
|
|
1764
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
1765
|
+
workingDirectory: wd,
|
|
1766
|
+
});
|
|
1767
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['ralplan'] });
|
|
1768
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1769
|
+
workingDirectory: wd,
|
|
1770
|
+
session_id: sessionId,
|
|
1771
|
+
});
|
|
1772
|
+
assert.deepEqual(sessionListed.payload, { active_modes: [] });
|
|
1773
|
+
}
|
|
1774
|
+
finally {
|
|
1775
|
+
await rm(wd, { recursive: true, force: true });
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
it('preserves unrelated active session skills when ralplan terminalizes', async () => {
|
|
1779
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-preserve-'));
|
|
1780
|
+
try {
|
|
1781
|
+
const sessionId = 'sess-ralplan-preserve';
|
|
1782
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1783
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1784
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1785
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1786
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({
|
|
1787
|
+
session_id: sessionId,
|
|
1788
|
+
cwd: wd,
|
|
1789
|
+
}, null, 2));
|
|
1790
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1791
|
+
mode: 'ralplan',
|
|
1792
|
+
active: true,
|
|
1793
|
+
current_phase: 'planning',
|
|
1794
|
+
session_id: sessionId,
|
|
1795
|
+
}, null, 2));
|
|
1796
|
+
await writeFile(join(sessionDir, 'autoresearch-state.json'), JSON.stringify({
|
|
1797
|
+
mode: 'autoresearch',
|
|
1798
|
+
active: true,
|
|
1799
|
+
current_phase: 'running',
|
|
1800
|
+
session_id: sessionId,
|
|
1801
|
+
}, null, 2));
|
|
1802
|
+
const mixedSkillState = {
|
|
1803
|
+
version: 1,
|
|
1804
|
+
active: true,
|
|
1805
|
+
skill: 'ralplan',
|
|
1806
|
+
phase: 'planning',
|
|
1807
|
+
updated_at: '2026-06-30T00:00:00.000Z',
|
|
1808
|
+
session_id: sessionId,
|
|
1809
|
+
active_skills: [
|
|
1810
|
+
{
|
|
1811
|
+
skill: 'ralplan',
|
|
1812
|
+
phase: 'planning',
|
|
1813
|
+
active: true,
|
|
1814
|
+
session_id: sessionId,
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
skill: 'autoresearch',
|
|
1818
|
+
phase: 'running',
|
|
1819
|
+
active: true,
|
|
1820
|
+
session_id: sessionId,
|
|
1821
|
+
},
|
|
1822
|
+
],
|
|
1823
|
+
};
|
|
1824
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify(mixedSkillState, null, 2));
|
|
1825
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify(mixedSkillState, null, 2));
|
|
1826
|
+
const response = await executeStateOperation('state_write', {
|
|
1827
|
+
workingDirectory: wd,
|
|
1828
|
+
mode: 'ralplan',
|
|
1829
|
+
active: false,
|
|
1830
|
+
current_phase: 'complete',
|
|
1831
|
+
status: 'complete',
|
|
1832
|
+
terminal_reason: 'consensus approved bounded no-op',
|
|
1833
|
+
ralplan_consensus_gate: consensusGate,
|
|
1834
|
+
});
|
|
1835
|
+
assert.equal(response.isError, undefined);
|
|
1836
|
+
const rootSkill = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf-8'));
|
|
1837
|
+
const sessionSkill = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
1838
|
+
for (const state of [rootSkill, sessionSkill]) {
|
|
1839
|
+
assert.equal(state.active, true);
|
|
1840
|
+
assert.equal(state.skill, 'autoresearch');
|
|
1841
|
+
assert.equal(state.phase, 'running');
|
|
1842
|
+
assert.deepEqual(state.active_skills?.map((entry) => entry.skill), ['autoresearch']);
|
|
1843
|
+
assert.deepEqual(state.active_skills?.map((entry) => entry.session_id), [sessionId]);
|
|
1844
|
+
}
|
|
1845
|
+
const listed = await executeStateOperation('state_list_active', {
|
|
1846
|
+
workingDirectory: wd,
|
|
1847
|
+
});
|
|
1848
|
+
assert.deepEqual(listed.payload, { active_modes: ['autoresearch'] });
|
|
1849
|
+
}
|
|
1850
|
+
finally {
|
|
1851
|
+
await rm(wd, { recursive: true, force: true });
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
it('preserves same-session root mirror skills when session canonical state is partial', async () => {
|
|
1855
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-partial-session-skill-'));
|
|
1856
|
+
try {
|
|
1857
|
+
const sessionId = 'sess-ralplan-partial-session-skill';
|
|
1858
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1859
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1860
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1861
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1862
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1863
|
+
mode: 'ralplan',
|
|
1864
|
+
active: true,
|
|
1865
|
+
current_phase: 'planning',
|
|
1866
|
+
session_id: sessionId,
|
|
1867
|
+
}, null, 2));
|
|
1868
|
+
await writeFile(join(sessionDir, 'team-state.json'), JSON.stringify({
|
|
1869
|
+
mode: 'team',
|
|
1870
|
+
active: true,
|
|
1871
|
+
current_phase: 'running',
|
|
1872
|
+
session_id: sessionId,
|
|
1873
|
+
}, null, 2));
|
|
1874
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1875
|
+
version: 1,
|
|
1876
|
+
active: true,
|
|
1877
|
+
skill: 'ralplan',
|
|
1878
|
+
phase: 'planning',
|
|
1879
|
+
session_id: sessionId,
|
|
1880
|
+
active_skills: [
|
|
1881
|
+
{
|
|
1882
|
+
skill: 'ralplan',
|
|
1883
|
+
phase: 'planning',
|
|
1884
|
+
active: true,
|
|
1885
|
+
session_id: sessionId,
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
skill: 'team',
|
|
1889
|
+
phase: 'running',
|
|
1890
|
+
active: true,
|
|
1891
|
+
session_id: sessionId,
|
|
1892
|
+
},
|
|
1893
|
+
],
|
|
1894
|
+
}, null, 2));
|
|
1895
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
|
|
1896
|
+
version: 1,
|
|
1897
|
+
active: true,
|
|
1898
|
+
skill: 'ralplan',
|
|
1899
|
+
phase: 'planning',
|
|
1900
|
+
session_id: sessionId,
|
|
1901
|
+
active_skills: [{
|
|
1902
|
+
skill: 'ralplan',
|
|
1903
|
+
phase: 'planning',
|
|
1904
|
+
active: true,
|
|
1905
|
+
session_id: sessionId,
|
|
1906
|
+
}],
|
|
1907
|
+
}, null, 2));
|
|
1908
|
+
const response = await executeStateOperation('state_write', {
|
|
1909
|
+
workingDirectory: wd,
|
|
1910
|
+
session_id: sessionId,
|
|
1911
|
+
mode: 'ralplan',
|
|
1912
|
+
active: false,
|
|
1913
|
+
current_phase: 'complete',
|
|
1914
|
+
ralplan_consensus_gate: consensusGate,
|
|
1915
|
+
});
|
|
1916
|
+
assert.equal(response.isError, undefined);
|
|
1917
|
+
const sessionSkill = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
1918
|
+
assert.equal(sessionSkill.active, true);
|
|
1919
|
+
assert.equal(sessionSkill.skill, 'team');
|
|
1920
|
+
assert.equal(sessionSkill.phase, 'running');
|
|
1921
|
+
assert.equal(sessionSkill.session_id, sessionId);
|
|
1922
|
+
assert.deepEqual(sessionSkill.active_skills?.map((entry) => entry.skill), ['team']);
|
|
1923
|
+
assert.deepEqual(sessionSkill.active_skills?.map((entry) => entry.session_id), [sessionId]);
|
|
1924
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
1925
|
+
workingDirectory: wd,
|
|
1926
|
+
session_id: sessionId,
|
|
1927
|
+
});
|
|
1928
|
+
assert.deepEqual(sessionListed.payload, { active_modes: ['team'] });
|
|
1929
|
+
}
|
|
1930
|
+
finally {
|
|
1931
|
+
await rm(wd, { recursive: true, force: true });
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
it('clears stale terminal phase aliases when preserving same-session root mirror skills', async () => {
|
|
1935
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-terminal-session-skill-'));
|
|
1936
|
+
try {
|
|
1937
|
+
const sessionId = 'sess-ralplan-terminal-session-skill';
|
|
1938
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
1939
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1940
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
1941
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1942
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
1943
|
+
mode: 'ralplan',
|
|
1944
|
+
active: true,
|
|
1945
|
+
current_phase: 'planning',
|
|
1946
|
+
session_id: sessionId,
|
|
1947
|
+
}, null, 2));
|
|
1948
|
+
await writeFile(join(sessionDir, 'team-state.json'), JSON.stringify({
|
|
1949
|
+
mode: 'team',
|
|
1950
|
+
active: true,
|
|
1951
|
+
current_phase: 'running',
|
|
1952
|
+
session_id: sessionId,
|
|
1953
|
+
}, null, 2));
|
|
1954
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
1955
|
+
version: 1,
|
|
1956
|
+
active: true,
|
|
1957
|
+
skill: 'ralplan',
|
|
1958
|
+
phase: 'planning',
|
|
1959
|
+
session_id: sessionId,
|
|
1960
|
+
active_skills: [
|
|
1961
|
+
{
|
|
1962
|
+
skill: 'ralplan',
|
|
1963
|
+
phase: 'planning',
|
|
1964
|
+
active: true,
|
|
1965
|
+
session_id: sessionId,
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
skill: 'team',
|
|
1969
|
+
active: true,
|
|
1970
|
+
session_id: sessionId,
|
|
1971
|
+
},
|
|
1972
|
+
],
|
|
1973
|
+
}, null, 2));
|
|
1974
|
+
await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
|
|
1975
|
+
version: 1,
|
|
1976
|
+
active: false,
|
|
1977
|
+
skill: 'ralplan',
|
|
1978
|
+
phase: 'blocked',
|
|
1979
|
+
session_id: sessionId,
|
|
1980
|
+
completed_at: '2026-06-30T00:00:00.000Z',
|
|
1981
|
+
terminal_reason: 'stale terminal marker',
|
|
1982
|
+
active_skills: [],
|
|
1983
|
+
}, null, 2));
|
|
1984
|
+
const response = await executeStateOperation('state_write', {
|
|
1985
|
+
workingDirectory: wd,
|
|
1986
|
+
session_id: sessionId,
|
|
1987
|
+
mode: 'ralplan',
|
|
1988
|
+
active: false,
|
|
1989
|
+
current_phase: 'complete',
|
|
1990
|
+
ralplan_consensus_gate: consensusGate,
|
|
1991
|
+
});
|
|
1992
|
+
assert.equal(response.isError, undefined);
|
|
1993
|
+
const sessionSkill = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
|
|
1994
|
+
assert.equal(sessionSkill.active, true);
|
|
1995
|
+
assert.equal(sessionSkill.skill, 'team');
|
|
1996
|
+
assert.equal(sessionSkill.phase, '');
|
|
1997
|
+
assert.equal(sessionSkill.completed_at, undefined);
|
|
1998
|
+
assert.equal(sessionSkill.terminal_reason, undefined);
|
|
1999
|
+
assert.deepEqual(sessionSkill.active_skills?.map((entry) => entry.skill), ['team']);
|
|
2000
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
2001
|
+
workingDirectory: wd,
|
|
2002
|
+
session_id: sessionId,
|
|
2003
|
+
});
|
|
2004
|
+
assert.deepEqual(sessionListed.payload, { active_modes: ['team'] });
|
|
2005
|
+
}
|
|
2006
|
+
finally {
|
|
2007
|
+
await rm(wd, { recursive: true, force: true });
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
it('does not hide detail-only active session state when ralplan terminalizes', async () => {
|
|
2011
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-session-detail-only-'));
|
|
2012
|
+
try {
|
|
2013
|
+
const sessionId = 'sess-ralplan-session-detail-only';
|
|
2014
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
2015
|
+
const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
|
|
2016
|
+
await mkdir(sessionDir, { recursive: true });
|
|
2017
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
2018
|
+
mode: 'ralplan',
|
|
2019
|
+
active: true,
|
|
2020
|
+
current_phase: 'planning',
|
|
2021
|
+
session_id: sessionId,
|
|
2022
|
+
}, null, 2));
|
|
2023
|
+
await writeFile(join(sessionDir, 'autoresearch-state.json'), JSON.stringify({
|
|
2024
|
+
mode: 'autoresearch',
|
|
2025
|
+
active: true,
|
|
2026
|
+
current_phase: 'running',
|
|
2027
|
+
session_id: sessionId,
|
|
2028
|
+
}, null, 2));
|
|
2029
|
+
const response = await executeStateOperation('state_write', {
|
|
2030
|
+
workingDirectory: wd,
|
|
2031
|
+
session_id: sessionId,
|
|
2032
|
+
mode: 'ralplan',
|
|
2033
|
+
active: false,
|
|
2034
|
+
current_phase: 'complete',
|
|
2035
|
+
ralplan_consensus_gate: consensusGate,
|
|
2036
|
+
});
|
|
2037
|
+
assert.equal(response.isError, undefined);
|
|
2038
|
+
assert.equal(existsSync(join(sessionDir, 'skill-active-state.json')), false);
|
|
2039
|
+
const listed = await executeStateOperation('state_list_active', {
|
|
2040
|
+
workingDirectory: wd,
|
|
2041
|
+
session_id: sessionId,
|
|
2042
|
+
});
|
|
2043
|
+
assert.deepEqual(listed.payload, { active_modes: ['autoresearch'] });
|
|
2044
|
+
}
|
|
2045
|
+
finally {
|
|
2046
|
+
await rm(wd, { recursive: true, force: true });
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
it('does not seed session canonical state from root-only skills when ralplan terminalizes', async () => {
|
|
2050
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-root-only-skill-'));
|
|
2051
|
+
try {
|
|
2052
|
+
const sessionId = 'sess-ralplan-root-only-skill';
|
|
2053
|
+
const consensusGate = await writeNativeRalplanConsensusGate(wd, sessionId);
|
|
2054
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2055
|
+
const sessionDir = join(stateDir, 'sessions', sessionId);
|
|
2056
|
+
await mkdir(sessionDir, { recursive: true });
|
|
2057
|
+
await writeFile(join(stateDir, 'autoresearch-state.json'), JSON.stringify({
|
|
2058
|
+
mode: 'autoresearch',
|
|
2059
|
+
active: true,
|
|
2060
|
+
current_phase: 'running',
|
|
2061
|
+
}, null, 2));
|
|
2062
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), JSON.stringify({
|
|
2063
|
+
version: 1,
|
|
2064
|
+
active: true,
|
|
2065
|
+
skill: 'autoresearch',
|
|
2066
|
+
phase: 'running',
|
|
2067
|
+
active_skills: [{
|
|
2068
|
+
skill: 'autoresearch',
|
|
2069
|
+
phase: 'running',
|
|
2070
|
+
active: true,
|
|
2071
|
+
}],
|
|
2072
|
+
}, null, 2));
|
|
2073
|
+
await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
|
|
2074
|
+
mode: 'ralplan',
|
|
2075
|
+
active: true,
|
|
2076
|
+
current_phase: 'planning',
|
|
2077
|
+
session_id: sessionId,
|
|
2078
|
+
}, null, 2));
|
|
2079
|
+
await writeFile(join(sessionDir, 'team-state.json'), JSON.stringify({
|
|
2080
|
+
mode: 'team',
|
|
2081
|
+
active: true,
|
|
2082
|
+
current_phase: 'running',
|
|
2083
|
+
session_id: sessionId,
|
|
2084
|
+
}, null, 2));
|
|
2085
|
+
const response = await executeStateOperation('state_write', {
|
|
2086
|
+
workingDirectory: wd,
|
|
2087
|
+
session_id: sessionId,
|
|
2088
|
+
mode: 'ralplan',
|
|
2089
|
+
active: false,
|
|
2090
|
+
current_phase: 'complete',
|
|
2091
|
+
ralplan_consensus_gate: consensusGate,
|
|
2092
|
+
});
|
|
2093
|
+
assert.equal(response.isError, undefined);
|
|
2094
|
+
assert.equal(existsSync(join(sessionDir, 'skill-active-state.json')), false);
|
|
2095
|
+
const rootListed = await executeStateOperation('state_list_active', {
|
|
2096
|
+
workingDirectory: wd,
|
|
2097
|
+
});
|
|
2098
|
+
assert.deepEqual(rootListed.payload, { active_modes: ['autoresearch'] });
|
|
2099
|
+
const sessionListed = await executeStateOperation('state_list_active', {
|
|
2100
|
+
workingDirectory: wd,
|
|
2101
|
+
session_id: sessionId,
|
|
2102
|
+
});
|
|
2103
|
+
assert.deepEqual(sessionListed.payload, { active_modes: ['team'] });
|
|
2104
|
+
}
|
|
2105
|
+
finally {
|
|
2106
|
+
await rm(wd, { recursive: true, force: true });
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
685
2109
|
it('denies unsupported overlaps without writing the requested mode state', async () => {
|
|
686
2110
|
const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-deny-overlap-'));
|
|
687
2111
|
try {
|