@oddessentials/agent-guild 0.11.0 → 0.12.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 -16
- package/bin/agent-guild-report.mjs +37 -17
- package/config/providers.default.json +15 -15
- package/package.json +1 -1
- package/src/manager/config.mjs +0 -1
- package/src/manager/install-channels.mjs +7 -0
- package/src/manager/main.mjs +3 -4
- package/src/manager/news.mjs +1 -1
- package/src/manager/providers.mjs +9 -11
- package/src/manager/report-shims.mjs +4 -4
- package/src/manager/server.mjs +1 -2
- package/src/manager/session-history.mjs +32 -74
- package/src/manager/session-hooks.mjs +79 -78
- package/src/manager/session-manager.mjs +3 -33
- package/src/manager/session.mjs +9 -83
- package/src/manager/usage.mjs +3 -258
- package/src/report/hooks.mjs +27 -48
- package/web/app.js +7 -9
- package/examples/gemini-settings.json +0 -61
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<a href="https://www.youtube.com/watch?v=ziT62WtXQ1M"><img src="https://img.shields.io/badge/trailer-watch%20on%20YouTube-e5534b?logo=youtube&logoColor=white" alt="Watch the Agent Guild trailer on YouTube"></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
**Agent Guild** runs Claude Code, Codex CLI,
|
|
14
|
+
**Agent Guild** runs Claude Code, Codex CLI, Antigravity CLI, Grok Build and your
|
|
15
15
|
own shell side by side, in real terminals, from one local web page. Each
|
|
16
16
|
session is a card that shows what the tool is doing, which model it runs and
|
|
17
17
|
which helper agents it has summoned. Close the page whenever you like; the
|
|
@@ -41,9 +41,10 @@ compiler and no WSL are needed.
|
|
|
41
41
|
|
|
42
42
|
## Features
|
|
43
43
|
|
|
44
|
-
**Every major coding CLI, one place.** Claude Code, Codex CLI,
|
|
44
|
+
**Every major coding CLI, one place.** Claude Code, Codex CLI, Antigravity CLI,
|
|
45
45
|
Grok Build and a plain shell each get a card. A tool that is missing shows
|
|
46
|
-
**Install**, which runs the install in a session you can watch
|
|
46
|
+
**Install**, which runs the install in a session you can watch, or the
|
|
47
|
+
vendor's install command for a tool not published on npm. An installed
|
|
47
48
|
tool shows its version and offers **Update** when a newer one is out, using
|
|
48
49
|
the same installer that put it there: npm, Homebrew, WinGet or the vendor's
|
|
49
50
|
own.
|
|
@@ -64,7 +65,7 @@ with every hour it runs.
|
|
|
64
65
|
one and switch with a chip on the card. Each account keeps its own sign-in,
|
|
65
66
|
usage meters and sessions. See [Accounts](docs/configuration.md#accounts).
|
|
66
67
|
|
|
67
|
-
**See what is left of your limits.** Claude Code
|
|
68
|
+
**See what is left of your limits.** Claude Code and Codex CLI
|
|
68
69
|
cards show a meter for each rate-limit window, such as 5-hour and 7-day, with
|
|
69
70
|
the time until it resets. They also show your plan, Claude's extra-usage
|
|
70
71
|
spend and Codex's credit balance when the account has them. Any other tool
|
|
@@ -143,9 +144,10 @@ The full reference, including every field and environment variable, is in
|
|
|
143
144
|
Agents are reported by the coding tool's hooks, not guessed from its output.
|
|
144
145
|
Each helper agent appears on the card while it runs. Agent Guild gives every
|
|
145
146
|
Claude Code and Codex CLI session its reporting hooks for that session only,
|
|
146
|
-
without changing the tool's own settings.
|
|
147
|
-
on **Agent reporting** on its card once, which
|
|
148
|
-
into
|
|
147
|
+
without changing the tool's own settings. Antigravity CLI has no such option:
|
|
148
|
+
turn on **Agent reporting** on its card once, which installs an Agent Guild
|
|
149
|
+
plugin into Antigravity. It reports the model and the conversation, but no
|
|
150
|
+
helper agents or shell commands. Grok Build cannot take hooks for one session yet; add the hooks
|
|
149
151
|
from [examples/grok-hooks.json](examples/grok-hooks.json) to see its agents.
|
|
150
152
|
When a tool's hooks do not run, because hooks are turned off, restricted by
|
|
151
153
|
an administrator or not trusted for the folder, the card says so.
|
|
@@ -172,8 +174,7 @@ Any other tool or script can report agents and its model too. See
|
|
|
172
174
|
own `~/.ssh` and Git configuration are not read or changed.
|
|
173
175
|
* Usage meters are fetched by the manager with each tool's own sign-in. The
|
|
174
176
|
page only receives percentages. On macOS the first lookup may ask for
|
|
175
|
-
keychain access to the "Claude Code-credentials"
|
|
176
|
-
items; choose Always Allow.
|
|
177
|
+
keychain access to the "Claude Code-credentials" item; choose Always Allow.
|
|
177
178
|
|
|
178
179
|
The manager makes these outbound requests, and none of them carry your code
|
|
179
180
|
or prompts:
|
|
@@ -181,7 +182,7 @@ or prompts:
|
|
|
181
182
|
| To | For | How often |
|
|
182
183
|
| --- | --- | --- |
|
|
183
184
|
| npm registry | Tool and Agent Guild version checks | About hourly; `AGENT_GUILD_NO_UPDATE_CHECK=1` turns them off |
|
|
184
|
-
| Anthropic
|
|
185
|
+
| Anthropic and OpenAI usage endpoints | Usage meters, with the tool's own sign-in | Every minute while the page is open |
|
|
185
186
|
| OpenRouter's public model list | Benchmarks | Every 6 hours |
|
|
186
187
|
| Public news feeds, Hacker News, arXiv and GitHub | The newsfeed | Every 30 minutes while the page is open |
|
|
187
188
|
| GitHub's releases API | What's new | Hourly |
|
|
@@ -224,12 +225,9 @@ npm test
|
|
|
224
225
|
## Current limits
|
|
225
226
|
|
|
226
227
|
* Sessions end when the manager stops or the computer restarts.
|
|
227
|
-
* Grok Build
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
OS keychain (macOS, or Linux with `secret-tool`) or its encrypted
|
|
231
|
-
credentials file. A sign-in kept in the Windows Credential Manager cannot
|
|
232
|
-
be read.
|
|
228
|
+
* Antigravity CLI and Grok Build have no usage meter.
|
|
229
|
+
* Antigravity CLI has one account per computer user: it keeps its sign-in in
|
|
230
|
+
the system keychain and has no setting for another home folder.
|
|
233
231
|
|
|
234
232
|
## License
|
|
235
233
|
|
|
@@ -10,40 +10,31 @@
|
|
|
10
10
|
// [--detail TEXT] [--kind KIND] [--remove]
|
|
11
11
|
// agent-guild-report --model NAME [--display-name TEXT]
|
|
12
12
|
// agent-guild-report --session ID (the tool's own session id, for resuming it later)
|
|
13
|
-
// agent-guild-report --hook
|
|
14
|
-
// Claude Code, Codex CLI,
|
|
13
|
+
// agent-guild-report --hook [--event NAME] (reads a hook event as JSON on stdin:
|
|
14
|
+
// Claude Code, Codex CLI, Antigravity CLI, Grok Build;
|
|
15
|
+
// --event names one that does not name itself)
|
|
15
16
|
// agent-guild-report --claude-statusline [--passthrough]
|
|
16
17
|
// (reads Claude Code status line JSON on stdin; prints a
|
|
17
18
|
// status line, or the JSON itself with --passthrough)
|
|
18
19
|
|
|
19
20
|
import fs from 'node:fs';
|
|
20
21
|
import { createRequire } from 'node:module';
|
|
21
|
-
import { hookToReports, claudeStatuslineToReport, formatStatusLine } from '../src/report/hooks.mjs';
|
|
22
|
+
import { hookToReports, claudeStatuslineToReport, formatStatusLine, antigravityUserConversation } from '../src/report/hooks.mjs';
|
|
22
23
|
|
|
23
24
|
// Importing node:http as an ES module costs a hook about 50 ms more than requiring it.
|
|
24
25
|
const http = createRequire(import.meta.url)('node:http');
|
|
25
26
|
const env = process.env;
|
|
26
27
|
|
|
27
|
-
|
|
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;
|
|
28
|
+
const token = env.AGENT_GUILD_URL && env.AGENT_GUILD_SESSION_ID ? env.AGENT_GUILD_REPORT_TOKEN || null : null;
|
|
38
29
|
const inSession = Boolean(token);
|
|
39
30
|
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
|
|
31
|
+
console.error('agent-guild-report: the session report token is missing; the tool may have removed AGENT_GUILD_REPORT_TOKEN from the environment');
|
|
41
32
|
}
|
|
42
33
|
|
|
43
34
|
const USAGE = `Usage: agent-guild-report <agent-id> [--name N] [--status working|waiting|idle|done] [--detail TEXT] [--kind KIND] [--remove]
|
|
44
35
|
agent-guild-report --model NAME [--display-name TEXT]
|
|
45
36
|
agent-guild-report --session ID (the tool's own session id)
|
|
46
|
-
agent-guild-report --hook
|
|
37
|
+
agent-guild-report --hook [--event NAME] (reads a coding tool's hook event JSON from stdin)
|
|
47
38
|
agent-guild-report --claude-statusline [--passthrough] (reads Claude Code status line JSON from stdin)`;
|
|
48
39
|
|
|
49
40
|
function parseArgs(argv) {
|
|
@@ -105,6 +96,32 @@ function send(report) {
|
|
|
105
96
|
/** A hook must never break the coding tool, so report failures quietly. */
|
|
106
97
|
const sendQuietly = (report) => send(report).catch((err) => console.error(`agent-guild-report: ${err.message}`));
|
|
107
98
|
|
|
99
|
+
const MAX_FIRST_LINE = 1024 * 1024;
|
|
100
|
+
|
|
101
|
+
/** The first JSON record of a JSONL file, or null. */
|
|
102
|
+
function firstRecord(file) {
|
|
103
|
+
if (typeof file !== 'string') return null;
|
|
104
|
+
let fd;
|
|
105
|
+
try {
|
|
106
|
+
fd = fs.openSync(file, 'r');
|
|
107
|
+
const chunks = [];
|
|
108
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
109
|
+
for (let total = 0; total < MAX_FIRST_LINE;) {
|
|
110
|
+
const n = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
111
|
+
if (n === 0) break;
|
|
112
|
+
const end = buffer.subarray(0, n).indexOf(10);
|
|
113
|
+
chunks.push(Buffer.from(buffer.subarray(0, end === -1 ? n : end)));
|
|
114
|
+
if (end !== -1) break;
|
|
115
|
+
total += n;
|
|
116
|
+
}
|
|
117
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
} finally {
|
|
121
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
108
125
|
function parseJson(raw) {
|
|
109
126
|
try { return JSON.parse(raw); } catch { return null; }
|
|
110
127
|
}
|
|
@@ -117,7 +134,10 @@ async function main() {
|
|
|
117
134
|
}
|
|
118
135
|
if (args.hook) {
|
|
119
136
|
const input = parseJson(await readStdin());
|
|
120
|
-
if (!inSession || !input) return;
|
|
137
|
+
if (!inSession || !input || typeof input !== 'object') return;
|
|
138
|
+
if (args.event && input.hook_event_name === undefined) input.hook_event_name = args.event;
|
|
139
|
+
// Antigravity CLI runs the hook in its sub-agents too, each a conversation of its own.
|
|
140
|
+
if (typeof input.conversationId === 'string' && !antigravityUserConversation(firstRecord(input.transcriptPath))) return;
|
|
121
141
|
for (const report of hookToReports(input)) await sendQuietly(report);
|
|
122
142
|
return;
|
|
123
143
|
}
|
|
@@ -76,28 +76,28 @@
|
|
|
76
76
|
{
|
|
77
77
|
"id": "google",
|
|
78
78
|
"vendor": "Google",
|
|
79
|
-
"tool": "
|
|
80
|
-
"command": "
|
|
81
|
-
"package": "@google/gemini-cli",
|
|
79
|
+
"tool": "Antigravity CLI",
|
|
80
|
+
"command": "agy",
|
|
82
81
|
"versionArgs": ["--version"],
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"reporting": "gemini",
|
|
86
|
-
"homeVar": "GEMINI_CLI_HOME",
|
|
87
|
-
"accountEnv": { "GEMINI_FORCE_FILE_STORAGE": "true" },
|
|
88
|
-
"hooks": { "path": ".gemini/settings.json", "example": "gemini-settings.json" },
|
|
82
|
+
"history": "antigravity",
|
|
83
|
+
"reporting": "antigravity",
|
|
89
84
|
"modelPattern": "\\bgemini-\\d[a-z0-9.-]*",
|
|
90
85
|
"args": [],
|
|
91
|
-
"resumeArgs": ["--
|
|
86
|
+
"resumeArgs": ["--conversation", "{id}"],
|
|
92
87
|
"color": "#4285F4",
|
|
93
88
|
"monogram": "G",
|
|
94
|
-
"install": "
|
|
89
|
+
"install": "curl -fsSL https://antigravity.google/cli/install.sh | bash",
|
|
95
90
|
"channels": {
|
|
96
|
-
"
|
|
91
|
+
"native": { "paths": ["~/.local/bin/agy"], "update": ["update"], "uninstall": "rm -f ~/.local/bin/agy" }
|
|
97
92
|
},
|
|
98
|
-
"docs": "https://
|
|
99
|
-
"
|
|
100
|
-
"
|
|
93
|
+
"docs": "https://antigravity.google/docs/cli/install",
|
|
94
|
+
"billingUrl": "https://one.google.com/settings",
|
|
95
|
+
"win32": {
|
|
96
|
+
"install": "irm https://antigravity.google/cli/install.ps1 | iex",
|
|
97
|
+
"channels": {
|
|
98
|
+
"native": { "paths": ["~/AppData/Local/agy/bin"], "update": ["update"], "uninstall": "Remove-Item -Path \"$env:LOCALAPPDATA\\agy\" -Recurse -Force" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"id": "xai",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddessentials/agent-guild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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": {
|
package/src/manager/config.mjs
CHANGED
|
@@ -48,7 +48,6 @@ export const paths = {
|
|
|
48
48
|
/** Launchers for agent-guild-report, put first on every session's PATH. */
|
|
49
49
|
get shims() { return path.join(dataDir(), 'bin'); },
|
|
50
50
|
get reporting() { return path.join(dataDir(), 'reporting'); },
|
|
51
|
-
get reportTokens() { return path.join(dataDir(), 'report-tokens'); },
|
|
52
51
|
};
|
|
53
52
|
|
|
54
53
|
function writePrivate(file, contents) {
|
|
@@ -122,6 +122,13 @@ function wingetOwner({ realPath, id, platform }) {
|
|
|
122
122
|
return folder.startsWith(`${id.toLowerCase()}_`) ? { id } : null;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** Whether `<tool> <update args> --help` shows the update command. Go programs exit 2 after their help; a failure that reports an error does not count. */
|
|
126
|
+
export function updateHelpAccepted({ stdout = '', stderr = '' }, args, { failed = false } = {}) {
|
|
127
|
+
const text = `${stdout}\n${stderr}`;
|
|
128
|
+
if (failed && /\b(?:error|unknown|unrecogni[sz]ed|invalid)\b/i.test(text)) return false;
|
|
129
|
+
return helpDescribes(text, args);
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
export function helpDescribes(text, args) {
|
|
126
133
|
const words = args.map((a) => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
127
134
|
const named = new RegExp(`(?<![\\w-])${words.join('(?![\\w-]).*(?<![\\w-])')}(?![\\w-])`, 'i');
|
package/src/manager/main.mjs
CHANGED
|
@@ -63,11 +63,11 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
63
63
|
let api;
|
|
64
64
|
const selfUpdate = new SelfUpdate({ pkg: PACKAGE_NAME, version, packageFile, registry });
|
|
65
65
|
const github = new GitHub({ dir: paths.github, registry, ...githubOptions });
|
|
66
|
-
const sessionHooks = new SessionHooks({ registry, dir: paths.reporting, version
|
|
67
|
-
registry.reportingEnabled = (provider
|
|
66
|
+
const sessionHooks = new SessionHooks({ registry, dir: paths.reporting, version });
|
|
67
|
+
registry.reportingEnabled = (provider) => sessionHooks.enabled(provider);
|
|
68
68
|
sessionHooks.warm();
|
|
69
69
|
const manager = new SessionManager({
|
|
70
|
-
registry, baseEnv, getApiUrl: () => api.url, sessionDefaults, shimDir, selfUpdate, github, sessionHooks,
|
|
70
|
+
registry, baseEnv, getApiUrl: () => api.url, sessionDefaults, shimDir, selfUpdate, github, sessionHooks,
|
|
71
71
|
});
|
|
72
72
|
const usage = new UsageMonitor({ registry, env: baseEnv });
|
|
73
73
|
const history = new SessionHistory({ registry, env: baseEnv });
|
|
@@ -147,7 +147,6 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
147
147
|
throw err;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
manager.sweepReportTokens();
|
|
151
150
|
writeRuntimeFile({
|
|
152
151
|
pid: process.pid,
|
|
153
152
|
host,
|
package/src/manager/news.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export const FEEDS = [
|
|
|
30
30
|
{ id: 'ollama-blog', name: 'Ollama', category: 'news', url: 'https://ollama.com/blog/rss.xml' },
|
|
31
31
|
latestRelease('claude-code', 'Claude Code', 'anthropics/claude-code', 'anthropic'),
|
|
32
32
|
latestRelease('codex-cli', 'Codex CLI', 'openai/codex', 'openai'),
|
|
33
|
-
latestRelease('
|
|
33
|
+
latestRelease('antigravity-cli', 'Antigravity CLI', 'google-antigravity/antigravity-cli', 'google'),
|
|
34
34
|
{ id: 'ollama', name: 'Ollama', category: 'releases', url: 'https://github.com/ollama/ollama/releases.atom' },
|
|
35
35
|
{ id: 'llama-cpp', name: 'llama.cpp', category: 'releases', url: 'https://github.com/ggml-org/llama.cpp/releases.atom' },
|
|
36
36
|
{ id: 'transformers', name: 'Transformers', category: 'releases', url: 'https://github.com/huggingface/transformers/releases.atom' },
|
|
@@ -11,7 +11,7 @@ import { EventEmitter } from 'node:events';
|
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { resolveCommand, resolveAllCommands, pathKey, buildSpawnSpec, runSpec } from './command-resolver.mjs';
|
|
13
13
|
import { compareVersions, probeVersion, fetchManifest, latestVersion, DEFAULT_NPM_REGISTRY } from './versions.mjs';
|
|
14
|
-
import { CHANNEL_LABELS, classifyInstall, expandHome, formatCommand,
|
|
14
|
+
import { CHANNEL_LABELS, classifyInstall, expandHome, formatCommand, knownLaunchers, listInstallations, platformDependency, updateHelpAccepted } from './install-channels.mjs';
|
|
15
15
|
import { weavePaths } from './shell-env.mjs';
|
|
16
16
|
import { paths } from './config.mjs';
|
|
17
17
|
|
|
@@ -19,7 +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', '
|
|
22
|
+
const REPORTING_MODES = new Set(['claude', 'codex', 'antigravity', 'grok']);
|
|
23
23
|
const VERSION_TTL_MS = 60 * 60 * 1000;
|
|
24
24
|
const FAILED_PROBE_TTL_MS = 5 * 60 * 1000;
|
|
25
25
|
const PATH_REFRESH_MS = 60 * 1000;
|
|
@@ -52,18 +52,18 @@ function normalizeEnv(env) {
|
|
|
52
52
|
return out;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** "claude", "codex",
|
|
55
|
+
/** "claude", "codex", a { command, args } that prints usage JSON, or null. */
|
|
56
56
|
function normalizeUsage(usage) {
|
|
57
|
-
if (usage === 'claude' || usage === 'codex'
|
|
57
|
+
if (usage === 'claude' || usage === 'codex') return usage;
|
|
58
58
|
if (usage && typeof usage === 'object' && typeof usage.command === 'string' && usage.command) {
|
|
59
59
|
return { command: usage.command, args: Array.isArray(usage.args) ? usage.args.map(String) : [] };
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
/** "claude", "codex", "
|
|
64
|
+
/** "claude", "codex", "antigravity", "grok", a { command, args } that prints past sessions as JSON, or null. */
|
|
65
65
|
function normalizeHistory(history) {
|
|
66
|
-
if (['claude', 'codex', '
|
|
66
|
+
if (['claude', 'codex', 'antigravity', 'grok'].includes(history)) return history;
|
|
67
67
|
if (history && typeof history === 'object' && typeof history.command === 'string' && history.command) {
|
|
68
68
|
return { command: history.command, args: Array.isArray(history.args) ? history.args.map(String) : [] };
|
|
69
69
|
}
|
|
@@ -349,7 +349,7 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
349
349
|
if (force || entry.probePath !== found || entry.probeMtime !== mtime || now - entry.probeAt > ttl) {
|
|
350
350
|
const spec = buildSpawnSpec(channel.update.file, [...channel.update.args, '--help'], this.env, this.platform);
|
|
351
351
|
const ok = await runSpec(spec, { env: { ...this.env, ...provider.env } })
|
|
352
|
-
.then((
|
|
352
|
+
.then((out) => updateHelpAccepted(out, channel.update.args), (err) => updateHelpAccepted(err, channel.update.args, { failed: true }));
|
|
353
353
|
changed ||= ok !== entry.probeOk;
|
|
354
354
|
Object.assign(entry, { probePath: found, probeMtime: mtime, probeAt: now, probeOk: ok });
|
|
355
355
|
}
|
|
@@ -627,10 +627,8 @@ export class ProviderRegistry extends EventEmitter {
|
|
|
627
627
|
usageSource: provider.usage === null ? null : typeof provider.usage === 'string' ? provider.usage : 'command',
|
|
628
628
|
historySource: provider.history === null ? null : typeof provider.history === 'string' ? provider.history : 'command',
|
|
629
629
|
reporting: provider.reporting,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return { id: account.id, label: account.label, ...(reportingEnabled === null ? {} : { reportingEnabled }) };
|
|
633
|
-
}),
|
|
630
|
+
reportingEnabled: this.reportingEnabled?.(provider) ?? null,
|
|
631
|
+
accounts: provider.accounts.map((account) => ({ id: account.id, label: account.label })),
|
|
634
632
|
modelPattern: provider.modelPattern,
|
|
635
633
|
color: provider.color,
|
|
636
634
|
monogram: provider.monogram,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// session, without `npm link` or a global install.
|
|
3
3
|
//
|
|
4
4
|
// The coding tools run their hook commands through a shell (sh or Git Bash
|
|
5
|
-
// for Claude Code, a login shell for Codex CLI,
|
|
6
|
-
//
|
|
5
|
+
// for Claude Code, a login shell for Codex CLI, sh or cmd.exe for
|
|
6
|
+
// Antigravity CLI, sh or PowerShell for Grok Build) and look the command up on
|
|
7
7
|
// the PATH they inherit from the terminal. The manager writes these shims
|
|
8
8
|
// into its data folder at every start and puts that folder first on each
|
|
9
9
|
// session's PATH. They run the reporter with the manager's own Node.js, and
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
//
|
|
13
13
|
// On Windows there is deliberately no .ps1 shim: PowerShell prefers a .ps1
|
|
14
14
|
// over the PATHEXT extensions, and the default Restricted execution policy
|
|
15
|
-
// refuses to run it.
|
|
16
|
-
// bypassing that policy, so
|
|
15
|
+
// refuses to run it. Grok Build runs hooks in PowerShell without
|
|
16
|
+
// bypassing that policy, so it would fail; with only a .cmd present,
|
|
17
17
|
// PowerShell falls through to it and cmd.exe runs it with no policy involved.
|
|
18
18
|
// cmd.exe reads a batch file in the OEM code page, so no path may be written
|
|
19
19
|
// into the .cmd: the script's path lives in a loader module next to it, as
|
package/src/manager/server.mjs
CHANGED
|
@@ -280,8 +280,7 @@ export function createManagerServer({
|
|
|
280
280
|
if (!provider) throw new HttpError(404, `unknown provider "${reportingMatch[1]}"`, 'unknown_provider');
|
|
281
281
|
const body = await readJsonBody(req);
|
|
282
282
|
if (typeof body.enabled !== 'boolean') throw new HttpError(400, 'enabled must be true or false', 'bad_request');
|
|
283
|
-
|
|
284
|
-
await manager.sessionHooks.setEnabled(provider, account, body.enabled);
|
|
283
|
+
await manager.sessionHooks.setEnabled(provider, body.enabled);
|
|
285
284
|
registry.emit('updated');
|
|
286
285
|
return sendJson(res, 200, { provider: registry.describe(provider) });
|
|
287
286
|
}
|
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import zlib from 'node:zlib';
|
|
9
9
|
import { resolveCommand, buildSpawnSpec, runSpec } from './command-resolver.mjs';
|
|
10
10
|
import { toIso } from './usage.mjs';
|
|
11
|
+
import { antigravityUserConversation } from '../report/hooks.mjs';
|
|
11
12
|
|
|
12
13
|
export const HISTORY_TTL_MS = 5 * 1000;
|
|
13
14
|
export const DEFAULT_LIMIT = 100;
|
|
@@ -255,88 +256,45 @@ export async function listCodexSessions(dir, memo = new FileMemo()) {
|
|
|
255
256
|
return dedupe(entries);
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
// ----
|
|
259
|
+
// ---- Antigravity CLI ------------------------------------------------------
|
|
259
260
|
|
|
260
|
-
export function
|
|
261
|
-
return path.join(env.
|
|
261
|
+
export function antigravityDir(env = process.env, platform = process.platform) {
|
|
262
|
+
return path.join((platform === 'win32' ? env.USERPROFILE : env.HOME) || os.homedir(), '.gemini', 'antigravity-cli');
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
if (!record || typeof record !== 'object') return null;
|
|
266
|
-
if (record.type === 'user' && record.id !== undefined) {
|
|
267
|
-
const text = contentText(record.content);
|
|
268
|
-
return /^[/?]/.test(text.trim()) ? null : promptTitle(text);
|
|
269
|
-
}
|
|
270
|
-
const checkpoint = record.$set?.messages;
|
|
271
|
-
if (Array.isArray(checkpoint)) {
|
|
272
|
-
for (const message of checkpoint) {
|
|
273
|
-
const title = geminiPrompt(message);
|
|
274
|
-
if (title) return title;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
265
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
279
266
|
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
records = Array.isArray(meta?.messages) ? meta.messages : [];
|
|
286
|
-
} else {
|
|
287
|
-
records = parseLines(text);
|
|
288
|
-
meta = records.shift();
|
|
289
|
-
}
|
|
290
|
-
if (!meta || typeof meta !== 'object' || typeof meta.sessionId !== 'string') return null;
|
|
291
|
-
if (meta.kind === 'subagent') return null;
|
|
292
|
-
let title = typeof meta.summary === 'string' && meta.summary.trim() ? meta.summary : null;
|
|
293
|
-
for (const record of records) {
|
|
294
|
-
if (title) break;
|
|
295
|
-
title = geminiPrompt(record);
|
|
296
|
-
}
|
|
297
|
-
return cleanEntry({ id: meta.sessionId, title, cwd, startedAt: meta.startTime, updatedAt: stat.mtime });
|
|
267
|
+
function antigravityEntry(id, text, stat) {
|
|
268
|
+
const [first] = parseLines(text ?? '');
|
|
269
|
+
if (!antigravityUserConversation(first) || typeof first.content !== 'string') return null;
|
|
270
|
+
const request = first.content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/)?.[1];
|
|
271
|
+
return cleanEntry({ id, title: promptTitle(request), startedAt: first.created_at, updatedAt: stat.mtime });
|
|
298
272
|
}
|
|
299
273
|
|
|
300
|
-
/** The
|
|
301
|
-
async function
|
|
302
|
-
const
|
|
303
|
-
let
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (registry === null) {
|
|
311
|
-
let projects = {};
|
|
312
|
-
try { projects = JSON.parse(await readText(path.join(path.dirname(root), 'projects.json')))?.projects ?? {}; } catch { /* no registry */ }
|
|
313
|
-
registry = new Map(Object.entries(projects).map(([dir, slug]) => [slug, dir]));
|
|
314
|
-
}
|
|
315
|
-
roots.set(folder, registry.get(folder) ?? null);
|
|
316
|
-
}
|
|
317
|
-
return roots;
|
|
274
|
+
/** The folder of each folder's latest conversation; the CLI keeps no folder for the others. */
|
|
275
|
+
async function antigravityFolders(dir) {
|
|
276
|
+
const folders = new Map();
|
|
277
|
+
let latest = null;
|
|
278
|
+
try { latest = JSON.parse(await readText(path.join(dir, 'cache', 'last_conversations.json'))); } catch { /* none yet */ }
|
|
279
|
+
if (!latest || typeof latest !== 'object') return folders;
|
|
280
|
+
for (const [folder, id] of Object.entries(latest)) if (typeof id === 'string') folders.set(id, folder);
|
|
281
|
+
return folders;
|
|
318
282
|
}
|
|
319
283
|
|
|
320
|
-
/**
|
|
321
|
-
export async function
|
|
322
|
-
const root = path.join(dir, '
|
|
284
|
+
/** Conversations under `<dir>/brain/<id>/.system_generated/logs/transcript.jsonl`. */
|
|
285
|
+
export async function listAntigravitySessions(dir, memo = new FileMemo()) {
|
|
286
|
+
const root = path.join(dir, 'brain');
|
|
287
|
+
const folders = await antigravityFolders(dir);
|
|
323
288
|
const entries = [];
|
|
324
289
|
const seen = new Set();
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const stat = await statFile(file);
|
|
334
|
-
if (!stat) continue;
|
|
335
|
-
seen.add(file);
|
|
336
|
-
const cwd = roots.get(folder);
|
|
337
|
-
const entry = await memo.entry(file, stat, async () => geminiEntry(legacy ? await readText(file) : await readHead(file), stat, cwd, legacy));
|
|
338
|
-
if (entry) entries.push(entry);
|
|
339
|
-
}
|
|
290
|
+
for (const item of await readDir(root)) {
|
|
291
|
+
if (!item.isDirectory() || !UUID_RE.test(item.name)) continue;
|
|
292
|
+
const file = path.join(root, item.name, '.system_generated', 'logs', 'transcript.jsonl');
|
|
293
|
+
const stat = await statFile(file);
|
|
294
|
+
if (!stat) continue;
|
|
295
|
+
seen.add(file);
|
|
296
|
+
const entry = await memo.entry(file, stat, async () => antigravityEntry(item.name, await readHead(file), stat));
|
|
297
|
+
if (entry) entries.push({ ...entry, cwd: folders.get(entry.id) ?? null });
|
|
340
298
|
}
|
|
341
299
|
memo.prune(root, seen);
|
|
342
300
|
return dedupe(entries);
|
|
@@ -447,7 +405,7 @@ export class SessionHistory {
|
|
|
447
405
|
this.env = env;
|
|
448
406
|
this.platform = platform;
|
|
449
407
|
this.ttlMs = ttlMs;
|
|
450
|
-
this.readers = { claude: listClaudeSessions, codex: listCodexSessions,
|
|
408
|
+
this.readers = { claude: listClaudeSessions, codex: listCodexSessions, antigravity: listAntigravitySessions, grok: listGrokSessions, ...readers };
|
|
451
409
|
this.memo = new FileMemo();
|
|
452
410
|
this.cache = new Map();
|
|
453
411
|
}
|
|
@@ -455,7 +413,7 @@ export class SessionHistory {
|
|
|
455
413
|
static sourceDir(source, env) {
|
|
456
414
|
if (source === 'claude') return claudeConfigDir(env);
|
|
457
415
|
if (source === 'codex') return codexHome(env);
|
|
458
|
-
if (source === '
|
|
416
|
+
if (source === 'antigravity') return antigravityDir(env);
|
|
459
417
|
if (source === 'grok') return grokHome(env);
|
|
460
418
|
return null;
|
|
461
419
|
}
|