@codex-infinity/pi-infinity 0.64.1 → 0.64.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +10 -4
- package/dist/core/agent-session-runtime.d.ts +136 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -0
- package/dist/core/agent-session-runtime.js +265 -0
- package/dist/core/agent-session-runtime.js.map +1 -0
- package/dist/core/agent-session.d.ts +5 -42
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -207
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts +2 -0
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +2 -2
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +16 -16
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +10 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +5 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +69 -8
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +42 -9
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +89 -86
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +6 -11
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +2 -2
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +37 -36
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +69 -49
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/extensions.md +75 -19
- package/docs/sdk.md +160 -72
- package/docs/tree.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/hello.ts +18 -17
- package/examples/extensions/hidden-thinking-label.ts +0 -4
- package/examples/extensions/rpc-demo.ts +3 -9
- package/examples/extensions/status-line.ts +0 -8
- package/examples/extensions/todo.ts +0 -2
- package/examples/extensions/tools.ts +0 -5
- package/examples/extensions/widget-placement.ts +4 -12
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/13-session-runtime.ts +49 -0
- package/examples/sdk/README.md +2 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- Removed extension post-transition events `session_switch` and `session_fork`. Extensions should now use `session_start` and inspect `event.reason`, which is now one of `"startup" | "reload" | "new" | "resume" | "fork"`. For `"new"`, `"resume"`, and `"fork"`, `session_start` also includes `previousSessionFile`. This is better because session replacement now fully reloads extensions, so one post-start hook with explicit reason matches the real lifecycle better than two extra non-cancellable post-transition events.
|
|
8
|
+
- Removed session-replacement methods from `AgentSession`. Use `AgentSessionRuntimeHost` for `newSession()`, `switchSession()`, `fork()`, and `importFromJsonl()`. This is better because cross-cwd session replacement rebuilds cwd-bound runtime state and can replace the live `AgentSession` instance entirely. Keeping those operations on a stable runtime host matches the real lifecycle and avoids pretending one `AgentSession` mutates into another.
|
|
9
|
+
|
|
10
|
+
#### Migration Notes
|
|
11
|
+
|
|
12
|
+
For existing extensions:
|
|
13
|
+
|
|
14
|
+
Before:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
pi.on("session_switch", async (event, ctx) => {
|
|
18
|
+
if (event.reason === "new") {
|
|
19
|
+
resetState();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
pi.on("session_fork", async (_event, ctx) => {
|
|
24
|
+
reconstructState(ctx);
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
After:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
pi.on("session_start", async (event, ctx) => {
|
|
32
|
+
if (event.reason === "new") {
|
|
33
|
+
resetState();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (event.reason === "fork" || event.reason === "resume" || event.reason === "startup") {
|
|
37
|
+
reconstructState(ctx);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For existing SDK integrations:
|
|
43
|
+
|
|
44
|
+
Before:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
await session.newSession();
|
|
48
|
+
await session.switchSession("/path/to/session.jsonl");
|
|
49
|
+
await session.fork("entry-id");
|
|
50
|
+
await session.importFromJsonl(jsonl);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
After:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const runtime = await createAgentSessionRuntime(bootstrap, {
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
sessionManager: SessionManager.create(process.cwd()),
|
|
59
|
+
});
|
|
60
|
+
const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
|
|
61
|
+
|
|
62
|
+
await runtimeHost.newSession();
|
|
63
|
+
await runtimeHost.switchSession("/path/to/session.jsonl");
|
|
64
|
+
await runtimeHost.fork("entry-id");
|
|
65
|
+
await runtimeHost.importFromJsonl(jsonl);
|
|
66
|
+
|
|
67
|
+
const session = runtimeHost.session;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After runtime replacement, use `runtimeHost.session` as the new live session and rebind any session-local subscriptions or extension bindings.
|
|
71
|
+
|
|
5
72
|
### Added
|
|
6
73
|
|
|
74
|
+
- Added public SDK runtime-host exports `createAgentSessionRuntime()` and `AgentSessionRuntimeHost` for apps that need runtime-backed session replacement and mode-style session switching
|
|
75
|
+
|
|
76
|
+
- Added `defineTool()` so standalone and array-based custom tool definitions keep inferred parameter types without manual casts ([#2746](https://github.com/badlogic/pi-mono/issues/2746))
|
|
77
|
+
|
|
7
78
|
- Added label timestamps to the session tree with a `Shift+T` toggle in `/tree`, smart date formatting, and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter))
|
|
8
79
|
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- Fixed startup resource loading to reuse the initial `ResourceLoader` for the first runtime, so extensions are not loaded twice before session startup and `session_start` handlers still fire for singleton-style extensions ([#2766](https://github.com/badlogic/pi-mono/issues/2766))
|
|
83
|
+
- Fixed theme `export` colors to resolve theme variables the same way as `colors`, so `/export` HTML backgrounds now honor entries like `pageBg: "base"` instead of requiring inline hex values ([#2707](https://github.com/badlogic/pi-mono/issues/2707))
|
|
84
|
+
|
|
9
85
|
## [0.64.0] - 2026-03-29
|
|
10
86
|
|
|
11
87
|
### New Features
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<!-- OSS_WEEKEND_START -->
|
|
2
2
|
# 🏖️ OSS Weekend
|
|
3
3
|
|
|
4
|
-
**Issue tracker reopens Monday, April
|
|
4
|
+
**Issue tracker reopens Monday, April 13, 2026.**
|
|
5
5
|
|
|
6
|
-
OSS weekend runs
|
|
6
|
+
OSS weekend runs Thursday, April 2, 2026 through Monday, April 13, 2026. New issues and PRs from unapproved contributors are auto-closed during this time. Approved contributors can still open issues and PRs if something is genuinely urgent, but please keep that to pressing matters only. For support, join [Discord](https://discord.com/invite/3cU7Bz4UPx).
|
|
7
|
+
|
|
8
|
+
> _Current focus: at the moment i'm deep in refactoring internals, and need to focus._
|
|
7
9
|
<!-- OSS_WEEKEND_END -->
|
|
8
10
|
|
|
9
11
|
---
|
|
@@ -392,15 +394,19 @@ See [docs/packages.md](docs/packages.md).
|
|
|
392
394
|
```typescript
|
|
393
395
|
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
394
396
|
|
|
397
|
+
const authStorage = AuthStorage.create();
|
|
398
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
395
399
|
const { session } = await createAgentSession({
|
|
396
400
|
sessionManager: SessionManager.inMemory(),
|
|
397
|
-
authStorage
|
|
398
|
-
modelRegistry
|
|
401
|
+
authStorage,
|
|
402
|
+
modelRegistry,
|
|
399
403
|
});
|
|
400
404
|
|
|
401
405
|
await session.prompt("What files are in the current directory?");
|
|
402
406
|
```
|
|
403
407
|
|
|
408
|
+
For advanced multi-session runtime replacement, use `createAgentSessionRuntime()` and `AgentSessionRuntimeHost`.
|
|
409
|
+
|
|
404
410
|
See [docs/sdk.md](docs/sdk.md) and [examples/sdk/](examples/sdk/).
|
|
405
411
|
|
|
406
412
|
### RPC Mode
|
|
@@ -0,0 +1,136 @@
|
|
|
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";
|
|
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 \"@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"]}
|
|
@@ -0,0 +1,265 @@
|
|
|
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
|
+
constructor(bootstrap, runtime) {
|
|
87
|
+
this.bootstrap = bootstrap;
|
|
88
|
+
this.runtime = runtime;
|
|
89
|
+
}
|
|
90
|
+
/** The currently active session instance. Re-read this after runtime replacement. */
|
|
91
|
+
get session() {
|
|
92
|
+
return this.runtime.session;
|
|
93
|
+
}
|
|
94
|
+
async emitBeforeSwitch(reason, targetSessionFile) {
|
|
95
|
+
const runner = this.runtime.session.extensionRunner;
|
|
96
|
+
if (!runner?.hasHandlers("session_before_switch")) {
|
|
97
|
+
return { cancelled: false };
|
|
98
|
+
}
|
|
99
|
+
const result = await runner.emit({
|
|
100
|
+
type: "session_before_switch",
|
|
101
|
+
reason,
|
|
102
|
+
targetSessionFile,
|
|
103
|
+
});
|
|
104
|
+
return { cancelled: result?.cancel === true };
|
|
105
|
+
}
|
|
106
|
+
async emitBeforeFork(entryId) {
|
|
107
|
+
const runner = this.runtime.session.extensionRunner;
|
|
108
|
+
if (!runner?.hasHandlers("session_before_fork")) {
|
|
109
|
+
return { cancelled: false };
|
|
110
|
+
}
|
|
111
|
+
const result = await runner.emit({
|
|
112
|
+
type: "session_before_fork",
|
|
113
|
+
entryId,
|
|
114
|
+
});
|
|
115
|
+
return { cancelled: result?.cancel === true };
|
|
116
|
+
}
|
|
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;
|
|
125
|
+
}
|
|
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
|
+
async switchSession(sessionPath) {
|
|
133
|
+
const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
|
|
134
|
+
if (beforeResult.cancelled) {
|
|
135
|
+
return beforeResult;
|
|
136
|
+
}
|
|
137
|
+
const previousSessionFile = this.runtime.session.sessionFile;
|
|
138
|
+
const sessionManager = SessionManager.open(sessionPath);
|
|
139
|
+
await this.replace({
|
|
140
|
+
cwd: sessionManager.getCwd(),
|
|
141
|
+
sessionManager,
|
|
142
|
+
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
|
|
143
|
+
});
|
|
144
|
+
return { cancelled: false };
|
|
145
|
+
}
|
|
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
|
+
async newSession(options) {
|
|
153
|
+
const beforeResult = await this.emitBeforeSwitch("new");
|
|
154
|
+
if (beforeResult.cancelled) {
|
|
155
|
+
return beforeResult;
|
|
156
|
+
}
|
|
157
|
+
const previousSessionFile = this.runtime.session.sessionFile;
|
|
158
|
+
const sessionDir = this.runtime.sessionManager.getSessionDir();
|
|
159
|
+
const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
|
|
160
|
+
if (options?.parentSession) {
|
|
161
|
+
sessionManager.newSession({ parentSession: options.parentSession });
|
|
162
|
+
}
|
|
163
|
+
await this.replace({
|
|
164
|
+
cwd: this.runtime.cwd,
|
|
165
|
+
sessionManager,
|
|
166
|
+
sessionStartEvent: { type: "session_start", reason: "new", previousSessionFile },
|
|
167
|
+
});
|
|
168
|
+
if (options?.setup) {
|
|
169
|
+
await options.setup(this.runtime.sessionManager);
|
|
170
|
+
this.runtime.session.agent.state.messages = this.runtime.sessionManager.buildSessionContext().messages;
|
|
171
|
+
}
|
|
172
|
+
return { cancelled: false };
|
|
173
|
+
}
|
|
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
|
+
async fork(entryId) {
|
|
182
|
+
const beforeResult = await this.emitBeforeFork(entryId);
|
|
183
|
+
if (beforeResult.cancelled) {
|
|
184
|
+
return { cancelled: true };
|
|
185
|
+
}
|
|
186
|
+
const selectedEntry = this.runtime.sessionManager.getEntry(entryId);
|
|
187
|
+
if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
|
|
188
|
+
throw new Error("Invalid entry ID for forking");
|
|
189
|
+
}
|
|
190
|
+
const previousSessionFile = this.runtime.session.sessionFile;
|
|
191
|
+
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();
|
|
195
|
+
if (!selectedEntry.parentId) {
|
|
196
|
+
const sessionManager = SessionManager.create(this.runtime.cwd, sessionDir);
|
|
197
|
+
sessionManager.newSession({ parentSession: currentSessionFile });
|
|
198
|
+
await this.replace({
|
|
199
|
+
cwd: this.runtime.cwd,
|
|
200
|
+
sessionManager,
|
|
201
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
|
|
202
|
+
});
|
|
203
|
+
return { cancelled: false, selectedText };
|
|
204
|
+
}
|
|
205
|
+
const sourceManager = SessionManager.open(currentSessionFile, sessionDir);
|
|
206
|
+
const forkedSessionPath = sourceManager.createBranchedSession(selectedEntry.parentId);
|
|
207
|
+
const sessionManager = SessionManager.open(forkedSessionPath, sessionDir);
|
|
208
|
+
await this.replace({
|
|
209
|
+
cwd: sessionManager.getCwd(),
|
|
210
|
+
sessionManager,
|
|
211
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
|
|
212
|
+
});
|
|
213
|
+
return { cancelled: false, selectedText };
|
|
214
|
+
}
|
|
215
|
+
const sessionManager = this.runtime.sessionManager;
|
|
216
|
+
if (!selectedEntry.parentId) {
|
|
217
|
+
sessionManager.newSession({ parentSession: this.runtime.session.sessionFile });
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
221
|
+
}
|
|
222
|
+
await this.replace({
|
|
223
|
+
cwd: this.runtime.cwd,
|
|
224
|
+
sessionManager,
|
|
225
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
|
|
226
|
+
});
|
|
227
|
+
return { cancelled: false, selectedText };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Import a JSONL session file into the current session directory and replace
|
|
231
|
+
* the active runtime with the imported session.
|
|
232
|
+
*/
|
|
233
|
+
async importFromJsonl(inputPath) {
|
|
234
|
+
const resolvedPath = resolve(inputPath);
|
|
235
|
+
if (!existsSync(resolvedPath)) {
|
|
236
|
+
throw new Error(`File not found: ${resolvedPath}`);
|
|
237
|
+
}
|
|
238
|
+
const sessionDir = this.runtime.sessionManager.getSessionDir();
|
|
239
|
+
if (!existsSync(sessionDir)) {
|
|
240
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
const destinationPath = join(sessionDir, basename(resolvedPath));
|
|
243
|
+
const beforeResult = await this.emitBeforeSwitch("resume", destinationPath);
|
|
244
|
+
if (beforeResult.cancelled) {
|
|
245
|
+
return beforeResult;
|
|
246
|
+
}
|
|
247
|
+
const previousSessionFile = this.runtime.session.sessionFile;
|
|
248
|
+
if (resolve(destinationPath) !== resolvedPath) {
|
|
249
|
+
copyFileSync(resolvedPath, destinationPath);
|
|
250
|
+
}
|
|
251
|
+
const sessionManager = SessionManager.open(destinationPath, sessionDir);
|
|
252
|
+
await this.replace({
|
|
253
|
+
cwd: sessionManager.getCwd(),
|
|
254
|
+
sessionManager,
|
|
255
|
+
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
|
|
256
|
+
});
|
|
257
|
+
return { cancelled: false };
|
|
258
|
+
}
|
|
259
|
+
/** Emit session shutdown for the active runtime and dispose it permanently. */
|
|
260
|
+
async dispose() {
|
|
261
|
+
await emitSessionShutdownEvent(this.runtime.session.extensionRunner);
|
|
262
|
+
this.runtime.session.dispose();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# 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 \"@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"]}
|