agy-worker-mcp 0.3.1

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 (227) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +309 -0
  4. package/commands/agy-ceiling.md +31 -0
  5. package/dist/broker/blockers.d.ts +86 -0
  6. package/dist/broker/blockers.d.ts.map +1 -0
  7. package/dist/broker/blockers.js +390 -0
  8. package/dist/broker/blockers.js.map +1 -0
  9. package/dist/broker/outcome.d.ts +85 -0
  10. package/dist/broker/outcome.d.ts.map +1 -0
  11. package/dist/broker/outcome.js +147 -0
  12. package/dist/broker/outcome.js.map +1 -0
  13. package/dist/broker/reconcile.d.ts +74 -0
  14. package/dist/broker/reconcile.d.ts.map +1 -0
  15. package/dist/broker/reconcile.js +415 -0
  16. package/dist/broker/reconcile.js.map +1 -0
  17. package/dist/broker/result.d.ts +88 -0
  18. package/dist/broker/result.d.ts.map +1 -0
  19. package/dist/broker/result.js +234 -0
  20. package/dist/broker/result.js.map +1 -0
  21. package/dist/broker/verify.d.ts +52 -0
  22. package/dist/broker/verify.d.ts.map +1 -0
  23. package/dist/broker/verify.js +174 -0
  24. package/dist/broker/verify.js.map +1 -0
  25. package/dist/contract/errors.d.ts +119 -0
  26. package/dist/contract/errors.d.ts.map +1 -0
  27. package/dist/contract/errors.js +99 -0
  28. package/dist/contract/errors.js.map +1 -0
  29. package/dist/contract/index.d.ts +10 -0
  30. package/dist/contract/index.d.ts.map +1 -0
  31. package/dist/contract/index.js +10 -0
  32. package/dist/contract/index.js.map +1 -0
  33. package/dist/contract/paths.d.ts +139 -0
  34. package/dist/contract/paths.d.ts.map +1 -0
  35. package/dist/contract/paths.js +359 -0
  36. package/dist/contract/paths.js.map +1 -0
  37. package/dist/contract/schema.sql +64 -0
  38. package/dist/contract/types.d.ts +1113 -0
  39. package/dist/contract/types.d.ts.map +1 -0
  40. package/dist/contract/types.js +114 -0
  41. package/dist/contract/types.js.map +1 -0
  42. package/dist/events/cursor.d.ts +65 -0
  43. package/dist/events/cursor.d.ts.map +1 -0
  44. package/dist/events/cursor.js +286 -0
  45. package/dist/events/cursor.js.map +1 -0
  46. package/dist/events/detect.d.ts +58 -0
  47. package/dist/events/detect.d.ts.map +1 -0
  48. package/dist/events/detect.js +183 -0
  49. package/dist/events/detect.js.map +1 -0
  50. package/dist/events/normalize.d.ts +30 -0
  51. package/dist/events/normalize.d.ts.map +1 -0
  52. package/dist/events/normalize.js +158 -0
  53. package/dist/events/normalize.js.map +1 -0
  54. package/dist/events/parse.d.ts +41 -0
  55. package/dist/events/parse.d.ts.map +1 -0
  56. package/dist/events/parse.js +89 -0
  57. package/dist/events/parse.js.map +1 -0
  58. package/dist/gate/bind.d.ts +28 -0
  59. package/dist/gate/bind.d.ts.map +1 -0
  60. package/dist/gate/bind.js +154 -0
  61. package/dist/gate/bind.js.map +1 -0
  62. package/dist/gate/gate.d.ts +139 -0
  63. package/dist/gate/gate.d.ts.map +1 -0
  64. package/dist/gate/gate.js +488 -0
  65. package/dist/gate/gate.js.map +1 -0
  66. package/dist/gate/hooks-file.d.ts +55 -0
  67. package/dist/gate/hooks-file.d.ts.map +1 -0
  68. package/dist/gate/hooks-file.js +104 -0
  69. package/dist/gate/hooks-file.js.map +1 -0
  70. package/dist/gate.d.ts +3 -0
  71. package/dist/gate.d.ts.map +1 -0
  72. package/dist/gate.js +20 -0
  73. package/dist/gate.js.map +1 -0
  74. package/dist/policy/ceiling-review.d.ts +22 -0
  75. package/dist/policy/ceiling-review.d.ts.map +1 -0
  76. package/dist/policy/ceiling-review.js +170 -0
  77. package/dist/policy/ceiling-review.js.map +1 -0
  78. package/dist/policy/ceiling.d.ts +128 -0
  79. package/dist/policy/ceiling.d.ts.map +1 -0
  80. package/dist/policy/ceiling.js +273 -0
  81. package/dist/policy/ceiling.js.map +1 -0
  82. package/dist/policy/containment.d.ts +100 -0
  83. package/dist/policy/containment.d.ts.map +1 -0
  84. package/dist/policy/containment.js +1071 -0
  85. package/dist/policy/containment.js.map +1 -0
  86. package/dist/policy/hard-deny.d.ts +28 -0
  87. package/dist/policy/hard-deny.d.ts.map +1 -0
  88. package/dist/policy/hard-deny.js +78 -0
  89. package/dist/policy/hard-deny.js.map +1 -0
  90. package/dist/policy/profiles.d.ts +82 -0
  91. package/dist/policy/profiles.d.ts.map +1 -0
  92. package/dist/policy/profiles.js +317 -0
  93. package/dist/policy/profiles.js.map +1 -0
  94. package/dist/policy/rules.d.ts +138 -0
  95. package/dist/policy/rules.d.ts.map +1 -0
  96. package/dist/policy/rules.js +1180 -0
  97. package/dist/policy/rules.js.map +1 -0
  98. package/dist/policy/seatbelt.d.ts +32 -0
  99. package/dist/policy/seatbelt.d.ts.map +1 -0
  100. package/dist/policy/seatbelt.js +59 -0
  101. package/dist/policy/seatbelt.js.map +1 -0
  102. package/dist/policy/tools.d.ts +36 -0
  103. package/dist/policy/tools.d.ts.map +1 -0
  104. package/dist/policy/tools.js +94 -0
  105. package/dist/policy/tools.js.map +1 -0
  106. package/dist/runner/gate-watchdog.d.ts +93 -0
  107. package/dist/runner/gate-watchdog.d.ts.map +1 -0
  108. package/dist/runner/gate-watchdog.js +153 -0
  109. package/dist/runner/gate-watchdog.js.map +1 -0
  110. package/dist/runner/idle.d.ts +51 -0
  111. package/dist/runner/idle.d.ts.map +1 -0
  112. package/dist/runner/idle.js +81 -0
  113. package/dist/runner/idle.js.map +1 -0
  114. package/dist/runner/inbox.d.ts +56 -0
  115. package/dist/runner/inbox.d.ts.map +1 -0
  116. package/dist/runner/inbox.js +134 -0
  117. package/dist/runner/inbox.js.map +1 -0
  118. package/dist/runner/reap.d.ts +65 -0
  119. package/dist/runner/reap.d.ts.map +1 -0
  120. package/dist/runner/reap.js +168 -0
  121. package/dist/runner/reap.js.map +1 -0
  122. package/dist/runner/runner.d.ts +51 -0
  123. package/dist/runner/runner.d.ts.map +1 -0
  124. package/dist/runner/runner.js +289 -0
  125. package/dist/runner/runner.js.map +1 -0
  126. package/dist/runner/spawn.d.ts +98 -0
  127. package/dist/runner/spawn.d.ts.map +1 -0
  128. package/dist/runner/spawn.js +230 -0
  129. package/dist/runner/spawn.js.map +1 -0
  130. package/dist/runner/verify.d.ts +24 -0
  131. package/dist/runner/verify.d.ts.map +1 -0
  132. package/dist/runner/verify.js +108 -0
  133. package/dist/runner/verify.js.map +1 -0
  134. package/dist/runner.d.ts +3 -0
  135. package/dist/runner.d.ts.map +1 -0
  136. package/dist/runner.js +15 -0
  137. package/dist/runner.js.map +1 -0
  138. package/dist/server/context.d.ts +41 -0
  139. package/dist/server/context.d.ts.map +1 -0
  140. package/dist/server/context.js +59 -0
  141. package/dist/server/context.js.map +1 -0
  142. package/dist/server/instructions.d.ts +13 -0
  143. package/dist/server/instructions.d.ts.map +1 -0
  144. package/dist/server/instructions.js +121 -0
  145. package/dist/server/instructions.js.map +1 -0
  146. package/dist/server/server.d.ts +20 -0
  147. package/dist/server/server.d.ts.map +1 -0
  148. package/dist/server/server.js +55 -0
  149. package/dist/server/server.js.map +1 -0
  150. package/dist/server/tools/cancel.d.ts +24 -0
  151. package/dist/server/tools/cancel.d.ts.map +1 -0
  152. package/dist/server/tools/cancel.js +59 -0
  153. package/dist/server/tools/cancel.js.map +1 -0
  154. package/dist/server/tools/capabilities.d.ts +36 -0
  155. package/dist/server/tools/capabilities.d.ts.map +1 -0
  156. package/dist/server/tools/capabilities.js +116 -0
  157. package/dist/server/tools/capabilities.js.map +1 -0
  158. package/dist/server/tools/ceiling.d.ts +31 -0
  159. package/dist/server/tools/ceiling.d.ts.map +1 -0
  160. package/dist/server/tools/ceiling.js +121 -0
  161. package/dist/server/tools/ceiling.js.map +1 -0
  162. package/dist/server/tools/index.d.ts +38 -0
  163. package/dist/server/tools/index.d.ts.map +1 -0
  164. package/dist/server/tools/index.js +115 -0
  165. package/dist/server/tools/index.js.map +1 -0
  166. package/dist/server/tools/listJobs.d.ts +30 -0
  167. package/dist/server/tools/listJobs.d.ts.map +1 -0
  168. package/dist/server/tools/listJobs.js +37 -0
  169. package/dist/server/tools/listJobs.js.map +1 -0
  170. package/dist/server/tools/logs.d.ts +31 -0
  171. package/dist/server/tools/logs.d.ts.map +1 -0
  172. package/dist/server/tools/logs.js +96 -0
  173. package/dist/server/tools/logs.js.map +1 -0
  174. package/dist/server/tools/result.d.ts +32 -0
  175. package/dist/server/tools/result.d.ts.map +1 -0
  176. package/dist/server/tools/result.js +95 -0
  177. package/dist/server/tools/result.js.map +1 -0
  178. package/dist/server/tools/send.d.ts +37 -0
  179. package/dist/server/tools/send.d.ts.map +1 -0
  180. package/dist/server/tools/send.js +71 -0
  181. package/dist/server/tools/send.js.map +1 -0
  182. package/dist/server/tools/sessions.d.ts +28 -0
  183. package/dist/server/tools/sessions.d.ts.map +1 -0
  184. package/dist/server/tools/sessions.js +54 -0
  185. package/dist/server/tools/sessions.js.map +1 -0
  186. package/dist/server/tools/start.d.ts +107 -0
  187. package/dist/server/tools/start.d.ts.map +1 -0
  188. package/dist/server/tools/start.js +482 -0
  189. package/dist/server/tools/start.js.map +1 -0
  190. package/dist/server/tools/wait.d.ts +29 -0
  191. package/dist/server/tools/wait.d.ts.map +1 -0
  192. package/dist/server/tools/wait.js +120 -0
  193. package/dist/server/tools/wait.js.map +1 -0
  194. package/dist/server.d.ts +3 -0
  195. package/dist/server.d.ts.map +1 -0
  196. package/dist/server.js +17 -0
  197. package/dist/server.js.map +1 -0
  198. package/dist/setup/install.d.ts +52 -0
  199. package/dist/setup/install.d.ts.map +1 -0
  200. package/dist/setup/install.js +108 -0
  201. package/dist/setup/install.js.map +1 -0
  202. package/dist/setup.d.ts +3 -0
  203. package/dist/setup.d.ts.map +1 -0
  204. package/dist/setup.js +24 -0
  205. package/dist/setup.js.map +1 -0
  206. package/dist/store/db.d.ts +49 -0
  207. package/dist/store/db.d.ts.map +1 -0
  208. package/dist/store/db.js +101 -0
  209. package/dist/store/db.js.map +1 -0
  210. package/dist/store/jobs.d.ts +45 -0
  211. package/dist/store/jobs.d.ts.map +1 -0
  212. package/dist/store/jobs.js +150 -0
  213. package/dist/store/jobs.js.map +1 -0
  214. package/dist/store/locks.d.ts +60 -0
  215. package/dist/store/locks.d.ts.map +1 -0
  216. package/dist/store/locks.js +172 -0
  217. package/dist/store/locks.js.map +1 -0
  218. package/dist/store/sessions.d.ts +37 -0
  219. package/dist/store/sessions.d.ts.map +1 -0
  220. package/dist/store/sessions.js +117 -0
  221. package/dist/store/sessions.js.map +1 -0
  222. package/dist/trace/digest.d.ts +77 -0
  223. package/dist/trace/digest.d.ts.map +1 -0
  224. package/dist/trace/digest.js +481 -0
  225. package/dist/trace/digest.js.map +1 -0
  226. package/package.json +68 -0
  227. package/skills/agy-ceiling/SKILL.md +72 -0
