@codex-infinity/pi-infinity 0.64.3 → 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 (87) hide show
  1. package/CHANGELOG.md +54 -34
  2. package/README.md +1 -1
  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 -104
  8. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  9. package/dist/core/agent-session-runtime.js +103 -141
  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 +1 -1
  19. package/dist/core/extensions/index.d.ts.map +1 -1
  20. package/dist/core/extensions/index.js.map +1 -1
  21. package/dist/core/extensions/types.d.ts +1 -13
  22. package/dist/core/extensions/types.d.ts.map +1 -1
  23. package/dist/core/extensions/types.js.map +1 -1
  24. package/dist/core/index.d.ts +2 -1
  25. package/dist/core/index.d.ts.map +1 -1
  26. package/dist/core/index.js +2 -1
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/core/keybindings.d.ts +4 -1
  29. package/dist/core/keybindings.d.ts.map +1 -1
  30. package/dist/core/keybindings.js +3 -14
  31. package/dist/core/keybindings.js.map +1 -1
  32. package/dist/core/package-manager.d.ts +20 -0
  33. package/dist/core/package-manager.d.ts.map +1 -1
  34. package/dist/core/package-manager.js +32 -0
  35. package/dist/core/package-manager.js.map +1 -1
  36. package/dist/core/resource-loader.d.ts.map +1 -1
  37. package/dist/core/resource-loader.js +21 -0
  38. package/dist/core/resource-loader.js.map +1 -1
  39. package/dist/core/sdk.d.ts +1 -1
  40. package/dist/core/sdk.d.ts.map +1 -1
  41. package/dist/core/sdk.js +1 -1
  42. package/dist/core/sdk.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +1 -1
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +2 -1
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/main.d.ts.map +1 -1
  52. package/dist/main.js +202 -457
  53. package/dist/main.js.map +1 -1
  54. package/dist/migrations.d.ts.map +1 -1
  55. package/dist/migrations.js +20 -0
  56. package/dist/migrations.js.map +1 -1
  57. package/dist/modes/interactive/interactive-mode.d.ts +3 -2
  58. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.js +56 -29
  60. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  61. package/dist/modes/print-mode.d.ts +2 -2
  62. package/dist/modes/print-mode.d.ts.map +1 -1
  63. package/dist/modes/print-mode.js +4 -0
  64. package/dist/modes/print-mode.js.map +1 -1
  65. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  66. package/dist/modes/rpc/rpc-client.js +1 -0
  67. package/dist/modes/rpc/rpc-client.js.map +1 -1
  68. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  69. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  70. package/dist/modes/rpc/rpc-mode.js +23 -15
  71. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  72. package/dist/package-manager-cli.d.ts +4 -0
  73. package/dist/package-manager-cli.d.ts.map +1 -0
  74. package/dist/package-manager-cli.js +234 -0
  75. package/dist/package-manager-cli.js.map +1 -0
  76. package/docs/extensions.md +2 -26
  77. package/docs/sdk.md +97 -37
  78. package/docs/settings.md +1 -1
  79. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  80. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  81. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  82. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  83. package/examples/extensions/with-deps/package-lock.json +2 -2
  84. package/examples/extensions/with-deps/package.json +1 -1
  85. package/examples/sdk/13-session-runtime.ts +30 -12
  86. package/examples/sdk/README.md +2 -0
  87. package/package.json +4 -4
@@ -1,136 +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 preloaded resource loader to reuse instead of creating and reloading one. */
52
- resourceLoader?: ResourceLoader;
53
- /** Optional session_start metadata to emit when the runtime binds extensions. */
54
- sessionStartEvent?: SessionStartEvent;
55
- }
56
- type AgentSessionRuntime = CreateAgentSessionResult & {
57
- cwd: string;
58
- agentDir: string;
59
- authStorage: AuthStorage;
60
- modelRegistry: ModelRegistry;
61
- settingsManager: SettingsManager;
62
- resourceLoader: ResourceLoader;
63
- sessionManager: SessionManager;
64
- };
65
16
  /**
66
- * Create one runtime instance containing an AgentSession plus the cwd-bound
67
- * services it depends on.
17
+ * Creates a full runtime for a target cwd and session manager.
68
18
  *
69
- * Most SDK callers should keep the returned value wrapped in an
70
- * AgentSessionRuntimeHost instead of holding it directly. The host owns
71
- * replacing the runtime when switching sessions across files or working
72
- * 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.
73
22
  */
74
- 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>;
75
29
  /**
76
- * Stable wrapper around a replaceable AgentSession runtime.
30
+ * Owns the current AgentSession plus its cwd-bound services.
77
31
  *
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.
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.
81
35
  */
