mixdog 0.9.19 → 0.9.21
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/LICENSE +21 -0
- package/README.md +100 -34
- package/package.json +3 -2
- package/scripts/build-tui.mjs +6 -0
- package/scripts/hook-bus-test.mjs +8 -0
- package/scripts/log-writer-guard-smoke.mjs +131 -0
- package/scripts/reactive-compact-persist-smoke.mjs +22 -6
- package/scripts/recall-bench-cases.json +0 -1
- package/scripts/recall-quality-cases.json +1 -2
- package/scripts/session-ingest-compaction-smoke.mjs +241 -0
- package/scripts/tool-smoke.mjs +150 -45
- package/src/defaults/skills/setup/SKILL.md +327 -0
- package/src/help.mjs +2 -5
- package/src/lib/mixdog-debug.cjs +13 -0
- package/src/mixdog-session-runtime.mjs +7 -3328
- package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -2
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -10
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +40 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +73 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +663 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop.mjs +8 -1860
- package/src/runtime/agent/orchestrator/session/manager/agent-runtime-singleton.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +686 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +60 -12
- package/src/runtime/agent/orchestrator/session/manager/env-utils.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +159 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +143 -0
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +124 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +258 -0
- package/src/runtime/agent/orchestrator/session/manager/session-errors.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-id.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +475 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +88 -2285
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +440 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +642 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +23 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +108 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +24 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/shell-policy-danger-target.mjs +5 -1
- package/src/runtime/channels/index.mjs +6 -2183
- package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
- package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
- package/src/runtime/channels/lib/network-retry.mjs +23 -0
- package/src/runtime/channels/lib/owned-runtime.mjs +627 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +20 -9
- package/src/runtime/channels/lib/transcript-binding.mjs +336 -0
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +39 -2
- package/src/runtime/channels/lib/worker-bootstrap.mjs +97 -0
- package/src/runtime/channels/lib/worker-ipc.mjs +201 -0
- package/src/runtime/channels/lib/worker-main.mjs +777 -0
- package/src/runtime/memory/index.mjs +163 -1725
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -2
- package/src/runtime/memory/lib/embedding-worker.mjs +47 -6
- package/src/runtime/memory/lib/http-router.mjs +811 -0
- package/src/runtime/memory/lib/memory-action-handlers.mjs +901 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +6 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +46 -9
- package/src/runtime/memory/lib/memory-cycle2.mjs +87 -11
- package/src/runtime/memory/lib/memory-embed.mjs +28 -7
- package/src/runtime/memory/lib/memory-recall-store.mjs +4 -4
- package/src/runtime/memory/lib/memory.mjs +39 -0
- package/src/runtime/memory/lib/query-handlers.mjs +2 -2
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
- package/src/runtime/shared/atomic-file.mjs +138 -80
- package/src/runtime/shared/child-guardian.mjs +61 -3
- package/src/session-runtime/boot-profile.mjs +36 -0
- package/src/session-runtime/channel-config-api.mjs +70 -0
- package/src/session-runtime/context-status.mjs +181 -0
- package/src/session-runtime/env.mjs +17 -0
- package/src/session-runtime/lifecycle-api.mjs +242 -0
- package/src/session-runtime/model-route-api.mjs +198 -0
- package/src/session-runtime/provider-auth-api.mjs +135 -0
- package/src/session-runtime/resource-api.mjs +282 -0
- package/src/session-runtime/runtime-core.mjs +2104 -0
- package/src/session-runtime/session-turn-api.mjs +274 -0
- package/src/session-runtime/tool-catalog.mjs +18 -264
- package/src/session-runtime/tool-defs.mjs +2 -2
- package/src/session-runtime/workflow-agents-api.mjs +238 -0
- package/src/standalone/agent-tool.mjs +2 -2
- package/src/standalone/channel-worker.mjs +67 -7
- package/src/standalone/folder-dialog.mjs +4 -1
- package/src/standalone/memory-runtime-proxy.mjs +154 -17
- package/src/standalone/seeds.mjs +28 -1
- package/src/tui/App.jsx +40 -28
- package/src/tui/app/core-memory-picker.mjs +1 -1
- package/src/tui/app/doctor.mjs +175 -0
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +9 -0
- package/src/tui/app/use-mouse-input.mjs +6 -0
- package/src/tui/app/use-transcript-scroll.mjs +77 -3
- package/src/tui/dist/index.mjs +2851 -2162
- package/src/tui/engine/context-state.mjs +145 -0
- package/src/tui/engine/prompt-history.mjs +27 -0
- package/src/tui/engine/session-api-ext.mjs +478 -0
- package/src/tui/engine/session-api.mjs +564 -0
- package/src/tui/engine/session-flow.mjs +485 -0
- package/src/tui/engine/turn.mjs +1078 -0
- package/src/tui/engine.mjs +68 -2620
- package/src/tui/index.jsx +7 -0
- package/vendor/ink/build/ink.js +16 -1
- package/vendor/ink/build/output.js +30 -4
- package/vendor/ink/build/render.js +5 -0
- package/src/workflows/sequential/WORKFLOW.md +0 -51
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// manager/session-crud.mjs
|
|
2
|
+
// Session read + mutate CRUD extracted verbatim from manager.mjs: lookup
|
|
3
|
+
// (getSession/listSessions/findSessionByScopeKey), message clear (with
|
|
4
|
+
// clear-fork), manual compaction, status update, and metric flush.
|
|
5
|
+
import { loadSession, saveSessionAsync, setLiveSession, listStoredSessionSummaries } from '../store.mjs';
|
|
6
|
+
import { estimateMessagesTokens, estimateTranscriptContextUsage } from '../context-utils.mjs';
|
|
7
|
+
import { normalizeCompactType, DEFAULT_COMPACT_TYPE, SUMMARY_PREFIX } from '../compact.mjs';
|
|
8
|
+
import { runSessionCompaction } from './compaction-runner.mjs';
|
|
9
|
+
import { hasUserConversationMessage } from './prompt-utils.mjs';
|
|
10
|
+
import { getProvider } from '../../providers/registry.mjs';
|
|
11
|
+
import { isSessionCompactionBlocked, getSessionAbortSignal, _runtimeEntries } from './runtime-liveness.mjs';
|
|
12
|
+
import { mintSessionId } from './session-id.mjs';
|
|
13
|
+
|
|
14
|
+
/** Force-flush session metrics to disk. Used by watchdog terminal-reap (fix B). */
|
|
15
|
+
export async function flushSessionMetrics(sessionId) {
|
|
16
|
+
if (!sessionId) return;
|
|
17
|
+
const session = loadSession(sessionId);
|
|
18
|
+
if (!session) return;
|
|
19
|
+
session.updatedAt = Date.now();
|
|
20
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Session lookup by scopeKey — used by CLI agent to resume a pinned
|
|
24
|
+
// scope session when the caller passes --scope (agent/<name>).
|
|
25
|
+
export function findSessionByScopeKey(scopeKey) {
|
|
26
|
+
if (!scopeKey) return null;
|
|
27
|
+
const summaries = listStoredSessionSummaries();
|
|
28
|
+
// Exclude tombstoned sessions (`closed === true`) so callers never receive
|
|
29
|
+
// a session whose controller was aborted by closeSession(). The `closed`
|
|
30
|
+
// bit is the authoritative tombstone flag; `status === 'error'` is not,
|
|
31
|
+
// since transient-error sessions remain resumable.
|
|
32
|
+
const summary = summaries.find(s => s.scopeKey === scopeKey && s.closed !== true) || null;
|
|
33
|
+
return summary?.id ? loadSession(summary.id) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- CRUD ---
|
|
37
|
+
export function getSession(id) {
|
|
38
|
+
return loadSession(id);
|
|
39
|
+
}
|
|
40
|
+
export function listSessions(opts = {}) {
|
|
41
|
+
const includeClosed = opts.includeClosed === true;
|
|
42
|
+
const sessions = listStoredSessionSummaries();
|
|
43
|
+
const hiddenIds = new Set([..._runtimeEntries()].filter(([, e]) => e.listHidden).map(([id]) => id));
|
|
44
|
+
// Tombstoned sessions (closed===true) are excluded unless the caller opts in
|
|
45
|
+
// (e.g. agent list includeClosed:true).
|
|
46
|
+
return sessions.filter(s => !hiddenIds.has(s.id) && (includeClosed || s.closed !== true));
|
|
47
|
+
}
|
|
48
|
+
// --- Clear messages (keep system prompt + provider/model/cwd) ---
|
|
49
|
+
export async function clearSessionMessages(sessionId, options = {}) {
|
|
50
|
+
const session = loadSession(sessionId);
|
|
51
|
+
if (!session)
|
|
52
|
+
return false;
|
|
53
|
+
// Don't resurrect a closed session just to clear its messages.
|
|
54
|
+
if (session.closed === true) return false;
|
|
55
|
+
const clearOptions = options && typeof options === 'object' ? options : {};
|
|
56
|
+
const requestedCompactType = clearOptions.compactType ?? clearOptions.compact_type ?? clearOptions.type;
|
|
57
|
+
const compactBeforeClear = requestedCompactType != null && requestedCompactType !== false && String(requestedCompactType).trim() !== '';
|
|
58
|
+
const keep = [];
|
|
59
|
+
let messages = Array.isArray(session.messages) ? session.messages : [];
|
|
60
|
+
const beforeMessageTokens = estimateMessagesTokens(messages);
|
|
61
|
+
let clearCompactType = null;
|
|
62
|
+
let clearCompactError = null;
|
|
63
|
+
if (compactBeforeClear && messages.length >= 3) {
|
|
64
|
+
clearCompactType = normalizeCompactType(requestedCompactType, DEFAULT_COMPACT_TYPE);
|
|
65
|
+
session.compaction = {
|
|
66
|
+
...(session.compaction || {}),
|
|
67
|
+
type: clearCompactType,
|
|
68
|
+
compactType: clearCompactType,
|
|
69
|
+
};
|
|
70
|
+
try {
|
|
71
|
+
const compactResult = await runSessionCompaction(session, { mode: 'manual', force: true, sessionId });
|
|
72
|
+
if (compactResult?.error) {
|
|
73
|
+
clearCompactError = new Error(compactResult.error);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
clearCompactError = err;
|
|
77
|
+
try { process.stderr.write(`[session] auto-clear pre-compact failed (sess=${sessionId}): ${err?.message || err}\n`); } catch { /* best-effort */ }
|
|
78
|
+
}
|
|
79
|
+
messages = Array.isArray(session.messages) ? session.messages : [];
|
|
80
|
+
}
|
|
81
|
+
if (compactBeforeClear && clearOptions.requireCompactSuccess === true) {
|
|
82
|
+
const hasRetainedSummary = messages.some((m) => (
|
|
83
|
+
m?.role === 'user'
|
|
84
|
+
&& typeof m.content === 'string'
|
|
85
|
+
&& m.content.startsWith(SUMMARY_PREFIX)
|
|
86
|
+
));
|
|
87
|
+
if (!hasRetainedSummary && !clearCompactError) {
|
|
88
|
+
clearCompactError = new Error('compact produced no retained summary');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (clearCompactError && clearOptions.requireCompactSuccess === true) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
session.compaction = {
|
|
94
|
+
...(session.compaction || {}),
|
|
95
|
+
lastStage: 'auto_clear_failed',
|
|
96
|
+
lastCheckedAt: now,
|
|
97
|
+
lastChanged: false,
|
|
98
|
+
lastClearAt: session.compaction?.lastClearAt || null,
|
|
99
|
+
lastClearCompactType: clearCompactType || session.compaction?.compactType || null,
|
|
100
|
+
lastClearCompactError: clearCompactError?.message || String(clearCompactError),
|
|
101
|
+
};
|
|
102
|
+
session.updatedAt = now;
|
|
103
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
104
|
+
throw new Error(`auto-clear compact failed; conversation kept: ${session.compaction.lastClearCompactError}`);
|
|
105
|
+
}
|
|
106
|
+
const preserveCompactSummary = compactBeforeClear && clearOptions.keepCompactSummary !== false;
|
|
107
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
108
|
+
const m = messages[i];
|
|
109
|
+
if (!m) continue;
|
|
110
|
+
if (m.role === 'system') {
|
|
111
|
+
// BP1/BP2/BP3 all ride `role:'system'` blocks now (BP3 sessionMarker
|
|
112
|
+
// moved off the `<system-reminder>` user wrapper), so the stable
|
|
113
|
+
// memory/meta layer is preserved here unconditionally — no sentinel
|
|
114
|
+
// scan / dummy-assistant pairing needed anymore.
|
|
115
|
+
keep.push(m);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (preserveCompactSummary
|
|
119
|
+
&& m.role === 'user'
|
|
120
|
+
&& typeof m.content === 'string'
|
|
121
|
+
&& m.content.startsWith(SUMMARY_PREFIX)) {
|
|
122
|
+
keep.push(m);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const afterMessageTokens = estimateMessagesTokens(keep);
|
|
126
|
+
const beforeTokens = estimateTranscriptContextUsage(messages, session.tools || []);
|
|
127
|
+
const afterTokens = estimateTranscriptContextUsage(keep, session.tools || []);
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
// --- Fork the outgoing transcript to a separate resumable session BEFORE
|
|
130
|
+
// the wipe below. Runs for every clear path (plain /clear, auto-clear,
|
|
131
|
+
// compact_clear) using the ORIGINAL `messages` (post-compact-gating, i.e.
|
|
132
|
+
// whatever survived the requireCompactSuccess throw above), so the
|
|
133
|
+
// conversation about to be discarded stays reachable via /resume under a
|
|
134
|
+
// fresh id. Skipped for scratch sessions with no real user turn — nothing
|
|
135
|
+
// worth resuming. Best-effort: any failure here must never block the
|
|
136
|
+
// clear itself (mirrors the pre-compact failure handling above).
|
|
137
|
+
if (hasUserConversationMessage(messages)) {
|
|
138
|
+
try {
|
|
139
|
+
const forkId = mintSessionId();
|
|
140
|
+
const fork = {
|
|
141
|
+
...session,
|
|
142
|
+
id: forkId,
|
|
143
|
+
messages: messages.map((m) => (m && typeof m === 'object' ? { ...m } : m)),
|
|
144
|
+
closed: false,
|
|
145
|
+
status: 'idle',
|
|
146
|
+
generation: 0,
|
|
147
|
+
createdAt: now,
|
|
148
|
+
updatedAt: now,
|
|
149
|
+
lastUsedAt: now,
|
|
150
|
+
lastHeartbeatAt: null,
|
|
151
|
+
mcpPid: process.pid,
|
|
152
|
+
// Strip runtime/liveness/routing state — the fork is a cold
|
|
153
|
+
// snapshot, not a live process-owned session.
|
|
154
|
+
clientHostPid: null,
|
|
155
|
+
providerState: undefined,
|
|
156
|
+
totalInputTokens: 0,
|
|
157
|
+
totalOutputTokens: 0,
|
|
158
|
+
totalCachedReadTokens: 0,
|
|
159
|
+
totalCacheWriteTokens: 0,
|
|
160
|
+
lastInputTokens: 0,
|
|
161
|
+
lastOutputTokens: 0,
|
|
162
|
+
lastCachedReadTokens: 0,
|
|
163
|
+
lastCacheWriteTokens: 0,
|
|
164
|
+
lastContextTokens: 0,
|
|
165
|
+
lastContextTokensUpdatedAt: now,
|
|
166
|
+
lastContextTokensStaleAfterCompact: false,
|
|
167
|
+
// Shell state must not alias the live session: resuming the
|
|
168
|
+
// fork would otherwise reuse/close the original session's
|
|
169
|
+
// persistent bash shells.
|
|
170
|
+
implicitBashSessionId: null,
|
|
171
|
+
allBashSessionIds: undefined,
|
|
172
|
+
};
|
|
173
|
+
delete fork.liveTurnMessages;
|
|
174
|
+
setLiveSession(fork);
|
|
175
|
+
void saveSessionAsync(fork).catch((err) => {
|
|
176
|
+
try { process.stderr.write(`[session] clear-fork save failed (sess=${forkId}): ${err?.message || err}\n`); } catch { /* best-effort */ }
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
try { process.stderr.write(`[session] clear-fork failed (sess=${sessionId}): ${err?.message || err}\n`); } catch { /* best-effort */ }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
session.messages = keep;
|
|
183
|
+
session.sessionStartMetaInjected = false;
|
|
184
|
+
session.totalInputTokens = 0;
|
|
185
|
+
session.totalOutputTokens = 0;
|
|
186
|
+
session.totalCachedReadTokens = 0;
|
|
187
|
+
session.totalCacheWriteTokens = 0;
|
|
188
|
+
session.lastInputTokens = 0;
|
|
189
|
+
session.lastOutputTokens = 0;
|
|
190
|
+
session.lastCachedReadTokens = 0;
|
|
191
|
+
session.lastCacheWriteTokens = 0;
|
|
192
|
+
session.lastContextTokens = 0;
|
|
193
|
+
session.lastContextTokensUpdatedAt = now;
|
|
194
|
+
session.lastContextTokensStaleAfterCompact = false;
|
|
195
|
+
session.providerState = undefined;
|
|
196
|
+
session.compaction = {
|
|
197
|
+
...(session.compaction || {}),
|
|
198
|
+
lastStage: 'auto_clear',
|
|
199
|
+
lastBeforeTokens: beforeTokens,
|
|
200
|
+
lastAfterTokens: afterTokens,
|
|
201
|
+
lastBeforeMessageTokens: beforeMessageTokens,
|
|
202
|
+
lastAfterMessageTokens: afterMessageTokens,
|
|
203
|
+
lastPressureTokens: beforeTokens,
|
|
204
|
+
lastCheckedAt: now,
|
|
205
|
+
lastChanged: beforeTokens !== afterTokens,
|
|
206
|
+
lastClearAt: now,
|
|
207
|
+
lastClearBeforeTokens: beforeTokens,
|
|
208
|
+
lastClearAfterTokens: afterTokens,
|
|
209
|
+
lastClearBeforeMessageTokens: beforeMessageTokens,
|
|
210
|
+
lastClearAfterMessageTokens: afterMessageTokens,
|
|
211
|
+
lastClearCompactType: clearCompactType || session.compaction?.compactType || null,
|
|
212
|
+
lastClearCompactError: clearCompactError?.message || null,
|
|
213
|
+
};
|
|
214
|
+
session.updatedAt = now;
|
|
215
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
216
|
+
return session;
|
|
217
|
+
}
|
|
218
|
+
export async function compactSessionMessages(sessionId) {
|
|
219
|
+
const session = loadSession(sessionId);
|
|
220
|
+
if (!session) return null;
|
|
221
|
+
if (session.closed === true) return null;
|
|
222
|
+
if (isSessionCompactionBlocked(sessionId)) {
|
|
223
|
+
return { changed: false, reason: 'compact skipped: turn in progress' };
|
|
224
|
+
}
|
|
225
|
+
const result = await runSessionCompaction(session, {
|
|
226
|
+
mode: 'manual',
|
|
227
|
+
force: true,
|
|
228
|
+
provider: getProvider(session.provider),
|
|
229
|
+
model: session.model,
|
|
230
|
+
sessionId,
|
|
231
|
+
signal: getSessionAbortSignal(sessionId),
|
|
232
|
+
});
|
|
233
|
+
if (!result) return null;
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
if (!result.error) {
|
|
236
|
+
session.lastInputTokens = 0;
|
|
237
|
+
session.lastOutputTokens = 0;
|
|
238
|
+
session.lastCachedReadTokens = 0;
|
|
239
|
+
session.lastCacheWriteTokens = 0;
|
|
240
|
+
session.lastContextTokens = 0;
|
|
241
|
+
session.lastContextTokensUpdatedAt = now;
|
|
242
|
+
session.lastContextTokensStaleAfterCompact = false;
|
|
243
|
+
}
|
|
244
|
+
session.updatedAt = Date.now();
|
|
245
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
export async function updateSessionStatus(id, status) {
|
|
249
|
+
const session = loadSession(id);
|
|
250
|
+
if (!session) return false;
|
|
251
|
+
// Respect tombstones — don't resurrect a closed session just to update a
|
|
252
|
+
// status label (agent handler emits running→idle/error around askSession).
|
|
253
|
+
if (session.closed === true) return false;
|
|
254
|
+
session.status = status;
|
|
255
|
+
session.updatedAt = Date.now();
|
|
256
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// manager/session-errors.mjs
|
|
2
|
+
// Extracted verbatim from manager.mjs. Re-exported through the facade so
|
|
3
|
+
// loop.mjs and other importers resolve SessionClosedError unchanged.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a session is closed while a call is in-flight. Callers (agent
|
|
7
|
+
* handler, CLI) should render this as "cancelled" rather than a hard error.
|
|
8
|
+
*/
|
|
9
|
+
export class SessionClosedError extends Error {
|
|
10
|
+
constructor(sessionId, reason, closeReason) {
|
|
11
|
+
super(reason ? `Session "${sessionId}" closed: ${reason}` : `Session "${sessionId}" closed`);
|
|
12
|
+
this.name = 'SessionClosedError';
|
|
13
|
+
this.sessionId = sessionId;
|
|
14
|
+
this.cancelled = true;
|
|
15
|
+
// closeReason is the diagnostic enum (request-abort / manual /
|
|
16
|
+
// idle-sweep / runner-crash). Kept separate from `reason` (the free
|
|
17
|
+
// -form message) so consumers can branch on it without regex parsing.
|
|
18
|
+
this.reason = closeReason || null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// manager/session-id.mjs
|
|
2
|
+
// Monotonic session-id minting extracted from manager.mjs. The counter is a
|
|
3
|
+
// module-level singleton shared by createSession (spawn) and
|
|
4
|
+
// clearSessionMessages (clear-fork), matching the original single `nextId`.
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
let nextId = Date.now();
|
|
7
|
+
export function mintSessionId() {
|
|
8
|
+
return `sess_${process.pid}_${nextId++}_${Date.now()}_${randomBytes(16).toString('hex')}`;
|
|
9
|
+
}
|