oh-my-copilot 4.13.21 → 4.13.45
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -0
- package/agents/code-reviewer.agent.md +24 -6
- package/agents/designer.agent.md +12 -0
- package/bridge/cli.cjs +1978 -1214
- package/bridge/mcp-server.cjs +678 -261
- package/bridge/runtime-cli.cjs +55 -8
- package/bridge/team.js +507 -38
- package/dist/__tests__/agent-registry.test.js +92 -1
- package/dist/__tests__/agent-registry.test.js.map +1 -1
- package/dist/__tests__/bedrock-model-routing.test.js +18 -17
- package/dist/__tests__/bedrock-model-routing.test.js.map +1 -1
- package/dist/__tests__/delegation-enforcer.test.js +46 -6
- package/dist/__tests__/delegation-enforcer.test.js.map +1 -1
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts +2 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js +93 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js.map +1 -0
- package/dist/__tests__/hud/stdin.test.d.ts +2 -0
- package/dist/__tests__/hud/stdin.test.d.ts.map +1 -0
- package/dist/__tests__/hud/stdin.test.js +556 -0
- package/dist/__tests__/hud/stdin.test.js.map +1 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts +2 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts.map +1 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js +75 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js.map +1 -0
- package/dist/__tests__/non-claude-provider-detection.test.js +37 -0
- package/dist/__tests__/non-claude-provider-detection.test.js.map +1 -1
- package/dist/__tests__/package-dir-resolution-regression.test.js +25 -0
- package/dist/__tests__/package-dir-resolution-regression.test.js.map +1 -1
- package/dist/__tests__/pre-tool-enforcer.test.js +51 -0
- package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
- package/dist/__tests__/session-start-script-context.test.js +121 -0
- package/dist/__tests__/session-start-script-context.test.js.map +1 -1
- package/dist/__tests__/setup-claude-md-script.test.js +44 -1
- package/dist/__tests__/setup-claude-md-script.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +2 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js +69 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js.map +1 -0
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +16 -2
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/launch.test.js +23 -0
- package/dist/cli/__tests__/launch.test.js.map +1 -1
- package/dist/cli/__tests__/self-improve-paths.test.js +3 -2
- package/dist/cli/__tests__/self-improve-paths.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +100 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/commands/__tests__/team-role-shorthand.test.js +30 -0
- package/dist/cli/commands/__tests__/team-role-shorthand.test.js.map +1 -1
- package/dist/cli/commands/__tests__/team.test.js +48 -1
- package/dist/cli/commands/__tests__/team.test.js.map +1 -1
- package/dist/cli/commands/team.d.ts +2 -0
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +9 -2
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/launch.d.ts.map +1 -1
- package/dist/cli/launch.js +7 -0
- package/dist/cli/launch.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +34 -3
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/loader.test.js +35 -4
- package/dist/config/__tests__/loader.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +126 -3
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/models.d.ts +8 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +119 -8
- package/dist/config/models.js.map +1 -1
- package/dist/features/delegation-enforcer.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.js +10 -9
- package/dist/features/delegation-enforcer.js.map +1 -1
- package/dist/hooks/__tests__/bridge-routing.test.js +1 -0
- package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
- package/dist/hooks/autopilot/enforcement.d.ts.map +1 -1
- package/dist/hooks/autopilot/enforcement.js +19 -0
- package/dist/hooks/autopilot/enforcement.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +26 -4
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +37 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
- package/dist/hooks/session-end/index.d.ts +9 -2
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +90 -35
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hud/stdin.d.ts +12 -1
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +123 -18
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/types.d.ts +1 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/installer/__tests__/session-start-template.test.d.ts +2 -0
- package/dist/installer/__tests__/session-start-template.test.d.ts.map +1 -0
- package/dist/installer/__tests__/session-start-template.test.js +174 -0
- package/dist/installer/__tests__/session-start-template.test.js.map +1 -0
- package/dist/lib/__tests__/mode-state-io.test.js +75 -12
- package/dist/lib/__tests__/mode-state-io.test.js.map +1 -1
- package/dist/lib/mode-state-io.d.ts +11 -0
- package/dist/lib/mode-state-io.d.ts.map +1 -1
- package/dist/lib/mode-state-io.js +64 -0
- package/dist/lib/mode-state-io.js.map +1 -1
- package/dist/lib/worktree-paths.d.ts +12 -0
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +75 -0
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.d.ts +2 -0
- package/dist/planning/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/planning/__tests__/artifacts.test.js +486 -0
- package/dist/planning/__tests__/artifacts.test.js.map +1 -0
- package/dist/planning/artifact-names.d.ts +13 -0
- package/dist/planning/artifact-names.d.ts.map +1 -0
- package/dist/planning/artifact-names.js +108 -0
- package/dist/planning/artifact-names.js.map +1 -0
- package/dist/planning/artifacts.d.ts +47 -0
- package/dist/planning/artifacts.d.ts.map +1 -0
- package/dist/planning/artifacts.js +221 -0
- package/dist/planning/artifacts.js.map +1 -0
- package/dist/team/__tests__/api-interop.compatibility.test.js +194 -5
- package/dist/team/__tests__/api-interop.compatibility.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +92 -2
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js +30 -0
- package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +39 -1
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +6 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +89 -2
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/delegation-evidence.d.ts +5 -0
- package/dist/team/delegation-evidence.d.ts.map +1 -0
- package/dist/team/delegation-evidence.js +29 -0
- package/dist/team/delegation-evidence.js.map +1 -0
- package/dist/team/model-contract.d.ts +8 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +13 -0
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/runtime-v2.d.ts +2 -1
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +3 -1
- package/dist/team/runtime-v2.js.map +1 -1
- package/dist/team/state/tasks.d.ts +4 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +38 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/team-ops.d.ts +4 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +2 -2
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/types.d.ts +22 -1
- package/dist/team/types.d.ts.map +1 -1
- package/dist/team/types.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +6 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/tools/__tests__/state-tools.test.js +322 -5
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts +2 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts.map +1 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js +66 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js.map +1 -0
- package/dist/tools/state-tools.d.ts +3 -10
- package/dist/tools/state-tools.d.ts.map +1 -1
- package/dist/tools/state-tools.js +393 -91
- package/dist/tools/state-tools.js.map +1 -1
- package/dist/tools/wiki-tools.js +8 -8
- package/dist/tools/wiki-tools.js.map +1 -1
- package/docs/GETTING-STARTED.md +7 -0
- package/docs/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/scripts/lib/model-routing-override-message.mjs +21 -0
- package/scripts/persistent-mode.cjs +36 -0
- package/scripts/persistent-mode.mjs +36 -0
- package/scripts/post-tool-verifier.mjs +1 -0
- package/scripts/pre-tool-enforcer.mjs +60 -0
- package/scripts/session-start.mjs +63 -0
- package/scripts/setup-copilot-instructions.sh +12 -2
- package/skills/configure-notifications/SKILL.md +3 -3
- package/skills/mcp-setup/SKILL.md +60 -45
- package/skills/omc-setup/SKILL.md +3 -3
- package/skills/omc-setup/phases/01-install-claude-md.md +1 -1
- package/skills/omc-setup/phases/04-welcome.md +8 -8
- package/skills/project-session-manager/SKILL.md +2 -2
- package/skills/project-session-manager/psm.sh +1 -1
- package/templates/hooks/lib/model-routing-override-message.mjs +21 -0
- package/templates/hooks/session-start.mjs +62 -0
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* All paths are validated to stay within the worktree boundary.
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
9
|
-
import {
|
|
8
|
+
import { existsSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { resolveStatePath, ensureOmcDir, validateWorkingDirectory, resolveSessionStatePath, ensureSessionStateDir, listSessionIds, validateSessionId, getOmcRoot, } from '../lib/worktree-paths.js';
|
|
10
11
|
import { atomicWriteJsonSync } from '../lib/atomic-write.js';
|
|
11
12
|
import { validatePayload } from '../lib/payload-limits.js';
|
|
13
|
+
import { canClearStateForSession, findCompletedSessionStateFiles, findSessionOwnedStateFiles, } from '../lib/mode-state-io.js';
|
|
12
14
|
import { isModeActive, getActiveModes, getAllModeStatuses, clearModeState, getStateFilePath, MODE_CONFIGS, getActiveSessionsForMode } from '../hooks/mode-registry/index.js';
|
|
13
15
|
// Canonical execution modes from mode-registry (deep-interview and self-improve
|
|
14
16
|
// are first-class modes with dedicated MODE_CONFIGS entries; ralplan remains an
|
|
@@ -25,6 +27,86 @@ const STATE_TOOL_MODES = [
|
|
|
25
27
|
];
|
|
26
28
|
const EXTRA_STATE_ONLY_MODES = ['ralplan', 'omc-teams', 'skill-active'];
|
|
27
29
|
const CANCEL_SIGNAL_TTL_MS = 30_000;
|
|
30
|
+
function readTeamNamesFromStateFile(statePath) {
|
|
31
|
+
if (!existsSync(statePath))
|
|
32
|
+
return [];
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
35
|
+
const teamName = typeof raw.team_name === 'string'
|
|
36
|
+
? raw.team_name.trim()
|
|
37
|
+
: typeof raw.teamName === 'string'
|
|
38
|
+
? raw.teamName.trim()
|
|
39
|
+
: '';
|
|
40
|
+
return teamName ? [teamName] : [];
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function pruneMissionBoardTeams(root, teamNames) {
|
|
47
|
+
const missionStatePath = join(getOmcRoot(root), 'state', 'mission-state.json');
|
|
48
|
+
if (!existsSync(missionStatePath))
|
|
49
|
+
return 0;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(readFileSync(missionStatePath, 'utf-8'));
|
|
52
|
+
if (!Array.isArray(parsed.missions))
|
|
53
|
+
return 0;
|
|
54
|
+
const shouldRemoveAll = teamNames == null;
|
|
55
|
+
const teamNameSet = new Set(teamNames ?? []);
|
|
56
|
+
const remainingMissions = parsed.missions.filter((mission) => {
|
|
57
|
+
if (mission.source !== 'team')
|
|
58
|
+
return true;
|
|
59
|
+
if (shouldRemoveAll)
|
|
60
|
+
return false;
|
|
61
|
+
const missionTeamName = typeof mission.teamName === 'string'
|
|
62
|
+
? mission.teamName.trim()
|
|
63
|
+
: typeof mission.name === 'string'
|
|
64
|
+
? mission.name.trim()
|
|
65
|
+
: '';
|
|
66
|
+
return !missionTeamName || !teamNameSet.has(missionTeamName);
|
|
67
|
+
});
|
|
68
|
+
const removed = parsed.missions.length - remainingMissions.length;
|
|
69
|
+
if (removed > 0) {
|
|
70
|
+
writeFileSync(missionStatePath, JSON.stringify({
|
|
71
|
+
...parsed,
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
missions: remainingMissions,
|
|
74
|
+
}, null, 2));
|
|
75
|
+
}
|
|
76
|
+
return removed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function cleanupTeamRuntimeState(root, teamNames) {
|
|
83
|
+
const teamStateRoot = join(getOmcRoot(root), 'state', 'team');
|
|
84
|
+
if (!existsSync(teamStateRoot))
|
|
85
|
+
return 0;
|
|
86
|
+
const shouldRemoveAll = teamNames == null;
|
|
87
|
+
let removed = 0;
|
|
88
|
+
if (shouldRemoveAll) {
|
|
89
|
+
try {
|
|
90
|
+
rmSync(teamStateRoot, { recursive: true, force: true });
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const teamName of teamNames ?? []) {
|
|
98
|
+
if (!teamName)
|
|
99
|
+
continue;
|
|
100
|
+
try {
|
|
101
|
+
rmSync(join(teamStateRoot, teamName), { recursive: true, force: true });
|
|
102
|
+
removed += 1;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// best effort
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return removed;
|
|
109
|
+
}
|
|
28
110
|
/**
|
|
29
111
|
* Get the state file path for any mode (including swarm and ralplan).
|
|
30
112
|
*
|
|
@@ -40,6 +122,104 @@ function getStatePath(mode, root) {
|
|
|
40
122
|
// Fallback for modes not in registry (e.g., ralplan)
|
|
41
123
|
return resolveStatePath(mode, root);
|
|
42
124
|
}
|
|
125
|
+
function getLegacyStateFileCandidates(mode, root) {
|
|
126
|
+
const normalizedName = mode.endsWith('-state') ? mode : `${mode}-state`;
|
|
127
|
+
const candidates = [
|
|
128
|
+
getStatePath(mode, root),
|
|
129
|
+
join(getOmcRoot(root), `${normalizedName}.json`),
|
|
130
|
+
];
|
|
131
|
+
return [...new Set(candidates)];
|
|
132
|
+
}
|
|
133
|
+
function clearLegacyStateCandidates(mode, root, sessionId) {
|
|
134
|
+
let cleared = 0;
|
|
135
|
+
let hadFailure = false;
|
|
136
|
+
for (const legacyPath of getLegacyStateFileCandidates(mode, root)) {
|
|
137
|
+
if (!existsSync(legacyPath)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (sessionId) {
|
|
142
|
+
const raw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
|
|
143
|
+
if (!canClearStateForSession(raw, sessionId)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
unlinkSync(legacyPath);
|
|
148
|
+
cleared++;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
hadFailure = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { cleared, hadFailure };
|
|
155
|
+
}
|
|
156
|
+
function clearSessionOwnedStateCandidates(mode, root, sessionId) {
|
|
157
|
+
let cleared = 0;
|
|
158
|
+
let hadFailure = false;
|
|
159
|
+
const paths = findSessionOwnedStateFiles(mode, sessionId, root);
|
|
160
|
+
for (const statePath of paths) {
|
|
161
|
+
try {
|
|
162
|
+
unlinkSync(statePath);
|
|
163
|
+
cleared++;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
hadFailure = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { cleared, hadFailure, paths };
|
|
170
|
+
}
|
|
171
|
+
function clearCompletedSessionStateCandidates(mode, root, requesterSessionId) {
|
|
172
|
+
let cleared = 0;
|
|
173
|
+
let hadFailure = false;
|
|
174
|
+
const paths = findCompletedSessionStateFiles(mode, root, requesterSessionId);
|
|
175
|
+
for (const statePath of paths) {
|
|
176
|
+
try {
|
|
177
|
+
unlinkSync(statePath);
|
|
178
|
+
cleared++;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
hadFailure = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { cleared, hadFailure, paths };
|
|
185
|
+
}
|
|
186
|
+
function getModeRuntimeArtifactNames(mode) {
|
|
187
|
+
return [
|
|
188
|
+
`${mode}-stop-breaker.json`,
|
|
189
|
+
`${mode}-last-steer-at`,
|
|
190
|
+
`${mode}-continue-steer.lock`,
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
function clearModeRuntimeArtifacts(mode, root, sessionId) {
|
|
194
|
+
let cleared = 0;
|
|
195
|
+
let hadFailure = false;
|
|
196
|
+
const stateRoot = join(getOmcRoot(root), 'state');
|
|
197
|
+
const candidateDirs = new Set([stateRoot]);
|
|
198
|
+
if (sessionId) {
|
|
199
|
+
candidateDirs.add(join(stateRoot, 'sessions', sessionId));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
for (const sid of listSessionIds(root)) {
|
|
203
|
+
candidateDirs.add(join(stateRoot, 'sessions', sid));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const dir of candidateDirs) {
|
|
207
|
+
for (const artifactName of getModeRuntimeArtifactNames(mode)) {
|
|
208
|
+
const artifactPath = join(dir, artifactName);
|
|
209
|
+
if (!existsSync(artifactPath)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
unlinkSync(artifactPath);
|
|
214
|
+
cleared++;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
hadFailure = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { cleared, hadFailure };
|
|
222
|
+
}
|
|
43
223
|
function writeSessionCancelSignal(root, sessionId, mode) {
|
|
44
224
|
const now = Date.now();
|
|
45
225
|
const cancelSignalPath = resolveSessionStatePath('cancel-signal', sessionId, root);
|
|
@@ -67,15 +247,6 @@ function isSessionModeActive(mode, root, sessionId) {
|
|
|
67
247
|
return false;
|
|
68
248
|
}
|
|
69
249
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Find a single other session that owns active state for the given mode.
|
|
72
|
-
*
|
|
73
|
-
* Resumed Ralph sessions can leave the only active state under a foreign
|
|
74
|
-
* session directory. When the requester's session has no matching state,
|
|
75
|
-
* this fallback lets `state_clear` reach the unambiguous single owner so
|
|
76
|
-
* the stop-hook enforcement path clears cleanly. Returns undefined if there
|
|
77
|
-
* are zero or multiple candidates — we never broad-clear across sessions.
|
|
78
|
-
*/
|
|
79
250
|
function findSingleOwningSessionForMode(mode, root, requesterSessionId) {
|
|
80
251
|
const owningSessions = listSessionIds(root).filter((sid) => (sid !== requesterSessionId && isSessionModeActive(mode, root, sid)));
|
|
81
252
|
return owningSessions.length === 1 ? owningSessions[0] : undefined;
|
|
@@ -104,6 +275,26 @@ export const stateReadTool = {
|
|
|
104
275
|
? getStateFilePath(root, mode, sessionId)
|
|
105
276
|
: resolveSessionStatePath(mode, sessionId, root);
|
|
106
277
|
if (!existsSync(statePath)) {
|
|
278
|
+
const completedSessionPaths = findCompletedSessionStateFiles(mode, root, sessionId);
|
|
279
|
+
if (completedSessionPaths.length > 0) {
|
|
280
|
+
const orphanList = completedSessionPaths
|
|
281
|
+
.map((orphanPath) => {
|
|
282
|
+
const sessionMarker = `${join('state', 'sessions')}/`;
|
|
283
|
+
const markerIndex = orphanPath.indexOf(sessionMarker);
|
|
284
|
+
if (markerIndex === -1)
|
|
285
|
+
return `- ${orphanPath}`;
|
|
286
|
+
const rest = orphanPath.slice(markerIndex + sessionMarker.length);
|
|
287
|
+
const orphanSessionId = rest.split(/[\\/]/)[0] || 'unknown';
|
|
288
|
+
return `- session: ${orphanSessionId}\n path: ${orphanPath}`;
|
|
289
|
+
})
|
|
290
|
+
.join('\n');
|
|
291
|
+
return {
|
|
292
|
+
content: [{
|
|
293
|
+
type: 'text',
|
|
294
|
+
text: `No state found for mode: ${mode} in session: ${sessionId}\nExpected path: ${statePath}\n\nDiscovered ${completedSessionPaths.length} completed-session orphan state file${completedSessionPaths.length === 1 ? '' : 's'} for this mode:\n${orphanList}\n\nRun state_clear(mode="${mode}", session_id="${sessionId}") to clear the current session plus these completed-session orphan files.`
|
|
295
|
+
}]
|
|
296
|
+
};
|
|
297
|
+
}
|
|
107
298
|
return {
|
|
108
299
|
content: [{
|
|
109
300
|
type: 'text',
|
|
@@ -314,56 +505,103 @@ export const stateClearTool = {
|
|
|
314
505
|
session_id: z.string().optional().describe('Session ID for session-scoped state isolation. When provided, the tool operates only within that session. When omitted, the tool aggregates legacy state plus all session-scoped state (may include other sessions).'),
|
|
315
506
|
},
|
|
316
507
|
handler: async (args) => {
|
|
317
|
-
const {
|
|
508
|
+
const { workingDirectory, session_id } = args;
|
|
509
|
+
// Zod 4 widens z.enum(tuple) value types; cast back to the runtime-validated mode.
|
|
510
|
+
const mode = args.mode;
|
|
318
511
|
try {
|
|
319
512
|
const root = validateWorkingDirectory(workingDirectory);
|
|
320
513
|
const sessionId = session_id;
|
|
514
|
+
const cleanedTeamNames = new Set();
|
|
515
|
+
const collectTeamNamesForCleanup = (statePath) => {
|
|
516
|
+
if (mode !== 'team')
|
|
517
|
+
return;
|
|
518
|
+
for (const teamName of readTeamNamesFromStateFile(statePath)) {
|
|
519
|
+
cleanedTeamNames.add(teamName);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
321
522
|
// If session_id provided, clear only session-specific state
|
|
322
523
|
if (sessionId) {
|
|
323
524
|
validateSessionId(sessionId);
|
|
525
|
+
const requestedSessionOwnedPaths = findSessionOwnedStateFiles(mode, sessionId, root);
|
|
526
|
+
for (const teamStatePath of findSessionOwnedStateFiles('team', sessionId, root)) {
|
|
527
|
+
collectTeamNamesForCleanup(teamStatePath);
|
|
528
|
+
}
|
|
529
|
+
if (mode === 'team') {
|
|
530
|
+
for (const teamStatePath of findCompletedSessionStateFiles('team', root, sessionId)) {
|
|
531
|
+
collectTeamNamesForCleanup(teamStatePath);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const completedSessionCleanup = clearCompletedSessionStateCandidates(mode, root, sessionId);
|
|
535
|
+
const runtimeCleanup = clearModeRuntimeArtifacts(mode, root, sessionId);
|
|
324
536
|
writeSessionCancelSignal(root, sessionId, mode);
|
|
325
537
|
if (MODE_CONFIGS[mode]) {
|
|
326
|
-
const requesterStatePath = getStateFilePath(root, mode, sessionId);
|
|
327
|
-
const hadRequesterState = existsSync(requesterStatePath);
|
|
328
538
|
const success = clearModeState(mode, root, sessionId);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// to this session (matching _meta.sessionId) or has no _meta block.
|
|
332
|
-
let ghostCleaned = false;
|
|
333
|
-
try {
|
|
334
|
-
const legacyPath = getStateFilePath(root, mode);
|
|
335
|
-
if (existsSync(legacyPath)) {
|
|
336
|
-
const raw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
|
|
337
|
-
const meta = raw._meta;
|
|
338
|
-
if (!meta || meta.sessionId === sessionId) {
|
|
339
|
-
unlinkSync(legacyPath);
|
|
340
|
-
ghostCleaned = true;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
// Best-effort ghost cleanup — ignore errors
|
|
346
|
-
}
|
|
347
|
-
// Cross-session fallback: if the requester had no local state and
|
|
348
|
-
// no ghost legacy file, but exactly one other session owns active
|
|
349
|
-
// state for this mode (e.g. a resumed Ralph whose state lives
|
|
350
|
-
// under the original session), clear that owner too. Keep bounded
|
|
351
|
-
// to the unambiguous single-owner case to avoid broad-clearing.
|
|
539
|
+
const sessionCleanup = clearSessionOwnedStateCandidates(mode, root, sessionId);
|
|
540
|
+
const legacyCleanup = clearLegacyStateCandidates(mode, root, sessionId);
|
|
352
541
|
let ownerSessionId;
|
|
353
|
-
|
|
542
|
+
let ownerSessionCleanup = { cleared: 0, hadFailure: false, paths: [] };
|
|
543
|
+
let ownerLegacyCleanup = { cleared: 0, hadFailure: false };
|
|
544
|
+
if (requestedSessionOwnedPaths.length === 0 &&
|
|
545
|
+
completedSessionCleanup.cleared === 0 &&
|
|
546
|
+
sessionCleanup.cleared === 0 &&
|
|
547
|
+
legacyCleanup.cleared === 0) {
|
|
354
548
|
ownerSessionId = findSingleOwningSessionForMode(mode, root, sessionId);
|
|
355
549
|
if (ownerSessionId) {
|
|
550
|
+
if (mode === 'team') {
|
|
551
|
+
for (const teamStatePath of findSessionOwnedStateFiles('team', ownerSessionId, root)) {
|
|
552
|
+
collectTeamNamesForCleanup(teamStatePath);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
356
555
|
writeSessionCancelSignal(root, ownerSessionId, mode);
|
|
556
|
+
const ownerRuntimeCleanup = clearModeRuntimeArtifacts(mode, root, ownerSessionId);
|
|
557
|
+
runtimeCleanup.cleared += ownerRuntimeCleanup.cleared;
|
|
558
|
+
runtimeCleanup.hadFailure ||= ownerRuntimeCleanup.hadFailure;
|
|
357
559
|
clearModeState(mode, root, ownerSessionId);
|
|
560
|
+
ownerSessionCleanup = clearSessionOwnedStateCandidates(mode, root, ownerSessionId);
|
|
561
|
+
ownerLegacyCleanup = clearLegacyStateCandidates(mode, root, ownerSessionId);
|
|
358
562
|
}
|
|
359
563
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
564
|
+
const ghostNoteParts = [];
|
|
565
|
+
if (legacyCleanup.cleared > 0) {
|
|
566
|
+
ghostNoteParts.push('ghost legacy file also removed');
|
|
567
|
+
}
|
|
568
|
+
if (completedSessionCleanup.cleared > 0) {
|
|
569
|
+
ghostNoteParts.push(`removed ${completedSessionCleanup.cleared} completed-session orphan file${completedSessionCleanup.cleared === 1 ? '' : 's'}`);
|
|
570
|
+
}
|
|
571
|
+
if (sessionCleanup.cleared > 0) {
|
|
572
|
+
ghostNoteParts.push(`removed ${sessionCleanup.cleared} recovered session file${sessionCleanup.cleared === 1 ? '' : 's'}`);
|
|
573
|
+
}
|
|
574
|
+
if (runtimeCleanup.cleared > 0) {
|
|
575
|
+
ghostNoteParts.push(`removed ${runtimeCleanup.cleared} runtime artifact${runtimeCleanup.cleared === 1 ? '' : 's'}`);
|
|
576
|
+
}
|
|
577
|
+
if (ownerSessionId) {
|
|
578
|
+
ghostNoteParts.push(`cleared owning session: ${ownerSessionId}`);
|
|
579
|
+
}
|
|
580
|
+
const ghostNote = ghostNoteParts.length > 0 ? ` (${ghostNoteParts.join(', ')})` : '';
|
|
581
|
+
const runtimeCleanupNote = (() => {
|
|
582
|
+
if (mode !== 'team')
|
|
583
|
+
return '';
|
|
584
|
+
const teamNames = [...cleanedTeamNames];
|
|
585
|
+
const removedRoots = cleanupTeamRuntimeState(root, teamNames);
|
|
586
|
+
const prunedMissions = pruneMissionBoardTeams(root, teamNames);
|
|
587
|
+
const details = [];
|
|
588
|
+
if (removedRoots > 0)
|
|
589
|
+
details.push(`removed ${removedRoots} team runtime root(s)`);
|
|
590
|
+
if (prunedMissions > 0)
|
|
591
|
+
details.push(`pruned ${prunedMissions} HUD mission entry(ies)`);
|
|
592
|
+
return details.length > 0 ? ` (${details.join(', ')})` : '';
|
|
593
|
+
})();
|
|
594
|
+
if (success &&
|
|
595
|
+
!legacyCleanup.hadFailure &&
|
|
596
|
+
!sessionCleanup.hadFailure &&
|
|
597
|
+
!completedSessionCleanup.hadFailure &&
|
|
598
|
+
!ownerSessionCleanup.hadFailure &&
|
|
599
|
+
!ownerLegacyCleanup.hadFailure &&
|
|
600
|
+
!runtimeCleanup.hadFailure) {
|
|
363
601
|
return {
|
|
364
602
|
content: [{
|
|
365
603
|
type: 'text',
|
|
366
|
-
text: `Successfully cleared state for mode: ${mode} in session: ${sessionId}${ghostNote}${
|
|
604
|
+
text: `Successfully cleared state for mode: ${mode} in session: ${sessionId}${ghostNote}${runtimeCleanupNote}`
|
|
367
605
|
}]
|
|
368
606
|
};
|
|
369
607
|
}
|
|
@@ -371,65 +609,111 @@ export const stateClearTool = {
|
|
|
371
609
|
return {
|
|
372
610
|
content: [{
|
|
373
611
|
type: 'text',
|
|
374
|
-
text: `Warning: Some files could not be removed for mode: ${mode} in session: ${sessionId}${ghostNote}${
|
|
612
|
+
text: `Warning: Some files could not be removed for mode: ${mode} in session: ${sessionId}${ghostNote}${runtimeCleanupNote}`
|
|
375
613
|
}]
|
|
376
614
|
};
|
|
377
615
|
}
|
|
378
616
|
}
|
|
379
617
|
// Fallback for modes not in registry (e.g., ralplan)
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
if (hadRequesterState) {
|
|
383
|
-
unlinkSync(statePath);
|
|
384
|
-
}
|
|
385
|
-
// Ghost-legacy cleanup for non-registry modes
|
|
386
|
-
let ghostCleaned = false;
|
|
387
|
-
try {
|
|
388
|
-
const legacyPath = resolveStatePath(mode, root);
|
|
389
|
-
if (existsSync(legacyPath)) {
|
|
390
|
-
const raw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
|
|
391
|
-
const meta = raw._meta;
|
|
392
|
-
if (!meta || meta.sessionId === sessionId) {
|
|
393
|
-
unlinkSync(legacyPath);
|
|
394
|
-
ghostCleaned = true;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
399
|
-
// Best-effort ghost cleanup
|
|
400
|
-
}
|
|
401
|
-
// Same cross-session fallback as the registry branch above.
|
|
618
|
+
const sessionCleanup = clearSessionOwnedStateCandidates(mode, root, sessionId);
|
|
619
|
+
const legacyCleanup = clearLegacyStateCandidates(mode, root, sessionId);
|
|
402
620
|
let ownerSessionId;
|
|
403
|
-
|
|
621
|
+
let ownerSessionCleanup = { cleared: 0, hadFailure: false, paths: [] };
|
|
622
|
+
let ownerLegacyCleanup = { cleared: 0, hadFailure: false };
|
|
623
|
+
if (requestedSessionOwnedPaths.length === 0 &&
|
|
624
|
+
completedSessionCleanup.cleared === 0 &&
|
|
625
|
+
sessionCleanup.cleared === 0 &&
|
|
626
|
+
legacyCleanup.cleared === 0) {
|
|
404
627
|
ownerSessionId = findSingleOwningSessionForMode(mode, root, sessionId);
|
|
405
628
|
if (ownerSessionId) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
unlinkSync(ownerStatePath);
|
|
629
|
+
if (mode === 'team') {
|
|
630
|
+
for (const teamStatePath of findSessionOwnedStateFiles('team', ownerSessionId, root)) {
|
|
631
|
+
collectTeamNamesForCleanup(teamStatePath);
|
|
411
632
|
}
|
|
412
|
-
catch { /* best-effort */ }
|
|
413
633
|
}
|
|
634
|
+
writeSessionCancelSignal(root, ownerSessionId, mode);
|
|
635
|
+
const ownerRuntimeCleanup = clearModeRuntimeArtifacts(mode, root, ownerSessionId);
|
|
636
|
+
runtimeCleanup.cleared += ownerRuntimeCleanup.cleared;
|
|
637
|
+
runtimeCleanup.hadFailure ||= ownerRuntimeCleanup.hadFailure;
|
|
638
|
+
ownerSessionCleanup = clearSessionOwnedStateCandidates(mode, root, ownerSessionId);
|
|
639
|
+
ownerLegacyCleanup = clearLegacyStateCandidates(mode, root, ownerSessionId);
|
|
414
640
|
}
|
|
415
641
|
}
|
|
416
|
-
const
|
|
417
|
-
|
|
642
|
+
const ghostNoteParts = [];
|
|
643
|
+
if (legacyCleanup.cleared > 0) {
|
|
644
|
+
ghostNoteParts.push('ghost legacy file also removed');
|
|
645
|
+
}
|
|
646
|
+
if (completedSessionCleanup.cleared > 0) {
|
|
647
|
+
ghostNoteParts.push(`removed ${completedSessionCleanup.cleared} completed-session orphan file${completedSessionCleanup.cleared === 1 ? '' : 's'}`);
|
|
648
|
+
}
|
|
649
|
+
if (sessionCleanup.cleared > 0) {
|
|
650
|
+
ghostNoteParts.push(`removed ${sessionCleanup.cleared} recovered session file${sessionCleanup.cleared === 1 ? '' : 's'}`);
|
|
651
|
+
}
|
|
652
|
+
if (runtimeCleanup.cleared > 0) {
|
|
653
|
+
ghostNoteParts.push(`removed ${runtimeCleanup.cleared} runtime artifact${runtimeCleanup.cleared === 1 ? '' : 's'}`);
|
|
654
|
+
}
|
|
655
|
+
if (ownerSessionId) {
|
|
656
|
+
ghostNoteParts.push(`cleared owning session: ${ownerSessionId}`);
|
|
657
|
+
}
|
|
658
|
+
const ghostNote = ghostNoteParts.length > 0 ? ` (${ghostNoteParts.join(', ')})` : '';
|
|
659
|
+
const runtimeCleanupNote = (() => {
|
|
660
|
+
if (mode !== 'team')
|
|
661
|
+
return '';
|
|
662
|
+
const teamNames = [...cleanedTeamNames];
|
|
663
|
+
const removedRoots = cleanupTeamRuntimeState(root, teamNames);
|
|
664
|
+
const prunedMissions = pruneMissionBoardTeams(root, teamNames);
|
|
665
|
+
const details = [];
|
|
666
|
+
if (removedRoots > 0)
|
|
667
|
+
details.push(`removed ${removedRoots} team runtime root(s)`);
|
|
668
|
+
if (prunedMissions > 0)
|
|
669
|
+
details.push(`pruned ${prunedMissions} HUD mission entry(ies)`);
|
|
670
|
+
return details.length > 0 ? ` (${details.join(', ')})` : '';
|
|
671
|
+
})();
|
|
418
672
|
return {
|
|
419
673
|
content: [{
|
|
420
674
|
type: 'text',
|
|
421
|
-
text:
|
|
675
|
+
text: `${legacyCleanup.hadFailure || sessionCleanup.hadFailure || completedSessionCleanup.hadFailure || ownerSessionCleanup.hadFailure || ownerLegacyCleanup.hadFailure || runtimeCleanup.hadFailure ? 'Warning: Some files could not be removed' : 'Successfully cleared state'} for mode: ${mode} in session: ${sessionId}${ghostNote}${runtimeCleanupNote}`
|
|
422
676
|
}]
|
|
423
677
|
};
|
|
424
678
|
}
|
|
425
679
|
// No session_id: clear from all locations (legacy + all sessions)
|
|
680
|
+
// Write cancel signals FIRST (before deleting files) so the stop hook's
|
|
681
|
+
// isSessionCancelInProgress check sees the signal during the deletion window.
|
|
682
|
+
// Mirrors the session_id path at line ~403. (patch: fix missing cancel signal)
|
|
683
|
+
{
|
|
684
|
+
const now = Date.now();
|
|
685
|
+
const cancelSignalPayload = {
|
|
686
|
+
active: true,
|
|
687
|
+
requested_at: new Date(now).toISOString(),
|
|
688
|
+
expires_at: new Date(now + CANCEL_SIGNAL_TTL_MS).toISOString(),
|
|
689
|
+
mode,
|
|
690
|
+
source: 'state_clear',
|
|
691
|
+
};
|
|
692
|
+
// Write to legacy path (checked by stop hook fallback)
|
|
693
|
+
const legacySignalPath = join(getOmcRoot(root), 'state', 'cancel-signal-state.json');
|
|
694
|
+
try {
|
|
695
|
+
atomicWriteJsonSync(legacySignalPath, cancelSignalPayload);
|
|
696
|
+
}
|
|
697
|
+
catch { /* best-effort */ }
|
|
698
|
+
// Write to each session path (checked by stop hook primary check)
|
|
699
|
+
for (const sid of listSessionIds(root)) {
|
|
700
|
+
try {
|
|
701
|
+
const sessionSignalPath = resolveSessionStatePath('cancel-signal', sid, root);
|
|
702
|
+
atomicWriteJsonSync(sessionSignalPath, cancelSignalPayload);
|
|
703
|
+
}
|
|
704
|
+
catch { /* best-effort */ }
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const runtimeCleanup = clearModeRuntimeArtifacts(mode, root);
|
|
426
708
|
let clearedCount = 0;
|
|
427
709
|
const errors = [];
|
|
710
|
+
if (mode === 'team') {
|
|
711
|
+
collectTeamNamesForCleanup(getStateFilePath(root, 'team'));
|
|
712
|
+
}
|
|
428
713
|
// Clear legacy path
|
|
429
714
|
if (MODE_CONFIGS[mode]) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (existsSync(legacyStatePath)) {
|
|
715
|
+
const primaryLegacyStatePath = getStateFilePath(root, mode);
|
|
716
|
+
if (existsSync(primaryLegacyStatePath)) {
|
|
433
717
|
if (clearModeState(mode, root)) {
|
|
434
718
|
clearedCount++;
|
|
435
719
|
}
|
|
@@ -438,21 +722,21 @@ export const stateClearTool = {
|
|
|
438
722
|
}
|
|
439
723
|
}
|
|
440
724
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
errors.push('legacy path');
|
|
450
|
-
}
|
|
451
|
-
}
|
|
725
|
+
const extraLegacyCleanup = clearLegacyStateCandidates(mode, root);
|
|
726
|
+
clearedCount += extraLegacyCleanup.cleared;
|
|
727
|
+
if (extraLegacyCleanup.hadFailure) {
|
|
728
|
+
errors.push('legacy path');
|
|
729
|
+
}
|
|
730
|
+
clearedCount += runtimeCleanup.cleared;
|
|
731
|
+
if (runtimeCleanup.hadFailure) {
|
|
732
|
+
errors.push('runtime artifacts');
|
|
452
733
|
}
|
|
453
734
|
// Clear all session-scoped state files
|
|
454
735
|
const sessionIds = listSessionIds(root);
|
|
455
736
|
for (const sid of sessionIds) {
|
|
737
|
+
if (mode === 'team') {
|
|
738
|
+
collectTeamNamesForCleanup(resolveSessionStatePath('team', sid, root));
|
|
739
|
+
}
|
|
456
740
|
if (MODE_CONFIGS[mode]) {
|
|
457
741
|
// Only clear if state file exists - avoid false counts for missing files
|
|
458
742
|
const sessionStatePath = getStateFilePath(root, mode, sid);
|
|
@@ -478,7 +762,15 @@ export const stateClearTool = {
|
|
|
478
762
|
}
|
|
479
763
|
}
|
|
480
764
|
}
|
|
481
|
-
|
|
765
|
+
let removedTeamRoots = 0;
|
|
766
|
+
let prunedMissionEntries = 0;
|
|
767
|
+
if (mode === 'team') {
|
|
768
|
+
const teamNames = [...cleanedTeamNames];
|
|
769
|
+
const removeSelector = teamNames.length > 0 ? teamNames : undefined;
|
|
770
|
+
removedTeamRoots = cleanupTeamRuntimeState(root, removeSelector);
|
|
771
|
+
prunedMissionEntries = pruneMissionBoardTeams(root, removeSelector);
|
|
772
|
+
}
|
|
773
|
+
if (clearedCount === 0 && errors.length === 0 && removedTeamRoots === 0 && prunedMissionEntries === 0) {
|
|
482
774
|
return {
|
|
483
775
|
content: [{
|
|
484
776
|
type: 'text',
|
|
@@ -490,6 +782,14 @@ export const stateClearTool = {
|
|
|
490
782
|
if (errors.length > 0) {
|
|
491
783
|
message += `\n- Errors: ${errors.join(', ')}`;
|
|
492
784
|
}
|
|
785
|
+
if (mode === 'team') {
|
|
786
|
+
if (removedTeamRoots > 0) {
|
|
787
|
+
message += `\n- Team runtime roots removed: ${removedTeamRoots}`;
|
|
788
|
+
}
|
|
789
|
+
if (prunedMissionEntries > 0) {
|
|
790
|
+
message += `\n- HUD mission entries pruned: ${prunedMissionEntries}`;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
493
793
|
message += '\nWARNING: No session_id provided. Cleared legacy plus all session-scoped state; this is a broad operation that may affect other sessions.';
|
|
494
794
|
return {
|
|
495
795
|
content: [{
|
|
@@ -655,7 +955,9 @@ export const stateGetStatusTool = {
|
|
|
655
955
|
session_id: z.string().optional().describe('Session ID for session-scoped state isolation. When provided, the tool operates only within that session. When omitted, the tool aggregates legacy state plus all session-scoped state (may include other sessions).'),
|
|
656
956
|
},
|
|
657
957
|
handler: async (args) => {
|
|
658
|
-
const {
|
|
958
|
+
const { workingDirectory, session_id } = args;
|
|
959
|
+
// Zod 4 widens z.enum(tuple) value types; cast back to the runtime-validated mode.
|
|
960
|
+
const mode = args.mode;
|
|
659
961
|
try {
|
|
660
962
|
const root = validateWorkingDirectory(workingDirectory);
|
|
661
963
|
const sessionId = session_id;
|