82
- export declare class AgentSessionRuntimeHost {
83
- private readonly bootstrap;
84
- private runtime;
85
- constructor(bootstrap: AgentSessionRuntimeBootstrap, runtime: AgentSessionRuntime);
86
- /** The currently active session instance. Re-read this after runtime replacement. */
87
- 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;
88
48
  private emitBeforeSwitch;
89
49
  private emitBeforeFork;
90
- private replace;
91
- /**
92
- * Replace the active runtime with one opened from an existing session file.
93
- *
94
- * Emits `session_before_switch` before replacement and returns
95
- * `{ cancelled: true }` if an extension vetoes the switch.
96
- */
50
+ private teardownCurrent;
51
+ private apply;
97
52
  switchSession(sessionPath: string): Promise<{
98
53
  cancelled: boolean;
99
54
  }>;
100
- /**
101
- * Replace the active runtime with a fresh session in the current cwd.
102
- *
103
- * `setup` runs after replacement against the new session manager, which lets
104
- * callers seed entries before normal use begins.
105
- */
106
55
  newSession(options?: {
107
- /** Optional parent session path recorded in the new session header. */
108
56
  parentSession?: string;
109
- /** Optional callback for seeding the new session manager after replacement. */
110
57
  setup?: (sessionManager: SessionManager) => Promise<void>;
111
58
  }): Promise<{
112
59
  cancelled: boolean;
113
60
  }>;
114
- /**
115
- * Replace the active runtime with a fork rooted at the given user-message
116
- * entry.
117
- *
118
- * Returns the selected user text so UIs can restore it into the editor after
119
- * the fork completes.
120
- */
121
61
  fork(entryId: string): Promise<{
122
62
  cancelled: boolean;
123
63
  selectedText?: string;
124
64
  }>;
125
- /**
126
- * Import a JSONL session file into the current session directory and replace
127
- * the active runtime with the imported session.
128
- */
129
65
  importFromJsonl(inputPath: string): Promise<{
130
66
  cancelled: boolean;
131
67
  }>;
132
- /** Emit session shutdown for the active runtime and dispose it permanently. */
133
68
  dispose(): Promise<void>;
134
69
  }
