@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-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 (198) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-M235FJQV.js} +3 -3
  3. package/dist/bundle/chunks/{args-B444OQBK.js → args-FBNJOJSR.js} +1 -1
  4. package/dist/bundle/chunks/azure-openai-responses-YQRTQ7H6.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  6. package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-3A3XCLNH.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-3LPWILUZ.js} +2 -2
  8. package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-3WP5RFQR.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-5FQMNHXV.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-5KZH6EYQ.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-5LWUUXMH.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-6U3TBFBA.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-7KOUQSBX.js} +2 -2
  14. package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ADQAPDPV.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-CDQPA4FP.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-DMFAVE5N.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-DYWIFF2Q.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-EZP6AYW2.js} +35 -29
  19. package/dist/bundle/chunks/chunk-GQJYNFMO.js +2 -0
  20. package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-I7QWTWVZ.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-J5FCH7TI.js} +1 -1
  26. package/dist/bundle/chunks/chunk-JFQUWEN7.js +13 -0
  27. package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-LO5MYLGF.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-MSHRIZGT.js} +8 -6
  30. package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-MY4ZD3EW.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-OQ3WDMB3.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-PH3JDRB2.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-QREKAXKX.js} +7 -5
  34. package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-QYFOHFPS.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-SOXQ42VS.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-UDM7VO6O.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-UW6M3EXY.js} +1 -1
  38. package/dist/bundle/chunks/chunk-VICDRG3J.js +410 -0
  39. package/dist/bundle/chunks/chunk-VL7O5PO4.js +5 -0
  40. package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-XQ45JMGN.js} +1 -1
  42. package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
  43. package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
  45. package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-ZLIVZQDY.js} +4 -4
  46. package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-ZXHLH4MW.js} +2 -2
  47. package/dist/bundle/chunks/cli-main-55EQA5PS.js +3 -0
  48. package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
  49. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-ZJQGIIRC.js} +1 -1
  50. package/dist/bundle/chunks/cursor-agent.js +3 -3
  51. package/dist/bundle/chunks/devin-agent.js +3 -3
  52. package/dist/bundle/chunks/dispatch-C2IBP5VV.js +9 -0
  53. package/dist/bundle/chunks/google-generative-ai-5G4RIQA6.js +2 -0
  54. package/dist/bundle/chunks/google-vertex-ALD4P27O.js +2 -0
  55. package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-PEIKGCKZ.js} +1 -1
  56. package/dist/bundle/chunks/host-command-657VCQ34.js +2 -0
  57. package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
  58. package/dist/bundle/chunks/host-lifecycle.js +2 -2
  59. package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-OV5XDOJK.js} +1 -1
  60. package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
  61. package/dist/bundle/chunks/interactive-mode-MLXSYK2H.js +2 -0
  62. package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
  63. package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-RAE4K2XY.js} +1 -1
  64. package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-774MACJU.js} +5 -6
  65. package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-3CW4R7TG.js} +1 -1
  66. package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-5UIJXQUF.js} +1 -1
  67. package/dist/bundle/chunks/openai-responses-2BO44MRW.js +3 -0
  68. package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-MZHVCX2S.js} +1 -1
  69. package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-RF6GER4R.js} +2 -2
  70. package/dist/bundle/chunks/{role-JVSBNKGE.js → role-ETSFSDTS.js} +1 -1
  71. package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-ZMQB4GPO.js} +1 -1
  72. package/dist/bundle/chunks/rpc-mode-UJXOIZCW.js +2 -0
  73. package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
  74. package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-KA7LFFNK.js} +1 -1
  75. package/dist/bundle/chunks/session-control-endpoint-MFWXUR7Z.js +3 -0
  76. package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-BP33NRSL.js} +1 -1
  77. package/dist/bundle/chunks/session-worker.js +199 -190
  78. package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-GNNZ5O5U.js} +1 -1
  79. package/dist/bundle/cli.js +2 -2
  80. package/dist/bundle/host-supervisor.js +2 -2
  81. package/dist/bundle/index.js +1 -1
  82. package/dist/bundle/rpc-entry.js +1 -1
  83. package/dist/bundle/runtime-manifest.json +1 -1
  84. package/dist/cli/host-command-args.d.ts +1 -0
  85. package/dist/cli/host-command-args.js +5 -0
  86. package/dist/cli/host-command.js +7 -1
  87. package/dist/cli.js +21 -3
  88. package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
  89. package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
  90. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
  91. package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
  92. package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
  93. package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
  94. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
  95. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  96. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
  97. package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
  98. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
  99. package/dist/core/extensions/builtin/mcp/modern/request.js +1 -1
  100. package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
  101. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
  102. package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
  103. package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
  104. package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
  105. package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
  106. package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
  107. package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
  108. package/dist/core/extensions/builtin/mcp/resources.js +4 -0
  109. package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
  110. package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
  111. package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
  112. package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
  113. package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
  114. package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
  115. package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
  116. package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
  117. package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
  118. package/dist/modes/interactive/session-control-commands.js +21 -4
  119. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  120. package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
  121. package/dist/modes/rpc/compact-content-store.d.ts +96 -0
  122. package/dist/modes/rpc/compact-content-store.js +269 -0
  123. package/dist/modes/rpc/compact-session-state.d.ts +111 -0
  124. package/dist/modes/rpc/compact-session-state.js +206 -0
  125. package/dist/modes/rpc/compact-spool-dir.d.ts +5 -0
  126. package/dist/modes/rpc/compact-spool-dir.js +61 -0
  127. package/dist/modes/rpc/compact-storage-io.d.ts +27 -0
  128. package/dist/modes/rpc/compact-storage-io.js +19 -0
  129. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  130. package/dist/modes/rpc/connection-handler.js +39 -7
  131. package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
  132. package/dist/modes/rpc/connection-question-bridge.js +6 -2
  133. package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
  134. package/dist/modes/rpc/extension-ui-response.js +27 -7
  135. package/dist/modes/rpc/get-snapshot.d.ts +18 -0
  136. package/dist/modes/rpc/get-snapshot.js +43 -0
  137. package/dist/modes/rpc/host-capabilities.js +3 -0
  138. package/dist/modes/rpc/host-ensure-start.js +1 -0
  139. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  140. package/dist/modes/rpc/host-ensure.js +1 -0
  141. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  142. package/dist/modes/rpc/host-handover-request.d.ts +1 -1
  143. package/dist/modes/rpc/host-handover-request.js +4 -3
  144. package/dist/modes/rpc/host-handover-wire.js +6 -0
  145. package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
  146. package/dist/modes/rpc/host-idle-handover.js +3 -1
  147. package/dist/modes/rpc/host-observe-request.js +1 -0
  148. package/dist/modes/rpc/host-runner.d.ts +1 -0
  149. package/dist/modes/rpc/host-runner.js +9 -7
  150. package/dist/modes/rpc/host-successor.js +4 -0
  151. package/dist/modes/rpc/json-normal-value.d.ts +6 -0
  152. package/dist/modes/rpc/json-normal-value.js +100 -0
  153. package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
  154. package/dist/modes/rpc/media-placeholders.d.ts +9 -3
  155. package/dist/modes/rpc/media-placeholders.js +12 -4
  156. package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
  157. package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
  158. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  159. package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
  160. package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
  161. package/dist/modes/rpc/rpc-session-state.js +2 -0
  162. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  163. package/dist/modes/rpc/session-attachment-gate.js +1 -0
  164. package/dist/modes/rpc/session-binding.js +21 -18
  165. package/dist/modes/rpc/session-command-activity.js +1 -0
  166. package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
  167. package/dist/modes/rpc/session-event-fanout.js +17 -9
  168. package/dist/modes/rpc/session-event-writer.js +22 -5
  169. package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
  170. package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
  171. package/dist/modes/rpc/session-held.js +1 -0
  172. package/dist/modes/rpc/session-status-stream.d.ts +2 -1
  173. package/dist/modes/rpc/session-status-stream.js +8 -2
  174. package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
  175. package/dist/modes/rpc/session-status-subscriptions.js +4 -0
  176. package/dist/modes/rpc/session-worker.js +5 -2
  177. package/dist/modes/rpc/snapshot-types.d.ts +29 -0
  178. package/dist/modes/rpc/snapshot-types.js +2 -0
  179. package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
  180. package/dist/modes/rpc/ui-answer-ledger.js +34 -0
  181. package/dist/senpi +21 -3
  182. package/docs/mcp.md +20 -0
  183. package/docs/rpc.md +44 -0
  184. package/package.json +7 -6
  185. package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
  186. package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
  187. package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
  188. package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
  189. package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
  190. package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
  191. package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
  192. package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
  193. package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
  194. package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
  195. package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
  196. package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
  197. package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
  198. package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
