@codex-infinity/pi-infinity 0.64.2 → 0.65.1

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 (90) hide show
  1. package/CHANGELOG.md +57 -34
  2. package/README.md +5 -3
  3. package/dist/cli/args.d.ts +7 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +37 -15
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-runtime.d.ts +51 -102
  8. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  9. package/dist/core/agent-session-runtime.js +103 -138
  10. package/dist/core/agent-session-runtime.js.map +1 -1
  11. package/dist/core/agent-session-services.d.ts +86 -0
  12. package/dist/core/agent-session-services.d.ts.map +1 -0
  13. package/dist/core/agent-session-services.js +116 -0
  14. package/dist/core/agent-session-services.js.map +1 -0
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +1 -1
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/extensions/index.d.ts +2 -2
  19. package/dist/core/extensions/index.d.ts.map +1 -1
  20. package/dist/core/extensions/index.js +1 -1
  21. package/dist/core/extensions/index.js.map +1 -1
  22. package/dist/core/extensions/types.d.ts +10 -13
  23. package/dist/core/extensions/types.d.ts.map +1 -1
  24. package/dist/core/extensions/types.js +10 -0
  25. package/dist/core/extensions/types.js.map +1 -1
  26. package/dist/core/index.d.ts +3 -2
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/core/index.js +3 -2
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/keybindings.d.ts +4 -1
  31. package/dist/core/keybindings.d.ts.map +1 -1
  32. package/dist/core/keybindings.js +3 -14
  33. package/dist/core/keybindings.js.map +1 -1
  34. package/dist/core/package-manager.d.ts +20 -0
  35. package/dist/core/package-manager.d.ts.map +1 -1
  36. package/dist/core/package-manager.js +32 -0
  37. package/dist/core/package-manager.js.map +1 -1
  38. package/dist/core/resource-loader.d.ts.map +1 -1
  39. package/dist/core/resource-loader.js +21 -0
  40. package/dist/core/resource-loader.js.map +1 -1
  41. package/dist/core/sdk.d.ts +1 -1
  42. package/dist/core/sdk.d.ts.map +1 -1
  43. package/dist/core/sdk.js +1 -1
  44. package/dist/core/sdk.js.map +1 -1
  45. package/dist/core/settings-manager.d.ts +1 -1
  46. package/dist/core/settings-manager.d.ts.map +1 -1
  47. package/dist/core/settings-manager.js +2 -1
  48. package/dist/core/settings-manager.js.map +1 -1
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +3 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/main.d.ts.map +1 -1
  54. package/dist/main.js +202 -456
  55. package/dist/main.js.map +1 -1
  56. package/dist/migrations.d.ts.map +1 -1
  57. package/dist/migrations.js +20 -0
  58. package/dist/migrations.js.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.d.ts +3 -2
  60. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  61. package/dist/modes/interactive/interactive-mode.js +56 -29
  62. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  63. package/dist/modes/print-mode.d.ts +2 -2
  64. package/dist/modes/print-mode.d.ts.map +1 -1
  65. package/dist/modes/print-mode.js +4 -0
  66. package/dist/modes/print-mode.js.map +1 -1
  67. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  68. package/dist/modes/rpc/rpc-client.js +1 -0
  69. package/dist/modes/rpc/rpc-client.js.map +1 -1
  70. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  71. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  72. package/dist/modes/rpc/rpc-mode.js +23 -15
  73. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  74. package/dist/package-manager-cli.d.ts +4 -0
  75. package/dist/package-manager-cli.d.ts.map +1 -0
  76. package/dist/package-manager-cli.js +234 -0
  77. package/dist/package-manager-cli.js.map +1 -0
  78. package/docs/extensions.md +34 -26
  79. package/docs/sdk.md +109 -46
  80. package/docs/settings.md +1 -1
  81. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  82. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  83. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  84. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  85. package/examples/extensions/hello.ts +18 -17
  86. package/examples/extensions/with-deps/package-lock.json +2 -2
  87. package/examples/extensions/with-deps/package.json +1 -1
  88. package/examples/sdk/13-session-runtime.ts +30 -12
  89. package/examples/sdk/README.md +2 -0
  90. package/package.json +4 -4
@@ -1,134 +1,83 @@
1
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
2
- import type { Model } from "@mariozechner/pi-ai";
3
- import { AuthStorage } from "./auth-storage.js";
4
- import type { SessionStartEvent, ToolDefinition } from "./extensions/index.js";
5
- import { ModelRegistry } from "./model-registry.js";
6
- import { type DefaultResourceLoaderOptions, type ResourceLoader } from "./resource-loader.js";
7
- import { type CreateAgentSessionResult } from "./sdk.js";
1
+ import type { AgentSession } from "./agent-session.js";
2
+ import type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from "./agent-session-services.js";
3
+ import type { SessionStartEvent } from "./extensions/index.js";
4
+ import type { CreateAgentSessionResult } from "./sdk.js";
8
5
  import { SessionManager } from "./session-manager.js";
9
- import { SettingsManager } from "./settings-manager.js";
10
- import type { Tool } from "./tools/index.js";
11
6
  /**
12
- * Stable bootstrap inputs reused whenever the active runtime is replaced.
7
+ * Result returned by runtime creation.
13
8
  *
14
- * Use this for process-level wiring that should survive `/new`, `/resume`,
15
- * `/fork`, and import flows. Session-local state belongs in the session file
16
- * or in settings, not here.
9
+ * The caller gets the created session, its cwd-bound services, and all
10
+ * diagnostics collected during setup.
17
11
  */