135
- 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";
136
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 preloaded resource loader to reuse instead of creating and reloading one. */\n\tresourceLoader?: ResourceLoader;\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\toptions.resourceLoader ??\n\t\t(typeof 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\tif (!options.resourceLoader) {\n\t\tawait resourceLoader.reload();\n\t}\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,71 +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 = options.resourceLoader ??
27
- (typeof bootstrap.resourceLoader === "function"
28
- ? await bootstrap.resourceLoader(cwd, agentDir)
29
- : new DefaultResourceLoader({
30
- ...(bootstrap.resourceLoader ?? {}),
31
- cwd,
32
- agentDir,
33
- settingsManager,
34
- }));
35
- if (!options.resourceLoader) {
36
- await resourceLoader.reload();
37
- }
38
- const extensionsResult = resourceLoader.getExtensions();
39
- for (const { name, config } of extensionsResult.runtime.pendingProviderRegistrations) {
40
- modelRegistry.registerProvider(name, config);
41
- }
42
- extensionsResult.runtime.pendingProviderRegistrations = [];
43
- const created = await createAgentSession({
44
- cwd,
45
- agentDir,
46
- authStorage,
47
- modelRegistry,
48
- settingsManager,
49
- resourceLoader,
50
- sessionManager: options.sessionManager,
51
- model: bootstrap.model,
52
- thinkingLevel: bootstrap.thinkingLevel,
53
- scopedModels: bootstrap.scopedModels,
54
- tools: bootstrap.tools,
55
- customTools: bootstrap.customTools,
56
- sessionStartEvent: options.sessionStartEvent,
57
- });
58
- return {
59
- ...created,
60
- cwd,
61
- agentDir,
62
- authStorage,
63
- modelRegistry,
64
- settingsManager,
65
- resourceLoader,
66
- sessionManager: created.session.sessionManager,
67
- };
68
- }
69
5
  function extractUserMessageText(content) {
70
6
  if (typeof content === "string") {
71
7
  return content;
@@ -76,23 +12,37 @@ function extractUserMessageText(content) {
76
12
  .join("");
77
13
  }
78
14
  /**
79
- * Stable wrapper around a replaceable AgentSession runtime.
15
+ * Owns the current AgentSession plus its cwd-bound services.
80
16
  *
81
- * Use this when your application needs `/new`, `/resume`, `/fork`, or import
82
- * behavior. After replacement, read `session` again and rebind any
83
- * 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.
84
20
  */
85
- export class AgentSessionRuntimeHost {
86
- constructor(bootstrap, runtime) {
87
- this.bootstrap = bootstrap;
88
- 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;
89
31
  }
90
- /** The currently active session instance. Re-read this after runtime replacement. */
91
32
  get session() {
92
- 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;
93
43
  }
94
44
  async emitBeforeSwitch(reason, targetSessionFile) {
95
- const runner = this.runtime.session.extensionRunner;
45
+ const runner = this.session.extensionRunner;
96
46
  if (!runner?.hasHandlers("session_before_switch")) {
97
47
  return { cancelled: false };
98
48
  }
@@ -104,7 +54,7 @@ export class AgentSessionRuntimeHost {
104
54
  return { cancelled: result?.cancel === true };
105
55
  }
106
56
  async emitBeforeFork(entryId) {
107
- const runner = this.runtime.session.extensionRunner;
57
+ const runner = this.session.extensionRunner;
108
58
  if (!runner?.hasHandlers("session_before_fork")) {
109
59
  return { cancelled: false };
110
60
  }
@@ -114,128 +64,125 @@ export class AgentSessionRuntimeHost {
114
64
  });
115
65
  return { cancelled: result?.cancel === true };
116
66
  }
117
- async replace(options) {
118
- const nextRuntime = await createAgentSessionRuntime(this.bootstrap, options);
119
- await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
120
- this.runtime.session.dispose();
121
- if (process.cwd() !== nextRuntime.cwd) {
122
- process.chdir(nextRuntime.cwd);
123
- }
124
- 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;
125
79
  }
126
- /**
127
- * Replace the active runtime with one opened from an existing session file.
128
- *
129
- * Emits `session_before_switch` before replacement and returns
130
- * `{ cancelled: true }` if an extension vetoes the switch.
131
- */
132
80
  async switchSession(sessionPath) {
133
81
  const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
134
82
  if (beforeResult.cancelled) {
135
83
  return beforeResult;
136
84
  }
137
- const previousSessionFile = this.runtime.session.sessionFile;
85
+ const previousSessionFile = this.session.sessionFile;
138
86
  const sessionManager = SessionManager.open(sessionPath);
139
- await this.replace({
87
+ await this.teardownCurrent();
88
+ this.apply(await this.createRuntime({
140
89
  cwd: sessionManager.getCwd(),
90
+ agentDir: this.services.agentDir,
141
91
  sessionManager,
142
92
  sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
143
- });
93
+ }));
144
94
  return { cancelled: false };
145
95
  }
146
- /**
147
- * Replace the active runtime with a fresh session in the current cwd.
148
- *
149
- * `setup` runs after replacement against the new session manager, which lets
150
- * callers seed entries before normal use begins.
151
- */
152
96
  async newSession(options) {
153
97
  const beforeResult = await this.emitBeforeSwitch("new");
154
98
  if (beforeResult.cancelled) {
155
99
  return beforeResult;
156
100
  }
157
- const previousSessionFile = this.runtime.session.sessionFile;
158
- const sessionDir = this.runtime.sessionManager.getSessionDir();
159
- 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);
160
104
  if (options?.parentSession) {
161
105
  sessionManager.newSession({ parentSession: options.parentSession });
162
106
  }
163
- await this.replace({
164
- cwd: this.runtime.cwd,
107
+ await this.teardownCurrent();
108
+ this.apply(await this.createRuntime({
109
+ cwd: this.cwd,
110
+ agentDir: this.services.agentDir,
165
111
  sessionManager,
166
112
  sessionStartEvent: { type: "session_start", reason: "new", previousSessionFile },
167
- });
113
+ }));
168
114
  if (options?.setup) {
169
- await options.setup(this.runtime.sessionManager);
170
- 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;
171
117
  }
172
118
  return { cancelled: false };
173
119
  }
174
- /**
175
- * Replace the active runtime with a fork rooted at the given user-message
176
- * entry.
177
- *
178
- * Returns the selected user text so UIs can restore it into the editor after
179
- * the fork completes.
180
- */
181
120
  async fork(entryId) {
182
121
  const beforeResult = await this.emitBeforeFork(entryId);
183
122
  if (beforeResult.cancelled) {
184
123
  return { cancelled: true };
185
124
  }
186
- const selectedEntry = this.runtime.sessionManager.getEntry(entryId);
125
+ const selectedEntry = this.session.sessionManager.getEntry(entryId);
187
126
  if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
188
127
  throw new Error("Invalid entry ID for forking");
189
128
  }
190
- const previousSessionFile = this.runtime.session.sessionFile;
129
+ const previousSessionFile = this.session.sessionFile;
191
130
  const selectedText = extractUserMessageText(selectedEntry.message.content);
192
- if (this.runtime.sessionManager.isPersisted()) {
193
- const currentSessionFile = this.runtime.session.sessionFile;
194
- 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();
195
137
  if (!selectedEntry.parentId) {
196
- const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
138
+ const sessionManager = SessionManager.create(this.cwd, sessionDir);
197
139
  sessionManager.newSession({ parentSession: currentSessionFile });
198
- await this.replace({
199
- cwd: this.runtime.cwd,
140
+ await this.teardownCurrent();
141
+ this.apply(await this.createRuntime({
142
+ cwd: this.cwd,
143
+ agentDir: this.services.agentDir,
200
144
  sessionManager,
201
145
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
202
- });
146
+ }));
203
147
  return { cancelled: false, selectedText };
204
148
  }
205
149
  const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
206
150
  const forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId);
151
+ if (!forkedSessionPath) {
152
+ throw new Error("Failed to create forked session");
153
+ }
207
154
  const sessionManager = SessionManager.open(forkedSessionPath, sessionDir);
208
- await this.replace({
155
+ await this.teardownCurrent();
156
+ this.apply(await this.createRuntime({
209
157
  cwd: sessionManager.getCwd(),
158
+ agentDir: this.services.agentDir,
210
159
  sessionManager,
211
160
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
212
- });
161
+ }));
213
162
  return { cancelled: false, selectedText };
214
163
  }
215
- const sessionManager = this.runtime.sessionManager;
164
+ const sessionManager = this.session.sessionManager;
216
165
  if (!selectedEntry.parentId) {
217
- sessionManager.newSession({ parentSession: this.runtime.session.sessionFile });
166
+ sessionManager.newSession({ parentSession: this.session.sessionFile });
218
167
  }
219
168
  else {
220
169
  sessionManager.createBranchedSession(selectedEntry.parentId);
221
170
  }
222
- await this.replace({
223
- cwd: this.runtime.cwd,
171
+ await this.teardownCurrent();
172
+ this.apply(await this.createRuntime({
173
+ cwd: this.cwd,
174
+ agentDir: this.services.agentDir,
224
175
  sessionManager,
225
176
  sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
226
- });
177
+ }));
227
178
  return { cancelled: false, selectedText };
228
179
  }
