@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,96 +1,290 @@
1
+ /**
2
+ * D-NEW / #271 Phase A 事故观察器测试。
3
+ *
4
+ * 覆盖真实 native ESM dist 产物的 uncaughtExceptionMonitor:有观察器时
5
+ * fatal 事故留下 JSONL、仍按宿主语义非零退出;无观察器时只有 Node 原生
6
+ * 非零退出。另覆盖同一 fd 的 fsync/close、写入失败返回值、监听器卸载和
7
+ * guardToolExecute 结构化业务失败。
8
+ *
9
+ * 运行: cd ts && npx tsx scripts/incident-tests.ts
10
+ */
1
11
  import assert from 'node:assert/strict';
2
12
  import { spawn } from 'node:child_process';
3
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
13
+ import fs from 'node:fs';
14
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
15
+ import { syncBuiltinESMExports } from 'node:module';
4
16
  import { tmpdir } from 'node:os';
5
17
  import { join } from 'node:path';
6
18
  import { fileURLToPath } from 'node:url';
7
19
  import { dirname } from 'node:path';
8
- import { recordIncident, resolveIncidentsPath } from '../capabilities/incident-log.js';
20
+ import { guardToolExecute, installProcessGuards, recordIncident, resolveIncidentsPath } from '../capabilities/incident-log.js';
9
21
  const here = dirname(fileURLToPath(import.meta.url));
22
+ const repoRoot = join(here, '../..');
23
+ const distModule = join(repoRoot, 'dist/capabilities/incident-log.js');
10
24
  const tmp = await mkdtemp(join(tmpdir(), 'incident-test-'));
