@abelxiaoxing/cadence 1.8.4 → 2.0.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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -0,0 +1,608 @@
1
+ import {
2
+ ContractError,
3
+ canonicalJson,
4
+ LIMITS,
5
+ utf8Bytes,
6
+ } from "./worker-public.mjs";
7
+
8
+ const marker = "[redacted]";
9
+ const sensitive =
10
+ /^(?:authorization|proxy-authorization|cookie|set-cookie|password|passwd|secret|token|api[-_]?key|access[-_]?token|refresh[-_]?token|credentials|headers|env)$/i;
11
+ /** Own data only. Deliberately never reads a getter, toJSON, exception or prototype method. */
12
+ export function own(value, name) {
13
+ if (!value || typeof value !== "object") throw new ContractError();
14
+ const d = Object.getOwnPropertyDescriptor(value, name);
15
+ if (!d) return undefined;
16
+ if (!("value" in d)) throw new ContractError();
17
+ return d.value;
18
+ }
19
+ export function ownArray(value, maximum = LIMITS.dataFrames) {
20
+ if (!Array.isArray(value)) throw new ContractError();
21
+ const length = own(value, "length");
22
+ if (!Number.isSafeInteger(length) || length > maximum)
23
+ throw new ContractError("oversize");
24
+ return Array.from({ length }, (_, index) => own(value, String(index)));
25
+ }
26
+ function sourceBytes(value) {
27
+ let bytes = 0;
28
+ for (let i = 0; i < value.length; i++) {
29
+ const code = value.charCodeAt(i);
30
+ if (code < 0x80) bytes++;
31
+ else if (code < 0x800) bytes += 2;
32
+ else if (
33
+ code >= 0xd800 &&
34
+ code <= 0xdbff &&
35
+ value.charCodeAt(i + 1) >= 0xdc00 &&
36
+ value.charCodeAt(i + 1) <= 0xdfff
37
+ ) {
38
+ bytes += 4;
39
+ i++;
40
+ } else if (code >= 0xd800 && code <= 0xdfff) throw new ContractError();
41
+ else bytes += 3;
42
+ }
43
+ return bytes;
44
+ }
45
+ /** Charge before cloning. Used at synchronous source ingress as well as public tool values. */
46
+ export function measureData(value, maximum = LIMITS.workerQueueBytes) {
47
+ let bytes = 0;
48
+ let nodes = 0;
49
+ const seen = new Set();
50
+ function visit(v, depth) {
51
+ if (++nodes > LIMITS.dataFrames || depth > 64)
52
+ throw new ContractError("oversize");
53
+ if (typeof v === "string") bytes += sourceBytes(v) * 6 + 2;
54
+ else if (v === null || typeof v === "boolean") bytes += 5;
55
+ else if (typeof v === "number" && Number.isFinite(v)) bytes += 32;
56
+ else if (v && typeof v === "object") {
57
+ if (
58
+ seen.has(v) ||
59
+ ![Object.prototype, Array.prototype, null].includes(
60
+ Object.getPrototypeOf(v),
61
+ )
62
+ )
63
+ throw new ContractError();
64
+ seen.add(v);
65
+ bytes += 2;
66
+ const names = Reflect.ownKeys(v);
67
+ if (names.length > LIMITS.dataFrames) throw new ContractError("oversize");
68
+ for (const key of names) {
69
+ if (Array.isArray(v) && key === "length") continue;
70
+ if (typeof key !== "string") throw new ContractError();
71
+ bytes += sourceBytes(key) * 6 + 4;
72
+ visit(own(v, key), depth + 1);
73
+ }
74
+ seen.delete(v);
75
+ } else throw new ContractError();
76
+ if (bytes > maximum) throw new ContractError("oversize");
77
+ }
78
+ visit(value, 0);
79
+ return bytes;
80
+ }
81
+
82
+ /** Streaming text sanitizer: literal prefixes, ANSI sequences and heuristic value
83
+ * states survive chunk boundaries. No truncation-to-success; bounded ambiguous
84
+ * URL authorities refuse. This is not universal business-secret detection. */
85
+ export class TextSanitizer {
86
+ constructor(secrets = []) {
87
+ if (
88
+ secrets.some((s) => typeof s !== "string") ||
89
+ secrets.reduce((n, s) => n + utf8Bytes(s), 0) >
90
+ LIMITS.workerRequestBytes * 2
91
+ )
92
+ throw new ContractError("oversize");
93
+ this.secrets = [];
94
+ this.addSecrets(secrets);
95
+ this.pending = "";
96
+ this.heuristic = "";
97
+ this.escape = "";
98
+ this.skipping = false;
99
+ this.redacted = false;
100
+ this.ended = false;
101
+ }
102
+ /** Owner-only updates. Preserve all stream state and prior literals; bound the
103
+ * union before retaining any new entries. Never evict a retired secret. */
104
+ addSecrets(secrets) {
105
+ if (this.ended || !Array.isArray(secrets)) throw new ContractError();
106
+ let incoming = 0;
107
+ for (const s of secrets) {
108
+ if (typeof s !== "string") throw new ContractError();
109
+ incoming += utf8Bytes(s);
110
+ if (incoming > LIMITS.workerRequestBytes * 2)
111
+ throw new ContractError("oversize");
112
+ }
113
+ let bytes = this.secrets.reduce((n, s) => n + utf8Bytes(s), 0);
114
+ const added = new Set();
115
+ for (const s of secrets) {
116
+ if (!s || this.secrets.includes(s) || added.has(s)) continue;
117
+ bytes += utf8Bytes(s);
118
+ if (bytes > LIMITS.workerRequestBytes * 2)
119
+ throw new ContractError("oversize");
120
+ added.add(s);
121
+ }
122
+ this.secrets = this.secrets
123
+ .concat([...added])
124
+ .sort((a, b) => b.length - a.length);
125
+ }
126
+ get retainedBytes() {
127
+ return utf8Bytes(this.pending + this.heuristic + this.escape);
128
+ }
129
+ write(input, final = false) {
130
+ if (this.ended || typeof input !== "string") throw new ContractError();
131
+ let plain = "";
132
+ for (const ch of input) {
133
+ if (this.escape) {
134
+ this.escape += ch;
135
+ if (this.escape.length > 8192) throw new ContractError("oversize");
136
+ if (
137
+ (this.escape.startsWith("\x1b[") &&
138
+ this.escape.length > 2 &&
139
+ /[@-~]/.test(ch)) ||
140
+ (this.escape.startsWith("\x1b]") &&
141
+ (ch === "\x07" || this.escape.endsWith("\x1b\\"))) ||
142
+ (this.escape.length === 2 && !"[]".includes(ch))
143
+ )
144
+ this.escape = "";
145
+ this.redacted = true;
146
+ } else if (ch === "\x1b") this.escape = ch;
147
+ else if (
148
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: strip terminal controls before literal matching
149
+ /[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/.test(ch)
150
+ )
151
+ this.redacted = true;
152
+ else plain += ch;
153
+ }
154
+ this.pending += plain;
155
+ let literal = "";
156
+ let at = 0;
157
+ while (at < this.pending.length) {
158
+ const rest = this.pending.slice(at);
159
+ // A longer literal prefix takes precedence over a shorter full match.
160
+ if (
161
+ !final &&
162
+ this.secrets.some((s) => s.length > rest.length && s.startsWith(rest))
163
+ )
164
+ break;
165
+ const match = this.secrets.find((s) => rest.startsWith(s));
166
+ if (match) {
167
+ literal += marker;
168
+ at += match.length;
169
+ this.redacted = true;
170
+ } else {
171
+ const code = this.pending.charCodeAt(at);
172
+ if (
173
+ !final &&
174
+ at + 1 === this.pending.length &&
175
+ code >= 0xd800 &&
176
+ code <= 0xdbff
177
+ )
178
+ break;
179
+ const cp = this.pending.codePointAt(at);
180
+ if (cp >= 0xd800 && cp <= 0xdfff) throw new ContractError();
181
+ literal += String.fromCodePoint(cp);
182
+ at += cp > 0xffff ? 2 : 1;
183
+ }
184
+ }
185
+ this.pending = this.pending.slice(at);
186
+ this.heuristic += literal;
187
+ let output = "";
188
+ while (this.heuristic) {
189
+ if (this.skipping) {
190
+ const end = this.heuristic.search(/[\s,;"'<>]/);
191
+ if (end < 0) {
192
+ this.heuristic = "";
193
+ break;
194
+ }
195
+ this.heuristic = this.heuristic.slice(end);
196
+ this.skipping = false;
197
+ }
198
+ const match =
199
+ /\b(?:Bearer\s+|(?:api[-_]?key|access[-_]?token|refresh[-_]?token|password|passwd|secret|token|authorization)\s*[:=]\s*)/i.exec(
200
+ this.heuristic,
201
+ );
202
+ const url = /https?:\/\//i.exec(this.heuristic);
203
+ if (url && (!match || url.index < match.index)) {
204
+ output += this.heuristic.slice(0, url.index);
205
+ this.heuristic = this.heuristic.slice(url.index);
206
+ const start = url[0].length;
207
+ const end = this.heuristic.slice(start).search(/[/\s?#"'<>]/);
208
+ if (end < 0 && !final) {
209
+ if (utf8Bytes(this.heuristic) > 8192)
210
+ throw new ContractError("oversize");
211
+ break;
212
+ }
213
+ const size = end < 0 ? this.heuristic.length : start + end;
214
+ const authority = this.heuristic.slice(start, size);
215
+ const pos = authority.lastIndexOf("@");
216
+ output +=
217
+ this.heuristic.slice(0, start) +
218
+ (pos < 0 ? authority : `${marker}@${authority.slice(pos + 1)}`);
219
+ this.redacted ||= pos >= 0;
220
+ this.heuristic = this.heuristic.slice(size);
221
+ } else if (match) {
222
+ output += this.heuristic.slice(0, match.index) + match[0] + marker;
223
+ this.heuristic = this.heuristic.slice(match.index + match[0].length);
224
+ this.skipping = true;
225
+ this.redacted = true;
226
+ } else {
227
+ // Finite heuristic names plus delimiters. Potentially unbounded whitespace
228
+ // after a candidate is held/refused, never released as an exposed value.
229
+ const candidate =
230
+ /(?:Bearer|api[-_]?key|access[-_]?token|refresh[-_]?token|password|passwd|secret|token|authorization)\s*$/i.exec(
231
+ this.heuristic,
232
+ );
233
+ const keep = final
234
+ ? 0
235
+ : Math.max(
236
+ 64,
237
+ candidate ? this.heuristic.length - candidate.index : 0,
238
+ );
239
+ if (keep > 8192) throw new ContractError("oversize");
240
+ let take = Math.max(0, this.heuristic.length - keep);
241
+ // The held heuristic suffix is measured in JS code units, but each
242
+ // emitted field/chunk must remain scalar UTF-8. Retain the whole pair
243
+ // when the suffix boundary would bisect a supplementary character.
244
+ const before = this.heuristic.charCodeAt(take - 1);
245
+ if (before >= 0xd800 && before <= 0xdbff) take--;
246
+ output += this.heuristic.slice(0, take);
247
+ this.heuristic = this.heuristic.slice(take);
248
+ break;
249
+ }
250
+ }
251
+ if (final) {
252
+ this.ended = true;
253
+ this.escape = "";
254
+ }
255
+ return output;
256
+ }
257
+ end() {
258
+ return this.write("", true);
259
+ }
260
+ }
261
+ export function sanitizeText(value, secrets = []) {
262
+ const sanitizer = new TextSanitizer(secrets);
263
+ return sanitizer.write(value, true);
264
+ }
265
+ /** Bounded own-data structural projection. Sensitive fields are never traversed.
266
+ * Only tool ordinary data may enter here; SDK/provider envelopes must be selected
267
+ * by the capture adapter instead of passed wholesale. */
268
+ export function sanitizeData(value, secrets = []) {
269
+ measureData(value);
270
+ function visit(v) {
271
+ if (typeof v === "string") return sanitizeText(v, secrets);
272
+ if (v === null || typeof v !== "object") return v;
273
+ if (Array.isArray(v)) return ownArray(v).map(visit);
274
+ const result = Object.create(null);
275
+ for (const key of Object.keys(v).sort()) {
276
+ const safeKey = sanitizeText(key, secrets);
277
+ if (Object.hasOwn(result, safeKey)) throw new ContractError();
278
+ result[safeKey] = sensitive.test(key) ? marker : visit(own(v, key));
279
+ }
280
+ return result;
281
+ }
282
+ return canonicalJson(visit(value), LIMITS.workerQueueBytes);
283
+ }
284
+
285
+ /** Streaming JSON projection. Escapes are decoded before literal matching,
286
+ * sensitive values (including subtrees) are replaced without buffering them.
287
+ * Partial mode is only for genuinely unfinished SDK argument declarations. */
288
+ export class DetailSanitizer {
289
+ constructor(encoding, secrets = [], partial = false) {
290
+ this.encoding = encoding;
291
+ this.secrets = secrets;
292
+ this.partial = partial;
293
+ this.limited = false;
294
+ this.text = new TextSanitizer(secrets);
295
+ this.stack = [];
296
+ this.root = "value";
297
+ this.inString = false;
298
+ this.escape = "";
299
+ this.key = "";
300
+ this.keyMode = false;
301
+ this.suppressed = false;
302
+ this.token = "";
303
+ this.redacted = false;
304
+ }
305
+ get retainedBytes() {
306
+ return (
307
+ this.text.retainedBytes +
308
+ utf8Bytes(this.escape + this.key + this.token) +
309
+ this.stack.reduce(
310
+ (sum, entry) =>
311
+ sum +
312
+ utf8Bytes(entry.lastKey ?? "") +
313
+ utf8Bytes(entry.lastOutputKey ?? ""),
314
+ 0,
315
+ )
316
+ );
317
+ }
318
+ write(input, final = false) {
319
+ if (this.limited) return "";
320
+ try {
321
+ return this.project(input, final);
322
+ } catch (error) {
323
+ if (
324
+ !this.partial ||
325
+ (error instanceof ContractError && error.code === "oversize")
326
+ )
327
+ throw error;
328
+ // Malformed partial argument syntax is an observation, not executable
329
+ // canonical JSON. Preserve the safe prefix and explicitly omit the
330
+ // undecodable suffix; never guess a key/value boundary or leak escapes.
331
+ this.limited = true;
332
+ this.redacted = true;
333
+ return "[redacted: unsupported partial arguments]";
334
+ }
335
+ }
336
+ project(input, final = false) {
337
+ if (this.encoding === "utf8") {
338
+ const output = this.text.write(input, final);
339
+ this.redacted = this.text.redacted;
340
+ return output;
341
+ }
342
+ let output = "";
343
+ const encoded = (s) => JSON.stringify(s).slice(1, -1);
344
+ const top = () => this.stack.at(-1);
345
+ const done = () => {
346
+ if (top()) {
347
+ top().expect = "comma";
348
+ top().sensitive = false;
349
+ top().count++;
350
+ } else this.root = "done";
351
+ };
352
+ const begin = () => {
353
+ const parent = top();
354
+ if ((parent?.expect ?? this.root) !== "value") throw new ContractError();
355
+ const hide = !!parent?.hide || !!parent?.sensitive;
356
+ if (parent?.sensitive && !parent.hide) {
357
+ output += JSON.stringify(marker);
358
+ this.redacted = true;
359
+ }
360
+ return hide;
361
+ };
362
+ const primitive = () => {
363
+ if (
364
+ !/^(?:true|false|null|-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)$/.test(
365
+ this.token,
366
+ )
367
+ ) {
368
+ if (!this.partial) throw new ContractError();
369
+ // An invalid unquoted token may contain escaped secrets outside JSON
370
+ // strings. Do not pretend it is ordinary final data or decode guesses.
371
+ if (!this.suppressed) output += marker;
372
+ this.redacted = true;
373
+ this.limited = true;
374
+ this.token = "";
375
+ done();
376
+ return;
377
+ }
378
+ const value = JSON.parse(this.token);
379
+ if (typeof value === "number" && !Number.isFinite(value))
380
+ throw new ContractError();
381
+ if (!this.suppressed) output += this.token;
382
+ this.token = "";
383
+ done();
384
+ };
385
+ for (const ch of input) {
386
+ if (this.inString) {
387
+ let decoded = ch;
388
+ if (this.escape) {
389
+ this.escape += ch;
390
+ if (
391
+ this.escape === "\\u" ||
392
+ (this.escape.startsWith("\\u") && this.escape.length < 6)
393
+ )
394
+ continue;
395
+ decoded = JSON.parse(`"${this.escape}"`);
396
+ this.escape = "";
397
+ } else if (ch === "\\") {
398
+ this.escape = ch;
399
+ continue;
400
+ } else if (ch === '"') {
401
+ if (this.keyMode) {
402
+ const parent = top();
403
+ if (
404
+ !this.partial &&
405
+ parent.lastKey !== undefined &&
406
+ this.key <= parent.lastKey
407
+ )
408
+ throw new ContractError();
409
+ parent.lastKey = this.key;
410
+ parent.sensitive = sensitive.test(this.key);
411
+ parent.expect = "colon";
412
+ if (!parent.hide) {
413
+ const safeKey = sanitizeText(this.key, this.secrets);
414
+ if (
415
+ !this.partial &&
416
+ parent.lastOutputKey !== undefined &&
417
+ safeKey <= parent.lastOutputKey
418
+ )
419
+ throw new ContractError();
420
+ parent.lastOutputKey = safeKey;
421
+ this.redacted ||= safeKey !== this.key;
422
+ output += JSON.stringify(safeKey);
423
+ }
424
+ this.key = "";
425
+ } else {
426
+ if (!this.suppressed) output += `${encoded(this.text.end())}"`;
427
+ this.redacted ||= this.text.redacted;
428
+ done();
429
+ }
430
+ this.inString = false;
431
+ continue;
432
+ }
433
+ if (this.keyMode) {
434
+ this.key += decoded;
435
+ if (utf8Bytes(this.key) > 8192) throw new ContractError("oversize");
436
+ } else if (!this.suppressed)
437
+ output += encoded(this.text.write(decoded));
438
+ continue;
439
+ }
440
+ if (this.token && /[\s,\]}]/.test(ch)) primitive();
441
+ if (this.token) {
442
+ this.token += ch;
443
+ if (this.token.length > 128) throw new ContractError("oversize");
444
+ continue;
445
+ }
446
+ if (/\s/.test(ch)) continue;
447
+ const parent = top();
448
+ if (ch === '"') {
449
+ this.keyMode = parent?.kind === "object" && parent.expect === "key";
450
+ this.suppressed = this.keyMode ? parent.hide : begin();
451
+ this.inString = true;
452
+ this.text = new TextSanitizer(this.secrets);
453
+ if (!this.keyMode && !this.suppressed) output += '"';
454
+ } else if (ch === "{" || ch === "[") {
455
+ const hide = begin();
456
+ if (!hide) output += ch;
457
+ if (this.stack.length >= 64) throw new ContractError("oversize");
458
+ this.stack.push({
459
+ kind: ch === "{" ? "object" : "array",
460
+ expect: ch === "{" ? "key" : "value",
461
+ hide,
462
+ sensitive: false,
463
+ count: 0,
464
+ lastKey: undefined,
465
+ lastOutputKey: undefined,
466
+ });
467
+ } else if (ch === "}" || ch === "]") {
468
+ if (!parent || (ch === "}") !== (parent.kind === "object"))
469
+ throw new ContractError();
470
+ if (
471
+ parent.expect !== "comma" &&
472
+ !(
473
+ parent.count === 0 &&
474
+ parent.expect === (parent.kind === "object" ? "key" : "value")
475
+ )
476
+ )
477
+ throw new ContractError();
478
+ if (!parent.hide) output += ch;
479
+ this.stack.pop();
480
+ done();
481
+ } else if (ch === ":") {
482
+ if (parent?.expect !== "colon") throw new ContractError();
483
+ parent.expect = "value";
484
+ if (!parent.hide) output += ch;
485
+ } else if (ch === ",") {
486
+ if (parent?.expect !== "comma") throw new ContractError();
487
+ parent.expect = parent.kind === "object" ? "key" : "value";
488
+ parent.count = Math.max(1, parent.count);
489
+ if (!parent.hide) output += ch;
490
+ } else {
491
+ this.suppressed = begin();
492
+ this.token = ch;
493
+ }
494
+ }
495
+ if (final) {
496
+ if (this.token) primitive();
497
+ if (this.partial) {
498
+ // This field is UTF-8 partial source, not canonical final arguments.
499
+ // Retain unfinished strings/keys without manufacturing closing syntax.
500
+ if (this.inString && !this.suppressed) {
501
+ output += this.keyMode
502
+ ? JSON.stringify(sanitizeText(this.key, this.secrets)).slice(0, -1)
503
+ : encoded(this.text.end());
504
+ }
505
+ if (this.escape) {
506
+ output += marker;
507
+ this.redacted = true;
508
+ this.limited = true;
509
+ }
510
+ } else if (
511
+ this.inString ||
512
+ this.escape ||
513
+ this.stack.length ||
514
+ this.root !== "done"
515
+ )
516
+ throw new ContractError();
517
+ this.redacted ||= this.text.redacted;
518
+ }
519
+ return output;
520
+ }
521
+ end() {
522
+ return this.write("", true);
523
+ }
524
+ }
525
+
526
+ /** Canonical ordinary JSON without materializing a whole large tool body.
527
+ * The caller holds the TextSanitizer prefix/expansion allowance. Traversal tables,
528
+ * sanitized keys and source/output slices are charged BEFORE copying, and held
529
+ * across each yield (including the consumer's original awaited writes). Iterator
530
+ * close releases only these no-longer-used copies, never an emitter write lease. */
531
+ export function* sanitizedJsonChunks(value, secrets, reserve) {
532
+ measureData(value, LIMITS.detailBytes * 8);
533
+ function* visit(v) {
534
+ if (typeof v === "string") {
535
+ yield '"';
536
+ const sanitizer = new TextSanitizer(secrets);
537
+ for (let at = 0; at < v.length; at += 1024) {
538
+ // At most three UTF-8 bytes per UTF-16 unit, with conservative literal
539
+ // replacement/JSON escaping expansion. Reserve before slice/projection.
540
+ const slice = reserve(Math.min(1024, v.length - at) * 36 + 64);
541
+ try {
542
+ const clean = sanitizer.write(v.slice(at, at + 1024));
543
+ yield JSON.stringify(clean).slice(1, -1);
544
+ } finally {
545
+ slice.release();
546
+ }
547
+ }
548
+ // Remaining known-literal/heuristic prefixes and their expansion remain
549
+ // covered by the caller's prefix lease until this final yield settles.
550
+ yield JSON.stringify(sanitizer.end()).slice(1, -1);
551
+ yield '"';
552
+ } else if (v === null || typeof v !== "object") yield JSON.stringify(v);
553
+ else if (Array.isArray(v)) {
554
+ yield "[";
555
+ const length = own(v, "length");
556
+ for (let index = 0; index < length; index++) {
557
+ if (index) yield ",";
558
+ yield* visit(own(v, String(index)));
559
+ }
560
+ yield "]";
561
+ } else {
562
+ const table = reserve(64);
563
+ let previous;
564
+ try {
565
+ // No Object.keys/sort copy before reservation. These are validated own
566
+ // data objects; key references and their UTF-16 storage are conservative
567
+ // traversal charges, not a claim about all SDK/V8 memory.
568
+ const keys = [];
569
+ for (const key in v) {
570
+ if (!Object.hasOwn(v, key)) continue;
571
+ const bytes = sourceBytes(key);
572
+ // Same completed-key bound as the owner/partial lexical projection.
573
+ if (bytes > 8192) throw new ContractError("oversize");
574
+ table.grow(bytes * 2 + 16);
575
+ keys.push(key);
576
+ }
577
+ keys.sort();
578
+ yield "{";
579
+ let lastKey;
580
+ for (let index = 0; index < keys.length; index++) {
581
+ const key = keys[index];
582
+ // Whole-key sanitization, escaped output and retained comparison key.
583
+ // Keep the old comparison lease until the replacement is allocated.
584
+ const current = reserve(sourceBytes(key) * 24 + 64);
585
+ try {
586
+ const safe = sanitizeText(key, secrets);
587
+ if (lastKey !== undefined && safe <= lastKey)
588
+ throw new ContractError();
589
+ lastKey = safe;
590
+ previous?.release();
591
+ previous = current;
592
+ if (index) yield ",";
593
+ yield `${JSON.stringify(safe)}:`;
594
+ if (sensitive.test(key)) yield JSON.stringify(marker);
595
+ else yield* visit(own(v, key));
596
+ } finally {
597
+ if (previous !== current) current.release();
598
+ }
599
+ }
600
+ yield "}";
601
+ } finally {
602
+ previous?.release();
603
+ table.release();
604
+ }
605
+ }
606
+ }
607
+ yield* visit(value);
608
+ }