18
- export interface AgentSessionRuntimeBootstrap {
19
- /** Agent directory used for auth, models, settings, sessions, and resource discovery. */
20
- agentDir?: string;
21
- /** Optional shared auth storage. If omitted, file-backed storage under agentDir is used. */
22
- authStorage?: AuthStorage;
23
- /** Initial model for the first created session runtime. */
24
- model?: Model<any>;
25
- /** Initial thinking level for the first created session runtime. */
26
- thinkingLevel?: ThinkingLevel;
27
- /** Optional scoped model list for model cycling and selection. */
28
- scopedModels?: Array<{
29
- model: Model<any>;
30
- thinkingLevel?: ThinkingLevel;
31
- }>;
32
- /** Built-in tool set override. */
33
- tools?: Tool[];
34
- /** Additional custom tools registered directly through the SDK. */
35
- customTools?: ToolDefinition[];
36
- /**
37
- * Resource loader input used for each created runtime.
38
- *
39
- * Pass either a factory that creates a fully custom ResourceLoader for the
40
- * target cwd, or DefaultResourceLoader options without cwd/agentDir/
41
- * settingsManager, which are supplied by the runtime.
42
- */
43
- resourceLoader?: ((cwd: string, agentDir: string) => Promise<ResourceLoader>) | Omit<DefaultResourceLoaderOptions, "cwd" | "agentDir" | "settingsManager">;
12
+ export interface CreateAgentSessionRuntimeResult extends CreateAgentSessionResult {
13
+ services: AgentSessionServices;
14
+ diagnostics: AgentSessionRuntimeDiagnostic[];
44
15
  }
45
- /** Options for creating one concrete runtime instance. */
46
- export interface CreateAgentSessionRuntimeOptions {
47
- /** Working directory for this runtime instance. */
48
- cwd: string;
49
- /** Optional preselected session manager. If omitted, normal session resolution applies. */
50
- sessionManager?: SessionManager;
51
- /** Optional session_start metadata to emit when the runtime binds extensions. */
52
- sessionStartEvent?: SessionStartEvent;
53
- }
54
- type AgentSessionRuntime = CreateAgentSessionResult & {
55
- cwd: string;
56
- agentDir: string;
57
- authStorage: AuthStorage;
58
- modelRegistry: ModelRegistry;
59
- settingsManager: SettingsManager;
60
- resourceLoader: ResourceLoader;
61
- sessionManager: SessionManager;
62
- };
63
16
  /**
64
- * Create one runtime instance containing an AgentSession plus the cwd-bound
65
- * services it depends on.
17
+ * Creates a full runtime for a target cwd and session manager.
66
18
  *
67
- * Most SDK callers should keep the returned value wrapped in an
68
- * AgentSessionRuntimeHost instead of holding it directly. The host owns
69
- * replacing the runtime when switching sessions across files or working
70
- * directories.
19
+ * The factory closes over process-global fixed inputs, recreates cwd-bound
20
+ * services for the effective cwd, resolves session options against those
21
+ * services, and finally creates the AgentSession.
71
22
  */
72
- export declare function createAgentSessionRuntime(bootstrap: AgentSessionRuntimeBootstrap, options: CreateAgentSessionRuntimeOptions): Promise<AgentSessionRuntime>;
23
+ export type CreateAgentSessionRuntimeFactory = (options: {
24
+ cwd: string;
25
+ agentDir: string;
26
+ sessionManager: SessionManager;
27
+ sessionStartEvent?: SessionStartEvent;
28
+ }) => Promise<CreateAgentSessionRuntimeResult>;
73
29
  /**
74
- * Stable wrapper around a replaceable AgentSession runtime.
30
+ * Owns the current AgentSession plus its cwd-bound services.
75
31
  *
76
- * Use this when your application needs `/new`, `/resume`, `/fork`, or import
77
- * behavior. After replacement, read `session` again and rebind any
78
- * session-local subscriptions or extension bindings.
32
+ * Session replacement methods tear down the current runtime first, then create
33
+ * and apply the next runtime. If creation fails, the error is propagated to the
34
+ * caller. The caller is responsible for user-facing error handling.
79
35
  */
80
- export declare class AgentSessionRuntimeHost {
81
- private readonly bootstrap;
82
- private runtime;
83
- constructor(bootstrap: AgentSessionRuntimeBootstrap, runtime: AgentSessionRuntime);
84
- /** The currently active session instance. Re-read this after runtime replacement. */
85
- get session(): import("./agent-session.js").AgentSession;
36
+ export declare class AgentSessionRuntime {
37
+ private _session;
38
+ private _services;
39
+ private readonly createRuntime;
40
+ private _diagnostics;
41
+ private _modelFallbackMessage?;
42
+ constructor(_session: AgentSession, _services: AgentSessionServices, createRuntime: CreateAgentSessionRuntimeFactory, _diagnostics?: AgentSessionRuntimeDiagnostic[], _modelFallbackMessage?: string | undefined);
43
+ get services(): AgentSessionServices;
44
+ get session(): AgentSession;
45
+ get cwd(): string;
46
+ get diagnostics(): readonly AgentSessionRuntimeDiagnostic[];
47
+ get modelFallbackMessage(): string | undefined;
86
48
  private emitBeforeSwitch;
87
49
  private emitBeforeFork;
88
- private replace;
89
- /**
90
- * Replace the active runtime with one opened from an existing session file.
91
- *
92
- * Emits `session_before_switch` before replacement and returns
93
- * `{ cancelled: true }` if an extension vetoes the switch.
94
- */
50
+ private teardownCurrent;
51
+ private apply;
95
52
  switchSession(sessionPath: string): Promise<{
96
53
  cancelled: boolean;
97
54
  }>;
98
- /**
99
- * Replace the active runtime with a fresh session in the current cwd.
100
- *
101
- * `setup` runs after replacement against the new session manager, which lets
102
- * callers seed entries before normal use begins.
103
- */
104
55
  newSession(options?: {
105
- /** Optional parent session path recorded in the new session header. */
106
56
  parentSession?: string;
107
- /** Optional callback for seeding the new session manager after replacement. */
108
57
  setup?: (sessionManager: SessionManager) => Promise<void>;
109
58
  }): Promise<{
110
59
  cancelled: boolean;
111
60
  }>;
112
- /**
113
- * Replace the active runtime with a fork rooted at the given user-message
114
- * entry.
115
- *
116
- * Returns the selected user text so UIs can restore it into the editor after
117
- * the fork completes.
118
- */
119
61
  fork(entryId: string): Promise<{
120
62
  cancelled: boolean;
121
63
  selectedText?: string;
122
64
  }>;
123
- /**
124
- * Import a JSONL session file into the current session directory and replace
125
- * the active runtime with the imported session.
126
- */
127
65
  importFromJsonl(inputPath: string): Promise<{
128
66
  cancelled: boolean;
129
67
  }>;
130
- /** Emit session shutdown for the active runtime and dispose it permanently. */
131
68
  dispose(): Promise<void>;
132
69
  }
133
- export {};
70
+ /**
71
+ * Create the initial runtime from a runtime factory and initial session target.
72
+ *
73
+ * The same factory is stored on the returned AgentSessionRuntime and reused for
74
+ * later /new, /resume, /fork, and import flows.
75
+ */
76
+ export declare function createAgentSessionRuntime(createRuntime: CreateAgentSessionRuntimeFactory, options: {
77
+ cwd: string;
78
+ agentDir: string;
79
+ sessionManager: SessionManager;
80
+ sessionStartEvent?: SessionStartEvent;
81
+ }): Promise<AgentSessionRuntime>;
82
+ export { type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionServicesOptions, createAgentSessionFromServices, createAgentSessionServices, } from "./agent-session-services.js";
134
83
  //# sourceMappingURL=agent-session-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-runtime.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { Model } from \"@mariozechner/pi-ai\";\nimport { getAgentDir } from \"../config.js\";\nimport { AuthStorage } from \"./auth-storage.js\";\nimport type { SessionStartEvent, ToolDefinition } from \"./extensions/index.js\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { DefaultResourceLoader, type DefaultResourceLoaderOptions, type ResourceLoader } from \"./resource-loader.js\";\nimport { type CreateAgentSessionResult, createAgentSession } from \"./sdk.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\nimport type { Tool } from \"./tools/index.js\";\n\n/**\n * Stable bootstrap inputs reused whenever the active runtime is replaced.\n *\n * Use this for process-level wiring that should survive `/new`, `/resume`,\n * `/fork`, and import flows. Session-local state belongs in the session file\n * or in settings, not here.\n */\nexport interface AgentSessionRuntimeBootstrap {\n\t/** Agent directory used for auth, models, settings, sessions, and resource discovery. */\n\tagentDir?: string;\n\t/** Optional shared auth storage. If omitted, file-backed storage under agentDir is used. */\n\tauthStorage?: AuthStorage;\n\t/** Initial model for the first created session runtime. */\n\tmodel?: Model<any>;\n\t/** Initial thinking level for the first created session runtime. */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Optional scoped model list for model cycling and selection. */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\t/** Built-in tool set override. */\n\ttools?: Tool[];\n\t/** Additional custom tools registered directly through the SDK. */\n\tcustomTools?: ToolDefinition[];\n\t/**\n\t * Resource loader input used for each created runtime.\n\t *\n\t * Pass either a factory that creates a fully custom ResourceLoader for the\n\t * target cwd, or DefaultResourceLoader options without cwd/agentDir/\n\t * settingsManager, which are supplied by the runtime.\n\t */\n\tresourceLoader?:\n\t\t| ((cwd: string, agentDir: string) => Promise<ResourceLoader>)\n\t\t| Omit<DefaultResourceLoaderOptions, \"cwd\" | \"agentDir\" | \"settingsManager\">;\n}\n\n/** Options for creating one concrete runtime instance. */\nexport interface CreateAgentSessionRuntimeOptions {\n\t/** Working directory for this runtime instance. */\n\tcwd: string;\n\t/** Optional preselected session manager. If omitted, normal session resolution applies. */\n\tsessionManager?: SessionManager;\n\t/** Optional session_start metadata to emit when the runtime binds extensions. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\ntype AgentSessionRuntime = CreateAgentSessionResult & {\n\tcwd: string;\n\tagentDir: string;\n\tauthStorage: AuthStorage;\n\tmodelRegistry: ModelRegistry;\n\tsettingsManager: SettingsManager;\n\tresourceLoader: ResourceLoader;\n\tsessionManager: SessionManager;\n};\n\n/**\n * Create one runtime instance containing an AgentSession plus the cwd-bound\n * services it depends on.\n *\n * Most SDK callers should keep the returned value wrapped in an\n * AgentSessionRuntimeHost instead of holding it directly. The host owns\n * replacing the runtime when switching sessions across files or working\n * directories.\n */\nexport async function createAgentSessionRuntime(\n\tbootstrap: AgentSessionRuntimeBootstrap,\n\toptions: CreateAgentSessionRuntimeOptions,\n): Promise<AgentSessionRuntime> {\n\tconst cwd = options.cwd;\n\tconst agentDir = bootstrap.agentDir ?? getAgentDir();\n\tconst authStorage = bootstrap.authStorage ?? AuthStorage.create(join(agentDir, \"auth.json\"));\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\tconst modelRegistry = ModelRegistry.create(authStorage, join(agentDir, \"models.json\"));\n\tconst resourceLoader =\n\t\ttypeof bootstrap.resourceLoader === \"function\"\n\t\t\t? await bootstrap.resourceLoader(cwd, agentDir)\n\t\t\t: new DefaultResourceLoader({\n\t\t\t\t\t...(bootstrap.resourceLoader ?? {}),\n\t\t\t\t\tcwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t});\n\tawait resourceLoader.reload();\n\n\tconst extensionsResult = resourceLoader.getExtensions();\n\tfor (const { name, config } of extensionsResult.runtime.pendingProviderRegistrations) {\n\t\tmodelRegistry.registerProvider(name, config);\n\t}\n\textensionsResult.runtime.pendingProviderRegistrations = [];\n\n\tconst created = await createAgentSession({\n\t\tcwd,\n\t\tagentDir,\n\t\tauthStorage,\n\t\tmodelRegistry,\n\t\tsettingsManager,\n\t\tresourceLoader,\n\t\tsessionManager: options.sessionManager,\n\t\tmodel: bootstrap.model,\n\t\tthinkingLevel: bootstrap.thinkingLevel,\n\t\tscopedModels: bootstrap.scopedModels,\n\t\ttools: bootstrap.tools,\n\t\tcustomTools: bootstrap.customTools,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n\n\treturn {\n\t\t...created,\n\t\tcwd,\n\t\tagentDir,\n\t\tauthStorage,\n\t\tmodelRegistry,\n\t\tsettingsManager,\n\t\tresourceLoader,\n\t\tsessionManager: created.session.sessionManager,\n\t};\n}\n\nfunction extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string {\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\n/**\n * Stable wrapper around a replaceable AgentSession runtime.\n *\n * Use this when your application needs `/new`, `/resume`, `/fork`, or import\n * behavior. After replacement, read `session` again and rebind any\n * session-local subscriptions or extension bindings.\n */\nexport class AgentSessionRuntimeHost {\n\tconstructor(\n\t\tprivate readonly bootstrap: AgentSessionRuntimeBootstrap,\n\t\tprivate runtime: AgentSessionRuntime,\n\t) {}\n\n\t/** The currently active session instance. Re-read this after runtime replacement. */\n\tget session() {\n\t\treturn this.runtime.session;\n\t}\n\n\tprivate async emitBeforeSwitch(\n\t\treason: \"new\" | \"resume\",\n\t\ttargetSessionFile?: string,\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.runtime.session.extensionRunner;\n\t\tif (!runner?.hasHandlers(\"session_before_switch\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_switch\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async emitBeforeFork(entryId: string): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.runtime.session.extensionRunner;\n\t\tif (!runner?.hasHandlers(\"session_before_fork\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_fork\",\n\t\t\tentryId,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async replace(options: CreateAgentSessionRuntimeOptions): Promise<void> {\n\t\tconst nextRuntime = await createAgentSessionRuntime(this.bootstrap, options);\n\t\tawait emitSessionShutdownEvent(this.runtime.session.extensionRunner);\n\t\tthis.runtime.session.dispose();\n\t\tif (process.cwd() !== nextRuntime.cwd) {\n\t\t\tprocess.chdir(nextRuntime.cwd);\n\t\t}\n\t\tthis.runtime = nextRuntime;\n\t}\n\n\t/**\n\t * Replace the active runtime with one opened from an existing session file.\n\t *\n\t * Emits `session_before_switch` before replacement and returns\n\t * `{ cancelled: true }` if an extension vetoes the switch.\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", sessionPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.runtime.session.sessionFile;\n\t\tconst sessionManager = SessionManager.open(sessionPath);\n\t\tawait this.replace({\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t});\n\t\treturn { cancelled: false };\n\t}\n\n\t/**\n\t * Replace the active runtime with a fresh session in the current cwd.\n\t *\n\t * `setup` runs after replacement against the new session manager, which lets\n\t * callers seed entries before normal use begins.\n\t */\n\tasync newSession(options?: {\n\t\t/** Optional parent session path recorded in the new session header. */\n\t\tparentSession?: string;\n\t\t/** Optional callback for seeding the new session manager after replacement. */\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"new\");\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.runtime.session.sessionFile;\n\t\tconst sessionDir = this.runtime.sessionManager.getSessionDir();\n\t\tconst sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);\n\t\tif (options?.parentSession) {\n\t\t\tsessionManager.newSession({ parentSession: options.parentSession });\n\t\t}\n\t\tawait this.replace({\n\t\t\tcwd: this.runtime.cwd,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"new\", previousSessionFile },\n\t\t});\n\t\tif (options?.setup) {\n\t\t\tawait options.setup(this.runtime.sessionManager);\n\t\t\tthis.runtime.session.agent.state.messages = this.runtime.sessionManager.buildSessionContext().messages;\n\t\t}\n\t\treturn { cancelled: false };\n\t}\n\n\t/**\n\t * Replace the active runtime with a fork rooted at the given user-message\n\t * entry.\n\t *\n\t * Returns the selected user text so UIs can restore it into the editor after\n\t * the fork completes.\n\t */\n\tasync fork(entryId: string): Promise<{ cancelled: boolean; selectedText?: string }> {\n\t\tconst beforeResult = await this.emitBeforeFork(entryId);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn { cancelled: true };\n\t\t}\n\n\t\tconst selectedEntry = this.runtime.sessionManager.getEntry(entryId);\n\t\tif (!selectedEntry || selectedEntry.type !== \"message\" || selectedEntry.message.role !== \"user\") {\n\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t}\n\n\t\tconst previousSessionFile = this.runtime.session.sessionFile;\n\t\tconst selectedText = extractUserMessageText(selectedEntry.message.content);\n\t\tif (this.runtime.sessionManager.isPersisted()) {\n\t\t\tconst currentSessionFile = this.runtime.session.sessionFile!;\n\t\t\tconst sessionDir = this.runtime.sessionManager.getSessionDir();\n\t\t\tif (!selectedEntry.parentId) {\n\t\t\t\tconst sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);\n\t\t\t\tsessionManager.newSession({ parentSession: currentSessionFile });\n\t\t\t\tawait this.replace({\n\t\t\t\t\tcwd: this.runtime.cwd,\n\t\t\t\t\tsessionManager,\n\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t});\n\t\t\t\treturn { cancelled: false, selectedText };\n\t\t\t}\n\n\t\t\tconst sourceManager = SessionManager.open(currentSessionFile, sessionDir);\n\t\t\tconst forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId)!;\n\t\t\tconst sessionManager = SessionManager.open(forkedSessionPath, sessionDir);\n\t\t\tawait this.replace({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t});\n\t\t\treturn { cancelled: false, selectedText };\n\t\t}\n\n\t\tconst sessionManager = this.runtime.sessionManager;\n\t\tif (!selectedEntry.parentId) {\n\t\t\tsessionManager.newSession({ parentSession: this.runtime.session.sessionFile });\n\t\t} else {\n\t\t\tsessionManager.createBranchedSession(selectedEntry.parentId);\n\t\t}\n\t\tawait this.replace({\n\t\t\tcwd: this.runtime.cwd,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t});\n\t\treturn { cancelled: false, selectedText };\n\t}\n\n\t/**\n\t * Import a JSONL session file into the current session directory and replace\n\t * the active runtime with the imported session.\n\t */\n\tasync importFromJsonl(inputPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst resolvedPath = resolve(inputPath);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tthrow new Error(`File not found: ${resolvedPath}`);\n\t\t}\n\n\t\tconst sessionDir = this.runtime.sessionManager.getSessionDir();\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\n\t\tconst destinationPath = join(sessionDir, basename(resolvedPath));\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", destinationPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.runtime.session.sessionFile;\n\t\tif (resolve(destinationPath) !== resolvedPath) {\n\t\t\tcopyFileSync(resolvedPath, destinationPath);\n\t\t}\n\n\t\tconst sessionManager = SessionManager.open(destinationPath, sessionDir);\n\t\tawait this.replace({\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t});\n\t\treturn { cancelled: false };\n\t}\n\n\t/** Emit session shutdown for the active runtime and dispose it permanently. */\n\tasync dispose(): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.runtime.session.extensionRunner);\n\t\tthis.runtime.session.dispose();\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-session-runtime.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,6BAA6B,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACvG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,+BAAgC,SAAQ,wBAAwB;IAChF,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,EAAE,6BAA6B,EAAE,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gCAAgC,GAAG,CAAC,OAAO,EAAE;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACtC,KAAK,OAAO,CAAC,+BAA+B,CAAC,CAAC;AAa/C;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAE9B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,qBAAqB,CAAC;IAL/B,YACS,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,oBAAoB,EACtB,aAAa,EAAE,gCAAgC,EACxD,YAAY,GAAE,6BAA6B,EAAO,EAClD,qBAAqB,CAAC,oBAAQ,EACnC;IAEJ,IAAI,QAAQ,IAAI,oBAAoB,CAEnC;IAED,IAAI,OAAO,IAAI,YAAY,CAE1B;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,WAAW,IAAI,SAAS,6BAA6B,EAAE,CAE1D;IAED,IAAI,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAE7C;YAEa,gBAAgB;YAiBhB,cAAc;YAad,eAAe;IAK7B,OAAO,CAAC,KAAK;IAUP,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAkBxE;IAEK,UAAU,CAAC,OAAO,CAAC,EAAE;QAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1D,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CA2BlC;IAEK,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAoElF;IAEK,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAiCxE;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAG7B;CACD;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC9C,aAAa,EAAE,gCAAgC,EAC/C,OAAO,EAAE;IACR,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACtC,GACC,OAAO,CAAC,mBAAmB,CAAC,CAY9B;AAED,OAAO,EACN,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,qCAAqC,EAC1C,KAAK,iCAAiC,EACtC,8BAA8B,EAC9B,0BAA0B,GAC1B,MAAM,6BAA6B,CAAC","sourcesContent":["import { copyFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport type { AgentSession } from \"./agent-session.js\";\nimport type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from \"./agent-session-services.js\";\nimport type { SessionStartEvent } from \"./extensions/index.js\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.js\";\nimport type { CreateAgentSessionResult } from \"./sdk.js\";\nimport { SessionManager } from \"./session-manager.js\";\n\n/**\n * Result returned by runtime creation.\n *\n * The caller gets the created session, its cwd-bound services, and all\n * diagnostics collected during setup.\n */\nexport interface CreateAgentSessionRuntimeResult extends CreateAgentSessionResult {\n\tservices: AgentSessionServices;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n}\n\n/**\n * Creates a full runtime for a target cwd and session manager.\n *\n * The factory closes over process-global fixed inputs, recreates cwd-bound\n * services for the effective cwd, resolves session options against those\n * services, and finally creates the AgentSession.\n */\nexport type CreateAgentSessionRuntimeFactory = (options: {\n\tcwd: string;\n\tagentDir: string;\n\tsessionManager: SessionManager;\n\tsessionStartEvent?: SessionStartEvent;\n}) => Promise<CreateAgentSessionRuntimeResult>;\n\nfunction extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string {\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\n/**\n * Owns the current AgentSession plus its cwd-bound services.\n *\n * Session replacement methods tear down the current runtime first, then create\n * and apply the next runtime. If creation fails, the error is propagated to the\n * caller. The caller is responsible for user-facing error handling.\n */\nexport class AgentSessionRuntime {\n\tconstructor(\n\t\tprivate _session: AgentSession,\n\t\tprivate _services: AgentSessionServices,\n\t\tprivate readonly createRuntime: CreateAgentSessionRuntimeFactory,\n\t\tprivate _diagnostics: AgentSessionRuntimeDiagnostic[] = [],\n\t\tprivate _modelFallbackMessage?: string,\n\t) {}\n\n\tget services(): AgentSessionServices {\n\t\treturn this._services;\n\t}\n\n\tget session(): AgentSession {\n\t\treturn this._session;\n\t}\n\n\tget cwd(): string {\n\t\treturn this._services.cwd;\n\t}\n\n\tget diagnostics(): readonly AgentSessionRuntimeDiagnostic[] {\n\t\treturn this._diagnostics;\n\t}\n\n\tget modelFallbackMessage(): string | undefined {\n\t\treturn this._modelFallbackMessage;\n\t}\n\n\tprivate async emitBeforeSwitch(\n\t\treason: \"new\" | \"resume\",\n\t\ttargetSessionFile?: string,\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner?.hasHandlers(\"session_before_switch\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_switch\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async emitBeforeFork(entryId: string): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner?.hasHandlers(\"session_before_fork\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_fork\",\n\t\t\tentryId,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async teardownCurrent(): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner);\n\t\tthis.session.dispose();\n\t}\n\n\tprivate apply(result: CreateAgentSessionRuntimeResult): void {\n\t\tif (process.cwd() !== result.services.cwd) {\n\t\t\tprocess.chdir(result.services.cwd);\n\t\t}\n\t\tthis._session = result.session;\n\t\tthis._services = result.services;\n\t\tthis._diagnostics = result.diagnostics;\n\t\tthis._modelFallbackMessage = result.modelFallbackMessage;\n\t}\n\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", sessionPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionManager = SessionManager.open(sessionPath);\n\t\tawait this.teardownCurrent();\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync newSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"new\");\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\tif (options?.parentSession) {\n\t\t\tsessionManager.newSession({ parentSession: options.parentSession });\n\t\t}\n\n\t\tawait this.teardownCurrent();\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"new\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tif (options?.setup) {\n\t\t\tawait options.setup(this.session.sessionManager);\n\t\t\tthis.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages;\n\t\t}\n\t\treturn { cancelled: false };\n\t}\n\n\tasync fork(entryId: string): Promise<{ cancelled: boolean; selectedText?: string }> {\n\t\tconst beforeResult = await this.emitBeforeFork(entryId);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn { cancelled: true };\n\t\t}\n\n\t\tconst selectedEntry = this.session.sessionManager.getEntry(entryId);\n\t\tif (!selectedEntry || selectedEntry.type !== \"message\" || selectedEntry.message.role !== \"user\") {\n\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst selectedText = extractUserMessageText(selectedEntry.message.content);\n\t\tif (this.session.sessionManager.isPersisted()) {\n\t\t\tconst currentSessionFile = this.session.sessionFile;\n\t\t\tif (!currentSessionFile) {\n\t\t\t\tthrow new Error(\"Persisted session is missing a session file\");\n\t\t\t}\n\t\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\t\tif (!selectedEntry.parentId) {\n\t\t\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\t\t\tsessionManager.newSession({ parentSession: currentSessionFile });\n\t\t\t\tawait this.teardownCurrent();\n\t\t\t\tthis.apply(\n\t\t\t\t\tawait this.createRuntime({\n\t\t\t\t\t\tcwd: this.cwd,\n\t\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\t\tsessionManager,\n\t\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn { cancelled: false, selectedText };\n\t\t\t}\n\n\t\t\tconst sourceManager = SessionManager.open(currentSessionFile, sessionDir);\n\t\t\tconst forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId);\n\t\t\tif (!forkedSessionPath) {\n\t\t\t\tthrow new Error(\"Failed to create forked session\");\n\t\t\t}\n\t\t\tconst sessionManager = SessionManager.open(forkedSessionPath, sessionDir);\n\t\t\tawait this.teardownCurrent();\n\t\t\tthis.apply(\n\t\t\t\tawait this.createRuntime({\n\t\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\tsessionManager,\n\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn { cancelled: false, selectedText };\n\t\t}\n\n\t\tconst sessionManager = this.session.sessionManager;\n\t\tif (!selectedEntry.parentId) {\n\t\t\tsessionManager.newSession({ parentSession: this.session.sessionFile });\n\t\t} else {\n\t\t\tsessionManager.createBranchedSession(selectedEntry.parentId);\n\t\t}\n\t\tawait this.teardownCurrent();\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\treturn { cancelled: false, selectedText };\n\t}\n\n\tasync importFromJsonl(inputPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst resolvedPath = resolve(inputPath);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tthrow new Error(`File not found: ${resolvedPath}`);\n\t\t}\n\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\n\t\tconst destinationPath = join(sessionDir, basename(resolvedPath));\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", destinationPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tif (resolve(destinationPath) !== resolvedPath) {\n\t\t\tcopyFileSync(resolvedPath, destinationPath);\n\t\t}\n\n\t\tconst sessionManager = SessionManager.open(destinationPath, sessionDir);\n\t\tawait this.teardownCurrent();\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner);\n\t\tthis.session.dispose();\n\t}\n}\n\n/**\n * Create the initial runtime from a runtime factory and initial session target.\n *\n * The same factory is stored on the returned AgentSessionRuntime and reused for\n * later /new, /resume, /fork, and import flows.\n */\nexport async function createAgentSessionRuntime(\n\tcreateRuntime: CreateAgentSessionRuntimeFactory,\n\toptions: {\n\t\tcwd: string;\n\t\tagentDir: string;\n\t\tsessionManager: SessionManager;\n\t\tsessionStartEvent?: SessionStartEvent;\n\t},\n): Promise<AgentSessionRuntime> {\n\tconst result = await createRuntime(options);\n\tif (process.cwd() !== result.services.cwd) {\n\t\tprocess.chdir(result.services.cwd);\n\t}\n\treturn new AgentSessionRuntime(\n\t\tresult.session,\n\t\tresult.services,\n\t\tcreateRuntime,\n\t\tresult.diagnostics,\n\t\tresult.modelFallbackMessage,\n\t);\n}\n\nexport {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionFromServicesOptions,\n\ttype CreateAgentSessionServicesOptions,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./agent-session-services.js\";\n"]}
@@ -1,68 +1,7 @@
1
1
  import { copyFileSync, existsSync, mkdirSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
- import { getAgentDir } from "../config.js";
4
- import { AuthStorage } from "./auth-storage.js";
5
3
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
6
- import { ModelRegistry } from "./model-registry.js";
7
- import { DefaultResourceLoader } from "./resource-loader.js";
8
- import { createAgentSession } from "./sdk.js";
9
4
  import { SessionManager } from "./session-manager.js";
10
- import { SettingsManager } from "./settings-manager.js";
11
- /**
12
- * Create one runtime instance containing an AgentSession plus the cwd-bound
13
- * services it depends on.
14
- *
15
- * Most SDK callers should keep the returned value wrapped in an
16
- * AgentSessionRuntimeHost instead of holding it directly. The host owns
17
- * replacing the runtime when switching sessions across files or working
18
- * directories.
19
- */
20
- export async function createAgentSessionRuntime(bootstrap, options) {
21
- const cwd = options.cwd;
22
- const agentDir = bootstrap.agentDir ?? getAgentDir();
23
- const authStorage = bootstrap.authStorage ?? AuthStorage.create(join(agentDir, "auth.json"));
24
- const settingsManager = SettingsManager.create(cwd, agentDir);
25
- const modelRegistry = ModelRegistry.create(authStorage, join(agentDir, "models.json"));
26
- const resourceLoader = typeof bootstrap.resourceLoader === "function"
27
- ? await bootstrap.resourceLoader(cwd, agentDir)
28
- : new DefaultResourceLoader({
29
- ...(bootstrap.resourceLoader ?? {}),
30
- cwd,
31
- agentDir,
32
- settingsManager,
33
- });
34
- await resourceLoader.reload();
35
- const extensionsResult = resourceLoader.getExtensions();
36
- for (const { name, config } of extensionsResult.runtime.pendingProviderRegistrations) {
37
- modelRegistry.registerProvider(name, config);
38
- }
39
- extensionsResult.runtime.pendingProviderRegistrations = [];
40
- const created = await createAgentSession({
41
- cwd,
42
- agentDir,
43
- authStorage,
44
- modelRegistry,
45
- settingsManager,
46
- resourceLoader,
47
- sessionManager: options.sessionManager,
48
- model: bootstrap.model,
49
- thinkingLevel: bootstrap.thinkingLevel,
50
- scopedModels: bootstrap.scopedModels,
51
- tools: bootstrap.tools,
52
- customTools: bootstrap.customTools,
53
- sessionStartEvent: options.sessionStartEvent,
54
- });
55
- return {
56
- ...created,
57
- cwd,
58
- agentDir,
59
- authStorage,
60
- modelRegistry,
61
- settingsManager,
62
- resourceLoader,
63
- sessionManager: created.session.sessionManager,
64
- };
65
- }
66
5
  function extractUserMessageText(content) {
67
6
  if (typeof content === "string") {
68
7
  return content;
@@ -73,23 +12,37 @@ function extractUserMessageText(content) {
73
12
  .join("");
74
13
  }
75
14
  /**
76
- * Stable wrapper around a replaceable AgentSession runtime.
15
+ * Owns the current AgentSession plus its cwd-bound services.
77
16
  *
78
- * Use this when your application needs `/new`, `/resume`, `/fork`, or import
79
- * behavior. After replacement, read `session` again and rebind any
80
- * session-local subscriptions or extension bindings.
17
+ * Session replacement methods tear down the current runtime first, then create
18
+ * and apply the next runtime. If creation fails, the error is propagated to the
19
+ * caller. The caller is responsible for user-facing error handling.
81
20
  */
82
- export class AgentSessionRuntimeHost {
83
- constructor(bootstrap, runtime) {
84
- this.bootstrap = bootstrap;
85
- this.runtime = runtime;
21
+ export class AgentSessionRuntime {
22
+ constructor(_session, _services, createRuntime, _diagnostics = [], _modelFallbackMessage) {
23
+ this._session = _session;
24
+ this._services = _services;
25
+ this.createRuntime = createRuntime;
26
+ this._diagnostics = _diagnostics;
27
+ this._modelFallbackMessage = _modelFallbackMessage;
28
+ }
29
+ get services() {
30
+ return this._services;
86
31
  }
87
- /** The currently active session instance. Re-read this after runtime replacement. */
88
32
  get session() {
89
- return this.runtime.session;
33
+ return this._session;
34
+ }
35
+ get cwd() {
36
+ return this._services.cwd;
37
+ }
38
+ get diagnostics() {
39
+ return this._diagnostics;
40
+ }
41
+ get modelFallbackMessage() {
42
+ return this._modelFallbackMessage;
90
43
  }
91
44
  async emitBeforeSwitch(reason, targetSessionFile) {
92
- const runner = this.runtime.session.extensionRunner;
45
+ const runner = this.session.extensionRunner;
93
46
  if (!runner?.hasHandlers("session_before_switch")) {
94
47
  return { cancelled: false };
95
48
  }
@@ -101,7 +54,7 @@ export class AgentSessionRuntimeHost {
101
54
  return { cancelled: result?.cancel === true };
102
55
  }
103
56
  async emitBeforeFork(entryId) {
104
- const runner = this.runtime.session.extensionRunner;
57
+ const runner = this.session.extensionRunner;
105
58
  if (!runner?.hasHandlers("session_before_fork")) {
106
59
  return { cancelled: false };
107
60
  }
@@ -111,128 +64,125 @@ export class AgentSessionRuntimeHost {
111
64
  });
112
65
  return { cancelled: result?.cancel === true };
113
66
  }
114
- async replace(options) {
115
- const nextRuntime = await createAgentSessionRuntime(this.bootstrap, options);
116
- await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
117
- this.runtime.session.dispose();
118
- if (process.cwd() !== nextRuntime.cwd) {
119
- process.chdir(nextRuntime.cwd);
120
- }
121
- this.runtime = nextRuntime;
67
+ async teardownCurrent() {
68
+ await emitSessionShutdownEvent(this.session.extensionRunner);
69
+ this.session.dispose();
70
+ }
71
+ apply(result) {
72
+ if (process.cwd() !== result.services.cwd) {
73
+ process.chdir(result.services.cwd);
74
+ }
75
+ this._session = result.session;
76
+ this._services = result.services;
77
+ this._diagnostics = result.diagnostics;
78
+ this._modelFallbackMessage = result.modelFallbackMessage;
122
79
  }
123
- /**
124
- * Replace the active runtime with one opened from an existing session file.
125
- *
126
- * Emits `session_before_switch` before replacement and returns
127
- * `{ cancelled: true }` if an extension vetoes the switch.
128
- */
129
80
  async switchSession(sessionPath) {
130
81
  const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
131
82
  if (beforeResult.cancelled) {
132
83
  return beforeResult;
133
84
  }
134
- const previousSessionFile = this.runtime.session.sessionFile;
85
+ const previousSessionFile = this.session.sessionFile;
135
86
  const sessionManager = SessionManager.open(sessionPath);
136
- await this.replace({
87
+ await this.teardownCurrent();
88
+ this.apply(await this.createRuntime({
137
89
  cwd: sessionManager.getCwd(),
90
+ agentDir: this.services.agentDir,
138
91
  sessionManager,
139
92
  sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
140
- });
93
+ }));
141
94
  return { cancelled: false };
142
95
  }
143
- /**
144
- * Replace the active runtime with a fresh session in the current cwd.
145
- *
146
- * `setup` runs after replacement against the new session manager, which lets
147
- * callers seed entries before normal use begins.
148
- */
149
96
  async newSession(options) {
150
97
  const beforeResult = await this.emitBeforeSwitch("new");
151
98
  if (beforeResult.cancelled) {
152
99
  return beforeResult;
153
100
  }
154
- const previousSessionFile = this.runtime.session.sessionFile;
155
- const sessionDir = this.runtime.sessionManager.getSessionDir();
156
- const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
101
+ const previousSessionFile = this.session.sessionFile;
102
+ const sessionDir = this.session.sessionManager.getSessionDir();
103
+ const sessionManager = SessionManager.create(this.cwd, sessionDir);
157
104
  if (options?.parentSession) {
158
105
  sessionManager.newSession({ parentSession: options.parentSession });
159
106
  }
160
- await this.replace({
161
- cwd: this.runtime.cwd,
107
+ await this.teardownCurrent();
108
+ this.apply(await this.createRuntime({
109
+ cwd: this.cwd,
110
+ agentDir: this.services.agentDir,
162
111
  sessionManager,
163
112
  sessionStartEvent: { type: "session_start", reason: "new", previousSessionFile },
164
- });
113
+ }));
165
114
  if (options?.setup) {
166
- await options.setup(this.runtime.sessionManager);
167
- this.runtime.session.agent.state.messages = this.runtime.sessionManager.buildSessionContext().messages;
115
+ await options.setup(this.session.sessionManager);
116
+ this.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages;
168
117
  }
169
118
  return { cancelled: false };
170
119
  }
171
- /**
172
- * Replace the active runtime with a fork rooted at the given user-message
173
- * entry.
174
- *
175
- * Returns the selected user text so UIs can restore it into the editor after
176
- * the fork completes.
177
- */
178
120
  async fork(entryId) {
179
121
  const beforeResult = await this.emitBeforeFork(entryId);
180
122
  if (beforeResult.cancelled) {
181
123
  return { cancelled: true };
182
124
  }
183
- const selectedEntry = this.runtime.sessionManager.getEntry(entryId);
125
+ const selectedEntry = this.session.sessionManager.getEntry(entryId);
184
126
  if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
185
127
  throw new Error("Invalid entry ID for forking");
186
128
  }
187
- const previousSessionFile = this.runtime.session.sessionFile;
129
+ const previousSessionFile = this.session.sessionFile;
188
130
  const selectedText = extractUserMessageText(selectedEntry.message.content);
189
- if (this.runtime.sessionManager.isPersisted()) {
190
- const currentSessionFile = this.runtime.session.sessionFile;
191
- const sessionDir = this.runtime.sessionManager.getSessionDir();
131
+ if (this.session.sessionManager.isPersisted()) {
132
+ const currentSessionFile = this.session.sessionFile;
133
+ if (!currentSessionFile) {
134
+ throw new Error("Persisted session is missing a session file");
135
+ }
136
+ const sessionDir = this.session.sessionManager.getSessionDir();
192
137
  if (!selectedEntry.parentId) {
193
- const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
138
+ const sessionManager = SessionManager.create(this.cwd, sessionDir);
194
139
  sessionManager.newSession({ parentSession: currentSessionFile });
195
- await this.replace({
196
- cwd: this.runtime.cwd,
140
+ await this.teardownCurrent();
141
+ this.apply(await this.createRuntime({
142
+ cwd: this.cwd,
143
+ agentDir: this.services.agentDir,
197
144
  sessionManager,
198
145
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
199
- });
146
+ }));
200
147
  return { cancelled: false, selectedText };
201
148
  }
202
149
  const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
203
150
  const forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId);
151
+ if (!forkedSessionPath) {
152
+ throw new Error("Failed to create forked session");
153
+ }
204
154
  const sessionManager = SessionManager.open(forkedSessionPath, sessionDir);
205
- await this.replace({
155
+ await this.teardownCurrent();
156
+ this.apply(await this.createRuntime({
206
157
  cwd: sessionManager.getCwd(),
158
+ agentDir: this.services.agentDir,
207
159
  sessionManager,
208
160
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
209
- });
161
+ }));
210
162
  return { cancelled: false, selectedText };
211
163
  }
212
- const sessionManager = this.runtime.sessionManager;
164
+ const sessionManager = this.session.sessionManager;
213
165
  if (!selectedEntry.parentId) {
214
- sessionManager.newSession({ parentSession: this.runtime.session.sessionFile });
166
+ sessionManager.newSession({ parentSession: this.session.sessionFile });
215
167
  }
216
168
  else {
217
169
  sessionManager.createBranchedSession(selectedEntry.parentId);
218
170
  }
219
- await this.replace({
220
- cwd: this.runtime.cwd,
171
+ await this.teardownCurrent();
172
+ this.apply(await this.createRuntime({
173
+ cwd: this.cwd,
174
+ agentDir: this.services.agentDir,
221
175
  sessionManager,
222
176
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
223
- });
177
+ }));
224
178
  return { cancelled: false, selectedText };
225
179
  }
226
- /**
227
- * Import a JSONL session file into the current session directory and replace
228
- * the active runtime with the imported session.
229
- */
230
180
  async importFromJsonl(inputPath) {
231
181
  const resolvedPath = resolve(inputPath);
232
182
  if (!existsSync(resolvedPath)) {
233
183
  throw new Error(`File not found: ${resolvedPath}`);
234
184
  }
235
- const sessionDir = this.runtime.sessionManager.getSessionDir();
185
+ const sessionDir = this.session.sessionManager.getSessionDir();
236
186
  if (!existsSync(sessionDir)) {
237
187
  mkdirSync(sessionDir, { recursive: true });
238
188
  }
@@ -241,22 +191,37 @@ export class AgentSessionRuntimeHost {
241
191
  if (beforeResult.cancelled) {
242
192
  return beforeResult;
243
193
  }
244
- const previousSessionFile = this.runtime.session.sessionFile;
194
+ const previousSessionFile = this.session.sessionFile;
245
195
  if (resolve(destinationPath) !== resolvedPath) {
246
196
  copyFileSync(resolvedPath, destinationPath);
247
197
  }
248
198
  const sessionManager = SessionManager.open(destinationPath, sessionDir);
249
- await this.replace({
199
+ await this.teardownCurrent();
200
+ this.apply(await this.createRuntime({
250
201
  cwd: sessionManager.getCwd(),
202
+ agentDir: this.services.agentDir,
251
203
  sessionManager,
252
204
  sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
253
- });
205
+ }));
254
206
  return { cancelled: false };
255
207
  }
256
- /** Emit session shutdown for the active runtime and dispose it permanently. */
257
208
  async dispose() {
258
- await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
259
- this.runtime.session.dispose();
209
+ await emitSessionShutdownEvent(this.session.extensionRunner);
210
+ this.session.dispose();
211
+ }
212
+ }
213
+ /**
214
+ * Create the initial runtime from a runtime factory and initial session target.
215
+ *
216
+ * The same factory is stored on the returned AgentSessionRuntime and reused for
217
+ * later /new, /resume, /fork, and import flows.
218
+ */
219
+ export async function createAgentSessionRuntime(createRuntime, options) {
220
+ const result = await createRuntime(options);
221
+ if (process.cwd() !== result.services.cwd) {
222
+ process.chdir(result.services.cwd);
260
223
  }
224
+ return new AgentSessionRuntime(result.session, result.services, createRuntime, result.diagnostics, result.modelFallbackMessage);
261
225
  }
226
+ export { createAgentSessionFromServices, createAgentSessionServices, } from "./agent-session-services.js";
262
227
  //# sourceMappingURL=agent-session-runtime.js.map