@dsh-electron/dsh-plugin-multi-root-workspace 0.1.13 → 0.1.15

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 (84) hide show
  1. package/CHANGELOG.en.md +36 -1
  2. package/CHANGELOG.md +36 -1
  3. package/README.en.md +27 -10
  4. package/README.md +27 -10
  5. package/cordis.patch.yml +5 -1
  6. package/detail/DETAIL.en.md +1 -0
  7. package/detail/DETAIL.md +1 -0
  8. package/detail/preview.svg +1 -0
  9. package/detail/preview.zh.svg +1 -0
  10. package/lib/{llm-message-fYORacvh.js → agent-instructions-BXuKhOzr.js} +1 -84
  11. package/lib/auto-review.js +2 -2
  12. package/lib/client.js +584 -73
  13. package/lib/client.js.map +1 -1
  14. package/lib/{command-B9855H7y.js → command-y5tfcAeC.js} +45 -12
  15. package/lib/command.js +1 -1
  16. package/lib/compat-CTJbpCxu.js +267 -0
  17. package/lib/compat.js +1 -1
  18. package/lib/{dialects-Dr4rDR-H.js → dialects-CdlG3RF9.js} +1 -1
  19. package/lib/{compat-Bg2HFVMX.js → dsh-version-DCwF7REk.js} +5 -265
  20. package/lib/{fs-CfzJq2wd.js → fs-Uws0ZpmV.js} +38 -0
  21. package/lib/fs.js +1 -1
  22. package/lib/index.js +11 -9
  23. package/lib/{instructions-KsUwUFE0.js → instructions-Ck9wp4J4.js} +695 -26
  24. package/lib/instructions.js +1 -1
  25. package/lib/llm-message-CepyzEBQ.js +84 -0
  26. package/lib/lsp.js +3 -3
  27. package/lib/{metadata-DgqSdVX_.js → metadata-99jgViHQ.js} +1 -1
  28. package/lib/{method-wrapper-CFY3p3j2.js → method-wrapper-D4oUCsEF.js} +22 -2
  29. package/lib/{native-win32-2waCRkPI.js → native-win32-C5oMtt7f.js} +2 -1
  30. package/lib/ops-compatibility.js +58 -0
  31. package/lib/ops-settings-UN2oOKEp.js +40 -0
  32. package/lib/ops-tools-k4JYaOrM.js +152 -0
  33. package/lib/process-fence.js +100 -24
  34. package/lib/{protocol-CJOvAvOS.js → protocol-CwDnFEyK.js} +54 -8
  35. package/lib/{registry-Bv431TfO.js → registry-DyDNDSJZ.js} +235 -79
  36. package/lib/{registry-lease-win32-DVTq9d4D.js → registry-lease-win32-r4puac9m.js} +2 -1
  37. package/lib/registry.js +2 -2
  38. package/lib/{review-context-NDpKeMCc.js → review-context-B-oHkU6a.js} +24 -7
  39. package/lib/review-context.js +1 -1
  40. package/lib/{roots-Ca_W1S9n.js → roots-CmaWxRVy.js} +112 -22
  41. package/lib/{sandbox-C-vCJCp3.js → sandbox-CZElTNPz.js} +1 -1
  42. package/lib/sandbox.js +1 -1
  43. package/lib/scope.js +72 -9
  44. package/lib/skills-utyuvmwr.js +104 -0
  45. package/lib/skills.js +4 -105
  46. package/lib/{tool-context-CpgIFcCB.js → tool-context-CCeY3mrK.js} +1 -1
  47. package/lib/types/authority/protocol.d.ts +5 -1
  48. package/lib/types/client/RootSwitcher.d.ts +2 -0
  49. package/lib/types/client/auto-review-settings.d.ts +18 -0
  50. package/lib/types/client/files-tree/FilesTree.d.ts +4 -1
  51. package/lib/types/client/files-tree/locales.d.ts +1 -1
  52. package/lib/types/client/locales.d.ts +40 -0
  53. package/lib/types/client/open-in-app/locales.d.ts +1 -1
  54. package/lib/types/client/ops-settings.d.ts +6 -0
  55. package/lib/types/client/skills-settings.d.ts +13 -0
  56. package/lib/types/client/styles.d.ts +1 -1
  57. package/lib/types/command.d.ts +3 -3
  58. package/lib/types/compat/auto-review-client.d.ts +58 -0
  59. package/lib/types/compat/connection-rpc.d.ts +12 -0
  60. package/lib/types/compat/dsh-version.d.ts +1 -1
  61. package/lib/types/compat/files-tree.d.ts +36 -0
  62. package/lib/types/compat/fs-patch.d.ts +6 -0
  63. package/lib/types/compat/instruction-activity.d.ts +32 -1
  64. package/lib/types/compat/koffi.d.ts +2 -0
  65. package/lib/types/compat/ops-confirmation.d.ts +17 -0
  66. package/lib/types/compat/ops-settings.d.ts +11 -0
  67. package/lib/types/compat/ops-tools.d.ts +25 -0
  68. package/lib/types/compat/skills-client.d.ts +1 -0
  69. package/lib/types/compat/tool-context.d.ts +4 -0
  70. package/lib/types/compat.d.ts +2 -2
  71. package/lib/types/fs.d.ts +3 -1
  72. package/lib/types/instructions.d.ts +6 -1
  73. package/lib/types/ops-compatibility.d.ts +34 -0
  74. package/lib/types/registry.d.ts +67 -8
  75. package/lib/types/review-context.d.ts +2 -0
  76. package/lib/types/roots.d.ts +114 -20
  77. package/lib/types/scope.d.ts +28 -2
  78. package/lib/workspace-files.js +3 -3
  79. package/lib/{workspace-routing-YPxaQ4V3.js → workspace-routing-D_qwMUh-.js} +4 -4
  80. package/locale/en.json +7 -0
  81. package/locale/zh.json +7 -0
  82. package/package.json +93 -82
  83. package/src/auto-review/README.en.md +1 -1
  84. package/src/auto-review/README.md +1 -1