11
- const path1 = resolveIncidentsPath(tmp);
12
- recordIncident({
13
- ts: '2026-08-22T00:00:00Z',
14
- kind: 'uncaughtException',
15
- message: 'synthetic',
16
- stack: 'no-stack'
17
- }, tmp);
18
- const content = await readFile(path1, 'utf-8');
19
- const written = JSON.parse(content.trim().split('\n')[0]);
20
- assert.equal(written.kind, 'uncaughtException');
21
- assert.equal(written.message, 'synthetic');
22
- assert.equal(written.stack, 'no-stack');
23
- console.log('UNIT 1 OK: recordIncident 同步 fsync 写盘');
24
- const childCode = `
25
- import { installProcessGuards } from '${here}/../capabilities/incident-log.ts'
26
- installProcessGuards('${tmp}', { uncaughtException: 'test-child' })
27
- setTimeout(() => { throw new Error('boom-from-child') }, 50)
28
- setTimeout(() => { console.log('CHILD_DONE'); process.exit(0) }, 400)
29
- `;
30
- const childPath = join(tmp, 'child.mjs');
31
- const { writeFile } = await import('node:fs/promises');
32
- await writeFile(childPath, childCode);
33
- const result = await new Promise((resolve, reject)=>{
34
- const child = spawn(process.execPath, [
35
- '--experimental-strip-types',
36
- childPath
37
- ], {
38
- stdio: [
39
- 'ignore',
40
- 'pipe',
41
- 'pipe'
42
- ]
25
+ const cleanEnv = () => {
26
+ const env = { ...process.env };
27
+ delete env.NODE_OPTIONS;
28
+ delete env.NODE_PATH;
29
+ return env;
30
+ };
31
+ async function runChild(code, stateRoot) {
32
+ const childPath = join(stateRoot, 'child.mjs');
33
+ await writeFile(childPath, code);
34
+ return await new Promise((resolve, reject) => {
35
+ const child = spawn(process.execPath, [childPath], { cwd: repoRoot, env: cleanEnv(), stdio: ['ignore', 'pipe', 'pipe'] });
36
+ let stdout = '';
37
+ let stderr = '';
38
+ const timer = setTimeout(() => {
39
+ child.kill('SIGKILL');
40
+ reject(new Error(`child timeout: ${stderr.slice(0, 400)}`));
41
+ }, 3000);
42
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
43
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
44
+ child.on('error', (error) => { clearTimeout(timer); reject(error); });
45
+ child.on('close', (codeValue, signal) => {
46
+ clearTimeout(timer);
47
+ resolve({ stdout, stderr, code: codeValue, signal });
48
+ });
43
49
  });
44
- let stdout = '', stderr = '';
45
- child.stdout.on('data', (d)=>{
46
- stdout += d.toString();
47
- });
48
- child.stderr.on('data', (d)=>{
49
- stderr += d.toString();
50
- });
51
- child.on('close', (code)=>resolve({
52
- stdout,
53
- stderr,
54
- code
55
- }));
56
- child.on('error', reject);
57
- });
58
- assert.equal(result.code, 0, `子进程应为 graceful exit,但拿到 ${result.code}: ${result.stderr.slice(0, 200)}`);
59
- assert.match(result.stdout, /CHILD_DONE/, `子进程应触发我的 400ms setTimeout: stdout=${result.stdout} stderr=${result.stderr.slice(0, 200)}`);
60
- const childLogPath = resolveIncidentsPath(tmp);
61
- const childLogContent = await readFile(childLogPath, 'utf-8');
62
- const lines = childLogContent.trim().split('\n').filter(Boolean);
63
- assert.ok(lines.length >= 1, `应至少写一行 incident, 实际为: ${childLogContent}`);
64
- const lastLine = JSON.parse(lines.at(-1));
65
- assert.equal(lastLine.kind, 'uncaughtException', `kind 应是 uncaughtException,实际: ${lastLine.kind}`);
66
- assert.match(lastLine.message, /boom-from-child/, `应记录错误消息,实际: ${lastLine.message}`);
67
- assert.equal(lastLine.source, 'test-child', `source 来自 labels.uncaughtException,实际: ${lastLine.source}`);
68
- console.log('INTEG 1 OK: 子进程 uncaughtException fsync 写盘,handler 不阻塞 graceful exit');
69
- {
70
- const { guardToolExecute } = await import('../capabilities/incident-log.js');
71
- const asyncBoom = guardToolExecute('synthetic-tool', tmp, async ()=>{
72
- throw new Error('boom-async');
73
- });
74
- const r1 = await asyncBoom({
75
- x: 1
76
- }, undefined);
77
- assert.equal(r1.ok, false, '异步抛错应降级为结构化错误,不向上抛');
78
- assert.ok(String(r1.summary).includes('synthetic-tool'), 'summary 指明来源工具');
79
- assert.ok(String(r1.evidence).includes('tool_execute_error'), 'evidence incident 标记');
80
- const syncBoom = guardToolExecute('synthetic-tool-sync', tmp, ()=>{
81
- throw new Error('boom-sync');
82
- });
83
- const r2 = await syncBoom({}, undefined);
84
- assert.equal(r2.ok, false, '同步抛错同样隔离');
85
- const guardedLog = (await readFile(path1, 'utf8')).trim().split('\n').filter(Boolean);
86
- const toolErrs = guardedLog.map((l)=>JSON.parse(l)).filter((x)=>x.kind === 'tool_execute_error');
87
- assert.equal(toolErrs.length, 2, '两条 tool_execute_error 均应落盘');
88
- console.log('UNIT 2 OK: guardToolExecute 同步/异步异常隔离 + incident 落盘');
89
- }await rm(tmp, {
90
- recursive: true,
91
- force: true
92
- });
93
- console.log('INCIDENT TESTS: 3/3 OK');
94
-
95
-
96
- //# sourceURL=ts/scripts/incident-tests.ts
50
+ }
51
+ try {
52
+ assert.ok(fs.existsSync(distModule), `build-dist output missing: ${distModule}`);
53
+ // Unit 1: the writer uses one fd, calls real fsync, and reports boolean success.
54
+ {
55
+ let fsyncCalls = 0;
56
+ const originalFsync = fs.fsyncSync;
57
+ fs.fsyncSync = ((fd) => { fsyncCalls += 1; return originalFsync(fd); });
58
+ syncBuiltinESMExports();
59
+ try {
60
+ const ok = recordIncident({ ts: '2026-09-09T00:00:00Z', kind: 'uncaughtException', message: 'synthetic', stack: 'no-stack' }, tmp);
61
+ assert.equal(ok, true);
62
+ assert.equal(fsyncCalls, 1);
63
+ const content = await readFile(resolveIncidentsPath(tmp), 'utf8');
64
+ const written = JSON.parse(content.trim().split('\n')[0]);
65
+ assert.equal(written.kind, 'uncaughtException');
66
+ assert.equal(written.message, 'synthetic');
67
+ assert.equal(written.stack, 'no-stack');
68
+ }
69
+ finally {
70
+ fs.fsyncSync = originalFsync;
71
+ syncBuiltinESMExports();
72
+ }
73
+ }
74
+ console.log('UNIT 1 OK: recordIncident 同一 fd 真实 fsync + boolean success');
75
+ // Unit 2: a failing fsync is contained, returns false, and closes the fd.
76
+ {
77
+ let closeCalls = 0;
78
+ const originalFsync = fs.fsyncSync;
79
+ const originalClose = fs.closeSync;
80
+ fs.fsyncSync = (() => { throw new Error('synthetic fsync failure'); });
81
+ fs.closeSync = ((fd) => { closeCalls += 1; return originalClose(fd); });
82
+ syncBuiltinESMExports();
83
+ try {
84
+ let result = true;
85
+ assert.doesNotThrow(() => { result = recordIncident({ ts: '2026-09-09T00:00:01Z', kind: 'plugin_error', message: 'fsync-failure' }, tmp); });
86
+ assert.equal(result, false);
87
+ assert.equal(closeCalls, 1);
88
+ }
89
+ finally {
90
+ fs.fsyncSync = originalFsync;
91
+ fs.closeSync = originalClose;
92
+ syncBuiltinESMExports();
93
+ }
94
+ closeCalls = 0;
95
+ const originalClose2 = fs.closeSync;
96
+ fs.closeSync = ((fd) => { closeCalls += 1; originalClose2(fd); throw new Error('synthetic close report'); });
97
+ syncBuiltinESMExports();
98
+ try {
99
+ assert.equal(recordIncident({ ts: '2026-09-09T00:00:02Z', kind: 'plugin_error', message: 'close-failure' }, tmp), false);
100
+ assert.equal(closeCalls, 1);
101
+ }
102
+ finally {
103
+ fs.closeSync = originalClose2;
104
+ syncBuiltinESMExports();
105
+ }
106
+ }
107
+ console.log('UNIT 2 OK: fsync failure returns false, closes fd, does not throw');
108
+ // Unit 3: install/dispose changes only the monitor listener and never leaks it.
109
+ {
110
+ const before = process.listenerCount('uncaughtExceptionMonitor');
111
+ const dispose = installProcessGuards(tmp, { uncaughtException: 'unit', unhandledRejection: 'unit' });
112
+ assert.equal(process.listenerCount('uncaughtExceptionMonitor'), before + 1);
113
+ const duplicateDispose = installProcessGuards(tmp);
114
+ duplicateDispose();
115
+ assert.equal(process.listenerCount('uncaughtExceptionMonitor'), before + 1);
116
+ dispose();
117
+ assert.equal(process.listenerCount('uncaughtExceptionMonitor'), before);
118
+ const disposeB = installProcessGuards(tmp);
119
+ dispose();
120
+ assert.equal(process.listenerCount('uncaughtExceptionMonitor'), before + 1);
121
+ disposeB();
122
+ assert.equal(process.listenerCount('uncaughtExceptionMonitor'), before);
123
+ }
124
+ console.log('UNIT 3 OK: monitor install/dispose 无监听泄漏');
125
+ // Integration 0: the packaged native ESM writer returns true and invokes real fsync.
126
+ {
127
+ const stateRoot = join(tmp, 'dist-writer');
128
+ await fs.promises.mkdir(stateRoot, { recursive: true });
129
+ const result = await runChild(`
130
+ import fs from 'node:fs'
131
+ import { syncBuiltinESMExports } from 'node:module'
132
+ import { recordIncident, resolveIncidentsPath } from ${JSON.stringify(distModule)}
133
+ const originalFsync = fs.fsyncSync
134
+ let fsyncCalls = 0
135
+ fs.fsyncSync = ((fd) => { fsyncCalls += 1; return originalFsync(fd) })
136
+ syncBuiltinESMExports()
137
+ const ok = recordIncident({ ts: '2026-09-09T00:00:03Z', kind: 'plugin_error', message: 'native-dist-writer' }, ${JSON.stringify(stateRoot)})
138
+ if (!ok || fsyncCalls !== 1 || !fs.existsSync(resolveIncidentsPath(${JSON.stringify(stateRoot)}))) process.exit(42)
139
+ `, stateRoot);
140
+ assert.equal(result.code, 0, `native dist writer assertion failed: ${result.stderr}`);
141
+ const line = JSON.parse((await readFile(resolveIncidentsPath(stateRoot), 'utf8')).trim());
142
+ assert.equal(line.message, 'native-dist-writer');
143
+ }
144
+ console.log('INTEG 0 OK: native dist writer true + real fsync + JSONL');
145
+ // Integration 0b: direct dist entrypoint smoke is fatal; importing it with
146
+ // argv2=--smoke does not trigger the CLI branch.
147
+ {
148
+ const directRoot = join(tmp, 'direct-smoke');
149
+ const importedRoot = join(tmp, 'imported-smoke');
150
+ const importedMarker = join(importedRoot, 'continued.marker');
151
+ await fs.promises.mkdir(directRoot, { recursive: true });
152
+ await fs.promises.mkdir(importedRoot, { recursive: true });
153
+ const direct = await new Promise((resolve, reject) => {
154
+ const child = spawn(process.execPath, [distModule, '--smoke', directRoot], { cwd: repoRoot, env: cleanEnv(), stdio: ['ignore', 'pipe', 'pipe'] });
155
+ let stdout = '', stderr = '';
156
+ const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('direct smoke timeout')); }, 3000);
157
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
158
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
159
+ child.on('error', (error) => { clearTimeout(timer); reject(error); });
160
+ child.on('close', (code, signal) => { clearTimeout(timer); resolve({ stdout, stderr, code, signal }); });
161
+ });
162
+ assert.equal(direct.code, 1, `direct dist smoke should exit 1: ${direct.stderr}`);
163
+ const directIncident = JSON.parse((await readFile(resolveIncidentsPath(directRoot), 'utf8')).trim());
164
+ assert.equal(directIncident.message, 'intentional smoke-uncaught');
165
+ const imported = await runChild(`
166
+ import { writeFileSync } from 'node:fs'
167
+ process.argv.push('--smoke', ${JSON.stringify(importedRoot)})
168
+ await import(${JSON.stringify(distModule)})
169
+ setTimeout(() => writeFileSync(${JSON.stringify(importedMarker)}, 'continued'), 50)
170
+ `, importedRoot);
171
+ assert.equal(imported.code, 0, `imported module must not trigger smoke: ${imported.stderr}`);
172
+ assert.equal(fs.existsSync(importedMarker), true);
173
+ assert.equal(fs.existsSync(resolveIncidentsPath(importedRoot)), false);
174
+ }
175
+ console.log('INTEG 0b OK: direct smoke fatal; imported --smoke inert');
176
+ // Integration 1-2: native dist, observer + fatal => durable log, exit 1, no marker.
177
+ for (const [kind, trigger, expectedKind] of [
178
+ ['uncaughtException', "setTimeout(() => { throw new Error('native-uncaught') }, 20)", 'uncaughtException'],
179
+ ['unhandledRejection', "setTimeout(() => Promise.reject(new Error('native-rejection')), 20)", 'unhandledRejection'],
180
+ ]) {
181
+ const stateRoot = join(tmp, `guard-${kind}`);
182
+ const marker = join(stateRoot, 'after-fatal.marker');
183
+ await fs.promises.mkdir(stateRoot, { recursive: true });
184
+ const result = await runChild(`
185
+ import { installProcessGuards } from ${JSON.stringify(distModule)}
186
+ import { writeFileSync } from 'node:fs'
187
+ installProcessGuards(${JSON.stringify(stateRoot)}, { uncaughtException: 'native-test', unhandledRejection: 'native-test' })
188
+ ${trigger}
189
+ setTimeout(() => writeFileSync(${JSON.stringify(marker)}, 'after-fatal'), 100)
190
+ `, stateRoot);
191
+ assert.equal(result.code, 1, `${kind} should retain native nonzero exit: ${result.stderr}`);
192
+ assert.equal(result.signal, null);
193
+ assert.equal(fs.existsSync(marker), false);
194
+ const lines = (await readFile(resolveIncidentsPath(stateRoot), 'utf8')).trim().split('\n').filter(Boolean);
195
+ assert.equal(lines.length, 1);
196
+ const incident = JSON.parse(lines[0]);
197
+ assert.equal(incident.kind, expectedKind);
198
+ assert.match(incident.message, /native-(uncaught|rejection)/);
199
+ assert.equal(incident.source, 'native-test');
200
+ }
201
+ console.log('INTEG 1-2 OK: native dist monitor records uncaught/rejection and preserves exit 1');
202
+ // Integration 3-4: no monitor is the Node native comparison pair.
203
+ for (const [kind, trigger] of [
204
+ ['uncaughtException', "setTimeout(() => { throw new Error('plain-uncaught') }, 20)"],
205
+ ['unhandledRejection', "setTimeout(() => Promise.reject(new Error('plain-rejection')), 20)"],
206
+ ]) {
207
+ const stateRoot = join(tmp, `plain-${kind}`);
208
+ const marker = join(stateRoot, 'after-fatal.marker');
209
+ await fs.promises.mkdir(stateRoot, { recursive: true });
210
+ const result = await runChild(`import { writeFileSync } from 'node:fs'\n${trigger}\nsetTimeout(() => writeFileSync(${JSON.stringify(marker)}, 'after-fatal'), 100)`, stateRoot);
211
+ assert.equal(result.code, 1, `${kind} without monitor should retain native nonzero exit: ${result.stderr}`);
212
+ assert.equal(result.signal, null);
213
+ assert.equal(fs.existsSync(marker), false);
214
+ assert.equal(fs.existsSync(resolveIncidentsPath(stateRoot)), false);
215
+ }
216
+ console.log('INTEG 3-4 OK: no-monitor native comparison exits 1 without incident log');
217
+ // Integration 5: an existing host handler owns the exit code; the observer does not bypass it.
218
+ {
219
+ const stateRoot = join(tmp, 'host-handler');
220
+ await fs.promises.mkdir(stateRoot, { recursive: true });
221
+ const hostMarker = join(stateRoot, 'host-handler.marker');
222
+ const result = await runChild(`
223
+ import { installProcessGuards } from ${JSON.stringify(distModule)}
224
+ import { writeFileSync } from 'node:fs'
225
+ installProcessGuards(${JSON.stringify(stateRoot)})
226
+ process.on('uncaughtException', (_error, origin) => {
227
+ writeFileSync(${JSON.stringify(hostMarker)}, origin)
228
+ process.exitCode = 23
229
+ })
230
+ setTimeout(() => { throw new Error('host-owned-fatal') }, 20)
231
+ setTimeout(() => {}, 100)
232
+ `, stateRoot);
233
+ assert.equal(result.code, 23, `host handler exit code was bypassed: ${result.stderr}`);
234
+ assert.equal(result.signal, null);
235
+ assert.equal((await readFile(hostMarker, 'utf8')), 'uncaughtException');
236
+ const incident = JSON.parse((await readFile(resolveIncidentsPath(stateRoot), 'utf8')).trim());
237
+ assert.equal(incident.kind, 'uncaughtException');
238
+ }
239
+ console.log('INTEG 5 OK: host fatal handler retains exit policy');
240
+ // Integration 6: a thrown non-Error with hostile message/stack accessors
241
+ // cannot break the observer; the host receives the original object identity.
242
+ {
243
+ const stateRoot = join(tmp, 'host-non-error');
244
+ const hostMarker = join(stateRoot, 'host-handler.marker');
245
+ await fs.promises.mkdir(stateRoot, { recursive: true });
246
+ const result = await runChild(`
247
+ import { installProcessGuards } from ${JSON.stringify(distModule)}
248
+ import { writeFileSync } from 'node:fs'
249
+ installProcessGuards(${JSON.stringify(stateRoot)})
250
+ const original = Object.create(null)
251
+ Object.defineProperty(original, 'message', { enumerable: true, get() { throw new Error('message getter') } })
252
+ Object.defineProperty(original, 'stack', { enumerable: true, get() { throw new Error('stack getter') } })
253
+ process.on('uncaughtException', (_error, origin) => {
254
+ writeFileSync(${JSON.stringify(hostMarker)}, _error === original ? origin : 'wrong-identity')
255
+ process.exitCode = 23
256
+ })
257
+ setTimeout(() => { throw original }, 20)
258
+ setTimeout(() => {}, 100)
259
+ `, stateRoot);
260
+ assert.equal(result.code, 23, `non-Error throw bypassed host handler: ${result.stderr}`);
261
+ assert.equal(result.signal, null);
262
+ assert.equal((await readFile(hostMarker, 'utf8')), 'uncaughtException');
263
+ const incident = JSON.parse((await readFile(resolveIncidentsPath(stateRoot), 'utf8')).trim());
264
+ assert.equal(incident.kind, 'uncaughtException');
265
+ assert.equal(typeof incident.message, 'string');
266
+ }
267
+ console.log('INTEG 6 OK: non-Error throw safe formatting + original host identity/exit policy');
268
+ // Unit 4: tool business errors remain structured and do not escape.
269
+ {
270
+ const asyncBoom = guardToolExecute('synthetic-tool', tmp, async () => { throw new Error('boom-async'); });
271
+ const r1 = await asyncBoom({ x: 1 }, undefined);
272
+ assert.equal(r1.ok, false);
273
+ assert.ok(String(r1.summary).includes('synthetic-tool'));
274
+ assert.ok(String(r1.evidence).includes('tool_execute_error'));
275
+ const syncBoom = guardToolExecute('synthetic-tool-sync', tmp, () => { throw new Error('boom-sync'); });
276
+ const r2 = await syncBoom({}, undefined);
277
+ assert.equal(r2.ok, false);
278
+ const nonErrorBoom = guardToolExecute('synthetic-tool-nonerror', tmp, () => { throw Object.create(null); });
279
+ const r3 = await nonErrorBoom({}, undefined);
280
+ assert.equal(r3.ok, false);
281
+ const toolErrs = (await readFile(resolveIncidentsPath(tmp), 'utf8')).trim().split('\n').filter(Boolean)
282
+ .map((line) => JSON.parse(line)).filter((line) => line.kind === 'tool_execute_error');
283
+ assert.equal(toolErrs.length, 3);
284
+ }
285
+ console.log('UNIT 4 OK: guardToolExecute 保持结构化业务失败');
286
+ }
287
+ finally {
288
+ await rm(tmp, { recursive: true, force: true });
289
+ }
290
+ console.log('INCIDENT TESTS: all focused checks OK');
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Issue #194 focused regression.
3
+ *
4
+ * This mounts the actual dsh ToolRuntime, LocalJobRegistry, and job tools, then
5
+ * supplies only a deterministic in-process subagent listing. It never starts a
6
+ * provider, opens Chrome, reads credentials, or writes shared state.
7
+ */
8
+ import assert from 'node:assert/strict';
9
+ import { Context } from '@deepseek-ai/cordis';
10
+ import { ToolRuntime } from '@deepseek-ai/dsh-tools';
11
+ import { LocalJobRegistry } from '@deepseek-ai/dsh-jobs-local';
12
+ import { SystemPrompt } from '@deepseek-ai/dsh-system-prompt';
13
+ import { apply as applyJobs } from '@deepseek-ai/dsh-tool-jobs';
14
+ import { installSubagentJobIdGuard } from '../src/subagent-job-id-guard.js';
15
+ const parentId = 'parent-session-issue-194';
16
+ const durableId = 'dc5c8d88-d23e-4df0-ba95-ccbc80e2742a';
17
+ const oneShotId = 'one-shot-session-issue-194';
18
+ const unrelatedId = 'unrelated-session-issue-194';
19
+ const listedParents = [];
20
+ const continuable = {
21
+ kind: 'child',
22
+ id: durableId,
23
+ activity: 'inactive',
24
+ hasChildren: false,
25
+ mode: 'continuable',
26
+ label: 'fixture continuable child',
27
+ };
28
+ const oneShot = {
29
+ kind: 'child',
30
+ id: oneShotId,
31
+ activity: 'inactive',
32
+ hasChildren: false,
33
+ mode: 'one-shot',
34
+ };
35
+ const ctx = new Context();
36
+ await ctx.plugin(SystemPrompt);
37
+ await ctx.plugin(ToolRuntime, {});
38
+ await ctx.plugin(LocalJobRegistry, {});
39
+ await ctx.inject(['tools', 'jobs'], () => applyJobs(ctx, {}));
40
+ const removeSubagents = ctx.provide('subagents', {
41
+ listChildren: async (id, _signal) => {
42
+ listedParents.push(String(id));
43
+ return id === parentId ? [continuable, oneShot] : [];
44
+ },
45
+ });
46
+ installSubagentJobIdGuard(ctx);
47
+ const agent = { id: parentId, ctx };
48
+ const signal = () => new AbortController().signal;
49
+ const resultText = (result) => {
50
+ const block = result.content[0];
51
+ if (!block || typeof block !== 'object' || !('text' in block))
52
+ return '';
53
+ return typeof block.text === 'string' ? block.text : '';
54
+ };
55
+ const execute = (name, jobId, caller = agent) => ctx.tools.execute({
56
+ callId: `issue-194-${name}-${jobId}`,
57
+ name,
58
+ arguments: { job_id: jobId },
59
+ agent: caller,
60
+ signal: signal(),
61
+ });
62
+ try {
63
+ for (const name of ['job_output', 'job_kill']) {
64
+ const result = await execute(name, durableId);
65
+ assert.equal(result.isError, true);
66
+ assert.match(resultText(result), /^Error: SUBAGENT_ID_IS_NOT_JOB_ID:/);
67
+ assert.match(resultText(result), /continuable subagent durable id, not a background job id/);
68
+ assert.match(resultText(result), /completion notice/);
69
+ assert.match(resultText(result), /list_agents/);
70
+ assert.match(resultText(result), /send_message/);
71
+ if (name === 'job_kill')
72
+ assert.match(resultText(result), /interrupt_agent/);
73
+ assert.match(result.error.message, /SUBAGENT_ID_IS_NOT_JOB_ID/);
74
+ assert.doesNotMatch(result.error.message, /unknown job/);
75
+ }
76
+ // A UUID-shaped but unlisted id keeps native dsh behavior; no global UUID
77
+ // heuristic or jobs ownership expansion is allowed.
78
+ const unrelated = await execute('job_output', '8e7f2c4a-3c92-4ce8-bc2a-2e1bdceec2f4');
79
+ assert.equal(unrelated.isError, true);
80
+ assert.match(resultText(unrelated), /^Error: unknown job /);
81
+ // A one-shot child is not a continuable durable conversation and also keeps
82
+ // native jobs behavior, even when it is visible in the same direct listing.
83
+ const oneShotResult = await execute('job_output', oneShotId);
84
+ assert.equal(oneShotResult.isError, true);
85
+ assert.match(resultText(oneShotResult), /^Error: unknown job /);
86
+ // The lookup is scoped to the exact caller's direct children.
87
+ const otherAgent = { id: unrelatedId, ctx };
88
+ const otherParentResult = await execute('job_output', durableId, otherAgent);
89
+ assert.equal(otherParentResult.isError, true);
90
+ assert.match(resultText(otherParentResult), /^Error: unknown job /);
91
+ assert.deepEqual(listedParents, [parentId, parentId, parentId, parentId, unrelatedId]);
92
+ console.log(JSON.stringify({
93
+ dsh: '0.1.5-rc.1',
94
+ toolRuntime: 'real ToolRuntime + LocalJobRegistry + dsh-tool-jobs',
95
+ guarded: ['job_output', 'job_kill'],
96
+ durableId,
97
+ nativeUnknownIds: 3,
98
+ exactParentLookups: listedParents,
99
+ sharedState: false,
100
+ liveSession: false,
101
+ }));
102
+ }
103
+ finally {
104
+ await removeSubagents();
105
+ await ctx.fiber.dispose();
106
+ }