@tekmidian/pai 1.0.4 → 1.0.6

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 (248) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/{aibroker-client-CQPGqLxL.mjs → aibroker-client-CXsyC7A8.mjs} +2 -2
  3. package/dist/aibroker-client-ChB7gSxV.mjs +3 -0
  4. package/dist/auto-route-Bc8xP6TQ.mjs +11 -0
  5. package/dist/{auto-route-21GdHL70.mjs → auto-route-YaEGVrFl.mjs} +2 -2
  6. package/dist/budget-DEiebT2A.mjs +15 -0
  7. package/dist/{caller-identity-yLFb7CKq.mjs → caller-identity-C6Nc96iM.mjs} +4 -4
  8. package/dist/{capacity-DEveQdFV.mjs → capacity-B2PgKAg4.mjs} +69 -6
  9. package/dist/{chain-D7PFur8Y.mjs → chain-CtK49-Ma.mjs} +5 -5
  10. package/dist/cli/index.mjs +2126 -290
  11. package/dist/{cockpit-actions-BfRMr2sm.mjs → cockpit-actions-C73mJAJ8.mjs} +18 -18
  12. package/dist/{cockpit-actions-OodH_vx_.mjs → cockpit-actions-D1OPP6Es.mjs} +30 -26
  13. package/dist/cockpit-memory-CLHZD64W.mjs +20 -0
  14. package/dist/config/defaults/config.yaml +21 -1
  15. package/dist/{config-XpZWOYOG.mjs → config-Bz8pAU5Q.mjs} +30 -5
  16. package/dist/{config-DtENJ6cL.mjs → config-DHgPx3ER.mjs} +1 -1
  17. package/dist/config-DLmyEK2q.mjs +4 -0
  18. package/dist/{config-Bu1_vbY8.mjs → config-SlZ-Z52i.mjs} +4 -3
  19. package/dist/{controls-JLm6g0yP.mjs → controls-rIItFeIJ.mjs} +59 -66
  20. package/dist/daemon/index.mjs +40 -39
  21. package/dist/daemon-176qA79N.mjs +46 -0
  22. package/dist/{daemon-nXanenDM.mjs → daemon-SoUvPUOf.mjs} +80 -30
  23. package/dist/daemon-mcp/index.mjs +49 -38
  24. package/dist/detect-C8nFP-kR.mjs +10 -0
  25. package/dist/{detect-BLKsUOgG.mjs → detect-DLjeYwnt.mjs} +2 -2
  26. package/dist/{embed-pass-e64AwDCa.mjs → embed-pass-CN1D8MXl.mjs} +5 -5
  27. package/dist/{embedding-gate-Cnk4YCyX.mjs → embedding-gate-BVQvUmox.mjs} +2 -2
  28. package/dist/{embedding-gate-6rZrWRXa.mjs → embedding-gate-iq0oQEkF.mjs} +3 -3
  29. package/dist/{endreason-BC-v05H_.mjs → endreason-C2-hYK0_.mjs} +4 -4
  30. package/dist/{factory-B7nv75rC.mjs → factory-CYwa0jYj.mjs} +6 -6
  31. package/dist/factory-DFwez99M.mjs +9 -0
  32. package/dist/{fallback-CBWpp2F2.mjs → fallback-CwPSCrX8.mjs} +9 -231
  33. package/dist/{health-C4FQmkJn.mjs → health-D0mUAVdw.mjs} +5 -5
  34. package/dist/{helpers-DSNK_1KP.mjs → helpers-Dj2nY56t.mjs} +2 -2
  35. package/dist/{helpers-Dz7mjTSw.mjs → helpers-DyHKAwrW.mjs} +6 -6
  36. package/dist/hooks/block-sleep-poll.mjs +1 -1
  37. package/dist/hooks/capture-all-events.mjs +68 -3
  38. package/dist/hooks/capture-tool-output.mjs +68 -3
  39. package/dist/hooks/handoff-link.mjs +128 -0
  40. package/dist/hooks/load-project-context.mjs +8 -8
  41. package/dist/hooks/local-model-hint.mjs +1 -1
  42. package/dist/hooks/route-agents-to-worker.mjs +1 -1
  43. package/dist/hooks/security-validator.mjs +842 -90
  44. package/dist/hooks/supervision-banner.mjs +186 -48
  45. package/dist/hooks/worker-edit-repair.mjs +2 -2
  46. package/dist/hooks/worker-guard.mjs +65 -9
  47. package/dist/hooks/worker-note.mjs +70 -5
  48. package/dist/hooks/worker-pool-host.mjs +1518 -265
  49. package/dist/hooks/worker-proxy.mjs +1 -1
  50. package/dist/hooks/worker-status-line.mjs +225 -86
  51. package/dist/hooks/worker-supervision.mjs +1 -1
  52. package/dist/hooks/worker-tool-name-repair.mjs +2 -2
  53. package/dist/index.mjs +9 -9
  54. package/dist/{ipc-client-CnYEF31R.mjs → ipc-client-CmagIFLh.mjs} +2 -2
  55. package/dist/{ipc-client-DN7EvoVo.mjs → ipc-client-DmbSIBUK.mjs} +1 -1
  56. package/dist/{kill-tree-DleMNZX3.mjs → kill-tree-DPiMscaY.mjs} +8 -8
  57. package/dist/kill-tree-Dxei6qlR.mjs +10 -0
  58. package/dist/{launch-DlZTKE3u.mjs → launch-BZT3JDoZ.mjs} +3 -3
  59. package/dist/{launch-Di7N-J5X.mjs → launch-Bn2Zf90h.mjs} +3 -3
  60. package/dist/{indexer-backend-BdjeL4P8.mjs → lessons-CFVU0cwA.mjs} +262 -43
  61. package/dist/{local-candidate-DLdwQt0s.mjs → local-candidate-DFd4pp-w.mjs} +3 -3
  62. package/dist/{local-instances-DOxSgT_L.mjs → local-instances-DhF4_sV2.mjs} +2 -2
  63. package/dist/{local-instances-ZeRkkuS5.mjs → local-instances-KTh8IeOT.mjs} +4 -4
  64. package/dist/{local-tiers-BE4jGaMa.mjs → local-tiers-CqwGUcEn.mjs} +3 -3
  65. package/dist/{local-tiers-DNMI5W86.mjs → local-tiers-DSlIYvvm.mjs} +3 -3
  66. package/dist/{main-config-ops-kTXR8Pyv.mjs → main-config-ops-rda08BCD.mjs} +3 -3
  67. package/dist/{main-resolver-yp_puSSb.mjs → main-resolver-BKwI0I6H.mjs} +18 -18
  68. package/dist/{migrate-DrA_OwlZ.mjs → migrate-C2z1YmWC.mjs} +2 -2
  69. package/dist/{model-inventory-h6Z9fN_o.mjs → model-inventory-BXv15B0d.mjs} +13 -13
  70. package/dist/{models-add-TVni3A7V.mjs → models-add-5yXTyz3g.mjs} +38 -37
  71. package/dist/operator-Bmv5tiHQ.mjs +7 -0
  72. package/dist/{operator-CyuB3aVw.mjs → operator-Br6GeswF.mjs} +2 -2
  73. package/dist/{pai-marker-BcZyJguM.mjs → pai-marker-D6OoVbM_.mjs} +2 -2
  74. package/dist/{pane-audit-Lqy9N_Ki.mjs → pane-audit-DLYy9-4g.mjs} +13 -23
  75. package/dist/{paths-CHifubDh.mjs → paths-CypxFTfn.mjs} +4 -4
  76. package/dist/{paths-CFHTloSe.mjs → paths-DIOwNcj-.mjs} +3 -3
  77. package/dist/{planner-j77ndkBf.mjs → planner-BzZ_2lc-.mjs} +29 -29
  78. package/dist/{pool-Dh-q3iJi.mjs → pool-9EsbKD-U.mjs} +2 -2
  79. package/dist/{pool-cli-DQ6OzRy2.mjs → pool-cli-Cy5mlycP.mjs} +6 -6
  80. package/dist/{pool-cli-BggU8GRp.mjs → pool-cli-Db0uDwB9.mjs} +5 -5
  81. package/dist/{postgres-BK4FP9Zg.mjs → postgres-B1B80bcj.mjs} +3 -3
  82. package/dist/{postgres-DNIOQijl.mjs → postgres-BL5oBBgY.mjs} +3 -3
  83. package/dist/{project-setup-CBRtFFh0.mjs → project-setup-DHxdJjEc.mjs} +24 -25
  84. package/dist/providers-BH6hQYbU.mjs +16 -0
  85. package/dist/{providers-C9QC0TtO.mjs → providers-BNV7CzC-.mjs} +6 -6
  86. package/dist/{query-feedback-DN52N9C5.mjs → query-feedback-BWl2HRwN.mjs} +2 -2
  87. package/dist/registrable-BwFiqazy.mjs +9 -0
  88. package/dist/{registrable-DpyTtajg.mjs → registrable-Dxs4oKIj.mjs} +4 -4
  89. package/dist/render-DVGXpfaT.mjs +13 -0
  90. package/dist/render-jPpMrudf.mjs +2240 -0
  91. package/dist/root-C8cxhdn9.mjs +36 -0
  92. package/dist/{root-C4oXlXS_.mjs → root-DmOzMeSv.mjs} +10 -10
  93. package/dist/route-table-COAmc1nB.mjs +15 -0
  94. package/dist/{router-CQSc3lk3.mjs → router-D2476pOp.mjs} +2 -2
  95. package/dist/{router-DwHJRgG9.mjs → router-ghujlEFb.mjs} +1 -1
  96. package/dist/{routing-f2zulGwD.mjs → routing-B_cg1_Xx.mjs} +13 -13
  97. package/dist/{routing-DmkheCRH.mjs → routing-aJKMIagQ.mjs} +16 -16
  98. package/dist/{run-BwZKBJZZ.mjs → run-DE9Q0ldN.mjs} +55 -31
  99. package/dist/{runtime-paths-WsO9sJFc.mjs → runtime-paths-twfV23YL.mjs} +2 -2
  100. package/dist/{scan-BBGvU5EB.mjs → scan-CNMcltOX.mjs} +7 -7
  101. package/dist/{scope-D-h6F6DN.mjs → scope-BTWstJ1B.mjs} +4 -4
  102. package/dist/session-DgZifjLx.mjs +35 -0
  103. package/dist/{session-Dn5OJXlg.mjs → session-HBYOABWU.mjs} +28 -28
  104. package/dist/session-scan-BZxnYN1v.mjs +9 -0
  105. package/dist/{session-scan-DUn10MBd.mjs → session-scan-CX3MKCNg.mjs} +6 -6
  106. package/dist/{session-usage-CPvXvSkw.mjs → session-usage-CCmPFFa6.mjs} +117 -2
  107. package/dist/{status-ggJ7lgVg.mjs → status-Cecau8Ps.mjs} +9 -5
  108. package/dist/status-D52xC_i0.mjs +6 -0
  109. package/dist/{tool-errors-7E13StWd.mjs → tool-errors-BFw1gv-z.mjs} +2 -2
  110. package/dist/{tool-errors-CJWODvlo.mjs → tool-errors-COM2JGEr.mjs} +6 -6
  111. package/dist/viewer-Ce-Xa9L3.mjs +22 -0
  112. package/dist/{viewer-wfKnvnrI.mjs → viewer-DvBg6N22.mjs} +16 -14
  113. package/dist/{wait-ByV0OyMZ.mjs → wait-C5iWILj_.mjs} +3 -3
  114. package/dist/worker-config-BmcJ9R0U.mjs +42 -0
  115. package/dist/{worker-config-eY5Qy4vl.mjs → worker-config-DkONNk_H.mjs} +164 -107
  116. package/dist/workers-config-DwzrtdYu.mjs +4 -0
  117. package/dist/{worktree-CRs2gyg0.mjs → worktree-O_kU6Q3y.mjs} +164 -8
  118. package/dist/{zettelkasten-tpCRBYvH.mjs → zettelkasten-BH8AdNh8.mjs} +4 -4
  119. package/dist/{zettelkasten-_h_tUm2T.mjs → zettelkasten-FFhQFESJ.mjs} +2 -2
  120. package/docs/commands/README.md +33 -2
  121. package/docs/commands/audit.md +34 -3
  122. package/docs/commands/backup.md +1 -1
  123. package/docs/commands/build.md +4 -4
  124. package/docs/commands/cd.md +1 -1
  125. package/docs/commands/clear-names.md +1 -1
  126. package/docs/commands/cockpit.md +1 -1
  127. package/docs/commands/config.md +21 -1
  128. package/docs/commands/daemon.md +1 -1
  129. package/docs/commands/db.md +1 -1
  130. package/docs/commands/doc.md +1 -1
  131. package/docs/commands/end.md +1 -1
  132. package/docs/commands/find.md +1 -1
  133. package/docs/commands/git.md +110 -0
  134. package/docs/commands/handoff.md +70 -0
  135. package/docs/commands/harness.md +74 -0
  136. package/docs/commands/help.md +1 -1
  137. package/docs/commands/home.md +1 -1
  138. package/docs/commands/hooks-db.md +1 -1
  139. package/docs/commands/identity.md +1 -1
  140. package/docs/commands/install.md +1 -1
  141. package/docs/commands/issue.md +1 -1
  142. package/docs/commands/kg.md +1 -1
  143. package/docs/commands/launch.md +1 -1
  144. package/docs/commands/lessons.md +201 -0
  145. package/docs/commands/list.md +1 -1
  146. package/docs/commands/ls.md +1 -1
  147. package/docs/commands/mcp.md +1 -1
  148. package/docs/commands/memory.md +1 -1
  149. package/docs/commands/models.md +1 -1
  150. package/docs/commands/more.md +1 -1
  151. package/docs/commands/new.md +1 -1
  152. package/docs/commands/notes.md +1 -1
  153. package/docs/commands/notify.md +1 -1
  154. package/docs/commands/observation.md +1 -1
  155. package/docs/commands/obsidian.md +1 -1
  156. package/docs/commands/open.md +1 -1
  157. package/docs/commands/pane.md +1 -1
  158. package/docs/commands/pause.md +1 -1
  159. package/docs/commands/plan.md +1 -1
  160. package/docs/commands/project.md +1 -1
  161. package/docs/commands/projects.md +1 -1
  162. package/docs/commands/providers.md +1 -1
  163. package/docs/commands/queue.md +1 -1
  164. package/docs/commands/registry.md +1 -1
  165. package/docs/commands/reload.md +1 -1
  166. package/docs/commands/rename.md +1 -1
  167. package/docs/commands/restore.md +1 -1
  168. package/docs/commands/resume.md +1 -1
  169. package/docs/commands/router.md +1 -1
  170. package/docs/commands/rules.md +70 -0
  171. package/docs/commands/session.md +1 -1
  172. package/docs/commands/sessions.md +1 -1
  173. package/docs/commands/setup.md +1 -1
  174. package/docs/commands/setups.md +1 -1
  175. package/docs/commands/shell-init.md +1 -1
  176. package/docs/commands/skill.md +1 -1
  177. package/docs/commands/task.md +1 -1
  178. package/docs/commands/tasks.md +1 -1
  179. package/docs/commands/topic.md +1 -1
  180. package/docs/commands/update.md +1 -1
  181. package/docs/commands/worker.md +1 -1
  182. package/docs/commands/zettel.md +1 -1
  183. package/docs/concepts/panes.md +3 -2
  184. package/gemini-extension.json +1 -1
  185. package/package.json +2 -2
  186. package/pai-plugin.json +1 -1
  187. package/plugins/context-preservation/plugin.json +1 -1
  188. package/plugins/core/plugin.json +1 -1
  189. package/plugins/creative/plugin.json +1 -1
  190. package/plugins/observability/plugin.json +1 -1
  191. package/plugins/productivity/plugin.json +1 -1
  192. package/plugins/semantic-search/plugin.json +1 -1
  193. package/plugins/ui/plugin.json +1 -1
  194. package/plugins/zettelkasten/plugin.json +1 -1
  195. package/src/hooks/ts/lib/loop-guard.ts +3 -5
  196. package/src/hooks/ts/lib/rules/builtin/base64-decode-to-shell.rule.yaml +13 -0
  197. package/src/hooks/ts/lib/rules/builtin/chmod-world-writable.rule.yaml +13 -0
  198. package/src/hooks/ts/lib/rules/builtin/chmod-zero.rule.yaml +11 -0
  199. package/src/hooks/ts/lib/rules/builtin/credential-file-read.rule.yaml +13 -0
  200. package/src/hooks/ts/lib/rules/builtin/credential-read-then-network.rule.yaml +29 -0
  201. package/src/hooks/ts/lib/rules/builtin/crontab-persistence.rule.yaml +10 -0
  202. package/src/hooks/ts/lib/rules/builtin/curl-piped-to-shell.rule.yaml +15 -0
  203. package/src/hooks/ts/lib/rules/builtin/download-then-execute.rule.yaml +30 -0
  204. package/src/hooks/ts/lib/rules/builtin/find-root-delete.rule.yaml +11 -0
  205. package/src/hooks/ts/lib/rules/builtin/git-push-force.rule.yaml +24 -0
  206. package/src/hooks/ts/lib/rules/builtin/git-push-plus-refspec.rule.yaml +12 -0
  207. package/src/hooks/ts/lib/rules/builtin/git-reset-hard.rule.yaml +12 -0
  208. package/src/hooks/ts/lib/rules/builtin/injection-then-tool.rule.yaml +30 -0
  209. package/src/hooks/ts/lib/rules/builtin/instruction-override-disregard.rule.yaml +12 -0
  210. package/src/hooks/ts/lib/rules/builtin/instruction-override-ignore.rule.yaml +13 -0
  211. package/src/hooks/ts/lib/rules/builtin/launchagents-write-persistence.rule.yaml +12 -0
  212. package/src/hooks/ts/lib/rules/builtin/launchctl-load-persistence.rule.yaml +10 -0
  213. package/src/hooks/ts/lib/rules/builtin/reverse-shell-dev-tcp.rule.yaml +11 -0
  214. package/src/hooks/ts/lib/rules/builtin/reverse-shell-interactive-bash.rule.yaml +11 -0
  215. package/src/hooks/ts/lib/rules/builtin/rm-recursive-parent.rule.yaml +11 -0
  216. package/src/hooks/ts/lib/rules/builtin/rm-recursive-relative.rule.yaml +11 -0
  217. package/src/hooks/ts/lib/rules/builtin/rm-recursive-root.rule.yaml +24 -0
  218. package/src/hooks/ts/lib/rules/builtin/rm-trailing-tilde.rule.yaml +11 -0
  219. package/src/hooks/ts/lib/rules/builtin/sudoers-nopasswd.rule.yaml +15 -0
  220. package/src/hooks/ts/lib/rules/builtin.generated.ts +530 -0
  221. package/src/hooks/ts/lib/rules/eval.ts +74 -0
  222. package/src/hooks/ts/lib/rules/lint.ts +102 -0
  223. package/src/hooks/ts/lib/rules/load.ts +229 -0
  224. package/src/hooks/ts/lib/rules/types.ts +56 -0
  225. package/src/hooks/ts/pre-tool-use/security-validator.ts +27 -96
  226. package/src/hooks/ts/user-prompt/handoff-link.ts +34 -0
  227. package/dist/aibroker-client-BxzeEHPF.mjs +0 -3
  228. package/dist/auto-route-C3S2W3Bc.mjs +0 -11
  229. package/dist/budget-BrloTOZW.mjs +0 -15
  230. package/dist/cockpit-memory-teG9M8bG.mjs +0 -20
  231. package/dist/config-Cxi-aBf4.mjs +0 -4
  232. package/dist/daemon-DeYImF1F.mjs +0 -45
  233. package/dist/detect-Rrimh0xc.mjs +0 -10
  234. package/dist/factory-Bs9imVMq.mjs +0 -9
  235. package/dist/kill-tree-SV6hrIa3.mjs +0 -10
  236. package/dist/operator-BTqvTIc8.mjs +0 -7
  237. package/dist/providers-FvCLzFge.mjs +0 -16
  238. package/dist/registrable-BzH4TObg.mjs +0 -9
  239. package/dist/render-B35bQTE-.mjs +0 -13
  240. package/dist/render-C84BqQRC.mjs +0 -903
  241. package/dist/root-DD1xx_Vu.mjs +0 -36
  242. package/dist/route-table-FEfDuhIV.mjs +0 -15
  243. package/dist/session-BAhtXdxj.mjs +0 -35
  244. package/dist/session-scan-B57dNuA4.mjs +0 -9
  245. package/dist/status-Dm4dYciN.mjs +0 -6
  246. package/dist/viewer-DEySE4c_.mjs +0 -22
  247. package/dist/worker-config-CO5FXLAT.mjs +0 -41
  248. package/dist/workers-config-f8KrkY4L.mjs +0 -4