@@ -0,0 +1,154 @@
1
+ import { closeSync, existsSync, openSync, readSync } from 'node:fs';
2
+ import { jobPaths, readJsonIfExists } from '../contract/paths.js';
3
+ import { listJobs, tryGetJob } from '../store/jobs.js';
4
+ import { bindConversationId, findSessionByConversation } from '../store/sessions.js';
5
+ /**
6
+ * Map a hook payload's `conversationId` onto one of our jobs (see docs/permissions.md).
7
+ *
8
+ * With no daemon there is nobody to build the mapping ahead of time, so the gate
9
+ * builds it itself: take the live jobs, read the **first line only** of each
10
+ * candidate's `events.ndjson`, and compare `conversation_id`. That line exists
11
+ * before any tool call because `init` is always first, so there is no guess
12
+ * and no race.
13
+ *
14
+ * `workspacePaths` alone is not enough — several jobs can share a workspace.
15
+ */
16
+ /**
17
+ * Binding candidates (`starting`/`running`; `queued` and
18
+ * `canceling` are included defensively). The runner only ever reaches the DB's
19
+ * `jobs.lifecycle` through `reconcile`'s absorption of `state.json` (finding 1/9),
20
+ * which can lag the gate hook firing for a job the server has not touched again
21
+ * since `agy_start` — `queued` covers that lag. `canceling` keeps a job bound
22
+ * (so its policy keeps applying) after `on_denial: 'abort'` marks it (finding 13).
23
+ */
24
+ const BIND_LIFECYCLES = ['queued', 'starting', 'running', 'canceling'];
25
+ /**
26
+ * Null means "not one of ours" — a user's own interactive agy session.
27
+ *
28
+ * ⚠ That case must produce `{"decision":"ask"}`, never `{}`. `{}` is a denial
29
+ * (§9) and would break every interactive session on the machine.
30
+ */
31
+ export function bindConversation(store, conversationId) {
32
+ const cached = lookupBoundJob(store, conversationId);
33
+ if (cached) {
34
+ const policy = loadJobPolicy(store, cached.job_id);
35
+ if (policy)
36
+ return { job: cached, policy, conversationId };
37
+ // policy.json missing for a job we thought was bound: fall through and
38
+ // re-scan rather than reporting a broken binding as "not ours".
39
+ }
40
+ const candidates = listJobs(store, { lifecycle: [...BIND_LIFECYCLES], limit: 10000 });
41
+ for (const job of candidates) {
42
+ const seen = readInitConversationId(store, job.job_id);
43
+ if (seen !== null && seen === conversationId) {
44
+ recordBinding(store, job.job_id, conversationId);
45
+ const policy = loadJobPolicy(store, job.job_id);
46
+ if (!policy)
47
+ return null;
48
+ return { job, policy, conversationId };
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Persist the mapping so later calls skip the file scan. Also fills in
55
+ * `sessions.conversation_id` when it was still NULL.
56
+ */
57
+ export function recordBinding(store, jobId, conversationId) {
58
+ const job = tryGetJob(store, jobId);
59
+ if (!job || !job.session_id)
60
+ return;
61
+ try {
62
+ bindConversationId(store, job.session_id, conversationId);
63
+ }
64
+ catch {
65
+ // Best effort. A lost race here just means the next hook call scans again.
66
+ }
67
+ }
68
+ /** Cached lookup for a conversation already bound in a previous hook invocation. */
69
+ export function lookupBoundJob(store, conversationId) {
70
+ const session = findSessionByConversation(store, conversationId);
71
+ if (!session)
72
+ return null;
73
+ const jobs = listJobs(store, {
74
+ sessionId: session.session_id,
75
+ lifecycle: [...BIND_LIFECYCLES],
76
+ limit: 1,
77
+ });
78
+ return jobs[0] ?? null;
79
+ }
80
+ /**
81
+ * Load `jobs/<id>/policy.json`. Null when it is missing or unreadable.
82
+ *
83
+ * Defaults bypass_sandbox to false if missing (fail closed).
84
+ */
85
+ export function loadJobPolicy(store, jobId) {
86
+ const paths = jobPaths(store.paths, jobId);
87
+ const raw = readJsonIfExists(paths.policy);
88
+ if (!raw)
89
+ return null;
90
+ return {
91
+ ...raw,
92
+ max_denials: typeof raw.max_denials === 'number' ? raw.max_denials : null,
93
+ bypass_sandbox: typeof raw.bypass_sandbox === 'boolean' ? raw.bypass_sandbox : false,
94
+ sandbox_forced_by: raw.sandbox_forced_by ?? null,
95
+ // Fail closed the same way bypass_sandbox does: a policy file without
96
+ // `sandbox` (pre-0.3.0) reads as agy's sandbox unless it said bypass.
97
+ sandbox: raw.sandbox === 'none' || raw.sandbox === 'seatbelt' || raw.sandbox === 'agy'
98
+ ? raw.sandbox
99
+ : raw.bypass_sandbox === true
100
+ ? 'none'
101
+ : 'agy',
102
+ sandbox_source: raw.sandbox_source ?? 'default',
103
+ seatbelt_write_roots: Array.isArray(raw.seatbelt_write_roots) ? raw.seatbelt_write_roots : [raw.workspace],
104
+ add_dirs: Array.isArray(raw.add_dirs) ? raw.add_dirs : Array.isArray(raw.additional_dirs) ? raw.additional_dirs : [],
105
+ add_dirs_source: raw.add_dirs_source ?? 'none',
106
+ command_policy: raw.command_policy === 'denylist' ? 'denylist' : 'allowlist',
107
+ lifted: Array.isArray(raw.lifted) ? raw.lifted : [],
108
+ ceiling_present: typeof raw.ceiling_present === 'boolean' ? raw.ceiling_present : true,
109
+ ceiling_path: typeof raw.ceiling_path === 'string' ? raw.ceiling_path : null,
110
+ policy_version: 3,
111
+ };
112
+ }
113
+ /**
114
+ * Reads only the first line of `events.ndjson` (well, the first ~8KB, which is
115
+ * always enough — `init` is a small fixed-shape object) and pulls
116
+ * `conversation_id` out of it. Never throws; a job whose events file does not
117
+ * exist yet simply is not a binding candidate this call.
118
+ */
119
+ function readInitConversationId(store, jobId) {
120
+ const path = jobPaths(store.paths, jobId).events;
121
+ if (!existsSync(path))
122
+ return null;
123
+ let fd;
124
+ try {
125
+ fd = openSync(path, 'r');
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ try {
131
+ const chunkSize = 8192;
132
+ const buf = Buffer.alloc(chunkSize);
133
+ const bytesRead = readSync(fd, buf, 0, chunkSize, 0);
134
+ if (bytesRead <= 0)
135
+ return null;
136
+ const text = buf.toString('utf8', 0, bytesRead);
137
+ const newlineIdx = text.indexOf('\n');
138
+ const line = newlineIdx >= 0 ? text.slice(0, newlineIdx) : text;
139
+ if (!line.trim())
140
+ return null;
141
+ const parsed = JSON.parse(line);
142
+ if (parsed.event === 'init' && typeof parsed.conversation_id === 'string') {
143
+ return parsed.conversation_id;
144
+ }
145
+ return null;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ finally {
151
+ closeSync(fd);
152
+ }
153
+ }
154
+ //# sourceMappingURL=bind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.js","sourceRoot":"","sources":["../../src/gate/bind.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import type { EffectivePolicy, GateDecision, GateLogEntry, GatePayload } from '../contract/types.js';
2
+ import type { BoundJob } from './bind.js';
3
+ /**
4
+ * The PreToolUse hook. Spawned fresh for every single tool call, so it must stay
5
+ * cheap — no warm caches, no heavy imports, no network.
6
+ *
7
+ * ⚠⚠ The one absolute rule of this file: **something with a `decision` field must
8
+ * reach stdout on every path.** `{}` and empty output are denials (§9). A crash,
9
+ * a JSON parse failure, a locked database, a missing job — all of them must still
10
+ * print a `decision`. This is the only code in the package that can affect
11
+ * anything outside our own jobs; getting it wrong breaks the user's interactive
12
+ * agy sessions machine-wide.
13
+ *
14
+ * That decision is not always `ask` any more (0.2.0, I1/I3). A *bound* job's gate never answers
15
+ * `ask` — every tool call is either allowed or denied, and any failure once we
16
+ * know a call is ours becomes `deny`, not a silent pass-through. `ask` survives
17
+ * only for the two cases where we genuinely cannot tell whose call this is: a
18
+ * payload we could not parse at all, and a `conversationId` that binds to none
19
+ * of our jobs (someone's own interactive agy session, which this package must
20
+ * never touch). Failing `ask` there is still correct because agy itself fails
21
+ * closed on anything else a hook can produce — a non-zero exit or output that
22
+ * does not parse as JSON is a *denial*, not a pass-through (measured against
23
+ * 1.1.23, §9) — so `ask` in print mode is the one output that reliably reaches
24
+ * agy's own built-in engine and, for a job we cannot identify, that engine's
25
+ * ordinary confirmation-under-`--dangerously-skip-permissions` behaviour is
26
+ * exactly what the user asked for by running their own session (M3-C:
27
+ * with no `overwrite.BypassSandbox`, the model's own value wins).
28
+ */
29
+ /** The safe pass-through. Delegates to agy's built-in engine. Never `{}`. */
30
+ export declare const PASSTHROUGH: GateDecision;
31
+ export interface GateDecideInput {
32
+ payload: GatePayload;
33
+ /** Null when the conversation is not one of ours. */
34
+ bound: BoundJob | null;
35
+ }
36
+ export interface GateOutcome {
37
+ decision: GateDecision;
38
+ /** Row to append to `gate-log.jsonl`; null when we are not logging (unbound). */
39
+ log: GateLogEntry | null;
40
+ /** True when `on_denial === 'abort'` and this call was denied. */
41
+ requestsAbort: boolean;
42
+ }
43
+ /**
44
+ * Pure decision function — no I/O, so it is exhaustively testable.
45
+ *
46
+ * Order is fixed (see docs/permissions.md) and each step matters:
47
+ *
48
+ * 1. `bound === null` → `ask`, return immediately. Not our job (§1.3 step 1).
49
+ * 2. `unsupported` (unclassifiable tool, or a subagent tool — M2) → `deny`,
50
+ * `required_rule: null`.
51
+ * 3. `control` (`manage_task`) → `allow`, no overwrite.
52
+ * 4. containment (write outside `write_roots` or into `{workspace}/.agents`
53
+ * — I6; read outside `read_roots`) → `deny`, ahead of every rule list.
54
+ * 5. deny list matches → `deny`.
55
+ * 6. allow list matches → `allow`; `run_command` additionally pins
56
+ * `overwrite.Cwd` and sets `overwrite.BypassSandbox` from `policy.bypass_sandbox`
57
+ * (I2) — every allow explicitly decides sandboxing, never leaves it to the
58
+ * model (M3 variant C: an unset overwrite lets the model's own value win).
59
+ * 7. otherwise → `deny`, `required_rule` filled in. I1: a bound job's gate
60
+ * never answers `ask` — `default_decision` is gone.
61
+ *
62
+ * A `deny` reason is an instruction channel, not an explanation: agy hands the
63
+ * string to the model verbatim (§10), so it should name the permitted alternative
64
+ * and carry a parsable `GATE_DENIAL_MARKER` payload for the broker.
65
+ */
66
+ export declare function decide(input: GateDecideInput): GateOutcome;
67
+ /**
68
+ * The `overwrite` every allowed `run_command` carries. `Cwd` is pinned to the
69
+ * workspace and `BypassSandbox` is always explicit (I2, M3 variant C). Under
70
+ * `sandbox: 'seatbelt'` the command itself is rewritten to run inside our own
71
+ * write-only profile (M9: `overwrite.CommandLine` is honoured by agy; the
72
+ * model and the event stream still see the original) with agy's sandbox off.
73
+ */
74
+ export declare function commandOverwrite(policy: EffectivePolicy, commandLine: string): {
75
+ Cwd: string;
76
+ BypassSandbox: boolean;
77
+ CommandLine?: string;
78
+ };
79
+ /**
80
+ * Count the number of 'deny' decisions in a gate-log.jsonl content string.
81
+ * Tolerates empty strings, trailing newlines, and malformed lines.
82
+ */
83
+ export declare function countDeniesInGateLog(text: string): number;
84
+ /** Read stdin to EOF. Returns null on any failure — the caller then passes through. */
85
+ export declare function readStdin(): string | null;
86
+ /** Tolerant payload parse. Null when the shape is unusable; never throws. */
87
+ export declare function parsePayload(raw: string | null): GatePayload | null;
88
+ /**
89
+ * Make stdout write-once and pollution-proof.
90
+ *
91
+ * Measured against agy 1.1.23: a hook that exits 0 but whose stdout does not
92
+ * parse is a DENIAL, not a passthrough —
93
+ * failed to unmarshal result from hook ... via protojson: not json at all
94
+ * and this process always exits 0 (see `src/gate.ts`), so the
95
+ * `|| printf '{"decision":"ask"}'` fallback in `hooks.json` — which only fires
96
+ * on a non-zero exit — cannot cover it. One stray `console.log` from anywhere
97
+ * in the import graph would therefore deny every tool call in the workspace,
98
+ * including the user's own interactive agy sessions.
99
+ *
100
+ * So: everything except {@link emit} is swallowed, and only the first decision
101
+ * is written.
102
+ */
103
+ export declare function guardStdout(): void;
104
+ export declare function emit(decision: GateDecision): void;
105
+ /** Test seam: `emitted` is module state and every test needs a clean one. */
106
+ export declare function resetEmitForTests(): void;
107
+ /** Append to `gate-log.jsonl`. Every verdict is recorded, allows included (§1.8). */
108
+ export declare function logDecision(jobDir: string, entry: GateLogEntry): void;
109
+ /**
110
+ * `dist/gate.js` entry point.
111
+ *
112
+ * Always resolves 0 — a non-zero exit from a hook is not a documented signal,
113
+ * and we must not find out what agy does with one. Emits exactly once: the
114
+ * decision is computed and everything that can fail (DB access, binding,
115
+ * logging, the `on_denial: 'abort'` mark) is contained in a try/catch that
116
+ * runs *before* `emit`, and everything after `emit` is itself wrapped so a
117
+ * failure there can never produce a second line on stdout.
118
+ *
119
+ * Failure paths (not all of them are
120
+ * `ask` any more, since I1 only protects a *bound* job's decision, not the
121
+ * gate process's own health):
122
+ * - unparsable payload → `ask` (we cannot even tell whose call this is —
123
+ * handled above, before this function's own try/catch begins).
124
+ * - the store cannot be opened at all → `deny`, `state unavailable`. A
125
+ * broken `AGY_WORKER_HOME`/project dir means we cannot tell bound from
126
+ * unbound either, but unlike a parse failure this is *our* infrastructure
127
+ * breaking, not a payload shape we cannot read — failing open here would
128
+ * mean every tool call in every workspace runs unguarded the moment our
129
+ * own state directory has a problem.
130
+ * - `bindConversation` resolves and the conversation is not one of ours →
131
+ * `ask` (unchanged; `decide()` returns `PASSTHROUGH` for `bound === null`
132
+ * without throwing, so this falls out of the ordinary call below).
133
+ * - the store opened and anything after that throws → `deny`, `internal
134
+ * error`. By this point we hold a real, open connection; failing closed
135
+ * is strictly safer than guessing `ask` and letting agy's own engine (or
136
+ * the model, per M3-C) decide unsupervised.
137
+ */
138
+ export declare function main(): Promise<number>;
139
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/gate/gate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAGZ,YAAY,EACZ,WAAW,EAEZ,MAAM,sBAAsB,CAAA;AAM7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAKzC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,WAAW,EAAE,YAAkC,CAAA;AAE5D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,WAAW,CAAA;IACpB,qDAAqD;IACrD,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,CAAA;IACtB,iFAAiF;IACjF,GAAG,EAAE,YAAY,GAAG,IAAI,CAAA;IACxB,kEAAkE;IAClE,aAAa,EAAE,OAAO,CAAA;CACvB;AAeD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,WAAW,CAwO1D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,GAClB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAS/D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBzD;AAED,uFAAuF;AACvF,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAMzC;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAkCnE;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAUlC;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAIjD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAErE;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAkG5C"}