@code-yeongyu/senpi 2026.10.26-1 → 2026.10.27-1

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 (179) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/bundle/chunks/{anthropic-messages-M235FJQV.js → anthropic-messages-ODSFJCXR.js} +1 -1
  3. package/dist/bundle/chunks/{args-FBNJOJSR.js → args-SWKJAYQD.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-YQRTQ7H6.js → azure-openai-responses-REGXS4XO.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-DMFAVE5N.js → chunk-4QVORFQ2.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-ZXHLH4MW.js → chunk-5W3DTM2J.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-QYFOHFPS.js → chunk-6CJC6JY4.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-QREKAXKX.js → chunk-6GAMRAVM.js} +5 -5
  10. package/dist/bundle/chunks/{chunk-MSHRIZGT.js → chunk-7IZH2KQX.js} +3 -3
  11. package/dist/bundle/chunks/{chunk-PH3JDRB2.js → chunk-ARSENVDY.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-5KZH6EYQ.js → chunk-CNDPCYNT.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-DYWIFF2Q.js → chunk-CYOHFREV.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-3LPWILUZ.js → chunk-DNDZPKIQ.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-ZLIVZQDY.js → chunk-EQNUL2WB.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-LO5MYLGF.js → chunk-FQDARFHT.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-7KOUQSBX.js → chunk-GPPOPK76.js} +3 -3
  18. package/dist/bundle/chunks/{chunk-I7QWTWVZ.js → chunk-IHKHCWWG.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-MY4ZD3EW.js → chunk-IY4G44MN.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-OQ3WDMB3.js → chunk-JH7MVRNJ.js} +1 -1
  21. package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
  22. package/dist/bundle/chunks/{chunk-UW6M3EXY.js → chunk-KRO63T4Q.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-J5FCH7TI.js → chunk-KTDIIF2F.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-3WP5RFQR.js → chunk-NDNIMC5W.js} +1 -1
  25. package/dist/bundle/chunks/chunk-NXQFBAUI.js +2 -0
  26. package/dist/bundle/chunks/{chunk-CDQPA4FP.js → chunk-ODRGPC5S.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-5FQMNHXV.js → chunk-P5E4EVQF.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-3A3XCLNH.js → chunk-PDG4XWO2.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-5LWUUXMH.js → chunk-PY4ZQHTN.js} +1 -1
  30. package/dist/bundle/chunks/chunk-Q2A2V6NT.js +5 -0
  31. package/dist/bundle/chunks/{chunk-SOXQ42VS.js → chunk-RQ5JTXUC.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-JPGT64RV.js → chunk-TV3UI63P.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-JFQUWEN7.js → chunk-TWHF52G6.js} +4 -4
  34. package/dist/bundle/chunks/{chunk-6U3TBFBA.js → chunk-UJPG4ROT.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-UDM7VO6O.js → chunk-XM36IFP3.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-XQ45JMGN.js → chunk-Z6BREXV3.js} +1 -1
  37. package/dist/bundle/chunks/chunk-ZAS4NBVK.js +410 -0
  38. package/dist/bundle/chunks/{chunk-ADQAPDPV.js → chunk-ZP6JJDYV.js} +1 -1
  39. package/dist/bundle/chunks/{chunk-EZP6AYW2.js → chunk-ZXIAVLJV.js} +88 -88
  40. package/dist/bundle/chunks/{cli-main-55EQA5PS.js → cli-main-36FRZ7ZB.js} +1 -1
  41. package/dist/bundle/chunks/client-CBDPMWZJ.js +2 -0
  42. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-ZJQGIIRC.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
  43. package/dist/bundle/chunks/cursor-agent.js +1 -1
  44. package/dist/bundle/chunks/devin-agent.js +1 -1
  45. package/dist/bundle/chunks/{dispatch-C2IBP5VV.js → dispatch-W5PENPZM.js} +3 -3
  46. package/dist/bundle/chunks/{google-generative-ai-5G4RIQA6.js → google-generative-ai-FHYFXKLA.js} +1 -1
  47. package/dist/bundle/chunks/{google-vertex-ALD4P27O.js → google-vertex-5DWE5U5K.js} +1 -1
  48. package/dist/bundle/chunks/{help-fast-path-PEIKGCKZ.js → help-fast-path-FETLIF3W.js} +1 -1
  49. package/dist/bundle/chunks/{host-command-657VCQ34.js → host-command-XEB7SHT3.js} +1 -1
  50. package/dist/bundle/chunks/{host-session-command-OV5XDOJK.js → host-session-command-FFN52LXJ.js} +1 -1
  51. package/dist/bundle/chunks/{interactive-mode-MLXSYK2H.js → interactive-mode-5S7NNV2Z.js} +1 -1
  52. package/dist/bundle/chunks/{mistral-conversations-RAE4K2XY.js → mistral-conversations-I2PI7DDF.js} +1 -1
  53. package/dist/bundle/chunks/{multi-session-host-774MACJU.js → multi-session-host-E3OPE6HJ.js} +3 -3
  54. package/dist/bundle/chunks/{openai-codex-responses-3CW4R7TG.js → openai-codex-responses-WSEX6SH7.js} +1 -1
  55. package/dist/bundle/chunks/{openai-completions-5UIJXQUF.js → openai-completions-YCBHTBSS.js} +1 -1
  56. package/dist/bundle/chunks/{openai-responses-2BO44MRW.js → openai-responses-GXNSQF4A.js} +1 -1
  57. package/dist/bundle/chunks/{package-manager-cli-MZHVCX2S.js → package-manager-cli-3XO456VE.js} +1 -1
  58. package/dist/bundle/chunks/{pi-messages-RF6GER4R.js → pi-messages-O6GISIRW.js} +1 -1
  59. package/dist/bundle/chunks/protocol-GLNWYI5G.js +2 -0
  60. package/dist/bundle/chunks/{role-ETSFSDTS.js → role-SHIZQKK4.js} +1 -1
  61. package/dist/bundle/chunks/{rotation-stream-ZMQB4GPO.js → rotation-stream-2PCZWNKF.js} +1 -1
  62. package/dist/bundle/chunks/{rpc-mode-UJXOIZCW.js → rpc-mode-GLWKLTMA.js} +1 -1
  63. package/dist/bundle/chunks/{schedule-command-KA7LFFNK.js → schedule-command-B7RRSANU.js} +1 -1
  64. package/dist/bundle/chunks/{session-control-endpoint-MFWXUR7Z.js → session-control-endpoint-KLE2X4RR.js} +1 -1
  65. package/dist/bundle/chunks/{session-picker-BP33NRSL.js → session-picker-DTGBOXDR.js} +1 -1
  66. package/dist/bundle/chunks/session-worker.js +131 -131
  67. package/dist/bundle/chunks/{typesafe-system-one-GNNZ5O5U.js → typesafe-system-one-XGYJJVSE.js} +1 -1
  68. package/dist/bundle/cli.js +2 -2
  69. package/dist/bundle/index.js +1 -1
  70. package/dist/bundle/rpc-entry.js +1 -1
  71. package/dist/bundle/runtime-manifest.json +1 -1
  72. package/dist/core/agent-session.d.ts +11 -1
  73. package/dist/core/agent-session.js +19 -2
  74. package/dist/core/extensions/builtin/anthropic-subscription/account-context.d.ts +15 -0
  75. package/dist/core/extensions/builtin/anthropic-subscription/account-context.js +53 -0
  76. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.d.ts +3 -0
  77. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +26 -9
  78. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.d.ts +16 -0
  79. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.js +27 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts +2 -13
  81. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -26
  82. package/dist/core/extensions/builtin/anthropic-subscription/errors.js +3 -2
  83. package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +6 -1
  84. package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +1 -1
  85. package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +13 -2
  86. package/dist/core/extensions/builtin/compaction/index.js +7 -1
  87. package/dist/core/extensions/builtin/herdr/index.js +9 -5
  88. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
  89. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
  90. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
  91. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
  92. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
  93. package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
  94. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +1 -0
  95. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
  96. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
  97. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
  98. package/dist/core/extensions/builtin/mcp/modern/request.js +6 -2
  99. package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
  100. package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
  101. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +2 -0
  102. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +2 -0
  103. package/dist/core/extensions/builtin/mcp/transport-sdk.js +12 -1
  104. package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
  105. package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
  106. package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
  107. package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
  108. package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
  109. package/dist/core/extensions/types.d.ts +2 -0
  110. package/dist/core/lifecycle-children.d.ts +3 -1
  111. package/dist/core/lifecycle-children.js +79 -15
  112. package/dist/core/lifecycle-record.js +1 -24
  113. package/dist/core/lifecycle-session-binding.js +2 -1
  114. package/dist/core/lifecycle-wait-items.d.ts +17 -0
  115. package/dist/core/lifecycle-wait-items.js +72 -0
  116. package/dist/core/model-runtime.js +6 -1
  117. package/dist/core/provider-api-key-auth.d.ts +6 -1
  118. package/dist/core/provider-api-key-auth.js +11 -4
  119. package/dist/core/provider-composer.d.ts +8 -1
  120. package/dist/core/provider-composer.js +7 -3
  121. package/dist/core/sdk.js +4 -1
  122. package/dist/core/session-activity.js +2 -0
  123. package/dist/core/session-lifecycle-types.d.ts +20 -4
  124. package/dist/core/session-lifecycle.js +35 -4
  125. package/dist/core/structured-session-lane.d.ts +33 -0
  126. package/dist/core/structured-session-lane.js +62 -0
  127. package/dist/core/work-custody-record.d.ts +2 -1
  128. package/dist/core/work-custody-record.js +2 -0
  129. package/dist/core/work-obligations.d.ts +1 -1
  130. package/dist/core/work-obligations.js +8 -2
  131. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  132. package/dist/modes/interactive/interactive-mode.js +2 -1
  133. package/dist/modes/json-event.d.ts +79 -1
  134. package/dist/modes/json-event.js +79 -1
  135. package/dist/modes/rpc/compact-commit-dependencies.d.ts +13 -0
  136. package/dist/modes/rpc/compact-commit-dependencies.js +48 -0
  137. package/dist/modes/rpc/compact-content-index.d.ts +84 -0
  138. package/dist/modes/rpc/compact-content-index.js +243 -0
  139. package/dist/modes/rpc/compact-content-store.d.ts +10 -43
  140. package/dist/modes/rpc/compact-content-store.js +61 -163
  141. package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
  142. package/dist/modes/rpc/compact-index-pages.js +31 -0
  143. package/dist/modes/rpc/compact-read-lease.d.ts +15 -0
  144. package/dist/modes/rpc/compact-read-lease.js +40 -0
  145. package/dist/modes/rpc/compact-session-state.d.ts +14 -63
  146. package/dist/modes/rpc/compact-session-state.js +69 -51
  147. package/dist/modes/rpc/compact-spool-dir.d.ts +4 -1
  148. package/dist/modes/rpc/compact-spool-dir.js +41 -8
  149. package/dist/modes/rpc/compact-spool-storage.d.ts +58 -0
  150. package/dist/modes/rpc/compact-spool-storage.js +195 -0
  151. package/dist/modes/rpc/compact-storage-io.d.ts +2 -1
  152. package/dist/modes/rpc/compact-storage-io.js +7 -1
  153. package/dist/modes/rpc/compact-text-roots.d.ts +22 -0
  154. package/dist/modes/rpc/compact-text-roots.js +138 -0
  155. package/dist/modes/rpc/compact-text-view.d.ts +22 -0
  156. package/dist/modes/rpc/compact-text-view.js +16 -0
  157. package/dist/modes/rpc/compact-transition.d.ts +41 -0
  158. package/dist/modes/rpc/compact-transition.js +12 -0
  159. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  160. package/dist/modes/rpc/connection-handler.js +12 -6
  161. package/dist/modes/rpc/host-ensure-start.js +5 -3
  162. package/dist/modes/rpc/security-presentations.js +2 -1
  163. package/dist/modes/rpc/session-binding.js +10 -0
  164. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  165. package/dist/modes/rpc/session-event-writer.js +2 -0
  166. package/dist/modes/rpc/session-record-stream.d.ts +3 -3
  167. package/dist/modes/rpc/session-record-stream.js +3 -3
  168. package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
  169. package/dist/modes/rpc/session-structured-lanes.js +24 -0
  170. package/dist/modes/rpc/session-worker-client.js +6 -5
  171. package/dist/modes/rpc/structured-session-records.d.ts +12 -0
  172. package/dist/modes/rpc/structured-session-records.js +88 -0
  173. package/docs/extensions.md +7 -1
  174. package/docs/rpc.md +17 -2
  175. package/package.json +6 -6
  176. package/dist/bundle/chunks/chunk-GQJYNFMO.js +0 -2
  177. package/dist/bundle/chunks/chunk-VICDRG3J.js +0 -410
  178. package/dist/bundle/chunks/chunk-VL7O5PO4.js +0 -5
  179. package/dist/bundle/chunks/client-LOS6GSEW.js +0 -2
