@openagents-org/agent-launcher 0.2.171 → 0.2.173

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-launcher",
3
- "version": "0.2.171",
4
- "description": "OpenAgents Launcher \u2014 install, configure, and run AI coding agents from your terminal",
3
+ "version": "0.2.173",
4
+ "description": "OpenAgents Launcher — install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "agn": "bin/agent-connector.js",
package/registry.json CHANGED
@@ -152,6 +152,7 @@
152
152
  "login_command": "claude login",
153
153
  "alt_check": "claude --print hi"
154
154
  },
155
+ "probe": { "args": ["--print", "hi"], "timeout_s": 120 },
155
156
  "env_config": []
156
157
  },
157
158
  {
@@ -417,7 +418,7 @@
417
418
  {
418
419
  "name": "gemini",
419
420
  "label": "Gemini CLI",
420
- "description": "Google's open-source AI agent for the command line",
421
+ "description": "Google's Gemini CLI (legacy — superseded by Antigravity CLI; needs a Gemini API key or Code Assist license since June 2026)",
421
422
  "homepage": "https://github.com/google-gemini/gemini-cli",
422
423
  "tags": [
423
424
  "coding",
@@ -462,13 +463,59 @@
462
463
  ],
463
464
  "saved_env_key": "GEMINI_API_KEY",
464
465
  "login_command": "gemini",
465
- "not_ready_message": "Needs sign-in — run `gemini` to sign in, or set GEMINI_API_KEY.",
466
+ "not_ready_message": "Set GEMINI_API_KEY (Google ended individual sign-in for Gemini CLI in June 2026), or run `gemini` if your org has a Code Assist license.",
466
467
  "unreadable_message": "Gemini credentials were found but could not be read. Check permissions on ~/.gemini, or run `gemini` to sign in again.",
467
468
  "auth_detected_labels": {
468
469
  "cli_login": "Google account sign-in detected",
469
470
  "api_key": "API key detected"
470
471
  }
471
472
  },
473
+ "probe": { "args": ["-p", "hi"], "timeout_s": 120 },
474
+ "env_config": []
475
+ },
476
+ {
477
+ "name": "antigravity",
478
+ "label": "Antigravity CLI",
479
+ "description": "Google's agent-first CLI (agy) — the successor to Gemini CLI",
480
+ "homepage": "https://antigravity.google",
481
+ "tags": [
482
+ "coding",
483
+ "google",
484
+ "cli"
485
+ ],
486
+ "builtin": true,
487
+ "support": { "install": true, "workspace": true, "collaboration": true },
488
+ "install": {
489
+ "binary": "agy",
490
+ "macos": "curl -fsSL https://antigravity.google/cli/install.sh | bash",
491
+ "linux": "curl -fsSL https://antigravity.google/cli/install.sh | bash",
492
+ "windows": "irm https://antigravity.google/cli/install.ps1 | iex"
493
+ },
494
+ "adapter": {
495
+ "module": "openagents.adapters.antigravity",
496
+ "class": "AntigravityAdapter"
497
+ },
498
+ "launch": {
499
+ "args": [
500
+ "-p",
501
+ "Your agent name is '{agent_name}'.",
502
+ "--dangerously-skip-permissions",
503
+ "--output-format",
504
+ "stream-json"
505
+ ]
506
+ },
507
+ "check_ready": {
508
+ "env_vars": [
509
+ "GEMINI_API_KEY"
510
+ ],
511
+ "saved_env_key": "GEMINI_API_KEY",
512
+ "login_command": "agy",
513
+ "not_ready_message": "Needs sign-in — run `agy` to sign in with Google, or set GEMINI_API_KEY.",
514
+ "auth_detected_labels": {
515
+ "cli_login": "Google account sign-in detected",
516
+ "api_key": "API key detected"
517
+ }
518
+ },
472
519
  "env_config": []
473
520
  },
