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,24 @@
|
|
|
1
|
+
import type { VerifyRecord } from '../contract/types.js';
|
|
2
|
+
export interface RunVerifyOptions {
|
|
3
|
+
command: string;
|
|
4
|
+
/** Canonical workspace — the same `cwd` agy itself ran in. */
|
|
5
|
+
cwd: string;
|
|
6
|
+
/** The same allowlisted child env agy's own process received. */
|
|
7
|
+
env: Record<string, string>;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
/** `jobs/<id>/verify.log` — truncated fresh for this run. */
|
|
10
|
+
logPath: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Spawn `sh -c <command>` detached (its own process group, same technique as
|
|
14
|
+
* `spawnAgyDetached`), append stdout+stderr to `logPath` capped at 1 MiB, and
|
|
15
|
+
* wait up to `timeoutMs` — killing the whole group on timeout.
|
|
16
|
+
*
|
|
17
|
+
* Unlike agy's own spawn, stdio is genuinely piped rather than fd-redirected:
|
|
18
|
+
* the runner is alive and actively draining both streams for the entire
|
|
19
|
+
* lifetime of this call (there is no detached-parent-exits race to guard
|
|
20
|
+
* against here), which is what makes counting bytes for the 1 MiB cap
|
|
21
|
+
* possible in the first place.
|
|
22
|
+
*/
|
|
23
|
+
export declare function runVerifyCommand(opts: RunVerifyOptions): Promise<VerifyRecord>;
|
|
24
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/runner/verify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAkBxD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuFpF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { closeSync, openSync, writeSync } from 'node:fs';
|
|
3
|
+
import { killProcessGroup } from './reap.js';
|
|
4
|
+
/**
|
|
5
|
+
* `verify_command` execution (PR6; see docs/permissions.md).
|
|
6
|
+
*
|
|
7
|
+
* Run once by the runner, after agy has exited normally, against the final
|
|
8
|
+
* workspace state — outside the model's own decisions entirely. Not a
|
|
9
|
+
* sandboxing feature: this runs as the user, with no isolation, at the same
|
|
10
|
+
* trust level as the parent agent running the command itself. Its own
|
|
11
|
+
* `deadline_at`-independent timeout (`timeoutMs`) means it may finish after
|
|
12
|
+
* the job's `deadline_at` has already passed; that is expected, not a bug —
|
|
13
|
+
* agy's deadline and the verify timeout are two separate clocks.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_VERIFY_LOG_BYTES = 1024 * 1024;
|
|
16
|
+
const TRUNCATION_MARKER = '\n[agy-worker: verify output truncated at 1 MiB]\n';
|
|
17
|
+
/**
|
|
18
|
+
* Spawn `sh -c <command>` detached (its own process group, same technique as
|
|
19
|
+
* `spawnAgyDetached`), append stdout+stderr to `logPath` capped at 1 MiB, and
|
|
20
|
+
* wait up to `timeoutMs` — killing the whole group on timeout.
|
|
21
|
+
*
|
|
22
|
+
* Unlike agy's own spawn, stdio is genuinely piped rather than fd-redirected:
|
|
23
|
+
* the runner is alive and actively draining both streams for the entire
|
|
24
|
+
* lifetime of this call (there is no detached-parent-exits race to guard
|
|
25
|
+
* against here), which is what makes counting bytes for the 1 MiB cap
|
|
26
|
+
* possible in the first place.
|
|
27
|
+
*/
|
|
28
|
+
export async function runVerifyCommand(opts) {
|
|
29
|
+
const startedAt = Date.now();
|
|
30
|
+
const startHr = process.hrtime.bigint();
|
|
31
|
+
// 'w': each job runs verify_command at most once, so a fresh file is always
|
|
32
|
+
// correct — there is nothing from a previous run to append to.
|
|
33
|
+
const logFd = openSync(opts.logPath, 'w', 0o600);
|
|
34
|
+
let bytesWritten = 0;
|
|
35
|
+
let truncated = false;
|
|
36
|
+
const appendChunk = (chunk) => {
|
|
37
|
+
if (truncated)
|
|
38
|
+
return;
|
|
39
|
+
const remaining = MAX_VERIFY_LOG_BYTES - bytesWritten;
|
|
40
|
+
if (remaining <= 0) {
|
|
41
|
+
truncated = true;
|
|
42
|
+
writeSync(logFd, TRUNCATION_MARKER);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
46
|
+
writeSync(logFd, slice);
|
|
47
|
+
bytesWritten += slice.length;
|
|
48
|
+
if (chunk.length > remaining) {
|
|
49
|
+
truncated = true;
|
|
50
|
+
writeSync(logFd, TRUNCATION_MARKER);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
let child;
|
|
54
|
+
try {
|
|
55
|
+
child = spawn('sh', ['-c', opts.command], {
|
|
56
|
+
cwd: opts.cwd,
|
|
57
|
+
env: opts.env,
|
|
58
|
+
detached: true,
|
|
59
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
64
|
+
writeSync(logFd, `[agy-worker: verify spawn failed: ${message}]\n`);
|
|
65
|
+
closeSync(logFd);
|
|
66
|
+
const durationMs = Math.round(Number(process.hrtime.bigint() - startHr) / 1e6);
|
|
67
|
+
return {
|
|
68
|
+
command: opts.command,
|
|
69
|
+
exit_code: null,
|
|
70
|
+
signal: null,
|
|
71
|
+
started_at: startedAt,
|
|
72
|
+
duration_ms: durationMs,
|
|
73
|
+
timed_out: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const pid = child.pid;
|
|
77
|
+
if (pid == null) {
|
|
78
|
+
closeSync(logFd);
|
|
79
|
+
throw new Error('agy-worker-runner: verify spawn produced no pid');
|
|
80
|
+
}
|
|
81
|
+
// detached:true makes the child the leader of its own process group, so
|
|
82
|
+
// pgid === pid — same as spawnAgyDetached.
|
|
83
|
+
const pgid = pid;
|
|
84
|
+
child.stdout?.on('data', appendChunk);
|
|
85
|
+
child.stderr?.on('data', appendChunk);
|
|
86
|
+
const exitPromise = new Promise((resolveExit) => {
|
|
87
|
+
child.once('exit', (code, signal) => resolveExit({ code, signal }));
|
|
88
|
+
child.once('error', () => resolveExit({ code: null, signal: null }));
|
|
89
|
+
});
|
|
90
|
+
let timedOut = false;
|
|
91
|
+
const timeoutTimer = setTimeout(() => {
|
|
92
|
+
timedOut = true;
|
|
93
|
+
void killProcessGroup(pgid);
|
|
94
|
+
}, opts.timeoutMs);
|
|
95
|
+
const { code, signal } = await exitPromise;
|
|
96
|
+
clearTimeout(timeoutTimer);
|
|
97
|
+
closeSync(logFd);
|
|
98
|
+
const durationMs = Math.round(Number(process.hrtime.bigint() - startHr) / 1e6);
|
|
99
|
+
return {
|
|
100
|
+
command: opts.command,
|
|
101
|
+
exit_code: code,
|
|
102
|
+
signal,
|
|
103
|
+
started_at: startedAt,
|
|
104
|
+
duration_ms: durationMs,
|
|
105
|
+
timed_out: timedOut,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/runner/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":""}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for the detached runner. Compiles to `dist/runner.js`.
|
|
4
|
+
*
|
|
5
|
+
* Spawned with `detached: true` by whichever server started the job, and expected
|
|
6
|
+
* to keep running after that server is gone.
|
|
7
|
+
*/
|
|
8
|
+
import { main } from './runner/runner.js';
|
|
9
|
+
main(process.argv.slice(2))
|
|
10
|
+
.then((code) => process.exit(code))
|
|
11
|
+
.catch((e) => {
|
|
12
|
+
process.stderr.write(`agy-worker-runner: ${e instanceof Error ? e.stack ?? e.message : String(e)}\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAEzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAClC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;IACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACrG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Capabilities, ClientSnapshot } from '../contract/types.js';
|
|
2
|
+
import type { ProjectPaths } from '../contract/paths.js';
|
|
3
|
+
import { type Store } from '../store/db.js';
|
|
4
|
+
/** Shared state every tool handler receives. One per server process. */
|
|
5
|
+
export interface ToolContext {
|
|
6
|
+
store: Store;
|
|
7
|
+
paths: ProjectPaths;
|
|
8
|
+
version: string;
|
|
9
|
+
limits: Capabilities['limits'];
|
|
10
|
+
/**
|
|
11
|
+
* Set by `createServer` once the transport exists. Left undefined when a test
|
|
12
|
+
* (or any other caller) drives the handlers directly with no client attached.
|
|
13
|
+
*/
|
|
14
|
+
getClient?: () => ClientSnapshot | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Standard MCP tool return. The JSON goes out as text content *and* as
|
|
18
|
+
* `structuredContent`: Codex's structured-output support is uneven, so text is
|
|
19
|
+
* the safe default while structured stays available for clients that use it
|
|
20
|
+
* (see docs/tools.md).
|
|
21
|
+
*/
|
|
22
|
+
export interface ToolReply {
|
|
23
|
+
content: Array<{
|
|
24
|
+
type: 'text';
|
|
25
|
+
text: string;
|
|
26
|
+
}>;
|
|
27
|
+
structuredContent?: Record<string, unknown>;
|
|
28
|
+
isError?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Wrap a JSON-serializable payload as text content plus structuredContent. */
|
|
31
|
+
export declare function reply(payload: unknown): ToolReply;
|
|
32
|
+
/**
|
|
33
|
+
* Turn any throw into a teaching error reply: the structured `detail` from
|
|
34
|
+
* `contract/errors.ts` plus its `remedy`, so the calling agent can self-correct
|
|
35
|
+
* without another round trip.
|
|
36
|
+
*/
|
|
37
|
+
export declare function errorReply(e: unknown): ToolReply;
|
|
38
|
+
export declare const DEFAULT_LIMITS: Capabilities['limits'];
|
|
39
|
+
/** Build the context, opening the store for the discovered project root. */
|
|
40
|
+
export declare function createContext(cwd?: string): ToolContext;
|
|
41
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAEtD,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,YAAY,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,cAAc,GAAG,IAAI,CAAA;CACxC;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,+EAA+E;AAC/E,wBAAgB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAOjD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,SAAS,CAOhD;AAYD,eAAO,MAAM,cAAc,EAAE,YAAY,CAAC,QAAQ,CAWjD,CAAA;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAQvD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { packageRoot } from '../contract/paths.js';
|
|
4
|
+
import { toErrorEnvelope } from '../contract/errors.js';
|
|
5
|
+
import { openStore } from '../store/db.js';
|
|
6
|
+
/** Wrap a JSON-serializable payload as text content plus structuredContent. */
|
|
7
|
+
export function reply(payload) {
|
|
8
|
+
const text = JSON.stringify(payload, null, 2);
|
|
9
|
+
const structuredContent = payload !== null && typeof payload === 'object' && !Array.isArray(payload)
|
|
10
|
+
? payload
|
|
11
|
+
: { value: payload };
|
|
12
|
+
return { content: [{ type: 'text', text }], structuredContent };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Turn any throw into a teaching error reply: the structured `detail` from
|
|
16
|
+
* `contract/errors.ts` plus its `remedy`, so the calling agent can self-correct
|
|
17
|
+
* without another round trip.
|
|
18
|
+
*/
|
|
19
|
+
export function errorReply(e) {
|
|
20
|
+
const envelope = toErrorEnvelope(e);
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: 'text', text: JSON.stringify(envelope, null, 2) }],
|
|
23
|
+
structuredContent: envelope,
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function readPackageVersion() {
|
|
28
|
+
try {
|
|
29
|
+
const raw = readFileSync(join(packageRoot(), 'package.json'), 'utf8');
|
|
30
|
+
const pkg = JSON.parse(raw);
|
|
31
|
+
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '0.0.0';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const DEFAULT_LIMITS = {
|
|
38
|
+
max_running_jobs: 3,
|
|
39
|
+
max_timeout_ms: 60 * 60 * 1000,
|
|
40
|
+
default_timeout_ms: 15 * 60 * 1000,
|
|
41
|
+
// See the comment on `Capabilities['limits']` for why
|
|
42
|
+
// this value is a judgment call, not a measurement.
|
|
43
|
+
default_idle_timeout_ms: 2 * 60 * 1000,
|
|
44
|
+
max_idle_timeout_ms: 60 * 60 * 1000,
|
|
45
|
+
default_verify_timeout_ms: 10 * 60 * 1000,
|
|
46
|
+
max_response_bytes: 32_000,
|
|
47
|
+
max_log_tail_lines: 30,
|
|
48
|
+
};
|
|
49
|
+
/** Build the context, opening the store for the discovered project root. */
|
|
50
|
+
export function createContext(cwd) {
|
|
51
|
+
const store = openStore({ cwd });
|
|
52
|
+
return {
|
|
53
|
+
store,
|
|
54
|
+
paths: store.paths,
|
|
55
|
+
version: readPackageVersion(),
|
|
56
|
+
limits: DEFAULT_LIMITS,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAIhC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAc,MAAM,gBAAgB,CAAA;AA2BtD,+EAA+E;AAC/E,MAAM,UAAU,KAAK,CAAC,OAAgB;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC7C,MAAM,iBAAiB,GACrB,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACxE,CAAC,CAAE,OAAmC;QACtC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAA;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,CAAU;IACnC,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QACpE,iBAAiB,EAAE,QAA8C;QACjE,OAAO,EAAE,IAAI;KACd,CAAA;AACH,CAAC;AAED,SAAS,kBAAkB;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAA;QACrE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAA;QACnD,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAA;IAChB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,gBAAgB,EAAE,CAAC;IACnB,cAAc,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IAC9B,kBAAkB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IAClC,sDAAsD;IACtD,oDAAoD;IACpD,uBAAuB,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI;IACtC,mBAAmB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACnC,yBAAyB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACzC,kBAAkB,EAAE,MAAM;IAC1B,kBAAkB,EAAE,EAAE;CACvB,CAAA;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;IAChC,OAAO;QACL,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,kBAAkB,EAAE;QAC7B,MAAM,EAAE,cAAc;KACvB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server instructions — returned in the MCP `initialize` response and injected
|
|
3
|
+
* into the calling agent's system prompt.
|
|
4
|
+
*
|
|
5
|
+
* This is the top layer of the three-layer tool surface:
|
|
6
|
+
* this file sets orientation and the traps that matter before the first call;
|
|
7
|
+
* per-tool `description` and per-parameter `.describe()` carry the call-by-call
|
|
8
|
+
* detail; `contract/errors.ts` carries the in-the-moment repair. Two rules for
|
|
9
|
+
* whoever edits it: never describe a capability that does not exist, and never
|
|
10
|
+
* omit a trap that does.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SERVER_INSTRUCTIONS = "agy-worker-mcp runs the Google Antigravity CLI (agy) as an asynchronous worker.\nJobs are detached: they survive this connection, and any client attached to the\nsame project can observe, wait on, or take over any job \u2014 including one another\nclient started.\n\nRecommended flow\n agy_capabilities -> profiles, models, limits, discovered project root\n agy_start -> returns a job_id immediately; nothing blocks\n agy_wait -> loop: call again with the returned cursor until\n lifecycle is \"finished\" (wait_ms: 0 = snapshot, no block)\n agy_result -> full verdict, denials, artifacts, response text\n agy_logs -> only if you need the raw or normalized stream itself,\n by cursor or tail\n\nTwo things that will mislead you if you forget them\n 1. Do not trust agy's self-report. Permission denials and sandbox blocks both\n come back as exit 0 with status SUCCESS. Read outcome and contract_status\n from the broker instead; agent_report is an unverified claim, never the\n basis for outcome.\n 2. verified_success means checks actually passed. success_unverified means\n nothing was checkable. blocked means something was denied (or, on a job\n that ran sandboxed, silently refused by the OS sandbox). Treat blocked as\n \"did not do what you asked,\" not as a lesser success.\n\nWhen a job is blocked\n agy_result -> verification.blockers[] is one list of everything that stood\n in the way. Read two fields first: \"actionable\" says whether a different\n agy_start can lift it, and \"remedy\" says what to change (a rule string like\n \"command(python -m pytest)\" for our gate; a rule the project ceiling lacks\n needs a human to widen the ceiling file, not agy_start).\n actionable: false means no agy_start argument will help \u2014 the command tried\n to leave the workspace, a subagent tool was used (subagents run outside this\n job's policy and are always denied), the ceiling refused the rule, or the\n ceiling forces the sandbox on. \"source\" says who refused: policy_ceiling,\n gate, agy_engine, sandbox, broker, tool_error. Our gate is the sole approval\n authority for a job (agy's own approval engine is disabled), so an\n agy_engine entry on a current job means something regressed \u2014 report it.\n Sandbox: allowed commands run WITHOUT agy's OS sandbox on general_worker\n (0.2.1) \u2014 the gate's allow list is the boundary, as in Claude Code \u2014 so an\n \"Operation not permitted\" there is the command's own failure and comes back\n as a warning, not a blocker. research_readonly is always sandboxed; a project\n can set sandbox: \"seatbelt\" (our write-only profile: writes stay inside the\n workspace and the ceiling's write_roots, builds still run) or sandbox: \"agy\"\n in its ceiling file, and the parent can raise it per job with\n permissions.sandbox. On agy 1.1.24 an agy-sandboxed job cannot\n write inside the workspace from a shell (builds, tests, git commit fail),\n though agy's own file tools still edit files. agy_start reports the same\n shape before a job runs:\n its policy_summary and blockers[] tell you if the ceiling rejected your\n permissions.allow. Note allow_count: 0 \u2014 a fully rejected request drops the\n profile's own defaults with it, and the fix is to start again with no\n permissions.allow at all.\n\nWhat is not possible\n You cannot interrupt or redirect a turn that is already running. agy_send\n only queues: the current turn finishes first, then the queued one runs, on a\n job started with session_mode \"session\".\n Sessions resume losslessly, so a finished oneshot job can be continued at\n any time with agy_start({ session_id }) \u2014 no need to keep a session-mode job\n running just to preserve context.\n\nBefore agy_start on general_worker (measured 2026-09-08 across 35 real jobs)\n - Give it something checkable: verify_command (the build or test command),\n expected_artifacts, or json_schema. Without one the best possible outcome\n is success_unverified and you will end up re-running the checks yourself;\n 12 of those 35 jobs went exactly that way.\n - Every shell command your prompt asks for must match the effective allow\n list (agy_capabilities: profiles[].allow plus ceiling.allow; a rule on\n profiles[].deny opens only through ceiling.exceptions). A prompt\n that says \"run JAVA_HOME=... ./gradlew\" against a list that only allows\n \"./gradlew\" guarantees a blocked job. Check with agy_start({ dry_run: true, expected_commands: [...] }).\n - One writing job per cwd at a time (a cwd_write lock; a second agy_start\n fails with LOCK_CONFLICT). For parallel work, give each job its own git\n worktree inside the project root as cwd.\n - When a blocker's remedy needs the ceiling (actionable: false, source\n policy_ceiling, or a rule the ceiling lacks), report the rule string and\n the ceiling file path (agy_capabilities -> ceiling.path) to the user\n verbatim and stop. Do not work around it by telling the next job not to\n run that command \u2014 that only moves the check back onto you. To propose a\n ceiling, call agy_ceiling (history + validation of a draft) and show the\n user the draft; never write policy.json without their explicit approval\n of that draft in the current conversation.\n - A warning starting \"no project ceiling at\" (agy_capabilities,\n agy_start, agy_result) means this project has no ceiling file and jobs\n get the profile as shipped. Not an error; do not repeat it to the user on\n every call. Mention it once when a job needs more than the profile\n allows, or when the user asks why something was denied: they can run\n /agy-ceiling themselves, or you can draft one via agy_ceiling / the\n agy-ceiling skill. Either way the user approves before anything is\n written.\n\nSaving quota\n agy_start({ dry_run: true }) resolves cwd, profile, policy and argv and\n returns the effective config without launching agy or spending quota. Use it\n to settle permissions before a real run, especially after a blocked job.\n\nverify_command\n agy_start({ verify_command }) runs a command once, after agy exits normally,\n against the final workspace state \u2014 outside the model's own decisions\n entirely; it cannot be skipped, reordered, or narrowed from inside the job.\n It is not a sandboxing feature: it runs as the user, unsandboxed, exactly the\n way the parent agent running the command itself would. A non-zero exit or a\n verify_timeout_ms timeout makes outcome \"failed\" \u2014 never a blocker, since\n nothing was refused. agy_result's verification.verify carries\n { command, exit_code, signal, duration_ms, timed_out, output_tail }, where\n output_tail is only the last 2 KiB; the full log is\n jobs/<job_id>/verify.log on disk. verify: null means no verify_command was\n configured (or the job's own deadline killed agy first, which skips verify\n entirely).";
|
|
13
|
+
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/server/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,y4NA4GnB,CAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server instructions — returned in the MCP `initialize` response and injected
|
|
3
|
+
* into the calling agent's system prompt.
|
|
4
|
+
*
|
|
5
|
+
* This is the top layer of the three-layer tool surface:
|
|
6
|
+
* this file sets orientation and the traps that matter before the first call;
|
|
7
|
+
* per-tool `description` and per-parameter `.describe()` carry the call-by-call
|
|
8
|
+
* detail; `contract/errors.ts` carries the in-the-moment repair. Two rules for
|
|
9
|
+
* whoever edits it: never describe a capability that does not exist, and never
|
|
10
|
+
* omit a trap that does.
|
|
11
|
+
*/
|
|
12
|
+
export const SERVER_INSTRUCTIONS = `agy-worker-mcp runs the Google Antigravity CLI (agy) as an asynchronous worker.
|
|
13
|
+
Jobs are detached: they survive this connection, and any client attached to the
|
|
14
|
+
same project can observe, wait on, or take over any job — including one another
|
|
15
|
+
client started.
|
|
16
|
+
|
|
17
|
+
Recommended flow
|
|
18
|
+
agy_capabilities -> profiles, models, limits, discovered project root
|
|
19
|
+
agy_start -> returns a job_id immediately; nothing blocks
|
|
20
|
+
agy_wait -> loop: call again with the returned cursor until
|
|
21
|
+
lifecycle is "finished" (wait_ms: 0 = snapshot, no block)
|
|
22
|
+
agy_result -> full verdict, denials, artifacts, response text
|
|
23
|
+
agy_logs -> only if you need the raw or normalized stream itself,
|
|
24
|
+
by cursor or tail
|
|
25
|
+
|
|
26
|
+
Two things that will mislead you if you forget them
|
|
27
|
+
1. Do not trust agy's self-report. Permission denials and sandbox blocks both
|
|
28
|
+
come back as exit 0 with status SUCCESS. Read outcome and contract_status
|
|
29
|
+
from the broker instead; agent_report is an unverified claim, never the
|
|
30
|
+
basis for outcome.
|
|
31
|
+
2. verified_success means checks actually passed. success_unverified means
|
|
32
|
+
nothing was checkable. blocked means something was denied (or, on a job
|
|
33
|
+
that ran sandboxed, silently refused by the OS sandbox). Treat blocked as
|
|
34
|
+
"did not do what you asked," not as a lesser success.
|
|
35
|
+
|
|
36
|
+
When a job is blocked
|
|
37
|
+
agy_result -> verification.blockers[] is one list of everything that stood
|
|
38
|
+
in the way. Read two fields first: "actionable" says whether a different
|
|
39
|
+
agy_start can lift it, and "remedy" says what to change (a rule string like
|
|
40
|
+
"command(python -m pytest)" for our gate; a rule the project ceiling lacks
|
|
41
|
+
needs a human to widen the ceiling file, not agy_start).
|
|
42
|
+
actionable: false means no agy_start argument will help — the command tried
|
|
43
|
+
to leave the workspace, a subagent tool was used (subagents run outside this
|
|
44
|
+
job's policy and are always denied), the ceiling refused the rule, or the
|
|
45
|
+
ceiling forces the sandbox on. "source" says who refused: policy_ceiling,
|
|
46
|
+
gate, agy_engine, sandbox, broker, tool_error. Our gate is the sole approval
|
|
47
|
+
authority for a job (agy's own approval engine is disabled), so an
|
|
48
|
+
agy_engine entry on a current job means something regressed — report it.
|
|
49
|
+
Sandbox: allowed commands run WITHOUT agy's OS sandbox on general_worker
|
|
50
|
+
(0.2.1) — the gate's allow list is the boundary, as in Claude Code — so an
|
|
51
|
+
"Operation not permitted" there is the command's own failure and comes back
|
|
52
|
+
as a warning, not a blocker. research_readonly is always sandboxed; a project
|
|
53
|
+
can set sandbox: "seatbelt" (our write-only profile: writes stay inside the
|
|
54
|
+
workspace and the ceiling's write_roots, builds still run) or sandbox: "agy"
|
|
55
|
+
in its ceiling file, and the parent can raise it per job with
|
|
56
|
+
permissions.sandbox. On agy 1.1.24 an agy-sandboxed job cannot
|
|
57
|
+
write inside the workspace from a shell (builds, tests, git commit fail),
|
|
58
|
+
though agy's own file tools still edit files. agy_start reports the same
|
|
59
|
+
shape before a job runs:
|
|
60
|
+
its policy_summary and blockers[] tell you if the ceiling rejected your
|
|
61
|
+
permissions.allow. Note allow_count: 0 — a fully rejected request drops the
|
|
62
|
+
profile's own defaults with it, and the fix is to start again with no
|
|
63
|
+
permissions.allow at all.
|
|
64
|
+
|
|
65
|
+
What is not possible
|
|
66
|
+
You cannot interrupt or redirect a turn that is already running. agy_send
|
|
67
|
+
only queues: the current turn finishes first, then the queued one runs, on a
|
|
68
|
+
job started with session_mode "session".
|
|
69
|
+
Sessions resume losslessly, so a finished oneshot job can be continued at
|
|
70
|
+
any time with agy_start({ session_id }) — no need to keep a session-mode job
|
|
71
|
+
running just to preserve context.
|
|
72
|
+
|
|
73
|
+
Before agy_start on general_worker (measured 2026-09-08 across 35 real jobs)
|
|
74
|
+
- Give it something checkable: verify_command (the build or test command),
|
|
75
|
+
expected_artifacts, or json_schema. Without one the best possible outcome
|
|
76
|
+
is success_unverified and you will end up re-running the checks yourself;
|
|
77
|
+
12 of those 35 jobs went exactly that way.
|
|
78
|
+
- Every shell command your prompt asks for must match the effective allow
|
|
79
|
+
list (agy_capabilities: profiles[].allow plus ceiling.allow; a rule on
|
|
80
|
+
profiles[].deny opens only through ceiling.exceptions). A prompt
|
|
81
|
+
that says "run JAVA_HOME=... ./gradlew" against a list that only allows
|
|
82
|
+
"./gradlew" guarantees a blocked job. Check with agy_start({ dry_run: true, expected_commands: [...] }).
|
|
83
|
+
- One writing job per cwd at a time (a cwd_write lock; a second agy_start
|
|
84
|
+
fails with LOCK_CONFLICT). For parallel work, give each job its own git
|
|
85
|
+
worktree inside the project root as cwd.
|
|
86
|
+
- When a blocker's remedy needs the ceiling (actionable: false, source
|
|
87
|
+
policy_ceiling, or a rule the ceiling lacks), report the rule string and
|
|
88
|
+
the ceiling file path (agy_capabilities -> ceiling.path) to the user
|
|
89
|
+
verbatim and stop. Do not work around it by telling the next job not to
|
|
90
|
+
run that command — that only moves the check back onto you. To propose a
|
|
91
|
+
ceiling, call agy_ceiling (history + validation of a draft) and show the
|
|
92
|
+
user the draft; never write policy.json without their explicit approval
|
|
93
|
+
of that draft in the current conversation.
|
|
94
|
+
- A warning starting "no project ceiling at" (agy_capabilities,
|
|
95
|
+
agy_start, agy_result) means this project has no ceiling file and jobs
|
|
96
|
+
get the profile as shipped. Not an error; do not repeat it to the user on
|
|
97
|
+
every call. Mention it once when a job needs more than the profile
|
|
98
|
+
allows, or when the user asks why something was denied: they can run
|
|
99
|
+
/agy-ceiling themselves, or you can draft one via agy_ceiling / the
|
|
100
|
+
agy-ceiling skill. Either way the user approves before anything is
|
|
101
|
+
written.
|
|
102
|
+
|
|
103
|
+
Saving quota
|
|
104
|
+
agy_start({ dry_run: true }) resolves cwd, profile, policy and argv and
|
|
105
|
+
returns the effective config without launching agy or spending quota. Use it
|
|
106
|
+
to settle permissions before a real run, especially after a blocked job.
|
|
107
|
+
|
|
108
|
+
verify_command
|
|
109
|
+
agy_start({ verify_command }) runs a command once, after agy exits normally,
|
|
110
|
+
against the final workspace state — outside the model's own decisions
|
|
111
|
+
entirely; it cannot be skipped, reordered, or narrowed from inside the job.
|
|
112
|
+
It is not a sandboxing feature: it runs as the user, unsandboxed, exactly the
|
|
113
|
+
way the parent agent running the command itself would. A non-zero exit or a
|
|
114
|
+
verify_timeout_ms timeout makes outcome "failed" — never a blocker, since
|
|
115
|
+
nothing was refused. agy_result's verification.verify carries
|
|
116
|
+
{ command, exit_code, signal, duration_ms, timed_out, output_tail }, where
|
|
117
|
+
output_tail is only the last 2 KiB; the full log is
|
|
118
|
+
jobs/<job_id>/verify.log on disk. verify: null means no verify_command was
|
|
119
|
+
configured (or the job's own deadline killed agy first, which skips verify
|
|
120
|
+
entirely).`;
|
|
121
|
+
//# sourceMappingURL=instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../src/server/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4GtB,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ToolContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* The stdio MCP server. One process per connected client —
|
|
5
|
+
* there is no daemon and no shared broker; coordination happens entirely through
|
|
6
|
+
* the project SQLite database and the job directories.
|
|
7
|
+
*
|
|
8
|
+
* Capabilities are `{ tools: {} }` only. No resources, no prompts.
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateServerOptions {
|
|
11
|
+
ctx: ToolContext;
|
|
12
|
+
version: string;
|
|
13
|
+
}
|
|
14
|
+
/** Build the server and register the nine tools. */
|
|
15
|
+
export declare function createServer(opts: CreateServerOptions): McpServer;
|
|
16
|
+
/** `dist/server.js` entry point. Connects a `StdioServerTransport` and stays up. */
|
|
17
|
+
export declare function main(): Promise<void>;
|
|
18
|
+
/** Close the store and flush state. Registered on SIGINT/SIGTERM. */
|
|
19
|
+
export declare function shutdown(ctx: ToolContext): void;
|
|
20
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAK/C;;;;;;GAMG;AAEH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,WAAW,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,CAwBjE;AAED,oFAAoF;AACpF,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB1C;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAM/C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createContext } from './context.js';
|
|
4
|
+
import { registerAllTools } from './tools/index.js';
|
|
5
|
+
import { SERVER_INSTRUCTIONS } from './instructions.js';
|
|
6
|
+
/** Build the server and register the nine tools. */
|
|
7
|
+
export function createServer(opts) {
|
|
8
|
+
const server = new McpServer({ name: 'agy-worker-mcp', version: opts.version }, {
|
|
9
|
+
capabilities: { tools: {} },
|
|
10
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
11
|
+
});
|
|
12
|
+
// What the client declared at `initialize`. The SDK only fills these in once
|
|
13
|
+
// the handshake completed, so read them per call rather than snapshotting at
|
|
14
|
+
// construction time.
|
|
15
|
+
opts.ctx.getClient = () => {
|
|
16
|
+
const impl = server.server.getClientVersion();
|
|
17
|
+
const caps = server.server.getClientCapabilities();
|
|
18
|
+
if (!impl && !caps)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
name: impl?.name ?? null,
|
|
22
|
+
version: impl?.version ?? null,
|
|
23
|
+
capabilities: caps ? caps : null,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
registerAllTools(server, opts.ctx);
|
|
27
|
+
return server;
|
|
28
|
+
}
|
|
29
|
+
/** `dist/server.js` entry point. Connects a `StdioServerTransport` and stays up. */
|
|
30
|
+
export async function main() {
|
|
31
|
+
const ctx = createContext();
|
|
32
|
+
const server = createServer({ ctx, version: ctx.version });
|
|
33
|
+
let shuttingDown = false;
|
|
34
|
+
const handleSignal = () => {
|
|
35
|
+
if (shuttingDown)
|
|
36
|
+
return;
|
|
37
|
+
shuttingDown = true;
|
|
38
|
+
shutdown(ctx);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
};
|
|
41
|
+
process.on('SIGINT', handleSignal);
|
|
42
|
+
process.on('SIGTERM', handleSignal);
|
|
43
|
+
const transport = new StdioServerTransport();
|
|
44
|
+
await server.connect(transport);
|
|
45
|
+
}
|
|
46
|
+
/** Close the store and flush state. Registered on SIGINT/SIGTERM. */
|
|
47
|
+
export function shutdown(ctx) {
|
|
48
|
+
try {
|
|
49
|
+
ctx.store.close();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// best effort — the process is exiting either way
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAGhF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAevD,oDAAoD;AACpD,MAAM,UAAU,YAAY,CAAC,IAAyB;IACpD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EACjD;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,YAAY,EAAE,mBAAmB;KAClC,CACF,CAAA;IACD,6EAA6E;IAC7E,6EAA6E;IAC7E,qBAAqB;IACrB,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE;QACxB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAA;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAA;QAClD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QAC/B,OAAO;YACL,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI;YACxB,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,IAAI;YAC9B,YAAY,EAAE,IAAI,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,IAAI;SAC9D,CAAA;IACH,CAAC,CAAA;IAED,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IAClC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,GAAG,GAAG,aAAa,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;IAE1D,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,MAAM,YAAY,GAAG,GAAS,EAAE;QAC9B,IAAI,YAAY;YAAE,OAAM;QACxB,YAAY,GAAG,IAAI,CAAA;QACnB,QAAQ,CAAC,GAAG,CAAC,CAAA;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAA;IACD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;IAClC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAEnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,QAAQ,CAAC,GAAgB;IACvC,IAAI,CAAC;QACH,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolContext, type ToolReply } from '../context.js';
|
|
3
|
+
/**
|
|
4
|
+
* `agy_cancel` — kill a running job and everything it spawned.
|
|
5
|
+
*
|
|
6
|
+
* Signals the process group, not the pid, so agy's own children die too
|
|
7
|
+
* (verified by checking `pgrep -g` afterwards).
|
|
8
|
+
*/
|
|
9
|
+
export declare const cancelInput: z.ZodObject<{
|
|
10
|
+
job_id: z.ZodString;
|
|
11
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
12
|
+
grace_ms: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
job_id: string;
|
|
15
|
+
reason?: string | undefined;
|
|
16
|
+
grace_ms?: number | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
job_id: string;
|
|
19
|
+
reason?: string | undefined;
|
|
20
|
+
grace_ms?: number | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
export type CancelInput = z.infer<typeof cancelInput>;
|
|
23
|
+
export declare function handleCancel(ctx: ToolContext, input: CancelInput): Promise<ToolReply>;
|
|
24
|
+
//# sourceMappingURL=cancel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancel.d.ts","sourceRoot":"","sources":["../../../src/server/tools/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAA;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;EAStB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAErD,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAyC3F"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { reconcile } from '../../broker/reconcile.js';
|
|
3
|
+
import { isPidAlive, isSameProcess, killProcessGroup } from '../../runner/reap.js';
|
|
4
|
+
import { getJob, updateJob } from '../../store/jobs.js';
|
|
5
|
+
import { errorReply, reply } from '../context.js';
|
|
6
|
+
/**
|
|
7
|
+
* `agy_cancel` — kill a running job and everything it spawned.
|
|
8
|
+
*
|
|
9
|
+
* Signals the process group, not the pid, so agy's own children die too
|
|
10
|
+
* (verified by checking `pgrep -g` afterwards).
|
|
11
|
+
*/
|
|
12
|
+
export const cancelInput = z.object({
|
|
13
|
+
job_id: z.string(),
|
|
14
|
+
reason: z.string().optional(),
|
|
15
|
+
grace_ms: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(0)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Milliseconds between SIGTERM and SIGKILL.'),
|
|
21
|
+
});
|
|
22
|
+
export async function handleCancel(ctx, input) {
|
|
23
|
+
try {
|
|
24
|
+
await reconcile(ctx.store);
|
|
25
|
+
let job = getJob(ctx.store, input.job_id);
|
|
26
|
+
if (job.lifecycle === 'finished') {
|
|
27
|
+
return reply({ job_id: job.job_id, lifecycle: job.lifecycle, killed: false, message: 'already finished' });
|
|
28
|
+
}
|
|
29
|
+
// Only signal the recorded pgid when we can confirm it still identifies
|
|
30
|
+
// *our* process: a null proc_start_time (nothing was ever recorded) or a pid
|
|
31
|
+
// that no longer matches it means the pgid may have been recycled by an
|
|
32
|
+
// unrelated process tree since (finding 6) — do not kill in that case.
|
|
33
|
+
let killed = false;
|
|
34
|
+
if (job.pgid !== null &&
|
|
35
|
+
job.pid !== null &&
|
|
36
|
+
job.proc_start_time !== null &&
|
|
37
|
+
isPidAlive(job.pid) &&
|
|
38
|
+
isSameProcess(job.pid, job.proc_start_time)) {
|
|
39
|
+
killed = await killProcessGroup(job.pgid, { graceMs: input.grace_ms });
|
|
40
|
+
}
|
|
41
|
+
job = updateJob(ctx.store, job.job_id, { lifecycle: 'canceling' });
|
|
42
|
+
// The runner still owns writing exit_code; the next reconcile finalizes
|
|
43
|
+
// this as `canceled` once it appears.
|
|
44
|
+
await reconcile(ctx.store);
|
|
45
|
+
job = getJob(ctx.store, job.job_id);
|
|
46
|
+
return reply({
|
|
47
|
+
job_id: job.job_id,
|
|
48
|
+
lifecycle: job.lifecycle,
|
|
49
|
+
outcome: job.outcome,
|
|
50
|
+
killed,
|
|
51
|
+
pgid: job.pgid,
|
|
52
|
+
reason: input.reason ?? null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
return errorReply(e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=cancel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancel.js","sourceRoot":"","sources":["../../../src/server/tools/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAoC,MAAM,eAAe,CAAA;AAEnF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,2CAA2C,CAAC;CACzD,CAAC,CAAA;AAIF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAgB,EAAE,KAAkB;IACrE,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAEzC,IAAI,GAAG,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAA;QAC5G,CAAC;QAED,wEAAwE;QACxE,6EAA6E;QAC7E,wEAAwE;QACxE,uEAAuE;QACvE,IAAI,MAAM,GAAG,KAAK,CAAA;QAClB,IACE,GAAG,CAAC,IAAI,KAAK,IAAI;YACjB,GAAG,CAAC,GAAG,KAAK,IAAI;YAChB,GAAG,CAAC,eAAe,KAAK,IAAI;YAC5B,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;YACnB,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,eAAe,CAAC,EAC3C,CAAC;YACD,MAAM,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;QACxE,CAAC;QAED,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;QAClE,wEAAwE;QACxE,sCAAsC;QACtC,MAAM,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC1B,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;QAEnC,OAAO,KAAK,CAAC;YACX,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM;YACN,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;SAC7B,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,UAAU,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC;AACH,CAAC"}
|