@codex-infinity/pi-infinity 0.64.1 → 0.64.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +10 -4
  3. package/dist/core/agent-session-runtime.d.ts +136 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +265 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +5 -42
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +13 -207
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  12. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  13. package/dist/core/export-html/tool-renderer.js +2 -2
  14. package/dist/core/export-html/tool-renderer.js.map +1 -1
  15. package/dist/core/extensions/index.d.ts +2 -2
  16. package/dist/core/extensions/index.d.ts.map +1 -1
  17. package/dist/core/extensions/index.js +1 -1
  18. package/dist/core/extensions/index.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +16 -16
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js +10 -0
  22. package/dist/core/extensions/types.js.map +1 -1
  23. package/dist/core/footer-data-provider.d.ts +5 -1
  24. package/dist/core/footer-data-provider.d.ts.map +1 -1
  25. package/dist/core/footer-data-provider.js +69 -8
  26. package/dist/core/footer-data-provider.js.map +1 -1
  27. package/dist/core/index.d.ts +2 -1
  28. package/dist/core/index.d.ts.map +1 -1
  29. package/dist/core/index.js +2 -1
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/sdk.d.ts +4 -1
  32. package/dist/core/sdk.d.ts.map +1 -1
  33. package/dist/core/sdk.js +3 -0
  34. package/dist/core/sdk.js.map +1 -1
  35. package/dist/index.d.ts +3 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/main.d.ts.map +1 -1
  40. package/dist/main.js +42 -9
  41. package/dist/main.js.map +1 -1
  42. package/dist/modes/interactive/components/footer.d.ts +1 -0
  43. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  44. package/dist/modes/interactive/components/footer.js +4 -1
  45. package/dist/modes/interactive/components/footer.js.map +1 -1
  46. package/dist/modes/interactive/interactive-mode.d.ts +8 -4
  47. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  48. package/dist/modes/interactive/interactive-mode.js +89 -86
  49. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  50. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  51. package/dist/modes/interactive/theme/theme.js +6 -11
  52. package/dist/modes/interactive/theme/theme.js.map +1 -1
  53. package/dist/modes/print-mode.d.ts +2 -2
  54. package/dist/modes/print-mode.d.ts.map +1 -1
  55. package/dist/modes/print-mode.js +37 -36
  56. package/dist/modes/print-mode.js.map +1 -1
  57. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  58. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  59. package/dist/modes/rpc/rpc-mode.js +69 -49
  60. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  61. package/docs/extensions.md +75 -19
  62. package/docs/sdk.md +160 -72
  63. package/docs/tree.md +1 -1
  64. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  65. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  66. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  67. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  68. package/examples/extensions/hello.ts +18 -17
  69. package/examples/extensions/hidden-thinking-label.ts +0 -4
  70. package/examples/extensions/rpc-demo.ts +3 -9
  71. package/examples/extensions/status-line.ts +0 -8
  72. package/examples/extensions/todo.ts +0 -2
  73. package/examples/extensions/tools.ts +0 -5
  74. package/examples/extensions/widget-placement.ts +4 -12
  75. package/examples/extensions/with-deps/package-lock.json +2 -2
  76. package/examples/extensions/with-deps/package.json +1 -1
  77. package/examples/sdk/13-session-runtime.ts +49 -0
  78. package/examples/sdk/README.md +2 -1
  79. package/package.json +4 -4
@@ -12,14 +12,14 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname, join, resolve } from "node:path";
17
17
  import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@mariozechner/pi-ai";
18
18
  import { getDocsPath } from "../config.js";
19
19
  import { theme } from "../modes/interactive/theme/theme.js";
20
20
  import { stripFrontmatter } from "../utils/frontmatter.js";
21
21
  import { sleep } from "../utils/sleep.js";
22
- import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
22
+ import { executeBashWithOperations } from "./bash-executor.js";
23
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
24
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
25
  import { exportSessionToHtml } from "./export-html/index.js";
@@ -29,6 +29,7 @@ import { expandPromptTemplate } from "./prompt-templates.js";
29
29
  import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
30
30
  import { createSyntheticSourceInfo } from "./source-info.js";
