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.
- package/CHANGELOG.md +141 -0
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/commands/agy-ceiling.md +31 -0
- package/dist/broker/blockers.d.ts +86 -0
- package/dist/broker/blockers.d.ts.map +1 -0
- package/dist/broker/blockers.js +390 -0
- package/dist/broker/blockers.js.map +1 -0
- package/dist/broker/outcome.d.ts +85 -0
- package/dist/broker/outcome.d.ts.map +1 -0
- package/dist/broker/outcome.js +147 -0
- package/dist/broker/outcome.js.map +1 -0
- package/dist/broker/reconcile.d.ts +74 -0
- package/dist/broker/reconcile.d.ts.map +1 -0
- package/dist/broker/reconcile.js +415 -0
- package/dist/broker/reconcile.js.map +1 -0
- package/dist/broker/result.d.ts +88 -0
- package/dist/broker/result.d.ts.map +1 -0
- package/dist/broker/result.js +234 -0
- package/dist/broker/result.js.map +1 -0
- package/dist/broker/verify.d.ts +52 -0
- package/dist/broker/verify.d.ts.map +1 -0
- package/dist/broker/verify.js +174 -0
- package/dist/broker/verify.js.map +1 -0
- package/dist/contract/errors.d.ts +119 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/errors.js +99 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/index.d.ts +10 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +10 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/paths.d.ts +139 -0
- package/dist/contract/paths.d.ts.map +1 -0
- package/dist/contract/paths.js +359 -0
- package/dist/contract/paths.js.map +1 -0
- package/dist/contract/schema.sql +64 -0
- package/dist/contract/types.d.ts +1113 -0
- package/dist/contract/types.d.ts.map +1 -0
- package/dist/contract/types.js +114 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/events/cursor.d.ts +65 -0
- package/dist/events/cursor.d.ts.map +1 -0
- package/dist/events/cursor.js +286 -0
- package/dist/events/cursor.js.map +1 -0
- package/dist/events/detect.d.ts +58 -0
- package/dist/events/detect.d.ts.map +1 -0
- package/dist/events/detect.js +183 -0
- package/dist/events/detect.js.map +1 -0
- package/dist/events/normalize.d.ts +30 -0
- package/dist/events/normalize.d.ts.map +1 -0
- package/dist/events/normalize.js +158 -0
- package/dist/events/normalize.js.map +1 -0
- package/dist/events/parse.d.ts +41 -0
- package/dist/events/parse.d.ts.map +1 -0
- package/dist/events/parse.js +89 -0
- package/dist/events/parse.js.map +1 -0
- package/dist/gate/bind.d.ts +28 -0
- package/dist/gate/bind.d.ts.map +1 -0
- package/dist/gate/bind.js +154 -0
- package/dist/gate/bind.js.map +1 -0
- package/dist/gate/gate.d.ts +139 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +488 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/hooks-file.d.ts +55 -0
- package/dist/gate/hooks-file.d.ts.map +1 -0
- package/dist/gate/hooks-file.js +104 -0
- package/dist/gate/hooks-file.js.map +1 -0
- package/dist/gate.d.ts +3 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +20 -0
- package/dist/gate.js.map +1 -0
- package/dist/policy/ceiling-review.d.ts +22 -0
- package/dist/policy/ceiling-review.d.ts.map +1 -0
- package/dist/policy/ceiling-review.js +170 -0
- package/dist/policy/ceiling-review.js.map +1 -0
- package/dist/policy/ceiling.d.ts +128 -0
- package/dist/policy/ceiling.d.ts.map +1 -0
- package/dist/policy/ceiling.js +273 -0
- package/dist/policy/ceiling.js.map +1 -0
- package/dist/policy/containment.d.ts +100 -0
- package/dist/policy/containment.d.ts.map +1 -0
- package/dist/policy/containment.js +1071 -0
- package/dist/policy/containment.js.map +1 -0
- package/dist/policy/hard-deny.d.ts +28 -0
- package/dist/policy/hard-deny.d.ts.map +1 -0
- package/dist/policy/hard-deny.js +78 -0
- package/dist/policy/hard-deny.js.map +1 -0
- package/dist/policy/profiles.d.ts +82 -0
- package/dist/policy/profiles.d.ts.map +1 -0
- package/dist/policy/profiles.js +317 -0
- package/dist/policy/profiles.js.map +1 -0
- package/dist/policy/rules.d.ts +138 -0
- package/dist/policy/rules.d.ts.map +1 -0
- package/dist/policy/rules.js +1180 -0
- package/dist/policy/rules.js.map +1 -0
- package/dist/policy/seatbelt.d.ts +32 -0
- package/dist/policy/seatbelt.d.ts.map +1 -0
- package/dist/policy/seatbelt.js +59 -0
- package/dist/policy/seatbelt.js.map +1 -0
- package/dist/policy/tools.d.ts +36 -0
- package/dist/policy/tools.d.ts.map +1 -0
- package/dist/policy/tools.js +94 -0
- package/dist/policy/tools.js.map +1 -0
- package/dist/runner/gate-watchdog.d.ts +93 -0
- package/dist/runner/gate-watchdog.d.ts.map +1 -0
- package/dist/runner/gate-watchdog.js +153 -0
- package/dist/runner/gate-watchdog.js.map +1 -0
- package/dist/runner/idle.d.ts +51 -0
- package/dist/runner/idle.d.ts.map +1 -0
- package/dist/runner/idle.js +81 -0
- package/dist/runner/idle.js.map +1 -0
- package/dist/runner/inbox.d.ts +56 -0
- package/dist/runner/inbox.d.ts.map +1 -0
- package/dist/runner/inbox.js +134 -0
- package/dist/runner/inbox.js.map +1 -0
- package/dist/runner/reap.d.ts +65 -0
- package/dist/runner/reap.d.ts.map +1 -0
- package/dist/runner/reap.js +168 -0
- package/dist/runner/reap.js.map +1 -0
- package/dist/runner/runner.d.ts +51 -0
- package/dist/runner/runner.d.ts.map +1 -0
- package/dist/runner/runner.js +289 -0
- package/dist/runner/runner.js.map +1 -0
- package/dist/runner/spawn.d.ts +98 -0
- package/dist/runner/spawn.d.ts.map +1 -0
- package/dist/runner/spawn.js +230 -0
- package/dist/runner/spawn.js.map +1 -0
- package/dist/runner/verify.d.ts +24 -0
- package/dist/runner/verify.d.ts.map +1 -0
- package/dist/runner/verify.js +108 -0
- package/dist/runner/verify.js.map +1 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +15 -0
- package/dist/runner.js.map +1 -0
- package/dist/server/context.d.ts +41 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +59 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/instructions.d.ts +13 -0
- package/dist/server/instructions.d.ts.map +1 -0
- package/dist/server/instructions.js +121 -0
- package/dist/server/instructions.js.map +1 -0
- package/dist/server/server.d.ts +20 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +55 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/tools/cancel.d.ts +24 -0
- package/dist/server/tools/cancel.d.ts.map +1 -0
- package/dist/server/tools/cancel.js +59 -0
- package/dist/server/tools/cancel.js.map +1 -0
- package/dist/server/tools/capabilities.d.ts +36 -0
- package/dist/server/tools/capabilities.d.ts.map +1 -0
- package/dist/server/tools/capabilities.js +116 -0
- package/dist/server/tools/capabilities.js.map +1 -0
- package/dist/server/tools/ceiling.d.ts +31 -0
- package/dist/server/tools/ceiling.d.ts.map +1 -0
- package/dist/server/tools/ceiling.js +121 -0
- package/dist/server/tools/ceiling.js.map +1 -0
- package/dist/server/tools/index.d.ts +38 -0
- package/dist/server/tools/index.d.ts.map +1 -0
- package/dist/server/tools/index.js +115 -0
- package/dist/server/tools/index.js.map +1 -0
- package/dist/server/tools/listJobs.d.ts +30 -0
- package/dist/server/tools/listJobs.d.ts.map +1 -0
- package/dist/server/tools/listJobs.js +37 -0
- package/dist/server/tools/listJobs.js.map +1 -0
- package/dist/server/tools/logs.d.ts +31 -0
- package/dist/server/tools/logs.d.ts.map +1 -0
- package/dist/server/tools/logs.js +96 -0
- package/dist/server/tools/logs.js.map +1 -0
- package/dist/server/tools/result.d.ts +32 -0
- package/dist/server/tools/result.d.ts.map +1 -0
- package/dist/server/tools/result.js +95 -0
- package/dist/server/tools/result.js.map +1 -0
- package/dist/server/tools/send.d.ts +37 -0
- package/dist/server/tools/send.d.ts.map +1 -0
- package/dist/server/tools/send.js +71 -0
- package/dist/server/tools/send.js.map +1 -0
- package/dist/server/tools/sessions.d.ts +28 -0
- package/dist/server/tools/sessions.d.ts.map +1 -0
- package/dist/server/tools/sessions.js +54 -0
- package/dist/server/tools/sessions.js.map +1 -0
- package/dist/server/tools/start.d.ts +107 -0
- package/dist/server/tools/start.d.ts.map +1 -0
- package/dist/server/tools/start.js +482 -0
- package/dist/server/tools/start.js.map +1 -0
- package/dist/server/tools/wait.d.ts +29 -0
- package/dist/server/tools/wait.d.ts.map +1 -0
- package/dist/server/tools/wait.js +120 -0
- package/dist/server/tools/wait.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +17 -0
- package/dist/server.js.map +1 -0
- package/dist/setup/install.d.ts +52 -0
- package/dist/setup/install.d.ts.map +1 -0
- package/dist/setup/install.js +108 -0
- package/dist/setup/install.js.map +1 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +24 -0
- package/dist/setup.js.map +1 -0
- package/dist/store/db.d.ts +49 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +101 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/jobs.d.ts +45 -0
- package/dist/store/jobs.d.ts.map +1 -0
- package/dist/store/jobs.js +150 -0
- package/dist/store/jobs.js.map +1 -0
- package/dist/store/locks.d.ts +60 -0
- package/dist/store/locks.d.ts.map +1 -0
- package/dist/store/locks.js +172 -0
- package/dist/store/locks.js.map +1 -0
- package/dist/store/sessions.d.ts +37 -0
- package/dist/store/sessions.d.ts.map +1 -0
- package/dist/store/sessions.js +117 -0
- package/dist/store/sessions.js.map +1 -0
- package/dist/trace/digest.d.ts +77 -0
- package/dist/trace/digest.d.ts.map +1 -0
- package/dist/trace/digest.js +481 -0
- package/dist/trace/digest.js.map +1 -0
- package/package.json +68 -0
- package/skills/agy-ceiling/SKILL.md +72 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { appendJsonLine, chmodDbFiles, jobPaths } from '../contract/paths.js';
|
|
4
|
+
import { GATE_DENIAL_MARKER } from '../contract/types.js';
|
|
5
|
+
import { evaluateCommandPolicy, firstMatch, firstMatchForDenial, parseRulesLenient, requiredRuleFor } from '../policy/rules.js';
|
|
6
|
+
import { seatbeltCommandLine } from '../policy/seatbelt.js';
|
|
7
|
+
import { ALLOWED_DEV_READS, assignmentReadPaths, extractCommandContainment, isAgentsPath, isContainedForKind } from '../policy/containment.js';
|
|
8
|
+
import { isCredentialPath } from '../policy/hard-deny.js';
|
|
9
|
+
import { classifyToolCall } from '../policy/tools.js';
|
|
10
|
+
import { bindConversation } from './bind.js';
|
|
11
|
+
import { openStore } from '../store/db.js';
|
|
12
|
+
import { updateJob } from '../store/jobs.js';
|
|
13
|
+
/**
|
|
14
|
+
* The PreToolUse hook. Spawned fresh for every single tool call, so it must stay
|
|
15
|
+
* cheap — no warm caches, no heavy imports, no network.
|
|
16
|
+
*
|
|
17
|
+
* ⚠⚠ The one absolute rule of this file: **something with a `decision` field must
|
|
18
|
+
* reach stdout on every path.** `{}` and empty output are denials (§9). A crash,
|
|
19
|
+
* a JSON parse failure, a locked database, a missing job — all of them must still
|
|
20
|
+
* print a `decision`. This is the only code in the package that can affect
|
|
21
|
+
* anything outside our own jobs; getting it wrong breaks the user's interactive
|
|
22
|
+
* agy sessions machine-wide.
|
|
23
|
+
*
|
|
24
|
+
* That decision is not always `ask` any more (0.2.0, I1/I3). A *bound* job's gate never answers
|
|
25
|
+
* `ask` — every tool call is either allowed or denied, and any failure once we
|
|
26
|
+
* know a call is ours becomes `deny`, not a silent pass-through. `ask` survives
|
|
27
|
+
* only for the two cases where we genuinely cannot tell whose call this is: a
|
|
28
|
+
* payload we could not parse at all, and a `conversationId` that binds to none
|
|
29
|
+
* of our jobs (someone's own interactive agy session, which this package must
|
|
30
|
+
* never touch). Failing `ask` there is still correct because agy itself fails
|
|
31
|
+
* closed on anything else a hook can produce — a non-zero exit or output that
|
|
32
|
+
* does not parse as JSON is a *denial*, not a pass-through (measured against
|
|
33
|
+
* 1.1.23, §9) — so `ask` in print mode is the one output that reliably reaches
|
|
34
|
+
* agy's own built-in engine and, for a job we cannot identify, that engine's
|
|
35
|
+
* ordinary confirmation-under-`--dangerously-skip-permissions` behaviour is
|
|
36
|
+
* exactly what the user asked for by running their own session (M3-C:
|
|
37
|
+
* with no `overwrite.BypassSandbox`, the model's own value wins).
|
|
38
|
+
*/
|
|
39
|
+
/** The safe pass-through. Delegates to agy's built-in engine. Never `{}`. */
|
|
40
|
+
export const PASSTHROUGH = { decision: 'ask' };
|
|
41
|
+
/**
|
|
42
|
+
* `<guidance sentence> [agy-worker-denial:{"required_rule":...}]` — agy passes
|
|
43
|
+
* `reason` to the model verbatim (measured, §10), so the human sentence goes
|
|
44
|
+
* first and the machine payload our own `events/detect.ts` parses trails it.
|
|
45
|
+
*/
|
|
46
|
+
function composeDenialReason(guidance, onDenial, payload) {
|
|
47
|
+
const abortNote = onDenial === 'abort'
|
|
48
|
+
? ' This job is configured with on_denial: "abort" and will be terminated immediately due to this denial.'
|
|
49
|
+
: '';
|
|
50
|
+
return `${guidance}${abortNote} [${GATE_DENIAL_MARKER}${JSON.stringify(payload)}]`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Pure decision function — no I/O, so it is exhaustively testable.
|
|
54
|
+
*
|
|
55
|
+
* Order is fixed (see docs/permissions.md) and each step matters:
|
|
56
|
+
*
|
|
57
|
+
* 1. `bound === null` → `ask`, return immediately. Not our job (§1.3 step 1).
|
|
58
|
+
* 2. `unsupported` (unclassifiable tool, or a subagent tool — M2) → `deny`,
|
|
59
|
+
* `required_rule: null`.
|
|
60
|
+
* 3. `control` (`manage_task`) → `allow`, no overwrite.
|
|
61
|
+
* 4. containment (write outside `write_roots` or into `{workspace}/.agents`
|
|
62
|
+
* — I6; read outside `read_roots`) → `deny`, ahead of every rule list.
|
|
63
|
+
* 5. deny list matches → `deny`.
|
|
64
|
+
* 6. allow list matches → `allow`; `run_command` additionally pins
|
|
65
|
+
* `overwrite.Cwd` and sets `overwrite.BypassSandbox` from `policy.bypass_sandbox`
|
|
66
|
+
* (I2) — every allow explicitly decides sandboxing, never leaves it to the
|
|
67
|
+
* model (M3 variant C: an unset overwrite lets the model's own value win).
|
|
68
|
+
* 7. otherwise → `deny`, `required_rule` filled in. I1: a bound job's gate
|
|
69
|
+
* never answers `ask` — `default_decision` is gone.
|
|
70
|
+
*
|
|
71
|
+
* A `deny` reason is an instruction channel, not an explanation: agy hands the
|
|
72
|
+
* string to the model verbatim (§10), so it should name the permitted alternative
|
|
73
|
+
* and carry a parsable `GATE_DENIAL_MARKER` payload for the broker.
|
|
74
|
+
*/
|
|
75
|
+
export function decide(input) {
|
|
76
|
+
const { payload, bound } = input;
|
|
77
|
+
if (!bound) {
|
|
78
|
+
return { decision: PASSTHROUGH, log: null, requestsAbort: false };
|
|
79
|
+
}
|
|
80
|
+
const { job, policy, conversationId } = bound;
|
|
81
|
+
const toolName = payload.toolCall?.name ?? 'unknown';
|
|
82
|
+
const args = payload.toolCall?.args ?? {};
|
|
83
|
+
const stepIdx = typeof payload.stepIdx === 'number' ? payload.stepIdx : null;
|
|
84
|
+
const toolClass = classifyToolCall(toolName, args, policy.workspace);
|
|
85
|
+
const command = toolClass.kind === 'subject' && toolClass.subject.verb === 'command' ? toolClass.subject.value : null;
|
|
86
|
+
const makeLog = (decision, policyStage, matchedRule, reason) => ({
|
|
87
|
+
ts: Date.now(),
|
|
88
|
+
job_id: job.job_id,
|
|
89
|
+
conversation_id: conversationId,
|
|
90
|
+
step_idx: stepIdx,
|
|
91
|
+
tool: toolName,
|
|
92
|
+
command,
|
|
93
|
+
decision,
|
|
94
|
+
policy: policyStage,
|
|
95
|
+
matched_rule: matchedRule,
|
|
96
|
+
reason,
|
|
97
|
+
});
|
|
98
|
+
/** Every `deny` in this function goes through here so the payload/log/reason stay in lockstep. */
|
|
99
|
+
const denyWith = (policyStage, requiredRule, guidance, options) => {
|
|
100
|
+
const denialPayload = {
|
|
101
|
+
job_id: job.job_id,
|
|
102
|
+
tool: toolName,
|
|
103
|
+
required_rule: requiredRule,
|
|
104
|
+
policy: policyStage,
|
|
105
|
+
on_denial: policy.on_denial,
|
|
106
|
+
};
|
|
107
|
+
const reason = composeDenialReason(guidance, policy.on_denial, denialPayload);
|
|
108
|
+
return {
|
|
109
|
+
decision: { decision: 'deny', reason },
|
|
110
|
+
log: makeLog('deny', policyStage, options?.matchedRule ?? null, reason),
|
|
111
|
+
requestsAbort: options?.requestsAbort ?? (policy.on_denial === 'abort'),
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
// Step 2 (§2.2): unclassifiable or subagent tool.
|
|
115
|
+
// 0.3.0 PR5: an unsupported tool call sets requestsAbort: false, EXCEPT when
|
|
116
|
+
// it is a subagent tool (M2). Subagents attempt to bypass policy by running
|
|
117
|
+
// under an unbound conversationId, so they must abort when on_denial === 'abort'.
|
|
118
|
+
// But a single call to an unclassified tool such as schedule is not a policy
|
|
119
|
+
// violation, and a flaky tool retry should not cost the whole job.
|
|
120
|
+
if (toolClass.kind === 'unsupported') {
|
|
121
|
+
const requestsAbort = toolClass.subagent && policy.on_denial === 'abort';
|
|
122
|
+
return denyWith('unsupported', null, toolClass.reason, { requestsAbort });
|
|
123
|
+
}
|
|
124
|
+
// Step 3: `manage_task` — conversation-scoped bookkeeping, not a rule subject.
|
|
125
|
+
if (toolClass.kind === 'control') {
|
|
126
|
+
return {
|
|
127
|
+
decision: { decision: 'allow' },
|
|
128
|
+
log: makeLog('allow', 'control', null, null),
|
|
129
|
+
requestsAbort: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// toolClass.kind === 'subject' from here.
|
|
133
|
+
const { subject } = toolClass;
|
|
134
|
+
const roots = { read: policy.read_roots, write: policy.write_roots };
|
|
135
|
+
// Step 4: containment & credential HARD_DENY, ahead of every rule list.
|
|
136
|
+
const commandLine = toolName === 'run_command' && typeof args.CommandLine === 'string' ? args.CommandLine : null;
|
|
137
|
+
if (toolName === 'run_command' && commandLine !== null) {
|
|
138
|
+
const analysis = extractCommandContainment(commandLine, policy.workspace);
|
|
139
|
+
// Item 1: Unexpanded shell expansions in path positions
|
|
140
|
+
if (analysis.unexpandedPath) {
|
|
141
|
+
return denyWith('containment', null, `unexpanded_path: Path argument (${analysis.unexpandedPath}) contains unexpanded shell expansions ($VAR, backticks, or non-leading ~) and cannot be verified. Expand the path to an absolute path or a workspace-relative path and retry (globs *, ?, and [ are evaluated against literal directories, so they may be used as-is).`);
|
|
142
|
+
}
|
|
143
|
+
// Item 3: Credential HARD_DENY for commands
|
|
144
|
+
for (const p of analysis.candidatePaths) {
|
|
145
|
+
if (isCredentialPath(p)) {
|
|
146
|
+
return denyWith('deny_list', null, `hard_deny: credential path protected: ${p}`, { matchedRule: 'hard_deny' });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Item 4 & 5: Write containment (resolved against policy.workspace, never args.Cwd)
|
|
150
|
+
const allWrite = analysis.writePaths;
|
|
151
|
+
for (const w of allWrite) {
|
|
152
|
+
if (isAgentsPath(w, policy.workspace)) {
|
|
153
|
+
return denyWith('containment', null, `This action attempts to write inside ${policy.workspace}/.agents (${w}). This directory contains the gate's own hook configuration and is always write-protected against all modifications including shell redirection (I6). Use a different path.`);
|
|
154
|
+
}
|
|
155
|
+
if (!isContainedForKind(w, roots, 'write')) {
|
|
156
|
+
return denyWith('containment', null, `This action attempts to write outside the workspace (${w}). Writes are permitted only within the workspace. Retry with a path inside the workspace.`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Item 2: Read-side containment for shell commands
|
|
160
|
+
const allRead = [...new Set([...analysis.readPaths, ...assignmentReadPaths(commandLine)])];
|
|
161
|
+
for (const r of allRead) {
|
|
162
|
+
if (ALLOWED_DEV_READS.has(r))
|
|
163
|
+
continue;
|
|
164
|
+
if (!isContainedForKind(r, roots, 'read')) {
|
|
165
|
+
return denyWith('containment', null, `read_outside_workspace: This action attempts to read outside the workspace (${r}). Reads are permitted only within the workspace.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
for (const w of toolClass.write) {
|
|
171
|
+
if (isAgentsPath(w, policy.workspace)) {
|
|
172
|
+
return denyWith('containment', null, `This action attempts to write inside ${policy.workspace}/.agents (${w}). This directory contains the gate's own hook configuration and is always write-protected against all modifications including shell redirection (I6). Use a different path.`);
|
|
173
|
+
}
|
|
174
|
+
if (!isContainedForKind(w, roots, 'write')) {
|
|
175
|
+
return denyWith('containment', null, `This action attempts to write outside the workspace (${w}). Writes are permitted only within the workspace. Retry with a path inside the workspace.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const r of toolClass.read) {
|
|
179
|
+
if (!isContainedForKind(r, roots, 'read')) {
|
|
180
|
+
return denyWith('containment', null, `This action attempts to read outside the workspace (${r}). Reads are permitted only within the workspace.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Step 5: deny list.
|
|
185
|
+
const denyHit = firstMatchForDenial(parseRulesLenient(policy.deny), subject);
|
|
186
|
+
if (denyHit) {
|
|
187
|
+
return denyWith('deny_list', null, `This action (${denyHit.raw}) is denied by this job's policy. Do not attempt to bypass this constraint with another tool; report this restriction directly in your final response.`);
|
|
188
|
+
}
|
|
189
|
+
// Step 6 & 7 for command:
|
|
190
|
+
if (subject.verb === 'command') {
|
|
191
|
+
const cmdEval = evaluateCommandPolicy(subject.value, parseRulesLenient(policy.allow), parseRulesLenient(policy.deny));
|
|
192
|
+
if (cmdEval.allowed) {
|
|
193
|
+
const overwrite = commandOverwrite(policy, subject.value);
|
|
194
|
+
return {
|
|
195
|
+
decision: { decision: 'allow', overwrite },
|
|
196
|
+
log: makeLog('allow', 'profile_allowlist', cmdEval.matchedRule, null),
|
|
197
|
+
requestsAbort: false,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (cmdEval.stage === 'deny_list') {
|
|
201
|
+
const guidance = cmdEval.reason === 'env_assignment_denied'
|
|
202
|
+
? 'env_assignment_denied'
|
|
203
|
+
: `This action is denied by this job's policy. Do not attempt to bypass this constraint with another tool; report this restriction directly in your final response.`;
|
|
204
|
+
return denyWith('deny_list', null, guidance);
|
|
205
|
+
}
|
|
206
|
+
// stage === 'default'
|
|
207
|
+
if (policy.command_policy === 'denylist') {
|
|
208
|
+
const overwrite = commandOverwrite(policy, subject.value);
|
|
209
|
+
return {
|
|
210
|
+
decision: { decision: 'allow', overwrite },
|
|
211
|
+
log: makeLog('allow', 'denylist_default', null, null),
|
|
212
|
+
requestsAbort: false,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return denyWith('default', cmdEval.requiredRule, `This action is not in the allowlist for profile "${policy.profile}". If it remains necessary, stop execution and report in your final response that the following rule is required: ${cmdEval.requiredRule}`);
|
|
216
|
+
}
|
|
217
|
+
// Step 6: allow list (for non-command subjects).
|
|
218
|
+
const allowHit = firstMatch(parseRulesLenient(policy.allow), subject);
|
|
219
|
+
if (allowHit) {
|
|
220
|
+
return {
|
|
221
|
+
decision: { decision: 'allow' },
|
|
222
|
+
log: makeLog('allow', 'profile_allowlist', allowHit.raw, null),
|
|
223
|
+
requestsAbort: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Step 7: nothing matched. I1 — a bound job's gate never answers `ask`;
|
|
227
|
+
// `default_decision` is gone, so this is always `deny`.
|
|
228
|
+
const requiredRule = requiredRuleFor(subject);
|
|
229
|
+
return denyWith('default', requiredRule, `This action is not in the allowlist for profile "${policy.profile}". If it remains necessary, stop execution and report in your final response that the following rule is required: ${requiredRule}`);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The `overwrite` every allowed `run_command` carries. `Cwd` is pinned to the
|
|
233
|
+
* workspace and `BypassSandbox` is always explicit (I2, M3 variant C). Under
|
|
234
|
+
* `sandbox: 'seatbelt'` the command itself is rewritten to run inside our own
|
|
235
|
+
* write-only profile (M9: `overwrite.CommandLine` is honoured by agy; the
|
|
236
|
+
* model and the event stream still see the original) with agy's sandbox off.
|
|
237
|
+
*/
|
|
238
|
+
export function commandOverwrite(policy, commandLine) {
|
|
239
|
+
if (policy.sandbox === 'seatbelt') {
|
|
240
|
+
return {
|
|
241
|
+
Cwd: policy.workspace,
|
|
242
|
+
BypassSandbox: true,
|
|
243
|
+
CommandLine: seatbeltCommandLine(commandLine, policy.seatbelt_write_roots),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return { Cwd: policy.workspace, BypassSandbox: policy.bypass_sandbox };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Count the number of 'deny' decisions in a gate-log.jsonl content string.
|
|
250
|
+
* Tolerates empty strings, trailing newlines, and malformed lines.
|
|
251
|
+
*/
|
|
252
|
+
export function countDeniesInGateLog(text) {
|
|
253
|
+
if (!text)
|
|
254
|
+
return 0;
|
|
255
|
+
let count = 0;
|
|
256
|
+
const lines = text.split('\n');
|
|
257
|
+
for (const line of lines) {
|
|
258
|
+
const trimmed = line.trim();
|
|
259
|
+
if (!trimmed)
|
|
260
|
+
continue;
|
|
261
|
+
try {
|
|
262
|
+
const parsed = JSON.parse(trimmed);
|
|
263
|
+
if (parsed && typeof parsed === 'object' && parsed.decision === 'deny') {
|
|
264
|
+
count++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// ignore malformed lines
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return count;
|
|
272
|
+
}
|
|
273
|
+
/** Read stdin to EOF. Returns null on any failure — the caller then passes through. */
|
|
274
|
+
export function readStdin() {
|
|
275
|
+
try {
|
|
276
|
+
return readFileSync(0, 'utf8');
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Tolerant payload parse. Null when the shape is unusable; never throws. */
|
|
283
|
+
export function parsePayload(raw) {
|
|
284
|
+
if (!raw || !raw.trim())
|
|
285
|
+
return null;
|
|
286
|
+
try {
|
|
287
|
+
const parsed = JSON.parse(raw);
|
|
288
|
+
if (!parsed || typeof parsed !== 'object')
|
|
289
|
+
return null;
|
|
290
|
+
const p = parsed;
|
|
291
|
+
if (typeof p.conversationId !== 'string' || p.conversationId.length === 0)
|
|
292
|
+
return null;
|
|
293
|
+
const toolCallRaw = p.toolCall;
|
|
294
|
+
if (!toolCallRaw || typeof toolCallRaw !== 'object')
|
|
295
|
+
return null;
|
|
296
|
+
const toolCall = toolCallRaw;
|
|
297
|
+
if (typeof toolCall.name !== 'string' || toolCall.name.length === 0)
|
|
298
|
+
return null;
|
|
299
|
+
const args = toolCall.args && typeof toolCall.args === 'object'
|
|
300
|
+
? toolCall.args
|
|
301
|
+
: undefined;
|
|
302
|
+
return {
|
|
303
|
+
conversationId: p.conversationId,
|
|
304
|
+
stepIdx: typeof p.stepIdx === 'number' ? p.stepIdx : undefined,
|
|
305
|
+
modelName: typeof p.modelName === 'string' ? p.modelName : undefined,
|
|
306
|
+
toolCall: { name: toolCall.name, args },
|
|
307
|
+
workspacePaths: Array.isArray(p.workspacePaths)
|
|
308
|
+
? (p.workspacePaths.filter((x) => typeof x === 'string'))
|
|
309
|
+
: undefined,
|
|
310
|
+
transcriptPath: typeof p.transcriptPath === 'string' ? p.transcriptPath : undefined,
|
|
311
|
+
artifactDirectoryPath: typeof p.artifactDirectoryPath === 'string' ? p.artifactDirectoryPath : undefined,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Write the decision to stdout as one line. The only place that writes stdout. */
|
|
319
|
+
/**
|
|
320
|
+
* Captured before {@link guardStdout} neuters the public one, so this stays the
|
|
321
|
+
* only path to the real file descriptor.
|
|
322
|
+
*/
|
|
323
|
+
const realStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
324
|
+
let emitted = false;
|
|
325
|
+
/**
|
|
326
|
+
* Make stdout write-once and pollution-proof.
|
|
327
|
+
*
|
|
328
|
+
* Measured against agy 1.1.23: a hook that exits 0 but whose stdout does not
|
|
329
|
+
* parse is a DENIAL, not a passthrough —
|
|
330
|
+
* failed to unmarshal result from hook ... via protojson: not json at all
|
|
331
|
+
* and this process always exits 0 (see `src/gate.ts`), so the
|
|
332
|
+
* `|| printf '{"decision":"ask"}'` fallback in `hooks.json` — which only fires
|
|
333
|
+
* on a non-zero exit — cannot cover it. One stray `console.log` from anywhere
|
|
334
|
+
* in the import graph would therefore deny every tool call in the workspace,
|
|
335
|
+
* including the user's own interactive agy sessions.
|
|
336
|
+
*
|
|
337
|
+
* So: everything except {@link emit} is swallowed, and only the first decision
|
|
338
|
+
* is written.
|
|
339
|
+
*/
|
|
340
|
+
export function guardStdout() {
|
|
341
|
+
process.stdout.write = ((_chunk, encoding, cb) => {
|
|
342
|
+
const done = typeof encoding === 'function' ? encoding : cb;
|
|
343
|
+
if (typeof done === 'function')
|
|
344
|
+
done();
|
|
345
|
+
return true;
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
export function emit(decision) {
|
|
349
|
+
if (emitted)
|
|
350
|
+
return;
|
|
351
|
+
emitted = true;
|
|
352
|
+
realStdoutWrite(JSON.stringify(decision));
|
|
353
|
+
}
|
|
354
|
+
/** Test seam: `emitted` is module state and every test needs a clean one. */
|
|
355
|
+
export function resetEmitForTests() {
|
|
356
|
+
emitted = false;
|
|
357
|
+
}
|
|
358
|
+
/** Append to `gate-log.jsonl`. Every verdict is recorded, allows included (§1.8). */
|
|
359
|
+
export function logDecision(jobDir, entry) {
|
|
360
|
+
appendJsonLine(join(jobDir, 'gate-log.jsonl'), entry);
|
|
361
|
+
}
|
|
362
|
+
/** `deny` reason used when the store cannot even be opened (§2.3). No job to log against. */
|
|
363
|
+
const STATE_UNAVAILABLE = { decision: 'deny', reason: 'agy-worker gate: state unavailable' };
|
|
364
|
+
/** `deny` reason for any failure once we are past "open the store" (§2.3). */
|
|
365
|
+
const INTERNAL_ERROR = { decision: 'deny', reason: 'agy-worker gate: internal error' };
|
|
366
|
+
/**
|
|
367
|
+
* `dist/gate.js` entry point.
|
|
368
|
+
*
|
|
369
|
+
* Always resolves 0 — a non-zero exit from a hook is not a documented signal,
|
|
370
|
+
* and we must not find out what agy does with one. Emits exactly once: the
|
|
371
|
+
* decision is computed and everything that can fail (DB access, binding,
|
|
372
|
+
* logging, the `on_denial: 'abort'` mark) is contained in a try/catch that
|
|
373
|
+
* runs *before* `emit`, and everything after `emit` is itself wrapped so a
|
|
374
|
+
* failure there can never produce a second line on stdout.
|
|
375
|
+
*
|
|
376
|
+
* Failure paths (not all of them are
|
|
377
|
+
* `ask` any more, since I1 only protects a *bound* job's decision, not the
|
|
378
|
+
* gate process's own health):
|
|
379
|
+
* - unparsable payload → `ask` (we cannot even tell whose call this is —
|
|
380
|
+
* handled above, before this function's own try/catch begins).
|
|
381
|
+
* - the store cannot be opened at all → `deny`, `state unavailable`. A
|
|
382
|
+
* broken `AGY_WORKER_HOME`/project dir means we cannot tell bound from
|
|
383
|
+
* unbound either, but unlike a parse failure this is *our* infrastructure
|
|
384
|
+
* breaking, not a payload shape we cannot read — failing open here would
|
|
385
|
+
* mean every tool call in every workspace runs unguarded the moment our
|
|
386
|
+
* own state directory has a problem.
|
|
387
|
+
* - `bindConversation` resolves and the conversation is not one of ours →
|
|
388
|
+
* `ask` (unchanged; `decide()` returns `PASSTHROUGH` for `bound === null`
|
|
389
|
+
* without throwing, so this falls out of the ordinary call below).
|
|
390
|
+
* - the store opened and anything after that throws → `deny`, `internal
|
|
391
|
+
* error`. By this point we hold a real, open connection; failing closed
|
|
392
|
+
* is strictly safer than guessing `ask` and letting agy's own engine (or
|
|
393
|
+
* the model, per M3-C) decide unsupervised.
|
|
394
|
+
*/
|
|
395
|
+
export async function main() {
|
|
396
|
+
const raw = readStdin();
|
|
397
|
+
const payload = parsePayload(raw);
|
|
398
|
+
if (!payload) {
|
|
399
|
+
emit(PASSTHROUGH);
|
|
400
|
+
return 0;
|
|
401
|
+
}
|
|
402
|
+
let outcome = { decision: INTERNAL_ERROR, log: null, requestsAbort: false };
|
|
403
|
+
let jobDir = null;
|
|
404
|
+
let store;
|
|
405
|
+
try {
|
|
406
|
+
store = openStore({ readOnly: true });
|
|
407
|
+
chmodDbFiles(store.paths.db);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
emit(STATE_UNAVAILABLE);
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
try {
|
|
415
|
+
const bound = bindConversation(store, payload.conversationId);
|
|
416
|
+
outcome = decide({ payload, bound });
|
|
417
|
+
if (bound) {
|
|
418
|
+
jobDir = jobPaths(store.paths, bound.job.job_id).dir;
|
|
419
|
+
let reachedMaxDenials = false;
|
|
420
|
+
if (outcome.log?.decision === 'deny' &&
|
|
421
|
+
typeof bound.policy.max_denials === 'number' &&
|
|
422
|
+
bound.job.lifecycle !== 'canceling' &&
|
|
423
|
+
bound.job.lifecycle !== 'finished') {
|
|
424
|
+
const gateLogFile = jobPaths(store.paths, bound.job.job_id).gateLog;
|
|
425
|
+
let existingDenies = 0;
|
|
426
|
+
try {
|
|
427
|
+
if (existsSync(gateLogFile)) {
|
|
428
|
+
existingDenies = countDeniesInGateLog(readFileSync(gateLogFile, 'utf8'));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Best effort; tolerate missing or unreadable gate-log.
|
|
433
|
+
}
|
|
434
|
+
reachedMaxDenials = existingDenies + 1 >= bound.policy.max_denials;
|
|
435
|
+
}
|
|
436
|
+
const shouldAbort = (outcome.requestsAbort || reachedMaxDenials) &&
|
|
437
|
+
bound.job.lifecycle !== 'canceling' &&
|
|
438
|
+
bound.job.lifecycle !== 'finished';
|
|
439
|
+
if (shouldAbort) {
|
|
440
|
+
const abortReason = outcome.requestsAbort
|
|
441
|
+
? 'on_denial'
|
|
442
|
+
: 'max_denials';
|
|
443
|
+
if (outcome.log) {
|
|
444
|
+
outcome.log.abort_reason = abortReason;
|
|
445
|
+
}
|
|
446
|
+
// The store above is now genuinely read-only (finding 19), so this
|
|
447
|
+
// one write opens its own short-lived read-write connection rather
|
|
448
|
+
// than widening the hot path everyone else pays for.
|
|
449
|
+
try {
|
|
450
|
+
const writeStore = openStore({ readOnly: false });
|
|
451
|
+
chmodDbFiles(writeStore.paths.db);
|
|
452
|
+
try {
|
|
453
|
+
// Mark only — the actual killpg is handled by the broker's
|
|
454
|
+
// reconcile loop, not by the gate.
|
|
455
|
+
updateJob(writeStore, bound.job.job_id, { lifecycle: 'canceling' });
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
writeStore.close();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Best effort; the deadline / next reconcile still catches it.
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
finally {
|
|
468
|
+
store.close();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
// Past this point we had a real store connection open — fail closed
|
|
473
|
+
// rather than passing through (§2.3's "bound, later exception" row).
|
|
474
|
+
outcome = { decision: INTERNAL_ERROR, log: null, requestsAbort: false };
|
|
475
|
+
jobDir = null;
|
|
476
|
+
}
|
|
477
|
+
emit(outcome.decision);
|
|
478
|
+
if (jobDir && outcome.log) {
|
|
479
|
+
try {
|
|
480
|
+
logDecision(jobDir, outcome.log);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Logging is best-effort; the decision already reached stdout.
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/gate/gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single owner of `<workspace>/.agents/hooks.json`'s lifecycle: `start.ts` writes
|
|
3
|
+
* our entry when a job spawns, `reconcile.ts` removes it once no live job on that
|
|
4
|
+
* `cwd` needs it any more. Kept in one module — with the key constant — so the two
|
|
5
|
+
* call sites can never drift on what "our entry" means.
|
|
6
|
+
*/
|
|
7
|
+
/** The one key this package ever writes into `hooks.json`. Never touch any other key. */
|
|
8
|
+
export declare const GATE_HOOK_KEY = "agy-worker-gate";
|
|
9
|
+
export declare function hooksFilePath(workspace: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Write (merging, never clobbering unrelated keys) `<workspace>/.agents/hooks.json`
|
|
12
|
+
* so the PreToolUse gate loads for this workspace. `--add-dir` is what makes agy
|
|
13
|
+
* load it at all (see docs/permissions.md) — this is the piece that puts the
|
|
14
|
+
* file there in the first place, since nothing else in the package owns it.
|
|
15
|
+
*
|
|
16
|
+
* The command is just `node '<gatePath>'` — quoted (an unquoted absolute path
|
|
17
|
+
* breaks on any install path containing a space). There used to be a
|
|
18
|
+
* `|| printf '{"decision":"ask"}'` fallback here; it is gone. agy already fails closed on its own —
|
|
19
|
+
* empty/non-JSON stdout or a non-zero exit is a *denial*, not a pass-through
|
|
20
|
+
* (measured against 1.1.23, `src/gate/gate.ts`'s own header comment) — so the
|
|
21
|
+
* fallback never protected anything: its only effect was to convert that
|
|
22
|
+
* already-safe failure into `{"decision":"ask"}`, which in print mode reaches
|
|
23
|
+
* agy's built-in engine and lets a `run_command` call proceed with whatever
|
|
24
|
+
* `BypassSandbox` value the model itself asked for (M3-C) — the exact hole I2 exists to close.
|
|
25
|
+
* Losing the fallback also means `ensureGateHook` must only ever be called once
|
|
26
|
+
* `gatePath` is confirmed to exist (`agy_start`'s own `existsSync` check).
|
|
27
|
+
*
|
|
28
|
+
* Our key is written **first** in the object, ahead of every pre-existing key.
|
|
29
|
+
* Measured (M6): agy evaluates
|
|
30
|
+
* `hooks.json`'s PreToolUse groups in declaration order and a `deny` from an
|
|
31
|
+
* earlier group short-circuits the rest — a group installed ahead of ours that
|
|
32
|
+
* denies would mean our gate never runs at all for that call, and the runtime
|
|
33
|
+
* watchdog (`src/runner/gate-watchdog.ts`) would misread the resulting silence
|
|
34
|
+
* in `gate-log.jsonl` as "hooks never loaded". Writing ours first guarantees it
|
|
35
|
+
* runs on every tool call regardless of what else is declared in this file.
|
|
36
|
+
* (A hook group installed *outside* this file entirely — e.g. a user's global
|
|
37
|
+
* `~/.gemini/config/hooks.json` — that denies ahead of ours is the one case
|
|
38
|
+
* this cannot cover; the watchdog still reports it correctly if bluntly, as
|
|
39
|
+
* "gate never fired", which is safe — fail closed — just imprecise about why.
|
|
40
|
+
* Not handled here, by design.)
|
|
41
|
+
*/
|
|
42
|
+
export declare function ensureGateHook(workspace: string, gatePath: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Remove only {@link GATE_HOOK_KEY} from `<workspace>/.agents/hooks.json`,
|
|
45
|
+
* preserving every other key untouched. Deletes the file when it held nothing
|
|
46
|
+
* else, and the `.agents` directory when that leaves it empty. A no-op when the
|
|
47
|
+
* file does not exist or never carried our key.
|
|
48
|
+
*
|
|
49
|
+
* Best effort by design: called from
|
|
50
|
+
* `reconcile`, which must never fail a tool call over hook-file housekeeping. A
|
|
51
|
+
* stale entry left behind by a failure here is harmless until the next
|
|
52
|
+
* `agy_start` on the same workspace, which overwrites it unconditionally.
|
|
53
|
+
*/
|
|
54
|
+
export declare function removeGateHook(workspace: string): void;
|
|
55
|
+
//# sourceMappingURL=hooks-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-file.d.ts","sourceRoot":"","sources":["../../src/gate/hooks-file.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH,yFAAyF;AACzF,eAAO,MAAM,aAAa,oBAAoB,CAAA;AAE9C,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAexE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAyBtD"}
|