@code-yeongyu/senpi 2026.11.10-1 → 2026.11.10-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-YPAQXRP3.js → chunk-47T6YMI2.js} +2 -2
  6. package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-3IYWH6RO.js → chunk-6Q7Y2G5A.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-X4XWAB3J.js → chunk-AC6MDRAE.js} +81 -81
  10. package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-LHEP3ACI.js → chunk-DEQLHN2J.js} +2 -2
  13. package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-DHAAWL2H.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-GPXZTSEX.js → chunk-DIHIEJFV.js} +3 -3
  16. package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-HI5JSLG5.js → chunk-H3VJVKUK.js} +4 -4
  22. package/dist/bundle/chunks/{chunk-L6NRDUWJ.js → chunk-HZDKQOWB.js} +1 -1
  23. package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
  24. package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-JSD6O3RJ.js} +2 -2
  25. package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
  26. package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-ARUV3US4.js → chunk-NN7NESW2.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
  31. package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
  32. package/dist/bundle/chunks/{chunk-TGW4R7SI.js → chunk-O5WJVIEU.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
  34. package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
  38. package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
  39. package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
  42. package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
  43. package/dist/bundle/chunks/{chunk-THYEZBUG.js → chunk-Z2C5N2LM.js} +2 -2
  44. package/dist/bundle/chunks/{cli-main-VG3C2EXI.js → cli-main-ZTT3N4DO.js} +1 -1
  45. package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-5KHVGSCY.js} +1 -1
  46. package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
  47. package/dist/bundle/chunks/{hand-entry-3LQKUTYS.js → hand-entry-UV5XPMBF.js} +3 -3
  48. package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
  49. package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
  50. package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
  51. package/dist/bundle/chunks/{host-command-CCASS7W4.js → host-command-BZN7WYYV.js} +1 -1
  52. package/dist/bundle/chunks/{host-lifecycle-WWC7HXQ6.js → host-lifecycle-TNHASSFJ.js} +1 -1
  53. package/dist/bundle/chunks/host-lifecycle.js +1 -1
  54. package/dist/bundle/chunks/{host-session-command-YJB2FLKU.js → host-session-command-O5WYHZXH.js} +1 -1
  55. package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
  56. package/dist/bundle/chunks/{interactive-mode-CUN3WMUF.js → interactive-mode-OCU4NDPC.js} +1 -1
  57. package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
  58. package/dist/bundle/chunks/{main-P7XRC2OI.js → main-CNDP6HCF.js} +1 -1
  59. package/dist/bundle/chunks/{multi-session-host-244X3P6L.js → multi-session-host-ODGJAGJP.js} +11 -8
  60. package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
  61. package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
  62. package/dist/bundle/chunks/{package-manager-cli-UVWJOQA3.js → package-manager-cli-HSPUG4RJ.js} +1 -1
  63. package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
  64. package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
  65. package/dist/bundle/chunks/rpc-mode-KOZPXU3U.js +2 -0
  66. package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
  67. package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-4AENRP7P.js} +1 -1
  68. package/dist/bundle/chunks/{schedule-command-5FG2OKSR.js → schedule-command-SG44FE23.js} +1 -1
  69. package/dist/bundle/chunks/session-control-endpoint-5A24JYPE.js +3 -0
  70. package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-73YRS3GE.js} +1 -1
  71. package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
  72. package/dist/bundle/chunks/session-worker.js +199 -196
  73. package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
  74. package/dist/bundle/cli.js +2 -2
  75. package/dist/bundle/host-supervisor.js +1 -1
  76. package/dist/bundle/index.js +1 -1
  77. package/dist/bundle/rpc-entry.js +1 -1
  78. package/dist/bundle/runtime-manifest.json +1 -1
  79. package/dist/core/agent-session-runtime.d.ts +2 -0
  80. package/dist/core/agent-session-runtime.js +5 -0
  81. package/dist/core/agent-session.d.ts +13 -3
  82. package/dist/core/agent-session.js +89 -18
  83. package/dist/core/assistant-error-redaction.d.ts +9 -0
  84. package/dist/core/assistant-error-redaction.js +27 -0
  85. package/dist/core/config-reference-redaction.js +90 -10
  86. package/dist/core/context-files.d.ts +10 -0
  87. package/dist/core/context-files.js +58 -12
  88. package/dist/core/egress/wire.d.ts +13 -0
  89. package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
  90. package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
  91. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
  92. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
  93. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +4 -17
  94. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +64 -75
  95. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
  96. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
  97. package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
  98. package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +18 -0
  99. package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
  100. package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
  101. package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
  102. package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
  103. package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
  104. package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
  105. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
  106. package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
  107. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +6 -6
  108. package/dist/core/extensions/loader.js +24 -4
  109. package/dist/core/extensions/runner.d.ts +9 -1
  110. package/dist/core/extensions/runner.js +9 -4
  111. package/dist/core/extensions/types.d.ts +17 -3
  112. package/dist/core/hand/hook-advertise.d.ts +41 -0
  113. package/dist/core/hand/hook-advertise.js +98 -0
  114. package/dist/core/hand/hook-contract.d.ts +66 -0
  115. package/dist/core/hand/hook-contract.js +138 -0
  116. package/dist/core/open-timing.d.ts +2 -0
  117. package/dist/core/open-timing.js +7 -1
  118. package/dist/core/resource-loader.js +2 -0
  119. package/dist/core/retry-fallback/log.d.ts +7 -1
  120. package/dist/core/retry-fallback/log.js +82 -5
  121. package/dist/core/retry-fallback/model-config-error.js +2 -1
  122. package/dist/core/session-manager.d.ts +2 -0
  123. package/dist/core/session-manager.js +15 -1
  124. package/dist/core/settings-manager.d.ts +6 -0
  125. package/dist/core/settings-manager.js +8 -0
  126. package/dist/core/turn-origin.d.ts +19 -13
  127. package/dist/core/turn-origin.js +34 -30
  128. package/dist/core/ultrafast-lanes.d.ts +1 -0
  129. package/dist/core/ultrafast-lanes.js +4 -2
  130. package/dist/modes/interactive/components/footer.d.ts +23 -5
  131. package/dist/modes/interactive/components/footer.js +45 -16
  132. package/dist/modes/interactive/interactive-mode.js +9 -1
  133. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  134. package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
  135. package/dist/modes/rpc/admit-external-message-command.d.ts +2 -2
  136. package/dist/modes/rpc/admit-external-message-command.js +2 -10
  137. package/dist/modes/rpc/compact-admission-validation.js +4 -1
  138. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  139. package/dist/modes/rpc/connection-handler.js +20 -8
  140. package/dist/modes/rpc/connection-roles.d.ts +2 -0
  141. package/dist/modes/rpc/connection-roles.js +4 -0
  142. package/dist/modes/rpc/custom-capability.d.ts +4 -0
  143. package/dist/modes/rpc/custom-capability.js +4 -0
  144. package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
  145. package/dist/modes/rpc/delegated-drive-origin.js +7 -8
  146. package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
  147. package/dist/modes/rpc/delegation-declaration.js +5 -1
  148. package/dist/modes/rpc/effect-commands.d.ts +21 -0
  149. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  150. package/dist/modes/rpc/host-capabilities.js +5 -1
  151. package/dist/modes/rpc/host-observe-request.d.ts +2 -0
  152. package/dist/modes/rpc/host-observe-request.js +6 -1
  153. package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
  154. package/dist/modes/rpc/host-park-ledger.js +110 -0
  155. package/dist/modes/rpc/lifecycle-delivery.js +4 -1
  156. package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
  157. package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
  158. package/dist/modes/rpc/media-placeholders.js +1 -0
  159. package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
  160. package/dist/modes/rpc/method-classes-schema-host.js +24 -21
  161. package/dist/modes/rpc/method-classes-schema.d.ts +42 -1
  162. package/dist/modes/rpc/method-classes-schema.js +5 -2
  163. package/dist/modes/rpc/method-classes.d.ts +2 -0
  164. package/dist/modes/rpc/method-classes.js +3 -0
  165. package/dist/modes/rpc/multi-session-host.js +1 -1
  166. package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
  167. package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
  168. package/dist/modes/rpc/session-attachment-gate.js +2 -0
  169. package/dist/modes/rpc/session-binding.d.ts +1 -0
  170. package/dist/modes/rpc/session-binding.js +6 -1
  171. package/dist/modes/rpc/session-connection-context.d.ts +2 -0
  172. package/dist/modes/rpc/session-connection-context.js +4 -0
  173. package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
  174. package/dist/modes/rpc/session-registry-attach.js +15 -11
  175. package/dist/modes/rpc/session-registry-claims.js +2 -0
  176. package/dist/modes/rpc/session-registry-open.js +13 -7
  177. package/dist/modes/rpc/session-registry-opening.js +2 -1
  178. package/dist/modes/rpc/session-registry-types.d.ts +4 -0
  179. package/dist/modes/rpc/session-registry.d.ts +3 -0
  180. package/dist/modes/rpc/session-registry.js +27 -3
  181. package/dist/modes/rpc/session-router-close.js +1 -0
  182. package/dist/modes/rpc/session-router-context.d.ts +4 -2
  183. package/dist/modes/rpc/session-router-context.js +3 -0
  184. package/dist/modes/rpc/session-router-host-reads.js +1 -0
  185. package/dist/modes/rpc/session-router-occupancy.js +10 -1
  186. package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
  187. package/dist/modes/rpc/session-router-open-result.js +63 -0
  188. package/dist/modes/rpc/session-router-open.d.ts +3 -0
  189. package/dist/modes/rpc/session-router-open.js +15 -63
  190. package/dist/modes/rpc/session-router-park.d.ts +15 -0
  191. package/dist/modes/rpc/session-router-park.js +176 -0
  192. package/dist/modes/rpc/session-router-routing.d.ts +1 -1
  193. package/dist/modes/rpc/session-router-routing.js +7 -0
  194. package/dist/modes/rpc/session-status-source.js +2 -2
  195. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  196. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +22 -2
  197. package/docs/extensions.md +10 -0
  198. package/docs/rpc.md +50 -0
  199. package/docs/settings.md +1 -0
  200. package/docs/usage.md +1 -1
  201. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  202. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  203. package/package.json +6 -6
  204. package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
  205. package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
  206. package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
  207. package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
  208. package/dist/bundle/chunks/rpc-mode-G4I3HHPB.js +0 -2
  209. package/dist/bundle/chunks/session-control-endpoint-XWCFLJYC.js +0 -3