31
31
  import { buildSystemPrompt } from "./system-prompt.js";
32
+ import { createLocalBashOperations } from "./tools/bash.js";
32
33
  import { createAllToolDefinitions } from "./tools/index.js";
33
34
  import { createToolDefinitionFromAgentTool, wrapToolDefinition } from "./tools/tool-definition-wrapper.js";
34
35
  /**
@@ -121,6 +122,7 @@ export class AgentSession {
121
122
  this._baseToolsOverride = config.baseToolsOverride;
122
123
  this._autoNextSteps = config.autoNextSteps ?? false;
123
124
  this._autoNextIdea = config.autoNextIdea ?? false;
125
+ this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
124
126
  // Always subscribe to agent events for internal handling
125
127
  // (session persistence, extensions, auto-compaction, retry logic)
126
128
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
@@ -1015,54 +1017,6 @@ export class AgentSession {
1015
1017
  this.agent.abort();
1016
1018
  await this.agent.waitForIdle();
1017
1019
  }
1018
- /**
1019
- * Start a new session, optionally with initial messages and parent tracking.
1020
- * Clears all messages and starts a new session.
1021
- * Listeners are preserved and will continue receiving events.
1022
- * @param options.parentSession - Optional parent session path for tracking
1023
- * @param options.setup - Optional callback to initialize session (e.g., append messages)
1024
- * @returns true if completed, false if cancelled by extension
1025
- */
1026
- async newSession(options) {
1027
- const previousSessionFile = this.sessionFile;
1028
- // Emit session_before_switch event with reason "new" (can be cancelled)
1029
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
1030
- const result = (await this._extensionRunner.emit({
1031
- type: "session_before_switch",
1032
- reason: "new",
1033
- }));
1034
- if (result?.cancel) {
1035
- return false;
1036
- }
1037
- }
1038
- this._disconnectFromAgent();
1039
- await this.abort();
1040
- this.agent.reset();
1041
- this.sessionManager.newSession({ parentSession: options?.parentSession });
1042
- this.agent.sessionId = this.sessionManager.getSessionId();
1043
- this._steeringMessages = [];
1044
- this._followUpMessages = [];
1045
- this._pendingNextTurnMessages = [];
1046
- this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1047
- // Run setup callback if provided (e.g., to append initial messages)
1048
- if (options?.setup) {
1049
- await options.setup(this.sessionManager);
1050
- // Sync agent state with session manager after setup
1051
- const sessionContext = this.sessionManager.buildSessionContext();
1052
- this.agent.state.messages = sessionContext.messages;
1053
- }
1054
- this._reconnectToAgent();
1055
- // Emit session_switch event with reason "new" to extensions
1056
- if (this._extensionRunner) {
1057
- await this._extensionRunner.emit({
1058
- type: "session_switch",
1059
- reason: "new",
1060
- previousSessionFile,
1061
- });
1062
- }
1063
- // Emit session event to custom tools
1064
- return true;
1065
- }
1066
1020
  // =========================================================================
1067
1021
  // Model Management
1068
1022
  // =========================================================================
@@ -1108,11 +1062,8 @@ export class AgentSession {
1108
1062
  }
1109
1063
  return this._cycleAvailableModel(direction);
1110
1064
  }
1111
- _getScopedModelsWithAuth() {
1112
- return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1113
- }
1114
1065
  async _cycleScopedModel(direction) {
1115
- const scopedModels = this._getScopedModelsWithAuth();
1066
+ const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1116
1067
  if (scopedModels.length <= 1)
1117
1068
  return undefined;
1118
1069
  const currentModel = this.model;
@@ -1644,8 +1595,8 @@ export class AgentSession {
1644
1595
  }
1645
1596
  if (this._extensionRunner) {
1646
1597
  this._applyExtensionBindings(this._extensionRunner);
1647
- await this._extensionRunner.emit({ type: "session_start" });
1648
- await this.extendResourcesFromExtensions("startup");
1598
+ await this._extensionRunner.emit(this._sessionStartEvent);
1599
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
1649
1600
  }
1650
1601
  }