229
- /**
230
- * Import a JSONL session file into the current session directory and replace
231
- * the active runtime with the imported session.
232
- */
233
180
  async importFromJsonl(inputPath) {
234
181
  const resolvedPath = resolve(inputPath);
235
182
  if (!existsSync(resolvedPath)) {
236
183
  throw new Error(`File not found: ${resolvedPath}`);
237
184
  }
238
- const sessionDir = this.runtime.sessionManager.getSessionDir();
185
+ const sessionDir = this.session.sessionManager.getSessionDir();
239
186
  if (!existsSync(sessionDir)) {
240
187
  mkdirSync(sessionDir, { recursive: true });
241
188
  }
@@ -244,22 +191,37 @@ export class AgentSessionRuntimeHost {
244
191
  if (beforeResult.cancelled) {
245
192
  return beforeResult;
246
193
  }
247
- const previousSessionFile = this.runtime.session.sessionFile;
194
+ const previousSessionFile = this.session.sessionFile;
248
195
  if (resolve(destinationPath) !== resolvedPath) {
249
196
  copyFileSync(resolvedPath, destinationPath);
250
197
  }
251
198
  const sessionManager = SessionManager.open(destinationPath, sessionDir);
252
- await this.replace({
199
+ await this.teardownCurrent();
200
+ this.apply(await this.createRuntime({
253
201
  cwd: sessionManager.getCwd(),
202
+ agentDir: this.services.agentDir,
254
203
  sessionManager,
255
204
  sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
256
- });
205
+ }));
257
206
  return { cancelled: false };
258
207
  }
259
- /** Emit session shutdown for the active runtime and dispose it permanently. */
260
208
  async dispose() {
261
- await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
262
- 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);
263
223
  }
224
+ return new AgentSessionRuntime(result.session, result.services, createRuntime, result.diagnostics, result.modelFallbackMessage);
264
225
  }
226
+ export { createAgentSessionFromServices, createAgentSessionServices, } from "./agent-session-services.js";
265
227
  //# sourceMappingURL=agent-session-runtime.js.map