@inhuman.tools/turn 0.0.0-stage → 0.2.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 (346) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/agent-calls/acceptance.d.ts +12 -0
  3. package/dist/agent-calls/acceptance.d.ts.map +1 -0
  4. package/dist/agent-calls/acceptance.js +64 -0
  5. package/dist/agent-calls/acceptance.js.map +1 -0
  6. package/dist/agent-calls/callbacks.d.ts +14 -0
  7. package/dist/agent-calls/callbacks.d.ts.map +1 -0
  8. package/dist/agent-calls/callbacks.js +59 -0
  9. package/dist/agent-calls/callbacks.js.map +1 -0
  10. package/dist/agent-calls/delivery.d.ts +7 -0
  11. package/dist/agent-calls/delivery.d.ts.map +1 -0
  12. package/dist/agent-calls/delivery.js +16 -0
  13. package/dist/agent-calls/delivery.js.map +1 -0
  14. package/dist/agent-calls/index.d.ts +7 -0
  15. package/dist/agent-calls/index.d.ts.map +1 -0
  16. package/dist/agent-calls/index.js +6 -0
  17. package/dist/agent-calls/index.js.map +1 -0
  18. package/dist/agent-calls/operator.d.ts +21 -0
  19. package/dist/agent-calls/operator.d.ts.map +1 -0
  20. package/dist/agent-calls/operator.js +78 -0
  21. package/dist/agent-calls/operator.js.map +1 -0
  22. package/dist/agent-calls/runtime.d.ts +99 -0
  23. package/dist/agent-calls/runtime.d.ts.map +1 -0
  24. package/dist/agent-calls/runtime.js +245 -0
  25. package/dist/agent-calls/runtime.js.map +1 -0
  26. package/dist/agent-calls/types.d.ts +31 -0
  27. package/dist/agent-calls/types.d.ts.map +1 -0
  28. package/dist/agent-calls/types.js +2 -0
  29. package/dist/agent-calls/types.js.map +1 -0
  30. package/dist/context/compact-floors.d.ts +43 -0
  31. package/dist/context/compact-floors.d.ts.map +1 -0
  32. package/dist/context/compact-floors.js +52 -0
  33. package/dist/context/compact-floors.js.map +1 -0
  34. package/dist/context/compact.d.ts +42 -0
  35. package/dist/context/compact.d.ts.map +1 -0
  36. package/dist/context/compact.js +155 -0
  37. package/dist/context/compact.js.map +1 -0
  38. package/dist/context/delivery.d.ts +13 -0
  39. package/dist/context/delivery.d.ts.map +1 -0
  40. package/dist/context/delivery.js +6 -0
  41. package/dist/context/delivery.js.map +1 -0
  42. package/dist/context/history.d.ts +66 -0
  43. package/dist/context/history.d.ts.map +1 -0
  44. package/dist/context/history.js +573 -0
  45. package/dist/context/history.js.map +1 -0
  46. package/dist/context/index.d.ts +10 -0
  47. package/dist/context/index.d.ts.map +1 -0
  48. package/dist/context/index.js +10 -0
  49. package/dist/context/index.js.map +1 -0
  50. package/dist/context/observations.d.ts +7 -0
  51. package/dist/context/observations.d.ts.map +1 -0
  52. package/dist/context/observations.js +11 -0
  53. package/dist/context/observations.js.map +1 -0
  54. package/dist/context/retention/defaults.d.ts +10 -0
  55. package/dist/context/retention/defaults.d.ts.map +1 -0
  56. package/dist/context/retention/defaults.js +47 -0
  57. package/dist/context/retention/defaults.js.map +1 -0
  58. package/dist/context/retention/index.d.ts +14 -0
  59. package/dist/context/retention/index.d.ts.map +1 -0
  60. package/dist/context/retention/index.js +21 -0
  61. package/dist/context/retention/index.js.map +1 -0
  62. package/dist/context/retention/narrow.d.ts +22 -0
  63. package/dist/context/retention/narrow.d.ts.map +1 -0
  64. package/dist/context/retention/narrow.js +140 -0
  65. package/dist/context/retention/narrow.js.map +1 -0
  66. package/dist/context/retention/render.d.ts +37 -0
  67. package/dist/context/retention/render.d.ts.map +1 -0
  68. package/dist/context/retention/render.js +149 -0
  69. package/dist/context/retention/render.js.map +1 -0
  70. package/dist/context/retention/resolve.d.ts +51 -0
  71. package/dist/context/retention/resolve.d.ts.map +1 -0
  72. package/dist/context/retention/resolve.js +254 -0
  73. package/dist/context/retention/resolve.js.map +1 -0
  74. package/dist/context/retention/schema.d.ts +345 -0
  75. package/dist/context/retention/schema.d.ts.map +1 -0
  76. package/dist/context/retention/schema.js +149 -0
  77. package/dist/context/retention/schema.js.map +1 -0
  78. package/dist/context/retention/stamp.d.ts +25 -0
  79. package/dist/context/retention/stamp.d.ts.map +1 -0
  80. package/dist/context/retention/stamp.js +103 -0
  81. package/dist/context/retention/stamp.js.map +1 -0
  82. package/dist/context/retention/step.d.ts +33 -0
  83. package/dist/context/retention/step.d.ts.map +1 -0
  84. package/dist/context/retention/step.js +74 -0
  85. package/dist/context/retention/step.js.map +1 -0
  86. package/dist/context/retention/view.d.ts +57 -0
  87. package/dist/context/retention/view.d.ts.map +1 -0
  88. package/dist/context/retention/view.js +121 -0
  89. package/dist/context/retention/view.js.map +1 -0
  90. package/dist/context/squeeze.d.ts +45 -0
  91. package/dist/context/squeeze.d.ts.map +1 -0
  92. package/dist/context/squeeze.js +182 -0
  93. package/dist/context/squeeze.js.map +1 -0
  94. package/dist/context/tools.d.ts +16 -0
  95. package/dist/context/tools.d.ts.map +1 -0
  96. package/dist/context/tools.js +73 -0
  97. package/dist/context/tools.js.map +1 -0
  98. package/dist/context/transcript.d.ts +151 -0
  99. package/dist/context/transcript.d.ts.map +1 -0
  100. package/dist/context/transcript.js +659 -0
  101. package/dist/context/transcript.js.map +1 -0
  102. package/dist/policy-processor/bypass.d.ts +95 -0
  103. package/dist/policy-processor/bypass.d.ts.map +1 -0
  104. package/dist/policy-processor/bypass.js +188 -0
  105. package/dist/policy-processor/bypass.js.map +1 -0
  106. package/dist/policy-processor/classifier.d.ts +47 -0
  107. package/dist/policy-processor/classifier.d.ts.map +1 -0
  108. package/dist/policy-processor/classifier.js +671 -0
  109. package/dist/policy-processor/classifier.js.map +1 -0
  110. package/dist/policy-processor/cluster.d.ts +7 -0
  111. package/dist/policy-processor/cluster.d.ts.map +1 -0
  112. package/dist/policy-processor/cluster.js +32 -0
  113. package/dist/policy-processor/cluster.js.map +1 -0
  114. package/dist/policy-processor/evidence.d.ts +26 -0
  115. package/dist/policy-processor/evidence.d.ts.map +1 -0
  116. package/dist/policy-processor/evidence.js +204 -0
  117. package/dist/policy-processor/evidence.js.map +1 -0
  118. package/dist/policy-processor/explain.d.ts +59 -0
  119. package/dist/policy-processor/explain.d.ts.map +1 -0
  120. package/dist/policy-processor/explain.js +224 -0
  121. package/dist/policy-processor/explain.js.map +1 -0
  122. package/dist/policy-processor/index.d.ts +6 -0
  123. package/dist/policy-processor/index.d.ts.map +1 -0
  124. package/dist/policy-processor/index.js +6 -0
  125. package/dist/policy-processor/index.js.map +1 -0
  126. package/dist/policy-processor/license.d.ts +23 -0
  127. package/dist/policy-processor/license.d.ts.map +1 -0
  128. package/dist/policy-processor/license.js +47 -0
  129. package/dist/policy-processor/license.js.map +1 -0
  130. package/dist/policy-processor/peel-cwd.d.ts +2 -0
  131. package/dist/policy-processor/peel-cwd.d.ts.map +1 -0
  132. package/dist/policy-processor/peel-cwd.js +2 -0
  133. package/dist/policy-processor/peel-cwd.js.map +1 -0
  134. package/dist/policy-processor/real-path.d.ts +7 -0
  135. package/dist/policy-processor/real-path.d.ts.map +1 -0
  136. package/dist/policy-processor/real-path.js +42 -0
  137. package/dist/policy-processor/real-path.js.map +1 -0
  138. package/dist/policy-processor/scope.d.ts +111 -0
  139. package/dist/policy-processor/scope.d.ts.map +1 -0
  140. package/dist/policy-processor/scope.js +629 -0
  141. package/dist/policy-processor/scope.js.map +1 -0
  142. package/dist/policy-processor/self-protect.d.ts +10 -0
  143. package/dist/policy-processor/self-protect.d.ts.map +1 -0
  144. package/dist/policy-processor/self-protect.js +48 -0
  145. package/dist/policy-processor/self-protect.js.map +1 -0
  146. package/dist/policy-processor/soft-deny.d.ts +75 -0
  147. package/dist/policy-processor/soft-deny.d.ts.map +1 -0
  148. package/dist/policy-processor/soft-deny.js +185 -0
  149. package/dist/policy-processor/soft-deny.js.map +1 -0
  150. package/dist/policy-processor/unattended-guard.d.ts +28 -0
  151. package/dist/policy-processor/unattended-guard.d.ts.map +1 -0
  152. package/dist/policy-processor/unattended-guard.js +145 -0
  153. package/dist/policy-processor/unattended-guard.js.map +1 -0
  154. package/dist/session/cancel.d.ts +14 -0
  155. package/dist/session/cancel.d.ts.map +1 -0
  156. package/dist/session/cancel.js +32 -0
  157. package/dist/session/cancel.js.map +1 -0
  158. package/dist/session/commands.d.ts +41 -0
  159. package/dist/session/commands.d.ts.map +1 -0
  160. package/dist/session/commands.js +241 -0
  161. package/dist/session/commands.js.map +1 -0
  162. package/dist/session/compact-actor.d.ts +23 -0
  163. package/dist/session/compact-actor.d.ts.map +1 -0
  164. package/dist/session/compact-actor.js +91 -0
  165. package/dist/session/compact-actor.js.map +1 -0
  166. package/dist/session/compact-env.d.ts +54 -0
  167. package/dist/session/compact-env.d.ts.map +1 -0
  168. package/dist/session/compact-env.js +345 -0
  169. package/dist/session/compact-env.js.map +1 -0
  170. package/dist/session/compact.d.ts +42 -0
  171. package/dist/session/compact.d.ts.map +1 -0
  172. package/dist/session/compact.js +68 -0
  173. package/dist/session/compact.js.map +1 -0
  174. package/dist/session/index.d.ts +9 -0
  175. package/dist/session/index.d.ts.map +1 -0
  176. package/dist/session/index.js +8 -0
  177. package/dist/session/index.js.map +1 -0
  178. package/dist/session/park.d.ts +16 -0
  179. package/dist/session/park.d.ts.map +1 -0
  180. package/dist/session/park.js +73 -0
  181. package/dist/session/park.js.map +1 -0
  182. package/dist/session/side-channel-reminder.d.ts +41 -0
  183. package/dist/session/side-channel-reminder.d.ts.map +1 -0
  184. package/dist/session/side-channel-reminder.js +132 -0
  185. package/dist/session/side-channel-reminder.js.map +1 -0
  186. package/dist/session/types.d.ts +44 -0
  187. package/dist/session/types.d.ts.map +1 -0
  188. package/dist/session/types.js +2 -0
  189. package/dist/session/types.js.map +1 -0
  190. package/dist/tool-catalog/catalog.d.ts +77 -0
  191. package/dist/tool-catalog/catalog.d.ts.map +1 -0
  192. package/dist/tool-catalog/catalog.js +380 -0
  193. package/dist/tool-catalog/catalog.js.map +1 -0
  194. package/dist/tool-catalog/index.d.ts +4 -0
  195. package/dist/tool-catalog/index.d.ts.map +1 -0
  196. package/dist/tool-catalog/index.js +3 -0
  197. package/dist/tool-catalog/index.js.map +1 -0
  198. package/dist/tool-catalog/recipes.d.ts +36 -0
  199. package/dist/tool-catalog/recipes.d.ts.map +1 -0
  200. package/dist/tool-catalog/recipes.js +121 -0
  201. package/dist/tool-catalog/recipes.js.map +1 -0
  202. package/dist/tool-catalog/types.d.ts +103 -0
  203. package/dist/tool-catalog/types.d.ts.map +1 -0
  204. package/dist/tool-catalog/types.js +5 -0
  205. package/dist/tool-catalog/types.js.map +1 -0
  206. package/dist/tool-runtime/approval/approved.d.ts +13 -0
  207. package/dist/tool-runtime/approval/approved.d.ts.map +1 -0
  208. package/dist/tool-runtime/approval/approved.js +20 -0
  209. package/dist/tool-runtime/approval/approved.js.map +1 -0
  210. package/dist/tool-runtime/approval/dist.d.ts +6 -0
  211. package/dist/tool-runtime/approval/dist.d.ts.map +1 -0
  212. package/dist/tool-runtime/approval/dist.js +28 -0
  213. package/dist/tool-runtime/approval/dist.js.map +1 -0
  214. package/dist/tool-runtime/approval/inbound.d.ts +7 -0
  215. package/dist/tool-runtime/approval/inbound.d.ts.map +1 -0
  216. package/dist/tool-runtime/approval/inbound.js +48 -0
  217. package/dist/tool-runtime/approval/inbound.js.map +1 -0
  218. package/dist/tool-runtime/approval/unattended.d.ts +8 -0
  219. package/dist/tool-runtime/approval/unattended.d.ts.map +1 -0
  220. package/dist/tool-runtime/approval/unattended.js +23 -0
  221. package/dist/tool-runtime/approval/unattended.js.map +1 -0
  222. package/dist/tool-runtime/blob-port.d.ts +46 -0
  223. package/dist/tool-runtime/blob-port.d.ts.map +1 -0
  224. package/dist/tool-runtime/blob-port.js +47 -0
  225. package/dist/tool-runtime/blob-port.js.map +1 -0
  226. package/dist/tool-runtime/extra-root.d.ts +3 -0
  227. package/dist/tool-runtime/extra-root.d.ts.map +1 -0
  228. package/dist/tool-runtime/extra-root.js +14 -0
  229. package/dist/tool-runtime/extra-root.js.map +1 -0
  230. package/dist/tool-runtime/handler.d.ts +29 -0
  231. package/dist/tool-runtime/handler.d.ts.map +1 -0
  232. package/dist/tool-runtime/handler.js +460 -0
  233. package/dist/tool-runtime/handler.js.map +1 -0
  234. package/dist/tool-runtime/index.d.ts +14 -0
  235. package/dist/tool-runtime/index.d.ts.map +1 -0
  236. package/dist/tool-runtime/index.js +14 -0
  237. package/dist/tool-runtime/index.js.map +1 -0
  238. package/dist/tool-runtime/peel.d.ts +19 -0
  239. package/dist/tool-runtime/peel.d.ts.map +1 -0
  240. package/dist/tool-runtime/peel.js +74 -0
  241. package/dist/tool-runtime/peel.js.map +1 -0
  242. package/dist/tool-runtime/plugin-context.d.ts +13 -0
  243. package/dist/tool-runtime/plugin-context.d.ts.map +1 -0
  244. package/dist/tool-runtime/plugin-context.js +55 -0
  245. package/dist/tool-runtime/plugin-context.js.map +1 -0
  246. package/dist/tool-runtime/recall.d.ts +18 -0
  247. package/dist/tool-runtime/recall.d.ts.map +1 -0
  248. package/dist/tool-runtime/recall.js +100 -0
  249. package/dist/tool-runtime/recall.js.map +1 -0
  250. package/dist/tool-runtime/run-system-tool.d.ts +13 -0
  251. package/dist/tool-runtime/run-system-tool.d.ts.map +1 -0
  252. package/dist/tool-runtime/run-system-tool.js +230 -0
  253. package/dist/tool-runtime/run-system-tool.js.map +1 -0
  254. package/dist/tool-runtime/tool-error.d.ts +6 -0
  255. package/dist/tool-runtime/tool-error.d.ts.map +1 -0
  256. package/dist/tool-runtime/tool-error.js +6 -0
  257. package/dist/tool-runtime/tool-error.js.map +1 -0
  258. package/dist/tool-runtime/worker-tools.d.ts +8 -0
  259. package/dist/tool-runtime/worker-tools.d.ts.map +1 -0
  260. package/dist/tool-runtime/worker-tools.js +65 -0
  261. package/dist/tool-runtime/worker-tools.js.map +1 -0
  262. package/dist/tool-runtime/write-admit.d.ts +21 -0
  263. package/dist/tool-runtime/write-admit.d.ts.map +1 -0
  264. package/dist/tool-runtime/write-admit.js +89 -0
  265. package/dist/tool-runtime/write-admit.js.map +1 -0
  266. package/package.json +114 -3
  267. package/policy/auto-mode-ask.scm +201 -0
  268. package/policy/bypass-detect.prompt +55 -0
  269. package/policy/classifier-bash-incomplete.prompt +46 -0
  270. package/policy/classifier-bash.prompt +45 -0
  271. package/policy/classifier-mcp.prompt +45 -0
  272. package/policy/classifier-read.prompt +44 -0
  273. package/policy/classifier-write-outside.prompt +46 -0
  274. package/policy/classifier-write-risky.prompt +45 -0
  275. package/policy/classifier-write-workspace.prompt +46 -0
  276. package/policy/classifier-write.prompt +45 -0
  277. package/policy/soft-deny-round1.prompt +51 -0
  278. package/policy/soft-deny-round2.prompt +60 -0
  279. package/src/agent-calls/acceptance.ts +71 -0
  280. package/src/agent-calls/callbacks.ts +75 -0
  281. package/src/agent-calls/delivery.ts +15 -0
  282. package/src/agent-calls/index.ts +16 -0
  283. package/src/agent-calls/operator.ts +99 -0
  284. package/src/agent-calls/runtime.ts +310 -0
  285. package/src/agent-calls/types.ts +33 -0
  286. package/src/context/compact-floors.ts +76 -0
  287. package/src/context/compact.ts +176 -0
  288. package/src/context/delivery.ts +15 -0
  289. package/src/context/history.ts +725 -0
  290. package/src/context/index.ts +35 -0
  291. package/src/context/observations.ts +13 -0
  292. package/src/context/retention/defaults.ts +58 -0
  293. package/src/context/retention/index.ts +45 -0
  294. package/src/context/retention/narrow.ts +123 -0
  295. package/src/context/retention/render.ts +177 -0
  296. package/src/context/retention/resolve.ts +306 -0
  297. package/src/context/retention/schema.ts +173 -0
  298. package/src/context/retention/stamp.ts +121 -0
  299. package/src/context/retention/step.ts +85 -0
  300. package/src/context/retention/view.ts +161 -0
  301. package/src/context/squeeze.ts +218 -0
  302. package/src/context/tools.ts +92 -0
  303. package/src/context/transcript.ts +705 -0
  304. package/src/policy-processor/README.md +43 -0
  305. package/src/policy-processor/bypass.ts +246 -0
  306. package/src/policy-processor/classifier.ts +711 -0
  307. package/src/policy-processor/cluster.ts +31 -0
  308. package/src/policy-processor/evidence.ts +214 -0
  309. package/src/policy-processor/explain.ts +310 -0
  310. package/src/policy-processor/index.ts +6 -0
  311. package/src/policy-processor/license.ts +45 -0
  312. package/src/policy-processor/peel-cwd.ts +1 -0
  313. package/src/policy-processor/real-path.ts +41 -0
  314. package/src/policy-processor/scope.ts +655 -0
  315. package/src/policy-processor/self-protect.ts +46 -0
  316. package/src/policy-processor/soft-deny.ts +225 -0
  317. package/src/policy-processor/unattended-guard.ts +158 -0
  318. package/src/session/cancel.ts +34 -0
  319. package/src/session/commands.ts +292 -0
  320. package/src/session/compact-actor.ts +132 -0
  321. package/src/session/compact-env.ts +539 -0
  322. package/src/session/compact.ts +95 -0
  323. package/src/session/index.ts +18 -0
  324. package/src/session/park.ts +73 -0
  325. package/src/session/side-channel-reminder.ts +148 -0
  326. package/src/session/types.ts +50 -0
  327. package/src/tool-catalog/catalog.ts +282 -0
  328. package/src/tool-catalog/index.ts +26 -0
  329. package/src/tool-catalog/recipes.ts +154 -0
  330. package/src/tool-catalog/types.ts +110 -0
  331. package/src/tool-runtime/approval/approved.ts +20 -0
  332. package/src/tool-runtime/approval/dist.ts +29 -0
  333. package/src/tool-runtime/approval/inbound.ts +50 -0
  334. package/src/tool-runtime/approval/unattended.ts +24 -0
  335. package/src/tool-runtime/blob-port.ts +64 -0
  336. package/src/tool-runtime/extra-root.ts +16 -0
  337. package/src/tool-runtime/handler.ts +576 -0
  338. package/src/tool-runtime/index.ts +21 -0
  339. package/src/tool-runtime/peel.ts +91 -0
  340. package/src/tool-runtime/plugin-context.ts +68 -0
  341. package/src/tool-runtime/recall.ts +104 -0
  342. package/src/tool-runtime/run-system-tool.ts +273 -0
  343. package/src/tool-runtime/tool-error.ts +6 -0
  344. package/src/tool-runtime/worker-tools.ts +76 -0
  345. package/src/tool-runtime/write-admit.ts +90 -0
  346. package/README.md +0 -3
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Build plugin call context from live session entities. Graph objects stay
3
+ * host-side. Storage is the prefix projection; propose is applied onto this job after run.
4
+ */
5
+ import {
6
+ CompletionExpectation,
7
+ hostingTurnOf,
8
+ projectionsOf,
9
+ reactChunks,
10
+ SessionRoot,
11
+ SessionTurn,
12
+ storageBefore,
13
+ tapeCompletion,
14
+ ToolCallJob,
15
+ } from "@inhuman.tools/harness-model";
16
+ import type { PluginCallContext } from "@inhuman.tools/plugin-sdk";
17
+
18
+ export const pendingPropose = new WeakMap<object, { readonly pluginId: string; readonly action: unknown }>();
19
+
20
+ function completionHosting(job: ToolCallJob): CompletionExpectation | null {
21
+ const turn = job.parent instanceof SessionTurn ? job.parent : null;
22
+ if (turn == null) return null;
23
+ const tape = reactChunks(turn);
24
+ for (let i = 0; i < tape.length; i += 1) {
25
+ const pew = tapeCompletion(tape[i]);
26
+ if (pew == null) continue;
27
+ if (pew.messages.includes(job)) return pew;
28
+ for (let index = i + 1; index < tape.length; index += 1) {
29
+ const cell = tape[index];
30
+ if (tapeCompletion(cell) != null) break;
31
+ if (cell === job) return pew;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ export function pluginCallContextOf(
38
+ root: SessionRoot,
39
+ job: ToolCallJob,
40
+ signal?: AbortSignal,
41
+ pluginId = "",
42
+ ): PluginCallContext {
43
+ const turn = hostingTurnOf(root, job) ?? (job.parent instanceof SessionTurn ? job.parent : null);
44
+ const completion = completionHosting(job);
45
+ const prefix = storageBefore(root, job);
46
+ const projection = pluginId === "" ? undefined : projectionsOf(root).get(pluginId);
47
+ const initial = projection?.initial() ?? {};
48
+ return {
49
+ root: root.path.trim() === "" ? undefined : root.path,
50
+ callerSessionId: root.sessionId,
51
+ turn,
52
+ completion,
53
+ invocation: job,
54
+ signal,
55
+ storage: {
56
+ session: pluginId === "" ? prefix : (prefix[pluginId] ?? initial),
57
+ invocation: initial,
58
+ propose(action: unknown) {
59
+ if (pluginId === "") return;
60
+ pendingPropose.set(job, { pluginId, action });
61
+ },
62
+ },
63
+ };
64
+ }
65
+
66
+ export function isModelOriginContext(ctx: PluginCallContext): boolean {
67
+ return ctx.completion instanceof CompletionExpectation;
68
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * `context_recall {ref}` — read a sealed result back from the graph, paged.
3
+ *
4
+ * It re-exposes only what the model already saw in this session, so it needs
5
+ * no policy floor beyond read-only. Refs resolve against turns reachable from
6
+ * the live head (`turnTail` chain); an abandoned branch or another session
7
+ * cannot be read.
8
+ */
9
+ import { ProgrammaticJob, reactChunks, SessionRoot, SessionTurn, ToolCallJob } from "@inhuman.tools/harness-model";
10
+ import type { ToolOutcomeSnap } from "@inhuman.tools/harness-model/schemas";
11
+
12
+ import { SQUEEZE_COMPACT_CHARS, liftToolImages, paintObservation } from "../context/squeeze.js";
13
+
14
+ export const CONTEXT_RECALL_TOOL_NAME = "context_recall";
15
+
16
+ const MIN_PREFIX = 4;
17
+
18
+ function reachableTurns(root: SessionRoot): SessionTurn[] {
19
+ const out: SessionTurn[] = [];
20
+ for (let cell: unknown = root.head; cell instanceof SessionTurn && !out.includes(cell); cell = cell.turnTail) {
21
+ out.push(cell);
22
+ }
23
+ return out;
24
+ }
25
+
26
+ function reachableJobs(root: SessionRoot): Array<ToolCallJob | ProgrammaticJob> {
27
+ const out: Array<ToolCallJob | ProgrammaticJob> = [];
28
+ for (const turn of reachableTurns(root)) {
29
+ for (const cell of reactChunks(turn)) {
30
+ if (cell instanceof ToolCallJob) out.push(cell);
31
+ else if (cell instanceof ProgrammaticJob) {
32
+ out.push(cell, ...cell.toolCalls);
33
+ }
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+
39
+ export function sealedJobText(job: ToolCallJob | ProgrammaticJob): string {
40
+ if (job instanceof ToolCallJob) return paintObservation(liftToolImages(job.observationPayload()).payload);
41
+ const text = job.resultText();
42
+ return text !== "" ? text : (job.receiptJson ?? "");
43
+ }
44
+
45
+ function nameOf(job: ToolCallJob | ProgrammaticJob): string {
46
+ return job instanceof ToolCallJob ? job.name : job.toolName;
47
+ }
48
+
49
+ export function recallContext(
50
+ root: SessionRoot,
51
+ args: { readonly ref?: unknown; readonly offset?: unknown; readonly length?: unknown },
52
+ ): ToolOutcomeSnap {
53
+ const refused = (text: string): ToolOutcomeSnap => ({ ok: false, text: `context_recall: ${text}` });
54
+ let ref = typeof args.ref === "string" ? args.ref.trim() : "";
55
+ if (ref === "") return refused("ref is required (job:<id> or visual:<id>)");
56
+ const offset = typeof args.offset === "number" && args.offset > 0 ? Math.floor(args.offset) : 0;
57
+ const length =
58
+ typeof args.length === "number" && args.length > 0
59
+ ? Math.min(Math.floor(args.length), SQUEEZE_COMPACT_CHARS)
60
+ : SQUEEZE_COMPACT_CHARS;
61
+
62
+ if (ref.startsWith("visual:")) {
63
+ const id = ref.slice("visual:".length);
64
+ for (const turn of reachableTurns(root)) {
65
+ const visual = turn.visuals.find((v) => v.id === id);
66
+ if (visual === undefined) continue;
67
+ return {
68
+ ok: true,
69
+ value: `[recall ${ref}] [Image attached: ${visual.mime}, ${visual.width}x${visual.height}${visual.sourcePath ? `, ${visual.sourcePath}` : ""}]`,
70
+ json: { visualId: visual.id },
71
+ };
72
+ }
73
+ return refused(`unknown or unreachable ref ${ref}`);
74
+ }
75
+
76
+ for (let hop = 0; hop < 4; hop += 1) {
77
+ if (!ref.startsWith("job:")) return refused(`unknown ref ${ref} (use job:<id> or visual:<id>)`);
78
+ const wantArgs = ref.endsWith("#args");
79
+ const prefix = ref.slice("job:".length).replace(/#args$/, "");
80
+ if (prefix.length < MIN_PREFIX) return refused(`ref ${ref} is too short`);
81
+ const hits = reachableJobs(root).filter((job) => job.uuid.startsWith(prefix));
82
+ if (hits.length === 0) return refused(`unknown or unreachable ref ${ref}`);
83
+ if (hits.length > 1) return refused(`ref ${ref} is ambiguous`);
84
+ const job = hits[0]!;
85
+ if (nameOf(job) === CONTEXT_RECALL_TOOL_NAME) {
86
+ // A recall of a recall resolves to the original.
87
+ const inner = job.args?.ref;
88
+ if (typeof inner !== "string") return refused(`ref ${ref} names an unreadable recall`);
89
+ ref = inner;
90
+ if (ref.startsWith("visual:")) return recallContext(root, { ref, offset, length });
91
+ continue;
92
+ }
93
+ if (!wantArgs && !job.completed) return refused(`${ref} has no sealed result yet`);
94
+ const text = wantArgs ? JSON.stringify(job.args ?? {}) : sealedJobText(job);
95
+ const total = text.length;
96
+ const pages = Math.max(1, Math.ceil(total / length));
97
+ const end = Math.min(total, offset + length);
98
+ const note =
99
+ !wantArgs && job instanceof ToolCallJob && job.regenerateable ? "; as observed, re-read for current" : "";
100
+ const header = `[recall ${ref} ${nameOf(job)}${wantArgs ? " args" : ""}: chars ${offset}-${end} of ${total}, page ${Math.floor(offset / length) + 1}/${pages}${end < total ? `; next offset ${end}` : ""}${note}]`;
101
+ return { ok: true, value: `${header}\n${text.slice(offset, end)}` };
102
+ }
103
+ return refused("ref chain too deep");
104
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * System-tool BODY. Claimed after an allow-side guard. Outcome lives on
3
+ * the job. This run does not vote. Effect bodies are catalog.execute.
4
+ */
5
+ import {
6
+ cancelOutcomeText,
7
+ DEFAULT_SHELL_TAIL_BYTES,
8
+ documentRoot,
9
+ FileMutateToolJob,
10
+ isAbortError,
11
+ isTimeoutAbortReason,
12
+ type JobStopReport,
13
+ parseShellTailBytes,
14
+ outcomeText,
15
+ projectionsOf,
16
+ rememberJobResult,
17
+ RESULT_NOTE_CHARS,
18
+ reportShellTail,
19
+ retractShellTail,
20
+ SessionRoot,
21
+ SystemToolJob,
22
+ writePluginMeta,
23
+ } from "@inhuman.tools/harness-model";
24
+ import type { ToolOutcomeSnap } from "@inhuman.tools/harness-model/schemas";
25
+ import { z } from "zod";
26
+
27
+ import { externalizePrior } from "./blob-port.js";
28
+ import { runWithExtraRoot } from "./extra-root.js";
29
+ import { pendingPropose, pluginCallContextOf } from "./plugin-context.js";
30
+ import { CONTEXT_RECALL_TOOL_NAME, recallContext } from "./recall.js";
31
+ import { ToolError } from "./tool-error.js";
32
+ import { workerToolAllowed } from "./worker-tools.js";
33
+ import { lineStartOfSpan } from "./write-admit.js";
34
+ import { behaviorEntriesOf, catalogOf, requireCatalog } from "../tool-catalog/index.js";
35
+
36
+ function cancelledSnap(signal: AbortSignal): ToolOutcomeSnap {
37
+ return { ok: false, text: cancelOutcomeText(signal.reason) };
38
+ }
39
+
40
+ const TAIL_MS = 50;
41
+
42
+ function tailPublisher(job: SystemToolJob): {
43
+ push: (frame: { readonly text: string; readonly totalBytes: number }) => void;
44
+ flush: () => void;
45
+ } {
46
+ let lastAt = 0;
47
+ let timer: ReturnType<typeof setTimeout> | undefined;
48
+ let latest: { text: string; totalBytes: number } | undefined;
49
+ const send = (frame: { text: string; totalBytes: number }): void => {
50
+ if (job.receiptJson != null && job.receiptJson !== "") return;
51
+ latest = undefined;
52
+ lastAt = Date.now();
53
+ reportShellTail(job, frame);
54
+ };
55
+ return {
56
+ push(frame) {
57
+ const now = Date.now();
58
+ if (now - lastAt >= TAIL_MS) {
59
+ if (timer !== undefined) clearTimeout(timer);
60
+ timer = undefined;
61
+ send(frame);
62
+ return;
63
+ }
64
+ latest = frame;
65
+ timer ??= setTimeout(() => {
66
+ timer = undefined;
67
+ if (latest !== undefined) send(latest);
68
+ }, TAIL_MS);
69
+ },
70
+ flush() {
71
+ if (timer !== undefined) clearTimeout(timer);
72
+ timer = undefined;
73
+ if (latest !== undefined) send(latest);
74
+ },
75
+ };
76
+ }
77
+
78
+ /** The captured prior text lands inline or by reference; the other field is cleared, so at most one is set. */
79
+ function stampPrior(job: FileMutateToolJob, text: string | null): void {
80
+ if (text === null) {
81
+ job.priorContent = null;
82
+ job.priorBlobJson = null;
83
+ return;
84
+ }
85
+ const stamp = externalizePrior(text);
86
+ if ("priorBlobJson" in stamp) {
87
+ job.priorContent = null;
88
+ job.priorBlobJson = stamp.priorBlobJson;
89
+ } else {
90
+ job.priorContent = stamp.priorContent;
91
+ job.priorBlobJson = null;
92
+ }
93
+ }
94
+
95
+ export function sealToolCall(job: SystemToolJob, outcome: ToolOutcomeSnap): void {
96
+ rememberJobResult(job, outcomeText(outcome), RESULT_NOTE_CHARS);
97
+ job.outcomeSnap ??= outcome;
98
+ retractShellTail(job);
99
+ }
100
+
101
+ function asToolResult(out: unknown): ToolOutcomeSnap {
102
+ if (out != null && typeof out === "object" && "ok" in out) {
103
+ const row = out as {
104
+ ok?: unknown;
105
+ text?: unknown;
106
+ value?: unknown;
107
+ truncated?: unknown;
108
+ };
109
+ if (typeof row.ok === "boolean") {
110
+ return {
111
+ ok: row.ok,
112
+ ...(typeof row.text === "string" ? { text: row.text } : {}),
113
+ ...("value" in row ? { value: row.value } : {}),
114
+ ...(row.truncated === true ? { truncated: true } : {}),
115
+ };
116
+ }
117
+ }
118
+ return { ok: true, value: out };
119
+ }
120
+
121
+ function doorText(error: unknown): string {
122
+ if (error instanceof z.ZodError) {
123
+ const issue = error.issues[0];
124
+ const path = issue.path.map(String).join(".");
125
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
126
+ }
127
+ if (error instanceof Error) return error.message;
128
+ return String(error);
129
+ }
130
+
131
+ export async function executeSystemTool(
132
+ job: SystemToolJob,
133
+ signal: AbortSignal,
134
+ opts?: {
135
+ readonly shellTailBytes?: number;
136
+ /** Host list/stop for `job_stop`. The tool body has no job table of its own. */
137
+ readonly stopJob?: (jobId: string) => JobStopReport | Promise<JobStopReport>;
138
+ },
139
+ ): Promise<void> {
140
+ const seal = (outcome: ToolOutcomeSnap): void => {
141
+ sealToolCall(job, outcome);
142
+ };
143
+ try {
144
+ if (signal.aborted) {
145
+ seal(cancelledSnap(signal));
146
+ return;
147
+ }
148
+
149
+ const { toolCallId, name } = job;
150
+ if (toolCallId === "") {
151
+ seal({ ok: false, text: "tool_call snapshot missing toolCallId" });
152
+ return;
153
+ }
154
+
155
+ const session = documentRoot(job);
156
+ if (!(session instanceof SessionRoot)) {
157
+ seal({ ok: false, text: "tool_call missing session" });
158
+ return;
159
+ }
160
+
161
+ const catalog = requireCatalog(catalogOf(session), name);
162
+ const entry = catalog.catalogEntry(name) ?? behaviorEntriesOf(session).find((row) => row.wireName === name);
163
+ if (session.agent.spec.name !== "" && !workerToolAllowed(catalog, session.agent.spec.name, name, session)) {
164
+ seal({ ok: false, text: "outside worker tool ceiling" });
165
+ return;
166
+ }
167
+ job.regenerateable = entry?.class === "read";
168
+ if (signal.aborted) {
169
+ seal(cancelledSnap(signal));
170
+ return;
171
+ }
172
+
173
+ const raw = job.invocation();
174
+ const args = job.args ?? (raw === "" ? {} : null);
175
+ if (args == null) {
176
+ seal({
177
+ ok: false,
178
+ text: `${name}: arguments were not valid JSON — ${raw.slice(0, 200)}`,
179
+ });
180
+ return;
181
+ }
182
+
183
+ if (name === CONTEXT_RECALL_TOOL_NAME) {
184
+ seal(recallContext(session, args));
185
+ return;
186
+ }
187
+
188
+ if (name === "job_stop") {
189
+ const jobIdRaw = args.job_id;
190
+ const jobId = typeof jobIdRaw === "string" ? jobIdRaw : "";
191
+ const report = await (opts?.stopJob?.(jobId) ?? {
192
+ ok: false as const,
193
+ text: "job_stop is not available on this host",
194
+ });
195
+ seal(report);
196
+ return;
197
+ }
198
+
199
+ const root = session.path.trim() === "" ? process.cwd() : session.path;
200
+ // Extra-root follows the license, not the human bit. Yolo and auto
201
+ // remainder leave approved null; writing it here would derive HITL.
202
+ // block never licenses. A folder the human said to treat as part of the session is a floor approve for
203
+ // reads under it (a session read root), so it opens the same door through `licenseAllows`; nothing is remembered
204
+ // from a single outside read.
205
+ const tailBytes = parseShellTailBytes(opts?.shellTailBytes ?? DEFAULT_SHELL_TAIL_BYTES);
206
+ const live = name === "shell" && tailBytes > 0 ? tailPublisher(job) : undefined;
207
+ let dispatched: unknown;
208
+ try {
209
+ ToolError.invariant(entry?.execute, () =>
210
+ name.startsWith("mcp__")
211
+ ? `unknown MCP tool "${name}" — connection missing, disabled, or not expanded`
212
+ : `unknown tool "${name}" — available: ${catalog.catalogWireNames().join(", ")}`,
213
+ );
214
+ try {
215
+ dispatched = await runWithExtraRoot(job.licenseAllows, () =>
216
+ entry.execute!(args, {
217
+ root,
218
+ catalog,
219
+ signal,
220
+ sessionId: session.sessionId,
221
+ onPriorContent:
222
+ job instanceof FileMutateToolJob
223
+ ? (text) => {
224
+ stampPrior(job, text);
225
+ if (typeof text === "string" && name === "edit_file") {
226
+ const old = typeof args.old_string === "string" ? args.old_string : "";
227
+ const line = lineStartOfSpan(text, old);
228
+ if (line != null) job.priorLineStart = line;
229
+ }
230
+ }
231
+ : undefined,
232
+ shellTailBytes: tailBytes,
233
+ onTail: live?.push,
234
+ ...pluginCallContextOf(session, job, signal, entry.source.kind === "pack" ? entry.source.pluginName : ""),
235
+ }),
236
+ );
237
+ } catch (error) {
238
+ if (error instanceof ToolError) throw error;
239
+ dispatched = {
240
+ ok: false,
241
+ text: error instanceof z.ZodError ? doorText(error) : `${name}: ${doorText(error)}`,
242
+ };
243
+ }
244
+ } finally {
245
+ live?.flush();
246
+ }
247
+ const proposed = pendingPropose.get(job);
248
+ pendingPropose.delete(job);
249
+ if (proposed != null) {
250
+ const projection = projectionsOf(job).get(proposed.pluginId);
251
+ if (projection?.hostOnly?.(proposed.action) === true) {
252
+ seal({ ok: false, text: `${proposed.pluginId}: host-only metadata` });
253
+ return;
254
+ }
255
+ writePluginMeta(job, proposed.pluginId, proposed.action, false);
256
+ }
257
+ seal(asToolResult(dispatched));
258
+ } catch (error) {
259
+ if (signal.aborted) {
260
+ seal(cancelledSnap(signal));
261
+ return;
262
+ }
263
+ if (error instanceof ToolError) {
264
+ seal({ ok: false, text: error.message });
265
+ return;
266
+ }
267
+ if (isAbortError(error) && !isTimeoutAbortReason(error)) {
268
+ seal({ ok: false, text: cancelOutcomeText(error) });
269
+ return;
270
+ }
271
+ seal({ ok: false, text: `error: ${error instanceof Error ? error.message : String(error)}` });
272
+ }
273
+ }
@@ -0,0 +1,6 @@
1
+ import { ErrorWithInvariant } from "@inhuman.tools/error-with-invariant";
2
+
3
+ /** Failure the model is meant to read and correct — never a turn-killer. */
4
+ export class ToolError extends ErrorWithInvariant {
5
+ readonly name = "ToolError";
6
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Worker tool ceiling. The one implementation: the session view and admit/execute both call it.
3
+ * Bot-granted MCP and private plugin MCP are answered by the catalog's `workerHost`.
4
+ */
5
+ import {
6
+ AGENT_ASK,
7
+ AGENT_MESSAGE,
8
+ AGENT_NOTIFY,
9
+ AGENT_RESPOND,
10
+ AGENT_SEND,
11
+ AGENT_VOID,
12
+ AskAgentSpec,
13
+ SCHEME_REPL_TOOL_NAME,
14
+ SCHEME_WATCH_TOOL_NAME,
15
+ SessionRoot,
16
+ } from "@inhuman.tools/harness-model";
17
+
18
+ import { behaviorEntriesOf, catalogTriggersReactLoop, type KernelToolCatalog } from "../tool-catalog/index.js";
19
+
20
+ function isAgentComms(wire: string): boolean {
21
+ return (
22
+ wire === AGENT_RESPOND ||
23
+ wire === AGENT_VOID ||
24
+ wire === AGENT_NOTIFY ||
25
+ wire === AGENT_MESSAGE ||
26
+ wire === AGENT_SEND ||
27
+ wire === AGENT_ASK
28
+ );
29
+ }
30
+
31
+ function askWorkerToolAllowed(catalog: KernelToolCatalog, wire: string): boolean {
32
+ if (wire === AGENT_VOID) return false;
33
+ return (
34
+ wire === SCHEME_REPL_TOOL_NAME ||
35
+ wire === SCHEME_WATCH_TOOL_NAME ||
36
+ catalog.catalogEntry(wire)?.isSideChannel === true ||
37
+ isAgentComms(wire)
38
+ );
39
+ }
40
+
41
+ export function workerToolAllowed(
42
+ catalog: KernelToolCatalog,
43
+ recipeName: string,
44
+ wire: string,
45
+ sessionRoot?: SessionRoot,
46
+ ): boolean {
47
+ const spec = sessionRoot?.agent.spec;
48
+ if (spec instanceof AskAgentSpec) {
49
+ if (wire === AGENT_VOID) return spec.voidLegal === true;
50
+ if (behaviorEntriesOf(sessionRoot).some((entry) => entry.wireName === wire)) return true;
51
+ if (spec.inheritCallerTools) return true;
52
+ if (askWorkerToolAllowed(catalog, wire)) return true;
53
+ if (spec.tools?.includes(wire) === true) return true;
54
+ if (spec.name === "" || spec.toolsOnly) return false;
55
+ const named = catalog.recipe(spec.name);
56
+ if (named == null) return false;
57
+ return catalog.workerHost.recipeGrantsWire(catalog, named.spec, wire, sessionRoot?.path ?? "");
58
+ }
59
+ if (wire === AGENT_VOID && spec?.voidLegal === false) return false;
60
+ if (recipeName === "") return true;
61
+ if (behaviorEntriesOf(sessionRoot).some((entry) => entry.wireName === wire)) return true;
62
+ const recipe = catalog.recipe(recipeName);
63
+ if (recipe == null) return false;
64
+ if (catalog.privateWorkerEntries.some((row) => row.wireName === wire)) return true;
65
+ const entry = catalog.catalogEntry(wire);
66
+ if (entry == null) return catalog.workerHost.privateWireAllowed(recipeName, wire, sessionRoot);
67
+ if (
68
+ wire === SCHEME_REPL_TOOL_NAME ||
69
+ wire === SCHEME_WATCH_TOOL_NAME ||
70
+ !catalogTriggersReactLoop(catalog, wire) ||
71
+ isAgentComms(wire)
72
+ ) {
73
+ return true;
74
+ }
75
+ return catalog.workerHost.recipeGrantsWire(catalog, recipe.spec, wire, sessionRoot?.path ?? "");
76
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * write_file overwrite gate and admit snapshot. Effect bodies stay
3
+ * outside this package; this is the door the guard still sees.
4
+ */
5
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
6
+ import path from "node:path";
7
+
8
+ import { parseJsonRecord } from "@inhuman.tools/harness-model/json-record";
9
+
10
+ const MAX_FILE_READ_BYTES = 4 * 1024 * 1024;
11
+
12
+ const OVERWRITE_EXISTING_DOOR =
13
+ "tried to overwrite the existing file, use write_file with the overwriteExisting flag if you need that to happen";
14
+
15
+ export function overwriteExistingDoor(pathText: string): string {
16
+ return `write_file: ${pathText} exists — ${OVERWRITE_EXISTING_DOOR}`;
17
+ }
18
+
19
+ /** True only for boolean true or the string "true". Missing / false → door on exist. */
20
+ export function overwriteExistingOf(args: unknown): boolean {
21
+ if (args == null || typeof args !== "object") return false;
22
+ const rec = args as Record<string, unknown>;
23
+ const value = rec.overwriteExisting ?? rec.overwrite_existing ?? rec.overwrite;
24
+ return value === true || value === "true";
25
+ }
26
+
27
+ /** Tape line annotation. Line 1 is omitted. */
28
+ export function lineStartOfSpan(file: string | null | undefined, span: string): number | null {
29
+ if (file == null || span === "") return null;
30
+ let idx = file.indexOf(span);
31
+ if (idx === -1) {
32
+ const firstLine = span.split("\n")[0] ?? "";
33
+ if (firstLine === "" || firstLine === span) return null;
34
+ idx = file.indexOf(firstLine);
35
+ }
36
+ if (idx === -1) return null;
37
+ let line = 1;
38
+ for (let i = 0; i < idx; i++) if (file[i] === "\n") line++;
39
+ return line <= 1 ? null : line;
40
+ }
41
+
42
+ /** NUL-free UTF-8 under the read budget. Empty file is "". */
43
+ export function priorTextSync(target: string): string | null {
44
+ try {
45
+ const info = statSync(target);
46
+ if (!info.isFile() || info.size > MAX_FILE_READ_BYTES) return null;
47
+ const buf = readFileSync(target);
48
+ if (buf.includes(0)) return null;
49
+ return buf.toString("utf8");
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ type WriteAdmitSnap = {
56
+ /** `null` means we could not tell — not a definite miss. */
57
+ readonly existed: boolean | null;
58
+ readonly priorContent: string | null;
59
+ readonly refuse: string | null;
60
+ };
61
+
62
+ function insideRoot(root: string, file: string): boolean {
63
+ const from = path.relative(root, file);
64
+ return from === "" || (!from.startsWith("..") && !path.isAbsolute(from));
65
+ }
66
+
67
+ /**
68
+ * Admit runs before any license. Only a path that stays inside the workspace, links resolved, may
69
+ * be read or probed here; anything else is left to the licensed body, which captures prior content
70
+ * after approval and keeps the overwrite door.
71
+ */
72
+ export function writeFileAdmitSnap(root: string, argsJson: string): WriteAdmitSnap {
73
+ const args = parseJsonRecord(argsJson === "" ? "{}" : argsJson);
74
+ const raw = args !== null && typeof args.path === "string" ? args.path : "";
75
+ if (raw.trim() === "") return { existed: null, priorContent: null, refuse: null };
76
+ const target = path.resolve(root, raw);
77
+ const unknown: WriteAdmitSnap = { existed: null, priorContent: null, refuse: null };
78
+ try {
79
+ const realRoot = realpathSync(root);
80
+ if (!insideRoot(path.resolve(root), target)) return unknown;
81
+ if (existsSync(target) && !insideRoot(realRoot, realpathSync(target))) return unknown;
82
+ if (!existsSync(target)) return { existed: false, priorContent: null, refuse: null };
83
+ if (!statSync(target).isFile()) return { existed: null, priorContent: null, refuse: null };
84
+ } catch {
85
+ return { existed: null, priorContent: null, refuse: null };
86
+ }
87
+ const priorContent = priorTextSync(target);
88
+ const refuse = overwriteExistingOf(args ?? {}) ? null : overwriteExistingDoor(raw);
89
+ return { existed: true, priorContent, refuse };
90
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.