1651
1602
  async extendResourcesFromExtensions(reason) {
@@ -1929,7 +1880,7 @@ export class AgentSession {
1929
1880
  this._extensionShutdownHandler ||
1930
1881
  this._extensionErrorListener;
1931
1882
  if (this._extensionRunner && hasBindings) {
1932
- await this._extensionRunner.emit({ type: "session_start" });
1883
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
1933
1884
  await this.extendResourcesFromExtensions("reload");
1934
1885
  }
1935
1886
  }
@@ -2110,15 +2061,10 @@ export class AgentSession {
2110
2061
  const prefix = this.settingsManager.getShellCommandPrefix();
2111
2062
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
2112
2063
  try {
2113
- const result = options?.operations
2114
- ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
2115
- onChunk,
2116
- signal: this._bashAbortController.signal,
2117
- })
2118
- : await executeBashCommand(resolvedCommand, {
2119
- onChunk,
2120
- signal: this._bashAbortController.signal,
2121
- });
2064
+ const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations(), {
2065
+ onChunk,
2066
+ signal: this._bashAbortController.signal,
2067
+ });
2122
2068
  this.recordBashResult(command, result, options);
2123
2069
  return result;
2124
2070
  }
@@ -2186,130 +2132,12 @@ export class AgentSession {
2186
2132
  // =========================================================================
2187
2133
  // Session Management
2188
2134
  // =========================================================================
2189
- /**
2190
- * Switch to a different session file.
2191
- * Aborts current operation, loads messages, restores model/thinking.
2192
- * Listeners are preserved and will continue receiving events.
2193
- * @returns true if switch completed, false if cancelled by extension
2194
- */
2195
- async switchSession(sessionPath) {
2196
- const previousSessionFile = this.sessionManager.getSessionFile();
2197
- // Emit session_before_switch event (can be cancelled)
2198
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
2199
- const result = (await this._extensionRunner.emit({
2200
- type: "session_before_switch",
2201
- reason: "resume",
2202
- targetSessionFile: sessionPath,
2203
- }));
2204
- if (result?.cancel) {
2205
- return false;
2206
- }
2207
- }
2208
- this._disconnectFromAgent();
2209
- await this.abort();
2210
- this._steeringMessages = [];
2211
- this._followUpMessages = [];
2212
- this._pendingNextTurnMessages = [];
2213
- // Set new session
2214
- this.sessionManager.setSessionFile(sessionPath);
2215
- this.agent.sessionId = this.sessionManager.getSessionId();
2216
- // Reload messages
2217
- const sessionContext = this.sessionManager.buildSessionContext();
2218
- // Emit session_switch event to extensions
2219
- if (this._extensionRunner) {
2220
- await this._extensionRunner.emit({
2221
- type: "session_switch",
2222
- reason: "resume",
2223
- previousSessionFile,
2224
- });
2225
- }
2226
- // Emit session event to custom tools
2227
- this.agent.state.messages = sessionContext.messages;
2228
- // Restore model if saved
2229
- if (sessionContext.model) {
2230
- const previousModel = this.model;
2231
- const availableModels = await this._modelRegistry.getAvailable();
2232
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2233
- if (match) {
2234
- this.agent.state.model = match;
2235
- await this._emitModelSelect(match, previousModel, "restore");
2236
- }
2237
- }
2238
- const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2239
- const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
2240
- if (hasThinkingEntry) {
2241
- // Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
2242
- this.setThinkingLevel(sessionContext.thinkingLevel);
2243
- }
2244
- else {
2245
- const availableLevels = this.getAvailableThinkingLevels();
2246
- const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
2247
- ? defaultThinkingLevel
2248
- : this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
2249
- this.agent.state.thinkingLevel = effectiveLevel;
2250
- this.sessionManager.appendThinkingLevelChange(effectiveLevel);
2251
- }
2252
- this._reconnectToAgent();
2253
- return true;
2254
- }
2255
2135
  /**
2256
2136
  * Set a display name for the current session.
2257
2137
  */
2258
2138
  setSessionName(name) {
2259
2139
  this.sessionManager.appendSessionInfo(name);
2260
2140
  }