@@ -1,3 +1,4 @@
1
+ import { ULTRAFAST_DOCUMENTED_MODEL_IDS } from "@earendil-works/pi-ai";
1
2
  /**
2
3
  * Providers that receive `service_tier: "ultrafast"`: OpenAI's own API and the ChatGPT
3
4
  * subscription backend. Gateways and other providers serving an OpenAI model on the Responses
@@ -5,7 +6,6 @@
5
6
  * sends Ultrafast only to first-party OpenAI and Codex models).
6
7
  */
7
8
  const ULTRAFAST_PROVIDERS = new Set(["openai", "chatgpt-subscription"]);
8
- const ULTRAFAST_DOCUMENTED_MODEL_IDS = new Set(["gpt-6-astra", "gpt-6.1-sol"]);
9
9
  export function serviceTierForProvider(provider, serviceTier) {
10
10
  if (serviceTier !== "ultrafast")
11
11
  return serviceTier;
@@ -18,7 +18,9 @@ export function ultrafastSelectionWarning(model, serviceTier) {
18
18
  if (!ULTRAFAST_PROVIDERS.has(model.provider)) {
19
19
  return `Ultrafast is only sent to OpenAI and ChatGPT Subscription; ${model.provider}/${model.id} runs at its default tier`;
20
20
  }
21
- if (ULTRAFAST_DOCUMENTED_MODEL_IDS.has(model.id))
21
+ // A tier-pinned row (for example `gpt-6.1-sol-ultrafast`) is documented by its upstream model,
22
+ // as the pricing code already checks (senpi#2978).
23
+ if (ULTRAFAST_DOCUMENTED_MODEL_IDS.has(model.upstreamModelId ?? model.id))
22
24
  return undefined;
23
25
  return `Ultrafast is documented for GPT-6 Astra and GPT-6.1 Sol; ${model.provider}/${model.id} may reject or ignore it`;
24
26
  }
@@ -10,11 +10,26 @@ export declare function formatTokens(count: number): string;
10
10
  /**
11
11
  * Mirrors the provider-shown-only-when->1 rule for accounts: the active account
12
12
  * name appears only when the provider actually pools more than one slot. The
13
- * pick shown is the pin when present, else the first account of the user order
14
- * (senpi#3591), else the session's HRW winner - the same ranking the rotation
15
- * engine uses, so the footer names the slot that will serve.
13
+ * pick shown is the effective pin (CLI > settings > stored, senpi#5983) when
14
+ * present, else the first account of the user order (senpi#3591), else the
15
+ * session's HRW winner - the same ranking the rotation engine uses, so the
16
+ * footer names the slot that will serve. The effective pin only reaches the
17
+ * footer for the Anthropic subscription provider (senpi#6222 review); other
18
+ * pooled providers keep their stored pin even when a slot shares the pin's
19
+ * name. A blocked pin (blockedUntil in the
20
+ * future) is skipped for the first usable slot in that ranking, and the
21
+ * suffix marks the blocked pin (`@B (A blocked until 14:00)`); when every
22
+ * slot is blocked it names the top-ranked slot and marks it blocked instead
23
+ * of silently naming a slot that cannot serve. Block state rides on the
24
+ * stored slots (SlotBlockState); slots without it are treated as usable.
16
25
  */
17
- export declare function accountFooterSuffix(credential: Credential | undefined, sessionId: string): string;
26
+ export type AccountFooterOptions = {
27
+ /** Effective pin from CLI/settings, outranking the stored pin (senpi#5983). */
28
+ pinnedAccount?: string;
29
+ /** Override for "now" (tests); defaults to `Date.now()`. */
30
+ now?: number;
31
+ };
32
+ export declare function accountFooterSuffix(credential: Credential | undefined, sessionId: string, options?: AccountFooterOptions): string;
18
33
  export declare function formatCwdForFooter(cwd: string, home: string | undefined): string;
19
34
  /**
20
35
  * Footer component that shows pwd, token stats, and context usage.
@@ -25,7 +40,10 @@ export declare class FooterComponent implements Component {
25
40
  private footerData;
26
41
  private autoCompactEnabled;
27
42
  private compactionDelegated;
28
- constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider);
43
+ private resolveAccountPin;
44
+ constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider, options?: {
45
+ resolveAccountPin?: (sessionId: string) => string | undefined;
46
+ });
29
47
  setSession(session: AgentSession): void;
30
48
  setAutoCompactEnabled(enabled: boolean): void;
31
49
  /**
@@ -4,6 +4,7 @@ import { slotsOfOwnKind } from "@earendil-works/pi-ai/auth/pool/kinds";
4
4
  import { applyAccountOrder, rendezvousOrder } from "@earendil-works/pi-ai/auth/pool/select";
5
5
  import { accountLabel } from "@earendil-works/pi-ai/auth/pool/slots";
6
6
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
7
+ import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "../../../core/extensions/builtin/anthropic-subscription/provider-id.js";
7
8
  import { theme } from "../theme/theme.js";
8
9
  import { planFooterLayout } from "./footer-layout.js";
9
10
  import { buildRightLabel, colorRightSide } from "./footer-right-label.js";
@@ -39,25 +40,43 @@ export function formatTokens(count) {
39
40
  return `${trim1(n / 1_000_000_000)}B`;
40
41
  return `${Math.round(n / 1_000_000_000)}B`;
41
42
  }
42
- /**
43
- * Mirrors the provider-shown-only-when->1 rule for accounts: the active account
44
- * name appears only when the provider actually pools more than one slot. The
45
- * pick shown is the pin when present, else the first account of the user order
46
- * (senpi#3591), else the session's HRW winner - the same ranking the rotation
47
- * engine uses, so the footer names the slot that will serve.
48
- */
49
- export function accountFooterSuffix(credential, sessionId) {
43
+ function formatBlockedUntil(until) {
44
+ const date = new Date(until);
45
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
46
+ }
47
+ export function accountFooterSuffix(credential, sessionId, options = {}) {
50
48
  const pooled = credential;
51
49
  // Only slots the credential can serve with; a mixed pool's other-kind slot never does (senpi#3486).
52
50
  const slots = credential ? slotsOfOwnKind(credential) : [];
53
51
  if (slots.length < 2)
54
52
  return "";
55
- const pinned = Object.entries(credential ?? {}).find(([key]) => key === "pinned")?.[1];
56
- const pinnedSlot = slots.find((slot) => slot.name === pinned);
57
- if (pinnedSlot)
58
- return `@${footerAccountLabel(pinnedSlot)}`;
59
- const winner = applyAccountOrder(pooled?.order, rendezvousOrder(sessionId, slots, (input) => createHash("sha256").update(input).digest().readBigUInt64BE(0)))[0];
60
- return winner === undefined ? "" : `@${footerAccountLabel(winner)}`;
53
+ const now = options.now ?? Date.now();
54
+ // Block state rides on the stored slots at runtime (SlotBlockState) but
55
+ // is not part of the CredentialSlot type, so read it through unknown.
56
+ const blockedUntilOf = (slot) => slot.blockedUntil;
57
+ const isBlocked = (slot) => {
58
+ const until = blockedUntilOf(slot);
59
+ return until !== undefined && until > now;
60
+ };
61
+ const ranked = applyAccountOrder(pooled?.order, rendezvousOrder(sessionId, slots, (input) => createHash("sha256").update(input).digest().readBigUInt64BE(0)));
62
+ const storedPinned = Object.entries(credential ?? {}).find(([key]) => key === "pinned")?.[1];
63
+ const pinnedName = options.pinnedAccount ?? storedPinned;
64
+ const pinnedSlot = pinnedName === undefined ? undefined : slots.find((slot) => slot.name === pinnedName);
65
+ if (pinnedSlot) {
66
+ if (!isBlocked(pinnedSlot))
67
+ return `@${footerAccountLabel(pinnedSlot)}`;
68
+ const until = blockedUntilOf(pinnedSlot);
69
+ const serving = ranked.find((slot) => slot !== pinnedSlot && !isBlocked(slot));
70
+ if (serving)
71
+ return `@${footerAccountLabel(serving)} (${footerAccountLabel(pinnedSlot)} blocked until ${formatBlockedUntil(until)})`;
72
+ return `@${footerAccountLabel(pinnedSlot)} (blocked until ${formatBlockedUntil(until)})`;
73
+ }
74
+ const winner = ranked.find((slot) => !isBlocked(slot)) ?? ranked[0];
75
+ if (winner === undefined)
76
+ return "";
77
+ if (isBlocked(winner))
78
+ return `@${footerAccountLabel(winner)} (blocked until ${formatBlockedUntil(blockedUntilOf(winner))})`;
79
+ return `@${footerAccountLabel(winner)}`;
61
80
  }
62
81
  /**
63
82
  * Widest account label the provider segment carries. A label wider than this
@@ -90,11 +109,12 @@ export function formatCwdForFooter(cwd, home) {
90
109
  * Computes token/context stats from session, gets git branch and extension statuses from provider.
91
110
  */
92
111
  export class FooterComponent {
93
- constructor(session, footerData) {
112
+ constructor(session, footerData, options = {}) {
94
113
  this.autoCompactEnabled = true;
95
114
  this.compactionDelegated = false;
96
115
  this.session = session;
97
116
  this.footerData = footerData;
117
+ this.resolveAccountPin = options.resolveAccountPin;
98
118
  }
99
119
  setSession(session) {
100
120
  this.session = session;
@@ -201,7 +221,16 @@ export class FooterComponent {
201
221
  let accountSuffix = "";
202
222
  if (state.model) {
203
223
  try {
204
- accountSuffix = accountFooterSuffix(this.session.modelRegistry.authStorage.get(state.model.provider), this.session.sessionManager.getSessionId());
224
+ const sessionId = this.session.sessionManager.getSessionId();
225
+ // The resolver carries the Anthropic CLI/settings pin; resolve
226
+ // it only when the current model uses the Anthropic
227
+ // subscription provider. A same-named slot of another pooled
228
+ // provider (e.g. openai-codex "default") keeps its own stored
229
+ // pin instead (senpi#6222 review).
230
+ const pinnedAccount = state.model.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID
231
+ ? this.resolveAccountPin?.(sessionId)
232
+ : undefined;
233
+ accountSuffix = accountFooterSuffix(this.session.modelRegistry.authStorage.get(state.model.provider), sessionId, pinnedAccount === undefined ? undefined : { pinnedAccount });
205
234
  }
206
235
  catch {
207
236
  // The footer must render even when credential storage is unreadable.
@@ -25,6 +25,8 @@ import { collectEntriesForBranchSummary } from "../../core/compaction/branch-sum
25
25
  import { findExtensionStackMatches } from "../../core/crash-log.js";
26
26
  import { AssistantEditError, assistantTextEquals } from "../../core/edited-assistant-message.js";
27
27
  import { subscribeProviderAccountEvents } from "../../core/extensions/builtin/anthropic-subscription/account-events.js";
28
+ import { getSessionClaudeAccountPin, resolveClaudeAccountPin, } from "../../core/extensions/builtin/anthropic-subscription/account-command.js";
29
+ import { loadAnthropicSubscriptionProviderSettingsFromDisk } from "../../core/extensions/builtin/anthropic-subscription/settings.js";
28
30
  import { formatAccountFailoverNotice } from "./account-failover-notice.js";
29
31
  import { askUserAnswerDisplayText, formatUserMessage, showsPartialAnswers, } from "../../core/extensions/builtin/ask-user/format.js";
30
32
  import { askUserRenderers } from "../../core/extensions/builtin/ask-user/render.js";
@@ -817,7 +819,13 @@ export class InteractiveMode {
817
819
  this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
818
820
  this.footer = this.chrome
819
821
  ? this.chrome.createFooter(this.session, this.footerDataProvider)
820
- : new FooterComponent(this.session, this.footerDataProvider);
822
+ : new FooterComponent(this.session, this.footerDataProvider, {
823
+ // CLI > settings > stored pin, resolved per render so a
824
+ // mid-session /claude-account pin or settings change is
825
+ // picked up (senpi#5983); the stored pin stays the
826
+ // fallback inside accountFooterSuffix.
827
+ resolveAccountPin: (sessionId) => resolveClaudeAccountPin(getSessionClaudeAccountPin(sessionId), loadAnthropicSubscriptionProviderSettingsFromDisk(this.sessionManager.getCwd()).pinnedAccount, undefined),
828
+ });
821
829
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
822
830
  this.footerContainer = new Container();
823
831
  this.footerContainer.addChild(this.footer);
@@ -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":
@@ -20,8 +20,8 @@ export interface DeliveryPeer {
20
20
  /** The remote device the connection speaks for (`ConnectionRoles.originOf`); absent on a plain local connection. */
21
21
  readonly origin: RpcRemoteOrigin | undefined;
22
22
  readonly connectionOrigin: ConnectionOrigin;
23
- /** The forwarder granted this connection `peer_agent` in its host-mode delegation (senpi#6305). */
24
- readonly peerAgent?: boolean;
23
+ /** The origin the turn this delivery opens counts as (senpi#6342); the connection's origin when absent. */
24
+ readonly turnOrigin?: ConnectionOrigin;
25
25
  }
26
26
  export type AdmitExternalMessageAnswer = {
27
27
  readonly ok: true;
@@ -9,17 +9,9 @@ function ledgerId(deliveryId, origin) {
9
9
  ? `rpc:local/${deliveryId}`
10
10
  : `device:${encodeURIComponent(origin.deviceId)}/${deliveryId}`;
11
11
  }
12
- /**
13
- * The turn a delivery opens: a steer joins the running turn; an agent's delivery on a delegated connection the
14
- * forwarder granted `peer_agent` is a peer turn (senpi#6305), the record's sender kind being only a consistency check;
15
- * anything else keeps the connection's origin.
16
- */
12
+ /** The turn a delivery opens: a steer joins the running turn; anything else counts as the connection's turn origin. */
17
13
  function deliveryTurnMark(command, peer) {
18
- if (command.deliverAs === "steer")
19
- return "join";
20
- if (peer.connectionOrigin === "delegated" && peer.peerAgent === true && command.sender?.kind === "agent")
21
- return "peer";
22
- return peer.connectionOrigin;
14
+ return command.deliverAs === "steer" ? "join" : (peer.turnOrigin ?? peer.connectionOrigin);
23
15
  }
24
16
  export function admitExternalMessage(session, command, peer) {
25
17
  const chain = admitInputChain(command.chainCount, session.settingsManager);
@@ -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
  /**
@@ -52,7 +52,8 @@ import { ConnectionQuestionBridge, degradeQuestion, sessionQuestionBridges } fro
52
52
  import { remoteOriginDeclaration, remoteOriginField } from "./remote-origin.js";
53
53
  import { advertisedCompactCapabilities, sessionDeclaration } from "./compact-negotiation.js";
54
54
  import { commandAuthority } from "./command-authority.js";
55
- import { noteDelegatedRecord } from "./delegated-drive-origin.js";
55
+ import { runInRecordScope } from "../../core/turn-origin.js";
56
+ import { recordTurnOrigin } from "./delegated-drive-origin.js";
56
57
  import { ConnectionRoles, DELEGATION_INCARNATION_STALE, roleConnection } from "./connection-roles.js";
57
58
  import { ACCOUNT_RESET_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, ENTRIES_TAIL_CAPABILITY, INTERACTIVE_USER_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUESTION_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, TOOL_BARRIER_CAPABILITY, TASK_PARENT_CAPABILITY, } from "./custom-capability.js";
58
59
  import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetThinkingLevel, } from "./effect-commands.js";
@@ -209,6 +210,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
209
210
  const currentConnectionId = () => roleConnection(options.clientInfo?.connectionId());
210
211
  const hasRole = (role) => connectionRoles.has(currentConnectionId(), role);
211
212
  const connectionOrigin = () => connectionRoles.isDelegated(currentConnectionId()) ? "delegated" : "local";
213
+ // senpi#6342: a turn follows this machine's own setting; only a device the forwarder did not state is the same account
214
+ // counts as delegated for the permission rule.
215
+ const turnOrigin = () => connectionOrigin() === "delegated" && !connectionRoles.sameAccount(currentConnectionId()) ? "delegated" : "local";
212
216
  const routingSessionId = options.sessionId;
213
217
  // True only while THIS connection's own command drives a replacement; the issuer
214
218
  // already learns the new identity from its command response.
@@ -1009,7 +1013,6 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1009
1013
  // Handle a single command
1010
1014
  const handleCommand = async (command, requestContext) => {
1011
1015
  const id = command.id;
1012
- noteDelegatedRecord(command, connectionOrigin(), session.sessionManager);
1013
1016
  switch (command.type) {
1014
1017
  case "get_protocol_info":
1015
1018
  return {
@@ -1047,7 +1050,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1047
1050
  ...(options.retryFallbackCommand ? [RETRY_FALLBACK_COMMAND_CAPABILITY] : []),
1048
1051
  ...(options.toolRuntime ? toolRuntimeCapabilities(options.toolRuntime) : []),
1049
1052
  // 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)),
1053
+ ...advertisedCompactCapabilities(options.capabilities ?? [], false).filter((capability) => capability !== SESSION_HELD_CAPABILITY &&
1054
+ capability !== "park_claim" &&
1055
+ !isHostOwnedToolRuntimeCapability(capability)),
1051
1056
  ]),
1052
1057
  ],
1053
1058
  mode: "classic",
@@ -1055,7 +1060,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1055
1060
  },
1056
1061
  };
1057
1062
  case "open_session":
1058
- return error(id, "open_session", "multi_session_disabled");
1063
+ case "park_session":
1064
+ case "claim_session":
1065
+ return error(id, command.type, "multi_session_disabled");
1059
1066
  // =================================================================
1060
1067
  // Prompting
1061
1068
  // =================================================================
@@ -1066,7 +1073,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1066
1073
  if (holdAutomaticInput(session, command, hasRole(INTERACTIVE_USER_CAPABILITY)))
1067
1074
  return success(id, "prompt", { disposition: "handled", heldAsNotice: true });
1068
1075
  turnRequested = true;
1069
- const admission = handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: connectionOrigin() });
1076
+ const admission = handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: turnOrigin() });
1070
1077
  promptCalls.add(admission);
1071
1078
  void admission.finally(() => promptCalls.delete(admission));
1072
1079
  return undefined;
@@ -1136,7 +1143,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1136
1143
  if (holdAutomaticInput(session, command, hasRole(INTERACTIVE_USER_CAPABILITY)))
1137
1144
  return success(id, command.type, { disposition: "handled", heldAsNotice: true });
1138
1145
  turnRequested = true;
1139
- await handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: connectionOrigin() });
1146
+ await handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: turnOrigin() });
1140
1147
  return undefined;
1141
1148
  }
1142
1149
  case "abort":
@@ -1238,7 +1245,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1238
1245
  const peer = {
1239
1246
  origin: connectionRoles.originOf(currentConnectionId()),
1240
1247
  connectionOrigin: connectionOrigin(),
1241
- peerAgent: connectionRoles.grantedByForwarder(currentConnectionId(), "peer_agent"),
1248
+ turnOrigin: turnOrigin(),
1242
1249
  };
1243
1250
  const answer = admitExternalMessage(session, command, peer);
1244
1251
  if (!answer.ok) {
@@ -1991,7 +1998,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1991
1998
  return;
1992
1999
  }
1993
2000
  try {
1994
- const response = await handleCommand(command, requestContext);
2001
+ // senpi#6342: the record's own handling, and the first run it starts, run under the record's origin.
2002
+ const response = await runInRecordScope(recordTurnOrigin(command, turnOrigin()), () => handleCommand(command, requestContext));
1995
2003
  if (response) {
1996
2004
  output(response);
1997
2005
  await waitForRpcBackpressure();
@@ -2052,6 +2060,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
2052
2060
  pendingPrompts() {
2053
2061
  return [...promptCalls];
2054
2062
  },
2063
+ setClientCapabilities(capabilities) {
2064
+ replaceClientCapabilities([...capabilities]);
2065
+ refreshGrantPresentation();
2066
+ },
2055
2067
  isShutdownRequested() {
2056
2068
  return shutdownRequested;
2057
2069
  },
@@ -59,6 +59,8 @@ export declare class ConnectionRoles {
59
59
  acceptsOrigin(connectionId: string, origin: RpcRemoteOrigin): boolean;
60
60
  /** Records the remote device a local connection speaks for; a delegated connection's origin is never declared. */
61
61
  declareOrigin(connectionId: string, origin: RpcRemoteOrigin): void;
62
+ /** Whether the forwarder stated this delegated connection's device is the host's own account (senpi#6342). */
63
+ sameAccount(connectionId: string | undefined): boolean;
62
64
  /** The remote device this connection speaks for: a delegated connection's principal, else its declared origin. */
63
65
  originOf(connectionId: string | undefined): RpcRemoteOrigin | undefined;
64
66
  /** Whether the connection is delegated (a remote device's): on the host, retained by a worker, or carried to one. */
@@ -117,6 +117,10 @@ export class ConnectionRoles {
117
117
  if (this.acceptsOrigin(connectionId, origin))
118
118
  this.origins.set(connectionId, origin);
119
119
  }
120
+ /** Whether the forwarder stated this delegated connection's device is the host's own account (senpi#6342). */
121
+ sameAccount(connectionId) {
122
+ return this.viewOf(roleConnection(connectionId))?.descriptor.principal.sameAccount === true;
123
+ }
120
124
  /** The remote device this connection speaks for: a delegated connection's principal, else its declared origin. */
121
125
  originOf(connectionId) {
122
126
  const key = roleConnection(connectionId);
@@ -65,6 +65,8 @@ export declare const DELEGATED_CONNECTION_CAPABILITY = "delegated_connection_v1"
65
65
  export declare const DELEGATION_ROLES_CAPABILITY = "delegation_roles_v1";
66
66
  /** A host-mode delegation may carry the forwarder-granted `peer_agent` role (senpi#6305). */
67
67
  export declare const PEER_AGENT_ROLE_CAPABILITY = "peer_agent_role_v1";
68
+ /** A delegation may state `principal.sameAccount`, so that device's turns follow this host's own setting (senpi#6342). */
69
+ export declare const SAME_ACCOUNT_DELEGATION_CAPABILITY = "same_account_delegation_v1";
68
70
  /**
69
71
  * Client opt-in (D11 S3-5): the connection renders the value-free `secret_input_notice` and `grant_select_notice` of a
70
72
  * raiser-only dialog another connection presents, and their `*_notice_settled` records. It never answers them.
@@ -117,6 +119,8 @@ export declare const AUTO_TITLE_PER_SESSION_CAPABILITY = "auto_title_per_session
117
119
  * cwd, kind and context needs without opening a session. Advertised only by an in-process runtime.
118
120
  */
119
121
  export declare const WARM_CAPABILITY = "warm";
122
+ /** HOST capability: invisible speculative sessions and single-use claim, in-process registries only. */
123
+ export declare const PARK_CLAIM_CAPABILITY = "park_claim";
120
124
  /**
121
125
  * HOST capability: this host honors `open_session.durableSessionId`, so a caller that already
122
126
  * owns a stable record id for the conversation can CREATE the session under that id and keep
@@ -65,6 +65,8 @@ export const DELEGATED_CONNECTION_CAPABILITY = "delegated_connection_v1";
65
65
  export const DELEGATION_ROLES_CAPABILITY = "delegation_roles_v1";
66
66
  /** A host-mode delegation may carry the forwarder-granted `peer_agent` role (senpi#6305). */
67
67
  export const PEER_AGENT_ROLE_CAPABILITY = "peer_agent_role_v1";
68
+ /** A delegation may state `principal.sameAccount`, so that device's turns follow this host's own setting (senpi#6342). */
69
+ export const SAME_ACCOUNT_DELEGATION_CAPABILITY = "same_account_delegation_v1";
68
70
  /**
69
71
  * Client opt-in (D11 S3-5): the connection renders the value-free `secret_input_notice` and `grant_select_notice` of a
70
72
  * raiser-only dialog another connection presents, and their `*_notice_settled` records. It never answers them.
@@ -117,6 +119,8 @@ export const AUTO_TITLE_PER_SESSION_CAPABILITY = "auto_title_per_session";
117
119
  * cwd, kind and context needs without opening a session. Advertised only by an in-process runtime.
118
120
  */
119
121
  export const WARM_CAPABILITY = "warm";
122
+ /** HOST capability: invisible speculative sessions and single-use claim, in-process registries only. */
123
+ export const PARK_CLAIM_CAPABILITY = "park_claim";
120
124
  /**
121
125
  * HOST capability: this host honors `open_session.durableSessionId`, so a caller that already
122
126
  * owns a stable record id for the conversation can CREATE the session under that id and keep
@@ -1,9 +1,9 @@
1
- import type { SessionManager } from "../../core/session-manager.ts";
2
- import { type ConnectionOrigin } from "../../core/turn-origin.ts";
1
+ import type { ConnectionOrigin } from "../../core/turn-origin.ts";
3
2
  /**
4
- * Marks a session as remote-driven when a delegated connection sends it any record that can act on its work, decided
5
- * by the engine's own method-class table (`drive`, and `device_admin`, which grants more), never by a hand-kept list of
6
- * commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...) cannot slip past the rule.
3
+ * The origin a record's own handling runs under (senpi#6342): delegated for a cross-account record that can act on the
4
+ * session's work, decided by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
5
+ * never by a hand-kept list of commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
6
+ * cannot slip past the rule. Any other record runs as local.
7
7
  */
8
- export declare function noteDelegatedRecord(command: object, origin: ConnectionOrigin, sessionManager: Pick<SessionManager, "getEntries" | "appendCustomEntry">): void;
8
+ export declare function recordTurnOrigin(command: object, origin: ConnectionOrigin): ConnectionOrigin;
9
9
  //# sourceMappingURL=delegated-drive-origin.d.ts.map
@@ -1,16 +1,15 @@
1
- import { recordDelegatedOrigin } from "../../core/turn-origin.js";
2
1
  import { classifyRpcMethod } from "./method-classes.js";
3
2
  const ACTING_CLASSES = new Set(["drive", "device_admin"]);
4
3
  /**
5
- * Marks a session as remote-driven when a delegated connection sends it any record that can act on its work, decided
6
- * by the engine's own method-class table (`drive`, and `device_admin`, which grants more), never by a hand-kept list of
7
- * commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...) cannot slip past the rule.
4
+ * The origin a record's own handling runs under (senpi#6342): delegated for a cross-account record that can act on the
5
+ * session's work, decided by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
6
+ * never by a hand-kept list of commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
7
+ * cannot slip past the rule. Any other record runs as local.
8
8
  */
9
- export function noteDelegatedRecord(command, origin, sessionManager) {
9
+ export function recordTurnOrigin(command, origin) {
10
10
  if (origin !== "delegated")
11
- return;
11
+ return "local";
12
12
  const methodClass = classifyRpcMethod(command);
13
- if (methodClass !== undefined && ACTING_CLASSES.has(methodClass))
14
- recordDelegatedOrigin(sessionManager);
13
+ return methodClass !== undefined && ACTING_CLASSES.has(methodClass) ? "delegated" : "local";
15
14
  }
16
15
  //# sourceMappingURL=delegated-drive-origin.js.map
@@ -4,6 +4,8 @@ export interface Principal {
4
4
  readonly deviceId: string;
5
5
  readonly deviceName?: string;
6
6
  readonly conn?: string;
7
+ /** The forwarder states this device is the host's own account (senpi#6342); fixed for the record's life. */
8
+ readonly sameAccount?: true;
7
9
  }
8
10
  export interface Declaration {
9
11
  readonly principal: Principal;
@@ -22,6 +22,9 @@ export function parseDeclaration(record) {
22
22
  return "principal.deviceName must be 1-256 characters";
23
23
  if (conn !== undefined && !boundedString(conn, MAX_CONN_FIELD))
24
24
  return "principal.conn must be 1-64 characters";
25
+ const sameAccount = Reflect.get(principal, "sameAccount");
26
+ if (sameAccount !== undefined && sameAccount !== true)
27
+ return "principal.sameAccount must be true when present";
25
28
  const granted = Reflect.get(record, "granted");
26
29
  if (!Array.isArray(granted))
27
30
  return "granted must be an array of method classes";
@@ -40,12 +43,13 @@ export function parseDeclaration(record) {
40
43
  deviceId: deviceId,
41
44
  ...(deviceName === undefined ? {} : { deviceName: deviceName }),
42
45
  ...(conn === undefined ? {} : { conn: conn }),
46
+ ...(sameAccount === true ? { sameAccount: true } : {}),
43
47
  },
44
48
  granted: classes,
45
49
  };
46
50
  }
47
51
  export function samePrincipal(a, b) {
48
- return a.deviceId === b.deviceId && a.deviceName === b.deviceName && a.conn === b.conn;
52
+ return (a.deviceId === b.deviceId && a.deviceName === b.deviceName && a.conn === b.conn && a.sameAccount === b.sameAccount);
49
53
  }
50
54
  function parseRoles(value) {
51
55
  if (value === undefined)
@@ -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, SAME_ACCOUNT_DELEGATION_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";
@@ -72,6 +72,8 @@ export function multiSessionHostCapabilities(options) {
72
72
  DELEGATION_ROLES_CAPABILITY,
73
73
  // ...and may carry the forwarder-granted `peer_agent` role on such a connection (senpi#6305).
74
74
  PEER_AGENT_ROLE_CAPABILITY,
75
+ // ...and lets the forwarder state a device is the host's own account, whose turns then ask nothing extra.
76
+ SAME_ACCOUNT_DELEGATION_CAPABILITY,
75
77
  // Only a multi-session host keeps sessions loaded that a client can ask it to free.
76
78
  UNLOAD_SESSION_CAPABILITY,
77
79
  // Only a multi-session host owns a move ledger and the hosted-session list export checks against.
@@ -103,9 +105,11 @@ export function multiSessionHostCapabilities(options) {
103
105
  AUTO_PERMISSION_PRESET_CAPABILITY,
104
106
  // Only an in-process runtime shares the loop a warm loads into (senpi#2314).
105
107
  ...(options.warm ? [WARM_CAPABILITY] : []),
108
+ ...(options.parkClaim ? [PARK_CLAIM_CAPABILITY] : []),
106
109
  ...toolRuntimeCapabilities(options.toolRuntime),
107
110
  // The host alone says where tool work runs; a negotiated copy would make two answers.
108
111
  ...options.negotiated.filter((capability) => capability !== "mcp_form_presenter_v1" &&
112
+ capability !== PARK_CLAIM_CAPABILITY &&
109
113
  // RULING-111 (LEAK-E): only the compact negotiation owns these names; flag off echoes them as main does.
110
114
  (!compactUpdatesEnabled() ||
111
115
  (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
  }