@abelxiaoxing/cadence 1.8.3 → 2.0.0

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 (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. 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
+ }