@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/bundle/chunks/{chunk-GPXZTSEX.js → chunk-2M54EAAY.js} +3 -3
  3. package/dist/bundle/chunks/{chunk-YPAQXRP3.js → chunk-47T6YMI2.js} +2 -2
  4. package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-HI5JSLG5.js → chunk-74IB3JPP.js} +2 -2
  6. package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-3IYWH6RO.js → chunk-IKJCA5SH.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-L6NRDUWJ.js → chunk-OF3XVGG4.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-THYEZBUG.js → chunk-Q4G5DECT.js} +2 -2
  13. package/dist/bundle/chunks/{chunk-ARUV3US4.js → chunk-QATU6BBC.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-TUEZNIBK.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-UOZDDYBN.js} +2 -2
  16. package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-TGW4R7SI.js → chunk-XH24X5B2.js} +1 -1
  18. package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
  19. package/dist/bundle/chunks/{chunk-X4XWAB3J.js → chunk-YCPURLPU.js} +38 -38
  20. package/dist/bundle/chunks/{chunk-LHEP3ACI.js → chunk-YNGEZBTP.js} +1 -1
  21. package/dist/bundle/chunks/{cli-main-VG3C2EXI.js → cli-main-Y6ALA5OI.js} +1 -1
  22. package/dist/bundle/chunks/{hand-entry-3LQKUTYS.js → hand-entry-IPDAIOGI.js} +1 -1
  23. package/dist/bundle/chunks/{host-command-CCASS7W4.js → host-command-2326GCUP.js} +1 -1
  24. package/dist/bundle/chunks/{host-lifecycle-WWC7HXQ6.js → host-lifecycle-TNHASSFJ.js} +1 -1
  25. package/dist/bundle/chunks/host-lifecycle.js +1 -1
  26. package/dist/bundle/chunks/{host-session-command-YJB2FLKU.js → host-session-command-BZYBDOIE.js} +1 -1
  27. package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
  28. package/dist/bundle/chunks/{interactive-mode-CUN3WMUF.js → interactive-mode-LBQB7DDR.js} +1 -1
  29. package/dist/bundle/chunks/{main-P7XRC2OI.js → main-FFRFP34W.js} +1 -1
  30. package/dist/bundle/chunks/{multi-session-host-244X3P6L.js → multi-session-host-NDIHHJ2L.js} +11 -8
  31. package/dist/bundle/chunks/{package-manager-cli-UVWJOQA3.js → package-manager-cli-XCPAMOYI.js} +1 -1
  32. package/dist/bundle/chunks/{rpc-mode-G4I3HHPB.js → rpc-mode-TKNZM4PZ.js} +1 -1
  33. package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
  34. package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-JDUKXDI2.js} +1 -1
  35. package/dist/bundle/chunks/{schedule-command-5FG2OKSR.js → schedule-command-HF2DHABB.js} +1 -1
  36. package/dist/bundle/chunks/{session-control-endpoint-XWCFLJYC.js → session-control-endpoint-A6R6W2JQ.js} +1 -1
  37. package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GWA4S2PO.js} +1 -1
  38. package/dist/bundle/chunks/session-worker.js +119 -116
  39. package/dist/bundle/cli.js +2 -2
  40. package/dist/bundle/host-supervisor.js +1 -1
  41. package/dist/bundle/index.js +1 -1
  42. package/dist/bundle/rpc-entry.js +1 -1
  43. package/dist/bundle/runtime-manifest.json +1 -1
  44. package/dist/core/agent-session-runtime.d.ts +2 -0
  45. package/dist/core/agent-session-runtime.js +5 -0
  46. package/dist/core/agent-session.d.ts +1 -1
  47. package/dist/core/agent-session.js +30 -10
  48. package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
  49. package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
  50. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
  51. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
  52. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +4 -17
  53. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +64 -75
  54. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
  55. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
  56. package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
  57. package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +18 -0
  58. package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
  59. package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
  60. package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
  61. package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
  62. package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
  63. package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
  64. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
  65. package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
  66. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +6 -6
  67. package/dist/core/open-timing.d.ts +2 -0
  68. package/dist/core/open-timing.js +7 -1
  69. package/dist/core/session-manager.d.ts +2 -0
  70. package/dist/core/session-manager.js +15 -1
  71. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  72. package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
  73. package/dist/modes/rpc/compact-admission-validation.js +4 -1
  74. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  75. package/dist/modes/rpc/connection-handler.js +10 -2
  76. package/dist/modes/rpc/custom-capability.d.ts +2 -0
  77. package/dist/modes/rpc/custom-capability.js +2 -0
  78. package/dist/modes/rpc/effect-commands.d.ts +21 -0
  79. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  80. package/dist/modes/rpc/host-capabilities.js +3 -1
  81. package/dist/modes/rpc/host-observe-request.d.ts +2 -0
  82. package/dist/modes/rpc/host-observe-request.js +6 -1
  83. package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
  84. package/dist/modes/rpc/host-park-ledger.js +110 -0
  85. package/dist/modes/rpc/lifecycle-delivery.js +4 -1
  86. package/dist/modes/rpc/media-placeholders.js +1 -0
  87. package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
  88. package/dist/modes/rpc/method-classes-schema-host.js +24 -21
  89. package/dist/modes/rpc/method-classes-schema.d.ts +42 -1
  90. package/dist/modes/rpc/method-classes-schema.js +5 -2
  91. package/dist/modes/rpc/method-classes.d.ts +2 -0
  92. package/dist/modes/rpc/method-classes.js +3 -0
  93. package/dist/modes/rpc/multi-session-host.js +1 -1
  94. package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
  95. package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
  96. package/dist/modes/rpc/session-attachment-gate.js +2 -0
  97. package/dist/modes/rpc/session-binding.d.ts +1 -0
  98. package/dist/modes/rpc/session-binding.js +6 -1
  99. package/dist/modes/rpc/session-connection-context.d.ts +2 -0
  100. package/dist/modes/rpc/session-connection-context.js +4 -0
  101. package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
  102. package/dist/modes/rpc/session-registry-attach.js +15 -11
  103. package/dist/modes/rpc/session-registry-claims.js +2 -0
  104. package/dist/modes/rpc/session-registry-open.js +13 -7
  105. package/dist/modes/rpc/session-registry-opening.js +2 -1
  106. package/dist/modes/rpc/session-registry-types.d.ts +4 -0
  107. package/dist/modes/rpc/session-registry.d.ts +3 -0
  108. package/dist/modes/rpc/session-registry.js +27 -3
  109. package/dist/modes/rpc/session-router-close.js +1 -0
  110. package/dist/modes/rpc/session-router-context.d.ts +4 -2
  111. package/dist/modes/rpc/session-router-context.js +3 -0
  112. package/dist/modes/rpc/session-router-host-reads.js +1 -0
  113. package/dist/modes/rpc/session-router-occupancy.js +10 -1
  114. package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
  115. package/dist/modes/rpc/session-router-open-result.js +63 -0
  116. package/dist/modes/rpc/session-router-open.d.ts +3 -0
  117. package/dist/modes/rpc/session-router-open.js +15 -63
  118. package/dist/modes/rpc/session-router-park.d.ts +15 -0
  119. package/dist/modes/rpc/session-router-park.js +176 -0
  120. package/dist/modes/rpc/session-router-routing.d.ts +1 -1
  121. package/dist/modes/rpc/session-router-routing.js +7 -0
  122. package/dist/modes/rpc/session-status-source.js +2 -2
  123. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  124. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +22 -2
  125. package/docs/rpc.md +50 -0
  126. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  127. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  128. package/package.json +6 -6
  129. 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 result = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
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(connection.client),
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
- await ensureMcpToolCallConnection(entry.connection, () => entry.authPlan?.refresh?.ensureFresh().then(() => undefined) ?? Promise.resolve());
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
- const registeredCatalog = entry.cachedCatalog;
58
- if (entry.connection instanceof SharedMcpLease) {
59
- const refreshed = await entry.connection.catalog();
60
- if (!ownsCatalog())
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
- // Tombstone removed tools BEFORE re-registration so the subsequent
93
- // setActiveTools (which excludes them) leaves the tombstones inactive.
94
- const seen = new Set(initial.map((target) => target.pi));
95
- const failures = [];
96
- let pending = stale;
97
- while (pending.length > 0) {
98
- for (const target of pending) {
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
- try {
102
- for (const removed of diff.removed)
103
- target.pi.registerTool(buildMcpTombstoneDefinition(removed, entry.name));
104
- await target.register();
105
- }
106
- catch (error) {
107
- failures.push(error);
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
- async function refreshConnectedMcpCatalog(entry, serverConfig) {
95
- if (serverConfig === undefined || entry.isCurrent?.() === false)
96
- return;
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
- const catalog = await entry.connection.catalog();
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"): with no ownership statement 5.2
20
- // repeats existing code instead of reusing it. The user owns the code here, so
21
- // the guide's ownership sentence stays out (it invites unrequested changes);
22
- // the smallest-change rule says to build on the code that already does the job.
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: the ownership sentence; "answer directly" or think-less
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
- Smallest correct change wins: build on the code that already does the job instead of adding a parallel copy, with no refactors beside a focused fix, 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.
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
@@ -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" || command.type === "attach_session" || command.type === "subscribe_sessions")
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 && !isHostOwnedToolRuntimeCapability(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
- return error(id, "open_session", "multi_session_disabled");
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" || record.type === "authenticate" || record.type === RPC_LAUNCH_PROOF_RECORD)
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