@@ -0,0 +1,102 @@
1
+ /**
2
+ * lint.ts — run each rule's own fixtures.
3
+ *
4
+ * Every `tests.match` must hit the rule, every `tests.no_match` must not.
5
+ * A single-step rule's fixtures are plain strings tested as Bash commands; a
6
+ * sequence rule's are lists of step contents, checked step by step (the
7
+ * window and ordering are the scan's business, not the fixtures'). A rule
8
+ * with no fixtures is ok (nothing to check).
9
+ */
10
+
11
+ import { evaluateRules } from "./eval.js";
12
+ import type { Rule, RuleSequenceStep } from "./types.js";
13
+
14
+ export interface RuleLint {
15
+ id: string;
16
+ ok: boolean;
17
+ failures: string[];
18
+ }
19
+
20
+ function stepRegex(step: RuleSequenceStep): RegExp | null {
21
+ try {
22
+ return new RegExp(step.match, step.flags ?? "");
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ /** One fixture step: the content tested, optionally the tool it belongs to. */
29
+ interface FixtureStep {
30
+ tool?: string;
31
+ content: string;
32
+ }
33
+
34
+ /**
35
+ * A fixture sequence hits when every step's regex matches its content and its
36
+ * tool pin, when set. A plain string is a Bash-shaped step; an object names
37
+ * the tool, so a rule whose step is pinned to a fetched result (WebFetch,
38
+ * Read) can still carry a fixture that hits. A step whose source
39
+ * backreferences an earlier step's group (`\1`) is checked with that group's
40
+ * capture substituted in — the same resolution the transcript scan applies —
41
+ * so a fixture like download-then-execute's "same file twice" checks the way
42
+ * the scan does.
43
+ */
44
+ function sequenceHit(rule: Rule, steps: FixtureStep[]): boolean {
45
+ const seqSteps = rule.sequence?.steps ?? [];
46
+ if (steps.length !== seqSteps.length) return false;
47
+ const prior: RegExpMatchArray[] = [];
48
+ for (let i = 0; i < seqSteps.length; i++) {
49
+ const step = seqSteps[i];
50
+ const { tool, content } = steps[i];
51
+ if (step.tool && !new RegExp(step.tool).test(tool ?? "Bash")) return false;
52
+ // a step that names an earlier step's capture (`\1`) is checked with that
53
+ // capture substituted in — the raw `\1` can never match text
54
+ if (step.match.includes("\\1") && prior.length) {
55
+ const cap = prior[0][1];
56
+ if (!cap || !new RegExp(step.match.replace(/\\1/g, cap), step.flags ?? "").test(content)) return false;
57
+ continue;
58
+ }
59
+ const re = stepRegex(step);
60
+ if (!re) return false;
61
+ const m = content.match(re);
62
+ if (!m) return false;
63
+ prior.push(m);
64
+ }
65
+ return true;
66
+ }
67
+
68
+ /** A fixture sequence as written in yaml: a list of strings or {tool, content} objects. */
69
+ type FixtureSequence = Array<string | { tool?: string; content: string }>;
70
+
71
+ function asFixtureSteps(t: string | FixtureSequence): FixtureStep[] | null {
72
+ if (!Array.isArray(t)) return null;
73
+ return t.map((s) => (typeof s === "string" ? { content: s } : { tool: s.tool, content: s.content }));
74
+ }
75
+
76
+ export function lintRules(rules: Rule[]): RuleLint[] {
77
+ return rules.map((rule) => {
78
+ const failures: string[] = [];
79
+ const show = (t: string | FixtureSequence): string =>
80
+ Array.isArray(t) ? t.map((s) => (typeof s === "string" ? s : `${s.tool ?? "Bash"}: ${s.content}`)).join(" | ") : t;
81
+ if (rule.sequence) {
82
+ for (const t of rule.tests?.match ?? []) {
83
+ const steps = asFixtureSteps(t);
84
+ if (!steps || !sequenceHit(rule, steps)) failures.push(`tests.match did not hit: ${show(t)}`);
85
+ }
86
+ for (const t of rule.tests?.no_match ?? []) {
87
+ const steps = asFixtureSteps(t);
88
+ if (steps && sequenceHit(rule, steps)) failures.push(`tests.no_match hit: ${show(t)}`);
89
+ }
90
+ return { id: rule.id, ok: failures.length === 0, failures };
91
+ }
92
+ for (const text of (rule.tests?.match ?? []) as string[]) {
93
+ const hit = evaluateRules([rule], "Bash", text);
94
+ if (!hit || hit.rule.id !== rule.id) failures.push(`tests.match did not hit: ${text}`);
95
+ }
96
+ for (const text of (rule.tests?.no_match ?? []) as string[]) {
97
+ const hit = evaluateRules([rule], "Bash", text);
98
+ if (hit && hit.rule.id === rule.id) failures.push(`tests.no_match hit: ${text}`);
99
+ }
100
+ return { id: rule.id, ok: failures.length === 0, failures };
101
+ });
102
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * load.ts — read `*.rule.yaml` files from directories into rules.
3
+ *
4
+ * One rule per file, or a top-level list of rules. A bad regex or a missing
5
+ * field is reported in `errors` and the rule is skipped — this never throws,
6
+ * because hooks fail open and a broken user rule must not break the hook.
7
+ *
8
+ * Directories are read in order; a later rule with the same id replaces an
9
+ * earlier one (user rules override built-ins), and `severity: off` disables
10
+ * that id entirely.
11
+ */
12
+
13
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { parse } from "yaml";
16
+ import { defaultPaiHomeDir, legacyPaiHomeDir } from "../../../../config/pai-home.js";
17
+ import { BUILTIN_RULES } from "./builtin.generated.js";
18
+ import type { LoadedRule, Rule, RuleSequence, RuleSequenceStep, RuleSeverity } from "./types.js";
19
+
20
+ const SEVERITIES: RuleSeverity[] = ["block", "confirm", "warn", "off"];
21
+
22
+ /** A YAML regex node (`/foo/i`) split into source and flags; a plain string is the source. */
23
+ function splitRegex(value: string): { source: string; flags: string } {
24
+ const isRegexNode = value.length > 2 && value.startsWith("/") && /\/[a-z]*$/.test(value);
25
+ return isRegexNode
26
+ ? { source: value.slice(1, value.lastIndexOf("/")), flags: value.slice(value.lastIndexOf("/") + 1) }
27
+ : { source: value, flags: "" };
28
+ }
29
+
30
+ /** A sequence mapping, validated (steps, window, order, every step regex compiles), or null with an error pushed. */
31
+ function asSequence(raw: Record<string, unknown>, id: string, source: string, errors: string[]): RuleSequence | null {
32
+ const seq = raw.sequence;
33
+ if (typeof seq !== "object" || seq === null || Array.isArray(seq)) {
34
+ errors.push(`${source} (${id}): sequence must be a mapping`);
35
+ return null;
36
+ }
37
+ const s = seq as Record<string, unknown>;
38
+ const rawSteps = Array.isArray(s.steps) ? s.steps : [];
39
+ const steps: RuleSequenceStep[] = [];
40
+ for (const item of rawSteps) {
41
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
42
+ errors.push(`${source} (${id}): sequence step must be a mapping`);
43
+ return null;
44
+ }
45
+ const step = item as Record<string, unknown>;
46
+ const match = typeof step.match === "string" ? step.match : "";
47
+ if (!match) {
48
+ errors.push(`${source} (${id}): sequence step missing match`);
49
+ return null;
50
+ }
51
+ const split = splitRegex(match);
52
+ const flags = typeof step.flags === "string" ? step.flags : split.flags;
53
+ try {
54
+ new RegExp(split.source, flags);
55
+ } catch (e) {
56
+ errors.push(`${source} (${id}): sequence step invalid regex: ${e instanceof Error ? e.message : String(e)}`);
57
+ return null;
58
+ }
59
+ steps.push({
60
+ tool: typeof step.tool === "string" ? step.tool : undefined,
61
+ match: split.source,
62
+ flags: flags || undefined,
63
+ });
64
+ }
65
+ if (!steps.length) {
66
+ errors.push(`${source} (${id}): sequence has no steps`);
67
+ return null;
68
+ }
69
+ const window = Number(s.window_seconds);
70
+ if (!Number.isFinite(window) || window <= 0) {
71
+ errors.push(`${source} (${id}): sequence needs a positive window_seconds`);
72
+ return null;
73
+ }
74
+ const order = s.order === "any" ? "any" : s.order === "strict" ? "strict" : null;
75
+ if (!order) {
76
+ errors.push(`${source} (${id}): sequence order must be strict or any`);
77
+ return null;
78
+ }
79
+ return { steps, window_seconds: window, order };
80
+ }
81
+
82
+ /**
83
+ * The user rule directories: PAI_HOME/rules and .pai/rules under the cwd.
84
+ * PAI_HOME is resolved like paiHomeDir() but without its stderr migration
85
+ * notice — a hook's stderr is machinery, and reading a rules directory is
86
+ * not a reason to complain about it.
87
+ */
88
+ export function userRuleDirs(cwd: string = process.cwd()): string[] {
89
+ const home = process.env.PAI_HOME
90
+ ? process.env.PAI_HOME
91
+ : !existsSync(join(defaultPaiHomeDir(), "config.yaml")) && existsSync(join(legacyPaiHomeDir(), "config.yaml"))
92
+ ? legacyPaiHomeDir()
93
+ : defaultPaiHomeDir();
94
+ return [join(home, "rules"), join(cwd, ".pai", "rules")];
95
+ }
96
+
97
+ function asRule(value: unknown, source: string, errors: string[]): Rule | null {
98
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
99
+ errors.push(`${source}: not a mapping`);
100
+ return null;
101
+ }
102
+ const raw = value as Record<string, unknown>;
103
+ const id = typeof raw.id === "string" ? raw.id : "";
104
+ const severity = raw.severity as RuleSeverity;
105
+ const reason = typeof raw.reason === "string" ? raw.reason : "";
106
+ if (!id) {
107
+ errors.push(`${source}: missing id`);
108
+ return null;
109
+ }
110
+ if (!SEVERITIES.includes(severity)) {
111
+ errors.push(`${source} (${id}): severity must be block, confirm, warn or off`);
112
+ return null;
113
+ }
114
+ if (!reason) {
115
+ errors.push(`${source} (${id}): missing reason`);
116
+ return null;
117
+ }
118
+ // `severity: off` disables the id; it needs no match.
119
+ if (severity === "off") return { id, severity, reason };
120
+ const match = typeof raw.match === "string" ? raw.match : "";
121
+ // A sequence rule carries its patterns in `sequence.steps` instead of `match`.
122
+ if (raw.sequence !== undefined) {
123
+ const sequence = asSequence(raw, id, source, errors);
124
+ if (!sequence) return null;
125
+ return { id, severity, sequence, category: typeof raw.category === "string" ? raw.category : undefined, reason, tests: asTests(raw) };
126
+ }
127
+ if (!match) {
128
+ errors.push(`${source} (${id}): missing match`);
129
+ return null;
130
+ }
131
+ // YAML accepts a bare regex node (`match: /foo/i`); a plain string is the
132
+ // regex source with flags in `flags`.
133
+ const split = splitRegex(match);
134
+ const flags = typeof raw.flags === "string" ? raw.flags : split.flags;
135
+ const source2 = split.source;
136
+ try {
137
+ new RegExp(source2, flags);
138
+ } catch (e) {
139
+ errors.push(`${source} (${id}): invalid regex: ${e instanceof Error ? e.message : String(e)}`);
140
+ return null;
141
+ }
142
+ return {
143
+ id,
144
+ severity,
145
+ tool: typeof raw.tool === "string" ? raw.tool : undefined,
146
+ category: typeof raw.category === "string" ? raw.category : undefined,
147
+ match: source2,
148
+ flags: flags || undefined,
149
+ quoted: raw.quoted === true ? true : undefined,
150
+ reason,
151
+ tests: asTests(raw),
152
+ };
153
+ }
154
+
155
+ /** Fixtures: plain strings for a single-step rule, lists of step contents for a sequence rule. */
156
+ function asTests(raw: Record<string, unknown>): Rule["tests"] {
157
+ if (typeof raw.tests !== "object" || raw.tests === null || Array.isArray(raw.tests)) return undefined;
158
+ const t = raw.tests as Record<string, unknown>;
159
+ const list = (v: unknown): string[] | string[][] | undefined => {
160
+ if (!Array.isArray(v)) return undefined;
161
+ return v.every((x) => Array.isArray(x)) ? (v as unknown[][]).map((x) => x.map(String)) : v.map(String);
162
+ };
163
+ return { match: list(t.match), no_match: list(t.no_match) };
164
+ }
165
+
166
+ /** Rules from one directory's `*.rule.yaml` files, in filename order. */
167
+ function loadDir(dir: string, errors: string[]): LoadedRule[] {
168
+ const out: LoadedRule[] = [];
169
+ let names: string[];
170
+ try {
171
+ names = readdirSync(dir).filter((f) => f.endsWith(".rule.yaml")).sort();
172
+ } catch {
173
+ return out; // missing directory is not an error
174
+ }
175
+ for (const name of names) {
176
+ const source = join(dir, name);
177
+ let text: string;
178
+ try {
179
+ text = readFileSync(source, "utf8");
180
+ } catch (e) {
181
+ errors.push(`${source}: ${e instanceof Error ? e.message : String(e)}`);
182
+ continue;
183
+ }
184
+ let doc: unknown;
185
+ try {
186
+ doc = parse(text);
187
+ } catch (e) {
188
+ errors.push(`${source}: ${e instanceof Error ? e.message : String(e)}`);
189
+ continue;
190
+ }
191
+ const items = Array.isArray(doc) ? doc : [doc];
192
+ for (const item of items) {
193
+ const rule = asRule(item, source, errors);
194
+ if (rule) out.push({ ...rule, source });
195
+ }
196
+ }
197
+ return out;
198
+ }
199
+
200
+ /**
201
+ * Built-in rules first, then each directory in order; a later rule with the
202
+ * same id replaces the earlier one, and `severity: off` removes the id.
203
+ */
204
+ export function loadRules(dirs: string[]): { rules: Rule[]; errors: string[] } {
205
+ const errors: string[] = [];
206
+ const byId = new Map<string, LoadedRule>();
207
+ for (const rule of BUILTIN_RULES) byId.set(rule.id, rule);
208
+ for (const dir of dirs) {
209
+ for (const rule of loadDir(dir, errors)) byId.set(rule.id, rule);
210
+ }
211
+ const rules: Rule[] = [];
212
+ for (const rule of byId.values()) {
213
+ if (rule.severity === "off") continue;
214
+ const { source: _source, ...rest } = rule;
215
+ rules.push(rest);
216
+ }
217
+ return { rules, errors };
218
+ }
219
+
220
+ /** Every rule with its source and enabled state, for `pai rules list`. */
221
+ export function listRules(dirs: string[]): { rules: LoadedRule[]; errors: string[] } {
222
+ const errors: string[] = [];
223
+ const byId = new Map<string, LoadedRule>();
224
+ for (const rule of BUILTIN_RULES) byId.set(rule.id, rule);
225
+ for (const dir of dirs) {
226
+ for (const rule of loadDir(dir, errors)) byId.set(rule.id, rule);
227
+ }
228
+ return { rules: [...byId.values()], errors };
229
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * types.ts — the shape of a security rule. Rules live in `*.rule.yaml` files
3
+ * (built-ins under ./builtin/, user rules under PAI_HOME/rules and
4
+ * .pai/rules), so patterns can be extended without editing TypeScript, and
5
+ * every rule carries its own fixtures.
6
+ */
7
+
8
+ export type RuleSeverity = "block" | "confirm" | "warn" | "off";
9
+
10
+ /** One step of a sequence rule: a regex on the tool's content, optionally pinned to a tool. */
11
+ export interface RuleSequenceStep {
12
+ /** Regex on the tool name; any tool when absent. */
13
+ tool?: string;
14
+ /** Regex source; flags come from `flags`. */
15
+ match: string;
16
+ flags?: string;
17
+ }
18
+
19
+ /**
20
+ * A two-step (or more) pattern: every step must occur within `window_seconds`
21
+ * of the first, in step order when `order` is "strict". Sequence rules are not
22
+ * evaluated live by the hook (eval.ts) — they are for the after-the-fact scan
23
+ * over a transcript (`pai audit scan`).
24
+ */
25
+ export interface RuleSequence {
26
+ steps: RuleSequenceStep[];
27
+ window_seconds: number;
28
+ order: "strict" | "any";
29
+ }
30
+
31
+ export interface Rule {
32
+ id: string;
33
+ severity: RuleSeverity;
34
+ /** Regex on the tool name; default "Bash". */
35
+ tool?: string;
36
+ /** Regex source; flags come from `flags`. Required unless severity is "off" or `sequence` is set. */
37
+ match?: string;
38
+ flags?: string;
39
+ /** Multi-step pattern; see RuleSequence. */
40
+ sequence?: RuleSequence;
41
+ /** Also test the text with only the quote marks removed (default: quoted strings are dropped). */
42
+ quoted?: boolean;
43
+ /** Legacy group name shown in deny messages and security events; defaults to the id. */
44
+ category?: string;
45
+ reason: string;
46
+ /** Built-in fixtures: every `match` must hit, every `no_match` must not. For a sequence rule each entry is the list of steps, in step order: a plain string is a Bash-shaped step, an object names the tool a step's content belongs to. */
47
+ tests?: {
48
+ match?: Array<string | string[] | Array<string | { tool?: string; content: string }>>;
49
+ no_match?: Array<string | string[] | Array<string | { tool?: string; content: string }>>;
50
+ };
51
+ }
52
+
53
+ /** Where a rule came from, for `pai rules list`. */
54
+ export interface LoadedRule extends Rule {
55
+ source: string;
56
+ }
@@ -23,75 +23,22 @@ import { dirname, resolve } from 'path';
23
23
  import { securityEventsPath } from '../lib/pai-paths.js';
24
24
  import { isSharedCheckout } from '../lib/shared-checkout.js';
25
25
  import { maskSecrets } from '../lib/loop-guard.js';
26
- import { decideWorkerGitGuard, splitSegments, workerGitGuardMessage } from '../lib/worker-git-guard.js';
26
+ import { decideWorkerGitGuard, workerGitGuardMessage } from '../lib/worker-git-guard.js';
27
27
  import { detectUnsafePlutil, plutilGuardMessage } from '../lib/plutil-guard.js';
28
+ import { loadRules, userRuleDirs } from '../lib/rules/load.js';
29
+ import { evaluateRules } from '../lib/rules/eval.js';
28
30
  import { readWorkersSection } from '../../../workers/config.js';
29
31
  import { workersLogDir } from '../../../workers/paths.js';
30
32
  import { worktreesDir } from '../../../workers/worktree.js';
31
33
 
32
34
  // ============================================================================
33
- // ATTACK PATTERNS - CUSTOMIZE THESE FOR YOUR ENVIRONMENT
35
+ // ATTACK RULES
34
36
  // ============================================================================
37
+ // Patterns live in *.rule.yaml files: built-ins under lib/rules/builtin/
38
+ // (embedded at build time), user rules under PAI_HOME/rules and .pai/rules.
39
+ // Loaded once per hook process; a broken rule file is skipped, never fatal.
35
40
 
36
- // Example: Reverse Shell Patterns (BLOCK - rarely legitimate)
37
- const REVERSE_SHELL_PATTERNS: RegExp[] = [
38
- /\/dev\/(tcp|udp)\/[0-9]/, // Bash TCP/UDP device
39
- /bash\s+-i\s+>&?\s*\/dev\//, // Interactive bash redirect
40
- // Add your own reverse shell patterns here
41
- ];
42
-
43
- // Example: Instruction Override (BLOCK - prompt injection)
44
- const INSTRUCTION_OVERRIDE_PATTERNS: RegExp[] = [
45
- /ignore\s+(all\s+)?previous\s+instructions?/i,
46
- /disregard\s+(all\s+)?(prior|previous)\s+(instructions?|rules?)/i,
47
- // Add your own prompt injection patterns here
48
- ];
49
-
50
- // Example: Catastrophic Deletion Patterns (BLOCK - filesystem destruction)
51
- const CATASTROPHIC_DELETION_PATTERNS: RegExp[] = [
52
- // The destructive verb must sit in command position (line start, after
53
- // ; & | ( ` $( or sudo), so `~`, `/` and `-` inside awk programs or python
54
- // strings never match on their own.
55
- // Recursive rm of /, ~, $HOME or a system directory
56
- /(?:^|[;&|(`\n]|\$\()\s*(?:sudo\s+(?:-\S+\s+)*)?rm\s+(?:-\S+\s+)*(?:-\w*[rR]\w*|--recursive)\s+(?:-\S+\s+)*(?:\S+\s+)*(?:\/\*?|~\/?\*?|\$\{?HOME\}?\/?\*?|\/(?:usr|etc|bin|sbin|lib|var|opt|home|System|Library|Users|Applications)\/?\*?)(?=\s|;|&|\||$)/m,
57
- // find / -delete
58
- /(?:^|[;&|(`\n]|\$\()\s*(?:sudo\s+(?:-\S+\s+)*)?find\s+(?:\/|~|\$\{?HOME\}?)\/?\s+(?:\S+\s+)*?-delete\b/m,
59
-
60
- // Trailing tilde bypass
61
- /(?:^|[;&|(`\n]|\$\()\s*(?:sudo\s+(?:-\S+\s+)*)?rm\s+(?:-[rfivd]+\s+)*\S+\s+~\/?(?:\s|$)/m, // rm something ~/
62
-
63
- // Relative path recursive deletion
64
- /(?:^|[;&|(`\n]|\$\()\s*(?:sudo\s+(?:-\S+\s+)*)?rm\s+(?:-[rfivd]+\s+)*\.\/\s*$/m, // rm -rf ./
65
- /(?:^|[;&|(`\n]|\$\()\s*(?:sudo\s+(?:-\S+\s+)*)?rm\s+(?:-[rfivd]+\s+)*\.\.\/\s*$/m, // rm -rf ../
66
-
67
- // Add your own dangerous deletion patterns here
68
- ];
69
-
70
- // Example: Dangerous File Operations (BLOCK - data destruction)
71
- const DANGEROUS_FILE_OPS_PATTERNS: RegExp[] = [
72
- /\bchmod\s+(-R\s+)?0{3,}/, // chmod 000
73
- // Add your own dangerous file operation patterns here
74
- ];
75
-
76
- // OPTIONAL: Operations that require confirmation instead of blocking
77
- const DANGEROUS_GIT_PATTERNS: RegExp[] = [
78
- /\bgit\b.*\spush\b.*(\s-f\b|\s--force(\s|$))/i, // git push -f / --force; --force-with-lease and --follow-tags pass
79
- /\bgit\b.*\spush\b.*\s\+\S/i, // a plus-prefixed refspec forces the update without -f
80
- /\bgit\s+reset\s+--hard/i, // git reset --hard
81
- // Add your own git safety patterns here
82
- ];
83
-
84
- // Combined patterns for fast iteration
85
- const ALL_BLOCK_PATTERNS: { category: string; patterns: RegExp[] }[] = [
86
- { category: 'reverse_shell', patterns: REVERSE_SHELL_PATTERNS },
87
- { category: 'instruction_override', patterns: INSTRUCTION_OVERRIDE_PATTERNS },
88
- { category: 'catastrophic_deletion', patterns: CATASTROPHIC_DELETION_PATTERNS },
89
- { category: 'dangerous_file_ops', patterns: DANGEROUS_FILE_OPS_PATTERNS },
90
- ];
91
-
92
- const CONFIRM_PATTERNS: { category: string; patterns: RegExp[] }[] = [
93
- { category: 'dangerous_git', patterns: DANGEROUS_GIT_PATTERNS },
94
- ];
41
+ const RULES = loadRules(userRuleDirs(process.cwd())).rules;
95
42
 
96
43
  // ============================================================================
97
44
  // TYPES
@@ -147,45 +94,18 @@ function deny(reason: string): never {
147
94
  interface DetectionResult {
148
95
  blocked: boolean;
149
96
  requiresConfirmation?: boolean;
97
+ warned?: boolean;
150
98
  category?: string;
151
99
  pattern?: string;
152
100
  }
153
101
 
154
- /** Quoted strings removed: text inside a commit message or echo is not a command. */
155
- function dropQuoted(segment: string): string {
156
- return segment.replace(/"(?:\\.|[^"\\])*"|'[^']*'/g, '""');
157
- }
158
-
159
- /** A quoted +refspec is still a forced push, so push segments also test with only the quote marks removed. */
160
- function gitPatternHit(pattern: RegExp, segment: string): boolean {
161
- const bare = dropQuoted(segment);
162
- if (pattern.test(bare)) return true;
163
- return /\bgit\b.*\spush\b/.test(bare) && pattern.test(segment.replace(/["']/g, ''));
164
- }
165
-
102
+ /** First block hit, else first confirm hit, else first warn hit (category = the rule's group, else its id). */
166
103
  function detectAttack(content: string): DetectionResult {
167
- // A quoted target is the same target: test the text with quote marks removed too.
168
- const unquoted = content.replace(/["']/g, '');
169
- // First check for hard blocks
170
- for (const { category, patterns } of ALL_BLOCK_PATTERNS) {
171
- for (const pattern of patterns) {
172
- if (pattern.test(content) || pattern.test(unquoted)) {
173
- return { blocked: true, category, pattern: pattern.source };
174
- }
175
- }
176
- }
177
-
178
- // Then check for confirmation-required patterns
179
- const segments = splitSegments(content);
180
- for (const { category, patterns } of CONFIRM_PATTERNS) {
181
- for (const pattern of patterns) {
182
- if (segments.some((seg) => gitPatternHit(pattern, seg))) {
183
- return { blocked: false, requiresConfirmation: true, category, pattern: pattern.source };
184
- }
185
- }
186
- }
187
-
188
- return { blocked: false };
104
+ const hit = evaluateRules(RULES, 'Bash', content);
105
+ if (!hit) return { blocked: false };
106
+ if (hit.rule.severity === 'block') return { blocked: true, category: hit.rule.category ?? hit.rule.id, pattern: hit.rule.match };
107
+ if (hit.rule.severity === 'confirm') return { blocked: false, requiresConfirmation: true, category: hit.rule.category ?? hit.rule.id, pattern: hit.rule.match };
108
+ return { blocked: false, warned: true, category: hit.rule.category ?? hit.rule.id, pattern: hit.rule.match };
189
109
  }
190
110
 
191
111
  // ============================================================================
@@ -334,7 +254,18 @@ async function main(): Promise<void> {
334
254
  deny(`This is a dangerous operation (${command.slice(0, 50)}...). This can cause data loss. If you're sure, explicitly confirm this command.`);
335
255
  }
336
256
 
337
- // Allow - no logging, immediate exit
257
+ if (result.warned) {
258
+ // Log the security event but allow the command.
259
+ logSecurityEvent({
260
+ type: 'security_warning',
261
+ category: result.category,
262
+ pattern: result.pattern,
263
+ command: command.slice(0, 200),
264
+ session_id: input.session_id,
265
+ });
266
+ }
267
+
268
+ // Allow - no further logging, immediate exit
338
269
  console.log(JSON.stringify({ permissionDecision: 'allow' }));
339
270
  }
340
271
 
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * handoff-link.ts — UserPromptSubmit
5
+ *
6
+ * Records a handoff link: when a prompt carries the `[pai-handoff from=<id>]`
7
+ * marker `pai handoff export` tells the user to paste, appends
8
+ * {ts, from, to} to <PAI_HOME>/handoffs/links.jsonl, so the chain of sessions
9
+ * that continued from one brief is reconstructable. Silent on any failure —
10
+ * never blocks a prompt.
11
+ */
12
+
13
+ import { appendHandoffLink } from "../../../session/handoff.js";
14
+
15
+ const MARKER = /\[pai-handoff from=([^\]\s]+)\]/;
16
+
17
+ async function main(): Promise<void> {
18
+ try {
19
+ const chunks: Buffer[] = [];
20
+ for await (const chunk of process.stdin) chunks.push(chunk);
21
+ const input = JSON.parse(Buffer.concat(chunks).toString("utf8")) as {
22
+ prompt?: string;
23
+ session_id?: string;
24
+ };
25
+ const prompt = typeof input.prompt === "string" ? input.prompt : "";
26
+ const m = MARKER.exec(prompt);
27
+ if (!m || !input.session_id) return;
28
+ appendHandoffLink(m[1], input.session_id);
29
+ } catch {
30
+ // silent failure — don't block user prompts
31
+ }
32
+ }
33
+
34
+ void main();
@@ -1,3 +0,0 @@
1
- import { i as switchToSession, n as fetchLiveSessions, r as sendToSession, t as callAiBroker } from "./aibroker-client-CQPGqLxL.mjs";
2
-
3
- export { callAiBroker, sendToSession };
@@ -1,11 +0,0 @@
1
- import "./config-XpZWOYOG.mjs";
2
- import "./config-Bu1_vbY8.mjs";
3
- import "./paths-CHifubDh.mjs";
4
- import "./worktree-CRs2gyg0.mjs";
5
- import "./status-ggJ7lgVg.mjs";
6
- import "./pool-Dh-q3iJi.mjs";
7
- import "./registrable-DpyTtajg.mjs";
8
- import "./pai-marker-BcZyJguM.mjs";
9
- import { n as formatAutoRouteJson, t as autoRoute } from "./auto-route-21GdHL70.mjs";
10
-
11
- export { autoRoute, formatAutoRouteJson };
@@ -1,15 +0,0 @@
1
- import "./config-XpZWOYOG.mjs";
2
- import "./config-Bu1_vbY8.mjs";
3
- import "./paths-CHifubDh.mjs";
4
- import "./status-ggJ7lgVg.mjs";
5
- import "./scope-D-h6F6DN.mjs";
6
- import { A as budgetSkipReason, D as budgetExceeded, E as budgetCache, M as budgetWall, N as fetchBudgetReading, O as budgetFresh, P as pickBudgetWindow, T as BudgetCache, j as budgetUntilLabel, k as budgetFull, w as BUDGET_TTL_MS } from "./routing-DmkheCRH.mjs";
7
- import "./tool-errors-7E13StWd.mjs";
8
- import "./wait-ByV0OyMZ.mjs";
9
- import "./local-tiers-BE4jGaMa.mjs";
10
- import "./caller-identity-yLFb7CKq.mjs";
11
- import "./capacity-DEveQdFV.mjs";
12
- import "./local-instances-DOxSgT_L.mjs";
13
- import "./local-candidate-DLdwQt0s.mjs";
14
-
15
- export { BUDGET_TTL_MS, BudgetCache, budgetCache, budgetExceeded, budgetFresh, budgetFull, budgetSkipReason, budgetUntilLabel, budgetWall, fetchBudgetReading, pickBudgetWindow };
@@ -1,20 +0,0 @@
1
- import "./config-XpZWOYOG.mjs";
2
- import "./config-Bu1_vbY8.mjs";
3
- import "./paths-CHifubDh.mjs";
4
- import "./status-ggJ7lgVg.mjs";
5
- import "./scope-D-h6F6DN.mjs";
6
- import "./routing-DmkheCRH.mjs";
7
- import "./tool-errors-7E13StWd.mjs";
8
- import "./wait-ByV0OyMZ.mjs";
9
- import "./local-tiers-BE4jGaMa.mjs";
10
- import "./caller-identity-yLFb7CKq.mjs";
11
- import "./capacity-DEveQdFV.mjs";
12
- import "./local-instances-DOxSgT_L.mjs";
13
- import "./local-candidate-DLdwQt0s.mjs";
14
- import "./providers-C9QC0TtO.mjs";
15
- import "./main-config-ops-kTXR8Pyv.mjs";
16
- import { A as cockpitMemoryPayload, F as memoryRunLog, I as rateSeries, L as runProgress, M as latestMemoryJob, N as liveMemoryDeps, P as memoryJobProgress, R as runningMemoryJob, j as fetchDaemonMemory, z as sameModel } from "./cockpit-actions-OodH_vx_.mjs";
17
- import "./ipc-client-CnYEF31R.mjs";
18
- import "./embed-pass-e64AwDCa.mjs";
19
-
20
- export { fetchDaemonMemory };
@@ -1,4 +0,0 @@
1
- import { A as providerQuotaSkipAt, C as parseMcpSetsValue, D as providerContextWindow, E as parseWorkersConfig, F as writeWorkersSection, M as resolveCapability, N as resolveModelCapability, O as providerCostTier, P as resolveProviderKey, S as parseClassesValue, T as parseProvider, _ as maskKey, a as MODEL_CAPABILITIES, b as nativeOrchestratorModel, c as WorkersConfigError, d as defaultWorkersConfig, f as escalationCloud, g as keysDir, h as isModelCapability, ht as DEFAULT_CONTEXT_WINDOW, i as DEFAULT_POOL_PORT, j as readWorkersSection, k as providerKeyPath, l as assertProviderRunnable, m as isLocalProvider, n as CAPABILITY_NAME_RE, o as PROVIDER_TAGS, p as getProviderOrNative, r as DEFAULT_COST_TIER, s as WORKER_CLASSES, t as ANTHROPIC_NATIVE, u as classModelCapability, v as nativeAnthropicModels, w as parseModelsBlock, x as parseCapabilitiesValue, y as nativeAnthropicProvider } from "./config-XpZWOYOG.mjs";
2
- import { o as expandHome, s as homeRel } from "./config-Bu1_vbY8.mjs";
3
-
4
- export { ANTHROPIC_NATIVE, CAPABILITY_NAME_RE, DEFAULT_CONTEXT_WINDOW, DEFAULT_COST_TIER, DEFAULT_POOL_PORT, MODEL_CAPABILITIES, PROVIDER_TAGS, WORKER_CLASSES, WorkersConfigError, assertProviderRunnable, classModelCapability, defaultWorkersConfig, escalationCloud, expandHome, getProviderOrNative, homeRel, isLocalProvider, isModelCapability, keysDir, maskKey, nativeAnthropicModels, nativeAnthropicProvider, nativeOrchestratorModel, parseCapabilitiesValue, parseClassesValue, parseMcpSetsValue, parseModelsBlock, parseProvider, parseWorkersConfig, providerContextWindow, providerCostTier, providerKeyPath, providerQuotaSkipAt, readWorkersSection, resolveCapability, resolveModelCapability, resolveProviderKey, writeWorkersSection };