@@ -1,8 +1,10 @@
1
1
  import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
2
  import { c as withRegistryRead, s as withRegistryAdmission } from "./registry-operation-C2ZV3Jhw.js";
3
- import { n as instructionsApi, t as createInstructionMessage } from "./llm-message-fYORacvh.js";
4
- import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
5
- import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
3
+ import { t as instructionsApi } from "./agent-instructions-BXuKhOzr.js";
4
+ import { t as createInstructionMessage } from "./llm-message-CepyzEBQ.js";
5
+ import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
6
+ import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
7
+ import { l as isCanonicallyUnder } from "./roots-CmaWxRVy.js";
6
8
  import { createHash } from "node:crypto";
7
9
  import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
8
10
  import "@deepseek-ai/cordis";
@@ -45,11 +47,96 @@ function ptcFileActivity(event) {
45
47
  failed: data.isError !== false || data.error !== void 0
46
48
  };
47
49
  }
50
+ const OPS_STABLE_MAX_BYTES = 2097152;
51
+ function stableOpsJson(value) {
52
+ const seen = /* @__PURE__ */ new Set();
53
+ let nodes = 0, bytes = 0;
54
+ const over = () => {
55
+ throw new Error("multi-root workspace: OPS fingerprint exceeded its budget");
56
+ };
57
+ const walk = (entry, depth) => {
58
+ if (depth > 24) over();
59
+ nodes += 1;
60
+ if (nodes > 65536) over();
61
+ if (entry === void 0) over();
62
+ if (entry === null || typeof entry === "string" || typeof entry === "boolean") {
63
+ if (typeof entry === "string") {
64
+ bytes += entry.length;
65
+ if (bytes > OPS_STABLE_MAX_BYTES) over();
66
+ }
67
+ return entry;
68
+ }
69
+ if (typeof entry === "number") {
70
+ if (!Number.isFinite(entry)) over();
71
+ return entry;
72
+ }
73
+ if (typeof entry !== "object" || entry === null) over();
74
+ const obj = entry;
75
+ if (seen.has(obj)) over();
76
+ seen.add(obj);
77
+ if (Array.isArray(obj)) {
78
+ const out = obj.map((item) => walk(item, depth + 1));
79
+ seen.delete(obj);
80
+ return out;
81
+ }
82
+ const keys = Object.keys(obj);
83
+ if (keys.length > 4096) over();
84
+ for (const key of keys) {
85
+ bytes += key.length;
86
+ if (bytes > OPS_STABLE_MAX_BYTES) over();
87
+ }
88
+ const out = Object.fromEntries(keys.sort().map((key) => [key, walk(obj[key], depth + 1)]));
89
+ seen.delete(obj);
90
+ return out;
91
+ };
92
+ const json = JSON.stringify(walk(value, 0));
93
+ if (json === void 0 || Buffer.byteLength(json, "utf8") > OPS_STABLE_MAX_BYTES) over();
94
+ return json;
95
+ }
96
+ const OPS_OPSID = (kind, id, name, args) => JSON.stringify([
97
+ kind,
98
+ id,
99
+ name,
100
+ createHash("sha256").update(stableOpsJson(args)).digest("hex")
101
+ ]);
102
+ const opsActivityEvidenceKey = OPS_OPSID;
103
+ const OPS_PUBLISH = {
104
+ ops_discover_tools: true,
105
+ ops_load_sdk: true
106
+ };
48
107
  /** Rebuild read-only discovery candidates from successful durable activity after resume. */
