@draht/coding-agent 2026.3.25-1 → 2026.4.5

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 (111) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +6 -2
  3. package/dist/core/agent-session-runtime.d.ts +136 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +267 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +22 -44
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +44 -248
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +3 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +5 -2
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  16. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  17. package/dist/core/compaction/branch-summarization.js +2 -2
  18. package/dist/core/compaction/branch-summarization.js.map +1 -1
  19. package/dist/core/compaction/compaction.d.ts +2 -2
  20. package/dist/core/compaction/compaction.d.ts.map +1 -1
  21. package/dist/core/compaction/compaction.js +9 -9
  22. package/dist/core/compaction/compaction.js.map +1 -1
  23. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  24. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  25. package/dist/core/export-html/tool-renderer.js +2 -2
  26. package/dist/core/export-html/tool-renderer.js.map +1 -1
  27. package/dist/core/extensions/index.d.ts +2 -2
  28. package/dist/core/extensions/index.d.ts.map +1 -1
  29. package/dist/core/extensions/index.js +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/types.d.ts +16 -16
  32. package/dist/core/extensions/types.d.ts.map +1 -1
  33. package/dist/core/extensions/types.js +10 -0
  34. package/dist/core/extensions/types.js.map +1 -1
  35. package/dist/core/footer-data-provider.d.ts +5 -1
  36. package/dist/core/footer-data-provider.d.ts.map +1 -1
  37. package/dist/core/footer-data-provider.js +70 -8
  38. package/dist/core/footer-data-provider.js.map +1 -1
  39. package/dist/core/index.d.ts +2 -1
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/index.js +2 -1
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/model-registry.d.ts +21 -3
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +90 -70
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts.map +1 -1
  48. package/dist/core/model-resolver.js +4 -4
  49. package/dist/core/model-resolver.js.map +1 -1
  50. package/dist/core/resolve-config-value.d.ts +6 -0
  51. package/dist/core/resolve-config-value.d.ts.map +1 -1
  52. package/dist/core/resolve-config-value.js +37 -5
  53. package/dist/core/resolve-config-value.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts +2 -0
  55. package/dist/core/resource-loader.d.ts.map +1 -1
  56. package/dist/core/resource-loader.js +5 -1
  57. package/dist/core/resource-loader.js.map +1 -1
  58. package/dist/core/sdk.d.ts +6 -3
  59. package/dist/core/sdk.d.ts.map +1 -1
  60. package/dist/core/sdk.js +17 -23
  61. package/dist/core/sdk.js.map +1 -1
  62. package/dist/index.d.ts +3 -3
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -3
  65. package/dist/index.js.map +1 -1
  66. package/dist/main.d.ts.map +1 -1
  67. package/dist/main.js +49 -10
  68. package/dist/main.js.map +1 -1
  69. package/dist/modes/interactive/components/footer.d.ts +1 -0
  70. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/footer.js +4 -1
  72. package/dist/modes/interactive/components/footer.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.d.ts +8 -4
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +90 -87
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  78. package/dist/modes/interactive/theme/theme.js +6 -11
  79. package/dist/modes/interactive/theme/theme.js.map +1 -1
  80. package/dist/modes/print-mode.d.ts +4 -4
  81. package/dist/modes/print-mode.d.ts.map +1 -1
  82. package/dist/modes/print-mode.js +87 -74
  83. package/dist/modes/print-mode.js.map +1 -1
  84. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  85. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.js +69 -49
  87. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  88. package/docs/development.md +2 -2
  89. package/docs/extensions.md +78 -22
  90. package/docs/models.md +6 -0
  91. package/docs/packages.md +3 -3
  92. package/docs/rpc.md +2 -2
  93. package/docs/sdk.md +170 -82
  94. package/docs/tree.md +1 -1
  95. package/examples/extensions/custom-compaction.ts +17 -4
  96. package/examples/extensions/handoff.ts +5 -2
  97. package/examples/extensions/hello.ts +18 -17
  98. package/examples/extensions/qna.ts +5 -2
  99. package/examples/extensions/rpc-demo.ts +3 -9
  100. package/examples/extensions/status-line.ts +0 -8
  101. package/examples/extensions/subagent/index.ts +1 -1
  102. package/examples/extensions/summarize.ts +15 -4
  103. package/examples/extensions/todo.ts +0 -2
  104. package/examples/extensions/tools.ts +0 -5
  105. package/examples/extensions/widget-placement.ts +4 -12
  106. package/examples/sdk/02-custom-model.ts +1 -1
  107. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  108. package/examples/sdk/12-full-control.ts +1 -1
  109. package/examples/sdk/13-session-runtime.ts +49 -0
  110. package/examples/sdk/README.md +5 -4
  111. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,112 @@
