@oddessentials/agent-guild 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -15
- package/bin/agent-guild-report.mjs +42 -20
- package/config/providers.default.json +4 -0
- package/examples/grok-hooks.json +43 -0
- package/package.json +1 -1
- package/src/manager/command-resolver.mjs +6 -2
- package/src/manager/config.mjs +2 -0
- package/src/manager/main.mjs +9 -2
- package/src/manager/providers.mjs +10 -3
- package/src/manager/report-shims.mjs +2 -0
- package/src/manager/server.mjs +19 -3
- package/src/manager/session-hooks.mjs +350 -0
- package/src/manager/session-manager.mjs +57 -19
- package/src/manager/session.mjs +240 -8
- package/src/report/hooks.mjs +79 -9
- package/web/app.js +90 -12
- package/web/index.html +6 -0
- package/web/skins/grove/characters/anthropic/idle.avif +0 -0
- package/web/skins/grove/characters/anthropic/idle.webp +0 -0
- package/web/skins/grove/characters/anthropic/locked.avif +0 -0
- package/web/skins/grove/characters/anthropic/locked.webp +0 -0
- package/web/skins/grove/characters/anthropic/working.avif +0 -0
- package/web/skins/grove/characters/anthropic/working.webp +0 -0
- package/web/skins/grove/characters/google/idle.avif +0 -0
- package/web/skins/grove/characters/google/idle.webp +0 -0
- package/web/skins/grove/characters/google/locked.avif +0 -0
- package/web/skins/grove/characters/google/locked.webp +0 -0
- package/web/skins/grove/characters/google/working.avif +0 -0
- package/web/skins/grove/characters/google/working.webp +0 -0
- package/web/skins/grove/characters/openai/idle.avif +0 -0
- package/web/skins/grove/characters/openai/idle.webp +0 -0
- package/web/skins/grove/characters/openai/locked.avif +0 -0
- package/web/skins/grove/characters/openai/locked.webp +0 -0
- package/web/skins/grove/characters/openai/working.avif +0 -0
- package/web/skins/grove/characters/openai/working.webp +0 -0
- package/web/skins/grove/characters/shell/idle.avif +0 -0
- package/web/skins/grove/characters/shell/idle.webp +0 -0
- package/web/skins/grove/characters/shell/locked.avif +0 -0
- package/web/skins/grove/characters/shell/locked.webp +0 -0
- package/web/skins/grove/characters/shell/working.avif +0 -0
- package/web/skins/grove/characters/shell/working.webp +0 -0
- package/web/skins/grove/characters/xai/idle.avif +0 -0
- package/web/skins/grove/characters/xai/idle.webp +0 -0
- package/web/skins/grove/characters/xai/locked.avif +0 -0
- package/web/skins/grove/characters/xai/locked.webp +0 -0
- package/web/skins/grove/characters/xai/working.avif +0 -0
- package/web/skins/grove/characters/xai/working.webp +0 -0
- package/web/skins/grove/familiars/aether.png +0 -0
- package/web/skins/grove/familiars/flame.png +0 -0
- package/web/skins/grove/familiars/leaf.png +0 -0
- package/web/skins/grove/familiars/night.png +0 -0
- package/web/skins/grove/fonts/OFL.txt +93 -0
- package/web/skins/grove/fonts/fraunces.woff2 +0 -0
- package/web/skins/grove/icons/anthropic.avif +0 -0
- package/web/skins/grove/icons/anthropic.webp +0 -0
- package/web/skins/grove/icons/google.avif +0 -0
- package/web/skins/grove/icons/google.webp +0 -0
- package/web/skins/grove/icons/openai.avif +0 -0
- package/web/skins/grove/icons/openai.webp +0 -0
- package/web/skins/grove/icons/shell.avif +0 -0
- package/web/skins/grove/icons/shell.webp +0 -0
- package/web/skins/grove/icons/xai.avif +0 -0
- package/web/skins/grove/icons/xai.webp +0 -0
- package/web/skins/grove/page-light.avif +0 -0
- package/web/skins/grove/page-light.webp +0 -0
- package/web/skins/grove/page.avif +0 -0
- package/web/skins/grove/page.webp +0 -0
- package/web/skins/grove/skin.css +395 -0
- package/web/skins/grove/ui/card-light.avif +0 -0
- package/web/skins/grove/ui/card-light.webp +0 -0
- package/web/skins/grove/ui/card.avif +0 -0
- package/web/skins/grove/ui/card.webp +0 -0
- package/web/skins/grove/ui/drop.svg +1 -0
- package/web/skins/grove/ui/empty-state.avif +0 -0
- package/web/skins/grove/ui/empty-state.webp +0 -0
- package/web/skins/grove/ui/leaf.svg +1 -0
- package/web/skins/grove/ui/lock.svg +1 -0
- package/web/skins/grove/ui/sprig.svg +1 -0
- package/web/skins/grove/ui/stone.png +0 -0
- package/web/styles.css +10 -2
- package/web/theme.js +1 -0
package/README.md
CHANGED
|
@@ -102,7 +102,8 @@ vX.Y.Z** switches over when you are ready. The version badge opens
|
|
|
102
102
|
**Appearance.** The Appearance menu in the top bar picks a skin and light or
|
|
103
103
|
dark mode, without a reload. **Guild** is the default fantasy look;
|
|
104
104
|
**Professional** is a plain business look with no characters; **Orbital**
|
|
105
|
-
puts a crew of little robots in deep space
|
|
105
|
+
puts a crew of little robots in deep space; **Grove** is a calm moss garden
|
|
106
|
+
of gentle nature spirits. The page
|
|
106
107
|
follows your system's light or dark setting until you pick one. See
|
|
107
108
|
[docs/SKINS.md](docs/SKINS.md) to make another.
|
|
108
109
|
|
|
@@ -139,20 +140,18 @@ The full reference, including every field and environment variable, is in
|
|
|
139
140
|
|
|
140
141
|
## Show agents and models
|
|
141
142
|
|
|
142
|
-
Agents are reported by the coding tool, not guessed from its output.
|
|
143
|
-
|
|
144
|
-
Claude Code
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
when it starts, or run `/hooks`.
|
|
155
|
-
* Claude Code runs none until you accept its workspace-trust prompt.
|
|
143
|
+
Agents are reported by the coding tool's hooks, not guessed from its output.
|
|
144
|
+
Each helper agent appears on the card while it runs. Agent Guild gives every
|
|
145
|
+
Claude Code and Codex CLI session its reporting hooks for that session only,
|
|
146
|
+
without changing the tool's own settings. Gemini CLI has no such option: turn
|
|
147
|
+
on **Agent reporting** on its card once, which links an Agent Guild extension
|
|
148
|
+
into Gemini. Grok Build cannot take hooks for one session yet; add the hooks
|
|
149
|
+
from [examples/grok-hooks.json](examples/grok-hooks.json) to see its agents.
|
|
150
|
+
When a tool's hooks do not run, because hooks are turned off, restricted by
|
|
151
|
+
an administrator or not trusted for the folder, the card says so.
|
|
152
|
+
|
|
153
|
+
The card also names the model, from the hooks, from `--model` or from the
|
|
154
|
+
tool's screen.
|
|
156
155
|
|
|
157
156
|
Any other tool or script can report agents and its model too. See
|
|
158
157
|
[docs/agent-reporting.md](docs/agent-reporting.md).
|
|
@@ -16,14 +16,28 @@
|
|
|
16
16
|
// (reads Claude Code status line JSON on stdin; prints a
|
|
17
17
|
// status line, or the JSON itself with --passthrough)
|
|
18
18
|
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
19
21
|
import { hookToReports, claudeStatuslineToReport, formatStatusLine } from '../src/report/hooks.mjs';
|
|
20
22
|
|
|
23
|
+
// Importing node:http as an ES module costs a hook about 50 ms more than requiring it.
|
|
24
|
+
const http = createRequire(import.meta.url)('node:http');
|
|
21
25
|
const env = process.env;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
function reportToken() {
|
|
28
|
+
if (env.AGENT_GUILD_REPORT_TOKEN) return env.AGENT_GUILD_REPORT_TOKEN;
|
|
29
|
+
if (!env.AGENT_GUILD_REPORT_FILE) return null;
|
|
30
|
+
try {
|
|
31
|
+
return fs.readFileSync(env.AGENT_GUILD_REPORT_FILE, 'utf8').trim() || null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const token = env.AGENT_GUILD_URL && env.AGENT_GUILD_SESSION_ID ? reportToken() : null;
|
|
38
|
+
const inSession = Boolean(token);
|
|
39
|
+
if (!inSession && env.AGENT_GUILD_SESSION_ID) {
|
|
40
|
+
console.error('agent-guild-report: the session report token is missing; the tool may have removed AGENT_GUILD_REPORT_TOKEN and AGENT_GUILD_REPORT_FILE from the environment, or the session has ended');
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
const USAGE = `Usage: agent-guild-report <agent-id> [--name N] [--status working|waiting|idle|done] [--detail TEXT] [--kind KIND] [--remove]
|
|
@@ -61,23 +75,31 @@ function readStdin() {
|
|
|
61
75
|
});
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
const kind = report.
|
|
78
|
+
function send(report) {
|
|
79
|
+
const kind = report.shell !== undefined ? 'shells'
|
|
80
|
+
: report.agentId !== undefined || report.finishForeground === true ? 'agents'
|
|
81
|
+
: report.hello === true ? 'reporting'
|
|
66
82
|
: report.toolSessionId !== undefined ? 'tool-session' : 'model';
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
const body = JSON.stringify(report);
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const req = http.request(`${env.AGENT_GUILD_URL}/api/v1/sessions/${env.AGENT_GUILD_SESSION_ID}/${kind}`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
'Content-Length': Buffer.byteLength(body),
|
|
90
|
+
'X-Agent-Guild-Report-Token': token,
|
|
91
|
+
},
|
|
92
|
+
timeout: 3000,
|
|
93
|
+
}, (res) => {
|
|
94
|
+
let text = '';
|
|
95
|
+
res.setEncoding('utf8');
|
|
96
|
+
res.on('data', (chunk) => { text += chunk; });
|
|
97
|
+
res.on('end', () => (res.statusCode >= 200 && res.statusCode < 300 ? resolve() : reject(new Error(`HTTP ${res.statusCode}: ${text}`))));
|
|
98
|
+
});
|
|
99
|
+
req.on('timeout', () => req.destroy(new Error('the manager did not answer in time')));
|
|
100
|
+
req.on('error', reject);
|
|
101
|
+
req.end(body);
|
|
76
102
|
});
|
|
77
|
-
if (!res.ok) {
|
|
78
|
-
const body = await res.text();
|
|
79
|
-
throw new Error(`HTTP ${res.status}: ${body}`);
|
|
80
|
-
}
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
/** A hook must never break the coding tool, so report failures quietly. */
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"versionArgs": ["--version"],
|
|
10
10
|
"usage": "claude",
|
|
11
11
|
"history": "claude",
|
|
12
|
+
"reporting": "claude",
|
|
12
13
|
"homeVar": "CLAUDE_CONFIG_DIR",
|
|
13
14
|
"accountEnv": { "CLAUDE_SECURESTORAGE_CONFIG_DIR": "{dir}" },
|
|
14
15
|
"hooks": { "path": "settings.json", "example": "claude-code-settings.json" },
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"versionArgs": ["--version"],
|
|
47
48
|
"usage": "codex",
|
|
48
49
|
"history": "codex",
|
|
50
|
+
"reporting": "codex",
|
|
49
51
|
"homeVar": "CODEX_HOME",
|
|
50
52
|
"hooks": { "path": "hooks.json", "example": "codex-hooks.json" },
|
|
51
53
|
"modelPattern": "\\bgpt-\\d[a-z0-9.-]*",
|
|
@@ -80,6 +82,7 @@
|
|
|
80
82
|
"versionArgs": ["--version"],
|
|
81
83
|
"usage": "gemini",
|
|
82
84
|
"history": "gemini",
|
|
85
|
+
"reporting": "gemini",
|
|
83
86
|
"homeVar": "GEMINI_CLI_HOME",
|
|
84
87
|
"accountEnv": { "GEMINI_FORCE_FILE_STORAGE": "true" },
|
|
85
88
|
"hooks": { "path": ".gemini/settings.json", "example": "gemini-settings.json" },
|
|
@@ -104,6 +107,7 @@
|
|
|
104
107
|
"package": "@xai-official/grok",
|
|
105
108
|
"versionArgs": ["--version"],
|
|
106
109
|
"history": "grok",
|
|
110
|
+
"reporting": "grok",
|
|
107
111
|
"homeVar": "GROK_HOME",
|
|
108
112
|
"hooks": { "path": "hooks/agent-guild.json", "example": "grok-hooks.json" },
|
|
109
113
|
"modelPattern": "\\bgrok-(?:build|\\d)[a-z0-9.-]*",
|
package/examples/grok-hooks.json
CHANGED
|
@@ -50,6 +50,49 @@
|
|
|
50
50
|
}
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
|
+
],
|
|
54
|
+
"PreToolUse": [
|
|
55
|
+
{
|
|
56
|
+
"matcher": "run_terminal_command",
|
|
57
|
+
"hooks": [
|
|
58
|
+
{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "agent-guild-report --hook"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"PostToolUse": [
|
|
66
|
+
{
|
|
67
|
+
"matcher": "run_terminal_command",
|
|
68
|
+
"hooks": [
|
|
69
|
+
{
|
|
70
|
+
"type": "command",
|
|
71
|
+
"command": "agent-guild-report --hook"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"PostToolUseFailure": [
|
|
77
|
+
{
|
|
78
|
+
"matcher": "run_terminal_command",
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "agent-guild-report --hook"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"Stop": [
|
|
88
|
+
{
|
|
89
|
+
"hooks": [
|
|
90
|
+
{
|
|
91
|
+
"type": "command",
|
|
92
|
+
"command": "agent-guild-report --hook"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
53
96
|
]
|
|
54
97
|
}
|
|
55
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddessentials/agent-guild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Launch and watch AI coding-assistant terminal sessions from one local web page. A bundled session manager owns the terminals so the page can close and reconnect.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -83,6 +83,10 @@ export function resolveAllCommands(command, env = process.env, platform = proces
|
|
|
83
83
|
return hits;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
export function killWindowsTree(pid, done = () => {}) {
|
|
87
|
+
execFile('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, timeout: 5000 }, (err) => done(err));
|
|
88
|
+
}
|
|
89
|
+
|
|
86
90
|
/** Quote one argument for a cmd.exe command line. */
|
|
87
91
|
export function quoteForCmd(arg) {
|
|
88
92
|
const s = String(arg);
|
|
@@ -120,9 +124,9 @@ export function buildSpawnSpec(resolvedPath, args = [], env = process.env, platf
|
|
|
120
124
|
* Run a spawn spec to completion without a terminal. Resolves with its
|
|
121
125
|
* output; rejects with the error carrying stdout and stderr.
|
|
122
126
|
*/
|
|
123
|
-
export function runSpec(spec, { env, timeoutMs = 15000 } = {}) {
|
|
127
|
+
export function runSpec(spec, { env, timeoutMs = 15000, cwd } = {}) {
|
|
124
128
|
return new Promise((resolve, reject) => {
|
|
125
|
-
const opts = { env, timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024 };
|
|
129
|
+
const opts = { env, cwd, timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024 };
|
|
126
130
|
let args = spec.args;
|
|
127
131
|
if (typeof args === 'string') {
|
|
128
132
|
opts.windowsVerbatimArguments = true;
|
package/src/manager/config.mjs
CHANGED
|
@@ -47,6 +47,8 @@ export const paths = {
|
|
|
47
47
|
get log() { return path.join(dataDir(), 'manager.log'); },
|
|
48
48
|
/** Launchers for agent-guild-report, put first on every session's PATH. */
|
|
49
49
|
get shims() { return path.join(dataDir(), 'bin'); },
|
|
50
|
+
get reporting() { return path.join(dataDir(), 'reporting'); },
|
|
51
|
+
get reportTokens() { return path.join(dataDir(), 'report-tokens'); },
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
function writePrivate(file, contents) {
|
package/src/manager/main.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { createManagerServer } from './server.mjs';
|
|
|
16
16
|
import { SelfUpdate } from './self-update.mjs';
|
|
17
17
|
import { resolveBaseEnv, pathReader } from './shell-env.mjs';
|
|
18
18
|
import { writeReportShims } from './report-shims.mjs';
|
|
19
|
+
import { SessionHooks } from './session-hooks.mjs';
|
|
19
20
|
import { launcherPath, spawnManager } from './launch.mjs';
|
|
20
21
|
import {
|
|
21
22
|
DEFAULT_HOST,
|
|
@@ -62,7 +63,12 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
62
63
|
let api;
|
|
63
64
|
const selfUpdate = new SelfUpdate({ pkg: PACKAGE_NAME, version, packageFile, registry });
|
|
64
65
|
const github = new GitHub({ dir: paths.github, registry, ...githubOptions });
|
|
65
|
-
const
|
|
66
|
+
const sessionHooks = new SessionHooks({ registry, dir: paths.reporting, version, shimDir });
|
|
67
|
+
registry.reportingEnabled = (provider, account) => sessionHooks.enabled(provider, account);
|
|
68
|
+
sessionHooks.warm();
|
|
69
|
+
const manager = new SessionManager({
|
|
70
|
+
registry, baseEnv, getApiUrl: () => api.url, sessionDefaults, shimDir, selfUpdate, github, sessionHooks, reportTokenDir: paths.reportTokens,
|
|
71
|
+
});
|
|
66
72
|
const usage = new UsageMonitor({ registry, env: baseEnv });
|
|
67
73
|
const history = new SessionHistory({ registry, env: baseEnv });
|
|
68
74
|
const modelStats = new ModelStats({ registry });
|
|
@@ -71,7 +77,7 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
71
77
|
let closing = null;
|
|
72
78
|
|
|
73
79
|
const refreshVersions = () => {
|
|
74
|
-
registry.refreshVersions().
|
|
80
|
+
registry.refreshVersions().then(() => sessionHooks.warm(), () => {});
|
|
75
81
|
selfUpdate.refresh().catch(() => {});
|
|
76
82
|
};
|
|
77
83
|
const versionTimer = setInterval(refreshVersions, VERSION_REFRESH_MS);
|
|
@@ -141,6 +147,7 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
141
147
|
throw err;
|
|
142
148
|
}
|
|
143
149
|
|
|
150
|
+
manager.sweepReportTokens();
|
|
144
151
|
writeRuntimeFile({
|
|
145
152
|
pid: process.pid,
|
|
146
153
|
host,
|
|
@@ -19,6 +19,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
|
|
|
19
19
|
export const DEFAULTS_FILE = path.resolve(here, '../../config/providers.default.json');
|
|
20
20
|
const ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
21
21
|
const PLATFORM_KEYS = ['win32', 'darwin', 'linux'];
|
|
22
|
+
const REPORTING_MODES = new Set(['claude', 'codex', 'gemini', 'grok']);
|
|
22
23
|
const VERSION_TTL_MS = 60 * 60 * 1000;
|
|
23
24
|
const FAILED_PROBE_TTL_MS = 5 * 60 * 1000;
|
|
24
25
|
const PATH_REFRESH_MS = 60 * 1000;
|
|
@@ -163,6 +164,7 @@ function normalize(raw, platform, warnings) {
|
|
|
163
164
|
versionArgs: Array.isArray(merged.versionArgs) && merged.versionArgs.length ? merged.versionArgs.map(String) : null,
|
|
164
165
|
usage: normalizeUsage(merged.usage),
|
|
165
166
|
history: normalizeHistory(merged.history),
|
|
167
|
+
reporting: REPORTING_MODES.has(merged.reporting) ? merged.reporting : null,
|
|
166
168
|
modelPattern: merged.modelPattern ? String(merged.modelPattern) : null,
|
|
167
169
|
args: Array.isArray(merged.args) ? merged.args.map(String) : [],
|
|
168
170
|
resumeArgs: Array.isArray(merged.resumeArgs) ? merged.resumeArgs.map(String) : [],
|
|
@@ -251,6 +253,7 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
251
253
|
this.versions = new Map();
|
|
252
254
|
this._refreshing = null;
|
|
253
255
|
this._npmRegistry = null;
|
|
256
|
+
this.reportingEnabled = null;
|
|
254
257
|
this.reload();
|
|
255
258
|
}
|
|
256
259
|
|
|
@@ -623,7 +626,11 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
623
626
|
npmNote: provider.npmNote,
|
|
624
627
|
usageSource: provider.usage === null ? null : typeof provider.usage === 'string' ? provider.usage : 'command',
|
|
625
628
|
historySource: provider.history === null ? null : typeof provider.history === 'string' ? provider.history : 'command',
|
|
626
|
-
|
|
629
|
+
reporting: provider.reporting,
|
|
630
|
+
accounts: provider.accounts.map((account) => {
|
|
631
|
+
const reportingEnabled = this.reportingEnabled?.(provider, this.accountFor(provider, account)) ?? null;
|
|
632
|
+
return { id: account.id, label: account.label, ...(reportingEnabled === null ? {} : { reportingEnabled }) };
|
|
633
|
+
}),
|
|
627
634
|
modelPattern: provider.modelPattern,
|
|
628
635
|
color: provider.color,
|
|
629
636
|
monogram: provider.monogram,
|
|
@@ -642,7 +649,7 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
642
649
|
}
|
|
643
650
|
|
|
644
651
|
/** Spawn spec for node-pty, or throws with a user-facing message. */
|
|
645
|
-
spawnSpec(provider, extraArgs = [], resume = null) {
|
|
652
|
+
spawnSpec(provider, extraArgs = [], resume = null, leadArgs = []) {
|
|
646
653
|
const resolved = this.resolve(provider);
|
|
647
654
|
if (!resolved) {
|
|
648
655
|
const hint = provider.install ? ` ${provider.install}` : '';
|
|
@@ -661,7 +668,7 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
661
668
|
}
|
|
662
669
|
resumeArgs = provider.resumeArgs.map((arg) => arg.replaceAll('{id}', resume));
|
|
663
670
|
}
|
|
664
|
-
return buildSpawnSpec(resolved, [...provider.args, ...resumeArgs, ...extraArgs], this.env, this.platform);
|
|
671
|
+
return buildSpawnSpec(resolved, [...leadArgs, ...provider.args, ...resumeArgs, ...extraArgs], this.env, this.platform);
|
|
665
672
|
}
|
|
666
673
|
|
|
667
674
|
async updateSpec(provider) {
|
|
@@ -48,6 +48,7 @@ export function shimContents({ execPath, script, platform = process.platform })
|
|
|
48
48
|
const files = {
|
|
49
49
|
[SHIM_NAME]: [
|
|
50
50
|
'#!/bin/sh',
|
|
51
|
+
'[ "$1" = --hook ] && [ -z "$AGENT_GUILD_SESSION_ID" ] && exec cat >/dev/null',
|
|
51
52
|
`n=${shQuote(forSh(execPath))}`,
|
|
52
53
|
'[ -x "$n" ] || n=node',
|
|
53
54
|
`exec "$n" ${shQuote(forSh(script))} "$@"`,
|
|
@@ -58,6 +59,7 @@ export function shimContents({ execPath, script, platform = process.platform })
|
|
|
58
59
|
files[LOADER_NAME] = `import ${JSON.stringify(fileUrl(script, platform))};\n`;
|
|
59
60
|
files[`${SHIM_NAME}.cmd`] = [
|
|
60
61
|
'@ECHO OFF',
|
|
62
|
+
'IF "%~1"=="--hook" IF NOT DEFINED AGENT_GUILD_SESSION_ID EXIT /B 0',
|
|
61
63
|
'IF EXIST "%AGENT_GUILD_NODE%" GOTO manager',
|
|
62
64
|
`node "%~dp0${LOADER_NAME}" %*`,
|
|
63
65
|
'EXIT /B %ERRORLEVEL%',
|
package/src/manager/server.mjs
CHANGED
|
@@ -207,7 +207,7 @@ export function createManagerServer({
|
|
|
207
207
|
// Agent, model and tool-session reports may authenticate with the
|
|
208
208
|
// per-session report token that the manager injects into each tool's
|
|
209
209
|
// environment.
|
|
210
|
-
const reportMatch = route.match(/^\/sessions\/([a-f0-9]+)\/(agents|model|tool-session)$/);
|
|
210
|
+
const reportMatch = route.match(/^\/sessions\/([a-f0-9]+)\/(agents|model|tool-session|reporting|shells)$/);
|
|
211
211
|
if (reportMatch && method === 'POST') {
|
|
212
212
|
const [, id, kind] = reportMatch;
|
|
213
213
|
const body = await readJsonBody(req);
|
|
@@ -217,6 +217,11 @@ export function createManagerServer({
|
|
|
217
217
|
};
|
|
218
218
|
if (kind === 'agents') return sendJson(res, 200, { agent: manager.reportAgent(id, body, auth) });
|
|
219
219
|
if (kind === 'model') return sendJson(res, 200, { model: manager.reportModel(id, body, auth) });
|
|
220
|
+
if (kind === 'reporting') return sendJson(res, 200, { reporting: manager.reportHello(id, auth) });
|
|
221
|
+
if (kind === 'shells') {
|
|
222
|
+
manager.reportShell(id, body, auth);
|
|
223
|
+
return sendJson(res, 200, { ok: true });
|
|
224
|
+
}
|
|
220
225
|
return sendJson(res, 200, { toolSessionId: manager.reportToolSession(id, body, auth) });
|
|
221
226
|
}
|
|
222
227
|
|
|
@@ -269,6 +274,17 @@ export function createManagerServer({
|
|
|
269
274
|
const account = registry.account(provider, url.searchParams.get('account'));
|
|
270
275
|
return sendJson(res, 200, { history: await history.list(provider, account, { limit: url.searchParams.get('limit') }) });
|
|
271
276
|
}
|
|
277
|
+
const reportingMatch = route.match(/^\/providers\/([a-z0-9][a-z0-9_-]{0,31})\/reporting$/);
|
|
278
|
+
if (reportingMatch && method === 'POST' && manager.sessionHooks) {
|
|
279
|
+
const provider = registry.get(reportingMatch[1]);
|
|
280
|
+
if (!provider) throw new HttpError(404, `unknown provider "${reportingMatch[1]}"`, 'unknown_provider');
|
|
281
|
+
const body = await readJsonBody(req);
|
|
282
|
+
if (typeof body.enabled !== 'boolean') throw new HttpError(400, 'enabled must be true or false', 'bad_request');
|
|
283
|
+
const account = registry.account(provider, body.account ?? null);
|
|
284
|
+
await manager.sessionHooks.setEnabled(provider, account, body.enabled);
|
|
285
|
+
registry.emit('updated');
|
|
286
|
+
return sendJson(res, 200, { provider: registry.describe(provider) });
|
|
287
|
+
}
|
|
272
288
|
const installMatch = route.match(/^\/providers\/([a-z0-9][a-z0-9_-]{0,31})\/install$/);
|
|
273
289
|
if (installMatch && method === 'POST') {
|
|
274
290
|
const body = await readJsonBody(req);
|
|
@@ -280,7 +296,7 @@ export function createManagerServer({
|
|
|
280
296
|
}
|
|
281
297
|
if (route === '/sessions' && method === 'POST') {
|
|
282
298
|
const body = await readJsonBody(req);
|
|
283
|
-
const session = manager.create(body);
|
|
299
|
+
const session = await manager.create(body);
|
|
284
300
|
return sendJson(res, 201, { session: session.toJSON() });
|
|
285
301
|
}
|
|
286
302
|
if (route === '/shutdown' && method === 'POST') {
|
|
@@ -452,7 +468,7 @@ export function createManagerServer({
|
|
|
452
468
|
if (isBinary) return;
|
|
453
469
|
let msg;
|
|
454
470
|
try { msg = JSON.parse(raw.toString('utf8')); } catch { return; }
|
|
455
|
-
if (msg.type === 'input' && typeof msg.data === 'string') session.
|
|
471
|
+
if (msg.type === 'input' && typeof msg.data === 'string') session.input(msg.data);
|
|
456
472
|
else if (msg.type === 'resize') session.resize(msg.cols, msg.rows);
|
|
457
473
|
});
|
|
458
474
|
}
|