2261
- /**
2262
- * Create a fork from a specific entry.
2263
- * Emits before_fork/fork session events to extensions.
2264
- *
2265
- * @param entryId ID of the entry to fork from
2266
- * @returns Object with:
2267
- * - selectedText: The text of the selected user message (for editor pre-fill)
2268
- * - cancelled: True if an extension cancelled the fork
2269
- */
2270
- async fork(entryId) {
2271
- const previousSessionFile = this.sessionFile;
2272
- const selectedEntry = this.sessionManager.getEntry(entryId);
2273
- if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
2274
- throw new Error("Invalid entry ID for forking");
2275
- }
2276
- const selectedText = this._extractUserMessageText(selectedEntry.message.content);
2277
- let skipConversationRestore = false;
2278
- // Emit session_before_fork event (can be cancelled)
2279
- if (this._extensionRunner?.hasHandlers("session_before_fork")) {
2280
- const result = (await this._extensionRunner.emit({
2281
- type: "session_before_fork",
2282
- entryId,
2283
- }));
2284
- if (result?.cancel) {
2285
- return { selectedText, cancelled: true };
2286
- }
2287
- skipConversationRestore = result?.skipConversationRestore ?? false;
2288
- }
2289
- // Clear pending messages (bound to old session state)
2290
- this._pendingNextTurnMessages = [];
2291
- if (!selectedEntry.parentId) {
2292
- this.sessionManager.newSession({ parentSession: previousSessionFile });
2293
- }
2294
- else {
2295
- this.sessionManager.createBranchedSession(selectedEntry.parentId);
2296
- }
2297
- this.agent.sessionId = this.sessionManager.getSessionId();
2298
- // Reload messages from entries (works for both file and in-memory mode)
2299
- const sessionContext = this.sessionManager.buildSessionContext();
2300
- // Emit session_fork event to extensions (after fork completes)
2301
- if (this._extensionRunner) {
2302
- await this._extensionRunner.emit({
2303
- type: "session_fork",
2304
- previousSessionFile,
2305
- });
2306
- }
2307
- // Emit session event to custom tools (with reason "fork")
2308
- if (!skipConversationRestore) {
2309
- this.agent.state.messages = sessionContext.messages;
2310
- }
2311
- return { selectedText, cancelled: false };
2312
- }
2313
2141
  // =========================================================================
2314
2142
  // Tree Navigation
2315
2143
  // =========================================================================
@@ -2605,6 +2433,7 @@ export class AgentSession {
2605
2433
  const toolRenderer = createToolHtmlRenderer({
2606
2434
  getToolDefinition: (name) => this.getToolDefinition(name),
2607
2435
  theme,
2436
+ cwd: this.sessionManager.getCwd(),
2608
2437
  });
2609
2438
  return await exportSessionToHtml(this.sessionManager, this.state, {
2610
2439
  outputPath,
@@ -2643,29 +2472,6 @@ export class AgentSession {
2643
2472
  writeFileSync(filePath, `${lines.join("\n")}\n`);
2644
2473
  return filePath;
2645
2474
  }
2646
- /**
2647
- * Import a JSONL session file.
2648
- * Copies the file into the session directory and switches to it (like /resume).
2649
- * @param inputPath Path to the JSONL file to import.
2650
- * @returns true if the session was switched successfully.
2651
- */
2652
- async importFromJsonl(inputPath) {
2653
- const resolved = resolve(inputPath);
2654
- if (!existsSync(resolved)) {
2655
- throw new Error(`File not found: ${resolved}`);
2656
- }
2657
- // Copy into the session directory so we don't modify the original
2658
- const sessionDir = this.sessionManager.getSessionDir();
2659
- if (!existsSync(sessionDir)) {
2660
- mkdirSync(sessionDir, { recursive: true });
2661
- }
2662
- const destPath = join(sessionDir, basename(resolved));
2663
- // Avoid overwriting if source and destination are the same file
2664
- if (resolve(destPath) !== resolved) {
2665
- copyFileSync(resolved, destPath);
2666
- }
2667
- return this.switchSession(destPath);
2668
- }
2669
2475
  // =========================================================================
2670
2476
  // Utilities
2671
2477
  // =========================================================================