49
- function replayedInstructionTargets(events, origin) {
108
+ function replayedInstructionTargets(events, origin, liveOpsEvidence, pendingExec, options) {
109
+ const opsOn = options?.opsCompatibility === true;
50
110
  let cwd = origin;
51
111
  const pending = /* @__PURE__ */ new Map();
112
+ const eventsPrior = /* @__PURE__ */ new Map();
113
+ const ended = /* @__PURE__ */ new Set();
52
114
  const paths = /* @__PURE__ */ new Set();
115
+ const isCurrentExec = (call) => {
116
+ if (pendingExec === void 0 || call.name !== pendingExec.name) return false;
117
+ const kind = pendingExec.parent === void 0 ? "native" : "ptc";
118
+ if (call.kind !== kind || call.id !== pendingExec.callId) return false;
119
+ let parsed;
120
+ try {
121
+ parsed = JSON.parse(call.arguments);
122
+ return stableOpsJson(parsed) === stableOpsJson(pendingExec.arguments);
123
+ } catch {
124
+ return false;
125
+ }
126
+ };
127
+ const opsEntry = (call) => {
128
+ if (liveOpsEvidence === void 0) throw new Error("review-context: OPS file activity lacks live evidence");
129
+ let parsed;
130
+ try {
131
+ parsed = JSON.parse(call.arguments);
132
+ } catch {
133
+ throw new Error("review-context: incomplete durable OPS arguments");
134
+ }
135
+ const entry = liveOpsEvidence.get(OPS_OPSID(call.kind, call.id, call.name, parsed));
136
+ if (entry === void 0) throw new Error("review-context: OPS file activity lacks live evidence");
137
+ if (entry.complete !== true) throw new Error("review-context: incomplete OPS file activity evidence");
138
+ return entry;
139
+ };
53
140
  for (const raw of events) {
54
141
  const event = raw;
55
142
  if (event.type === "working-directory/change" && typeof event.data.cwd === "string") {
@@ -65,7 +152,13 @@ function replayedInstructionTargets(events, origin) {
65
152
  arguments: ptc.arguments
66
153
  } : event.data;
67
154
  if (typeof call.callId !== "string" || typeof call.name !== "string" || typeof call.arguments !== "string") throw new Error("review-context: incomplete durable file activity");
68
- pending.set(call.callId, {
155
+ const kind = ptc?.kind === "start" ? "ptc" : "native";
156
+ const id = ptc?.kind === "start" ? ptc.id.slice(4) : call.callId;
157
+ const key = JSON.stringify([kind, id]);
158
+ if (opsOn && call.name.startsWith("ops_") && (pending.has(key) || ended.has(key))) throw new Error("review-context: duplicate durable OPS call");
159
+ pending.set(key, {
160
+ kind,
161
+ id,
69
162
  name: call.name,
70
163
  arguments: call.arguments,
71
164
  directories: new Set(cwd === void 0 ? [] : [cwd])
@@ -75,12 +168,32 @@ function replayedInstructionTargets(events, origin) {
75
168
  }
76
169
  if (event.type !== "tool/result" && ptc?.kind !== "end") continue;
77
170
  const message = event.data.message;
78
- const id = ptc?.kind === "end" ? ptc.id : message?.source?.callId;
79
- if (typeof id !== "string") throw new Error("review-context: incomplete durable result");
80
- const call = pending.get(id);
81
- pending.delete(id);
82
- if (call === void 0) continue;
83
- if (ptc?.kind === "end" ? ptc.failed : event.data.error !== void 0 || toolResultFailed(message ?? {})) continue;
171
+ const rawId = ptc?.kind === "end" ? ptc.id : message?.source?.callId;
172
+ if (typeof rawId !== "string") throw new Error("review-context: incomplete durable result");
173
+ const kind = ptc?.kind === "end" ? "ptc" : "native";
174
+ const id = ptc?.kind === "end" ? ptc.id.slice(4) : rawId;
175
+ const key = JSON.stringify([kind, id]);
176
+ const call = pending.get(key);
177
+ if (call === void 0) {
178
+ const prior = eventsPrior.get(key);
179
+ if (opsOn && prior !== void 0 && prior.name.startsWith("ops_") && OPS_PUBLISH[prior.name] !== true) throw new Error("review-context: duplicate durable OPS result");
180
+ continue;
181
+ }
182
+ pending.delete(key);
183
+ if (opsOn && call.name.startsWith("ops_") && OPS_PUBLISH[call.name] !== true) {
184
+ eventsPrior.set(key, call);
185
+ ended.add(key);
186
+ if (ended.size > 512) throw new Error("review-context: durable OPS result index exceeded");
187
+ }
188
+ const failed = ptc?.kind === "end" ? ptc.failed : event.data.error !== void 0 || toolResultFailed(message ?? {});
189
+ if (opsOn && call.name.startsWith("ops_") && OPS_PUBLISH[call.name] !== true) {
190
+ const entry = opsEntry(call);
191
+ if (failed && entry.targets.length > 0) throw new Error("review-context: OPS activity contradicts its durable failure");
192
+ for (const target of entry.targets) paths.add(target.path);
193
+ if (paths.size > 512) throw new Error("review-context: durable work index exceeded");
194
+ continue;
195
+ }
196
+ if (failed) continue;
84
197
  const path = touchedPathOfToolCall(call.name, call.arguments);
85
198
  if (path === void 0) continue;
86
199
  if (isAbsolute(path)) paths.add(path);
@@ -90,6 +203,12 @@ function replayedInstructionTargets(events, origin) {
90
203
  }
91
204
  if (paths.size > 512) throw new Error("review-context: durable work index exceeded");
92
205
  }
206
+ for (const call of pending.values()) {
207
+ if (!call.name.startsWith("ops_") || OPS_PUBLISH[call.name] === true) continue;
208
+ if (!opsOn) continue;
209
+ if (isCurrentExec(call)) continue;
210
+ throw new Error("review-context: OPS file activity has no final result");
211
+ }
93
212
  return [...paths];
94
213
  }
95
214
  /** fs tools whose success touches the file at their `file_path` argument. */
@@ -110,6 +229,344 @@ function touchedPathOfToolCall(name, argumentsJson) {
110
229
  if (typeof parsed !== "object" || parsed === null || !("file_path" in parsed) || typeof parsed.file_path !== "string") return void 0;
111
230
  return parsed.file_path.trim() || void 0;
112
231
  }
232
+ const OPS_READ_TARGETS = {
233
+ inspect_local_file: true,
234
+ stat: true,
235
+ read_files: true
236
+ };
237
+ const OPS_WRITE_TARGETS = {
238
+ write_file: true,
239
+ edit_file: true,
240
+ replace: true,
241
+ copy: true
242
+ };
243
+ const OPS_TARGETS = {
244
+ ...OPS_READ_TARGETS,
245
+ ...OPS_WRITE_TARGETS,
246
+ move: true,
247
+ remove: true
248
+ };
249
+ function record(value) {
250
+ return typeof value === "object" && value !== null && !Array.isArray(value);
251
+ }
252
+ function canonicalize(path) {
253
+ if (!isAbsolute(path)) return void 0;
254
+ try {
255
+ return canonicalPath(path);
256
+ } catch {
257
+ return;
258
+ }
259
+ }
260
+ function itemPath(item) {
261
+ if (!record(item) || typeof item.path !== "string" || item.path === "") return void 0;
262
+ return item.path;
263
+ }
264
+ function canonicalizeArg(path, cwd) {
265
+ if (typeof path !== "string" || path.trim() === "") return void 0;
266
+ const absolute = isAbsolute(path) ? path : cwd === void 0 ? void 0 : resolve(cwd, path);
267
+ return absolute === void 0 ? void 0 : canonicalize(absolute);
268
+ }
269
+ function requestCanonicals(name, args, cwd) {
270
+ if (!record(args)) return void 0;
271
+ if (name === "ops_read_files") {
272
+ if (!Array.isArray(args.files) || args.files.length === 0 || args.files.length > 32) return void 0;
273
+ const paths = args.files.map((file) => canonicalizeArg(record(file) ? file.path : void 0, cwd));
274
+ return paths.every((path) => path !== void 0) ? paths : void 0;
275
+ }
276
+ const path = canonicalizeArg(args.path, cwd);
277
+ if (path === void 0) return void 0;
278
+ const to = name === "ops_copy" || name === "ops_move" ? canonicalizeArg(args.to, cwd) : void 0;
279
+ if ((name === "ops_copy" || name === "ops_move") && to === void 0) return void 0;
280
+ return to === void 0 ? [path] : [path, to];
281
+ }
282
+ function observed(observedPaths, path) {
283
+ const canonical = canonicalize(path);
284
+ return canonical !== void 0 && observedPaths.some((candidate) => canonicalize(candidate) === canonical);
285
+ }
286
+ const REVISION = /^[0-9a-f]{64}$/;
287
+ const safeBytes = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
288
+ function dedupe(targets) {
289
+ const seen = /* @__PURE__ */ new Set();
290
+ return targets.filter((target) => {
291
+ const key = `${target.kind}:${canonicalize(target.path) ?? target.path}`;
292
+ if (seen.has(key)) return false;
293
+ seen.add(key);
294
+ return true;
295
+ });
296
+ }
297
+ const inside = (directory, path) => {
298
+ const rel = relative(directory, path);
299
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
300
+ };
301
+ function normalizeOpsFileActivity(input) {
302
+ const bare = input.name.startsWith("ops_") ? input.name.slice(4) : void 0;
303
+ if (bare === void 0 || OPS_TARGETS[bare] !== true) return {
304
+ targets: [],
305
+ complete: false
306
+ };
307
+ const value = input.finalValue;
308
+ if (input.isError === true) return {
309
+ targets: [],
310
+ complete: record(value) && record(value.error) && value.error.effectState === "none"
311
+ };
312
+ if (!record(value) || typeof value.status !== "string") return {
313
+ targets: [],
314
+ complete: false
315
+ };
316
+ if (value.status === "error") return {
317
+ targets: [],
318
+ complete: (record(value.error) ? value.error.effectState : void 0) === "none"
319
+ };
320
+ if (value.status !== "completed" && value.status !== "partial") return {
321
+ targets: [],
322
+ complete: false
323
+ };
324
+ const receipt = "data" in value ? record(value.data) ? value.data : void 0 : value;
325
+ if (!record(receipt)) return {
326
+ targets: [],
327
+ complete: false
328
+ };
329
+ const outerPartial = value.status === "partial";
330
+ if (record(value.error) || record(receipt.error) || typeof receipt.status === "string" && receipt.status !== "completed" && receipt.status !== "partial") return {
331
+ targets: [],
332
+ complete: false
333
+ };
334
+ const outerCwd = typeof receipt.resolvedFrom === "string" ? receipt.resolvedFrom : void 0;
335
+ if (outerCwd !== void 0 && input.cwd !== void 0 && canonicalize(outerCwd) !== canonicalize(input.cwd)) return {
336
+ targets: [],
337
+ complete: false
338
+ };
339
+ const proven = (path) => {
340
+ if (path === void 0) return void 0;
341
+ const canonical = canonicalize(path);
342
+ return canonical !== void 0 && observed(input.observedPaths, canonical) ? canonical : void 0;
343
+ };
344
+ if (OPS_WRITE_TARGETS[bare] === true || bare === "move" || bare === "remove") {
345
+ const args = record(input.args) ? input.args : {};
346
+ if (receipt.status !== "completed") return {
347
+ targets: [],
348
+ complete: false
349
+ };
350
+ if (record(receipt.error)) return {
351
+ targets: [],
352
+ complete: false
353
+ };
354
+ if (args.dryRun === true || receipt.dryRun === true || receipt.planned === true) return {
355
+ targets: [],
356
+ complete: true
357
+ };
358
+ if (receipt.phase !== "verify") return {
359
+ targets: [],
360
+ complete: false
361
+ };
362
+ if (receipt.changed === 0) return {
363
+ targets: [],
364
+ complete: true
365
+ };
366
+ if (typeof receipt.changed !== "number" || !Number.isSafeInteger(receipt.changed) || receipt.changed <= 0) return {
367
+ targets: [],
368
+ complete: false
369
+ };
370
+ const items = Array.isArray(receipt.items) ? receipt.items : void 0;
371
+ if (items === void 0 || items.length === 0) return {
372
+ targets: [],
373
+ complete: false
374
+ };
375
+ const request = requestCanonicals(input.name, input.args, input.cwd);
376
+ const targets = [];
377
+ let unproven = false;
378
+ let destProven = false;
379
+ let sourceProven = false;
380
+ for (const item of items) {
381
+ const path = itemPath(item);
382
+ const canonical = proven(path);
383
+ if (path === void 0 || canonical === void 0) {
384
+ unproven = true;
385
+ continue;
386
+ }
387
+ if (record(item) && record(item.error)) {
388
+ unproven = true;
389
+ continue;
390
+ }
391
+ if (record(item) && item.removed === true) {
392
+ if ((bare === "move" || bare === "remove") && request !== void 0 && request[0] === canonical) {
393
+ targets.push({
394
+ path: canonical,
395
+ kind: "remove"
396
+ });
397
+ sourceProven = true;
398
+ } else unproven = true;
399
+ continue;
400
+ }
401
+ if (bare === "remove") {
402
+ unproven = true;
403
+ continue;
404
+ }
405
+ const bytesAfter = record(item) && safeBytes(item.bytesAfter);
406
+ const revision = record(item) && (typeof item.revision === "string" && REVISION.test(item.revision) || typeof item.revisionAfter === "string" && REVISION.test(item.revisionAfter));
407
+ if (!bytesAfter || !revision) {
408
+ unproven = true;
409
+ continue;
410
+ }
411
+ if (!(bare === "replace" ? request !== void 0 && (request[0] === canonical || inside(request[0], canonical)) : request !== void 0 && request[bare === "copy" || bare === "move" ? 1 : 0] === canonical)) {
412
+ unproven = true;
413
+ continue;
414
+ }
415
+ targets.push({
416
+ path: canonical,
417
+ kind: "write"
418
+ });
419
+ destProven = true;
420
+ }
421
+ if (bare === "move" && (!destProven || !sourceProven)) unproven = true;
422
+ const complete = !unproven && !outerPartial && receipt.complete === true && (receipt.failed === void 0 || receipt.failed === 0) && (receipt.pending === void 0 || receipt.pending === 0);
423
+ return {
424
+ targets: dedupe(targets),
425
+ complete
426
+ };
427
+ }
428
+ if (bare === "stat") {
429
+ const canonical = proven(itemPath(receipt));
430
+ const request = canonicalizeArg(record(input.args) ? input.args.path : void 0, input.cwd);
431
+ const provenStat = canonical !== void 0 && receipt.exists === true && typeof receipt.revision === "string" && REVISION.test(receipt.revision) && safeBytes(receipt.bytes) && request === canonical;
432
+ return {
433
+ targets: provenStat ? [{
434
+ path: canonical,
435
+ kind: "read"
436
+ }] : [],
437
+ complete: provenStat === true && receipt.complete === true
438
+ };
439
+ }
440
+ const topItems = Array.isArray(receipt.items) ? receipt.items : void 0;
441
+ if (topItems === void 0 && bare === "inspect_local_file") return {
442
+ targets: [],
443
+ complete: false
444
+ };
445
+ const requests = requestCanonicals(input.name, input.args, input.cwd);
446
+ const targets = [];
447
+ let unproven = false;
448
+ if (bare === "read_files") {
449
+ if (topItems === void 0 || requests === void 0) return {
450
+ targets: [],
451
+ complete: false
452
+ };
453
+ const provenIndexes = /* @__PURE__ */ new Set();
454
+ for (const [index, item] of topItems.entries()) {
455
+ if (!record(item) || item.status !== "completed" || record(item.error)) {
456
+ unproven = true;
457
+ continue;
458
+ }
459
+ const canonical = proven(itemPath(item));
460
+ const expected = requests[index];
461
+ if (canonical === void 0 || expected === void 0 || canonical !== expected) {
462
+ unproven = true;
463
+ continue;
464
+ }
465
+ if (typeof item.revision !== "string" || !REVISION.test(item.revision) || !safeBytes(item.bytes)) {
466
+ unproven = true;
467
+ continue;
468
+ }
469
+ provenIndexes.add(index);
470
+ targets.push({
471
+ path: canonical,
472
+ kind: "read"
473
+ });
474
+ }
475
+ for (const index of requests.keys()) if (!provenIndexes.has(index)) unproven = true;
476
+ const complete = !unproven && !outerPartial && provenIndexes.size === requests.length && receipt.complete === true && (receipt.failed === void 0 || receipt.failed === 0) && (receipt.pending === void 0 || receipt.pending === 0);
477
+ return {
478
+ targets: dedupe(targets),
479
+ complete
480
+ };
481
+ }
482
+ if (receipt.status !== "completed" || record(receipt.error)) return {
483
+ targets: [],
484
+ complete: false
485
+ };
486
+ const rows = topItems ?? [];
487
+ const request = requests?.[0];
488
+ let path = proven(itemPath(receipt));
489
+ let unprovenRow = false;
490
+ for (const row of rows) {
491
+ if (!record(row) || record(row.error)) {
492
+ unprovenRow = true;
493
+ continue;
494
+ }
495
+ const rowPath = proven(itemPath(row));
496
+ if (rowPath === void 0) {
497
+ unprovenRow = true;
498
+ continue;
499
+ }
500
+ const textRow = Number.isSafeInteger(row.line) && row.line > 0 && typeof row.text === "string";
501
+ const hexRow = row.view === "hex" && Number.isSafeInteger(row.offset) && row.offset >= 0 && typeof row.text === "string";
502
+ if (!textRow && !hexRow) {
503
+ unprovenRow = true;
504
+ continue;
505
+ }
506
+ if (path === void 0) path = rowPath;
507
+ else if (path !== rowPath) unprovenRow = true;
508
+ }
509
+ const provenInspect = path !== void 0 && request === path && !unprovenRow && typeof receipt.revision === "string" && REVISION.test(receipt.revision) && safeBytes(receipt.bytes);
510
+ return {
511
+ targets: provenInspect === true ? [{
512
+ path,
513
+ kind: "read"
514
+ }] : [],
515
+ complete: provenInspect === true && receipt.complete === true && !outerPartial
516
+ };
517
+ }
518
+ const PENDING_EMPTY = {
519
+ discover_tools: true,
520
+ load_sdk: true
521
+ };
522
+ const PENDING_PATH = {
523
+ inspect_local_file: true,
524
+ stat: true,
525
+ read_table: true,
526
+ read_docx: true,
527
+ read_pdf: true,
528
+ write_file: true,
529
+ edit_file: true,
530
+ remove: true
531
+ };
532
+ const PENDING_PAIR = {
533
+ copy: true,
534
+ move: true
535
+ };
536
+ function pendingInstructionTargets(name, args) {
537
+ if (FILE_TOUCH_TOOL_NAMES[name] === true) {
538
+ const parsed = typeof args === "string" ? (() => {
539
+ try {
540
+ return JSON.parse(args);
541
+ } catch {
542
+ return;
543
+ }
544
+ })() : args;
545
+ if (!record(parsed) || typeof parsed.file_path !== "string" || parsed.file_path.trim() === "") return [];
546
+ return [parsed.file_path.trim()];
547
+ }
548
+ if (!name.startsWith("ops_")) return [];
549
+ const bare = name.slice(4);
550
+ if (PENDING_EMPTY[bare] === true) return [];
551
+ if (!record(args)) throw new Error(`review-context: ${name} arguments are incomplete`);
552
+ if (bare === "read_files") {
553
+ if (!Array.isArray(args.files) || args.files.length === 0 || args.files.length > 32) throw new Error(`review-context: ${name} files argument is incomplete`);
554
+ return args.files.map((file) => {
555
+ if (!record(file) || typeof file.path !== "string" || file.path.trim() === "") throw new Error(`review-context: ${name} files argument is incomplete`);
556
+ return file.path.trim();
557
+ });
558
+ }
559
+ if (PENDING_PAIR[bare] === true) {
560
+ const paths = [args.path, args.to];
561
+ if (paths.some((path) => typeof path !== "string" || path.trim() === "")) throw new Error(`review-context: ${name} path arguments are incomplete`);
562
+ return paths.map((path) => path.trim());
563
+ }
564
+ if (PENDING_PATH[bare] === true) {
565
+ if (typeof args.path !== "string" || args.path.trim() === "") throw new Error(`review-context: ${name} path argument is incomplete`);
566
+ return [args.path.trim()];
567
+ }
568
+ throw new Error(`review-context: ${name} has dynamic instruction targets that cannot be determined`);
569
+ }
113
570
  //#endregion
114
571
  //#region src/compat/instruction-overlap.ts
115
572
  /** Recognize files already represented by the native instruction baseline in this step. */
@@ -216,6 +673,9 @@ const DEFAULT_MAX_BYTES = 65536;
216
673
  const DEFAULT_MAX_SOURCE_BYTES = 32768;
217
674
  /** Cap on unanswered `tool/call` records remembered per session. */
218
675
  const MAX_PENDING_CALLS = 128;
676
+ const MAX_LIVE_OPS_CALLS = 128;
677
+ const MAX_LIVE_OPS_OBSERVED = 64;
678
+ const MAX_LIVE_OPS_EVIDENCE = 128;
219
679
  /** Cap on touched paths remembered per session; beyond it, touches are dropped. */
220
680
  const MAX_TOUCHED_PATHS = 64;
221
681
  /** Cap on delivered scopes remembered per session; beyond it, nested scopes stop. */
@@ -310,7 +770,199 @@ function apply(ctx, config = {}) {
310
770
  const states = /* @__PURE__ */ new WeakMap();
311
771
  const callsBySession = /* @__PURE__ */ new WeakMap();
312
772
  const executions = new AsyncLocalStorage();
313
- aroundTool(ctx, (exec, next) => executions.run(exec, next));
773
+ const fingerprintOf = (value) => createHash("sha256").update(stableOpsJson(value)).digest("hex");
774
+ const record = (value) => value !== null && typeof value === "object";
775
+ const keyOf = (exec) => exec.token ?? exec;
776
+ let disposed = false;
777
+ const opsLive = /* @__PURE__ */ new Map();
778
+ const removeEntry = (key) => {
779
+ const call = opsLive.get(key);
780
+ if (call === void 0) return void 0;
781
+ if (call.signal !== void 0 && call.abort !== void 0) call.signal.removeEventListener("abort", call.abort);
782
+ opsLive.delete(key);
783
+ return call;
784
+ };
785
+ const boundedLiveSet = (key, call) => {
786
+ if (opsLive.size >= MAX_LIVE_OPS_CALLS) {
787
+ const oldest = opsLive.keys().next();
788
+ if (!oldest.done) {
789
+ const evicted = removeEntry(oldest.value);
790
+ if (evicted !== void 0 && evicted.mode === opsCompatibilityToken(ctx)) stateOf(evicted.session).activityIncomplete = true;
791
+ }
792
+ }
793
+ opsLive.set(key, call);
794
+ };
795
+ const putEvidence = (state, key, entry) => {
796
+ const existing = state.liveOpsEvidence.get(key);
797
+ if (existing !== void 0) {
798
+ if (existing.complete === entry.complete && JSON.stringify(existing.targets) === JSON.stringify(entry.targets)) return;
799
+ if (existing.complete === true) {
800
+ state.activityIncomplete = true;
801
+ return;
802
+ }
803
+ } else if (state.liveOpsEvidence.size >= MAX_LIVE_OPS_EVIDENCE) {
804
+ const oldest = state.liveOpsEvidence.keys().next();
805
+ if (!oldest.done) state.liveOpsEvidence.delete(oldest.value);
806
+ state.activityIncomplete = true;
807
+ }
808
+ state.liveOpsEvidence.set(key, entry);
809
+ };
810
+ const mark = (call) => {
811
+ if (call.mode !== opsCompatibilityToken(ctx)) return;
812
+ const state = stateOf(call.session);
813
+ state.activityIncomplete = true;
814
+ try {
815
+ putEvidence(state, opsActivityEvidenceKey(call.kind, call.callId, call.name, call.args), {
816
+ targets: [],
817
+ complete: false
818
+ });
819
+ } catch (error) {
820
+ ctx.logger.warn(`multi-root instructions: OPS activity key failed: ${error.message}`);
821
+ }
822
+ };
823
+ aroundTool(ctx, (exec, next) => executions.run(exec, () => runLive(exec, next)));
824
+ const runLive = async (exec, next) => {
825
+ const agent = exec.agent;
826
+ const bare = exec.name.startsWith("ops_") ? exec.name.slice(4) : "";
827
+ const modeToken = opsCompatibilityToken(ctx);
828
+ if (bare === "" || bare === "discover_tools" || bare === "load_sdk" || agent === void 0 || disposed || modeToken === void 0) return await next();
829
+ let argsJson = "";
830
+ let incomplete = false;
831
+ try {
832
+ argsJson = stableOpsJson(exec.arguments);
833
+ } catch {
834
+ incomplete = true;
835
+ }
836
+ const directories = ctx.get("workingDirectory");
837
+ const ensured = directories === void 0 ? void 0 : await directories.ensure(agent, exec.signal).catch(() => void 0);
838
+ if (disposed || opsCompatibilityToken(ctx) !== modeToken) return await next();
839
+ const cwd = ensured ?? effectiveDirectory(ctx, agent.session) ?? agent.session?.header?.cwd;
840
+ const call = {
841
+ exec,
842
+ session: agent.session,
843
+ agent,
844
+ token: keyOf(exec),
845
+ kind: exec.parent === void 0 ? "native" : "ptc",
846
+ callId: exec.callId,
847
+ name: exec.name,
848
+ args: incomplete ? void 0 : JSON.parse(argsJson),
849
+ argsJson,
850
+ cwd,
851
+ observed: /* @__PURE__ */ new Set(),
852
+ incomplete,
853
+ signal: exec.signal,
854
+ mode: modeToken
855
+ };
856
+ if (exec.signal?.aborted === true) {
857
+ mark(call);
858
+ return await next();
859
+ }
860
+ boundedLiveSet(call.token, call);
861
+ call.abort = () => {
862
+ const removed = removeEntry(call.token);
863
+ if (removed !== void 0) mark(removed);
864
+ };
865
+ call.signal?.addEventListener("abort", call.abort);
866
+ let result;
867
+ try {
868
+ result = await next();
869
+ } catch (error) {
870
+ removeEntry(call.token);
871
+ throw error;
872
+ }
873
+ if (call.mode !== opsCompatibilityToken(ctx)) {
874
+ removeEntry(call.token);
875
+ return result;
876
+ }
877
+ try {
878
+ const raw = result !== null && typeof result === "object" && "value" in result ? result.value : result;
879
+ call.fingerprint = fingerprintOf(raw);
880
+ } catch {
881
+ call.incomplete = true;
882
+ ctx.logger.warn("multi-root instructions: OPS activity fingerprint exceeded its budget");
883
+ }
884
+ return result;
885
+ };
886
+ ctx.on.call(ctx, "tools/result", (exec, result) => {
887
+ const call = removeEntry(keyOf(exec));
888
+ if (call === void 0) return;
889
+ if (call.mode !== opsCompatibilityToken(ctx)) return;
890
+ try {
891
+ const final = result;
892
+ const sameArgs = (() => {
893
+ try {
894
+ return stableOpsJson(exec.arguments) === call.argsJson;
895
+ } catch {
896
+ return false;
897
+ }
898
+ })();
899
+ const identity = exec === call.exec && keyOf(exec) === call.token && exec.agent?.session === call.session && exec.name === call.name && exec.callId === call.callId && sameArgs && exec.signal?.aborted !== true && !disposed && call.incomplete !== true;
900
+ const effectNone = final.isError === true && record(final.value) && record(final.value.error) && final.value.error.effectState === "none";
901
+ if (!identity || call.fingerprint === void 0) {
902
+ mark(call);
903
+ return;
904
+ }
905
+ let same = false;
906
+ try {
907
+ same = fingerprintOf(final.value) === call.fingerprint;
908
+ } catch {
909
+ ctx.logger.warn("multi-root instructions: OPS final fingerprint exceeded its budget");
910
+ }
911
+ if (!same) {
912
+ mark(call);
913
+ return;
914
+ }
915
+ if (final.isError === true) {
916
+ if (effectNone === true) try {
917
+ putEvidence(stateOf(call.session), opsActivityEvidenceKey(call.kind, call.callId, call.name, call.args), {
918
+ targets: [],
919
+ complete: true
920
+ });
921
+ } catch {
922
+ mark(call);
923
+ }
924
+ else mark(call);
925
+ return;
926
+ }
927
+ const activity = normalizeOpsFileActivity({
928
+ name: call.name,
929
+ args: call.args,
930
+ cwd: call.cwd,
931
+ finalValue: final.value,
932
+ observedPaths: [...call.observed]
933
+ });
934
+ const state = stateOf(call.session);
935
+ if (!activity.complete) state.activityIncomplete = true;
936
+ for (const target of activity.targets) {
937
+ if (state.worked.size >= MAX_DELIVERED_SCOPES && !state.worked.has(target.path)) state.overflow = true;
938
+ else state.worked.add(target.path);
939
+ if (state.touched.size >= MAX_TOUCHED_PATHS) state.overflow = true;
940
+ else state.touched.add(target.path);
941
+ }
942
+ try {
943
+ putEvidence(state, opsActivityEvidenceKey(call.kind, call.callId, call.name, call.args), {
944
+ targets: activity.targets,
945
+ complete: activity.complete
946
+ });
947
+ } catch {
948
+ mark(call);
949
+ }
950
+ } catch (error) {
951
+ ctx.logger.warn(`multi-root instructions: live OPS activity observer failed: ${error.message}`);
952
+ mark(call);
953
+ }
954
+ }, { global: true });
955
+ const onDispose = ctx.on;
956
+ onDispose.call(ctx, "agent/disposed", (payload) => {
957
+ for (const [key, call] of [...opsLive]) if (call.agent === payload.agent) removeEntry(key);
958
+ }, { global: true });
959
+ onDispose.call(ctx, "session/disposed", (session) => {
960
+ for (const [key, call] of [...opsLive]) if (call.session === session) removeEntry(key);
961
+ }, { global: true });
962
+ ctx.effect(() => () => {
963
+ disposed = true;
964
+ for (const key of [...opsLive.keys()]) removeEntry(key);
965
+ });
314
966
  const stateOf = (session) => {
315
967
  const existing = states.get(session);
316
968
  if (existing !== void 0) return existing;
@@ -318,7 +970,8 @@ function apply(ctx, config = {}) {
318
970
  scopes: /* @__PURE__ */ new Map(),
319
971
  provenanceRoots: /* @__PURE__ */ new Set(),
320
972
  touched: /* @__PURE__ */ new Set(),
321
- worked: /* @__PURE__ */ new Set()
973
+ worked: /* @__PURE__ */ new Set(),
974
+ liveOpsEvidence: /* @__PURE__ */ new Map()
322
975
  };
323
976
  states.set(session, created);
324
977
  return created;
@@ -367,16 +1020,16 @@ function apply(ctx, config = {}) {
367
1020
  knownRoots(session) {
368
1021
  return [...states.get(session)?.provenanceRoots ?? []];
369
1022
  },
370
- async snapshot(agent, roots, target, signal) {
371
- return withRegistryRead(scope, scope.sandboxPolicy.resolve({ session: agent.session }).workspaceRoot, "instructions", async () => {
1023
+ async snapshot(agent, roots, target, signal, pending) {
1024
+ const modeToken = opsCompatibilityToken(scope);
1025
+ const snapshot = await withRegistryRead(scope, scope.sandboxPolicy.resolve({ session: agent.session }).workspaceRoot, "instructions", async () => {
1026
+ if (opsCompatibilityToken(scope) !== modeToken) throw new Error("review-context: OPS compatibility changed during the snapshot; retry");
372
1027
  const allowed = new Set(scope.multiRootScope.scopeOf(scope.sandboxPolicy.resolve({ session: agent.session }).workspaceRoot));
373
1028
  roots = roots.filter((root) => allowed.has(root));
374
1029
  signal.throwIfAborted();
375
- if (roots.length === 0) return [];
376
- const api = await instructionsApi();
377
- if (api === void 0) throw new Error("review-context: instruction discovery unavailable");
378
1030
  const existing = states.get(agent.session);
379
1031
  if (existing?.overflow) throw new Error("review-context: successful work index exceeded");
1032
+ if (modeToken !== void 0 && existing?.activityIncomplete === true) throw new Error("review-context: incomplete OPS file activity");
380
1033
  const state = {
381
1034
  scopes: new Map([...existing?.scopes ?? []].map(([key, value]) => [key, {
382
1035
  ...value,
@@ -384,10 +1037,15 @@ function apply(ctx, config = {}) {
384
1037
  }])),
385
1038
  provenanceRoots: new Set(existing?.provenanceRoots),
386
1039
  touched: /* @__PURE__ */ new Set([...existing?.touched ?? [], ...existing?.worked ?? []]),
387
- worked: /* @__PURE__ */ new Set()
1040
+ worked: /* @__PURE__ */ new Set(),
1041
+ liveOpsEvidence: new Map(existing?.liveOpsEvidence ?? [])
388
1042
  };
389
- for (const path of replayedInstructionTargets(agent.session.snapshotEvents(), agent.session.header.cwd)) state.touched.add(path);
390
- if (target !== void 0) state.touched.add(target);
1043
+ for (const path of replayedInstructionTargets(agent.session.snapshotEvents(), agent.session.header.cwd, modeToken === void 0 ? void 0 : existing?.liveOpsEvidence, modeToken === void 0 ? void 0 : pending, { opsCompatibility: modeToken !== void 0 })) state.touched.add(path);
1044
+ const targets = typeof target === "string" ? [target] : target ?? [];
1045
+ for (const item of targets) state.touched.add(item);
1046
+ if (roots.length === 0) return [];
1047
+ const api = await instructionsApi();
1048
+ if (api === void 0) throw new Error("review-context: instruction discovery unavailable");
391
1049
  const cwd = effectiveDirectory(scope, agent.session);
392
1050
  const reviewCwd = cwd === void 0 ? void 0 : canonicalPath(cwd);
393
1051
  const result = [];
@@ -403,17 +1061,27 @@ function apply(ctx, config = {}) {
403
1061
  }
404
1062
  signal.throwIfAborted();
405
1063
  if (Buffer.byteLength(JSON.stringify(result), "utf8") > maxBytes) throw new Error("review-context: instruction budget exceeded");
1064
+ if (opsCompatibilityToken(scope) !== modeToken) throw new Error("review-context: OPS compatibility changed during the snapshot; retry");
406
1065
  return result;
407
1066
  });
1067
+ if (opsCompatibilityToken(scope) !== modeToken) throw new Error("review-context: OPS compatibility changed during the snapshot; retry");
1068
+ return snapshot;
408
1069
  }
409
1070
  });
410
- scope.on("fs/observed", (target, _observation, actor) => {
1071
+ scope.on.call(scope, "fs/observed", (target, _observation, actor) => {
411
1072
  const exec = executions.getStore();
412
- if (exec?.agent === void 0 || FILE_TOUCH_TOOL_NAMES[exec.name] !== true || actor !== exec) return;
1073
+ if (exec?.agent === void 0 || actor !== exec) return;
1074
+ const live = opsLive.get(keyOf(exec));
1075
+ if (live !== void 0 && live.mode === opsCompatibilityToken(ctx)) {
1076
+ if (live.observed.size < MAX_LIVE_OPS_OBSERVED) live.observed.add(scope.fs.processPath(target));
1077
+ else live.incomplete = true;
1078
+ return;
1079
+ }
1080
+ if (FILE_TOUCH_TOOL_NAMES[exec.name] !== true) return;
413
1081
  const calls = callsBySession.get(exec.agent.session);
414
1082
  const call = calls?.get(exec.callId) ?? calls?.get(`ptc:${exec.callId}`);
415
1083
  if (call !== void 0) call.observedPath = scope.fs.processPath(target);
416
- });
1084
+ }, { global: true });
417
1085
  scope.on("agent/pre-step", async ({ agent, messages, signal }, next) => {
418
1086
  const decision = await next();
419
1087
  if (decision.kind !== "enter") return decision;
@@ -448,7 +1116,8 @@ async function pending(ctx, states, agent, config, maxBytes, signal, messages, r
448
1116
  scopes: /* @__PURE__ */ new Map(),
449
1117
  provenanceRoots: /* @__PURE__ */ new Set(),
450
1118
  touched: /* @__PURE__ */ new Set(),
451
- worked: /* @__PURE__ */ new Set()
1119
+ worked: /* @__PURE__ */ new Set(),
1120
+ liveOpsEvidence: /* @__PURE__ */ new Map()
452
1121
  };
453
1122
  states.set(session, state);
454
1123
  const evaluatedTouches = new Set(state.touched);
@@ -747,4 +1416,4 @@ async function loadDirectory(fs, api, root, config, signal, cwd, strict = false)
747
1416
  };
748
1417
  }
749
1418
  //#endregion
750
- export { composeInstructionMessage as a, apply as i, DEFAULT_MAX_SOURCE_BYTES as n, inject as o, PLUGIN_SOURCE as r, touchedPathOfToolCall as s, DEFAULT_MAX_BYTES as t };
1419
+ export { composeInstructionMessage as a, stableOpsJson as c, apply as i, touchedPathOfToolCall as l, DEFAULT_MAX_SOURCE_BYTES as n, inject as o, PLUGIN_SOURCE as r, pendingInstructionTargets as s, DEFAULT_MAX_BYTES as t };