@@ -7,6 +7,8 @@ export type PermissionRequest = Pick<Request, "permission" | "patterns" | "alway
7
7
  };
8
8
  /** Parser function that extracts permission requests from tool input */
9
9
  export type ToolPermissionParser = (toolName: string, input: Record<string, unknown>, cwd: string) => PermissionRequest[];
10
+ /** Marks a bash request target that no command-specific rule can match: only a `*` rule decides it. */
11
+ export declare const UNPARSED_BASH_PREFIX = "(unparsed) ";
10
12
  /** The `eval` class a cell carries on its own, used when its tool-owned parser names anything narrower. */
11
13
  export declare function fallbackEvalPermissionRequest(): PermissionRequest;
12
14
  /**
@@ -2,6 +2,7 @@ import { dirname, resolve } from "node:path";
2
2
  import { realpathWithoutOpen } from "../../../../utils/paths.js";
3
3
  import { extractPatchedPaths } from "../gpt-apply-patch/index.js";
4
4
  import { BashArity } from "../permission-system/arity.js";
5
+ import { analyzeBashCommand, bestEffortCommandLines } from "../permission-system/bash-command-segments.js";
5
6
  import { extractExternalPaths, isExternalPath, toParentDirectoryPattern, } from "../permission-system/external-dir.js";
6
7
  import { setApprovedMonitorParent } from "../terminal/monitor-permission.js";
7
8
  import { urlOriginAlias, widerAlwaysScope } from "./always-scope.js";
@@ -73,6 +74,48 @@ function withExternalDirectoryRequests(requests, paths, cwd, scope) {
73
74
  },
74
75
  ];
75
76
  }
77
+ const uniq = (values) => [...new Set(values)];
78
+ /**
79
+ * The targets an "Allow always" answer may store. Stored rules are matched as globs, so a target holding
80
+ * its own `*` or `?` would be remembered as a wildcard that matches other commands; such a target is
81
+ * allowed once and never stored. Request patterns stay raw, so existing rules match exactly as before.
82
+ */
83
+ const rememberable = (targets) => targets.filter((target) => !/[*?]/.test(target));
84
+ /** Marks a bash request target that no command-specific rule can match: only a `*` rule decides it. */
85
+ export const UNPARSED_BASH_PREFIX = "(unparsed) ";
86
+ /**
87
+ * The `bash` requests for one command. Every simple command it runs is decided, so the call is as
88
+ * strict as its strictest command: one pattern per command (its arity prefix), all of which must be
89
+ * allowed, and "Allow always" stores exactly those prefixes. A command the lexer cannot parse is one
90
+ * target only a `*` rule (a preset, a blanket setting) can decide, so no grant for one command can
91
+ * allow it; its plain words are still checked against deny rules.
92
+ */
93
+ function bashCommandRequests(command) {
94
+ const analysis = analyzeBashCommand(command);
95
+ if (analysis.kind === "unparsed") {
96
+ const target = `${UNPARSED_BASH_PREFIX}${command}`;
97
+ const candidates = uniq(bestEffortCommandLines(command)
98
+ .map((words) => BashArity.prefix(words).join(" "))
99
+ .filter(Boolean));
100
+ // The deny-only check runs first, so a denied call is refused before anyone is asked.
101
+ return [
102
+ ...(candidates.length === 0
103
+ ? []
104
+ : [{ permission: "bash", patterns: candidates, always: [], autoApproveAsk: true }]),
105
+ { permission: "bash", patterns: [target], always: rememberable([target]) },
106
+ ];
107
+ }
108
+ const prefixes = uniq(analysis.segments.map((words) => BashArity.prefix([...words]).join(" ")).filter(Boolean));
109
+ if (prefixes.length === 0)
110
+ return [{ permission: "bash", patterns: [command], always: rememberable([command]) }];
111
+ return [
112
+ {
113
+ permission: "bash",
114
+ patterns: prefixes,
115
+ always: rememberable(prefixes).flatMap((prefix) => [prefix, `${prefix} *`]),
116
+ },
117
+ ];
118
+ }
76
119
  /** The `eval` class a cell carries on its own, used when its tool-owned parser names anything narrower. */