@@ -16,6 +16,7 @@
16
16
  * `AgentSession.isSessionBusy` instead of testing individual flags, so a source
17
17
  * added later cannot be silently missed by one call site.
18
18
  */
19
+ import { normalizeWaitItems, wakeSourceItems } from "./lifecycle-wait-items.js";
19
20
  /** Live counts of extension-published wake sources, keyed by source name. */
20
21
  export class WakeSourceTracker {
21
22
  constructor() {
@@ -39,6 +40,7 @@ export class WakeSourceTracker {
39
40
  this.counts.set(source, {
40
41
  activeCount,
41
42
  kind: "kind" in data && data.kind === "children" ? "children" : "background",
43
+ ...normalizeWaitItems(wakeSourceItems(data)),
42
44
  });
43
45
  else
44
46
  this.counts.delete(source);
@@ -9,13 +9,26 @@ export interface HumanWait {
9
9
  readonly childLabel?: string;
10
10
  readonly since: string;
11
11
  }
12
+ export interface LifecycleWaitItem {
13
+ readonly id: string;
14
+ readonly label?: string;
15
+ readonly startedAt?: string;
16
+ }
17
+ export interface LifecycleWaitSource {
18
+ readonly source: string;
19
+ readonly activeCount: number;
20
+ readonly items?: readonly LifecycleWaitItem[];
21
+ readonly omittedItems?: number;
22
+ }
12
23
  export interface LifecycleWaits {
13
24
  readonly children: number;
14
- readonly background: readonly {
15
- readonly source: string;
16
- readonly activeCount: number;
25
+ /** Named detail can be incomplete even without truncation: custody need not have a named report. */
26
+ readonly childSources?: readonly LifecycleWaitSource[];
27
+ readonly childReports?: readonly Pick<ChildLifecycleReport, "taskId" | "childSessionId" | "runEpoch" | "state" | "label" | "since">[];
28
+ readonly childReportsOmitted?: number;
29
+ readonly background: readonly (LifecycleWaitSource & {
17
30
  readonly retryAtMs?: number;
18
- }[];
31
+ })[];
19
32
  readonly human: readonly HumanWait[];
20
33
  }
21
34
  export interface LifecycleHold {
@@ -83,6 +96,9 @@ export interface ChildLifecycleReport {
83
96
  readonly runEpoch: number;
84
97
  readonly generation: number;
85
98
  readonly state: SessionLifecycleState;
99
+ readonly label?: string;
100
+ /** The child's own state-entry time, not the parent's evaluation time. */
101
+ readonly since?: string;
86
102
  readonly waitingOn?: LifecycleWaits;
87
103
  readonly terminal?: "completed" | "error" | "lost" | "interrupted" | "cancelled";
88
104
  }
@@ -1,6 +1,7 @@
1
1
  import { effectiveWakes } from "./extensions/builtin/goal/turn-end-liveness.js";
2
2
  import { LifecycleChildren } from "./lifecycle-children.js";
3
3
  import { LifecycleLedger } from "./lifecycle-ledger.js";
4
+ import { projectWaitItems } from "./lifecycle-wait-items.js";
4
5
  import { sameStop, stopDetail } from "./lifecycle-stop.js";
5
6
  import { lifecycleState, lifecycleTerminal } from "./session-lifecycle-reducer.js";
6
7
  import { SessionWorkObligations } from "./work-obligations.js";
@@ -189,11 +190,34 @@ export class SessionLifecycleController {
189
190
  evaluate() {
190
191
  const runtime = this.options.readRuntime();
191
192
  const active = this.obligations.active;
192
- const childRecords = this.children.reports.filter((report) => report.childSessionId !== this.options.sessionId && report.terminal === undefined).length;
193
+ const childRecords = this.children.reports
194
+ .filter((report) => report.childSessionId !== this.options.sessionId && report.terminal === undefined)
195
+ .sort((a, b) => (a.taskId < b.taskId ? -1 : a.taskId > b.taskId ? 1 : 0));
196
+ const childReports = childRecords
197
+ .slice(0, 32)
198
+ .map(({ taskId, childSessionId, runEpoch, state, label, since }) => ({
199
+ taskId,
200
+ childSessionId,
201
+ runEpoch,
202
+ state,
203
+ ...(label === undefined ? {} : { label }),
204
+ ...(since === undefined ? {} : { since }),
205
+ }));
206
+ const childSources = active
207
+ .filter((record) => record.kind === "children")
208
+ .map((record) => ({
209
+ source: record.source ?? record.key,
210
+ activeCount: record.activeCount ?? 1,
211
+ ...projectWaitItems(record),
212
+ }));
193
213
  const background = active
194
214
  .filter((record) => record.kind === "background")
195
- .map((record) => ({ source: record.source ?? record.key, activeCount: record.activeCount ?? 1 }));
196
- const children = Math.max(childRecords, active
215
+ .map((record) => ({
216
+ source: record.source ?? record.key,
217
+ activeCount: record.activeCount ?? 1,
218
+ ...projectWaitItems(record),
219
+ }));
220
+ const children = Math.max(childRecords.length, active
197
221
  .filter((record) => record.kind === "children")
198
222
  .reduce((sum, record) => sum + (record.activeCount ?? 1), 0));
199
223
  const input = this.liveness;
@@ -297,7 +321,14 @@ export class SessionLifecycleController {
297
321
  runEpoch: this.ownTask.runEpoch,
298
322
  }
299
323
  : undefined;
300
- const waits = { children, background, human };
324
+ const waits = {
325
+ children,
326
+ background,
327
+ human,
328
+ ...(childSources.length ? { childSources } : {}),
329
+ ...(childReports.length ? { childReports } : {}),
330
+ ...(childRecords.length > 32 ? { childReportsOmitted: childRecords.length - 32 } : {}),
331
+ };
301
332
  const lastStop = this.lastStop(input, synthetic.some((wait) => wait.kind === "stalled"));
302
333
  const state = lifecycleState({
303
334
  role: this.ledger.snapshot.role,
@@ -0,0 +1,33 @@
1
+ import type { InternalAgentChannel } from "@earendil-works/pi-agent-core";
2
+ import { type AssistantProgress } from "@earendil-works/pi-ai";
3
+ import type { AgentSessionEvent } from "./agent-session.ts";
4
+ /** One record of a session's internal structured lane: a session event or a block control, in session order. */
5
+ export type AgentSessionStructuredRecord = AgentSessionEvent | AssistantProgress;
6
+ type StructuredListener = (record: AgentSessionStructuredRecord) => void;
7
+ export declare class StructuredLaneUnavailableError extends Error {
8
+ constructor();
9
+ }
10
+ /**
11
+ * The AgentSession end of the internal-only agent channel (senpi#4272, R3.C6.2). While a structured listener is
12
+ * attached, it holds the channel's capture subscription; block controls enter the session's own event queue, so they
13
+ * keep their position among the ordinary events. Extension listeners, `session.subscribe` and every legacy
14
+ * destination never see a control.
15
+ *
16
+ * The capture subscription is module-wide (`subscribeAssistantCapture`): while it is held, every capture-enabled
17
+ * provider stream in the process that first pushes afterwards builds capture records, including streams of sessions
18
+ * with no structured listener, which drop them at their public iterator.
19
+ */
20
+ export declare class StructuredSessionLane {
21
+ #private;
22
+ private readonly listeners;
23
+ private releaseChannel;
24
+ constructor(channel: InternalAgentChannel | undefined, enqueue: (deliver: () => void) => void);
25
+ get active(): boolean;
26
+ subscribe(listener: StructuredListener): () => void;
27
+ /** Publishes a session event with the provider capture of its `source` agent event. */
28
+ emitEvent(event: AgentSessionEvent, source: object): void;
29
+ dispose(): void;
30
+ private emit;
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=structured-session-lane.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { carryAssistantCapture } from "@earendil-works/pi-ai";
2
+ export class StructuredLaneUnavailableError extends Error {
3
+ constructor() {
4
+ super("Structured lane unavailable: Agent was constructed outside the session layer");
5
+ this.name = "StructuredLaneUnavailableError";
6
+ }
7
+ }
8
+ /**
9
+ * The AgentSession end of the internal-only agent channel (senpi#4272, R3.C6.2). While a structured listener is
10
+ * attached, it holds the channel's capture subscription; block controls enter the session's own event queue, so they
11
+ * keep their position among the ordinary events. Extension listeners, `session.subscribe` and every legacy
12
+ * destination never see a control.
13
+ *
14
+ * The capture subscription is module-wide (`subscribeAssistantCapture`): while it is held, every capture-enabled
15
+ * provider stream in the process that first pushes afterwards builds capture records, including streams of sessions
16
+ * with no structured listener, which drop them at their public iterator.
17
+ */
18
+ export class StructuredSessionLane {
19
+ #channel;
20
+ #enqueue;
21
+ constructor(channel, enqueue) {
22
+ this.listeners = new Set();
23
+ this.#channel = channel;
24
+ this.#enqueue = enqueue;
25
+ }
26
+ get active() {
27
+ return this.listeners.size > 0;
28
+ }
29
+ subscribe(listener) {
30
+ if (!this.#channel)
31
+ throw new StructuredLaneUnavailableError();
32
+ this.listeners.add(listener);
33
+ this.releaseChannel ??= this.#channel.subscribe((record) => {
34
+ if ("progress" in record)
35
+ this.#enqueue(() => this.emit(record.progress));
36
+ });
37
+ return () => {
38
+ this.listeners.delete(listener);
39
+ if (this.listeners.size === 0)
40
+ this.dispose();
41
+ };
42
+ }
43
+ /** Publishes a session event with the provider capture of its `source` agent event. */
44
+ emitEvent(event, source) {
45
+ const message = (event.type === "message_start" || event.type === "message_end") && event.message.role === "assistant"
46
+ ? event.message
47
+ : undefined;
48
+ // A user-facing description replaced the engine message, so the capture's frozen message must follow it.
49
+ const described = message && "message" in source && source.message !== message ? message : undefined;
50
+ this.emit(carryAssistantCapture(source, event, described));
51
+ }
52
+ dispose() {
53
+ this.listeners.clear();
54
+ this.releaseChannel?.();
55
+ this.releaseChannel = undefined;
56
+ }
57
+ emit(record) {
58
+ for (const listener of [...this.listeners])
59
+ listener(record);
60
+ }
61
+ }
62
+ //# sourceMappingURL=structured-session-lane.js.map
@@ -1,4 +1,5 @@
1
1
  import type { CustomMessage } from "./messages.ts";
2
+ import { type WaitSourceItems } from "./lifecycle-wait-items.ts";
2
3
  import type { ObligationKind } from "./session-lifecycle-types.ts";
3
4
  export interface CustodyPayload {
4
5
  readonly message: Pick<CustomMessage, "customType" | "content" | "display" | "details">;
@@ -7,7 +8,7 @@ export interface CustodyPayload {
7
8
  readonly itemIds?: readonly string[];
8
9
  }
9
10
  /** Mutable only by the registry; every replacement is appended before installation. */
10
- export interface WorkCustodyRecord {
11
+ export interface WorkCustodyRecord extends WaitSourceItems {
11
12
  readonly id: string;
12
13
  readonly key: string;
13
14
  readonly kind: ObligationKind;
@@ -1,3 +1,4 @@
1
+ import { normalizeWaitItems } from "./lifecycle-wait-items.js";
1
2
  export function isRecord(value) {
2
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
3
4
  }
@@ -25,6 +26,7 @@ export function readWorkCustody(value) {
25
26
  key: value.key,
26
27
  kind: value.kind,
27
28
  state: value.state,
29
+ ...normalizeWaitItems(value.items, value.omittedItems),
28
30
  ...(typeof value.source === "string" ? { source: value.source } : {}),
29
31
  ...(typeof value.activeCount === "number" ? { activeCount: value.activeCount } : {}),
30
32
  ...(typeof value.detail === "string" ? { detail: value.detail } : {}),
@@ -36,7 +36,7 @@ export declare class SessionWorkObligations implements WorkObligations {
36
36
  get activeCount(): number;
37
37
  get persisted(): boolean;
38
38
  open(key: string, kind: ObligationKind, detail?: string): ObligationToken;
39
- syncSource(source: string, kind: ObligationKind, activeCount: number): void;
39
+ syncSource(source: string, kind: ObligationKind, activeCount: number, items?: unknown): void;
40
40
  /** After a restart, a restored wake source stays open only if its producer published again. */
41
41
  retireRestoredSources(): void;
42
42
  /**
@@ -1,3 +1,4 @@
1
+ import { normalizeWaitItems, waitItemSignature } from "./lifecycle-wait-items.js";
1
2
  import { custodyIsOpen, readWorkCustody } from "./work-custody-record.js";
2
3
  /** Custody keys the engine opens itself (senpi#3986): queued deliveries and published wake sources. */
3
4
  export const ENGINE_DELIVERY_KEY_PREFIX = "delivery:";
@@ -59,7 +60,7 @@ export class SessionWorkObligations {
59
60
  this.store.changed();
60
61
  return this.token(record.id);
61
62
  }
62
- syncSource(source, kind, activeCount) {
63
+ syncSource(source, kind, activeCount, items) {
63
64
  const key = `${WAKE_SOURCE_KEY_PREFIX}${source}`;
64
65
  this.publishedSources.add(key);
65
66
  const current = this.active.find((record) => record.key === key);
@@ -68,7 +69,10 @@ export class SessionWorkObligations {
68
69
  this.close(current.id, "passive");
69
70
  return;
70
71
  }
71
- if (current?.activeCount === activeCount && current.kind === kind)
72
+ const detail = normalizeWaitItems(items);
73
+ if (current?.activeCount === activeCount &&
74
+ current.kind === kind &&
75
+ waitItemSignature(current) === waitItemSignature(detail))
72
76
  return;
73
77
  this.install({
74
78
  ...current,
@@ -77,6 +81,8 @@ export class SessionWorkObligations {
77
81
  source,
78
82
  kind,
79
83
  activeCount,
84
+ items: detail.items,
85
+ omittedItems: detail.omittedItems,
80
86
  state: current?.state ?? "open",
81
87
  });
82
88
  this.store.changed();
@@ -329,7 +329,8 @@ export declare class InteractiveMode {
329
329
  */
330
330
  private unsubscribeSecurityNotices?;
331
331
  private detachMcpGrantPresentation?;
332
- private mcpMrtrPresentationLifetime;
332
+ /** A presentation is optional until extension UI is created; shutdown may precede it. */
333
+ private mcpMrtrPresentationLifetime?;
333
334
  private mcpLocalPresenter?;
334
335
  private bindCurrentSessionExtensions;
335
336
  private applyFullscreenScrollbarSetting;
@@ -691,7 +691,6 @@ export class InteractiveMode {
691
691
  this.onRightClickPaste = () => {
692
692
  void this.handleRightClickPaste();
693
693
  };
694
- this.mcpMrtrPresentationLifetime = new AbortController();
695
694
  /**
696
695
  * Gracefully shutdown the agent.
697
696
  * Stops the TUI before emitting shutdown events so extension UI cleanup cannot
@@ -2859,6 +2858,8 @@ export class InteractiveMode {
2859
2858
  }
2860
2859
  createExtensionUIContext() {
2861
2860
  this.mcpMrtrPresentationLifetime ??= new AbortController();
2861
+ if (this.isShuttingDown)
2862
+ this.mcpMrtrPresentationLifetime.abort();
2862
2863
  const context = {
2863
2864
  select: (title, options, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionSelector(title, options, opts), opts),
2864
2865
  confirm: async (title, message, opts) => (await this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionConfirm(title, message, opts), opts)) ?? false,
@@ -1,4 +1,4 @@
1
- import type { Usage } from "@earendil-works/pi-ai";
1
+ import { type AssistantMessage, getAssistantCapture, type TextContent, type ThinkingContent, type ToolCall, type Usage } from "@earendil-works/pi-ai";
2
2
  import type { AgentSessionEvent } from "../core/agent-session.ts";
3
3
  type WithoutPartial<T> = T extends {
4
4
  partial: unknown;
@@ -13,6 +13,7 @@ type ToJsonAssistantMessageEvent<T> = T extends {
13
13
  type MessageUpdateEvent = Extract<AgentSessionEvent, {
14
14
  type: "message_update";
15
15
  }>;
16
+ type AssistantCapture = NonNullable<ReturnType<typeof getAssistantCapture>>;
16
17
  type JsonMessageUpdateEvent = {
17
18
  type: "message_update";
18
19
  usage: Usage;
@@ -23,6 +24,82 @@ type JsonMessageUpdateEvent = {
23
24
  export type JsonAgentSessionEvent = Exclude<AgentSessionEvent, {
24
25
  type: "message_update";
25
26
  }> | JsonMessageUpdateEvent;
27
+ /** The engine-staged block-seed projection (senpi#4272, R3.C6.2); no public destination selects it before S17. */
28
+ export interface BlockSeedOptions {
29
+ readonly blockSeeds: 1;
30
+ }
31
+ type SeededAssistantEvent = {
32
+ type: "start";
33
+ seed: {
34
+ message: AssistantMessage;
35
+ };
36
+ } | {
37
+ type: "text_start";
38
+ contentIndex: number;
39
+ seed: {
40
+ block: TextContent;
41
+ coveredCodeUnits: number;
42
+ };
43
+ } | {
44
+ type: "thinking_start";
45
+ contentIndex: number;
46
+ seed: {
47
+ block: ThinkingContent;
48
+ coveredCodeUnits: number;
49
+ };
50
+ } | {
51
+ type: "toolcall_start";
52
+ contentIndex: number;
53
+ id: string;
54
+ toolName: string;
55
+ seed: {
56
+ block: ToolCall;
57
+ rawArgumentPrefix: string;
58
+ parsedThrough: number;
59
+ coveredCodeUnits: number;
60
+ };
61
+ } | {
62
+ type: "text_delta" | "thinking_delta";
63
+ contentIndex: number;
64
+ delta: string;
65
+ offset: number;
66
+ } | {
67
+ type: "toolcall_delta";
68
+ contentIndex: number;
69
+ delta: string;
70
+ offset: number;
71
+ parsedThrough: number;
72
+ } | {
73
+ type: "text_end";
74
+ contentIndex: number;
75
+ content: string;
76
+ block: TextContent;
77
+ } | {
78
+ type: "thinking_end";
79
+ contentIndex: number;
80
+ content: string;
81
+ block: ThinkingContent;
82
+ } | {
83
+ type: "toolcall_end";
84
+ contentIndex: number;
85
+ toolCall: ToolCall;
86
+ } | {
87
+ type: "done";
88
+ reason: string;
89
+ message: AssistantMessage;
90
+ } | {
91
+ type: "error";
92
+ reason: string;
93
+ error: AssistantMessage;
94
+ };
95
+ export type SeededJsonMessageUpdateEvent = Omit<MessageUpdateEvent, "message" | "assistantMessageEvent"> & {
96
+ usage: Usage;
97
+ messageMetadata?: AssistantCapture["metadata"];
98
+ assistantMessageEvent: SeededAssistantEvent;
99
+ };
100
+ export type SeededJsonAgentSessionEvent = Exclude<AgentSessionEvent, {
101
+ type: "message_update";
102
+ }> | SeededJsonMessageUpdateEvent;
26
103
  /**
27
104
  * Remove cumulative assistant snapshots from streaming wire events.
28
105
  * `message_start` provides the initial message, deltas build it, and
@@ -31,5 +108,6 @@ export type JsonAgentSessionEvent = Exclude<AgentSessionEvent, {
31
108
  */
32
109
  export declare function toJsonEvent(event: MessageUpdateEvent): JsonMessageUpdateEvent;
33
110
  export declare function toJsonEvent(event: AgentSessionEvent): JsonAgentSessionEvent;
111
+ export declare function toJsonEvent(event: AgentSessionEvent, options: BlockSeedOptions): SeededJsonAgentSessionEvent | undefined;
34
112
  export {};
35
113
  //# sourceMappingURL=json-event.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { getAssistantCapture, } from "@earendil-works/pi-ai";
1
2
  function toJsonAssistantMessageEvent(event) {
2
3
  if (event.type === "toolcall_start") {
3
4
  const toolCall = event.partial.content[event.contentIndex];
@@ -13,7 +14,84 @@ function toJsonAssistantMessageEvent(event) {
13
14
  const { partial: _partial, ...deltaEvent } = event;
14
15
  return deltaEvent;
15
16
  }
16
- export function toJsonEvent(event) {
17
+ /**
18
+ * Builds a seeded assistant event from the producer-time capture only: an ordinary delta reads its index, its
19
+ * delta string and the capture, never the shared partial, the block or the parsed arguments.
20
+ */
21
+ function toSeededAssistantEvent(event, capture) {
22
+ if (event.type === "start")
23
+ return capture.message && { type: "start", seed: { message: capture.message } };
24
+ if (event.type === "done")
25
+ return { type: "done", reason: event.reason, message: event.message };
26
+ if (event.type === "error")
27
+ return { type: "error", reason: event.reason, error: event.error };
28
+ const contentIndex = capture.contentIndex ?? event.contentIndex;
29
+ if (event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta") {
30
+ const { offset, parsedThrough } = capture;
31
+ if (offset === undefined)
32
+ return undefined;
33
+ if (event.type !== "toolcall_delta")
34
+ return { type: event.type, contentIndex, delta: event.delta, offset };
35
+ if (parsedThrough === undefined)
36
+ return undefined;
37
+ return { type: event.type, contentIndex, delta: event.delta, offset, parsedThrough };
38
+ }
39
+ const { block, coveredCodeUnits } = capture;
40
+ if (event.type === "toolcall_end")
41
+ return block?.type === "toolCall" ? { type: event.type, contentIndex, toolCall: block } : undefined;
42
+ if (event.type === "text_end")
43
+ return block?.type === "text" ? { type: event.type, contentIndex, content: event.content, block } : undefined;
44
+ if (event.type === "thinking_end")
45
+ return block?.type === "thinking" ? { type: event.type, contentIndex, content: event.content, block } : undefined;
46
+ if (coveredCodeUnits === undefined)
47
+ return undefined;
48
+ if (event.type === "text_start")
49
+ return block?.type === "text" ? { type: event.type, contentIndex, seed: { block, coveredCodeUnits } } : undefined;
50
+ if (event.type === "thinking_start")
51
+ return block?.type === "thinking"
52
+ ? { type: event.type, contentIndex, seed: { block, coveredCodeUnits } }
53
+ : undefined;
54
+ const { rawArgumentPrefix, parsedThrough } = capture;
55
+ if (block?.type !== "toolCall" || rawArgumentPrefix === undefined || parsedThrough === undefined)
56
+ return undefined;
57
+ return {
58
+ type: event.type,
59
+ contentIndex,
60
+ id: block.id,
61
+ toolName: block.name,
62
+ seed: { block, rawArgumentPrefix, parsedThrough, coveredCodeUnits },
63
+ };
64
+ }
65
+ /**
66
+ * An assistant record with no capture has no seeded form (`undefined`): its producer captured nothing, so any
67
+ * seed would be read from the live partial and could already hold later bytes. Identity is never invented.
68
+ */
69
+ function toSeededJsonEvent(event) {
70
+ if ((event.type === "message_start" || event.type === "message_end") && event.message.role === "assistant") {
71
+ const message = getAssistantCapture(event)?.message;
72
+ if (message)
73
+ return { ...event, message };
74
+ // The terminal message is complete when emitted; only an uncaptured start may already hold later bytes.
75
+ return event.type === "message_end" ? event : undefined;
76
+ }
77
+ if (event.type !== "message_update")
78
+ return event;
79
+ const capture = getAssistantCapture(event);
80
+ const assistantMessageEvent = capture && toSeededAssistantEvent(event.assistantMessageEvent, capture);
81
+ if (!capture || !assistantMessageEvent)
82
+ return undefined;
83
+ const { message: _message, assistantMessageEvent: _event, ...outer } = event;
84
+ const changed = capture.metadata !== undefined && Object.keys(capture.metadata).length > 0;
85
+ return {
86
+ ...outer,
87
+ usage: capture.usage,
88
+ ...(changed ? { messageMetadata: capture.metadata } : {}),
89
+ assistantMessageEvent,
90
+ };
91
+ }
92
+ export function toJsonEvent(event, options) {
93
+ if (options)
94
+ return toSeededJsonEvent(event);
17
95
  if (event.type !== "message_update") {
18
96
  return event;
19
97
  }
@@ -0,0 +1,13 @@
1
+ import type { CompactContentStore, LeafId } from "./compact-content-store.ts";
2
+ /** Per-owner readiness, independent of counted references: one leaf can unblock many rows/trees. */
3
+ export declare class CompactCommitDependencies<Key> {
4
+ private readonly pending;
5
+ private readonly dependants;
6
+ private readonly store;
7
+ constructor(store: CompactContentStore, ready: (key: Key) => void);
8
+ get size(): number;
9
+ has(key: Key): boolean;
10
+ set(key: Key, refs: readonly LeafId[]): void;
11
+ delete(key: Key): void;
12
+ }
13
+ //# sourceMappingURL=compact-commit-dependencies.d.ts.map
@@ -0,0 +1,48 @@
1
+ /** Per-owner readiness, independent of counted references: one leaf can unblock many rows/trees. */
2
+ export class CompactCommitDependencies {
3
+ constructor(store, ready) {
4
+ this.pending = new Map();
5
+ this.dependants = new Map();
6
+ this.store = store;
7
+ store.subscribeCommitted((id) => {
8
+ const keys = this.dependants.get(id);
9
+ this.dependants.delete(id);
10
+ for (const key of keys ?? []) {
11
+ const pending = this.pending.get(key);
12
+ pending?.delete(id);
13
+ if (pending?.size === 0) {
14
+ this.pending.delete(key);
15
+ ready(key);
16
+ }
17
+ }
18
+ });
19
+ }
20
+ get size() {
21
+ return this.pending.size;
22
+ }
23
+ has(key) {
24
+ return this.pending.has(key);
25
+ }
26
+ set(key, refs) {
27
+ this.delete(key);
28
+ const pending = new Set(refs.filter((id) => !this.store.isCommitted(id)));
29
+ if (pending.size === 0)
30
+ return;
31
+ this.pending.set(key, pending);
32
+ for (const id of pending) {
33
+ const keys = this.dependants.get(id) ?? new Set();
34
+ keys.add(key);
35
+ this.dependants.set(id, keys);
36
+ }
37
+ }
38
+ delete(key) {
39
+ for (const id of this.pending.get(key) ?? []) {
40
+ const keys = this.dependants.get(id);
41
+ keys?.delete(key);
42
+ if (keys?.size === 0)
43
+ this.dependants.delete(id);
44
+ }
45
+ this.pending.delete(key);
46
+ }
47
+ }
48
+ //# sourceMappingURL=compact-commit-dependencies.js.map
@@ -0,0 +1,84 @@
1
+ import type { CompactContentStore, LeafId } from "./compact-content-store.ts";
2
+ /**
3
+ * A spillable keyed collection: an immutable paged disk base plus one resident override slot per key
4
+ * (senpi#4272). Updates coalesce into that slot, so N updates to one key hold one slot, and a cold row
5
+ * changes without reading its page. Background compaction folds eligible overrides into new pages and
6
+ * publishes them in one synchronous swap that never removes an override newer than the one it read.
7
+ * A failed compaction leaves the previous base in place and the next one free to run.
8
+ * Pages are internal storage only; nothing here is a wire record.
9
+ */
10
+ type Override<Row> = {
11
+ readonly kind: "row";
12
+ readonly row: Row;
13
+ } | {
14
+ readonly kind: "patch";
15
+ readonly patch: Partial<Row>;
16
+ } | {
17
+ readonly kind: "tombstone";
18
+ };
19
+ interface Page {
20
+ readonly path: string;
21
+ readonly bytes: number;
22
+ /** Published keys; a row skipped at publication stays in the file but is never read back. */
23
+ readonly keys: readonly string[];
24
+ readonly leaves: readonly LeafId[];
25
+ inBase: boolean;
26
+ pins: number;
27
+ }
28
+ interface IndexBase {
29
+ readonly pages: readonly Page[];
30
+ readonly pageOf: ReadonlyMap<string, Page>;
31
+ }
32
+ export interface FrozenIndex<Row> {
33
+ readonly base: IndexBase;
34
+ readonly overrides: ReadonlyMap<string, Override<Row>>;
35
+ }
36
+ export declare class CompactContentIndex<Row extends object> {
37
+ private readonly store;
38
+ private readonly leavesOf;
39
+ private readonly compactAboveRows;
40
+ private base;
41
+ private readonly overrides;
42
+ /** Uncommitted leaves per row override; only overrides absent here are eligible to fold. */
43
+ private readonly unsettledLeaves;
44
+ private compacting;
45
+ private tombstones;
46
+ private tombstoneKeyUnits;
47
+ private readonly frozen;
48
+ constructor(store: CompactContentStore, leavesOf: (row: Row) => readonly LeafId[], compactAboveRows: number);
49
+ has(key: string): boolean;
50
+ set(key: string, row: Row): void;
51
+ patch(key: string, fields: Partial<Row>): void;
52
+ /** A key only the overlay ever held leaves no tombstone; a committed one is masked until compaction. */
53
+ delete(key: string): void;
54
+ freeze(): FrozenIndex<Row>;
55
+ release(frozen: FrozenIndex<Row>): void;
56
+ /** Every row visible at a frozen root, reading only that root's own pages. */
57
+ entries(frozen: FrozenIndex<Row>): Promise<Map<string, Row>>;
58
+ status(): {
59
+ overrideSlots: number;
60
+ deletionMasks: number;
61
+ deletionMaskBytes: number;
62
+ basePages: number;
63
+ baseRows: number;
64
+ };
65
+ /** The acceptance path checks counts only: an override is eligible once none of its leaves is uncommitted. */
66
+ maybeCompact(): void;
67
+ /** Folds eligible overrides and every small base page; false leaves the previous base untouched. */
68
+ private compact;
69
+ /**
70
+ * The publication point: one synchronous step, so no acceptance observes a half-swapped index. A folded
71
+ * row is published only while its value is still observable (its override is the folded one, or a newer
72
+ * patch over it); a row removed or wholly replaced meanwhile is skipped, so every published leaf is still
73
+ * owned by the folded override or a touched page when its reference is added.
74
+ */
75
+ private swap;
76
+ private readPage;
77
+ private reclaimPage;
78
+ private put;
79
+ private drop;
80
+ private countTombstone;
81
+ private pinRow;
82
+ }
83
+ export {};
84
+ //# sourceMappingURL=compact-content-index.d.ts.map