1
1
  # Changelog
2
2
 
3
+ ## [2026.4.5] - 2026-04-05
4
+
5
+ ### Added
6
+
7
+ - add startup profiling scripts for tui/rpc (#2497)
8
+ - add resizable share sidebar (#2435)
9
+ - add JSONL export/import for sessions (#2356)
10
+ - add --fork session flag closes #2290
11
+ - expose local bash operations closes #2299
12
+ - allow supplying custom session ID in newSession() (#2130)
13
+ - refine session_directory hook closes #1729
14
+ - add session_directory extension event
15
+
16
+ ### Changed
17
+
18
+ - reproduce missing subagent builtin extension registration
19
+ - fix(coding-agent): reuse initial resource loader on startup closes #2766
20
+ - feat(coding-agent): add defineTool helper closes #2746
21
+ - test(coding-agent): add session lifecycle characterization suite
22
+ - test(coding-agent): add queue characterization coverage
23
+ - fix(coding-agent): resolve theme export variables closes #2707
24
+ - refactor(coding-agent): add runtime host for session switching closes #2024
25
+ - fix(coding-agent): resolve models.json auth per request closes #1835
26
+ - feat(ai,coding-agent): add faux provider and ModelRegistry factories
27
+ - docs: add switchSession to ExtensionCommandContext in extensions.md
28
+ - fix(tui): await async slash command completions closes #2719
29
+ - expand branding sweep rules in rebase-upstream prompt
30
+ - rename pi to draht in process title and system prompt
31
+ - add AgentSession test harness with faux provider
32
+ - extend OSS weekend to 2026-03-30
33
+ - document xfce4-terminal and terminator keyboard protocol limitations (#2166)
34
+ - clarify compat flags for openai-compatible local servers closes #2177
35
+
36
+ ### Fixed
37
+
38
+ - register subagent builtin extension in DefaultResourceLoader
39
+ - resolve type errors and branding from upstream sync
40
+ - branding sweep for upstream cherry-picks
41
+ - emit session_shutdown in print mode closes #2576
42
+ - replace π with D in terminal title
43
+ - update changelog URL from pi-mono to draht
44
+ - resolve upstream sync conflicts and fix branding
45
+ - respect export theme backgrounds closes #2565
46
+ - recompute interactive bash preview width closes #2569
47
+ - reduce git update fetch noise closes #2548
48
+ - expose rpc context usage closes #2550
49
+ - disambiguate duplicate slash commands, fixes #1061
50
+ - unify source provenance, closes #1734
51
+ - attach source info to resources and commands, fixes #1734
52
+ - enforce safe auto-resized image limits closes #2055
53
+ - reserve stdout in print and json mode closes #2482
54
+ - skip no-op git package reinstalls
55
+ - built-in tools work like extension tools
56
+ - disable cli-highlight auto-detection for languageless code blocks
57
+ - update project npm packages
58
+ - reuse current pi invocation for child agents closes #2464 (#2465)
59
+ - export ToolCallEventResult closes #2458
60
+ - respect agentDir for sdk session paths closes #2457
61
+ - keep suspend resume alive on fg closes #2454
62
+ - align minimax and zai defaults (#2445)
63
+ - migrate to namespaced ids closes #2391
64
+ - queue file mutations across edit and write
65
+ - protect rpc stdout fixes #2388
66
+ - resolve waitForRetry() race when auto-retry produces tool calls (#2440)
67
+ - /model shows stale scoped models (#2408)
68
+ - suppress process warnings in CLI closes #2404
69
+ - show bash tool elapsed time at bottom closes #2406
70
+ - reload custom themes from disk watcher\n\ncloses #2417\ncloses #2003
71
+ - avoid blocking footer branch refresh and isolate invalid extension provider registrations closes #2418 closes #2431
72
+ - fix windows hanging when descendants inherit stdout/stderr handles (#2389)
73
+ - use native clipboard text copy on desktop closes #2347
74
+ - make tests keybinding-agnostic
75
+ - use `setBedrockProviderModule` in Bun entrypoint closes #2349 (#2350)
76
+ - stop updating packages on startup closes #1963
77
+ - refresh active model after provider updates closes #2291
78
+ - reload keybindings on /reload closes #2309
79
+ - restore Bun binary lazy provider loading closes #2314
80
+ - handle z.ai network_error closes #2313
81
+ - merge piped stdin into initial prompt closes #2315
82
+ - skip tmux extended-keys warning when tmux is unreachable (#2311)
83
+ - handle empty session titles and session info metadata (#2304)
84
+ - handle reftable footer branch detection (#2300)
85
+ - emit OSC 133 command-executed marker closes #2242
86
+ - make prompt snippets opt in closes #2285
87
+ - retry provider returned error closes #2264
88
+ - normalize fuzzy edit matching and run touched tests closes #2044
89
+ - handle slash-delimited /model refs closes #2174
90
+ - handle WSL clipboard image fallback\n\nCloses #1722
91
+ - start UI before session_start closes #2035
92
+ - add uninstall alias
93
+ - stabilize windows shell/path handling
94
+ - silence headless clipboard fallback errors closes #2056
95
+ - honor provider compat in models.json closes #2062
96
+ - normalize prompt cwd for bash-safe windows paths closes #2080
97
+ - support configurable npm wrapper command closes #2072
98
+ - stop skill recursion at root closes #2075
99
+ - expand paste markers in ctx.ui.getEditorText(), closes #2084
100
+ - fix startup crash when downloading fd/ripgrep on first run
101
+ - sync tool hooks with agent event processing closes #2113
102
+ - keep only ISO date in system prompt closes #2131
103
+ - match server_error and internal_error in retry regex (#2117)
104
+ - Fix EXIF orientation for JPEG and WebP images (#2105)
105
+ - support PI_CODING_AGENT_DIR env var in example extensions (#2009)
106
+ - use strict JSONL framing fixes #1911
107
+ - allow threshold compaction for error messages using last successful usage, fixes #1834
108
+ - retry sync lockfile acquisition to prevent false auth errors during parallel startup
109
+
3
110
  ## [2026.3.14] - 2026-03-14
4
111
 
5
112
  ### Added
package/README.md CHANGED
@@ -373,15 +373,19 @@ See [docs/packages.md](docs/packages.md).
373
373
  ```typescript
374
374
  import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@draht/coding-agent";
375
375
 
376
+ const authStorage = AuthStorage.create();
377
+ const modelRegistry = ModelRegistry.create(authStorage);
376
378
  const { session } = await createAgentSession({
377
379
  sessionManager: SessionManager.inMemory(),
378
- authStorage: AuthStorage.create(),
379
- modelRegistry: new ModelRegistry(authStorage),
380
+ authStorage,
381
+ modelRegistry,
380
382
  });
381
383
 
382
384
  await session.prompt("What files are in the current directory?");
383
385
  ```
384
386
 
387
+ For advanced multi-session runtime replacement, use `createAgentSessionRuntime()` and `AgentSessionRuntimeHost`.
388
+
385
389
  See [docs/sdk.md](docs/sdk.md) and [examples/sdk/](examples/sdk/).
386
390
 
387
391
  ### RPC Mode
@@ -0,0 +1,136 @@
1
+ import type { ThinkingLevel } from "@draht/agent-core";
2
+ import type { Model } from "@draht/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";
8
+ import { SessionManager } from "./session-manager.js";
9
+ import { SettingsManager } from "./settings-manager.js";
10
+ import type { Tool } from "./tools/index.js";
11
+ /**
12
+ * Stable bootstrap inputs reused whenever the active runtime is replaced.
13
+ *
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.
17
+ */
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">;
44
+ }
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
+ /**
66
+ * Create one runtime instance containing an AgentSession plus the cwd-bound
67
+ * services it depends on.
68
+ *
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.
73
+ */
74
+ export declare function createAgentSessionRuntime(bootstrap: AgentSessionRuntimeBootstrap, options: CreateAgentSessionRuntimeOptions): Promise<AgentSessionRuntime>;
75
+ /**
76
+ * Stable wrapper around a replaceable AgentSession runtime.
77
+ *
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.
81
+ */
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;
88
+ private emitBeforeSwitch;
89
+ 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
+ */
97
+ switchSession(sessionPath: string): Promise<{
98
+ cancelled: boolean;
99
+ }>;
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
+ newSession(options?: {
107
+ /** Optional parent session path recorded in the new session header. */
108
+ parentSession?: string;
109
+ /** Optional callback for seeding the new session manager after replacement. */
110
+ setup?: (sessionManager: SessionManager) => Promise<void>;
111
+ }): Promise<{
112
+ cancelled: boolean;
113
+ }>;
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
+ fork(entryId: string): Promise<{
122
+ cancelled: boolean;
123
+ selectedText?: string;
124
+ }>;
125
+ /**
126
+ * Import a JSONL session file into the current session directory and replace
127
+ * the active runtime with the imported session.
128
+ */
129
+ importFromJsonl(inputPath: string): Promise<{
130
+ cancelled: boolean;
131
+ }>;
132
+ /** Emit session shutdown for the active runtime and dispose it permanently. */
133
+ dispose(): Promise<void>;
134
+ }
135
+ export {};
136
+ //# sourceMappingURL=agent-session-runtime.d.ts.map
@@ -0,0 +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 \"@draht/agent-core\";\nimport type { Model } from \"@draht/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"]}
@@ -0,0 +1,267 @@
1
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { getAgentDir } from "../config.js";
4
+ import { AuthStorage } from "./auth-storage.js";
5
+ 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
+ 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
+ function extractUserMessageText(content) {
70
+ if (typeof content === "string") {
71
+ return content;
72
+ }
73
+ return content
74
+ .filter((part) => part.type === "text" && typeof part.text === "string")
75
+ .map((part) => part.text)
76
+ .join("");
77
+ }
78
+ /**
79
+ * Stable wrapper around a replaceable AgentSession runtime.
80
+ *
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.
84
+ */
85
+ export class AgentSessionRuntimeHost {
86
+ bootstrap;
87
+ runtime;
88
+ constructor(bootstrap, runtime) {
89
+ this.bootstrap = bootstrap;
90
+ this.runtime = runtime;
91
+ }
92
+ /** The currently active session instance. Re-read this after runtime replacement. */
93
+ get session() {
94
+ return this.runtime.session;
95
+ }
96
+ async emitBeforeSwitch(reason, targetSessionFile) {
97
+ const runner = this.runtime.session.extensionRunner;
98
+ if (!runner?.hasHandlers("session_before_switch")) {
99
+ return { cancelled: false };
100
+ }
101
+ const result = await runner.emit({
102
+ type: "session_before_switch",
103
+ reason,
104
+ targetSessionFile,
105
+ });
106
+ return { cancelled: result?.cancel === true };
107
+ }
108
+ async emitBeforeFork(entryId) {
109
+ const runner = this.runtime.session.extensionRunner;
110
+ if (!runner?.hasHandlers("session_before_fork")) {
111
+ return { cancelled: false };
112
+ }
113
+ const result = await runner.emit({
114
+ type: "session_before_fork",
115
+ entryId,
116
+ });
117
+ return { cancelled: result?.cancel === true };
118
+ }
119
+ async replace(options) {
120
+ const nextRuntime = await createAgentSessionRuntime(this.bootstrap, options);
121
+ await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
122
+ this.runtime.session.dispose();
123
+ if (process.cwd() !== nextRuntime.cwd) {
124
+ process.chdir(nextRuntime.cwd);
125
+ }
126
+ this.runtime = nextRuntime;
127
+ }
128
+ /**
129
+ * Replace the active runtime with one opened from an existing session file.
130
+ *
131
+ * Emits `session_before_switch` before replacement and returns
132
+ * `{ cancelled: true }` if an extension vetoes the switch.
133
+ */
134
+ async switchSession(sessionPath) {
135
+ const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
136
+ if (beforeResult.cancelled) {
137
+ return beforeResult;
138
+ }
139
+ const previousSessionFile = this.runtime.session.sessionFile;
140
+ const sessionManager = SessionManager.open(sessionPath);
141
+ await this.replace({
142
+ cwd: sessionManager.getCwd(),
143
+ sessionManager,
144
+ sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
145
+ });
146
+ return { cancelled: false };
147
+ }
148
+ /**
149
+ * Replace the active runtime with a fresh session in the current cwd.
150
+ *
151
+ * `setup` runs after replacement against the new session manager, which lets
152
+ * callers seed entries before normal use begins.
153
+ */
154
+ async newSession(options) {
155
+ const beforeResult = await this.emitBeforeSwitch("new");
156
+ if (beforeResult.cancelled) {
157
+ return beforeResult;
158
+ }
159
+ const previousSessionFile = this.runtime.session.sessionFile;
160
+ const sessionDir = this.runtime.sessionManager.getSessionDir();
161
+ const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
162
+ if (options?.parentSession) {
163
+ sessionManager.newSession({ parentSession: options.parentSession });
164
+ }
165
+ await this.replace({
166
+ cwd: this.runtime.cwd,
167
+ sessionManager,
168
+ sessionStartEvent: { type: "session_start", reason: "new", previousSessionFile },
169
+ });
170
+ if (options?.setup) {
171
+ await options.setup(this.runtime.sessionManager);
172
+ this.runtime.session.agent.state.messages = this.runtime.sessionManager.buildSessionContext().messages;
173
+ }
174
+ return { cancelled: false };
175
+ }
176
+ /**
177
+ * Replace the active runtime with a fork rooted at the given user-message
178
+ * entry.
179
+ *
180
+ * Returns the selected user text so UIs can restore it into the editor after
181
+ * the fork completes.
182
+ */
183
+ async fork(entryId) {
184
+ const beforeResult = await this.emitBeforeFork(entryId);
185
+ if (beforeResult.cancelled) {
186
+ return { cancelled: true };
187
+ }
188
+ const selectedEntry = this.runtime.sessionManager.getEntry(entryId);
189
+ if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
190
+ throw new Error("Invalid entry ID for forking");
191
+ }
192
+ const previousSessionFile = this.runtime.session.sessionFile;
193
+ const selectedText = extractUserMessageText(selectedEntry.message.content);
194
+ if (this.runtime.sessionManager.isPersisted()) {
195
+ const currentSessionFile = this.runtime.session.sessionFile;
196
+ const sessionDir = this.runtime.sessionManager.getSessionDir();
197
+ if (!selectedEntry.parentId) {
198
+ const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
199
+ sessionManager.newSession({ parentSession: currentSessionFile });
200
+ await this.replace({
201
+ cwd: this.runtime.cwd,
202
+ sessionManager,
203
+ sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
204
+ });
205
+ return { cancelled: false, selectedText };
206
+ }
207
+ const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
208
+ const forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId);
209
+ const sessionManager = SessionManager.open(forkedSessionPath, sessionDir);
210
+ await this.replace({
211
+ cwd: sessionManager.getCwd(),
212
+ sessionManager,
213
+ sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
214
+ });
215
+ return { cancelled: false, selectedText };
216
+ }
217
+ const sessionManager = this.runtime.sessionManager;
218
+ if (!selectedEntry.parentId) {
219
+ sessionManager.newSession({ parentSession: this.runtime.session.sessionFile });
220
+ }
221
+ else {
222
+ sessionManager.createBranchedSession(selectedEntry.parentId);
223
+ }
224
+ await this.replace({
225
+ cwd: this.runtime.cwd,
226
+ sessionManager,
227
+ sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
228
+ });
229
+ return { cancelled: false, selectedText };
230
+ }
231
+ /**
232
+ * Import a JSONL session file into the current session directory and replace
233
+ * the active runtime with the imported session.
234
+ */
235
+ async importFromJsonl(inputPath) {
236
+ const resolvedPath = resolve(inputPath);
237
+ if (!existsSync(resolvedPath)) {
238
+ throw new Error(`File not found: ${resolvedPath}`);
239
+ }
240
+ const sessionDir = this.runtime.sessionManager.getSessionDir();
241
+ if (!existsSync(sessionDir)) {
242
+ mkdirSync(sessionDir, { recursive: true });
243
+ }
244
+ const destinationPath = join(sessionDir, basename(resolvedPath));
245
+ const beforeResult = await this.emitBeforeSwitch("resume", destinationPath);
246
+ if (beforeResult.cancelled) {
247
+ return beforeResult;
248
+ }
249
+ const previousSessionFile = this.runtime.session.sessionFile;
250
+ if (resolve(destinationPath) !== resolvedPath) {
251
+ copyFileSync(resolvedPath, destinationPath);
252
+ }
253
+ const sessionManager = SessionManager.open(destinationPath, sessionDir);
254
+ await this.replace({
255
+ cwd: sessionManager.getCwd(),
256
+ sessionManager,
257
+ sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
258
+ });
259
+ return { cancelled: false };
260
+ }
261
+ /** Emit session shutdown for the active runtime and dispose it permanently. */
262
+ async dispose() {
263
+ await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
264
+ this.runtime.session.dispose();
265
+ }
266
+ }
267
+ //# sourceMappingURL=agent-session-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-session-runtime.js","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 \"@draht/agent-core\";\nimport type { Model } from \"@draht/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"]}