Merge remote-tracking branch 'can1357/main' into fix/empty-stop-guard-tooluse
This commit is contained in:
@@ -91,6 +91,7 @@ import {
|
||||
extractRetryHint,
|
||||
getAgentDbPath,
|
||||
getInstallId,
|
||||
isBunTestRuntime,
|
||||
isEnoent,
|
||||
isUnexpectedSocketCloseMessage,
|
||||
logger,
|
||||
@@ -127,7 +128,6 @@ import {
|
||||
} from "../eval/py/executor";
|
||||
import { defaultEvalSessionId } from "../eval/session-id";
|
||||
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
||||
import { exportSessionToHtml } from "../export/html";
|
||||
import type { TtsrManager, TtsrMatchContext } from "../export/ttsr";
|
||||
import type { LoadedCustomCommand } from "../extensibility/custom-commands";
|
||||
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
||||
@@ -192,6 +192,7 @@ import {
|
||||
toReasoningEffort,
|
||||
} from "../thinking";
|
||||
import { shutdownTinyTitleClient } from "../tiny/title-client";
|
||||
import { countToolsForAutoDiscovery, resolveEffectiveToolDiscoveryMode } from "../tool-discovery/mode";
|
||||
import {
|
||||
buildDiscoverableToolSearchIndex,
|
||||
collectDiscoverableTools,
|
||||
@@ -470,6 +471,12 @@ export interface SessionStats {
|
||||
cost: number;
|
||||
}
|
||||
|
||||
export interface FreshSessionResult {
|
||||
previousSessionId: string;
|
||||
sessionId: string;
|
||||
closedProviderSessions: number;
|
||||
}
|
||||
|
||||
/** Internal marker for hook messages queued through the agent loop */
|
||||
// ============================================================================
|
||||
// Constants
|
||||
@@ -921,6 +928,7 @@ export class AgentSession {
|
||||
#agentId: string | undefined;
|
||||
#agentRegistry: AgentRegistry | undefined;
|
||||
#providerSessionId: string | undefined;
|
||||
#freshProviderSessionId: string | undefined;
|
||||
#isDisposed = false;
|
||||
// Extension system
|
||||
#extensionRunner: ExtensionRunner | undefined = undefined;
|
||||
@@ -1035,6 +1043,7 @@ export class AgentSession {
|
||||
|
||||
#acquirePowerAssertion(): void {
|
||||
if (process.platform !== "darwin") return;
|
||||
if (isBunTestRuntime()) return;
|
||||
if (this.#powerAssertion) return;
|
||||
const idle = this.settings.get("power.preventIdleSleep");
|
||||
const system = this.settings.get("power.preventSystemSleep");
|
||||
@@ -1273,6 +1282,14 @@ export class AgentSession {
|
||||
return this.#modelRegistry;
|
||||
}
|
||||
|
||||
get asyncJobManager(): AsyncJobManager | undefined {
|
||||
return this.#asyncJobManager;
|
||||
}
|
||||
|
||||
getAgentId(): string | undefined {
|
||||
return this.#agentId;
|
||||
}
|
||||
|
||||
/** Advance the tool-choice queue and return the next directive for the upcoming LLM call. */
|
||||
nextToolChoice(): ToolChoice | undefined {
|
||||
return this.#toolChoiceQueue.nextToolChoice();
|
||||
@@ -1679,7 +1696,7 @@ export class AgentSession {
|
||||
// Abort the stream immediately — do not gate on extension callbacks
|
||||
this.#ttsrAbortPending = true;
|
||||
this.#ensureTtsrResumePromise();
|
||||
this.agent.abort();
|
||||
this.agent.abort(this.#formatTtsrAbortReason(matches));
|
||||
// Notify extensions (fire-and-forget, does not block abort)
|
||||
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
||||
// Schedule retry after a short delay
|
||||
@@ -2160,6 +2177,12 @@ export class AgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
#formatTtsrAbortReason(rules: Rule[]): string {
|
||||
const label = rules.length === 1 ? "rule" : "rules";
|
||||
const ruleNames = rules.map(rule => rule.name).join(", ");
|
||||
return `TTSR matched ${label}: ${ruleNames}`;
|
||||
}
|
||||
|
||||
/** Get TTSR injection payload and clear pending injections. */
|
||||
#getTtsrInjectionContent(): { content: string; rules: Rule[] } | undefined {
|
||||
if (this.#pendingTtsrInjections.length === 0) return undefined;
|
||||
@@ -2183,13 +2206,20 @@ export class AgentSession {
|
||||
* project, `~`-relative when it lives under home, else the raw path.
|
||||
*/
|
||||
#displayRulePath(rulePath: string): string {
|
||||
const cwdRel = relativePathWithinRoot(this.sessionManager.getCwd(), rulePath);
|
||||
const cwdRel =
|
||||
relativePathWithinRoot(this.sessionManager.getCwd(), rulePath) ??
|
||||
this.#displayPathWithinRoot(this.sessionManager.getCwd(), rulePath);
|
||||
if (cwdRel) return cwdRel;
|
||||
const homeRel = relativePathWithinRoot(os.homedir(), rulePath);
|
||||
if (homeRel) return `~/${homeRel}`;
|
||||
return rulePath;
|
||||
}
|
||||
|
||||
#displayPathWithinRoot(root: string, candidate: string): string | null {
|
||||
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
||||
return relative && !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : null;
|
||||
}
|
||||
|
||||
#addPendingTtsrInjections(rules: Rule[]): void {
|
||||
const seen = new Set(this.#pendingTtsrInjections.map(rule => rule.name));
|
||||
for (const rule of rules) {
|
||||
@@ -2944,6 +2974,10 @@ export class AgentSession {
|
||||
this.#unsubscribeAgent = this.agent.subscribe(this.#handleAgentEvent);
|
||||
}
|
||||
|
||||
#activeProviderSessionId(sessionId?: string): string {
|
||||
return this.#freshProviderSessionId ?? this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set agent.sessionId from the session manager and install a dynamic
|
||||
* metadata resolver so every Anthropic API request carries
|
||||
@@ -2956,7 +2990,7 @@ export class AgentSession {
|
||||
* `#syncAgentSessionId()` on every such event.
|
||||
*/
|
||||
#syncAgentSessionId(sessionId?: string): void {
|
||||
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
||||
const sid = this.#activeProviderSessionId(sessionId);
|
||||
this.agent.sessionId = sid;
|
||||
this.agent.setMetadataResolver((provider: string) =>
|
||||
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
|
||||
@@ -2964,14 +2998,14 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
#rekeyHindsightMemoryForCurrentSessionId(): void {
|
||||
if (resolveMemoryBackend(this.settings).id !== "hindsight") return;
|
||||
if (this.settings.get("memory.backend") !== "hindsight") return;
|
||||
const sid = this.agent.sessionId;
|
||||
if (!sid) return;
|
||||
this.getHindsightSessionState()?.setSessionId(sid);
|
||||
}
|
||||
|
||||
#rekeyMnemopiMemoryForCurrentSessionId(): void {
|
||||
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
||||
if (this.settings.get("memory.backend") !== "mnemopi") return;
|
||||
const sid = this.agent.sessionId;
|
||||
if (!sid) return;
|
||||
this.getMnemopiSessionState()?.setSessionId(sid);
|
||||
@@ -2979,14 +3013,14 @@ export class AgentSession {
|
||||
|
||||
/** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
|
||||
#resetHindsightConversationTrackingIfHindsight(): void {
|
||||
if (resolveMemoryBackend(this.settings).id !== "hindsight") return;
|
||||
if (this.settings.get("memory.backend") !== "hindsight") return;
|
||||
const state = this.getHindsightSessionState();
|
||||
if (!state || state.aliasOf) return;
|
||||
state.resetConversationTracking();
|
||||
}
|
||||
|
||||
#resetMnemopiConversationTrackingIfMnemopi(): void {
|
||||
if (resolveMemoryBackend(this.settings).id !== "mnemopi") return;
|
||||
if (this.settings.get("memory.backend") !== "mnemopi") return;
|
||||
const state = this.getMnemopiSessionState();
|
||||
if (!state || state.aliasOf) return;
|
||||
state.resetConversationTracking();
|
||||
@@ -3086,6 +3120,23 @@ export class AgentSession {
|
||||
this.#providerSessionState.clear();
|
||||
}
|
||||
|
||||
freshSession(): FreshSessionResult | undefined {
|
||||
if (this.isStreaming) return undefined;
|
||||
const previousSessionId = this.sessionId;
|
||||
const closedProviderSessions = this.#providerSessionState.size;
|
||||
this.#closeAllProviderSessions("fresh session");
|
||||
this.#freshProviderSessionId = Bun.randomUUIDv7();
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
this.agent.appendOnlyContext?.invalidateForModelChange();
|
||||
return {
|
||||
previousSessionId,
|
||||
sessionId: this.sessionId,
|
||||
closedProviderSessions,
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Read-only State Access
|
||||
// =========================================================================
|
||||
@@ -3325,12 +3376,14 @@ export class AgentSession {
|
||||
|
||||
// ── Generic tool discovery (covers built-in + MCP + extension) ────────────
|
||||
|
||||
/** Resolve effective discovery mode: tools.discoveryMode wins; mcp.discoveryMode is back-compat alias. */
|
||||
/** Resolve effective discovery mode from the current registry size. */
|
||||
#resolveEffectiveDiscoveryMode(): "off" | "mcp-only" | "all" {
|
||||
const toolsMode = this.settings.get("tools.discoveryMode");
|
||||
if (toolsMode !== "off") return toolsMode as "off" | "mcp-only" | "all";
|
||||
if (this.settings.get("mcp.discoveryMode")) return "mcp-only";
|
||||
return "off";
|
||||
const mode = resolveEffectiveToolDiscoveryMode(
|
||||
this.settings,
|
||||
countToolsForAutoDiscovery(this.#toolRegistry.keys()),
|
||||
);
|
||||
if (mode !== "off") return mode;
|
||||
return this.#mcpDiscoveryEnabled ? "mcp-only" : "off";
|
||||
}
|
||||
|
||||
isToolDiscoveryEnabled(): boolean {
|
||||
@@ -3665,7 +3718,7 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
async #buildSystemPromptForAgentStart(promptText: string): Promise<string[]> {
|
||||
const backend = resolveMemoryBackend(this.settings);
|
||||
const backend = await resolveMemoryBackend(this.settings);
|
||||
if (!backend.beforeAgentStartPrompt) return this.#baseSystemPrompt;
|
||||
|
||||
try {
|
||||
@@ -3988,7 +4041,7 @@ export class AgentSession {
|
||||
|
||||
/** Current session ID */
|
||||
get sessionId(): string {
|
||||
return this.#providerSessionId ?? this.sessionManager.getSessionId();
|
||||
return this.#activeProviderSessionId();
|
||||
}
|
||||
getEvalSessionId(): string | null {
|
||||
if (this.#parentEvalSessionId !== undefined) return this.#parentEvalSessionId;
|
||||
@@ -5087,8 +5140,13 @@ export class AgentSession {
|
||||
|
||||
/**
|
||||
* Abort current operation and wait for agent to become idle.
|
||||
*
|
||||
* `reason` (e.g. `USER_INTERRUPT_LABEL`) rides the agent's `AbortController`
|
||||
* and surfaces verbatim on the aborted assistant message's `errorMessage`, so
|
||||
* the transcript can distinguish a deliberate user interrupt from an opaque
|
||||
* abort. Omit it for internal/lifecycle aborts.
|
||||
*/
|
||||
async abort(options?: { goalReason?: "interrupted" | "internal" }): Promise<void> {
|
||||
async abort(options?: { goalReason?: "interrupted" | "internal"; reason?: string }): Promise<void> {
|
||||
this.abortRetry();
|
||||
this.#promptGeneration++;
|
||||
this.#scheduledHiddenNextTurnGeneration = undefined;
|
||||
@@ -5097,7 +5155,7 @@ export class AgentSession {
|
||||
this.abortBash();
|
||||
this.abortEval();
|
||||
const postPromptDrain = this.#cancelPostPromptTasks();
|
||||
this.agent.abort();
|
||||
this.agent.abort(options?.reason);
|
||||
await postPromptDrain;
|
||||
await this.agent.waitForIdle();
|
||||
await this.#goalRuntime.onTaskAborted({ reason: options?.goalReason ?? "interrupted" });
|
||||
@@ -5114,6 +5172,19 @@ export class AgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort active work, then immediately resume the agent so queued steer/follow-up
|
||||
* messages drain instead of waiting for another natural turn boundary.
|
||||
*/
|
||||
async interruptAndFlushQueuedMessages(options?: { reason?: string }): Promise<void> {
|
||||
if (!this.agent.hasQueuedMessages()) return;
|
||||
await this.abort({ reason: options?.reason });
|
||||
if (!this.agent.hasQueuedMessages()) return;
|
||||
if (this.isCompacting || this.isGeneratingHandoff) return;
|
||||
await this.#maybeRestoreRetryFallbackPrimary();
|
||||
await this.agent.continue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new session, optionally with initial messages and parent tracking.
|
||||
* Clears all messages and starts a new session.
|
||||
@@ -5158,6 +5229,7 @@ export class AgentSession {
|
||||
}
|
||||
await this.sessionManager.newSession(options);
|
||||
this.setTodoPhases([]);
|
||||
this.#freshProviderSessionId = undefined;
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -5255,6 +5327,7 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
// Update agent session ID
|
||||
this.#freshProviderSessionId = undefined;
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -6091,7 +6164,7 @@ export class AgentSession {
|
||||
messagesToSummarize: AgentMessage[];
|
||||
turnPrefixMessages: AgentMessage[];
|
||||
}): Promise<string | undefined> {
|
||||
const backend = resolveMemoryBackend(this.settings);
|
||||
const backend = await resolveMemoryBackend(this.settings);
|
||||
if (!backend.preCompactionContext) return undefined;
|
||||
const messages = preparation.messagesToSummarize.concat(preparation.turnPrefixMessages);
|
||||
try {
|
||||
@@ -6222,6 +6295,7 @@ export class AgentSession {
|
||||
this.#cancelOwnAsyncJobs();
|
||||
await this.sessionManager.newSession(previousSessionFile ? { parentSession: previousSessionFile } : undefined);
|
||||
this.agent.reset();
|
||||
this.#freshProviderSessionId = undefined;
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -8150,8 +8224,10 @@ export class AgentSession {
|
||||
|
||||
const currentSelector = this.model ? formatRetryFallbackSelector(this.model, this.thinkingLevel) : undefined;
|
||||
if (!switchedCredential && currentSelector) {
|
||||
this.#noteRetryFallbackCooldown(currentSelector, parsedRetryAfterMs, errorMessage);
|
||||
switchedModel = await this.#tryRetryModelFallback(currentSelector);
|
||||
if (retrySettings.modelFallback) {
|
||||
this.#noteRetryFallbackCooldown(currentSelector, parsedRetryAfterMs, errorMessage);
|
||||
switchedModel = await this.#tryRetryModelFallback(currentSelector);
|
||||
}
|
||||
if (switchedModel) {
|
||||
delayMs = 0;
|
||||
} else if (parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
|
||||
@@ -8952,6 +9028,7 @@ export class AgentSession {
|
||||
const previousTools = [...this.agent.state.tools];
|
||||
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
||||
const previousSystemPrompt = this.agent.state.systemPrompt;
|
||||
const previousFreshProviderSessionId = this.#freshProviderSessionId;
|
||||
const previousFallbackSelectedMCPToolNames = previousSessionFile
|
||||
? this.#getSessionDefaultSelectedMCPToolNames(previousSessionFile)
|
||||
: undefined;
|
||||
@@ -8963,6 +9040,9 @@ export class AgentSession {
|
||||
|
||||
try {
|
||||
await this.sessionManager.setSessionFile(sessionPath);
|
||||
if (switchingToDifferentSession) {
|
||||
this.#freshProviderSessionId = undefined;
|
||||
}
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -9072,6 +9152,7 @@ export class AgentSession {
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.sessionManager.restoreState(previousSessionState);
|
||||
this.#freshProviderSessionId = previousFreshProviderSessionId;
|
||||
this.#syncAgentSessionId(previousSessionState.sessionId);
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -9170,6 +9251,7 @@ export class AgentSession {
|
||||
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
||||
}
|
||||
this.#syncTodoPhasesFromBranch();
|
||||
this.#freshProviderSessionId = undefined;
|
||||
this.#syncAgentSessionId();
|
||||
this.#rekeyHindsightMemoryForCurrentSessionId();
|
||||
this.#rekeyMnemopiMemoryForCurrentSessionId();
|
||||
@@ -9598,6 +9680,7 @@ export class AgentSession {
|
||||
*/
|
||||
async exportToHtml(outputPath?: string): Promise<string> {
|
||||
const themeName = getCurrentThemeName();
|
||||
const { exportSessionToHtml } = await import("../export/html");
|
||||
return exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName });
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,32 @@ export function isSilentAbort(errorMessage: string | undefined): boolean {
|
||||
return errorMessage === SILENT_ABORT_MARKER;
|
||||
}
|
||||
|
||||
/** Reason threaded through `AbortController.abort(reason)` when the user aborts
|
||||
* the turn with Esc (see `AgentSession.abort`). The agent surfaces it verbatim
|
||||
* on the aborted assistant message's `errorMessage`, so the transcript reads as
|
||||
* a deliberate user interrupt instead of an opaque failure. */
|
||||
export const USER_INTERRUPT_LABEL = "Interrupted by user";
|
||||
|
||||
/** Sentinel `errorMessage` the agent stamps on any abort that carried no custom
|
||||
* reason (bare `abort()`). Renderers treat it as "no specific reason given". */
|
||||
const GENERIC_ABORT_SENTINEL = "Request was aborted";
|
||||
|
||||
/** Resolve the operator-facing label for an aborted assistant turn. A custom
|
||||
* abort reason (e.g. `USER_INTERRUPT_LABEL`) threaded onto `errorMessage` is
|
||||
* shown verbatim; aborts with no threaded reason fall back to the retry-aware
|
||||
* generic label. Centralizes the live-stream (`EventController`), replay
|
||||
* (`ui-helpers`), and component (`AssistantMessageComponent`) render paths so
|
||||
* they stay in lockstep. */
|
||||
export function resolveAbortLabel(errorMessage: string | undefined, retryAttempt = 0): string {
|
||||
if (errorMessage && errorMessage !== GENERIC_ABORT_SENTINEL && !isSilentAbort(errorMessage)) {
|
||||
return errorMessage;
|
||||
}
|
||||
if (retryAttempt > 0) {
|
||||
return `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`;
|
||||
}
|
||||
return "Operation aborted";
|
||||
}
|
||||
|
||||
/** Extract the optional `__pendingDisplayTag` field from a CustomMessage's
|
||||
* `details` blob. Safe over `unknown`; returns undefined when the field is
|
||||
* absent or non-string. */
|
||||
|
||||
@@ -1967,6 +1967,7 @@ export class SessionManager {
|
||||
#inMemoryArtifacts: Map<string, string> | null = null;
|
||||
#inMemoryArtifactCounter = 0;
|
||||
readonly #blobStore: BlobStore;
|
||||
#suppressBreadcrumb = false;
|
||||
|
||||
private constructor(
|
||||
private cwd: string,
|
||||
@@ -1981,6 +1982,11 @@ export class SessionManager {
|
||||
// Note: call _initSession() or _initSessionFile() after construction
|
||||
}
|
||||
|
||||
#maybeWriteBreadcrumb(cwd: string, sessionFile: string): void {
|
||||
if (this.#suppressBreadcrumb) return;
|
||||
writeTerminalBreadcrumb(cwd, sessionFile);
|
||||
}
|
||||
|
||||
/** Puts a binary blob into the blob store and returns the blob reference */
|
||||
async putBlob(data: Buffer, options?: BlobPutOptions): Promise<BlobPutResult> {
|
||||
return this.#blobStore.put(data, options);
|
||||
@@ -2027,7 +2033,7 @@ export class SessionManager {
|
||||
this.#adoptedArtifactManager = null;
|
||||
this.#buildIndex();
|
||||
if (this.#sessionFile) {
|
||||
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
||||
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2047,7 +2053,7 @@ export class SessionManager {
|
||||
this.#persistError = undefined;
|
||||
this.#persistErrorReported = false;
|
||||
this.#sessionFile = path.resolve(sessionFile);
|
||||
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
||||
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
||||
this.#fileEntries = await loadEntriesFromFile(this.#sessionFile, this.storage);
|
||||
if (this.#fileEntries.length > 0) {
|
||||
const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
|
||||
@@ -2064,7 +2070,7 @@ export class SessionManager {
|
||||
if (headerCwd && headerCwd !== this.cwd) {
|
||||
this.cwd = headerCwd;
|
||||
this.sessionDir = path.resolve(this.#sessionFile, "..");
|
||||
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
||||
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
||||
}
|
||||
|
||||
this.#needsFullRewriteOnNextPersist = migrateToCurrentVersion(this.#fileEntries);
|
||||
@@ -2245,7 +2251,7 @@ export class SessionManager {
|
||||
|
||||
// Update terminal breadcrumb
|
||||
if (this.#sessionFile) {
|
||||
writeTerminalBreadcrumb(resolvedCwd, this.#sessionFile);
|
||||
this.#maybeWriteBreadcrumb(resolvedCwd, this.#sessionFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2280,7 +2286,7 @@ export class SessionManager {
|
||||
if (this.persist) {
|
||||
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
||||
this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
|
||||
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
||||
this.#maybeWriteBreadcrumb(this.cwd, this.#sessionFile);
|
||||
}
|
||||
return this.#sessionFile;
|
||||
}
|
||||
@@ -3429,9 +3435,11 @@ export class SessionManager {
|
||||
cwd: string,
|
||||
sessionDir?: string,
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
options?: { suppressBreadcrumb?: boolean },
|
||||
): Promise<SessionManager> {
|
||||
const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
|
||||
const manager = new SessionManager(cwd, dir, true, storage);
|
||||
manager.#suppressBreadcrumb = options?.suppressBreadcrumb === true;
|
||||
const forkEntries = structuredClone(await loadEntriesFromFile(sourcePath, storage)) as FileEntry[];
|
||||
migrateToCurrentVersion(forkEntries);
|
||||
await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
|
||||
|
||||
Reference in New Issue
Block a user