77
120
  export function fallbackEvalPermissionRequest() {
78
121
  return { permission: "eval", patterns: ["*"], always: ["*"] };
@@ -135,16 +178,7 @@ export function createBuiltinParserRegistry() {
135
178
  if (!command) {
136
179
  return [fallbackPermissionRequest("bash")];
137
180
  }
138
- const tokens = command.split(/\s+/).filter(Boolean);
139
- const prefix = BashArity.prefix(tokens).join(" ");
140
- const always = prefix ? [prefix, `${prefix} *`] : ["*"];
141
- const requests = [
142
- {
143
- permission: "bash",
144
- patterns: [prefix || command],
145
- always,
146
- },
147
- ];
181
+ const requests = bashCommandRequests(command);
148
182
  const externalPaths = extractExternalPaths(command, cwd);
149
183
  if (externalPaths.length > 0) {
150
184
  requests.push({
@@ -13,6 +13,7 @@ import { ENTRIES_TAIL_CAPABILITY, OBSERVE_READS_CAPABILITY, SESSION_STATUS_CAPAB
13
13
  import { answerGetEntries } from "../rpc/get-entries-command.js";
14
14
  import { answeredUiRequestId, settledQuestionStatus, unansweredQuestionIds } from "../rpc/extension-ui-response.js";
15
15
  import { buildRpcSessionState } from "../rpc/rpc-session-state.js";
16
+ import { sessionUiAnswerLedger } from "../rpc/ui-answer-ledger.js";
16
17
  import { runTuiRpcCommand, TUI_RPC_COMMANDS } from "./session-control-rpc-commands.js";
17
18
  import { failure, success } from "./session-control-server.js";
18
19
  import { runSessionControlCommand, SESSION_CONTROL_COMMANDS, } from "./session-control-session-commands.js";
@@ -45,6 +46,8 @@ export async function runControlCommand(context, connection, command) {
45
46
  TUI_OBSERVER_CAPABILITY,
46
47
  OBSERVE_READS_CAPABILITY,
47
48
  ENTRIES_TAIL_CAPABILITY,
49
+ "snapshot_cursor",
50
+ "ui_answer_ledger",
48
51
  ],
49
52
  mode: "tui",
50
53
  endpoint_kind: "tui",
@@ -89,7 +92,7 @@ export async function runControlCommand(context, connection, command) {
89
92
  return success(id, type, { admitted: result.admitted ?? [] });
90
93
  }
91
94
  case "extension_ui_response":
92
- return answerQuestion(context.surface, command);
95
+ return answerQuestion(context, command);
93
96
  default: {
94
97
  const rpc = await runTuiRpcCommand(context, connection, command);
95
98
  if (rpc !== undefined)
@@ -121,12 +124,25 @@ function sessionRow(session) {
121
124
  * settles by the host's rule (`settledQuestionStatus`), so the same frame reaches the model as the same
122
125
  * message on either surface.
123
126
  */
124
- function answerQuestion(surface, command) {
127
+ function answerQuestion(context, command) {
128
+ const { surface, session } = context;
129
+ const ledger = sessionUiAnswerLedger(session);
130
+ const answerId = command.answerId;
131
+ if (answerId !== undefined && (typeof answerId !== "string" || answerId.length < 1 || answerId.length > 256))
132
+ return failure(command.id, "extension_ui_response", "invalid_answer_id");
125
133
  const frameId = command.id;
126
134
  const requestId = answeredUiRequestId(command);
135
+ const receipt = requestId === undefined || answerId === undefined ? undefined : ledger.lookup(requestId, answerId);
136
+ if (receipt === "replay")
137
+ return { ...success(frameId, "extension_ui_response"), disposition: receipt };
138
+ if (receipt === "already_answered")
139
+ return { ...failure(frameId, "extension_ui_response", "already_answered"), disposition: receipt };
127
140
  const request = requestId === undefined ? undefined : surface.pendingQuestion(requestId);
128
141
  if (requestId === undefined || request === undefined) {
129
- return failure(frameId, "extension_ui_response", "unknown_request");
142
+ return {
143
+ ...failure(frameId, "extension_ui_response", answerId === undefined ? "unknown_request" : "unknown_extension_ui_request"),
144
+ disposition: "unknown",
145
+ };
130
146
  }
131
147
  const response = questionResponse(command, request.questions);
132
148
  if (response === "question_incomplete")
@@ -134,7 +150,8 @@ function answerQuestion(surface, command) {
134
150
  if (response === undefined || !surface.answerQuestion(requestId, response)) {
135
151
  return failure(frameId, "extension_ui_response", "invalid_response");
136
152
  }
137
- return success(frameId, "extension_ui_response");
153
+ ledger.record(requestId, answerId);
154
+ return { ...success(frameId, "extension_ui_response"), disposition: "applied" };
138
155
  }
139
156
  function questionResponse(command, questions) {
140
157
  // Another client's cancel (the desktop's Remove) is the user's; the builtin settles it from its own draft.
@@ -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", "subscribe_sessions", "unsubscribe_sessions", "steer", "follow_up", "prompt", "get_effect_status", "set_client_info", "attach_session", "release_attachment", "abort"];
4
+ export declare const TUI_RPC_COMMANDS: readonly ["open_session", "get_snapshot", "subscribe_sessions", "unsubscribe_sessions", "steer", "follow_up", "prompt", "get_effect_status", "set_client_info", "attach_session", "release_attachment", "abort"];
5
5
  export interface RpcCommandContext {
6
6
  /** This endpoint's instance id: the `expectedHostInstance` an `attach_session` may name. */
7
7
  readonly instanceId: string;
@@ -17,11 +17,13 @@ import { CONNECTION_ROLE_FIXED } from "../rpc/connection-roles.js";
17
17
  import { INTERACTIVE_USER_CAPABILITY } from "../rpc/custom-capability.js";
18
18
  import { runAbort, runGetEffectStatus } from "../rpc/effect-commands.js";
19
19
  import { buildRpcSessionState } from "../rpc/rpc-session-state.js";
20
+ import { getSnapshot } from "../rpc/get-snapshot.js";
20
21
  import { answerSubscribeSessions, answerUnsubscribeSessions, refusal } from "../rpc/session-status-commands.js";
21
22
  import { keyOf } from "./session-control-attach.js";
22
23
  import { failure, success } from "./session-control-server.js";
23
24
  export const TUI_RPC_COMMANDS = [
24
25
  "open_session",
26
+ "get_snapshot",
25
27
  "subscribe_sessions",
26
28
  "unsubscribe_sessions",
27
29
  "steer",
@@ -38,6 +40,23 @@ const STOP_REQUIRES_INTERACTIVE_CLIENT = "stop_requires_interactive_client: decl
38
40
  /** `null` = answered on the writer already; `undefined` = not a command this file owns. */
39
41
  export async function runTuiRpcCommand(context, connection, command) {
40
42
  switch (command.type) {
43
+ case "get_snapshot": {
44
+ const { attachments, session } = context;
45
+ if (command.sessionId !== undefined && command.sessionId !== attachments.handle)
46
+ return {
47
+ id: command.id,
48
+ type: "response",
49
+ command: command.type,
50
+ success: false,
51
+ error: "unknown_session",
52
+ };
53
+ const snapshot = getSnapshot(session, { tail: command.tail, maxBytes: command.maxBytes }, {
54
+ enqueue: (markerId) => attachments.run(connection, () => attachments.writer.enqueue(attachments.handle, { type: "snapshot_marker", markerId })),
55
+ });
56
+ return "error" in snapshot
57
+ ? { id: command.id, type: "response", command: command.type, success: false, error: snapshot.error }
58
+ : answered(idOf(command), command.type, snapshot.data);
59
+ }
41
60
  case "open_session":
42
61
  return openSession(context, connection, command);
43
62
  case "subscribe_sessions":
@@ -0,0 +1,96 @@
1
+ import { type CompactStorageFailure, type CompactStorageIo } from "./compact-storage-io.ts";
2
+ /**
3
+ * Owned bounded leaves for compact current state (senpi#4272, R3.C6.4 / R3.C2.4).
4
+ *
5
+ * Acceptance never waits here: a leaf is created synchronously in RAM, and a spill only relocates
6
+ * that immutable value. Its RAM copy is released only after the whole file was written and closed;
7
+ * a failed write keeps the bytes resident, halts further spilling and is reported through
8
+ * `status()`, never dropped. All storage work runs as one serial asynchronous job chain.
9
+ */
10
+ /** 32,768 UTF-16 code units (64 KiB as UTF-16LE), so a split surrogate survives a leaf boundary exactly. */
11
+ export declare const COMPACT_LEAF_CODE_UNITS = 32768;
12
+ export type LeafId = number;
13
+ export interface CompactStorageStatus {
14
+ readonly residentPayloadBytes: number;
15
+ readonly pendingIoCopyBytes: number;
16
+ readonly pinnedOnlyBytes: number;
17
+ readonly spilledLiveBytes: number;
18
+ readonly spillBytesWritten: number;
19
+ readonly reclaimDebtBytes: number;
20
+ /** Resident bytes above the spill target that a halted spool keeps in RAM (ruling 08 condition 1). */
21
+ readonly spillFailureRetainedBytes: number;
22
+ readonly spillHalted: boolean;
23
+ readonly failures: readonly CompactStorageFailure[];
24
+ }
25
+ export interface CompactContentStoreOptions {
26
+ /** Private epoch spool directory the store creates itself (0700, never an existing one; files 0600). Not a transcript. */
27
+ readonly dir: string;
28
+ readonly io?: CompactStorageIo;
29
+ /** Spilling starts above this many resident leaf bytes and stops at the target. */
30
+ readonly spillAboveBytes?: number;
31
+ readonly spillTargetBytes?: number;
32
+ }
33
+ export declare class CompactContentStore {
34
+ readonly dir: string;
35
+ private readonly io;
36
+ private readonly spillAboveBytes;
37
+ private readonly spillTargetBytes;
38
+ private readonly leaves;
39
+ /** Resident leaves in creation order, so each spill takes the oldest without rescanning committed ones. */
40
+ private readonly ramLeaves;
41
+ private readonly failures;
42
+ private nextLeafId;
43
+ private nextFile;
44
+ private dirReady;
45
+ private spoolToken;
46
+ private chain;
47
+ private queued;
48
+ private spillQueued;
49
+ private halted;
50
+ private retired;
51
+ private residentPayloadBytes;
52
+ private pendingIoCopyBytes;
53
+ private pinnedOnlyBytes;
54
+ private spilledLiveBytes;
55
+ private spillBytesWritten;
56
+ private reclaimDebtBytes;
57
+ constructor(options: CompactContentStoreOptions);
58
+ /** Takes ownership of an immutable value with one live reference. */
59
+ createLeaf(text: string): LeafId;
60
+ /** Moves live and cut-pin references; the last reference drops RAM or reclaims the file. */
61
+ adjust(id: LeafId, live: number, pins: number): void;
62
+ /** Reads a leaf the caller still references, from RAM or its committed file. */
63
+ readLeaf(id: LeafId): Promise<string>;
64
+ /** Resolves once every queued storage job, including the ones they queued, has completed. */
65
+ idle(): Promise<void>;
66
+ /**
67
+ * Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
68
+ * created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
69
+ */
70
+ retire(): Promise<void>;
71
+ status(): CompactStorageStatus;
72
+ /** Reads a committed file and rejects a missing, unreadable or short one. */
73
+ private readFile;
74
+ /** Writes one new spool file; undefined means the write failed, was reported, and spilling halted. */
75
+ private writeFile;
76
+ /**
77
+ * An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
78
+ * finds it gone. Files dropped after retirement started belong to the directory retirement removes.
79
+ */
80
+ private discard;
81
+ /** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
82
+ private reclaim;
83
+ private get spillAllowed();
84
+ /** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
85
+ private get spillStopped();
86
+ private enqueue;
87
+ private scheduleSpill;
88
+ /** Spills one oldest resident leaf per job, so other storage work interleaves fairly. */
89
+ private spillNext;
90
+ /** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
91
+ private ensureDir;
92
+ private readFailure;
93
+ private fail;
94
+ private record;
95
+ }
96
+ //# sourceMappingURL=compact-content-store.d.ts.map
@@ -0,0 +1,269 @@
1
+ import { join } from "node:path";
2
+ import { createSpoolDir, removeSpoolDir } from "./compact-spool-dir.js";
3
+ import { CompactStorageReadError, describeCause, nodeCompactStorageIo, } from "./compact-storage-io.js";
4
+ /**
5
+ * Owned bounded leaves for compact current state (senpi#4272, R3.C6.4 / R3.C2.4).
6
+ *
7
+ * Acceptance never waits here: a leaf is created synchronously in RAM, and a spill only relocates
8
+ * that immutable value. Its RAM copy is released only after the whole file was written and closed;
9
+ * a failed write keeps the bytes resident, halts further spilling and is reported through
10
+ * `status()`, never dropped. All storage work runs as one serial asynchronous job chain.
11
+ */
12
+ /** 32,768 UTF-16 code units (64 KiB as UTF-16LE), so a split surrogate survives a leaf boundary exactly. */
13
+ export const COMPACT_LEAF_CODE_UNITS = 32_768;
14
+ const RECLAIM_WORK_ALLOWANCE_BYTES = 1024 * 1024;
15
+ const RECLAIM_RETRY_DELAYS_MS = [100, 400, 1_600, 6_400, 25_600];
16
+ const FAILURE_HISTORY = 32;
17
+ export class CompactContentStore {
18
+ constructor(options) {
19
+ this.leaves = new Map();
20
+ /** Resident leaves in creation order, so each spill takes the oldest without rescanning committed ones. */
21
+ this.ramLeaves = new Set();
22
+ this.failures = [];
23
+ this.nextLeafId = 1;
24
+ this.nextFile = 1;
25
+ this.chain = Promise.resolve();
26
+ this.queued = 0;
27
+ this.spillQueued = false;
28
+ this.halted = false;
29
+ this.retired = false;
30
+ this.residentPayloadBytes = 0;
31
+ this.pendingIoCopyBytes = 0;
32
+ this.pinnedOnlyBytes = 0;
33
+ this.spilledLiveBytes = 0;
34
+ this.spillBytesWritten = 0;
35
+ this.reclaimDebtBytes = 0;
36
+ this.dir = options.dir;
37
+ this.io = options.io ?? nodeCompactStorageIo;
38
+ this.spillAboveBytes = options.spillAboveBytes ?? 4 * 1024 * 1024;
39
+ this.spillTargetBytes = options.spillTargetBytes ?? 2 * 1024 * 1024;
40
+ }
41
+ /** Takes ownership of an immutable value with one live reference. */
42
+ createLeaf(text) {
43
+ const id = this.nextLeafId++;
44
+ this.leaves.set(id, { text, units: text.length, live: 1, pins: 0, state: "ram" });
45
+ this.ramLeaves.add(id);
46
+ this.residentPayloadBytes += text.length * 2;
47
+ this.scheduleSpill();
48
+ return id;
49
+ }
50
+ /** Moves live and cut-pin references; the last reference drops RAM or reclaims the file. */
51
+ adjust(id, live, pins) {
52
+ const leaf = this.leaves.get(id);
53
+ if (!leaf)
54
+ throw new Error(`compact leaf ${id} is not owned`);
55
+ const pinnedOnly = leaf.live === 0 && leaf.pins > 0;
56
+ leaf.live += live;
57
+ leaf.pins += pins;
58
+ if (pinnedOnly !== (leaf.live === 0 && leaf.pins > 0))
59
+ this.pinnedOnlyBytes += (pinnedOnly ? -2 : 2) * leaf.units;
60
+ if (leaf.live > 0 || leaf.pins > 0)
61
+ return;
62
+ this.leaves.delete(id);
63
+ this.ramLeaves.delete(id);
64
+ if (leaf.state === "committed" && leaf.path) {
65
+ this.spilledLiveBytes -= leaf.units * 2;
66
+ this.discard(leaf.path, leaf.units * 2);
67
+ return;
68
+ }
69
+ this.residentPayloadBytes -= leaf.units * 2;
70
+ }
71
+ /** Reads a leaf the caller still references, from RAM or its committed file. */
72
+ async readLeaf(id) {
73
+ const leaf = this.leaves.get(id);
74
+ if (!leaf)
75
+ throw new Error(`compact leaf ${id} is not owned`);
76
+ if (leaf.text !== undefined)
77
+ return leaf.text;
78
+ return (await this.readFile(leaf.path ?? "", leaf.units * 2, "leaf_read")).toString("utf16le");
79
+ }
80
+ /** Resolves once every queued storage job, including the ones they queued, has completed. */
81
+ async idle() {
82
+ while (this.queued > 0)
83
+ await this.chain;
84
+ }
85
+ /**
86
+ * Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
87
+ * created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
88
+ */
89
+ async retire() {
90
+ this.retired = true;
91
+ await this.idle();
92
+ const token = this.spoolToken;
93
+ this.spoolToken = undefined;
94
+ if (token !== undefined)
95
+ await removeSpoolDir(this.dir, token);
96
+ }
97
+ status() {
98
+ return {
99
+ residentPayloadBytes: this.residentPayloadBytes,
100
+ pendingIoCopyBytes: this.pendingIoCopyBytes,
101
+ pinnedOnlyBytes: this.pinnedOnlyBytes,
102
+ spilledLiveBytes: this.spilledLiveBytes,
103
+ spillBytesWritten: this.spillBytesWritten,
104
+ reclaimDebtBytes: this.reclaimDebtBytes,
105
+ spillFailureRetainedBytes: this.spillStopped
106
+ ? Math.max(0, this.residentPayloadBytes - this.spillTargetBytes)
107
+ : 0,
108
+ spillHalted: this.spillStopped,
109
+ failures: [...this.failures],
110
+ };
111
+ }
112
+ /** Reads a committed file and rejects a missing, unreadable or short one. */
113
+ async readFile(path, bytes, operation) {
114
+ let data;
115
+ try {
116
+ data = await this.io.readFile(path);
117
+ }
118
+ catch (cause) {
119
+ throw this.readFailure(operation, path, cause);
120
+ }
121
+ if (data.byteLength !== bytes) {
122
+ throw this.readFailure(operation, path, new Error(`expected ${bytes} bytes, found ${data.byteLength}`));
123
+ }
124
+ return data;
125
+ }
126
+ /** Writes one new spool file; undefined means the write failed, was reported, and spilling halted. */
127
+ async writeFile(data, operation, retainedInMemoryBytes) {
128
+ const path = await this.ensureDir(retainedInMemoryBytes);
129
+ if (path === undefined)
130
+ return undefined;
131
+ const file = join(path, `leaf-${this.nextFile++}.bin`);
132
+ this.pendingIoCopyBytes += data.byteLength;
133
+ try {
134
+ await this.io.writeFile(file, data);
135
+ this.spillBytesWritten += data.byteLength;
136
+ return file;
137
+ }
138
+ catch (cause) {
139
+ this.fail(operation, file, cause, retainedInMemoryBytes);
140
+ this.discard(file, data.byteLength);
141
+ return undefined;
142
+ }
143
+ finally {
144
+ this.pendingIoCopyBytes -= data.byteLength;
145
+ }
146
+ }
147
+ /**
148
+ * An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
149
+ * finds it gone. Files dropped after retirement started belong to the directory retirement removes.
150
+ */
151
+ discard(path, bytes) {
152
+ if (this.retired)
153
+ return;
154
+ this.reclaimDebtBytes += bytes;
155
+ this.enqueue("reclaim", () => this.reclaim(path, bytes, 0));
156
+ }
157
+ /** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
158
+ async reclaim(path, bytes, attempt) {
159
+ try {
160
+ await this.io.unlink(path);
161
+ }
162
+ catch (cause) {
163
+ if (cause.code !== "ENOENT") {
164
+ this.fail("reclaim", path, cause, 0);
165
+ const delay = RECLAIM_RETRY_DELAYS_MS[attempt];
166
+ if (delay !== undefined) {
167
+ setTimeout(() => {
168
+ if (this.retired)
169
+ this.reclaimDebtBytes -= bytes;
170
+ else
171
+ this.enqueue("reclaim", () => this.reclaim(path, bytes, attempt + 1));
172
+ }, delay).unref?.();
173
+ }
174
+ return;
175
+ }
176
+ }
177
+ this.reclaimDebtBytes -= bytes;
178
+ this.scheduleSpill();
179
+ }
180
+ get spillAllowed() {
181
+ return !this.halted && !this.retired;
182
+ }
183
+ /** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
184
+ get spillStopped() {
185
+ return this.halted || this.reclaimDebtBytes > RECLAIM_WORK_ALLOWANCE_BYTES;
186
+ }
187
+ enqueue(operation, job) {
188
+ this.queued++;
189
+ this.chain = this.chain
190
+ .then(job)
191
+ .catch((cause) => {
192
+ if (operation === "spill_write")
193
+ this.spillQueued = false;
194
+ this.fail(operation, this.dir, cause, 0);
195
+ })
196
+ .finally(() => {
197
+ this.queued--;
198
+ });
199
+ }
200
+ scheduleSpill() {
201
+ if (this.spillQueued || !this.spillAllowed || this.residentPayloadBytes <= this.spillAboveBytes)
202
+ return;
203
+ if (this.reclaimDebtBytes > RECLAIM_WORK_ALLOWANCE_BYTES)
204
+ return;
205
+ this.spillQueued = true;
206
+ this.enqueue("spill_write", () => this.spillNext());
207
+ }
208
+ /** Spills one oldest resident leaf per job, so other storage work interleaves fairly. */
209
+ async spillNext() {
210
+ const [id] = this.ramLeaves;
211
+ const leaf = id === undefined ? undefined : this.leaves.get(id);
212
+ if (id === undefined || !leaf || leaf.text === undefined || !this.spillAllowed) {
213
+ this.spillQueued = false;
214
+ return;
215
+ }
216
+ const bytes = leaf.units * 2;
217
+ leaf.state = "pending";
218
+ this.ramLeaves.delete(id);
219
+ const path = await this.writeFile(Buffer.from(leaf.text, "utf16le"), "spill_write", bytes);
220
+ const owned = this.leaves.get(id) === leaf;
221
+ if (path === undefined) {
222
+ leaf.state = "ram";
223
+ if (owned)
224
+ this.ramLeaves.add(id);
225
+ }
226
+ else if (!owned || this.retired) {
227
+ this.discard(path, bytes);
228
+ }
229
+ else {
230
+ leaf.state = "committed";
231
+ leaf.path = path;
232
+ leaf.text = undefined;
233
+ this.residentPayloadBytes -= bytes;
234
+ this.spilledLiveBytes += bytes;
235
+ }
236
+ this.spillQueued = false;
237
+ if (this.residentPayloadBytes > this.spillTargetBytes) {
238
+ this.spillQueued = this.spillAllowed;
239
+ if (this.spillQueued)
240
+ this.enqueue("spill_write", () => this.spillNext());
241
+ }
242
+ }
243
+ /** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
244
+ ensureDir(retained) {
245
+ this.dirReady ??= createSpoolDir(this.dir).then((token) => {
246
+ this.spoolToken = token;
247
+ return this.dir;
248
+ }, (cause) => {
249
+ this.fail("spool_create", this.dir, cause, retained);
250
+ return undefined;
251
+ });
252
+ return this.dirReady;
253
+ }
254
+ readFailure(operation, path, cause) {
255
+ this.fail(operation, path, cause, 0);
256
+ return new CompactStorageReadError(operation, path, cause);
257
+ }
258
+ fail(operation, path, cause, retained) {
259
+ if (operation === "spill_write" || operation === "spool_create")
260
+ this.halted = true;
261
+ this.record({ operation, path, cause: describeCause(cause), retainedInMemoryBytes: retained });
262
+ }
263
+ record(failure) {
264
+ this.failures.push(failure);
265
+ if (this.failures.length > FAILURE_HISTORY)
266
+ this.failures.shift();
267
+ }
268
+ }
269
+ //# sourceMappingURL=compact-content-store.js.map
@@ -0,0 +1,111 @@
1
+ import { type CompactContentStoreOptions, type CompactStorageStatus, type LeafId } from "./compact-content-store.ts";
2
+ /**
3
+ * The in-memory current-state owner over compact storage (senpi#4272, R3.C6.4 first half).
4
+ *
5
+ * `accept` is synchronous: it validates a typed transition completely, then publishes the next
6
+ * sequence, or throws and leaves the prior root untouched. Block text lives in owned bounded leaves
7
+ * that spill asynchronously; settled message rows live in a resident map whose persistence status
8
+ * changes only through `message_status`, never through storage success or failure. The map is
9
+ * exact for every transition: a status change replaces the whole row, a removal deletes it, and a
10
+ * transition addressing a removed or unknown message is refused rather than masked. `freeze` pins
11
+ * an immutable cut synchronously, and `read` materializes it asynchronously from RAM or disk.
12
+ */
13
+ type CompactBlockKind = "text" | "thinking" | "tool_arguments";
14
+ type CompactPersistence = "pending" | "appended" | "failed";
15
+ interface BlockAddress {
16
+ readonly messageId: string;
17
+ readonly contentIndex: number;
18
+ }
19
+ export type CompactTransition = (BlockAddress & {
20
+ readonly type: "block_start";
21
+ readonly kind: CompactBlockKind;
22
+ readonly seed: string;
23
+ /**
24
+ * The whole seed when later deltas replay it from offset 0, or 0 when they continue after it. Partial
25
+ * coverage is refused: the uncovered seed tail would make every later delta overlap or leave a gap.
26
+ */
27
+ readonly coveredCodeUnits: number;
28
+ }) | (BlockAddress & {
29
+ readonly type: "block_delta";
30
+ readonly offset: number;
31
+ readonly text: string;
32
+ }) | (BlockAddress & {
33
+ readonly type: "block_replace";
34
+ readonly text: string;
35
+ }) | {
36
+ readonly type: "message_status";
37
+ readonly messageId: string;
38
+ readonly persistence: CompactPersistence;
39
+ } | {
40
+ readonly type: "message_remove";
41
+ readonly messageId: string;
42
+ };
43
+ interface FrozenBlock {
44
+ readonly contentIndex: number;
45
+ readonly kind: CompactBlockKind;
46
+ readonly leaves: readonly LeafId[];
47
+ /** A cut shares the immutable active tail string (under one leaf) instead of minting a leaf per freeze. */
48
+ readonly tail: string;
49
+ }
50
+ interface SettledRow {
51
+ readonly persistence: CompactPersistence;
52
+ readonly blocks: readonly FrozenBlock[];
53
+ }
54
+ interface FrozenCompactState {
55
+ readonly sequence: number;
56
+ readonly active: ReadonlyMap<string, readonly FrozenBlock[]>;
57
+ readonly settled: ReadonlyMap<string, SettledRow>;
58
+ }
59
+ interface CompactMessageView {
60
+ readonly messageId: string;
61
+ readonly persistence: CompactPersistence | undefined;
62
+ readonly blocks: readonly {
63
+ readonly contentIndex: number;
64
+ readonly kind: CompactBlockKind;
65
+ readonly text: string;
66
+ }[];
67
+ }
68
+ export declare class CompactSessionState {
69
+ private readonly store;
70
+ private readonly settled;
71
+ private readonly active;
72
+ private sequence;
73
+ private frozenTailBytes;
74
+ constructor(options: CompactContentStoreOptions);
75
+ get acceptedSequence(): number;
76
+ accept(transition: CompactTransition): number;
77
+ freeze(): FrozenCompactState;
78
+ release(frozen: FrozenCompactState): void;
79
+ /** Rejects with the storage error when committed content is gone; never returns empty content for it. */
80
+ read(frozen: FrozenCompactState): Promise<{
81
+ sequence: number;
82
+ messages: CompactMessageView[];
83
+ }>;
84
+ storageStatus(): CompactStorageStatus & {
85
+ readonly residentTailBytes: number;
86
+ /** Active tails held by unreleased cuts; may share storage with `residentTailBytes`. */
87
+ readonly frozenTailBytes: number;
88
+ readonly hotWitnessBytes: number;
89
+ /** Settled rows kept resident: S03a-1 has no paged row index, so every row header stays in RAM. */
90
+ readonly residentSettledRows: number;
91
+ };
92
+ idle(): Promise<void>;
93
+ retire(): Promise<void>;
94
+ private start;
95
+ /**
96
+ * Appends only the new suffix. Overlap exists only while a fully covered seed is being replayed
97
+ * (`emitted < covered == units`), so it always lies inside the producer-declared coverage.
98
+ */
99
+ private delta;
100
+ private replace;
101
+ private setPersistence;
102
+ private remove;
103
+ private block;
104
+ private append;
105
+ /** Settling mints the final tail into a spillable leaf; a freeze of an active block shares the tail. */
106
+ private seal;
107
+ private pin;
108
+ private texts;
109
+ }
110
+ export {};
111
+ //# sourceMappingURL=compact-session-state.d.ts.map