@code-yeongyu/senpi 2026.8.30-3 → 2026.8.31
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 +34 -0
- package/README.md +3 -0
- package/dist/core/agent-session.d.ts +26 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +67 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry.d.ts +9 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry.js +10 -3
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +4 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts +1 -0
- package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative-summary.js +1 -0
- package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative.js +14 -1
- package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust-state-json.d.ts +5 -0
- package/dist/core/extensions/builtin/hooks/trust-state-json.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-state-json.js +76 -0
- package/dist/core/extensions/builtin/hooks/trust-state-json.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts +7 -1
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust-storage.js +60 -8
- package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust.d.ts +1 -2
- package/dist/core/extensions/builtin/hooks/trust.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust.js +1 -72
- package/dist/core/extensions/builtin/hooks/trust.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ttsr/index.js +4 -0
- package/dist/core/extensions/builtin/ttsr/index.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/manager.d.ts +2 -0
- package/dist/core/extensions/builtin/ttsr/manager.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ttsr/manager.js +28 -1
- package/dist/core/extensions/builtin/ttsr/manager.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +7 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts +14 -0
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js +33 -3
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/log.js +1 -1
- package/dist/core/retry-fallback/log.js.map +1 -1
- package/dist/core/session-activity.d.ts +47 -0
- package/dist/core/session-activity.d.ts.map +1 -0
- package/dist/core/session-activity.js +58 -0
- package/dist/core/session-activity.js.map +1 -0
- package/dist/core/session-manager.d.ts +8 -12
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +118 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-resident-store.d.ts +8 -1
- package/dist/core/session-resident-store.d.ts.map +1 -1
- package/dist/core/session-resident-store.js +12 -5
- package/dist/core/session-resident-store.js.map +1 -1
- package/dist/modes/app-server/daemon.js +3 -3
- package/dist/modes/app-server/daemon.js.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts +3 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.js +26 -4
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +24 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts +12 -5
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +149 -12
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/custom-capability.d.ts +1 -0
- package/dist/modes/rpc/custom-capability.d.ts.map +1 -1
- package/dist/modes/rpc/custom-capability.js +1 -0
- package/dist/modes/rpc/custom-capability.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
- package/dist/modes/rpc/host-ensure.js +3 -3
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts +10 -0
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +15 -2
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.d.ts +38 -0
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +55 -5
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/ownership-safe-lock.d.ts +14 -0
- package/dist/modes/rpc/ownership-safe-lock.d.ts.map +1 -0
- package/dist/modes/rpc/ownership-safe-lock.js +87 -0
- package/dist/modes/rpc/ownership-safe-lock.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +1 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-binding.d.ts +2 -1
- package/dist/modes/rpc/session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/session-binding.js +1 -0
- package/dist/modes/rpc/session-binding.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +54 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +174 -2
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +18 -0
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +88 -8
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +11 -1
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +25 -0
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/widget-line-renderer.d.ts +17 -0
- package/dist/modes/rpc/widget-line-renderer.d.ts.map +1 -0
- package/dist/modes/rpc/widget-line-renderer.js +73 -0
- package/dist/modes/rpc/widget-line-renderer.js.map +1 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +11 -2
- package/dist/utils/fs-watch.js.map +1 -1
- package/docs/app-server.md +2 -2
- package/docs/containerization.md +2 -2
- package/docs/development.md +7 -7
- package/docs/extensions.md +4 -4
- package/docs/grok-neo.md +1 -1
- package/docs/index.md +1 -1
- package/docs/packages.md +2 -2
- package/docs/quickstart.md +4 -4
- package/docs/release-guide.md +21 -21
- package/docs/rpc.md +59 -4
- package/docs/sdk.md +1 -1
- package/docs/skills.md +2 -2
- package/docs/terminal-tools.md +1 -1
- package/docs/termux.md +1 -1
- package/docs/tui.md +1 -1
- package/examples/AGENTS.md +1 -1
- package/examples/sdk/README.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +16 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +2 -2
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/skill-contribution.ts +71 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +3 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/SKILL.md +44 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/breaking-changes.md +75 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/build.md +114 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/dev-tooling.md +34 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/http-networking.md +113 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/install.md +93 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/node-compat-platforms.md +36 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/performance.md +33 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/runtime-apis.md +156 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/security.md +52 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/test.md +103 -0
- package/node_modules/@earendil-works/pi-agent-core/README.md +3 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +6 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +316 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.js +23 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-conversation-rotation.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts +24 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +20 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/README.md +6 -3
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/README.md +5 -4
- package/node_modules/@earendil-works/pi-tui/native/darwin/README.md +2 -2
- package/node_modules/@earendil-works/pi-tui/native/win32/README.md +2 -2
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -29,6 +29,11 @@ export class RpcSessionRegistry {
|
|
|
29
29
|
this.reservations = new Set();
|
|
30
30
|
this.nextHandle = 0;
|
|
31
31
|
this.options = options;
|
|
32
|
+
this.now = options.now ?? Date.now;
|
|
33
|
+
}
|
|
34
|
+
/** Number of live entries, including ones still opening or closing. */
|
|
35
|
+
get size() {
|
|
36
|
+
return this.entries.size;
|
|
32
37
|
}
|
|
33
38
|
async openSession(profile) {
|
|
34
39
|
this.validateProfile(profile);
|
|
@@ -46,6 +51,7 @@ export class RpcSessionRegistry {
|
|
|
46
51
|
entry.attachments += 1;
|
|
47
52
|
if (!entry.durableSessionId)
|
|
48
53
|
throw new RpcSessionRegistryError("session_path_in_use");
|
|
54
|
+
entry.lastCommandAt = this.now();
|
|
49
55
|
return {
|
|
50
56
|
sessionId: handle,
|
|
51
57
|
durableSessionId: entry.durableSessionId,
|
|
@@ -53,6 +59,13 @@ export class RpcSessionRegistry {
|
|
|
53
59
|
attached: true,
|
|
54
60
|
};
|
|
55
61
|
}
|
|
62
|
+
const maxSessions = this.options.maxSessions;
|
|
63
|
+
if (maxSessions !== undefined && this.countActiveSessions() >= maxSessions) {
|
|
64
|
+
// Reconnect-churn backstop: every open_session owns a complete runtime
|
|
65
|
+
// (hundreds of MB measured), so admission is bounded. Attachments to a
|
|
66
|
+
// live session add none and stay allowed via the branch above.
|
|
67
|
+
throw new RpcSessionRegistryError("too_many_sessions");
|
|
68
|
+
}
|
|
56
69
|
if (sessionPath)
|
|
57
70
|
this.reservations.add(sessionPath);
|
|
58
71
|
// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):
|
|
@@ -73,6 +86,7 @@ export class RpcSessionRegistry {
|
|
|
73
86
|
reservationKey: sessionPath,
|
|
74
87
|
cwd: storedProfile.cwd,
|
|
75
88
|
attachments: 1,
|
|
89
|
+
lastCommandAt: this.now(),
|
|
76
90
|
lifecycleMutex: Promise.resolve(),
|
|
77
91
|
};
|
|
78
92
|
entry.switchSession = (sessionPath, options) => {
|
|
@@ -159,6 +173,9 @@ export class RpcSessionRegistry {
|
|
|
159
173
|
}
|
|
160
174
|
if (entry.state !== "open" && entry.state !== "closing")
|
|
161
175
|
throw new RpcSessionRegistryError("unknown_session");
|
|
176
|
+
// Every routed command counts as activity for idle eviction. Lookups that
|
|
177
|
+
// must not refresh idleness (sweeps, listing) use peek()/list() instead.
|
|
178
|
+
entry.lastCommandAt = this.now();
|
|
162
179
|
return entry;
|
|
163
180
|
}
|
|
164
181
|
/**
|
|
@@ -249,5 +266,13 @@ export class RpcSessionRegistry {
|
|
|
249
266
|
throw new RpcSessionRegistryError("invalid_path");
|
|
250
267
|
}
|
|
251
268
|
}
|
|
269
|
+
countActiveSessions() {
|
|
270
|
+
let count = 0;
|
|
271
|
+
for (const entry of this.entries.values()) {
|
|
272
|
+
if (entry.state === "opening" || entry.state === "open")
|
|
273
|
+
count += 1;
|
|
274
|
+
}
|
|
275
|
+
return count;
|
|
276
|
+
}
|
|
252
277
|
}
|
|
253
278
|
//# sourceMappingURL=session-registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAChG,OAAO,EAEN,mBAAmB,EAEnB,yBAAyB,GACzB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AA8B/D,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAGjD,YAAY,IAAqC,EAAE,MAAe;QACjE,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACvC,CAAC;CACD;AAeD,SAAS,aAAa,CAAC,IAAY;IAClC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,YAAY,CAAC,YAAY,CAAC,CAAC;IAChE,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,SAAS,aAAa,CAAC,OAAgC;IACtD,OAAO,MAAM,CAAC,MAAM,CAAC;QACpB,GAAG,OAAO;QACV,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChG,CAAC,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,kBAAkB;IAM9B,YAAY,OAAkC;QAL7B,YAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;QAC7C,iBAAY,GAAG,IAAI,GAAG,EAAU,CAAC;QAC1C,eAAU,GAAG,CAAC,CAAC;QAItB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAgC;QACjD,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,WAAW,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACvD,8EAA8E;YAC9E,6EAA6E;YAC7E,wEAAwE;YACxE,8CAA8C;YAC9C,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,cAAc,KAAK,WAAW,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,CAC7E,CAAC;YACF,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,uBAAuB,CAAC,qBAAqB,CAAC,CAAC;YACxE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAC;YACjC,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,gBAAgB;gBAAE,MAAM,IAAI,uBAAuB,CAAC,qBAAqB,CAAC,CAAC;YACtF,OAAO;gBACN,SAAS,EAAE,MAAM;gBACjB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;gBACxC,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,QAAQ,EAAE,IAAI;aACd,CAAC;QACH,CAAC;QACD,IAAI,WAAW;YAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEpD,qEAAqE;QACrE,uEAAuE;QACvE,2EAA2E;QAC3E,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,WAAW,KAAK,SAAS,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC;QACtE,MAAM,aAAa,GAAG,aAAa,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7F,MAAM,cAAc,GAAG,QAAQ;YAC9B,CAAC,CAAC,aAAa,CAAC,EAAE,GAAG,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,oBAAoB,EAAE,SAAS,EAAE,CAAC;YAChG,CAAC,CAAC,aAAa,CAAC;QAEjB,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAoB;YAC9B,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,IAAI,aAAa,EAAE;YAC1B,OAAO,EAAE,aAAa;YACtB,WAAW;YACX,cAAc,EAAE,WAAW;YAC3B,GAAG,EAAE,aAAa,CAAC,GAAG;YACtB,WAAW,EAAE,CAAC;YACd,cAAc,EAAE,OAAO,CAAC,OAAO,EAAE;SACjC,CAAC;QACF,KAAK,CAAC,aAAa,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE;YAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;gBACtD,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBAC9C,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;gBACtD,CAAC;gBACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;gBAC9B,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC;gBACzC,MAAM,UAAU,GAAG,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,WAAW,CAAC;gBACxG,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBACjE,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,UAAU;oBAAE,OAAO,MAAM,CAAC;gBAEnD,uEAAuE;gBACvE,wEAAwE;gBACxE,wEAAwE;gBACxE,uDAAuD;gBACvD,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAC1C,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,QAAQ,EAChB,IAAI,CAAC,OAAO,CAAC,aAAa,EAC1B,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,EACxB,OAAO,CAAC,oBAAoB,EAC5B,OAAO,CAAC,aAAa,CACrB,CAAC;gBACF,WAAW,CAAC,gBAAgB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBAClD,KAAK,CAAC,OAAO,GAAG,WAAW,CAAC;gBAC5B,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC3B,OAAO,MAAM,CAAC;YACf,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,cAAc,GAAG,SAAS,CAAC,IAAI,CACpC,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CACf,CAAC;YACF,OAAO,SAAS,CAAC;QAClB,CAAC,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,WAAW;gBAC1B,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC;gBAChE,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAC5C,KAAK,CAAC,OAAO,GAAG,MAAM,oBAAoB,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAC5D,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE;gBACrD,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE;gBACrB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,cAAc,EAAE,OAAO;gBACvB,aAAa,EAAE,cAAc;aAC7B,CAAC,CACF,CAAC;YACF,KAAK,CAAC,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAChD,KAAK,CAAC,WAAW,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC;YAC/C,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;YACrB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;QACxG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,2EAA2E;YAC3E,0EAA0E;YAC1E,yEAAyE;YACzE,4DAA4D;YAC5D,IAAI,CAAC;gBACJ,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACR,wEAAwE;YACzE,CAAC;oBAAS,CAAC;gBACV,IAAI,CAAC;oBACJ,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC7B,CAAC;wBAAS,CAAC;oBACV,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAC5B,IAAI,WAAW;wBAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBACxD,CAAC;YACF,CAAC;YACD,IAAI,KAAK,YAAY,uBAAuB;gBAAE,MAAM,KAAK,CAAC;YAC1D,MAAM,IAAI,uBAAuB,CAAC,aAAa,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtG,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,MAAc;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,aAAa,CAAC,MAAc,EAAE,OAAe;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,uBAAuB,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACtG,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;QAC9G,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,MAAc;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,KAAK,EAAE,KAAK,KAAK,MAAM;YAAE,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;QAClF,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;QACvB,IAAI,KAAK,CAAC,WAAW,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;QACxB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACtC,mFAAmF;QACnF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO;QACtC,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,WAAW,CAAC,MAAc;QAC/B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,KAAK,EAAE,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;QACrF,MAAM,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC;QAC/C,KAAK,CAAC,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;YAClC,MAAM,iBAAiB,CAAC;YACxB,IAAI,CAAC;gBACJ,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;gBACrC,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;gBAC3C,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;gBAC/B,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;YAC7B,CAAC;oBAAS,CAAC;gBACV,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC;gBACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC5B,IAAI,KAAK,CAAC,cAAc;oBAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YAC1E,CAAC;QACF,CAAC,CAAC,EAAE,CAAC;QACL,OAAO,KAAK,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,IAAI;QAQH,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACrD,SAAS;YACT,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE;YAC5D,MAAM,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,qEAAqE;IAC7D,mBAAmB;QAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC;YACtD,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;YAC7C,8EAA8E;YAC9E,6EAA6E;YAC7E,4EAA4E;YAC5E,yCAAyC;YACzC,IAAI,WAAW,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC;gBACvC,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBACxE,IAAI,KAAK,CAAC,cAAc;oBAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;gBACzE,IAAI,UAAU;oBAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAClD,KAAK,CAAC,cAAc,GAAG,UAAU,CAAC;gBAClC,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;YACjC,CAAC;YACD,KAAK,CAAC,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAChD,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC9B,CAAC;IACF,CAAC;IAEO,eAAe,CAAC,OAAgC;QACvD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YACzG,MAAM,IAAI,uBAAuB,CAAC,cAAc,CAAC,CAAC;QACnD,CAAC;IACF,CAAC;CACD","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, resolve } from \"node:path\";\nimport { ProviderScope, runWithProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport {\n\ttype AgentSessionLaunchProfile,\n\tAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionRuntime,\n} from \"../../core/agent-session-runtime.ts\";\nimport { SessionManager } from \"../../core/session-manager.ts\";\n\n/** The immutable flags selected when a routing session is opened. */\nexport interface RpcSessionLaunchProfile extends AgentSessionLaunchProfile {\n\tsessionPath?: string;\n}\n\nexport type SessionRuntime = AgentSessionRuntime;\nexport type RpcSessionState = \"opening\" | \"open\" | \"closing\" | \"closed\";\n\nexport interface RpcSessionEntry {\n\tstate: RpcSessionState;\n\truntime?: SessionRuntime;\n\t/** Resolves replacement against the runtime currently owned by this entry. */\n\tswitchSession?: SessionRuntime[\"switchSession\"];\n\t/** Rebind callback installed by the shared RPC connection handler. */\n\trebindSession?: Parameters<SessionRuntime[\"setRebindSession\"]>[0];\n\tscope: ProviderScope;\n\tprofile: Readonly<RpcSessionLaunchProfile>;\n\tdurableSessionId?: string;\n\tsessionPath?: string;\n\t/** Canonical reservation key for path-opened sessions; matches the reservations set. */\n\treservationKey?: string;\n\t/** Current runtime cwd, which can change when a session is replaced. */\n\tcwd: string;\n\t/** Live attachments (open + later attaches). The runtime is disposed only when the last one closes. */\n\tattachments: number;\n\tlifecycleMutex: Promise<void>;\n}\n\nexport class RpcSessionRegistryError extends Error {\n\treadonly code: \"unknown_session\" | \"session_closing\" | \"session_path_in_use\" | \"invalid_path\" | \"open_failed\";\n\n\tconstructor(code: RpcSessionRegistryError[\"code\"], reason?: string) {\n\t\tsuper(code === \"open_failed\" && reason ? `${code}: ${reason}` : code);\n\t\tthis.code = code;\n\t\tthis.name = \"RpcSessionRegistryError\";\n\t}\n}\n\nexport interface RpcSessionRegistryOptions {\n\tagentDir: string;\n\tcreateRuntime: CreateAgentSessionRuntimeFactory;\n}\n\nexport interface OpenRpcSession {\n\tsessionId: string;\n\tdurableSessionId: string;\n\tsessionPath?: string;\n\t/** True when this open attached to an already-open session instead of creating one. */\n\tattached?: boolean;\n}\n\nfunction canonicalPath(path: string): string {\n\tconst absolutePath = resolve(path);\n\tif (existsSync(absolutePath)) return realpathSync(absolutePath);\n\treturn `${realpathSync(dirname(absolutePath))}/${basename(absolutePath)}`;\n}\n\nfunction frozenProfile(profile: RpcSessionLaunchProfile): Readonly<RpcSessionLaunchProfile> {\n\treturn Object.freeze({\n\t\t...profile,\n\t\t...(profile.creationModel ? { creationModel: Object.freeze({ ...profile.creationModel }) } : {}),\n\t});\n}\n\n/** Process-local lifecycle owner for multi-session RPC runtimes. */\nexport class RpcSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new Set<string>();\n\tprivate nextHandle = 0;\n\tprivate readonly options: RpcSessionRegistryOptions;\n\n\tconstructor(options: RpcSessionRegistryOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync openSession(profile: RpcSessionLaunchProfile): Promise<OpenRpcSession> {\n\t\tthis.validateProfile(profile);\n\t\tthis.syncRuntimeMetadata();\n\t\tconst sessionPath = profile.sessionPath ? canonicalPath(profile.sessionPath) : undefined;\n\t\tif (sessionPath && this.reservations.has(sessionPath)) {\n\t\t\t// Attach-on-open: a live session outlives individual client attachments, so a\n\t\t\t// resume (or a second surface) for an already-hosted path joins the existing\n\t\t\t// runtime instead of failing. Entries still opening or closing keep the\n\t\t\t// exclusive reservation and reject as before.\n\t\t\tconst existing = [...this.entries].find(\n\t\t\t\t([, entry]) => entry.reservationKey === sessionPath && entry.state === \"open\",\n\t\t\t);\n\t\t\tif (!existing) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\tconst [handle, entry] = existing;\n\t\t\tentry.attachments += 1;\n\t\t\tif (!entry.durableSessionId) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\treturn {\n\t\t\t\tsessionId: handle,\n\t\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\t\tsessionPath: entry.sessionPath,\n\t\t\t\tattached: true,\n\t\t\t};\n\t\t}\n\t\tif (sessionPath) this.reservations.add(sessionPath);\n\n\t\t// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):\n\t\t// Create-only launch semantics mirror classic startup flags. A resumed\n\t\t// session restores its persisted model and thinking level instead of being\n\t\t// overridden by the new open_session request.\n\t\tconst isResume = sessionPath !== undefined && existsSync(sessionPath);\n\t\tconst storedProfile = frozenProfile({ ...profile, ...(sessionPath ? { sessionPath } : {}) });\n\t\tconst runtimeProfile = isResume\n\t\t\t? frozenProfile({ ...storedProfile, creationModel: undefined, initialThinkingLevel: undefined })\n\t\t\t: storedProfile;\n\n\t\tconst handle = `rpc-${++this.nextHandle}`;\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\tsessionPath,\n\t\t\treservationKey: sessionPath,\n\t\t\tcwd: storedProfile.cwd,\n\t\t\tattachments: 1,\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tentry.switchSession = (sessionPath, options) => {\n\t\t\tconst operation = entry.lifecycleMutex.then(async () => {\n\t\t\t\tif (entry.state !== \"open\" || !entry.runtime) {\n\t\t\t\t\tthrow new RpcSessionRegistryError(\"unknown_session\");\n\t\t\t\t}\n\t\t\t\tconst runtime = entry.runtime;\n\t\t\t\tconst cwdOverride = options?.cwdOverride;\n\t\t\t\tconst cwdChanged = cwdOverride !== undefined && runtime.session.sessionManager.getCwd() !== cwdOverride;\n\t\t\t\tconst result = await runtime.switchSession(sessionPath, options);\n\t\t\t\tif (result.cancelled || !cwdChanged) return result;\n\n\t\t\t\t// A multi-session binding outlives an individual replacement. Keep the\n\t\t\t\t// entry's runtime object aligned with the replacement so every attached\n\t\t\t\t// client resolves getters and future commands against the new cwd-bound\n\t\t\t\t// runtime, not the object created during open_session.\n\t\t\t\tconst replacement = new AgentSessionRuntime(\n\t\t\t\t\truntime.session,\n\t\t\t\t\truntime.services,\n\t\t\t\t\tthis.options.createRuntime,\n\t\t\t\t\t[...runtime.diagnostics],\n\t\t\t\t\truntime.modelFallbackMessage,\n\t\t\t\t\truntime.launchProfile,\n\t\t\t\t);\n\t\t\t\treplacement.setRebindSession(entry.rebindSession);\n\t\t\t\tentry.runtime = replacement;\n\t\t\t\tthis.syncRuntimeMetadata();\n\t\t\t\treturn result;\n\t\t\t});\n\t\t\tentry.lifecycleMutex = operation.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\treturn operation;\n\t\t};\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst manager = sessionPath\n\t\t\t\t? SessionManager.open(sessionPath, undefined, storedProfile.cwd)\n\t\t\t\t: SessionManager.create(storedProfile.cwd);\n\t\t\tentry.runtime = await runWithProviderScope(entry.scope, () =>\n\t\t\t\tcreateAgentSessionRuntime(this.options.createRuntime, {\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\t\tsessionManager: manager,\n\t\t\t\t\tlaunchProfile: runtimeProfile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.sessionPath ??= manager.getSessionFile();\n\t\t\tentry.state = \"open\";\n\t\t\treturn { sessionId: handle, durableSessionId: entry.durableSessionId, sessionPath: entry.sessionPath };\n\t\t} catch (error) {\n\t\t\t// Runtime construction may have started extensions, watchers, and provider\n\t\t\t// registrations before it rejects. Keep the reservation and entry private\n\t\t\t// until all of those resources have been torn down, then release them as\n\t\t\t// one rollback so the path can immediately be opened again.\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t} catch {\n\t\t\t\t// The original construction error remains the externally visible cause.\n\t\t\t} finally {\n\t\t\t\ttry {\n\t\t\t\t\tawait entry.scope.close?.();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.entries.delete(handle);\n\t\t\t\t\tif (sessionPath) this.reservations.delete(sessionPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (error instanceof RpcSessionRegistryError) throw error;\n\t\t\tthrow new RpcSessionRegistryError(\"open_failed\", error instanceof Error ? error.message : undefined);\n\t\t}\n\t}\n\n\t/**\n\t * Read-only lookup with no state transitions or attachment accounting.\n\t * Exists so lifecycle decisions (e.g. deferring a dropped connection's\n\t * release while a turn is still streaming) can inspect the live entry\n\t * without claiming it.\n\t */\n\tpeek(handle: string): RpcSessionEntry | undefined {\n\t\treturn this.entries.get(handle);\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" && ![\"abort\", \"abort_bash\", \"extension_ui_response\"].includes(command)) {\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Starts a close synchronously. Call this before disposing any session-owned\n\t * binding so a concurrent command cannot reach a half-disposed handler.\n\t * Each call releases one attachment; the entry transitions to \"closing\" only\n\t * when the last attachment closes, so callers must check the returned state\n\t * and skip finalization while other attachments remain.\n\t */\n\tbeginClose(handle: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.attachments -= 1;\n\t\tif (entry.attachments > 0) return entry;\n\t\tentry.state = \"closing\";\n\t\treturn entry;\n\t}\n\n\tasync close(handle: string): Promise<void> {\n\t\tconst entry = this.beginClose(handle);\n\t\t// Other attachments may still own the live session; only the last close finalizes.\n\t\tif (entry.state !== \"closing\") return;\n\t\treturn this.closeMarked(handle);\n\t}\n\n\t/** Completes a close previously made visible by beginClose(). */\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tthis.syncRuntimeMetadata();\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tconst previousLifecycle = entry.lifecycleMutex;\n\t\tentry.lifecycleMutex = (async () => {\n\t\t\tawait previousLifecycle;\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.session.abort();\n\t\t\t\tawait entry.runtime?.session.waitForIdle();\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t\tawait entry.scope.close?.();\n\t\t\t} finally {\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tif (entry.reservationKey) this.reservations.delete(entry.reservationKey);\n\t\t\t}\n\t\t})();\n\t\treturn entry.lifecycleMutex;\n\t}\n\n\tlist(): Array<{\n\t\tsessionId: string;\n\t\tdurableSessionId?: string;\n\t\tsessionPath?: string;\n\t\tcwd: string;\n\t\tname?: string;\n\t\tstatus: RpcSessionState;\n\t}> {\n\t\tthis.syncRuntimeMetadata();\n\t\treturn [...this.entries].map(([sessionId, entry]) => ({\n\t\t\tsessionId,\n\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\tsessionPath: entry.sessionPath,\n\t\t\tcwd: entry.cwd,\n\t\t\tname: entry.runtime?.session.sessionManager.getSessionName(),\n\t\t\tstatus: entry.state,\n\t\t}));\n\t}\n\n\t/** Reconcile path and durable identity after runtime replacement. */\n\tprivate syncRuntimeMetadata(): void {\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tconst manager = entry.runtime?.session.sessionManager;\n\t\t\tif (!manager) continue;\n\t\t\tconst currentPath = manager.getSessionFile();\n\t\t\t// Preserve the originally canonicalized key while the runtime still points at\n\t\t\t// the same path. SessionManager may expose a symlink-resolved spelling after\n\t\t\t// opening a file that did not exist yet; treating that as replacement would\n\t\t\t// break ordinary attach-on-open aliases.\n\t\t\tif (currentPath !== entry.sessionPath) {\n\t\t\t\tconst currentKey = currentPath ? canonicalPath(currentPath) : undefined;\n\t\t\t\tif (entry.reservationKey) this.reservations.delete(entry.reservationKey);\n\t\t\t\tif (currentKey) this.reservations.add(currentKey);\n\t\t\t\tentry.reservationKey = currentKey;\n\t\t\t\tentry.sessionPath = currentPath;\n\t\t\t}\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.cwd = manager.getCwd();\n\t\t}\n\t}\n\n\tprivate validateProfile(profile: RpcSessionLaunchProfile): void {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath))) {\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, resolve } from \"node:path\";\nimport { ProviderScope, runWithProviderScope } from \"@earendil-works/pi-ai/node/provider-scope\";\nimport {\n\ttype AgentSessionLaunchProfile,\n\tAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionRuntime,\n} from \"../../core/agent-session-runtime.ts\";\nimport { SessionManager } from \"../../core/session-manager.ts\";\n\n/** The immutable flags selected when a routing session is opened. */\nexport interface RpcSessionLaunchProfile extends AgentSessionLaunchProfile {\n\tsessionPath?: string;\n}\n\nexport type SessionRuntime = AgentSessionRuntime;\nexport type RpcSessionState = \"opening\" | \"open\" | \"closing\" | \"closed\";\n\nexport interface RpcSessionEntry {\n\tstate: RpcSessionState;\n\truntime?: SessionRuntime;\n\t/** Resolves replacement against the runtime currently owned by this entry. */\n\tswitchSession?: SessionRuntime[\"switchSession\"];\n\t/** Rebind callback installed by the shared RPC connection handler. */\n\trebindSession?: Parameters<SessionRuntime[\"setRebindSession\"]>[0];\n\tscope: ProviderScope;\n\tprofile: Readonly<RpcSessionLaunchProfile>;\n\tdurableSessionId?: string;\n\tsessionPath?: string;\n\t/** Canonical reservation key for path-opened sessions; matches the reservations set. */\n\treservationKey?: string;\n\t/** Current runtime cwd, which can change when a session is replaced. */\n\tcwd: string;\n\t/** Live attachments (open + later attaches). The runtime is disposed only when the last one closes. */\n\tattachments: number;\n\t/** Timestamp of the last routed command / observed activity; drives idle eviction. */\n\tlastCommandAt: number;\n\tlifecycleMutex: Promise<void>;\n}\n\nexport class RpcSessionRegistryError extends Error {\n\treadonly code:\n\t\t| \"unknown_session\"\n\t\t| \"session_closing\"\n\t\t| \"session_path_in_use\"\n\t\t| \"invalid_path\"\n\t\t| \"open_failed\"\n\t\t| \"too_many_sessions\";\n\n\tconstructor(code: RpcSessionRegistryError[\"code\"], reason?: string) {\n\t\tsuper(code === \"open_failed\" && reason ? `${code}: ${reason}` : code);\n\t\tthis.code = code;\n\t\tthis.name = \"RpcSessionRegistryError\";\n\t}\n}\n\nexport interface RpcSessionRegistryOptions {\n\tagentDir: string;\n\tcreateRuntime: CreateAgentSessionRuntimeFactory;\n\t/** Injectable clock (defaults to Date.now) so idle bookkeeping is testable. */\n\tnow?: () => number;\n\t/** Cap on concurrently opening/open sessions; attach-on-open is exempt. */\n\tmaxSessions?: number;\n}\n\nexport interface OpenRpcSession {\n\tsessionId: string;\n\tdurableSessionId: string;\n\tsessionPath?: string;\n\t/** True when this open attached to an already-open session instead of creating one. */\n\tattached?: boolean;\n}\n\nfunction canonicalPath(path: string): string {\n\tconst absolutePath = resolve(path);\n\tif (existsSync(absolutePath)) return realpathSync(absolutePath);\n\treturn `${realpathSync(dirname(absolutePath))}/${basename(absolutePath)}`;\n}\n\nfunction frozenProfile(profile: RpcSessionLaunchProfile): Readonly<RpcSessionLaunchProfile> {\n\treturn Object.freeze({\n\t\t...profile,\n\t\t...(profile.creationModel ? { creationModel: Object.freeze({ ...profile.creationModel }) } : {}),\n\t});\n}\n\n/** Process-local lifecycle owner for multi-session RPC runtimes. */\nexport class RpcSessionRegistry {\n\tprivate readonly entries = new Map<string, RpcSessionEntry>();\n\tprivate readonly reservations = new Set<string>();\n\tprivate nextHandle = 0;\n\tprivate readonly options: RpcSessionRegistryOptions;\n\tprivate readonly now: () => number;\n\n\tconstructor(options: RpcSessionRegistryOptions) {\n\t\tthis.options = options;\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\t/** Number of live entries, including ones still opening or closing. */\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\tasync openSession(profile: RpcSessionLaunchProfile): Promise<OpenRpcSession> {\n\t\tthis.validateProfile(profile);\n\t\tthis.syncRuntimeMetadata();\n\t\tconst sessionPath = profile.sessionPath ? canonicalPath(profile.sessionPath) : undefined;\n\t\tif (sessionPath && this.reservations.has(sessionPath)) {\n\t\t\t// Attach-on-open: a live session outlives individual client attachments, so a\n\t\t\t// resume (or a second surface) for an already-hosted path joins the existing\n\t\t\t// runtime instead of failing. Entries still opening or closing keep the\n\t\t\t// exclusive reservation and reject as before.\n\t\t\tconst existing = [...this.entries].find(\n\t\t\t\t([, entry]) => entry.reservationKey === sessionPath && entry.state === \"open\",\n\t\t\t);\n\t\t\tif (!existing) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\tconst [handle, entry] = existing;\n\t\t\tentry.attachments += 1;\n\t\t\tif (!entry.durableSessionId) throw new RpcSessionRegistryError(\"session_path_in_use\");\n\t\t\tentry.lastCommandAt = this.now();\n\t\t\treturn {\n\t\t\t\tsessionId: handle,\n\t\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\t\tsessionPath: entry.sessionPath,\n\t\t\t\tattached: true,\n\t\t\t};\n\t\t}\n\t\tconst maxSessions = this.options.maxSessions;\n\t\tif (maxSessions !== undefined && this.countActiveSessions() >= maxSessions) {\n\t\t\t// Reconnect-churn backstop: every open_session owns a complete runtime\n\t\t\t// (hundreds of MB measured), so admission is bounded. Attachments to a\n\t\t\t// live session add none and stay allowed via the branch above.\n\t\t\tthrow new RpcSessionRegistryError(\"too_many_sessions\");\n\t\t}\n\t\tif (sessionPath) this.reservations.add(sessionPath);\n\n\t\t// Resume vs create parity (D1 + omo SenpiSessionRuntime.ts:198-200):\n\t\t// Create-only launch semantics mirror classic startup flags. A resumed\n\t\t// session restores its persisted model and thinking level instead of being\n\t\t// overridden by the new open_session request.\n\t\tconst isResume = sessionPath !== undefined && existsSync(sessionPath);\n\t\tconst storedProfile = frozenProfile({ ...profile, ...(sessionPath ? { sessionPath } : {}) });\n\t\tconst runtimeProfile = isResume\n\t\t\t? frozenProfile({ ...storedProfile, creationModel: undefined, initialThinkingLevel: undefined })\n\t\t\t: storedProfile;\n\n\t\tconst handle = `rpc-${++this.nextHandle}`;\n\t\tconst entry: RpcSessionEntry = {\n\t\t\tstate: \"opening\",\n\t\t\tscope: new ProviderScope(),\n\t\t\tprofile: storedProfile,\n\t\t\tsessionPath,\n\t\t\treservationKey: sessionPath,\n\t\t\tcwd: storedProfile.cwd,\n\t\t\tattachments: 1,\n\t\t\tlastCommandAt: this.now(),\n\t\t\tlifecycleMutex: Promise.resolve(),\n\t\t};\n\t\tentry.switchSession = (sessionPath, options) => {\n\t\t\tconst operation = entry.lifecycleMutex.then(async () => {\n\t\t\t\tif (entry.state !== \"open\" || !entry.runtime) {\n\t\t\t\t\tthrow new RpcSessionRegistryError(\"unknown_session\");\n\t\t\t\t}\n\t\t\t\tconst runtime = entry.runtime;\n\t\t\t\tconst cwdOverride = options?.cwdOverride;\n\t\t\t\tconst cwdChanged = cwdOverride !== undefined && runtime.session.sessionManager.getCwd() !== cwdOverride;\n\t\t\t\tconst result = await runtime.switchSession(sessionPath, options);\n\t\t\t\tif (result.cancelled || !cwdChanged) return result;\n\n\t\t\t\t// A multi-session binding outlives an individual replacement. Keep the\n\t\t\t\t// entry's runtime object aligned with the replacement so every attached\n\t\t\t\t// client resolves getters and future commands against the new cwd-bound\n\t\t\t\t// runtime, not the object created during open_session.\n\t\t\t\tconst replacement = new AgentSessionRuntime(\n\t\t\t\t\truntime.session,\n\t\t\t\t\truntime.services,\n\t\t\t\t\tthis.options.createRuntime,\n\t\t\t\t\t[...runtime.diagnostics],\n\t\t\t\t\truntime.modelFallbackMessage,\n\t\t\t\t\truntime.launchProfile,\n\t\t\t\t);\n\t\t\t\treplacement.setRebindSession(entry.rebindSession);\n\t\t\t\tentry.runtime = replacement;\n\t\t\t\tthis.syncRuntimeMetadata();\n\t\t\t\treturn result;\n\t\t\t});\n\t\t\tentry.lifecycleMutex = operation.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\treturn operation;\n\t\t};\n\t\tthis.entries.set(handle, entry);\n\t\ttry {\n\t\t\tconst manager = sessionPath\n\t\t\t\t? SessionManager.open(sessionPath, undefined, storedProfile.cwd)\n\t\t\t\t: SessionManager.create(storedProfile.cwd);\n\t\t\tentry.runtime = await runWithProviderScope(entry.scope, () =>\n\t\t\t\tcreateAgentSessionRuntime(this.options.createRuntime, {\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tagentDir: this.options.agentDir,\n\t\t\t\t\tsessionManager: manager,\n\t\t\t\t\tlaunchProfile: runtimeProfile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.sessionPath ??= manager.getSessionFile();\n\t\t\tentry.state = \"open\";\n\t\t\treturn { sessionId: handle, durableSessionId: entry.durableSessionId, sessionPath: entry.sessionPath };\n\t\t} catch (error) {\n\t\t\t// Runtime construction may have started extensions, watchers, and provider\n\t\t\t// registrations before it rejects. Keep the reservation and entry private\n\t\t\t// until all of those resources have been torn down, then release them as\n\t\t\t// one rollback so the path can immediately be opened again.\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t} catch {\n\t\t\t\t// The original construction error remains the externally visible cause.\n\t\t\t} finally {\n\t\t\t\ttry {\n\t\t\t\t\tawait entry.scope.close?.();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.entries.delete(handle);\n\t\t\t\t\tif (sessionPath) this.reservations.delete(sessionPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (error instanceof RpcSessionRegistryError) throw error;\n\t\t\tthrow new RpcSessionRegistryError(\"open_failed\", error instanceof Error ? error.message : undefined);\n\t\t}\n\t}\n\n\t/**\n\t * Read-only lookup with no state transitions or attachment accounting.\n\t * Exists so lifecycle decisions (e.g. deferring a dropped connection's\n\t * release while a turn is still streaming) can inspect the live entry\n\t * without claiming it.\n\t */\n\tpeek(handle: string): RpcSessionEntry | undefined {\n\t\treturn this.entries.get(handle);\n\t}\n\n\tgetForCommand(handle: string, command: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (!entry) throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tif (entry.state === \"closing\" && ![\"abort\", \"abort_bash\", \"extension_ui_response\"].includes(command)) {\n\t\t\tthrow new RpcSessionRegistryError(\"session_closing\");\n\t\t}\n\t\tif (entry.state !== \"open\" && entry.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\t// Every routed command counts as activity for idle eviction. Lookups that\n\t\t// must not refresh idleness (sweeps, listing) use peek()/list() instead.\n\t\tentry.lastCommandAt = this.now();\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Starts a close synchronously. Call this before disposing any session-owned\n\t * binding so a concurrent command cannot reach a half-disposed handler.\n\t * Each call releases one attachment; the entry transitions to \"closing\" only\n\t * when the last attachment closes, so callers must check the returned state\n\t * and skip finalization while other attachments remain.\n\t */\n\tbeginClose(handle: string): RpcSessionEntry {\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"open\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tentry.attachments -= 1;\n\t\tif (entry.attachments > 0) return entry;\n\t\tentry.state = \"closing\";\n\t\treturn entry;\n\t}\n\n\tasync close(handle: string): Promise<void> {\n\t\tconst entry = this.beginClose(handle);\n\t\t// Other attachments may still own the live session; only the last close finalizes.\n\t\tif (entry.state !== \"closing\") return;\n\t\treturn this.closeMarked(handle);\n\t}\n\n\t/** Completes a close previously made visible by beginClose(). */\n\tasync closeMarked(handle: string): Promise<void> {\n\t\tthis.syncRuntimeMetadata();\n\t\tconst entry = this.entries.get(handle);\n\t\tif (entry?.state !== \"closing\") throw new RpcSessionRegistryError(\"unknown_session\");\n\t\tconst previousLifecycle = entry.lifecycleMutex;\n\t\tentry.lifecycleMutex = (async () => {\n\t\t\tawait previousLifecycle;\n\t\t\ttry {\n\t\t\t\tawait entry.runtime?.session.abort();\n\t\t\t\tawait entry.runtime?.session.waitForIdle();\n\t\t\t\tawait entry.runtime?.dispose();\n\t\t\t\tawait entry.scope.close?.();\n\t\t\t} finally {\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.entries.delete(handle);\n\t\t\t\tif (entry.reservationKey) this.reservations.delete(entry.reservationKey);\n\t\t\t}\n\t\t})();\n\t\treturn entry.lifecycleMutex;\n\t}\n\n\tlist(): Array<{\n\t\tsessionId: string;\n\t\tdurableSessionId?: string;\n\t\tsessionPath?: string;\n\t\tcwd: string;\n\t\tname?: string;\n\t\tstatus: RpcSessionState;\n\t}> {\n\t\tthis.syncRuntimeMetadata();\n\t\treturn [...this.entries].map(([sessionId, entry]) => ({\n\t\t\tsessionId,\n\t\t\tdurableSessionId: entry.durableSessionId,\n\t\t\tsessionPath: entry.sessionPath,\n\t\t\tcwd: entry.cwd,\n\t\t\tname: entry.runtime?.session.sessionManager.getSessionName(),\n\t\t\tstatus: entry.state,\n\t\t}));\n\t}\n\n\t/** Reconcile path and durable identity after runtime replacement. */\n\tprivate syncRuntimeMetadata(): void {\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tconst manager = entry.runtime?.session.sessionManager;\n\t\t\tif (!manager) continue;\n\t\t\tconst currentPath = manager.getSessionFile();\n\t\t\t// Preserve the originally canonicalized key while the runtime still points at\n\t\t\t// the same path. SessionManager may expose a symlink-resolved spelling after\n\t\t\t// opening a file that did not exist yet; treating that as replacement would\n\t\t\t// break ordinary attach-on-open aliases.\n\t\t\tif (currentPath !== entry.sessionPath) {\n\t\t\t\tconst currentKey = currentPath ? canonicalPath(currentPath) : undefined;\n\t\t\t\tif (entry.reservationKey) this.reservations.delete(entry.reservationKey);\n\t\t\t\tif (currentKey) this.reservations.add(currentKey);\n\t\t\t\tentry.reservationKey = currentKey;\n\t\t\t\tentry.sessionPath = currentPath;\n\t\t\t}\n\t\t\tentry.durableSessionId = manager.getSessionId();\n\t\t\tentry.cwd = manager.getCwd();\n\t\t}\n\t}\n\n\tprivate validateProfile(profile: RpcSessionLaunchProfile): void {\n\t\tif (!isAbsolute(profile.cwd) || (profile.sessionPath !== undefined && !isAbsolute(profile.sessionPath))) {\n\t\t\tthrow new RpcSessionRegistryError(\"invalid_path\");\n\t\t}\n\t}\n\n\tprivate countActiveSessions(): number {\n\t\tlet count = 0;\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tif (entry.state === \"opening\" || entry.state === \"open\") count += 1;\n\t\t}\n\t\treturn count;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
2
|
+
import { type Theme } from "../interactive/theme/theme.ts";
|
|
3
|
+
export interface LiveComponentRenderer {
|
|
4
|
+
rerender(): void;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLiveComponentRenderer(options: {
|
|
8
|
+
factory: (...args: [TUI, Theme, ...unknown[]]) => Component & {
|
|
9
|
+
dispose?(): void;
|
|
10
|
+
};
|
|
11
|
+
/** Additional arguments are used by footer factories; widgets retain the 2-arg path. */
|
|
12
|
+
factoryArgs?: readonly unknown[];
|
|
13
|
+
getWidth: () => number;
|
|
14
|
+
emit: (lines: string[]) => void;
|
|
15
|
+
onRenderFault?: (error: unknown) => void;
|
|
16
|
+
}): LiveComponentRenderer | undefined;
|
|
17
|
+
//# sourceMappingURL=widget-line-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-line-renderer.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/widget-line-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAa,KAAK,KAAK,EAAS,MAAM,+BAA+B,CAAC;AAE7E,MAAM,WAAW,qBAAqB;IACrC,QAAQ,IAAI,IAAI,CAAC;IACjB,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACpD,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,KAAK,SAAS,GAAG;QAAE,OAAO,CAAC,IAAI,IAAI,CAAA;KAAE,CAAC;IACnF,wFAAwF;IACxF,WAAW,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACjC,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC,GAAG,qBAAqB,GAAG,SAAS,CA+DpC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { initTheme, theme } from "../interactive/theme/theme.js";
|
|
2
|
+
export function createLiveComponentRenderer(options) {
|
|
3
|
+
let disposed = false;
|
|
4
|
+
let timer;
|
|
5
|
+
try {
|
|
6
|
+
// RPC hosts do not necessarily pass through the interactive theme setup.
|
|
7
|
+
try {
|
|
8
|
+
void theme.name;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
initTheme("dark");
|
|
12
|
+
}
|
|
13
|
+
let component;
|
|
14
|
+
let lastLines;
|
|
15
|
+
const render = () => {
|
|
16
|
+
if (disposed)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const lines = component.render(options.getWidth());
|
|
20
|
+
if (lastLines &&
|
|
21
|
+
lines.length === lastLines.length &&
|
|
22
|
+
lines.every((line, i) => line === lastLines[i]))
|
|
23
|
+
return;
|
|
24
|
+
lastLines = [...lines];
|
|
25
|
+
options.emit(lines);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// Allow a recovered render to be emitted even when it matches the last
|
|
29
|
+
// successful frame; the fault means the client may have lost that frame.
|
|
30
|
+
lastLines = undefined;
|
|
31
|
+
options.onRenderFault?.(error);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const requestRender = () => {
|
|
35
|
+
if (disposed || timer !== undefined)
|
|
36
|
+
return;
|
|
37
|
+
timer = setTimeout(() => {
|
|
38
|
+
timer = undefined;
|
|
39
|
+
render();
|
|
40
|
+
}, 0);
|
|
41
|
+
};
|
|
42
|
+
const tui = new Proxy({ requestRender }, {
|
|
43
|
+
get(target, property) {
|
|
44
|
+
if (property in target)
|
|
45
|
+
return target[property];
|
|
46
|
+
throw new Error(`RPC live component TUI member is unsupported: ${String(property)}`);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
component = options.factory(tui, theme, ...(options.factoryArgs ?? []));
|
|
50
|
+
render();
|
|
51
|
+
return {
|
|
52
|
+
rerender: render,
|
|
53
|
+
dispose: () => {
|
|
54
|
+
if (disposed)
|
|
55
|
+
return;
|
|
56
|
+
disposed = true;
|
|
57
|
+
if (timer !== undefined)
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
timer = undefined;
|
|
60
|
+
component.dispose?.();
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
disposed = true;
|
|
66
|
+
if (timer !== undefined)
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
timer = undefined;
|
|
69
|
+
options.onRenderFault?.(error);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=widget-line-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-line-renderer.js","sourceRoot":"","sources":["../../../src/modes/rpc/widget-line-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAc,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAO7E,MAAM,UAAU,2BAA2B,CAAC,OAO3C;IACA,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,KAAgD,CAAC;IACrD,IAAI,CAAC;QACJ,yEAAyE;QACzE,IAAI,CAAC;YACJ,KAAK,KAAK,CAAC,IAAI,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACR,SAAS,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,SAA2C,CAAC;QAChD,IAAI,SAA+B,CAAC;QACpC,MAAM,MAAM,GAAG,GAAG,EAAE;YACnB,IAAI,QAAQ;gBAAE,OAAO;YACrB,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACnD,IACC,SAAS;oBACT,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;oBACjC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAY,EAAE,CAAS,EAAE,EAAE,CAAC,IAAI,KAAK,SAAU,CAAC,CAAC,CAAC,CAAC;oBAEhE,OAAO;gBACR,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;gBACvB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,uEAAuE;gBACvE,yEAAyE;gBACzE,SAAS,GAAG,SAAS,CAAC;gBACtB,OAAO,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;QACF,CAAC,CAAC;QACF,MAAM,aAAa,GAAG,GAAG,EAAE;YAC1B,IAAI,QAAQ,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO;YAC5C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACvB,KAAK,GAAG,SAAS,CAAC;gBAClB,MAAM,EAAE,CAAC;YACV,CAAC,EAAE,CAAC,CAAC,CAAC;QACP,CAAC,CAAC;QACF,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,aAAa,EAA6B,EAAE;YACnE,GAAG,CAAC,MAAM,EAAE,QAAyB;gBACpC,IAAI,QAAQ,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAC,QAAkB,CAAC,CAAC;gBAC1D,MAAM,IAAI,KAAK,CAAC,iDAAiD,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;SACD,CAAmB,CAAC;QACrB,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,EAAE,CAAC;QACT,OAAO;YACN,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,GAAG,EAAE;gBACb,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,KAAK,KAAK,SAAS;oBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;gBAC7C,KAAK,GAAG,SAAS,CAAC;gBAClB,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;YACvB,CAAC;SACD,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,KAAK,KAAK,SAAS;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC7C,KAAK,GAAG,SAAS,CAAC;QAClB,OAAO,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC","sourcesContent":["import type { Component, TUI } from \"@earendil-works/pi-tui\";\nimport { initTheme, type Theme, theme } from \"../interactive/theme/theme.ts\";\n\nexport interface LiveComponentRenderer {\n\trerender(): void;\n\tdispose(): void;\n}\n\nexport function createLiveComponentRenderer(options: {\n\tfactory: (...args: [TUI, Theme, ...unknown[]]) => Component & { dispose?(): void };\n\t/** Additional arguments are used by footer factories; widgets retain the 2-arg path. */\n\tfactoryArgs?: readonly unknown[];\n\tgetWidth: () => number;\n\temit: (lines: string[]) => void;\n\tonRenderFault?: (error: unknown) => void;\n}): LiveComponentRenderer | undefined {\n\tlet disposed = false;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\t// RPC hosts do not necessarily pass through the interactive theme setup.\n\t\ttry {\n\t\t\tvoid theme.name;\n\t\t} catch {\n\t\t\tinitTheme(\"dark\");\n\t\t}\n\t\tlet component: Component & { dispose?(): void };\n\t\tlet lastLines: string[] | undefined;\n\t\tconst render = () => {\n\t\t\tif (disposed) return;\n\t\t\ttry {\n\t\t\t\tconst lines = component.render(options.getWidth());\n\t\t\t\tif (\n\t\t\t\t\tlastLines &&\n\t\t\t\t\tlines.length === lastLines.length &&\n\t\t\t\t\tlines.every((line: string, i: number) => line === lastLines![i])\n\t\t\t\t)\n\t\t\t\t\treturn;\n\t\t\t\tlastLines = [...lines];\n\t\t\t\toptions.emit(lines);\n\t\t\t} catch (error) {\n\t\t\t\t// Allow a recovered render to be emitted even when it matches the last\n\t\t\t\t// successful frame; the fault means the client may have lost that frame.\n\t\t\t\tlastLines = undefined;\n\t\t\t\toptions.onRenderFault?.(error);\n\t\t\t}\n\t\t};\n\t\tconst requestRender = () => {\n\t\t\tif (disposed || timer !== undefined) return;\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttimer = undefined;\n\t\t\t\trender();\n\t\t\t}, 0);\n\t\t};\n\t\tconst tui = new Proxy({ requestRender } as Record<string, unknown>, {\n\t\t\tget(target, property: string | symbol) {\n\t\t\t\tif (property in target) return target[property as string];\n\t\t\t\tthrow new Error(`RPC live component TUI member is unsupported: ${String(property)}`);\n\t\t\t},\n\t\t}) as unknown as TUI;\n\t\tcomponent = options.factory(tui, theme, ...(options.factoryArgs ?? []));\n\t\trender();\n\t\treturn {\n\t\t\trerender: render,\n\t\t\tdispose: () => {\n\t\t\t\tif (disposed) return;\n\t\t\t\tdisposed = true;\n\t\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\t\ttimer = undefined;\n\t\t\t\tcomponent.dispose?.();\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tdisposed = true;\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\toptions.onRenderFault?.(error);\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAS,MAAM,SAAS,CAAC;AAErG,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAUxE;AAED,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,CAAC,EAAE,YAAY,GACpB,SAAS,GAAG,IAAI,CAiBlB"}
|
package/dist/utils/fs-watch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { watch } from "node:fs";
|
|
1
|
+
import { realpathSync, watch } from "node:fs";
|
|
2
2
|
export const FS_WATCH_RETRY_DELAY_MS = 5000;
|
|
3
3
|
export function closeWatcher(watcher) {
|
|
4
4
|
if (!watcher) {
|
|
@@ -13,7 +13,16 @@ export function closeWatcher(watcher) {
|
|
|
13
13
|
}
|
|
14
14
|
export function watchWithErrorHandler(path, listener, onError, options) {
|
|
15
15
|
try {
|
|
16
|
-
|
|
16
|
+
let watchPath = path;
|
|
17
|
+
if (process.platform === "win32") {
|
|
18
|
+
try {
|
|
19
|
+
watchPath = realpathSync.native(path);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Keep the raw path when it does not exist yet.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const watcher = watch(watchPath, { ...options, encoding: "utf8" }, listener);
|
|
17
26
|
watcher.on("error", onError);
|
|
18
27
|
return watcher;
|
|
19
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,YAAY,EAAyC,KAAK,EAAE,MAAM,SAAS,CAAC;AAErG,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAqC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACR,8BAA8B;IAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,IAAY,EACZ,QAA+B,EAC/B,OAAmB,EACnB,OAAsB;IAEtB,IAAI,CAAC;QACJ,IAAI,SAAS,GAAG,IAAI,CAAC;QACrB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC;gBACJ,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACR,gDAAgD;YACjD,CAAC;QACF,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC7E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { type FSWatcher, realpathSync, type WatchListener, type WatchOptions, watch } from \"node:fs\";\n\nexport const FS_WATCH_RETRY_DELAY_MS = 5000;\n\nexport function closeWatcher(watcher: FSWatcher | null | undefined): void {\n\tif (!watcher) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\twatcher.close();\n\t} catch {\n\t\t// Ignore watcher close errors\n\t}\n}\n\nexport function watchWithErrorHandler(\n\tpath: string,\n\tlistener: WatchListener<string>,\n\tonError: () => void,\n\toptions?: WatchOptions,\n): FSWatcher | null {\n\ttry {\n\t\tlet watchPath = path;\n\t\tif (process.platform === \"win32\") {\n\t\t\ttry {\n\t\t\t\twatchPath = realpathSync.native(path);\n\t\t\t} catch {\n\t\t\t\t// Keep the raw path when it does not exist yet.\n\t\t\t}\n\t\t}\n\t\tconst watcher = watch(watchPath, { ...options, encoding: \"utf8\" }, listener);\n\t\twatcher.on(\"error\", onError);\n\t\treturn watcher;\n\t} catch {\n\t\tonError();\n\t\treturn null;\n\t}\n}\n"]}
|
package/docs/app-server.md
CHANGED
|
@@ -537,8 +537,8 @@ timestamps, paths, and tokens, and preserves frame order, array order, and notif
|
|
|
537
537
|
From `packages/coding-agent`, build the pinned Codex oracle once, then run the available handshake scenario:
|
|
538
538
|
|
|
539
539
|
```bash
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
bun scripts/qa-app-server/differential/build-oracle.mjs
|
|
541
|
+
bun scripts/qa-app-server/differential/run.mjs --scenario handshake
|
|
542
542
|
```
|
|
543
543
|
|
|
544
544
|
The build uses `/Users/yeongyu/local-workspaces/codex/codex-rs/Cargo.toml` and writes the binary under that checkout's
|
package/docs/containerization.md
CHANGED
|
@@ -26,7 +26,7 @@ Setup:
|
|
|
26
26
|
```bash
|
|
27
27
|
cp -R packages/coding-agent/examples/extensions/gondolin ~/.senpi/agent/extensions/gondolin
|
|
28
28
|
cd ~/.senpi/agent/extensions/gondolin
|
|
29
|
-
|
|
29
|
+
bun add --ignore-scripts
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Run from the project you want mounted:
|
|
@@ -54,7 +54,7 @@ FROM node:24-bookworm-slim
|
|
|
54
54
|
RUN apt-get update \
|
|
55
55
|
&& apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
|
|
56
56
|
&& rm -rf /var/lib/apt/lists/*
|
|
57
|
-
RUN
|
|
57
|
+
RUN bun add -g --ignore-scripts @code-yeongyu/senpi
|
|
58
58
|
|
|
59
59
|
WORKDIR /workspace
|
|
60
60
|
ENTRYPOINT ["senpi"]
|
package/docs/development.md
CHANGED
|
@@ -7,8 +7,8 @@ See [AGENTS.md](../../../AGENTS.md) at the monorepo root for fork-specific guide
|
|
|
7
7
|
```bash
|
|
8
8
|
git clone https://github.com/code-yeongyu/senpi
|
|
9
9
|
cd senpi
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
bun install
|
|
11
|
+
bun run build
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Run from source:
|
|
@@ -36,7 +36,7 @@ Change `name`, `configDir`, and `bin` field for your fork. Affects CLI banner, c
|
|
|
36
36
|
|
|
37
37
|
## Path Resolution
|
|
38
38
|
|
|
39
|
-
Three execution modes:
|
|
39
|
+
Three execution modes: bun install, standalone binary (`bun build --compile`), tsx from source.
|
|
40
40
|
|
|
41
41
|
**Always use `src/config.ts`** for package assets:
|
|
42
42
|
|
|
@@ -55,17 +55,17 @@ Never use `__dirname` directly for package assets.
|
|
|
55
55
|
## Testing
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
|
|
58
|
+
bun run test # Vitest across workspaces (skips live-API; default test runner)
|
|
59
59
|
./pi-test.sh # Launch the CLI from source via tsx for manual testing (--no-env unsets API keys)
|
|
60
|
-
|
|
60
|
+
bun run check # Biome + tsc + browser-smoke check (pre-commit equivalent)
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Live-API tests are env-gated vitest tests. Set `PI_ENABLE_LIVE_API_TESTS=1` (or a per-provider flag from `packages/ai/test/live-api-gates.ts`) plus the provider API keys, then run `
|
|
63
|
+
Live-API tests are env-gated vitest tests. Set `PI_ENABLE_LIVE_API_TESTS=1` (or a per-provider flag from `packages/ai/test/live-api-gates.ts`) plus the provider API keys, then run `bun run test`.
|
|
64
64
|
|
|
65
65
|
Run a specific test:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
|
|
68
|
+
bun run test --workspace @code-yeongyu/senpi -- test/specific.test.ts
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
## Project Structure
|
package/docs/extensions.md
CHANGED
|
@@ -184,9 +184,9 @@ To share extensions via npm or git as senpi packages, see [packages.md](packages
|
|
|
184
184
|
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
|
|
185
185
|
| `@earendil-works/pi-tui` | TUI components for custom rendering |
|
|
186
186
|
|
|
187
|
-
npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `
|
|
187
|
+
npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `bun install`, and imports from `node_modules/` are resolved automatically.
|
|
188
188
|
|
|
189
|
-
For distributed senpi packages installed with `senpi install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`
|
|
189
|
+
For distributed senpi packages installed with `senpi install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`bun add --omit=dev`) by default, so `devDependencies` are not available at runtime; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers.
|
|
190
190
|
|
|
191
191
|
Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
|
192
192
|
|
|
@@ -288,7 +288,7 @@ Defer background resource startup until `session_start` or the command/tool/even
|
|
|
288
288
|
└── my-extension/
|
|
289
289
|
├── package.json # Declares dependencies and entry points
|
|
290
290
|
├── package-lock.json
|
|
291
|
-
├── node_modules/ # After
|
|
291
|
+
├── node_modules/ # After bun install
|
|
292
292
|
└── src/
|
|
293
293
|
└── index.ts
|
|
294
294
|
```
|
|
@@ -307,7 +307,7 @@ Defer background resource startup until `session_start` or the command/tool/even
|
|
|
307
307
|
}
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
-
Run `
|
|
310
|
+
Run `bun install` in the extension directory, then imports from `node_modules/` work automatically.
|
|
311
311
|
|
|
312
312
|
## Events
|
|
313
313
|
|
package/docs/grok-neo.md
CHANGED
|
@@ -64,7 +64,7 @@ Senpi is a rebranded distribution, so its first-time setup is intentionally unav
|
|
|
64
64
|
|
|
65
65
|
`--grok-neo` runs **in-process**: the chrome is a presentation strategy inside the ordinary interactive mode, in the same senpi process. There is no separate binary and no daemon — unlike the retired `--neo`, which launched a Go TUI binary talking to senpi over a JSONL RPC daemon.
|
|
66
66
|
|
|
67
|
-
For the Bun-compiled binary (`
|
|
67
|
+
For the Bun-compiled binary (`bun run build:binary`) this means one process and one deployable directory. `bun build --compile` produces a single `dist/pi` executable, and its runtime assets ship alongside it in `dist/`: theme JSON files, image assets, the photon WASM module, and the pty native addon. Native `.node` addons are **not** embedded by `bun build --compile`; the pty addon is resolved as a sidecar relative to the executable (`dist/native/prebuilds/<platform-arch>/`). So the honest claim is "one process, one deployable directory" — not one file.
|
|
68
68
|
|
|
69
69
|
## Experimental Status
|
|
70
70
|
|
package/docs/index.md
CHANGED
|
@@ -7,7 +7,7 @@ Senpi is a minimal terminal coding harness. It is designed to stay small at the
|
|
|
7
7
|
Install with npm:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
bun add -g @code-yeongyu/senpi
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
To uninstall senpi itself, use the package manager that installed it:
|
package/docs/packages.md
CHANGED
|
@@ -90,7 +90,7 @@ ssh://git@github.com/user/repo@v1
|
|
|
90
90
|
- Refs are pinned tags or commits. `pi update --extensions` and `pi update --all` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
|
|
91
91
|
- Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
|
|
92
92
|
- Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
|
|
93
|
-
- When reconciliation changes the checkout, pi resets and cleans the clone, then runs `
|
|
93
|
+
- When reconciliation changes the checkout, pi resets and cleans the clone, then runs `bun install` if `package.json` exists.
|
|
94
94
|
|
|
95
95
|
**SSH examples:**
|
|
96
96
|
```bash
|
|
@@ -166,7 +166,7 @@ If no `pi` manifest is present, pi auto-discovers resources from these directori
|
|
|
166
166
|
|
|
167
167
|
## Dependencies
|
|
168
168
|
|
|
169
|
-
Third party runtime dependencies belong in `dependencies` in `package.json`. Dependencies that do not register extensions, skills, prompt templates, or themes also belong in `dependencies`. When pi installs a package from npm or git, it runs `
|
|
169
|
+
Third party runtime dependencies belong in `dependencies` in `package.json`. Dependencies that do not register extensions, skills, prompt templates, or themes also belong in `dependencies`. When pi installs a package from npm or git, it runs `bun install`, so those dependencies are installed automatically.
|
|
170
170
|
|
|
171
171
|
Pi bundles core packages for extensions and skills. If you import any of these, list them in `peerDependencies` with a `"*"` range and do not bundle them: `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, `typebox`.
|
|
172
172
|
|
package/docs/quickstart.md
CHANGED
|
@@ -10,7 +10,7 @@ procedure, see the [release and installation guide](release-guide.md).
|
|
|
10
10
|
Senpi is distributed as an npm package:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
|
|
13
|
+
bun add -g @code-yeongyu/senpi
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
### Uninstall
|
|
@@ -18,7 +18,7 @@ npm install -g @code-yeongyu/senpi
|
|
|
18
18
|
Use the package manager that installed senpi:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
#
|
|
21
|
+
# bun add -g
|
|
22
22
|
npm uninstall -g @code-yeongyu/senpi
|
|
23
23
|
|
|
24
24
|
# pnpm
|
|
@@ -91,7 +91,7 @@ Senpi loads context files at startup. Add an `AGENTS.md` file to tell it how to
|
|
|
91
91
|
```markdown
|
|
92
92
|
# Project Instructions
|
|
93
93
|
|
|
94
|
-
- Run `
|
|
94
|
+
- Run `bun run check` after code changes.
|
|
95
95
|
- Do not run production migrations locally.
|
|
96
96
|
- Keep responses concise.
|
|
97
97
|
```
|
|
@@ -123,7 +123,7 @@ Images or text can be pasted with Ctrl+V (Alt+V on Windows); images can also be
|
|
|
123
123
|
In interactive mode:
|
|
124
124
|
|
|
125
125
|
```text
|
|
126
|
-
!
|
|
126
|
+
!bun run lint
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
The command output is sent to the model. Use `!!command` to run a command without adding its output to the model context.
|
package/docs/release-guide.md
CHANGED
|
@@ -216,7 +216,7 @@ change the supported Node/npm installation path or require a configuration migra
|
|
|
216
216
|
`release:local` stages the publish manifest and bundled package tree inside the source checkout
|
|
217
217
|
while packing. Run it from a clean, dedicated release checkout rather than a shared dirty
|
|
218
218
|
worktree. After packing, verify `packages/coding-agent/package.json` still has only the five
|
|
219
|
-
intentional internal bundle entries and run `
|
|
219
|
+
intentional internal bundle entries and run `bun install --ignore-scripts` before resuming
|
|
220
220
|
development so workspace dependency resolution is restored. Do not use destructive Git cleanup
|
|
221
221
|
when other work is present.
|
|
222
222
|
|
|
@@ -225,25 +225,25 @@ when other work is present.
|
|
|
225
225
|
Run from the repository root:
|
|
226
226
|
|
|
227
227
|
```bash
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
CI=1
|
|
228
|
+
bun run check
|
|
229
|
+
bun run build
|
|
230
|
+
CI=1 bun run test
|
|
231
231
|
npm audit --omit=dev
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
The release-script checks are:
|
|
235
235
|
|
|
236
236
|
```bash
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
bun scripts/generate-coding-agent-shrinkwrap.mjs --check
|
|
238
|
+
bun scripts/generate-coding-agent-install-lock.mjs --check
|
|
239
|
+
bun scripts/upstream-release-worthy.mjs
|
|
240
|
+
bun run release --dry-run
|
|
241
|
+
bun scripts/release-notes.mjs extract \
|
|
242
242
|
--version 2026.7.30-2 \
|
|
243
243
|
--tag v2026.7.30-2
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
-
`
|
|
246
|
+
`bun run release --dry-run` is safe for this workflow. Do not run the live release command:
|
|
247
247
|
the live path commits, tags, and pushes.
|
|
248
248
|
|
|
249
249
|
## Plugin verification
|
|
@@ -251,7 +251,7 @@ the live path commits, tags, and pushes.
|
|
|
251
251
|
Run the focused extension suites:
|
|
252
252
|
|
|
253
253
|
```bash
|
|
254
|
-
|
|
254
|
+
bunx --cwd packages/coding-agent vitest \
|
|
255
255
|
--run \
|
|
256
256
|
test/suite/builtin-extension-sync.test.ts \
|
|
257
257
|
test/suite/vendored-builtins.test.ts \
|
|
@@ -304,7 +304,7 @@ Choose one durable output directory outside the repository:
|
|
|
304
304
|
```bash
|
|
305
305
|
cd /Users/yeongyu/local-workspaces/senpi
|
|
306
306
|
ARTIFACT_ROOT="$HOME/.local/share/senpi-releases/2026.7.30-2"
|
|
307
|
-
|
|
307
|
+
bun run release:local --force --out "$ARTIFACT_ROOT"
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
The canonical npm tarball is:
|
|
@@ -341,7 +341,7 @@ Install the verified tarball through npm without lifecycle scripts, then refresh
|
|
|
341
341
|
command cache:
|
|
342
342
|
|
|
343
343
|
```bash
|
|
344
|
-
|
|
344
|
+
bun add -g --ignore-scripts "$TARBALL"
|
|
345
345
|
hash -r
|
|
346
346
|
command -v senpi
|
|
347
347
|
senpi --version
|
|
@@ -358,7 +358,7 @@ upgrade.
|
|
|
358
358
|
Install the verified tarball:
|
|
359
359
|
|
|
360
360
|
```bash
|
|
361
|
-
|
|
361
|
+
bun add -g --ignore-scripts "$TARBALL"
|
|
362
362
|
```
|
|
363
363
|
|
|
364
364
|
Create an isolated runtime home:
|
|
@@ -437,7 +437,7 @@ Install the transferred artifact:
|
|
|
437
437
|
|
|
438
438
|
```bash
|
|
439
439
|
ssh mengmotaHost \
|
|
440
|
-
'
|
|
440
|
+
'bun add -g --ignore-scripts \
|
|
441
441
|
/tmp/senpi-2026.7.30-2/code-yeongyu-senpi-2026.7.30-2.tgz'
|
|
442
442
|
```
|
|
443
443
|
|
|
@@ -516,10 +516,10 @@ counts must match exactly on each machine.
|
|
|
516
516
|
Re-run version synchronization and lock generation:
|
|
517
517
|
|
|
518
518
|
```bash
|
|
519
|
-
|
|
520
|
-
PI_ALLOW_LOCKFILE_CHANGE=1
|
|
521
|
-
|
|
522
|
-
|
|
519
|
+
bun scripts/sync-versions.js
|
|
520
|
+
PI_ALLOW_LOCKFILE_CHANGE=1 bun install --lockfile-only --ignore-scripts
|
|
521
|
+
bun scripts/generate-coding-agent-shrinkwrap.mjs
|
|
522
|
+
bun scripts/generate-coding-agent-install-lock.mjs
|
|
523
523
|
```
|
|
524
524
|
|
|
525
525
|
Then rebuild before packaging.
|
|
@@ -567,7 +567,7 @@ configuration paths.
|
|
|
567
567
|
Run the MCP suite directly:
|
|
568
568
|
|
|
569
569
|
```bash
|
|
570
|
-
|
|
570
|
+
bunx --cwd packages/coding-agent vitest --run test/mcp/
|
|
571
571
|
```
|
|
572
572
|
|
|
573
573
|
Make only compatibility changes required by a reproduced failure. Do not add fallback logic
|
|
@@ -581,7 +581,7 @@ Rollback changes the executable package only. It does not remove or modify
|
|
|
581
581
|
Reinstall the prior published version:
|
|
582
582
|
|
|
583
583
|
```bash
|
|
584
|
-
|
|
584
|
+
bun add -g @code-yeongyu/senpi@2026.7.30
|
|
585
585
|
```
|
|
586
586
|
|
|
587
587
|
Verify rollback from an isolated home:
|