474
521
  {
@@ -0,0 +1,242 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pure helpers for the Antigravity CLI (agy) adapter — argv construction,
5
+ * stream-json event interpretation, and failure classification. No I/O and no
6
+ * process state, so every branch is unit-testable (see
7
+ * test/antigravity-stream.test.js).
8
+ *
9
+ * agy's headless protocol (`--output-format stream-json`) is NDJSON with an
10
+ * `event` field per line: exactly one `init`, any number of `step_update`,
11
+ * exactly one `result`. That differs from the old Gemini CLI stream (`type`
12
+ * field, `message`/`tool_use` events), which is why this is a new module
13
+ * rather than a tweak to the gemini adapter.
14
+ */
15
+
16
+ /** Build the argv tail (everything after the binary) for one headless run. */
17
+ function buildAgyArgv({ prompt, model, conversationId, skipResume = false } = {}) {
18
+ const args = [
19
+ '-p', prompt || '',
20
+ // Counterpart of gemini's `-y`: headless runs have no interactive
21
+ // approval prompt, and a soft-denied tool call silently degrades the
22
+ // answer. The daemon is the approval boundary here, same as every other
23
+ // CLI adapter.
24
+ '--dangerously-skip-permissions',
25
+ '--output-format', 'stream-json',
26
+ ];
27
+ if (model) args.push('--model', model);
28
+ if (conversationId && !skipResume) args.push('--conversation', conversationId);
29
+ return args;
30
+ }
31
+
32
+ /** Parse one NDJSON line. Returns the event object, or null for non-events. */
33
+ function parseAgyEvent(line) {
34
+ const trimmed = (line || '').trim();
35
+ if (!trimmed) return null;
36
+ let obj;
37
+ try { obj = JSON.parse(trimmed); } catch { return null; }
38
+ if (!obj || typeof obj !== 'object' || typeof obj.event !== 'string') return null;
39
+ return obj;
40
+ }
41
+
42
+ /** One-line preview of a tool call's parameters for the status ticker. */
43
+ function toolPreview(parameters) {
44
+ if (!parameters || typeof parameters !== 'object') return '';
45
+ // agy uses PascalCase parameter names (e.g. CommandLine); check both.
46
+ const pick = (...names) => {
47
+ for (const n of names) {
48
+ const v = parameters[n];
49
+ if (typeof v === 'string' && v) return v;
50
+ }
51
+ return null;
52
+ };
53
+ return (
54
+ pick('CommandLine', 'command') ||
55
+ pick('AbsolutePath', 'file_path', 'path') ||
56
+ pick('Query', 'query', 'pattern') ||
57
+ JSON.stringify(parameters).slice(0, 150)
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Reducer over one run's event stream. `consume(event)` returns display
63
+ * actions for the adapter to translate into workspace calls:
64
+ *
65
+ * {type:'conversation', id} — persist for --conversation resume
66
+ * {type:'thinking', text} — a finished agent_response block
67
+ * {type:'tool', name, preview} — a tool step began
68
+ * {type:'result', status, response, error}
69
+ *
70
+ * `finalResponse()` afterwards yields the text to post: the result envelope's
71
+ * `response` when present (authoritative), else the last completed
72
+ * agent_response block — mirroring the gemini adapter's "text after the last
73
+ * tool use" semantics for streams that die before their result event.
74
+ */
75
+ class AgyRunState {
76
+ constructor() {
77
+ this.conversationId = null;
78
+ this.status = null;
79
+ this.error = null;
80
+ this._resultResponse = '';
81
+ this._blocks = []; // completed agent_response texts since last tool
82
+ this._pending = ''; // text_delta accumulator for the ACTIVE block
83
+ this._toolSteps = new Set(); // step_index values already announced
84
+ }
85
+
86
+ consume(event) {
87
+ if (!event || typeof event !== 'object') return [];
88
+ const actions = [];
89
+
90
+ if (event.event === 'init') {
91
+ if (event.conversation_id) {
92
+ this.conversationId = event.conversation_id;
93
+ actions.push({ type: 'conversation', id: event.conversation_id });
94
+ }
95
+ return actions;
96
+ }
97
+
98
+ if (event.event === 'step_update') {
99
+ const step = event.step_update || {};
100
+ if (step.step_type === 'agent_response') {
101
+ if (typeof step.text_delta === 'string') this._pending += step.text_delta;
102
+ if (step.state === 'DONE') {
103
+ const text = this._pending.trim();
104
+ this._pending = '';
105
+ if (text) {
106
+ this._blocks.push(text);
107
+ actions.push({ type: 'thinking', text });
108
+ }
109
+ }
110
+ } else if (step.step_type === 'tool') {
111
+ // Announce each tool step once, on first sight — agy repeats the step
112
+ // across ACTIVE/DONE updates.
113
+ const key = step.step_index != null ? step.step_index : `t${this._toolSteps.size}`;
114
+ if (!this._toolSteps.has(key)) {
115
+ this._toolSteps.add(key);
116
+ const info = step.tool_info || {};
117
+ const name = info.name || step.tool_name || 'tool';
118
+ actions.push({ type: 'tool', name, preview: toolPreview(info.parameters) });
119
+ // A tool ran: any text before it was narration, not the answer.
120
+ this._blocks = [];
121
+ }
122
+ }
123
+ return actions;
124
+ }
125
+
126
+ if (event.event === 'result') {
127
+ const result = event.result || {};
128
+ if (result.conversation_id && !this.conversationId) {
129
+ this.conversationId = result.conversation_id;
130
+ actions.push({ type: 'conversation', id: result.conversation_id });
131
+ }
132
+ this.status = result.status || null;
133
+ this.error = result.error || null;
134
+ if (typeof result.response === 'string') this._resultResponse = result.response;
135
+ actions.push({
136
+ type: 'result',
137
+ status: this.status,
138
+ response: this._resultResponse,
139
+ error: this.error,
140
+ });
141
+ return actions;
142
+ }
143
+
144
+ return actions;
145
+ }
146
+
147
+ /** True once the stream produced any user-visible text. */
148
+ sawText() {
149
+ return this._blocks.length > 0 || !!this._resultResponse.trim();
150
+ }
151
+
152
+ finalResponse() {
153
+ const fromResult = this._resultResponse.trim();
154
+ if (fromResult) return fromResult;
155
+ // Stream died before its result event — fall back to the accumulated
156
+ // agent_response text (plus any half-flushed delta).
157
+ return [...this._blocks, this._pending.trim()].filter(Boolean).join('\n\n').trim();
158
+ }
159
+ }
160
+
161
+ const AUTH_RE = /authentication (required|failed)|not authenticated|sign[ -]?in|log[ -]?in required|no valid credentials|GEMINI_API_KEY/i;
162
+ const PROVIDER_RE = /modelProvider|model provider/i;
163
+ const MODEL_RE = /unknown model|invalid model/i;
164
+ const TIMEOUT_RE = /print-timeout|timed? ?out/i;
165
+
166
+ /**
167
+ * Turn a failed run (non-zero exit, or a result event with status ERROR) into
168
+ * a { kind, message } the adapter can post verbatim. This is the fix for the
169
+ * "No response generated. Please try again." dead end: the real reason was in
170
+ * stderr all along, so surface it.
171
+ */
172
+ function classifyAgyFailure({ code, stderr, error } = {}) {
173
+ // The docs describe `result.error` as {type, message}; agy 1.1.17 actually
174
+ // emits a plain string ("authentication failed or timed out"). Take both.
175
+ const err = error && typeof error === 'object' ? error : {};
176
+ const errText =
177
+ typeof error === 'string' ? error : [err.type, err.message].filter(Boolean).join(' ');
178
+ const text = [errText, stderr].filter(Boolean).join('\n');
179
+
180
+ if (AUTH_RE.test(text)) {
181
+ return {
182
+ kind: 'auth',
183
+ message:
184
+ 'Antigravity CLI needs authentication. Run `agy` once in a terminal to ' +
185
+ 'sign in with Google, or set GEMINI_API_KEY for this agent (the ' +
186
+ 'connector configures the provider automatically).',
187
+ };
188
+ }
189
+ if (PROVIDER_RE.test(text)) {
190
+ return {
191
+ kind: 'config',
192
+ message:
193
+ 'Antigravity CLI is configured for API-key auth but no key is set. ' +
194
+ 'Set GEMINI_API_KEY for this agent, or remove "modelProvider" from ' +
195
+ '~/.gemini/antigravity-cli/settings.json to use Google sign-in.',
196
+ };
197
+ }
198
+ if (MODEL_RE.test(text)) {
199
+ return {
200
+ kind: 'model',
201
+ message:
202
+ 'Antigravity CLI rejected the configured model. Check ANTIGRAVITY_MODEL ' +
203
+ '(list valid slugs with `agy models`).',
204
+ };
205
+ }
206
+ if (TIMEOUT_RE.test(text)) {
207
+ return { kind: 'timeout', message: 'Antigravity CLI timed out before producing a response.' };
208
+ }
209
+ const detail = (errText || (stderr || '').trim().split('\n').pop() || '').slice(0, 200);
210
+ return {
211
+ kind: 'unknown',
212
+ message: detail
213
+ ? `Antigravity CLI failed (exit ${code ?? '?'}): ${detail}`
214
+ : `Antigravity CLI exited with code ${code ?? '?'} without a response.`,
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Filesystem candidates for the agy binary, beyond a PATH lookup. The install
220
+ * script targets ~/.local/bin on macOS/Linux and %LOCALAPPDATA%\agy\bin on
221
+ * Windows. Pure: the adapter applies fs.existsSync.
222
+ */
223
+ function agyBinaryCandidates({ home, isWindows, localAppData } = {}) {
224
+ if (isWindows) {
225
+ const base = localAppData || '';
226
+ return base ? [`${base}\\agy\\bin\\agy.exe`] : [];
227
+ }
228
+ return [
229
+ `${home}/.local/bin/agy`,
230
+ '/opt/homebrew/bin/agy',
231
+ '/usr/local/bin/agy',
232
+ ];
233
+ }
234
+
235
+ module.exports = {
236
+ buildAgyArgv,
237
+ parseAgyEvent,
238
+ toolPreview,
239
+ AgyRunState,
240
+ classifyAgyFailure,
241
+ agyBinaryCandidates,
242
+ };