@code-yeongyu/senpi 2026.11.10-1 → 2026.11.10-2
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 +20 -0
- package/dist/bundle/chunks/{chunk-GPXZTSEX.js → chunk-2M54EAAY.js} +3 -3
- package/dist/bundle/chunks/{chunk-YPAQXRP3.js → chunk-47T6YMI2.js} +2 -2
- package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HI5JSLG5.js → chunk-74IB3JPP.js} +2 -2
- package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-3IYWH6RO.js → chunk-IKJCA5SH.js} +1 -1
- package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
- package/dist/bundle/chunks/{chunk-L6NRDUWJ.js → chunk-OF3XVGG4.js} +1 -1
- package/dist/bundle/chunks/{chunk-THYEZBUG.js → chunk-Q4G5DECT.js} +2 -2
- package/dist/bundle/chunks/{chunk-ARUV3US4.js → chunk-QATU6BBC.js} +1 -1
- package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-TUEZNIBK.js} +1 -1
- package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-UOZDDYBN.js} +2 -2
- package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
- package/dist/bundle/chunks/{chunk-TGW4R7SI.js → chunk-XH24X5B2.js} +1 -1
- package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
- package/dist/bundle/chunks/{chunk-X4XWAB3J.js → chunk-YCPURLPU.js} +38 -38
- package/dist/bundle/chunks/{chunk-LHEP3ACI.js → chunk-YNGEZBTP.js} +1 -1
- package/dist/bundle/chunks/{cli-main-VG3C2EXI.js → cli-main-Y6ALA5OI.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-3LQKUTYS.js → hand-entry-IPDAIOGI.js} +1 -1
- package/dist/bundle/chunks/{host-command-CCASS7W4.js → host-command-2326GCUP.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-WWC7HXQ6.js → host-lifecycle-TNHASSFJ.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/{host-session-command-YJB2FLKU.js → host-session-command-BZYBDOIE.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-CUN3WMUF.js → interactive-mode-LBQB7DDR.js} +1 -1
- package/dist/bundle/chunks/{main-P7XRC2OI.js → main-FFRFP34W.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-244X3P6L.js → multi-session-host-NDIHHJ2L.js} +11 -8
- package/dist/bundle/chunks/{package-manager-cli-UVWJOQA3.js → package-manager-cli-XCPAMOYI.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-G4I3HHPB.js → rpc-mode-TKNZM4PZ.js} +1 -1
- package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-JDUKXDI2.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-5FG2OKSR.js → schedule-command-HF2DHABB.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-XWCFLJYC.js → session-control-endpoint-A6R6W2JQ.js} +1 -1
- package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GWA4S2PO.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +119 -116
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +2 -0
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.js +30 -10
- package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +4 -17
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +64 -75
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
- package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +18 -0
- package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
- package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
- package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
- package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
- package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +6 -6
- package/dist/core/open-timing.d.ts +2 -0
- package/dist/core/open-timing.js +7 -1
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.js +15 -1
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
- package/dist/modes/rpc/compact-admission-validation.js +4 -1
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +10 -2
- package/dist/modes/rpc/custom-capability.d.ts +2 -0
- package/dist/modes/rpc/custom-capability.js +2 -0
- package/dist/modes/rpc/effect-commands.d.ts +21 -0
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +3 -1
- package/dist/modes/rpc/host-observe-request.d.ts +2 -0
- package/dist/modes/rpc/host-observe-request.js +6 -1
- package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
- package/dist/modes/rpc/host-park-ledger.js +110 -0
- package/dist/modes/rpc/lifecycle-delivery.js +4 -1
- package/dist/modes/rpc/media-placeholders.js +1 -0
- package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
- package/dist/modes/rpc/method-classes-schema-host.js +24 -21
- package/dist/modes/rpc/method-classes-schema.d.ts +42 -1
- package/dist/modes/rpc/method-classes-schema.js +5 -2
- package/dist/modes/rpc/method-classes.d.ts +2 -0
- package/dist/modes/rpc/method-classes.js +3 -0
- package/dist/modes/rpc/multi-session-host.js +1 -1
- package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
- package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
- package/dist/modes/rpc/session-attachment-gate.js +2 -0
- package/dist/modes/rpc/session-binding.d.ts +1 -0
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-connection-context.d.ts +2 -0
- package/dist/modes/rpc/session-connection-context.js +4 -0
- package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
- package/dist/modes/rpc/session-registry-attach.js +15 -11
- package/dist/modes/rpc/session-registry-claims.js +2 -0
- package/dist/modes/rpc/session-registry-open.js +13 -7
- package/dist/modes/rpc/session-registry-opening.js +2 -1
- package/dist/modes/rpc/session-registry-types.d.ts +4 -0
- package/dist/modes/rpc/session-registry.d.ts +3 -0
- package/dist/modes/rpc/session-registry.js +27 -3
- package/dist/modes/rpc/session-router-close.js +1 -0
- package/dist/modes/rpc/session-router-context.d.ts +4 -2
- package/dist/modes/rpc/session-router-context.js +3 -0
- package/dist/modes/rpc/session-router-host-reads.js +1 -0
- package/dist/modes/rpc/session-router-occupancy.js +10 -1
- package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
- package/dist/modes/rpc/session-router-open-result.js +63 -0
- package/dist/modes/rpc/session-router-open.d.ts +3 -0
- package/dist/modes/rpc/session-router-open.js +15 -63
- package/dist/modes/rpc/session-router-park.d.ts +15 -0
- package/dist/modes/rpc/session-router-park.js +176 -0
- package/dist/modes/rpc/session-router-routing.d.ts +1 -1
- package/dist/modes/rpc/session-router-routing.js +7 -0
- package/dist/modes/rpc/session-status-source.js +2 -2
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +22 -2
- package/docs/rpc.md +50 -0
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
|
@@ -3,7 +3,8 @@ import { isModernMcpClient } from "./modern/request.js";
|
|
|
3
3
|
import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
|
|
4
4
|
import { collectAllPages } from "./expose/pagination.js";
|
|
5
5
|
export async function collectToolCatalog(server, connection, config, options = {}) {
|
|
6
|
-
const
|
|
6
|
+
const client = connection.client;
|
|
7
|
+
const result = await collectAllPages((cursor) => listMcpTools(client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
|
|
7
8
|
return result.items.map((tool) => ({
|
|
8
9
|
requestOrigin: options.requestOrigin,
|
|
9
10
|
annotations: tool.annotations,
|
|
@@ -17,7 +18,7 @@ export async function collectToolCatalog(server, connection, config, options = {
|
|
|
17
18
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
18
19
|
schema: tool.inputSchema,
|
|
19
20
|
outputSchema: tool.outputSchema,
|
|
20
|
-
modern: isModernMcpClient(
|
|
21
|
+
modern: isModernMcpClient(client),
|
|
21
22
|
server,
|
|
22
23
|
tool: tool.name,
|
|
23
24
|
}));
|
|
@@ -61,6 +61,12 @@ export function createMcpInvocationResolver(scope) {
|
|
|
61
61
|
try {
|
|
62
62
|
await Promise.race([ready, interrupted]);
|
|
63
63
|
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
readySignal.throwIfAborted();
|
|
66
|
+
if (!scope.isCurrent())
|
|
67
|
+
return refuse("unavailable", "MCP session or server configuration was replaced.");
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
64
70
|
finally {
|
|
65
71
|
clearTimeout(timeout);
|
|
66
72
|
readySignal.removeEventListener("abort", abort);
|
|
@@ -3,7 +3,7 @@ import { computeMcpExposurePolicy } from "./expose/policy.js";
|
|
|
3
3
|
import { registerDirectMcpTools } from "./expose/session.js";
|
|
4
4
|
import { ensureMcpToolCallConnection } from "./health.js";
|
|
5
5
|
import { createMcpInvocationResolver } from "./invocation.js";
|
|
6
|
-
import { connectAndRefreshMcpCatalog } from "./startup-race.js";
|
|
6
|
+
import { connectAndRefreshMcpCatalog, prepareMcpAuthentication } from "./startup-race.js";
|
|
7
7
|
export async function registerMcpServiceDirectTools(pi, config, entries, toolSearchService, options = {}) {
|
|
8
8
|
return await registerDirectMcpTools(pi, config, [...entries].map((entry) => {
|
|
9
9
|
const serverConfig = config.servers[entry.name]?.config;
|
|
@@ -18,7 +18,21 @@ export async function registerMcpServiceDirectTools(pi, config, entries, toolSea
|
|
|
18
18
|
servesSession: options.servesSession,
|
|
19
19
|
isCurrent: () => entry.isCurrent?.() !== false && options.isCurrent?.(entry) !== false,
|
|
20
20
|
ready: async () => {
|
|
21
|
-
|
|
21
|
+
if (!(await prepareMcpAuthentication(entry)))
|
|
22
|
+
return;
|
|
23
|
+
if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
|
|
24
|
+
return;
|
|
25
|
+
if (entry.credentialsCurrent?.() === false) {
|
|
26
|
+
await entry.onCredentialsChanged?.();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await ensureMcpToolCallConnection(entry.connection);
|
|
30
|
+
if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
|
|
31
|
+
return;
|
|
32
|
+
if (entry.credentialsCurrent?.() === false) {
|
|
33
|
+
await entry.onCredentialsChanged?.();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
22
36
|
await connectAndRefreshMcpCatalog(entry, serverConfig);
|
|
23
37
|
await entry.startupCatalogClaim?.settled();
|
|
24
38
|
if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
|
|
@@ -54,71 +54,78 @@ export async function refreshMcpToolsOnListChanged(entry, targets, config, conne
|
|
|
54
54
|
// The startup connect that still owns registration registers its refreshed catalog itself.
|
|
55
55
|
if (connectOnly && entry.startupCatalogClaim?.ownsRegistration() === true)
|
|
56
56
|
return;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return;
|
|
62
|
-
entry.cachedCatalog = refreshed;
|
|
63
|
-
}
|
|
64
|
-
const catalog = await collectToolCatalog(entry.name, entry.connection, server.config, {
|
|
65
|
-
agentDir: entry.agentDir,
|
|
66
|
-
outputGuard: config.settings.outputGuard,
|
|
67
|
-
});
|
|
68
|
-
if (!ownsCatalog())
|
|
69
|
-
return;
|
|
70
|
-
const newNames = mapMcpCatalogNames(catalog).map(({ name }) => name);
|
|
71
|
-
// Before the first refresh, the registered names are the catalog the startup pass registered.
|
|
72
|
-
const knownNames = entry.knownToolNames ??
|
|
73
|
-
(registeredCatalog === undefined
|
|
74
|
-
? newNames
|
|
75
|
-
: mapMcpCatalogNames(cachedToolsToCatalogEntries(entry.name, registeredCatalog, entry.connection, server.config.requestTimeoutMs, async () => { })).map(({ name }) => name));
|
|
76
|
-
const diff = diffMcpToolNames(knownNames, newNames);
|
|
77
|
-
const identity = mcpRegistrationIdentity(catalog, entry.cachedCatalog);
|
|
78
|
-
const initial = targets();
|
|
79
|
-
const stale = connectOnly ? initial.filter((target) => target.registeredIdentity() !== identity) : initial;
|
|
80
|
-
if (stale.length > 0) {
|
|
81
|
-
// Registration reads entry.cachedCatalog. A shared lease refreshed it above; nothing else
|
|
82
|
-
// refreshes a non-shared connection's catalog after its startup connect (#2188).
|
|
83
|
-
if (!(entry.connection instanceof SharedMcpLease)) {
|
|
84
|
-
const refreshed = await collectServerCatalogForCache(entry.connection, server.config, entry.generationHash ?? entry.configHash, entry.credentialIdentity);
|
|
57
|
+
try {
|
|
58
|
+
const registeredCatalog = entry.cachedCatalog;
|
|
59
|
+
if (entry.connection instanceof SharedMcpLease) {
|
|
60
|
+
const refreshed = await entry.connection.catalog();
|
|
85
61
|
if (!ownsCatalog())
|
|
86
62
|
return;
|
|
87
63
|
entry.cachedCatalog = refreshed;
|
|
88
|
-
await writeMcpCachedServer(entry.agentDir, entry.name, refreshed, ownsCatalog);
|
|
89
64
|
}
|
|
65
|
+
const catalog = await collectToolCatalog(entry.name, entry.connection, server.config, {
|
|
66
|
+
agentDir: entry.agentDir,
|
|
67
|
+
outputGuard: config.settings.outputGuard,
|
|
68
|
+
});
|
|
90
69
|
if (!ownsCatalog())
|
|
91
70
|
return;
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
71
|
+
const newNames = mapMcpCatalogNames(catalog).map(({ name }) => name);
|
|
72
|
+
// Before the first refresh, the registered names are the catalog the startup pass registered.
|
|
73
|
+
const knownNames = entry.knownToolNames ??
|
|
74
|
+
(registeredCatalog === undefined
|
|
75
|
+
? newNames
|
|
76
|
+
: mapMcpCatalogNames(cachedToolsToCatalogEntries(entry.name, registeredCatalog, entry.connection, server.config.requestTimeoutMs, async () => { })).map(({ name }) => name));
|
|
77
|
+
const diff = diffMcpToolNames(knownNames, newNames);
|
|
78
|
+
const identity = mcpRegistrationIdentity(catalog, entry.cachedCatalog);
|
|
79
|
+
const initial = targets();
|
|
80
|
+
const stale = connectOnly ? initial.filter((target) => target.registeredIdentity() !== identity) : initial;
|
|
81
|
+
if (stale.length > 0) {
|
|
82
|
+
// Registration reads entry.cachedCatalog. A shared lease refreshed it above; nothing else
|
|
83
|
+
// refreshes a non-shared connection's catalog after its startup connect (#2188).
|
|
84
|
+
if (!(entry.connection instanceof SharedMcpLease)) {
|
|
85
|
+
const refreshed = await collectServerCatalogForCache(entry.connection, server.config, entry.generationHash ?? entry.configHash, entry.credentialIdentity);
|
|
99
86
|
if (!ownsCatalog())
|
|
100
87
|
return;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
88
|
+
entry.cachedCatalog = refreshed;
|
|
89
|
+
await writeMcpCachedServer(entry.agentDir, entry.name, refreshed, ownsCatalog);
|
|
90
|
+
}
|
|
91
|
+
if (!ownsCatalog())
|
|
92
|
+
return;
|
|
93
|
+
// Tombstone removed tools BEFORE re-registration so the subsequent
|
|
94
|
+
// setActiveTools (which excludes them) leaves the tombstones inactive.
|
|
95
|
+
const seen = new Set(initial.map((target) => target.pi));
|
|
96
|
+
const failures = [];
|
|
97
|
+
let pending = stale;
|
|
98
|
+
while (pending.length > 0) {
|
|
99
|
+
for (const target of pending) {
|
|
100
|
+
if (!ownsCatalog())
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
for (const removed of diff.removed)
|
|
104
|
+
target.pi.registerTool(buildMcpTombstoneDefinition(removed, entry.name));
|
|
105
|
+
await target.register();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
failures.push(error);
|
|
109
|
+
}
|
|
108
110
|
}
|
|
111
|
+
// A session that attached during this refresh registered the catalog cached before it.
|
|
112
|
+
pending = targets().filter((target) => !seen.has(target.pi));
|
|
113
|
+
for (const target of pending)
|
|
114
|
+
seen.add(target.pi);
|
|
115
|
+
}
|
|
116
|
+
if (failures.length > 0) {
|
|
117
|
+
entry.knownToolNames = newNames;
|
|
118
|
+
entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
|
|
119
|
+
throw new AggregateError(failures, `MCP ${entry.name} tool refresh failed in ${failures.length} of ${seen.size} session(s)`);
|
|
109
120
|
}
|
|
110
|
-
// A session that attached during this refresh registered the catalog cached before it.
|
|
111
|
-
pending = targets().filter((target) => !seen.has(target.pi));
|
|
112
|
-
for (const target of pending)
|
|
113
|
-
seen.add(target.pi);
|
|
114
|
-
}
|
|
115
|
-
if (failures.length > 0) {
|
|
116
|
-
entry.knownToolNames = newNames;
|
|
117
|
-
entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
|
|
118
|
-
throw new AggregateError(failures, `MCP ${entry.name} tool refresh failed in ${failures.length} of ${seen.size} session(s)`);
|
|
119
121
|
}
|
|
122
|
+
entry.knownToolNames = newNames;
|
|
123
|
+
entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (!ownsCatalog())
|
|
127
|
+
return;
|
|
128
|
+
throw error;
|
|
120
129
|
}
|
|
121
|
-
entry.knownToolNames = newNames;
|
|
122
|
-
entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
|
|
123
130
|
}
|
|
124
131
|
//# sourceMappingURL=service-tools-changed.js.map
|
|
@@ -43,6 +43,8 @@ export declare class McpDeferredAttach {
|
|
|
43
43
|
}
|
|
44
44
|
export declare function raceMcpStartupConnect(options: RaceMcpStartupConnectOptions): Promise<void>;
|
|
45
45
|
export declare function connectAndRefreshMcpCatalog(entry: McpConnectionEntry, serverConfig: ResolvedMcpServer["config"]): Promise<void>;
|
|
46
|
+
/** Reconcile the grant before health recovery without attempting a transport connection. */
|
|
47
|
+
export declare function prepareMcpAuthentication(entry: McpConnectionEntry): Promise<boolean>;
|
|
46
48
|
export declare function ignoreStartupNeedsAuth(entry: McpConnectionEntry, connect: Promise<void>): Promise<void>;
|
|
47
49
|
export declare function waitForMcpStartupRace(connect: Promise<void>, deadlineMs?: number): Promise<McpStartupRaceResult>;
|
|
48
50
|
export declare function shouldRaceMcpStartup(lifecycle: "lazy" | "eager" | "keep-alive"): boolean;
|
|
@@ -91,15 +91,16 @@ export function connectAndRefreshMcpCatalog(entry, serverConfig) {
|
|
|
91
91
|
void refresh.then(finish, finish);
|
|
92
92
|
return refresh;
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
/** Reconcile the grant before health recovery without attempting a transport connection. */
|
|
95
|
+
export async function prepareMcpAuthentication(entry) {
|
|
96
|
+
if (entry.isCurrent?.() === false)
|
|
97
|
+
return false;
|
|
97
98
|
try {
|
|
98
99
|
await entry.authPlan?.refresh?.ensureFresh();
|
|
99
100
|
}
|
|
100
101
|
catch (error) {
|
|
101
|
-
if (entry.connection.state === "disabled")
|
|
102
|
-
return;
|
|
102
|
+
if (entry.isCurrent?.() === false || entry.connection.state === "disabled")
|
|
103
|
+
return false;
|
|
103
104
|
const authError = markMcpConnectionNeedsAuth(entry.connection, error);
|
|
104
105
|
if (authError !== undefined) {
|
|
105
106
|
entry.logger.warn(authError.message);
|
|
@@ -107,10 +108,17 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
|
|
|
107
108
|
}
|
|
108
109
|
throw error;
|
|
109
110
|
}
|
|
111
|
+
if (entry.isCurrent?.() === false)
|
|
112
|
+
return false;
|
|
110
113
|
if (entry.credentialsCurrent?.() === false) {
|
|
111
114
|
await entry.onCredentialsChanged?.();
|
|
112
|
-
return;
|
|
115
|
+
return false;
|
|
113
116
|
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
async function refreshConnectedMcpCatalog(entry, serverConfig) {
|
|
120
|
+
if (serverConfig === undefined || !(await prepareMcpAuthentication(entry)))
|
|
121
|
+
return;
|
|
114
122
|
await connectMcpServer(entry.connection, entry.logger);
|
|
115
123
|
if (entry.connection.state !== "connected" || entry.isCurrent?.() === false)
|
|
116
124
|
return;
|
|
@@ -120,7 +128,15 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
|
|
|
120
128
|
entry.connection.state === "connected" &&
|
|
121
129
|
entry.connection.generation === generation;
|
|
122
130
|
if (entry.connection instanceof SharedMcpLease) {
|
|
123
|
-
|
|
131
|
+
let catalog;
|
|
132
|
+
try {
|
|
133
|
+
catalog = await entry.connection.catalog();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (!ownsCatalog())
|
|
137
|
+
return;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
124
140
|
if (!ownsCatalog()) {
|
|
125
141
|
if (entry.credentialsCurrent?.() === false)
|
|
126
142
|
await entry.onCredentialsChanged?.();
|
|
@@ -150,6 +166,8 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
|
|
|
150
166
|
await ensureMcpResourceSubscriptions(entry.connection.client, catalog.resources ?? []);
|
|
151
167
|
}
|
|
152
168
|
catch (error) {
|
|
169
|
+
if (!ownsCatalog())
|
|
170
|
+
return;
|
|
153
171
|
entry.logger.warn("Failed to refresh MCP catalog cache", {
|
|
154
172
|
error: error instanceof Error ? error.message : String(error),
|
|
155
173
|
});
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
// retries after a busy tool from nearly all tries to four in ten. "Blind
|
|
17
17
|
// retries" becomes the exact action it meant, repeating a failure that has not
|
|
18
18
|
// been diagnosed, so it no longer collides with "wait and retry".
|
|
19
|
-
// - Scope, structure ("Say who owns the code"):
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// - Scope, structure ("Say who owns the code"): the guide's ownership sentence
|
|
20
|
+
// opens the structure paragraph and replaces the smallest-change sentence
|
|
21
|
+
// (owner order). Without it, 5.2 acts as a guest: parallel code, duplicates
|
|
22
|
+
// left in place, and nearby bugs only mentioned.
|
|
23
23
|
// - Working the Task, delegation ("Set expectations for subagents"): the 5.1
|
|
24
24
|
// sentence was a standing invitation to delegate, which the guide says to
|
|
25
25
|
// review first. It now states the cost before the invitation, what a brief
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// mannered-prose sentence. 5.2 reports run dense, so the rule asks for plain
|
|
30
30
|
// words, mostly short one-idea sentences, one word per thing, and no names
|
|
31
31
|
// made up during the work, for a reader switching in from something else.
|
|
32
|
-
// Deliberately absent:
|
|
32
|
+
// Deliberately absent: "answer directly" or think-less
|
|
33
33
|
// lines (the guide: no effect at medium effort and above, lower the effort
|
|
34
34
|
// instead); a token budget; the "treat that answer as done" lines; hard
|
|
35
35
|
// sentence-length limits; any instruction to write reasoning in the reply (a
|
|
@@ -77,7 +77,7 @@ The request sets the scope, and the scope is the deliverable: deliver all of it
|
|
|
77
77
|
|
|
78
78
|
Errors, timeouts, locked files, empty results, and failing tools are ordinary obstacles, not blockers: diagnose them, then work through them with the access you have (wait and retry a call that is safe to repeat, fix the request, use another tool or source) without asking first. A file marked not to be touched, withheld access, or a safety guardrail is a deliberate blocker: leave it alone and look for another safe way to finish. If part of the task stays blocked, finish every other part and say exactly what you left out and why.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Add no helpers or abstractions for hypothetical needs and no defensive checks inside trusted code; validate only at system boundaries. A pre-existing bug or performance concern you notice is a follow-up for your summary, not a change in this diff. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
|
|
81
81
|
|
|
82
82
|
## Working the Task
|
|
83
83
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface OpenTimer {
|
|
2
2
|
mark(phase: string): void;
|
|
3
3
|
}
|
|
4
|
+
/** Speculative builds have no caller to charge; drop their timing rather than report a misleading open. */
|
|
5
|
+
export declare function withoutOpenTiming<T>(build: () => T): T;
|
|
4
6
|
export declare function openTimingEnabled(): boolean;
|
|
5
7
|
export declare function startOpenTimer(scope: string): OpenTimer;
|
|
6
8
|
//# sourceMappingURL=open-timing.d.ts.map
|
package/dist/core/open-timing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { envValue } from "./brand.js";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
/**
|
|
3
4
|
* Per-phase timing of a session open (senpi#5707), for naming the await that holds a slow open.
|
|
4
5
|
* Enable with SENPI_OPEN_TIMING=1. Off, every call returns before reading a clock or touching
|
|
@@ -11,11 +12,16 @@ import { envValue } from "./brand.js";
|
|
|
11
12
|
*/
|
|
12
13
|
const ENABLED = envValue("OPEN_TIMING") === "1";
|
|
13
14
|
const DISABLED = { mark: () => { } };
|
|
15
|
+
const speculative = new AsyncLocalStorage();
|
|
16
|
+
/** Speculative builds have no caller to charge; drop their timing rather than report a misleading open. */
|
|
17
|
+
export function withoutOpenTiming(build) {
|
|
18
|
+
return speculative.run(true, build);
|
|
19
|
+
}
|
|
14
20
|
export function openTimingEnabled() {
|
|
15
21
|
return ENABLED;
|
|
16
22
|
}
|
|
17
23
|
export function startOpenTimer(scope) {
|
|
18
|
-
if (!ENABLED)
|
|
24
|
+
if (!ENABLED || speculative.getStore())
|
|
19
25
|
return DISABLED;
|
|
20
26
|
let last = performance.now();
|
|
21
27
|
return {
|
|
@@ -440,6 +440,8 @@ export declare class SessionManager {
|
|
|
440
440
|
private _transcriptEntryBytes;
|
|
441
441
|
private _serializedFileEntries;
|
|
442
442
|
isPersisted(): boolean;
|
|
443
|
+
/** Give an unclaimed in-memory session its first file without rebuilding or changing its identity. */
|
|
444
|
+
enablePersistence(): Promise<void>;
|
|
443
445
|
getCwd(): string;
|
|
444
446
|
getSessionDir(): string;
|
|
445
447
|
usesDefaultSessionDir(): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getCurrentSystemMessage, normalizeProviderId } from "@earendil-works/pi-ai";
|
|
2
2
|
import { randomBytes, randomUUID } from "crypto";
|
|
3
3
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readSync, rmSync, statSync, writeFileSync, } from "fs";
|
|
4
|
-
import { appendFile, open, readdir, rm } from "fs/promises";
|
|
4
|
+
import { appendFile, mkdir, open, readdir, rm } from "fs/promises";
|
|
5
5
|
import { basename, join, resolve } from "path";
|
|
6
6
|
import { StringDecoder } from "string_decoder";
|
|
7
7
|
import { APP_NAME, getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
@@ -1230,6 +1230,20 @@ export class SessionManager {
|
|
|
1230
1230
|
isPersisted() {
|
|
1231
1231
|
return this.persist;
|
|
1232
1232
|
}
|
|
1233
|
+
/** Give an unclaimed in-memory session its first file without rebuilding or changing its identity. */
|
|
1234
|
+
async enablePersistence() {
|
|
1235
|
+
if (this.persist)
|
|
1236
|
+
return;
|
|
1237
|
+
const dir = getDefaultSessionDirPath(this.cwd);
|
|
1238
|
+
await mkdir(dir, { recursive: true });
|
|
1239
|
+
const header = this.fileEntries[0];
|
|
1240
|
+
const path = join(dir, `${header.timestamp.replace(/[:.]/g, "-")}_${this.sessionId}.jsonl`);
|
|
1241
|
+
reserveSessionWrite(path);
|
|
1242
|
+
this.sessionDir = dir;
|
|
1243
|
+
this.sessionFile = path;
|
|
1244
|
+
this.persist = true;
|
|
1245
|
+
registerSessionWriter(this);
|
|
1246
|
+
}
|
|
1233
1247
|
getCwd() {
|
|
1234
1248
|
return this.cwd;
|
|
1235
1249
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentSession } from "../../core/agent-session.ts";
|
|
2
2
|
import { type ControlAttachments } from "./session-control-attach.ts";
|
|
3
3
|
import { type ControlCommand, type ControlConnection } from "./session-control-server.ts";
|
|
4
|
-
export declare const TUI_RPC_COMMANDS: readonly ["open_session", "get_snapshot", "subscribe_sessions", "unsubscribe_sessions", "steer", "follow_up", "prompt", "admit_external_message", "get_effect_status", "set_client_info", "attach_session", "release_attachment", "abort", "mcp_presenter_context", "mcp_form_presented", "mcp_form_response", "mcp_form_sync", "mcp_presenter_secret_answer"];
|
|
4
|
+
export declare const TUI_RPC_COMMANDS: readonly ["open_session", "park_session", "claim_session", "get_snapshot", "subscribe_sessions", "unsubscribe_sessions", "steer", "follow_up", "prompt", "admit_external_message", "get_effect_status", "set_client_info", "attach_session", "release_attachment", "abort", "mcp_presenter_context", "mcp_form_presented", "mcp_form_response", "mcp_form_sync", "mcp_presenter_secret_answer"];
|
|
5
5
|
export interface RpcCommandContext {
|
|
6
6
|
/** This endpoint's instance id: the `expectedHostInstance` an `attach_session` may name. */
|
|
7
7
|
readonly instanceId: string;
|
|
@@ -26,6 +26,8 @@ import { failure, success } from "./session-control-server.js";
|
|
|
26
26
|
import { TERMINAL_PRESENTER_COMMANDS } from "./terminal-presenter-surface.js";
|
|
27
27
|
export const TUI_RPC_COMMANDS = [
|
|
28
28
|
"open_session",
|
|
29
|
+
"park_session",
|
|
30
|
+
"claim_session",
|
|
29
31
|
"get_snapshot",
|
|
30
32
|
"subscribe_sessions",
|
|
31
33
|
"unsubscribe_sessions",
|
|
@@ -64,6 +66,9 @@ export async function runTuiRpcCommand(context, connection, command) {
|
|
|
64
66
|
}
|
|
65
67
|
case "open_session":
|
|
66
68
|
return openSession(context, connection, command);
|
|
69
|
+
case "park_session":
|
|
70
|
+
case "claim_session":
|
|
71
|
+
return failure(idOf(command), command.type, "multi_session_disabled");
|
|
67
72
|
case "subscribe_sessions":
|
|
68
73
|
return subscribe(context, connection, frame(command));
|
|
69
74
|
case "unsubscribe_sessions":
|
|
@@ -22,7 +22,10 @@ export function unknownCompactAdmission(host, command) {
|
|
|
22
22
|
if (!host.admissions || command.admissionId === undefined)
|
|
23
23
|
return undefined;
|
|
24
24
|
// These commands introduce client IDs; their bounded attempt index owns retry/conflict/retirement.
|
|
25
|
-
if (command.type === "open_session" ||
|
|
25
|
+
if (command.type === "open_session" ||
|
|
26
|
+
command.type === "claim_session" ||
|
|
27
|
+
command.type === "attach_session" ||
|
|
28
|
+
command.type === "subscribe_sessions")
|
|
26
29
|
return undefined;
|
|
27
30
|
// RULING-96: recovery IDs select token/lease authority, not the ordinary attempt ledger.
|
|
28
31
|
if (command.type === "get_recovery_page" || command.type === "release_recovery")
|
|
@@ -73,6 +73,8 @@ export interface RpcConnectionHandler {
|
|
|
73
73
|
* extension UI context is installed (used by tests that drive ctx.ui directly).
|
|
74
74
|
*/
|
|
75
75
|
readonly ready: Promise<void>;
|
|
76
|
+
/** Complete a prebuilt binding with the capabilities its opener supplied. */
|
|
77
|
+
setClientCapabilities(capabilities: readonly string[]): void;
|
|
76
78
|
/** Feed one inbound JSONL line (command or extension_ui_response). */
|
|
77
79
|
handleInputLine(line: string): Promise<void>;
|
|
78
80
|
/**
|
|
@@ -1047,7 +1047,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1047
1047
|
...(options.retryFallbackCommand ? [RETRY_FALLBACK_COMMAND_CAPABILITY] : []),
|
|
1048
1048
|
...(options.toolRuntime ? toolRuntimeCapabilities(options.toolRuntime) : []),
|
|
1049
1049
|
// Client flags cannot advertise a host guard this classic binding does not run, nor a tool runtime.
|
|
1050
|
-
...advertisedCompactCapabilities(options.capabilities ?? [], false).filter((capability) => capability !== SESSION_HELD_CAPABILITY &&
|
|
1050
|
+
...advertisedCompactCapabilities(options.capabilities ?? [], false).filter((capability) => capability !== SESSION_HELD_CAPABILITY &&
|
|
1051
|
+
capability !== "park_claim" &&
|
|
1052
|
+
!isHostOwnedToolRuntimeCapability(capability)),
|
|
1051
1053
|
]),
|
|
1052
1054
|
],
|
|
1053
1055
|
mode: "classic",
|
|
@@ -1055,7 +1057,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1055
1057
|
},
|
|
1056
1058
|
};
|
|
1057
1059
|
case "open_session":
|
|
1058
|
-
|
|
1060
|
+
case "park_session":
|
|
1061
|
+
case "claim_session":
|
|
1062
|
+
return error(id, command.type, "multi_session_disabled");
|
|
1059
1063
|
// =================================================================
|
|
1060
1064
|
// Prompting
|
|
1061
1065
|
// =================================================================
|
|
@@ -2052,6 +2056,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
2052
2056
|
pendingPrompts() {
|
|
2053
2057
|
return [...promptCalls];
|
|
2054
2058
|
},
|
|
2059
|
+
setClientCapabilities(capabilities) {
|
|
2060
|
+
replaceClientCapabilities([...capabilities]);
|
|
2061
|
+
refreshGrantPresentation();
|
|
2062
|
+
},
|
|
2055
2063
|
isShutdownRequested() {
|
|
2056
2064
|
return shutdownRequested;
|
|
2057
2065
|
},
|
|
@@ -117,6 +117,8 @@ export declare const AUTO_TITLE_PER_SESSION_CAPABILITY = "auto_title_per_session
|
|
|
117
117
|
* cwd, kind and context needs without opening a session. Advertised only by an in-process runtime.
|
|
118
118
|
*/
|
|
119
119
|
export declare const WARM_CAPABILITY = "warm";
|
|
120
|
+
/** HOST capability: invisible speculative sessions and single-use claim, in-process registries only. */
|
|
121
|
+
export declare const PARK_CLAIM_CAPABILITY = "park_claim";
|
|
120
122
|
/**
|
|
121
123
|
* HOST capability: this host honors `open_session.durableSessionId`, so a caller that already
|
|
122
124
|
* owns a stable record id for the conversation can CREATE the session under that id and keep
|
|
@@ -117,6 +117,8 @@ export const AUTO_TITLE_PER_SESSION_CAPABILITY = "auto_title_per_session";
|
|
|
117
117
|
* cwd, kind and context needs without opening a session. Advertised only by an in-process runtime.
|
|
118
118
|
*/
|
|
119
119
|
export const WARM_CAPABILITY = "warm";
|
|
120
|
+
/** HOST capability: invisible speculative sessions and single-use claim, in-process registries only. */
|
|
121
|
+
export const PARK_CLAIM_CAPABILITY = "park_claim";
|
|
120
122
|
/**
|
|
121
123
|
* HOST capability: this host honors `open_session.durableSessionId`, so a caller that already
|
|
122
124
|
* owns a stable record id for the conversation can CREATE the session under that id and keep
|
|
@@ -66,6 +66,27 @@ export declare function runSetThinkingLevel(ctx: EffectCommandContext, command:
|
|
|
66
66
|
readonly reclaimed: true;
|
|
67
67
|
readonly sessionId: string;
|
|
68
68
|
};
|
|
69
|
+
} | {
|
|
70
|
+
id?: string;
|
|
71
|
+
type: "response";
|
|
72
|
+
command: "park_session";
|
|
73
|
+
success: true;
|
|
74
|
+
data: {
|
|
75
|
+
state: "parked" | "already_parked" | "full";
|
|
76
|
+
};
|
|
77
|
+
} | {
|
|
78
|
+
id?: string;
|
|
79
|
+
type: "response";
|
|
80
|
+
command: "claim_session";
|
|
81
|
+
success: true;
|
|
82
|
+
data: {
|
|
83
|
+
claimed: false;
|
|
84
|
+
} | {
|
|
85
|
+
sessionId: string;
|
|
86
|
+
state: import("./rpc-session-state-types.ts").RpcSessionState;
|
|
87
|
+
attached?: boolean;
|
|
88
|
+
presence?: import("./rpc-host-response-types.ts").RpcSessionPresence;
|
|
89
|
+
};
|
|
69
90
|
} | {
|
|
70
91
|
id?: string;
|
|
71
92
|
type: "response";
|
|
@@ -14,6 +14,7 @@ export declare const SET_PERMISSION_PRESET_CAPABILITY = "set_permission_preset_v
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function multiSessionHostCapabilities(options: {
|
|
16
16
|
readonly warm: boolean;
|
|
17
|
+
readonly parkClaim?: boolean;
|
|
17
18
|
readonly toolRuntime: ToolRuntimeKind;
|
|
18
19
|
readonly negotiated: readonly string[];
|
|
19
20
|
readonly mcpPresenters?: boolean;
|
|
@@ -2,7 +2,7 @@ import { compactUpdatesEnabled } from "../../core/engine-socket-flag.js";
|
|
|
2
2
|
import { isHostOwnedToolRuntimeCapability, toolRuntimeCapabilities, } from "../../cli/tool-runtime.js";
|
|
3
3
|
import { ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY, AUTO_PERMISSION_PRESET_CAPABILITY, } from "../../core/extensions/builtin/permission-system/config.js";
|
|
4
4
|
import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.js";
|
|
5
|
-
import { ACCOUNT_RESET_CAPABILITY, ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DELEGATED_CONNECTION_CAPABILITY, DELEGATION_ROLES_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, PEER_AGENT_ROLE_CAPABILITY, ENTRIES_TAIL_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, OBSERVE_READS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_ENV_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, } from "./custom-capability.js";
|
|
5
|
+
import { ACCOUNT_RESET_CAPABILITY, ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DELEGATED_CONNECTION_CAPABILITY, DELEGATION_ROLES_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, PEER_AGENT_ROLE_CAPABILITY, ENTRIES_TAIL_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, OBSERVE_READS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_ENV_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, PARK_CLAIM_CAPABILITY, } from "./custom-capability.js";
|
|
6
6
|
import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
|
|
7
7
|
import { SESSION_MODEL_SCOPE_CAPABILITY } from "./rpc-session-command-types.js";
|
|
8
8
|
import { STEER_WHEN_IDLE_CAPABILITY, USER_ACTIVITY_CAPABILITY } from "./rpc-types.js";
|
|
@@ -103,9 +103,11 @@ export function multiSessionHostCapabilities(options) {
|
|
|
103
103
|
AUTO_PERMISSION_PRESET_CAPABILITY,
|
|
104
104
|
// Only an in-process runtime shares the loop a warm loads into (senpi#2314).
|
|
105
105
|
...(options.warm ? [WARM_CAPABILITY] : []),
|
|
106
|
+
...(options.parkClaim ? [PARK_CLAIM_CAPABILITY] : []),
|
|
106
107
|
...toolRuntimeCapabilities(options.toolRuntime),
|
|
107
108
|
// The host alone says where tool work runs; a negotiated copy would make two answers.
|
|
108
109
|
...options.negotiated.filter((capability) => capability !== "mcp_form_presenter_v1" &&
|
|
110
|
+
capability !== PARK_CLAIM_CAPABILITY &&
|
|
109
111
|
// RULING-111 (LEAK-E): only the compact negotiation owns these names; flag off echoes them as main does.
|
|
110
112
|
(!compactUpdatesEnabled() ||
|
|
111
113
|
(capability !== "compact_message_updates_v1" && capability !== "compact_events")) &&
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* and the connection attaches, so a client cannot hold a session while its host idles out under it.
|
|
13
13
|
* `warm` never attaches, marked or not: it opens nothing, so a host that only received it idles out
|
|
14
14
|
* on its normal deadline (senpi#2314).
|
|
15
|
+
* `park_session` likewise holds no client attachment: its speculative runtime is discarded at host exit.
|
|
16
|
+
* `claim_session` takes an ordinary opener attachment and is never observing.
|
|
15
17
|
* Hosts that predate the field ignore it (commands are parsed by `type`), so a marked read works
|
|
16
18
|
* against every generation.
|
|
17
19
|
*
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* and the connection attaches, so a client cannot hold a session while its host idles out under it.
|
|
13
13
|
* `warm` never attaches, marked or not: it opens nothing, so a host that only received it idles out
|
|
14
14
|
* on its normal deadline (senpi#2314).
|
|
15
|
+
* `park_session` likewise holds no client attachment: its speculative runtime is discarded at host exit.
|
|
16
|
+
* `claim_session` takes an ordinary opener attachment and is never observing.
|
|
15
17
|
* Hosts that predate the field ignore it (commands are parsed by `type`), so a marked read works
|
|
16
18
|
* against every generation.
|
|
17
19
|
*
|
|
@@ -59,7 +61,10 @@ export function isObservingRequest(line) {
|
|
|
59
61
|
if (typeof request !== "object" || request === null || Array.isArray(request))
|
|
60
62
|
return false;
|
|
61
63
|
const record = request;
|
|
62
|
-
if (record.type === "warm" ||
|
|
64
|
+
if (record.type === "warm" ||
|
|
65
|
+
record.type === "park_session" ||
|
|
66
|
+
record.type === "authenticate" ||
|
|
67
|
+
record.type === RPC_LAUNCH_PROOF_RECORD)
|
|
63
68
|
return true;
|
|
64
69
|
return record[OBSERVE_REQUEST_FIELD] === true && OBSERVING_COMMANDS.has(record.type);
|
|
65
70
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OpenRpcSession } from "./session-registry.ts";
|
|
2
|
+
/** Owns both in-flight builds and ready parks, including the races with claim, expiry and shutdown. */
|
|
3
|
+
export declare class HostParkLedger {
|
|
4
|
+
private readonly parks;
|
|
5
|
+
private readonly replacements;
|
|
6
|
+
private stopped;
|
|
7
|
+
private readonly close;
|
|
8
|
+
constructor(close: (opened: OpenRpcSession) => Promise<void>);
|
|
9
|
+
get size(): number;
|
|
10
|
+
park(key: string, build: () => Promise<OpenRpcSession>): Promise<"parked" | "already_parked" | "full">;
|
|
11
|
+
/** A claim never waits for a build, and consumes the ready park synchronously. */
|
|
12
|
+
take(key: string): OpenRpcSession | undefined;
|
|
13
|
+
finish(key: string): void;
|
|
14
|
+
replace(key: string, build: () => Promise<OpenRpcSession>): void;
|
|
15
|
+
/** Fences new builds synchronously; every build already accepted is closed when it finishes. */
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
private retire;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=host-park-ledger.d.ts.map
|