@kolisachint/hoocode-agent 0.4.108 → 0.4.109
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/CHANGELOG.md +2 -0
- package/dist/core/agent-session-compaction.d.ts +79 -0
- package/dist/core/agent-session-compaction.d.ts.map +1 -0
- package/dist/core/agent-session-compaction.js +346 -0
- package/dist/core/agent-session-compaction.js.map +1 -0
- package/dist/core/agent-session-retry.d.ts +76 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -0
- package/dist/core/agent-session-retry.js +192 -0
- package/dist/core/agent-session-retry.js.map +1 -0
- package/dist/core/agent-session-skills.d.ts +34 -0
- package/dist/core/agent-session-skills.d.ts.map +1 -0
- package/dist/core/agent-session-skills.js +52 -0
- package/dist/core/agent-session-skills.js.map +1 -0
- package/dist/core/agent-session-stats.d.ts +74 -0
- package/dist/core/agent-session-stats.d.ts.map +1 -0
- package/dist/core/agent-session-stats.js +187 -0
- package/dist/core/agent-session-stats.js.map +1 -0
- package/dist/core/agent-session-tree-navigation.d.ts +69 -0
- package/dist/core/agent-session-tree-navigation.d.ts.map +1 -0
- package/dist/core/agent-session-tree-navigation.js +198 -0
- package/dist/core/agent-session-tree-navigation.js.map +1 -0
- package/dist/core/agent-session.d.ts +10 -66
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +96 -806
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/context-files.d.ts +25 -0
- package/dist/core/context-files.d.ts.map +1 -0
- package/dist/core/context-files.js +97 -0
- package/dist/core/context-files.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +3 -519
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-resource-discovery.d.ts +62 -0
- package/dist/core/package-resource-discovery.d.ts.map +1 -0
- package/dist/core/package-resource-discovery.js +530 -0
- package/dist/core/package-resource-discovery.js.map +1 -0
- package/dist/core/resource-loader.d.ts +1 -10
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -83
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -140
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -81
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-storage.d.ts +29 -0
- package/dist/core/settings-storage.d.ts.map +1 -0
- package/dist/core/settings-storage.js +90 -0
- package/dist/core/settings-storage.js.map +1 -0
- package/dist/core/settings-types.d.ts +128 -0
- package/dist/core/settings-types.d.ts.map +1 -0
- package/dist/core/settings-types.js +9 -0
- package/dist/core/settings-types.js.map +1 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-navigation controller for AgentSession.
|
|
3
|
+
*
|
|
4
|
+
* Handles navigating to a different node in the session tree (staying in the
|
|
5
|
+
* same session file, unlike fork). When the user opts to summarize the
|
|
6
|
+
* abandoned branch it runs the `session_before_tree` hook, generates a branch
|
|
7
|
+
* summary (from an extension or the default summarizer), attaches it at the new
|
|
8
|
+
* leaf, refreshes agent context, and emits `session_tree`. Owns the branch
|
|
9
|
+
* summarization abort controller. Extracted from agent-session.ts behind a
|
|
10
|
+
* narrow TreeNavigationControllerDeps interface.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentMessage } from "@kolisachint/hoocode-agent-core";
|
|
13
|
+
import type { Model } from "@kolisachint/hoocode-ai";
|
|
14
|
+
import type { ExtensionRunner } from "./extensions/index.js";
|
|
15
|
+
import type { BranchSummaryEntry, SessionManager } from "./session-manager.js";
|
|
16
|
+
import type { SettingsManager } from "./settings-manager.js";
|
|
17
|
+
/** Options for navigating the session tree. */
|
|
18
|
+
export interface NavigateTreeOptions {
|
|
19
|
+
/** Whether the user wants to summarize the abandoned branch */
|
|
20
|
+
summarize?: boolean;
|
|
21
|
+
/** Custom instructions for the summarizer */
|
|
22
|
+
customInstructions?: string;
|
|
23
|
+
/** If true, customInstructions replaces the default prompt */
|
|
24
|
+
replaceInstructions?: boolean;
|
|
25
|
+
/** Label to attach to the branch summary entry */
|
|
26
|
+
label?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Result of navigating the session tree. */
|
|
29
|
+
export interface NavigateTreeResult {
|
|
30
|
+
editorText?: string;
|
|
31
|
+
cancelled: boolean;
|
|
32
|
+
aborted?: boolean;
|
|
33
|
+
summaryEntry?: BranchSummaryEntry;
|
|
34
|
+
}
|
|
35
|
+
/** Narrow dependencies the tree-navigation controller needs from AgentSession. */
|
|
36
|
+
export interface TreeNavigationControllerDeps {
|
|
37
|
+
sessionManager: SessionManager;
|
|
38
|
+
settingsManager: SettingsManager;
|
|
39
|
+
getModel(): Model<any> | undefined;
|
|
40
|
+
/** Read at call time; the extension runner is swapped on reload. */
|
|
41
|
+
getExtensionRunner(): ExtensionRunner;
|
|
42
|
+
getRequiredRequestAuth(model: Model<any>): Promise<{
|
|
43
|
+
apiKey: string;
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
}>;
|
|
46
|
+
setAgentMessages(messages: AgentMessage[]): void;
|
|
47
|
+
}
|
|
48
|
+
export declare class TreeNavigationController {
|
|
49
|
+
private readonly deps;
|
|
50
|
+
private _branchSummaryAbortController;
|
|
51
|
+
constructor(deps: TreeNavigationControllerDeps);
|
|
52
|
+
/** Whether branch summarization is currently running */
|
|
53
|
+
get isSummarizing(): boolean;
|
|
54
|
+
/** Cancel in-progress branch summarization. */
|
|
55
|
+
abortBranchSummary(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Navigate to a different node in the session tree.
|
|
58
|
+
* Unlike fork() which creates a new session file, this stays in the same file.
|
|
59
|
+
*
|
|
60
|
+
* @param targetId The entry ID to navigate to
|
|
61
|
+
* @param options.summarize Whether user wants to summarize abandoned branch
|
|
62
|
+
* @param options.customInstructions Custom instructions for summarizer
|
|
63
|
+
* @param options.replaceInstructions If true, customInstructions replaces the default prompt
|
|
64
|
+
* @param options.label Label to attach to the branch summary entry
|
|
65
|
+
* @returns Result with editorText (if user message) and cancelled status
|
|
66
|
+
*/
|
|
67
|
+
navigateTree(targetId: string, options?: NavigateTreeOptions): Promise<NavigateTreeResult>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=agent-session-tree-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session-tree-navigation.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-tree-navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEpE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAA4C,MAAM,uBAAuB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6CAA6C;AAC7C,MAAM,WAAW,kBAAkB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,kFAAkF;AAClF,MAAM,WAAW,4BAA4B;IAC5C,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACnC,oEAAoE;IACpE,kBAAkB,IAAI,eAAe,CAAC;IACtC,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;IACzG,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;CACjD;AAED,qBAAa,wBAAwB;IAGxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,6BAA6B,CAA0C;IAE/E,YAA6B,IAAI,EAAE,4BAA4B,EAAI;IAEnE,wDAAwD;IACxD,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,+CAA+C;IAC/C,kBAAkB,IAAI,IAAI,CAEzB;IAED;;;;;;;;;;OAUG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAgLnG;CACD","sourcesContent":["/**\n * Tree-navigation controller for AgentSession.\n *\n * Handles navigating to a different node in the session tree (staying in the\n * same session file, unlike fork). When the user opts to summarize the\n * abandoned branch it runs the `session_before_tree` hook, generates a branch\n * summary (from an extension or the default summarizer), attaches it at the new\n * leaf, refreshes agent context, and emits `session_tree`. Owns the branch\n * summarization abort controller. Extracted from agent-session.ts behind a\n * narrow TreeNavigationControllerDeps interface.\n */\n\nimport type { AgentMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"@kolisachint/hoocode-agent-core\";\nimport type { Model } from \"@kolisachint/hoocode-ai\";\nimport { extractUserMessageText } from \"./agent-session-stats.js\";\nimport type { ExtensionRunner, SessionBeforeTreeResult, TreePreparation } from \"./extensions/index.js\";\nimport type { BranchSummaryEntry, SessionManager } from \"./session-manager.js\";\nimport type { SettingsManager } from \"./settings-manager.js\";\n\n/** Options for navigating the session tree. */\nexport interface NavigateTreeOptions {\n\t/** Whether the user wants to summarize the abandoned branch */\n\tsummarize?: boolean;\n\t/** Custom instructions for the summarizer */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt */\n\treplaceInstructions?: boolean;\n\t/** Label to attach to the branch summary entry */\n\tlabel?: string;\n}\n\n/** Result of navigating the session tree. */\nexport interface NavigateTreeResult {\n\teditorText?: string;\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryEntry?: BranchSummaryEntry;\n}\n\n/** Narrow dependencies the tree-navigation controller needs from AgentSession. */\nexport interface TreeNavigationControllerDeps {\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tgetModel(): Model<any> | undefined;\n\t/** Read at call time; the extension runner is swapped on reload. */\n\tgetExtensionRunner(): ExtensionRunner;\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey: string; headers?: Record<string, string> }>;\n\tsetAgentMessages(messages: AgentMessage[]): void;\n}\n\nexport class TreeNavigationController {\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\tconstructor(private readonly deps: TreeNavigationControllerDeps) {}\n\n\t/** Whether branch summarization is currently running */\n\tget isSummarizing(): boolean {\n\t\treturn this._branchSummaryAbortController !== undefined;\n\t}\n\n\t/** Cancel in-progress branch summarization. */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tasync navigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\tconst sessionManager = this.deps.sessionManager;\n\t\tconst extensionRunner = this.deps.getExtensionRunner();\n\t\tconst oldLeafId = sessionManager.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.deps.getModel()) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = sessionManager.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = await collectEntriesForBranchSummary(\n\t\t\tsessionManager,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tlet customInstructions = options.customInstructions;\n\t\tlet replaceInstructions = options.replaceInstructions;\n\t\tlet label = options.label;\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions,\n\t\t\treplaceInstructions,\n\t\t\tlabel,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tlet extensionSummary: { summary: string; details?: unknown } | undefined;\n\t\t\tlet fromExtension = false;\n\n\t\t\t// Emit session_before_tree event\n\t\t\tif (extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\t\tconst result = (await extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_tree\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t})) as SessionBeforeTreeResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t}\n\n\t\t\t\tif (result?.summary && options.summarize) {\n\t\t\t\t\textensionSummary = result.summary;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\n\t\t\t\t// Allow extensions to override instructions and label\n\t\t\t\tif (result?.customInstructions !== undefined) {\n\t\t\t\t\tcustomInstructions = result.customInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.replaceInstructions !== undefined) {\n\t\t\t\t\treplaceInstructions = result.replaceInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.label !== undefined) {\n\t\t\t\t\tlabel = result.label;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Run default summarizer if needed\n\t\t\tlet summaryText: string | undefined;\n\t\t\tlet summaryDetails: unknown;\n\t\t\tif (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {\n\t\t\t\tconst model = this.deps.getModel()!;\n\t\t\t\tconst { apiKey, headers } = await this.deps.getRequiredRequestAuth(model);\n\t\t\t\tconst branchSummarySettings = this.deps.settingsManager.getBranchSummarySettings();\n\t\t\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treplaceInstructions,\n\t\t\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\t\t});\n\t\t\t\tif (result.aborted) {\n\t\t\t\t\treturn { cancelled: true, aborted: true };\n\t\t\t\t}\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthrow new Error(result.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = result.summary;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t\t};\n\t\t\t} else if (extensionSummary) {\n\t\t\t\tsummaryText = extensionSummary.summary;\n\t\t\t\tsummaryDetails = extensionSummary.details;\n\t\t\t}\n\n\t\t\t// Determine the new leaf position based on target type\n\t\t\tlet newLeafId: string | null;\n\t\t\tlet editorText: string | undefined;\n\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\t// User message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = extractUserMessageText(targetEntry.message.content);\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\t// Custom message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else {\n\t\t\t\t// Non-user message: leaf = selected node\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\n\t\t\t// Switch leaf (with or without summary)\n\t\t\t// Summary is attached at the navigation target position (newLeafId), not the old branch\n\t\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\t\tif (summaryText) {\n\t\t\t\t// Create summary at target position (can be null for root)\n\t\t\t\tconst summaryId = sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);\n\t\t\t\tsummaryEntry = sessionManager.getEntry(summaryId) as BranchSummaryEntry;\n\n\t\t\t\t// Attach label to the summary entry\n\t\t\t\tif (label) {\n\t\t\t\t\tsessionManager.appendLabelChange(summaryId, label);\n\t\t\t\t}\n\t\t\t} else if (newLeafId === null) {\n\t\t\t\t// No summary, navigating to root - reset leaf\n\t\t\t\tsessionManager.resetLeaf();\n\t\t\t} else {\n\t\t\t\t// No summary, navigating to non-root\n\t\t\t\tsessionManager.branch(newLeafId);\n\t\t\t}\n\n\t\t\t// Attach label to target entry when not summarizing (no summary entry to label)\n\t\t\tif (label && !summaryText) {\n\t\t\t\tsessionManager.appendLabelChange(targetId, label);\n\t\t\t}\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = sessionManager.buildSessionContext();\n\t\t\tthis.deps.setAgentMessages(sessionContext.messages);\n\n\t\t\t// Emit session_tree event\n\t\t\tawait extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: sessionManager.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summaryText ? fromExtension : undefined,\n\t\t\t});\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-navigation controller for AgentSession.
|
|
3
|
+
*
|
|
4
|
+
* Handles navigating to a different node in the session tree (staying in the
|
|
5
|
+
* same session file, unlike fork). When the user opts to summarize the
|
|
6
|
+
* abandoned branch it runs the `session_before_tree` hook, generates a branch
|
|
7
|
+
* summary (from an extension or the default summarizer), attaches it at the new
|
|
8
|
+
* leaf, refreshes agent context, and emits `session_tree`. Owns the branch
|
|
9
|
+
* summarization abort controller. Extracted from agent-session.ts behind a
|
|
10
|
+
* narrow TreeNavigationControllerDeps interface.
|
|
11
|
+
*/
|
|
12
|
+
import { collectEntriesForBranchSummary, generateBranchSummary } from "@kolisachint/hoocode-agent-core";
|
|
13
|
+
import { extractUserMessageText } from "./agent-session-stats.js";
|
|
14
|
+
export class TreeNavigationController {
|
|
15
|
+
deps;
|
|
16
|
+
_branchSummaryAbortController = undefined;
|
|
17
|
+
constructor(deps) {
|
|
18
|
+
this.deps = deps;
|
|
19
|
+
}
|
|
20
|
+
/** Whether branch summarization is currently running */
|
|
21
|
+
get isSummarizing() {
|
|
22
|
+
return this._branchSummaryAbortController !== undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Cancel in-progress branch summarization. */
|
|
25
|
+
abortBranchSummary() {
|
|
26
|
+
this._branchSummaryAbortController?.abort();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Navigate to a different node in the session tree.
|
|
30
|
+
* Unlike fork() which creates a new session file, this stays in the same file.
|
|
31
|
+
*
|
|
32
|
+
* @param targetId The entry ID to navigate to
|
|
33
|
+
* @param options.summarize Whether user wants to summarize abandoned branch
|
|
34
|
+
* @param options.customInstructions Custom instructions for summarizer
|
|
35
|
+
* @param options.replaceInstructions If true, customInstructions replaces the default prompt
|
|
36
|
+
* @param options.label Label to attach to the branch summary entry
|
|
37
|
+
* @returns Result with editorText (if user message) and cancelled status
|
|
38
|
+
*/
|
|
39
|
+
async navigateTree(targetId, options = {}) {
|
|
40
|
+
const sessionManager = this.deps.sessionManager;
|
|
41
|
+
const extensionRunner = this.deps.getExtensionRunner();
|
|
42
|
+
const oldLeafId = sessionManager.getLeafId();
|
|
43
|
+
// No-op if already at target
|
|
44
|
+
if (targetId === oldLeafId) {
|
|
45
|
+
return { cancelled: false };
|
|
46
|
+
}
|
|
47
|
+
// Model required for summarization
|
|
48
|
+
if (options.summarize && !this.deps.getModel()) {
|
|
49
|
+
throw new Error("No model available for summarization");
|
|
50
|
+
}
|
|
51
|
+
const targetEntry = sessionManager.getEntry(targetId);
|
|
52
|
+
if (!targetEntry) {
|
|
53
|
+
throw new Error(`Entry ${targetId} not found`);
|
|
54
|
+
}
|
|
55
|
+
// Collect entries to summarize (from old leaf to common ancestor)
|
|
56
|
+
const { entries: entriesToSummarize, commonAncestorId } = await collectEntriesForBranchSummary(sessionManager, oldLeafId, targetId);
|
|
57
|
+
// Prepare event data - mutable so extensions can override
|
|
58
|
+
let customInstructions = options.customInstructions;
|
|
59
|
+
let replaceInstructions = options.replaceInstructions;
|
|
60
|
+
let label = options.label;
|
|
61
|
+
const preparation = {
|
|
62
|
+
targetId,
|
|
63
|
+
oldLeafId,
|
|
64
|
+
commonAncestorId,
|
|
65
|
+
entriesToSummarize,
|
|
66
|
+
userWantsSummary: options.summarize ?? false,
|
|
67
|
+
customInstructions,
|
|
68
|
+
replaceInstructions,
|
|
69
|
+
label,
|
|
70
|
+
};
|
|
71
|
+
// Set up abort controller for summarization
|
|
72
|
+
this._branchSummaryAbortController = new AbortController();
|
|
73
|
+
try {
|
|
74
|
+
let extensionSummary;
|
|
75
|
+
let fromExtension = false;
|
|
76
|
+
// Emit session_before_tree event
|
|
77
|
+
if (extensionRunner.hasHandlers("session_before_tree")) {
|
|
78
|
+
const result = (await extensionRunner.emit({
|
|
79
|
+
type: "session_before_tree",
|
|
80
|
+
preparation,
|
|
81
|
+
signal: this._branchSummaryAbortController.signal,
|
|
82
|
+
}));
|
|
83
|
+
if (result?.cancel) {
|
|
84
|
+
return { cancelled: true };
|
|
85
|
+
}
|
|
86
|
+
if (result?.summary && options.summarize) {
|
|
87
|
+
extensionSummary = result.summary;
|
|
88
|
+
fromExtension = true;
|
|
89
|
+
}
|
|
90
|
+
// Allow extensions to override instructions and label
|
|
91
|
+
if (result?.customInstructions !== undefined) {
|
|
92
|
+
customInstructions = result.customInstructions;
|
|
93
|
+
}
|
|
94
|
+
if (result?.replaceInstructions !== undefined) {
|
|
95
|
+
replaceInstructions = result.replaceInstructions;
|
|
96
|
+
}
|
|
97
|
+
if (result?.label !== undefined) {
|
|
98
|
+
label = result.label;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Run default summarizer if needed
|
|
102
|
+
let summaryText;
|
|
103
|
+
let summaryDetails;
|
|
104
|
+
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
|
|
105
|
+
const model = this.deps.getModel();
|
|
106
|
+
const { apiKey, headers } = await this.deps.getRequiredRequestAuth(model);
|
|
107
|
+
const branchSummarySettings = this.deps.settingsManager.getBranchSummarySettings();
|
|
108
|
+
const result = await generateBranchSummary(entriesToSummarize, {
|
|
109
|
+
model,
|
|
110
|
+
apiKey,
|
|
111
|
+
headers,
|
|
112
|
+
signal: this._branchSummaryAbortController.signal,
|
|
113
|
+
customInstructions,
|
|
114
|
+
replaceInstructions,
|
|
115
|
+
reserveTokens: branchSummarySettings.reserveTokens,
|
|
116
|
+
});
|
|
117
|
+
if (result.aborted) {
|
|
118
|
+
return { cancelled: true, aborted: true };
|
|
119
|
+
}
|
|
120
|
+
if (result.error) {
|
|
121
|
+
throw new Error(result.error);
|
|
122
|
+
}
|
|
123
|
+
summaryText = result.summary;
|
|
124
|
+
summaryDetails = {
|
|
125
|
+
readFiles: result.readFiles || [],
|
|
126
|
+
modifiedFiles: result.modifiedFiles || [],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
else if (extensionSummary) {
|
|
130
|
+
summaryText = extensionSummary.summary;
|
|
131
|
+
summaryDetails = extensionSummary.details;
|
|
132
|
+
}
|
|
133
|
+
// Determine the new leaf position based on target type
|
|
134
|
+
let newLeafId;
|
|
135
|
+
let editorText;
|
|
136
|
+
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
137
|
+
// User message: leaf = parent (null if root), text goes to editor
|
|
138
|
+
newLeafId = targetEntry.parentId;
|
|
139
|
+
editorText = extractUserMessageText(targetEntry.message.content);
|
|
140
|
+
}
|
|
141
|
+
else if (targetEntry.type === "custom_message") {
|
|
142
|
+
// Custom message: leaf = parent (null if root), text goes to editor
|
|
143
|
+
newLeafId = targetEntry.parentId;
|
|
144
|
+
editorText =
|
|
145
|
+
typeof targetEntry.content === "string"
|
|
146
|
+
? targetEntry.content
|
|
147
|
+
: targetEntry.content
|
|
148
|
+
.filter((c) => c.type === "text")
|
|
149
|
+
.map((c) => c.text)
|
|
150
|
+
.join("");
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Non-user message: leaf = selected node
|
|
154
|
+
newLeafId = targetId;
|
|
155
|
+
}
|
|
156
|
+
// Switch leaf (with or without summary)
|
|
157
|
+
// Summary is attached at the navigation target position (newLeafId), not the old branch
|
|
158
|
+
let summaryEntry;
|
|
159
|
+
if (summaryText) {
|
|
160
|
+
// Create summary at target position (can be null for root)
|
|
161
|
+
const summaryId = sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
|
|
162
|
+
summaryEntry = sessionManager.getEntry(summaryId);
|
|
163
|
+
// Attach label to the summary entry
|
|
164
|
+
if (label) {
|
|
165
|
+
sessionManager.appendLabelChange(summaryId, label);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (newLeafId === null) {
|
|
169
|
+
// No summary, navigating to root - reset leaf
|
|
170
|
+
sessionManager.resetLeaf();
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// No summary, navigating to non-root
|
|
174
|
+
sessionManager.branch(newLeafId);
|
|
175
|
+
}
|
|
176
|
+
// Attach label to target entry when not summarizing (no summary entry to label)
|
|
177
|
+
if (label && !summaryText) {
|
|
178
|
+
sessionManager.appendLabelChange(targetId, label);
|
|
179
|
+
}
|
|
180
|
+
// Update agent state
|
|
181
|
+
const sessionContext = sessionManager.buildSessionContext();
|
|
182
|
+
this.deps.setAgentMessages(sessionContext.messages);
|
|
183
|
+
// Emit session_tree event
|
|
184
|
+
await extensionRunner.emit({
|
|
185
|
+
type: "session_tree",
|
|
186
|
+
newLeafId: sessionManager.getLeafId(),
|
|
187
|
+
oldLeafId,
|
|
188
|
+
summaryEntry,
|
|
189
|
+
fromExtension: summaryText ? fromExtension : undefined,
|
|
190
|
+
});
|
|
191
|
+
return { editorText, cancelled: false, summaryEntry };
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
this._branchSummaryAbortController = undefined;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=agent-session-tree-navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session-tree-navigation.js","sourceRoot":"","sources":["../../src/core/agent-session-tree-navigation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tree-navigation controller for AgentSession.\n *\n * Handles navigating to a different node in the session tree (staying in the\n * same session file, unlike fork). When the user opts to summarize the\n * abandoned branch it runs the `session_before_tree` hook, generates a branch\n * summary (from an extension or the default summarizer), attaches it at the new\n * leaf, refreshes agent context, and emits `session_tree`. Owns the branch\n * summarization abort controller. Extracted from agent-session.ts behind a\n * narrow TreeNavigationControllerDeps interface.\n */\n\nimport type { AgentMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"@kolisachint/hoocode-agent-core\";\nimport type { Model } from \"@kolisachint/hoocode-ai\";\nimport { extractUserMessageText } from \"./agent-session-stats.js\";\nimport type { ExtensionRunner, SessionBeforeTreeResult, TreePreparation } from \"./extensions/index.js\";\nimport type { BranchSummaryEntry, SessionManager } from \"./session-manager.js\";\nimport type { SettingsManager } from \"./settings-manager.js\";\n\n/** Options for navigating the session tree. */\nexport interface NavigateTreeOptions {\n\t/** Whether the user wants to summarize the abandoned branch */\n\tsummarize?: boolean;\n\t/** Custom instructions for the summarizer */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt */\n\treplaceInstructions?: boolean;\n\t/** Label to attach to the branch summary entry */\n\tlabel?: string;\n}\n\n/** Result of navigating the session tree. */\nexport interface NavigateTreeResult {\n\teditorText?: string;\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryEntry?: BranchSummaryEntry;\n}\n\n/** Narrow dependencies the tree-navigation controller needs from AgentSession. */\nexport interface TreeNavigationControllerDeps {\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tgetModel(): Model<any> | undefined;\n\t/** Read at call time; the extension runner is swapped on reload. */\n\tgetExtensionRunner(): ExtensionRunner;\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey: string; headers?: Record<string, string> }>;\n\tsetAgentMessages(messages: AgentMessage[]): void;\n}\n\nexport class TreeNavigationController {\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\tconstructor(private readonly deps: TreeNavigationControllerDeps) {}\n\n\t/** Whether branch summarization is currently running */\n\tget isSummarizing(): boolean {\n\t\treturn this._branchSummaryAbortController !== undefined;\n\t}\n\n\t/** Cancel in-progress branch summarization. */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tasync navigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\tconst sessionManager = this.deps.sessionManager;\n\t\tconst extensionRunner = this.deps.getExtensionRunner();\n\t\tconst oldLeafId = sessionManager.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.deps.getModel()) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = sessionManager.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = await collectEntriesForBranchSummary(\n\t\t\tsessionManager,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tlet customInstructions = options.customInstructions;\n\t\tlet replaceInstructions = options.replaceInstructions;\n\t\tlet label = options.label;\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions,\n\t\t\treplaceInstructions,\n\t\t\tlabel,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tlet extensionSummary: { summary: string; details?: unknown } | undefined;\n\t\t\tlet fromExtension = false;\n\n\t\t\t// Emit session_before_tree event\n\t\t\tif (extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\t\tconst result = (await extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_tree\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t})) as SessionBeforeTreeResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t}\n\n\t\t\t\tif (result?.summary && options.summarize) {\n\t\t\t\t\textensionSummary = result.summary;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\n\t\t\t\t// Allow extensions to override instructions and label\n\t\t\t\tif (result?.customInstructions !== undefined) {\n\t\t\t\t\tcustomInstructions = result.customInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.replaceInstructions !== undefined) {\n\t\t\t\t\treplaceInstructions = result.replaceInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.label !== undefined) {\n\t\t\t\t\tlabel = result.label;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Run default summarizer if needed\n\t\t\tlet summaryText: string | undefined;\n\t\t\tlet summaryDetails: unknown;\n\t\t\tif (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {\n\t\t\t\tconst model = this.deps.getModel()!;\n\t\t\t\tconst { apiKey, headers } = await this.deps.getRequiredRequestAuth(model);\n\t\t\t\tconst branchSummarySettings = this.deps.settingsManager.getBranchSummarySettings();\n\t\t\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treplaceInstructions,\n\t\t\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\t\t});\n\t\t\t\tif (result.aborted) {\n\t\t\t\t\treturn { cancelled: true, aborted: true };\n\t\t\t\t}\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthrow new Error(result.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = result.summary;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t\t};\n\t\t\t} else if (extensionSummary) {\n\t\t\t\tsummaryText = extensionSummary.summary;\n\t\t\t\tsummaryDetails = extensionSummary.details;\n\t\t\t}\n\n\t\t\t// Determine the new leaf position based on target type\n\t\t\tlet newLeafId: string | null;\n\t\t\tlet editorText: string | undefined;\n\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\t// User message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = extractUserMessageText(targetEntry.message.content);\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\t// Custom message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else {\n\t\t\t\t// Non-user message: leaf = selected node\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\n\t\t\t// Switch leaf (with or without summary)\n\t\t\t// Summary is attached at the navigation target position (newLeafId), not the old branch\n\t\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\t\tif (summaryText) {\n\t\t\t\t// Create summary at target position (can be null for root)\n\t\t\t\tconst summaryId = sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);\n\t\t\t\tsummaryEntry = sessionManager.getEntry(summaryId) as BranchSummaryEntry;\n\n\t\t\t\t// Attach label to the summary entry\n\t\t\t\tif (label) {\n\t\t\t\t\tsessionManager.appendLabelChange(summaryId, label);\n\t\t\t\t}\n\t\t\t} else if (newLeafId === null) {\n\t\t\t\t// No summary, navigating to root - reset leaf\n\t\t\t\tsessionManager.resetLeaf();\n\t\t\t} else {\n\t\t\t\t// No summary, navigating to non-root\n\t\t\t\tsessionManager.branch(newLeafId);\n\t\t\t}\n\n\t\t\t// Attach label to target entry when not summarizing (no summary entry to label)\n\t\t\tif (label && !summaryText) {\n\t\t\t\tsessionManager.appendLabelChange(targetId, label);\n\t\t\t}\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = sessionManager.buildSessionContext();\n\t\t\tthis.deps.setAgentMessages(sessionContext.messages);\n\n\t\t\t// Emit session_tree event\n\t\t\tawait extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: sessionManager.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summaryText ? fromExtension : undefined,\n\t\t\t});\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -14,26 +14,18 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, CompactionResult, CustomMessage, ThinkingLevel } from "@kolisachint/hoocode-agent-core";
|
|
16
16
|
import type { ImageContent, Model, TextContent } from "@kolisachint/hoocode-ai";
|
|
17
|
+
import { type SessionStats } from "./agent-session-stats.js";
|
|
18
|
+
import { type NavigateTreeOptions, type NavigateTreeResult } from "./agent-session-tree-navigation.js";
|
|
17
19
|
import { type BashResult } from "./bash-executor.js";
|
|
18
20
|
import { type ContextUsage, type ExtensionCommandContextActions, type ExtensionErrorListener, ExtensionRunner, type ExtensionUIContext, type InputSource, type ReplacedSessionContext, type SessionStartEvent, type ShutdownHandler, type ToolDefinition, type ToolInfo } from "./extensions/index.js";
|
|
19
21
|
import type { ModelRegistry } from "./model-registry.js";
|
|
20
22
|
import { type PromptTemplate } from "./prompt-templates.js";
|
|
21
23
|
import type { ResourceLoader } from "./resource-loader.js";
|
|
22
|
-
import type {
|
|
24
|
+
import type { SessionManager } from "./session-manager.js";
|
|
23
25
|
import type { SettingsManager } from "./settings-manager.js";
|
|
24
26
|
import { type BashOperations } from "./tools/bash.js";
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
name: string;
|
|
28
|
-
location: string;
|
|
29
|
-
content: string;
|
|
30
|
-
userMessage: string | undefined;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Parse a skill block from message text.
|
|
34
|
-
* Returns null if the text doesn't contain a skill block.
|
|
35
|
-
*/
|
|
36
|
-
export declare function parseSkillBlock(text: string): ParsedSkillBlock | null;
|
|
27
|
+
export type { ParsedSkillBlock } from "./agent-session-skills.js";
|
|
28
|
+
export { parseSkillBlock } from "./agent-session-skills.js";
|
|
37
29
|
/** Session-specific events that extend the core AgentEvent */
|
|
38
30
|
export type AgentSessionEvent = AgentEvent | {
|
|
39
31
|
type: "queue_update";
|
|
@@ -131,25 +123,7 @@ export interface ModelCycleResult {
|
|
|
131
123
|
/** Whether cycling through scoped models (--models flag) or all available */
|
|
132
124
|
isScoped: boolean;
|
|
133
125
|
}
|
|
134
|
-
|
|
135
|
-
export interface SessionStats {
|
|
136
|
-
sessionFile: string | undefined;
|
|
137
|
-
sessionId: string;
|
|
138
|
-
userMessages: number;
|
|
139
|
-
assistantMessages: number;
|
|
140
|
-
toolCalls: number;
|
|
141
|
-
toolResults: number;
|
|
142
|
-
totalMessages: number;
|
|
143
|
-
tokens: {
|
|
144
|
-
input: number;
|
|
145
|
-
output: number;
|
|
146
|
-
cacheRead: number;
|
|
147
|
-
cacheWrite: number;
|
|
148
|
-
total: number;
|
|
149
|
-
};
|
|
150
|
-
cost: number;
|
|
151
|
-
contextUsage?: ContextUsage;
|
|
152
|
-
}
|
|
126
|
+
export type { SessionStats } from "./agent-session-stats.js";
|
|
153
127
|
export declare class AgentSession {
|
|
154
128
|
readonly agent: Agent;
|
|
155
129
|
readonly sessionManager: SessionManager;
|
|
@@ -164,14 +138,9 @@ export declare class AgentSession {
|
|
|
164
138
|
private _followUpMessages;
|
|
165
139
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
166
140
|
private _pendingNextTurnMessages;
|
|
167
|
-
private
|
|
168
|
-
private
|
|
169
|
-
private
|
|
170
|
-
private _branchSummaryAbortController;
|
|
171
|
-
private _retryAbortController;
|
|
172
|
-
private _retryAttempt;
|
|
173
|
-
private _retryPromise;
|
|
174
|
-
private _retryResolve;
|
|
141
|
+
private _compaction;
|
|
142
|
+
private _tree;
|
|
143
|
+
private _retry;
|
|
175
144
|
private _bashAbortController;
|
|
176
145
|
private _pendingBashMessages;
|
|
177
146
|
private _extensionRunner;
|
|
@@ -217,11 +186,7 @@ export declare class AgentSession {
|
|
|
217
186
|
private _lastAssistantMessage;
|
|
218
187
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
219
188
|
private _handleAgentEvent;
|
|
220
|
-
private _createRetryPromiseForAgentEnd;
|
|
221
|
-
private _findLastAssistantInMessages;
|
|
222
189
|
private _processAgentEvent;
|
|
223
|
-
/** Resolve the pending retry promise */
|
|
224
|
-
private _resolveRetry;
|
|
225
190
|
/** Extract text content from a message */
|
|
226
191
|
private _getUserMessageText;
|
|
227
192
|
/** Find the last assistant message in agent state (including aborted ones) */
|
|
@@ -442,7 +407,6 @@ export declare class AgentSession {
|
|
|
442
407
|
* Saves to settings.
|
|
443
408
|
*/
|
|
444
409
|
setFollowUpMode(mode: "all" | "one-at-a-time"): void;
|
|
445
|
-
private _applyCompaction;
|
|
446
410
|
/**
|
|
447
411
|
* Manually compact the session context.
|
|
448
412
|
* Aborts current agent operation first.
|
|
@@ -457,8 +421,6 @@ export declare class AgentSession {
|
|
|
457
421
|
* Cancel in-progress branch summarization.
|
|
458
422
|
*/
|
|
459
423
|
abortBranchSummary(): void;
|
|
460
|
-
private _checkCompaction;
|
|
461
|
-
private _runAutoCompaction;
|
|
462
424
|
/**
|
|
463
425
|
* Toggle auto-compaction setting.
|
|
464
426
|
*/
|
|
@@ -475,17 +437,10 @@ export declare class AgentSession {
|
|
|
475
437
|
private _refreshToolRegistry;
|
|
476
438
|
private _buildRuntime;
|
|
477
439
|
reload(): Promise<void>;
|
|
478
|
-
/**
|
|
479
|
-
* Check if an error is retryable (overloaded, rate limit, server errors).
|
|
480
|
-
* Context overflow errors are NOT retryable (handled by compaction instead).
|
|
481
|
-
*/
|
|
482
|
-
private _isRetryableError;
|
|
483
|
-
private _handleRetryableError;
|
|
484
440
|
/**
|
|
485
441
|
* Cancel in-progress retry.
|
|
486
442
|
*/
|
|
487
443
|
abortRetry(): void;
|
|
488
|
-
private waitForRetry;
|
|
489
444
|
/** Whether auto-retry is currently in progress */
|
|
490
445
|
get isRetrying(): boolean;
|
|
491
446
|
/** Whether auto-retry is enabled */
|
|
@@ -541,17 +496,7 @@ export declare class AgentSession {
|
|
|
541
496
|
* @param options.label Label to attach to the branch summary entry
|
|
542
497
|
* @returns Result with editorText (if user message) and cancelled status
|
|
543
498
|
*/
|
|
544
|
-
navigateTree(targetId: string, options?:
|
|
545
|
-
summarize?: boolean;
|
|
546
|
-
customInstructions?: string;
|
|
547
|
-
replaceInstructions?: boolean;
|
|
548
|
-
label?: string;
|
|
549
|
-
}): Promise<{
|
|
550
|
-
editorText?: string;
|
|
551
|
-
cancelled: boolean;
|
|
552
|
-
aborted?: boolean;
|
|
553
|
-
summaryEntry?: BranchSummaryEntry;
|
|
554
|
-
}>;
|
|
499
|
+
navigateTree(targetId: string, options?: NavigateTreeOptions): Promise<NavigateTreeResult>;
|
|
555
500
|
/**
|
|
556
501
|
* Get all user messages from session for fork selector.
|
|
557
502
|
*/
|
|
@@ -559,7 +504,6 @@ export declare class AgentSession {
|
|
|
559
504
|
entryId: string;
|
|
560
505
|
text: string;
|
|
561
506
|
}>;
|
|
562
|
-
private _extractUserMessageText;
|
|
563
507
|
/**
|
|
564
508
|
* Get session statistics.
|
|
565
509
|
*/
|