@cjhyy/code-shell-core 0.8.1 → 0.8.3
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/dist/engine/engine.js +17 -10
- package/dist/engine/run-tooling.js +4 -0
- package/dist/engine/turn-loop.d.ts +3 -0
- package/dist/engine/turn-loop.js +80 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm/providers/openai.js +49 -2
- package/dist/protocol/chat-session-manager.js +9 -0
- package/dist/run/EngineRunner.js +5 -4
- package/dist/session/session-manager.d.ts +12 -13
- package/dist/session/session-manager.js +214 -28
- package/dist/session/transcript.d.ts +20 -4
- package/dist/session/transcript.js +97 -36
- package/dist/settings/schema.d.ts +99 -0
- package/dist/settings/schema.js +13 -0
- package/dist/tool-system/builtin/tool-search.js +44 -2
- package/dist/tool-system/context.d.ts +14 -1
- package/dist/tool-system/external-tool-exposure.js +50 -26
- package/dist/tool-system/session-tool-host.d.ts +1 -1
- package/dist/tool-system/session-tool-host.js +31 -3
- package/dist/types.d.ts +7 -3
- package/package.json +1 -1
package/dist/engine/engine.js
CHANGED
|
@@ -1501,7 +1501,9 @@ export class Engine {
|
|
|
1501
1501
|
this.lastMessages = messages;
|
|
1502
1502
|
// Wire up LLM summarization for context compaction
|
|
1503
1503
|
// Uses a lightweight call without tools
|
|
1504
|
-
|
|
1504
|
+
if (session.transcript.isPersistent()) {
|
|
1505
|
+
contextManager.setTranscriptPath(session.transcript.getFilePath());
|
|
1506
|
+
}
|
|
1505
1507
|
// Re-derive frozen persistence decisions from the messages we just
|
|
1506
1508
|
// loaded. Skipped on cold start (messages == [userContextMsg] only).
|
|
1507
1509
|
// Critical for resume — otherwise a result that was persisted last
|
|
@@ -1704,14 +1706,17 @@ export class Engine {
|
|
|
1704
1706
|
// capture the persisted total at run start and fold this run's usage onto
|
|
1705
1707
|
// it (see foldRunUsage). Snapshot now, before any turn boundary fires.
|
|
1706
1708
|
const usageBaseline = { ...session.state.tokenUsage };
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1709
|
+
const fileHistoryHook = isEphemeralSessionState(session.state)
|
|
1710
|
+
? { dispose() { } }
|
|
1711
|
+
: registerFileHistoryHook({
|
|
1712
|
+
hooks: this.hooks,
|
|
1713
|
+
sessionDir: join(this.config.sessionStorageDir ?? sessionsRoot(), session.state.sessionId),
|
|
1714
|
+
cwd,
|
|
1715
|
+
getTurnSeq: () => session.state.turnSeq,
|
|
1716
|
+
contributions: this.capabilities.flatMap((capability) => [
|
|
1717
|
+
...(capability.fileHistory ?? []),
|
|
1718
|
+
]),
|
|
1719
|
+
});
|
|
1715
1720
|
// Hook: agent start
|
|
1716
1721
|
await this.emitHook("on_agent_start", {
|
|
1717
1722
|
sessionId: session.state.sessionId,
|
|
@@ -2800,7 +2805,9 @@ export class Engine {
|
|
|
2800
2805
|
maxTokens: this.resolveMaxContextTokens(),
|
|
2801
2806
|
...Object.fromEntries(Object.entries(this.resolveContextRatios()).filter(([, v]) => v !== undefined)),
|
|
2802
2807
|
});
|
|
2803
|
-
|
|
2808
|
+
if (session.transcript.isPersistent()) {
|
|
2809
|
+
contextManager.setTranscriptPath(session.transcript.getFilePath());
|
|
2810
|
+
}
|
|
2804
2811
|
contextManager.initReplacementStateFromMessages(sourceMessages);
|
|
2805
2812
|
this.lastContextManager = contextManager;
|
|
2806
2813
|
}
|
|
@@ -279,5 +279,9 @@ export function assembleRunToolDefs(args) {
|
|
|
279
279
|
const toolDefs = args.runPlanMode
|
|
280
280
|
? profileToolDefs.filter((t) => PLAN_MODE_ALLOWED_TOOLS.has(t.name))
|
|
281
281
|
: profileToolDefs;
|
|
282
|
+
// ToolSearch shares the worker registry across Sessions, but discovery must
|
|
283
|
+
// reflect this exact run's filtered and rewritten surface. Keep the context
|
|
284
|
+
// snapshot in lockstep with the definitions sent to the model.
|
|
285
|
+
toolCtx.searchableToolDefinitions = toolDefs;
|
|
282
286
|
return toolDefs;
|
|
283
287
|
}
|
|
@@ -202,6 +202,9 @@ export declare class TurnLoop {
|
|
|
202
202
|
* loop forces a stop so a stuck goal can't loop forever.
|
|
203
203
|
*/
|
|
204
204
|
private stopBlockCount;
|
|
205
|
+
/** Consecutive identical tool-call/result batches, ignoring provider call ids. */
|
|
206
|
+
private repeatedToolBatchFingerprint;
|
|
207
|
+
private repeatedToolBatchCount;
|
|
205
208
|
/**
|
|
206
209
|
* Run-scoped goal budget tracker (Goal mode). Hoisted to an instance field
|
|
207
210
|
* (not a run() local) so extend() can bump its budgets mid-run. Null when no
|
package/dist/engine/turn-loop.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Following Claude Code's po_() pattern:
|
|
5
5
|
* pre_check → model_call → post_check → tool_exec → context_mgmt → hook_notify → next turn
|
|
6
6
|
*/
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { buildAgentDirectionMessage } from "../tool-system/builtin/agent-notifications.js";
|
|
9
9
|
import { newTurnId } from "./turn-state.js";
|
|
10
10
|
import { formatFriendlyError } from "./friendly-error.js";
|
|
@@ -40,6 +40,39 @@ export function toolResultToBlock(result) {
|
|
|
40
40
|
block.is_error = true;
|
|
41
41
|
return block;
|
|
42
42
|
}
|
|
43
|
+
const REPEATED_TOOL_BATCH_LIMIT = 3;
|
|
44
|
+
function canonicalToolValue(value) {
|
|
45
|
+
if (value === null)
|
|
46
|
+
return "null";
|
|
47
|
+
if (value === undefined)
|
|
48
|
+
return "undefined";
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return `[${value.map(canonicalToolValue).join(",")}]`;
|
|
51
|
+
if (typeof value === "object") {
|
|
52
|
+
return `{${Object.entries(value)
|
|
53
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
54
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalToolValue(entry)}`)
|
|
55
|
+
.join(",")}}`;
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
function repeatedToolBatchFingerprint(toolCalls, results) {
|
|
60
|
+
// Hash immediately and never log the canonical source: tool results may
|
|
61
|
+
// contain credentials or large media payloads. Call ids are deliberately
|
|
62
|
+
// omitted because providers generate a fresh id for every identical retry.
|
|
63
|
+
return createHash("sha256")
|
|
64
|
+
.update(canonicalToolValue({
|
|
65
|
+
calls: toolCalls.map((call) => ({ toolName: call.toolName, args: call.args })),
|
|
66
|
+
results: results.map((result) => ({
|
|
67
|
+
toolName: result.toolName,
|
|
68
|
+
isError: result.isError === true || Boolean(result.error),
|
|
69
|
+
error: result.error,
|
|
70
|
+
result: result.result,
|
|
71
|
+
contentBlocks: result.contentBlocks,
|
|
72
|
+
})),
|
|
73
|
+
}))
|
|
74
|
+
.digest("hex");
|
|
75
|
+
}
|
|
43
76
|
export class TurnLoop {
|
|
44
77
|
deps;
|
|
45
78
|
config;
|
|
@@ -76,6 +109,9 @@ export class TurnLoop {
|
|
|
76
109
|
* loop forces a stop so a stuck goal can't loop forever.
|
|
77
110
|
*/
|
|
78
111
|
stopBlockCount = 0;
|
|
112
|
+
/** Consecutive identical tool-call/result batches, ignoring provider call ids. */
|
|
113
|
+
repeatedToolBatchFingerprint;
|
|
114
|
+
repeatedToolBatchCount = 0;
|
|
79
115
|
/**
|
|
80
116
|
* Run-scoped goal budget tracker (Goal mode). Hoisted to an instance field
|
|
81
117
|
* (not a run() local) so extend() can bump its budgets mid-run. Null when no
|
|
@@ -1409,6 +1445,49 @@ export class TurnLoop {
|
|
|
1409
1445
|
tlog.info("guard.stale_task", { cat: "guard", turn: this.turnCount });
|
|
1410
1446
|
}
|
|
1411
1447
|
}
|
|
1448
|
+
const toolBatchFingerprint = repeatedToolBatchFingerprint(toolCalls, results);
|
|
1449
|
+
if (toolBatchFingerprint === this.repeatedToolBatchFingerprint) {
|
|
1450
|
+
this.repeatedToolBatchCount++;
|
|
1451
|
+
}
|
|
1452
|
+
else {
|
|
1453
|
+
this.repeatedToolBatchFingerprint = toolBatchFingerprint;
|
|
1454
|
+
this.repeatedToolBatchCount = 1;
|
|
1455
|
+
}
|
|
1456
|
+
if (this.repeatedToolBatchCount >= REPEATED_TOOL_BATCH_LIMIT) {
|
|
1457
|
+
tlog.warn("turn.repeated_tool_batch_stopped", {
|
|
1458
|
+
cat: "turn",
|
|
1459
|
+
repeatedCount: this.repeatedToolBatchCount,
|
|
1460
|
+
tools: toolCalls.map((call) => call.toolName),
|
|
1461
|
+
});
|
|
1462
|
+
await this.emitHook("on_turn_end", {
|
|
1463
|
+
turnNumber: this.turnCount,
|
|
1464
|
+
hasToolUse: true,
|
|
1465
|
+
toolCallCount: toolCalls.length,
|
|
1466
|
+
});
|
|
1467
|
+
finalText =
|
|
1468
|
+
`检测到同一组工具调用及其结果连续重复 ${REPEATED_TOOL_BATCH_LIMIT} 次,` +
|
|
1469
|
+
"已自动停止,避免继续空转。请调整请求或让 Session 获取新的上下文后再试。";
|
|
1470
|
+
this.deps.transcript.appendMessage("assistant", finalText);
|
|
1471
|
+
messages.push({ role: "assistant", content: finalText });
|
|
1472
|
+
this.config.onStream?.({
|
|
1473
|
+
type: "assistant_message",
|
|
1474
|
+
messageId: assistantMessageId,
|
|
1475
|
+
message: { role: "assistant", content: finalText },
|
|
1476
|
+
});
|
|
1477
|
+
this.finalizeModelTurn();
|
|
1478
|
+
if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
|
|
1479
|
+
this.repeatedToolBatchFingerprint = undefined;
|
|
1480
|
+
this.repeatedToolBatchCount = 0;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
messages = this.redactConsumedSensitiveToolResults(messages);
|
|
1484
|
+
return {
|
|
1485
|
+
text: finalText,
|
|
1486
|
+
reason: "completed",
|
|
1487
|
+
messages,
|
|
1488
|
+
completionKind: "limit_stop",
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1412
1491
|
// Hook: turn end
|
|
1413
1492
|
await this.emitHook("on_turn_end", {
|
|
1414
1493
|
turnNumber: this.turnCount,
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export declare const VERSION = "0.8.
|
|
6
|
+
export declare const VERSION = "0.8.3";
|
|
7
7
|
export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
|
|
8
8
|
export type { GoalConfig, GoalLifecycleConfig, GoalLifecyclePhase, GoalLifecycleTerminalReason, GoalLifecycleV1, } from "./goal/lifecycle.js";
|
|
9
9
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export const VERSION = "0.8.
|
|
6
|
+
export const VERSION = "0.8.3";
|
|
7
7
|
// ─── Exceptions ──────────────────────────────────────────────────
|
|
8
8
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
|
9
9
|
// ─── Engine (primary API) ────────────────────────────────────────
|
|
@@ -145,6 +145,52 @@ export async function runStreamWithWatchdog(stream, opts = {}) {
|
|
|
145
145
|
}
|
|
146
146
|
return text;
|
|
147
147
|
}
|
|
148
|
+
const MISSING_TOOL_RESULT_WIRE_TEXT = "Error: Tool execution did not complete before the conversation resumed.";
|
|
149
|
+
/**
|
|
150
|
+
* OpenAI requires each assistant tool_calls batch to be followed immediately
|
|
151
|
+
* by exactly one role:tool message per id. Normalize at the provider boundary
|
|
152
|
+
* as a final guard against legacy/corrupt transcripts: keep the latest result
|
|
153
|
+
* for a duplicate id, synthesize a request-local result for a missing id, and
|
|
154
|
+
* discard tool messages that were never declared by the preceding assistant.
|
|
155
|
+
*/
|
|
156
|
+
function normalizeOpenAIToolMessagePairs(messages) {
|
|
157
|
+
const normalized = [];
|
|
158
|
+
for (let index = 0; index < messages.length; index++) {
|
|
159
|
+
const message = messages[index];
|
|
160
|
+
if (message.role === "tool")
|
|
161
|
+
continue;
|
|
162
|
+
normalized.push(message);
|
|
163
|
+
if (message.role !== "assistant")
|
|
164
|
+
continue;
|
|
165
|
+
const toolCalls = message.tool_calls;
|
|
166
|
+
const expectedIds = Array.isArray(toolCalls)
|
|
167
|
+
? toolCalls
|
|
168
|
+
.map((toolCall) => toolCall.id)
|
|
169
|
+
.filter((id) => typeof id === "string" && id.length > 0)
|
|
170
|
+
: [];
|
|
171
|
+
if (expectedIds.length === 0)
|
|
172
|
+
continue;
|
|
173
|
+
const expected = new Set(expectedIds);
|
|
174
|
+
const latestResultById = new Map();
|
|
175
|
+
let cursor = index + 1;
|
|
176
|
+
while (cursor < messages.length && messages[cursor]?.role === "tool") {
|
|
177
|
+
const toolMessage = messages[cursor];
|
|
178
|
+
if (expected.has(toolMessage.tool_call_id)) {
|
|
179
|
+
latestResultById.set(toolMessage.tool_call_id, toolMessage);
|
|
180
|
+
}
|
|
181
|
+
cursor++;
|
|
182
|
+
}
|
|
183
|
+
for (const id of expectedIds) {
|
|
184
|
+
normalized.push(latestResultById.get(id) ?? {
|
|
185
|
+
role: "tool",
|
|
186
|
+
tool_call_id: id,
|
|
187
|
+
content: MISSING_TOOL_RESULT_WIRE_TEXT,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
index = cursor - 1;
|
|
191
|
+
}
|
|
192
|
+
return normalized;
|
|
193
|
+
}
|
|
148
194
|
export class OpenAIClient extends LLMClientBase {
|
|
149
195
|
_client = null;
|
|
150
196
|
// Sticky override: once the endpoint tells us `max_tokens` is rejected for
|
|
@@ -839,10 +885,11 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
839
885
|
}
|
|
840
886
|
}
|
|
841
887
|
}
|
|
888
|
+
const normalized = normalizeOpenAIToolMessagePairs(result);
|
|
842
889
|
if (this.isOpenRouterAnthropic) {
|
|
843
|
-
this.applyAnthropicCacheBreakpoints(
|
|
890
|
+
this.applyAnthropicCacheBreakpoints(normalized);
|
|
844
891
|
}
|
|
845
|
-
return
|
|
892
|
+
return normalized;
|
|
846
893
|
}
|
|
847
894
|
/**
|
|
848
895
|
* In-place: add prompt-cache breakpoints for Anthropic-over-OpenRouter.
|
|
@@ -190,6 +190,14 @@ export class ChatSessionManager {
|
|
|
190
190
|
return alreadyClosing;
|
|
191
191
|
const s = this.sessions.get(sessionId);
|
|
192
192
|
if (!s) {
|
|
193
|
+
if (sessionId.startsWith("qchat-")) {
|
|
194
|
+
try {
|
|
195
|
+
this.engineSessionManager(this.factory({}))?.forgetEphemeralSession?.(sessionId);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Closing an already-expired process-local chat is idempotent.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
193
201
|
if (markClosed)
|
|
194
202
|
this.rememberClosedSession(sessionId);
|
|
195
203
|
return Promise.resolve();
|
|
@@ -204,6 +212,7 @@ export class ChatSessionManager {
|
|
|
204
212
|
clearCredentialSessionAllow(sessionId);
|
|
205
213
|
clearInjectCredentialSessionAllow(sessionId);
|
|
206
214
|
const finishClose = () => {
|
|
215
|
+
sessionManager?.forgetEphemeralSession?.(sessionId);
|
|
207
216
|
this.unregisterMcpOwner(s);
|
|
208
217
|
if (this.sessions.get(sessionId) === s)
|
|
209
218
|
this.sessions.delete(sessionId);
|
package/dist/run/EngineRunner.js
CHANGED
|
@@ -27,10 +27,11 @@ export const AUTOMATION_RUN_SOURCE = "automation";
|
|
|
27
27
|
export const AUTOMATION_PROMPT_NOTE = "This is an unattended, scheduled automation run. No human is watching, and " +
|
|
28
28
|
"AskUserQuestion will not reach anyone. You ARE the automation — do not ask " +
|
|
29
29
|
"the user questions and do not offer to set up or schedule automation. " +
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
30
|
+
"When uncertain, state your assumption and proceed. When the work is ready, " +
|
|
31
|
+
"first call UpdateAutomationMemory exactly once with a concise summary of this " +
|
|
32
|
+
"run's key findings/state for the next run. After that call succeeds, return the " +
|
|
33
|
+
"complete requested output as the final assistant message. Do not replace the " +
|
|
34
|
+
"requested output with a completion acknowledgement or put it in a formatting tool.";
|
|
34
35
|
/** Compose the run's appendSystemPrompt: prepend the automation note when the
|
|
35
36
|
* run is tagged source "automation", preserving any host-provided append. */
|
|
36
37
|
export function buildAppendSystemPrompt(hostAppend, metadata) {
|
|
@@ -18,7 +18,7 @@ export interface ForkSessionOptions {
|
|
|
18
18
|
throughEventId?: string;
|
|
19
19
|
/** `completed` is the interrupted snapshot used by ephemeral side chats. */
|
|
20
20
|
snapshotMode?: "tail" | "completed";
|
|
21
|
-
/**
|
|
21
|
+
/** Keep this temporary fork in process memory only. */
|
|
22
22
|
ephemeral?: boolean;
|
|
23
23
|
}
|
|
24
24
|
export interface ForkSessionResult {
|
|
@@ -94,24 +94,22 @@ export declare class SessionManager {
|
|
|
94
94
|
private readonly registeredCloseEpochs;
|
|
95
95
|
private readonly workspaceCapability?;
|
|
96
96
|
constructor(storageDir?: string, workspaceCapability?: SessionWorkspaceCapability);
|
|
97
|
+
private processLocalKey;
|
|
98
|
+
private processLocalBundle;
|
|
99
|
+
private storeProcessLocalBundle;
|
|
100
|
+
/** Forget a Quick Chat/side-chat bundle immediately; nothing remains on disk. */
|
|
101
|
+
forgetEphemeralSession(sessionId: string): boolean;
|
|
97
102
|
private cleanupStaleForkStaging;
|
|
98
103
|
/** Bind one Engine/session pair to the current close epoch without advancing it. */
|
|
99
104
|
registerSessionGeneration(sessionId: string): number;
|
|
100
105
|
/** Advance the close epoch once before close waits for the old run to settle. */
|
|
101
106
|
incrementSessionGeneration(sessionId: string): number;
|
|
102
107
|
/**
|
|
103
|
-
* Create a
|
|
104
|
-
*
|
|
105
|
-
* "tui-main" and expect us to honor it). Otherwise generate one with
|
|
106
|
-
* nanoid. Either way the on-disk directory is materialized and the
|
|
107
|
-
* state.json + transcript.jsonl files are written before return.
|
|
108
|
+
* Create a session. `qchat-` sessions stay process-local; ordinary sessions
|
|
109
|
+
* materialize state.json + transcript.jsonl before return.
|
|
108
110
|
*/
|
|
109
111
|
create(cwd: string, model: string, provider: string, explicitSessionId?: string, parentSessionId?: string | null, origin?: import("../types.js").SessionOrigin, kind?: SessionKind): SessionBundle;
|
|
110
|
-
/**
|
|
111
|
-
* Whether a session directory exists on disk. Used by ChatSession-driven
|
|
112
|
-
* cold starts to decide between resume vs create-with-explicit-sid
|
|
113
|
-
* without catching SessionError.
|
|
114
|
-
*/
|
|
112
|
+
/** Whether a persisted or process-local session exists. */
|
|
115
113
|
exists(sessionId: string): boolean;
|
|
116
114
|
/**
|
|
117
115
|
* Cheap persisted-main-root probe — reads only state.json, NOT the transcript
|
|
@@ -142,10 +140,10 @@ export declare class SessionManager {
|
|
|
142
140
|
}): string[];
|
|
143
141
|
/** @deprecated Use readSessionMainRoot; retained for public API compatibility. */
|
|
144
142
|
readCwd(sessionId: string): string | undefined;
|
|
145
|
-
/**
|
|
143
|
+
/** Direct-parent ACL metadata. Undefined means unprovable/corrupt. */
|
|
146
144
|
readParentSessionId(sessionId: string): string | null | undefined;
|
|
147
145
|
/**
|
|
148
|
-
*
|
|
146
|
+
* Workspace pointer reader. Legacy sessions written before
|
|
149
147
|
* `workspace` existed are treated as main-workspace sessions rooted at
|
|
150
148
|
* `state.cwd`; the read is intentionally non-mutating.
|
|
151
149
|
*/
|
|
@@ -306,6 +304,7 @@ export declare class SessionManager {
|
|
|
306
304
|
/** Publish a summary-only top-level fork after summarization has succeeded. */
|
|
307
305
|
createSummaryFork(sourceSessionId: string, options: SummaryForkOptions): ForkSessionResult;
|
|
308
306
|
private readForkSnapshot;
|
|
307
|
+
private freezeForkSnapshot;
|
|
309
308
|
private publishSessionAtomically;
|
|
310
309
|
list(limit?: number, opts?: {
|
|
311
310
|
excludeKinds?: readonly string[];
|