@gobing-ai/ts-ai-runner 0.3.20 → 0.3.21
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 +39 -8
- package/dist/agent-detector.d.ts +7 -3
- package/dist/agent-detector.d.ts.map +1 -1
- package/dist/agent-detector.js +27 -15
- package/dist/agent-spec.d.ts +4 -4
- package/dist/agent-spec.d.ts.map +1 -1
- package/dist/agent-spec.js +11 -11
- package/dist/agents/shims.d.ts +31 -8
- package/dist/agents/shims.d.ts.map +1 -1
- package/dist/agents/shims.js +131 -14
- package/dist/ai-runner.d.ts +9 -1
- package/dist/ai-runner.d.ts.map +1 -1
- package/dist/ai-runner.js +27 -17
- package/dist/doctor-runner.d.ts +8 -0
- package/dist/doctor-runner.d.ts.map +1 -1
- package/dist/doctor-runner.js +21 -8
- package/dist/slash-command.d.ts.map +1 -1
- package/dist/slash-command.js +1 -0
- package/dist/team-orchestrator.d.ts +4 -4
- package/dist/team-orchestrator.d.ts.map +1 -1
- package/dist/team-orchestrator.js +19 -27
- package/package.json +4 -4
- package/src/agent-detector.ts +37 -18
- package/src/agent-spec.ts +14 -12
- package/src/agents/shims.ts +157 -18
- package/src/ai-runner.ts +36 -21
- package/src/doctor-runner.ts +29 -9
- package/src/slash-command.ts +1 -0
- package/src/team-orchestrator.ts +26 -30
package/src/agents/shims.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { getLogger } from '@gobing-ai/ts-infra';
|
|
2
|
+
|
|
3
|
+
/** Identifier for one supported coding agent (canonical id). */
|
|
4
|
+
export type AgentName =
|
|
5
|
+
| 'claude'
|
|
6
|
+
| 'codex'
|
|
7
|
+
| 'gemini'
|
|
8
|
+
| 'pi'
|
|
9
|
+
| 'opencode'
|
|
10
|
+
| 'antigravity-cli'
|
|
11
|
+
| 'openclaw'
|
|
12
|
+
| 'hermes'
|
|
13
|
+
| 'omp';
|
|
3
14
|
|
|
4
15
|
/** Output mode for prompt invocations. */
|
|
5
16
|
export type OutputMode = 'text' | 'json';
|
|
@@ -34,14 +45,26 @@ export interface PromptOptions {
|
|
|
34
45
|
peers?: Array<{ id: string; type: string; purpose?: string }>;
|
|
35
46
|
}
|
|
36
47
|
|
|
48
|
+
/** Deprecation metadata attached to a retired or superseded agent shim. */
|
|
49
|
+
export interface AgentDeprecation {
|
|
50
|
+
/** Release date (ISO) when the id was marked deprecated. */
|
|
51
|
+
readonly since: string;
|
|
52
|
+
/** Canonical replacement, when one exists. */
|
|
53
|
+
readonly replacedBy?: AgentName;
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
/** Pure command builder for one coding-agent CLI. */
|
|
38
57
|
export interface AgentShim {
|
|
39
|
-
/** Stable agent identifier. */
|
|
58
|
+
/** Stable canonical agent identifier. */
|
|
40
59
|
readonly name: AgentName;
|
|
41
60
|
/** Executable command name. */
|
|
42
61
|
readonly command: string;
|
|
43
62
|
/** Capability tier: 1 = direct CLI, 2 = gateway/TUI constrained. */
|
|
44
63
|
readonly tier: 1 | 2;
|
|
64
|
+
/** Non-canonical ids that resolve to this shim (aliases map to this canonical id). */
|
|
65
|
+
readonly aliases?: readonly string[];
|
|
66
|
+
/** Deprecation marker; resolving a deprecated id warns and reports canonical status. */
|
|
67
|
+
readonly deprecated?: AgentDeprecation;
|
|
45
68
|
/** Build a help-display command. */
|
|
46
69
|
getHelpCommand(): ShimCommand;
|
|
47
70
|
/** Build a version-detection command. */
|
|
@@ -90,6 +113,7 @@ const geminiShim: AgentShim = {
|
|
|
90
113
|
name: 'gemini',
|
|
91
114
|
command: 'gemini',
|
|
92
115
|
tier: 1,
|
|
116
|
+
deprecated: { since: '2026-06-20', replacedBy: 'antigravity-cli' },
|
|
93
117
|
getHelpCommand: () => ({ command: 'gemini', args: ['--help'] }),
|
|
94
118
|
getVersionCommand: () => ({ command: 'gemini', args: ['--version'] }),
|
|
95
119
|
getPromptCommand: (options) => {
|
|
@@ -136,13 +160,24 @@ const opencodeShim: AgentShim = {
|
|
|
136
160
|
getAuthCommand: () => ({ command: 'opencode', args: ['providers'] }),
|
|
137
161
|
};
|
|
138
162
|
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Antigravity CLI (`agy`) — the scriptable/headless successor to Gemini CLI
|
|
165
|
+
* (Antigravity 2.0 split, 2026-06). Tier-1: `-p`/`--print` one-shot, `--model`,
|
|
166
|
+
* `agy models`, `--continue` resumes the most recent session.
|
|
167
|
+
*/
|
|
168
|
+
const antigravityCliShim: AgentShim = {
|
|
169
|
+
name: 'antigravity-cli',
|
|
141
170
|
command: 'agy',
|
|
142
|
-
tier:
|
|
171
|
+
tier: 1,
|
|
172
|
+
aliases: ['antigravity'],
|
|
143
173
|
getHelpCommand: () => ({ command: 'agy', args: ['--help'] }),
|
|
144
174
|
getVersionCommand: () => ({ command: 'agy', args: ['--version'] }),
|
|
145
|
-
getPromptCommand: (options) =>
|
|
175
|
+
getPromptCommand: (options) => {
|
|
176
|
+
const args = ['-p', options.input ?? ''];
|
|
177
|
+
if (options.continue === true) args.push('--continue');
|
|
178
|
+
if (options.model !== undefined) args.push('--model', options.model);
|
|
179
|
+
return { command: 'agy', args };
|
|
180
|
+
},
|
|
146
181
|
getAuthCommand: () => null,
|
|
147
182
|
};
|
|
148
183
|
|
|
@@ -156,19 +191,70 @@ const openclawShim: AgentShim = {
|
|
|
156
191
|
getAuthCommand: () => ({ command: 'openclaw', args: ['health'] }),
|
|
157
192
|
};
|
|
158
193
|
|
|
159
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Hermes Agent (NousResearch) — OpenClaw-compatible coding agent. One-shot via
|
|
196
|
+
* `hermes chat -q <input>`; model/provider overrides; `hermes doctor` health probe.
|
|
197
|
+
*/
|
|
198
|
+
const hermesShim: AgentShim = {
|
|
199
|
+
name: 'hermes',
|
|
200
|
+
command: 'hermes',
|
|
201
|
+
tier: 1,
|
|
202
|
+
getHelpCommand: () => ({ command: 'hermes', args: ['chat', '--help'] }),
|
|
203
|
+
getVersionCommand: () => ({ command: 'hermes', args: ['--version'] }),
|
|
204
|
+
getPromptCommand: (options) => {
|
|
205
|
+
const args = ['chat', '-q', options.input ?? ''];
|
|
206
|
+
if (options.continue === true) args.push('--continue');
|
|
207
|
+
if (options.model !== undefined) args.push('-m', options.model);
|
|
208
|
+
return { command: 'hermes', args };
|
|
209
|
+
},
|
|
210
|
+
getAuthCommand: () => ({ command: 'hermes', args: ['doctor'] }),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* omp (oh-my-pi) — a Pi fork. argv is Pi-compatible for the one-shot surface
|
|
215
|
+
* (`--no-session`, `-p`, `-c`, `--model`, `--mode`); speaks Pi's `/skill:` slash dialect.
|
|
216
|
+
*/
|
|
217
|
+
const ompShim: AgentShim = {
|
|
218
|
+
name: 'omp',
|
|
219
|
+
command: 'omp',
|
|
220
|
+
tier: 1,
|
|
221
|
+
getHelpCommand: () => ({ command: 'omp', args: ['--help'] }),
|
|
222
|
+
getVersionCommand: () => ({ command: 'omp', args: ['--version'] }),
|
|
223
|
+
getPromptCommand: (options) => {
|
|
224
|
+
const args: string[] = [];
|
|
225
|
+
if (options.continue !== true) args.push('--no-session');
|
|
226
|
+
args.push('-p', options.input ?? '');
|
|
227
|
+
if (options.continue === true) args.push('-c');
|
|
228
|
+
if (options.model !== undefined) args.push('--model', options.model);
|
|
229
|
+
args.push('--mode', options.mode ?? 'text');
|
|
230
|
+
return { command: 'omp', args };
|
|
231
|
+
},
|
|
232
|
+
getAuthCommand: () => ({ command: 'omp', args: ['--list-models'] }),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** All bundled agent shims keyed by canonical agent name. */
|
|
160
236
|
export const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>> = {
|
|
161
237
|
claude: claudeShim,
|
|
162
238
|
codex: codexShim,
|
|
163
239
|
gemini: geminiShim,
|
|
164
240
|
pi: piShim,
|
|
165
241
|
opencode: opencodeShim,
|
|
166
|
-
antigravity:
|
|
242
|
+
'antigravity-cli': antigravityCliShim,
|
|
167
243
|
openclaw: openclawShim,
|
|
244
|
+
hermes: hermesShim,
|
|
245
|
+
omp: ompShim,
|
|
168
246
|
};
|
|
169
247
|
|
|
170
|
-
/** Tier-1 auto-selection priority. */
|
|
171
|
-
export const TIER1_PRIORITY: readonly AgentName[] = [
|
|
248
|
+
/** Tier-1 auto-selection priority. Deprecated ids are excluded. */
|
|
249
|
+
export const TIER1_PRIORITY: readonly AgentName[] = [
|
|
250
|
+
'pi',
|
|
251
|
+
'omp',
|
|
252
|
+
'codex',
|
|
253
|
+
'antigravity-cli',
|
|
254
|
+
'claude',
|
|
255
|
+
'hermes',
|
|
256
|
+
'opencode',
|
|
257
|
+
];
|
|
172
258
|
|
|
173
259
|
/** Display order for doctor and list commands. */
|
|
174
260
|
export const DISPLAY_ORDER: readonly AgentName[] = [
|
|
@@ -176,20 +262,73 @@ export const DISPLAY_ORDER: readonly AgentName[] = [
|
|
|
176
262
|
'codex',
|
|
177
263
|
'gemini',
|
|
178
264
|
'pi',
|
|
265
|
+
'omp',
|
|
179
266
|
'opencode',
|
|
180
|
-
'antigravity',
|
|
267
|
+
'antigravity-cli',
|
|
181
268
|
'openclaw',
|
|
269
|
+
'hermes',
|
|
182
270
|
];
|
|
183
271
|
|
|
184
272
|
/** Set of gateway/TUI-constrained agents. */
|
|
185
|
-
export const TIER2_AGENTS: ReadonlySet<AgentName> = new Set(['
|
|
273
|
+
export const TIER2_AGENTS: ReadonlySet<AgentName> = new Set(['openclaw']);
|
|
186
274
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
275
|
+
/** Logger for alias/deprecation resolution diagnostics. */
|
|
276
|
+
const logger = getLogger('ai-runner.shims');
|
|
277
|
+
|
|
278
|
+
/** Alias → canonical id, derived once from `AGENT_SHIMS[*].aliases`. */
|
|
279
|
+
const ALIAS_TO_CANONICAL: Readonly<Record<string, AgentName>> = Object.fromEntries(
|
|
280
|
+
Object.values(AGENT_SHIMS).flatMap((shim) => (shim.aliases ?? []).map((alias) => [alias, shim.name] as const)),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Resolve a canonical or alias id to its canonical `AgentName`.
|
|
285
|
+
*
|
|
286
|
+
* - Canonical ids resolve to themselves.
|
|
287
|
+
* - Aliases resolve to their canonical id (e.g. `'antigravity' → 'antigravity-cli'`).
|
|
288
|
+
* - Unknown ids resolve to `undefined`.
|
|
289
|
+
*
|
|
290
|
+
* Resolving a deprecated or aliased id emits exactly one `warn` through the
|
|
291
|
+
* logger seam and never throws.
|
|
292
|
+
*/
|
|
293
|
+
export function resolveAgentName(input: string): AgentName | undefined {
|
|
294
|
+
if (isCanonicalName(input)) return input;
|
|
295
|
+
const canonical = ALIAS_TO_CANONICAL[input];
|
|
296
|
+
if (canonical !== undefined) {
|
|
297
|
+
warnDeprecatedOrAlias(input, canonical);
|
|
298
|
+
return canonical;
|
|
299
|
+
}
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Return true when a value is a known canonical id or alias. Does not narrow;
|
|
304
|
+
* use `resolveAgentName()` to obtain the canonical `AgentName`. */
|
|
305
|
+
export function isAgentName(value: string): boolean {
|
|
306
|
+
return isCanonicalName(value) || value in ALIAS_TO_CANONICAL;
|
|
190
307
|
}
|
|
191
308
|
|
|
192
|
-
/** Look up a bundled agent shim. */
|
|
309
|
+
/** Look up a bundled agent shim, resolving aliases to the canonical shim. */
|
|
193
310
|
export function getAgentShim(agent: AgentName): AgentShim {
|
|
194
|
-
|
|
311
|
+
const canonical = resolveAgentName(agent);
|
|
312
|
+
if (canonical === undefined) {
|
|
313
|
+
throw new Error(`Unsupported agent: ${agent}`);
|
|
314
|
+
}
|
|
315
|
+
return AGENT_SHIMS[canonical];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function isCanonicalName(value: string): value is AgentName {
|
|
319
|
+
return Object.hasOwn(AGENT_SHIMS, value);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function warnDeprecatedOrAlias(input: string, canonical: AgentName): void {
|
|
323
|
+
const shim = AGENT_SHIMS[canonical];
|
|
324
|
+
if (shim.deprecated !== undefined) {
|
|
325
|
+
const replacement = shim.deprecated.replacedBy ?? canonical;
|
|
326
|
+
logger.warn(`agent '${input}' is deprecated (since ${shim.deprecated.since}); use '${replacement}'`, {
|
|
327
|
+
input,
|
|
328
|
+
canonical,
|
|
329
|
+
replacedBy: replacement,
|
|
330
|
+
});
|
|
331
|
+
} else {
|
|
332
|
+
logger.warn(`agent '${input}' is an alias; resolving to canonical '${canonical}'`, { input, canonical });
|
|
333
|
+
}
|
|
195
334
|
}
|
package/src/ai-runner.ts
CHANGED
|
@@ -113,7 +113,9 @@ export class AiRunner {
|
|
|
113
113
|
|
|
114
114
|
/** Build an agent prompt command without executing it. */
|
|
115
115
|
buildPromptCommand(agent: AgentName, promptOptions: PromptOptions, options: AgentRunOptions = {}): ShimCommand {
|
|
116
|
-
return
|
|
116
|
+
return buildAgentCommand(agent, promptOptions, {
|
|
117
|
+
workspace: options.cwd ?? this.defaultCwd ?? getProcessCwd(),
|
|
118
|
+
});
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
/** Run an agent authentication command, or return null when unsupported. */
|
|
@@ -164,26 +166,6 @@ export class AiRunner {
|
|
|
164
166
|
durationMs: result.durationMs,
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
|
-
|
|
168
|
-
private withIdentityPreamble(
|
|
169
|
-
agent: AgentName,
|
|
170
|
-
promptOptions: PromptOptions,
|
|
171
|
-
options: AgentRunOptions,
|
|
172
|
-
): PromptOptions {
|
|
173
|
-
if (!hasIdentityOptions(promptOptions)) return promptOptions;
|
|
174
|
-
const workspace = options.cwd ?? this.defaultCwd ?? getProcessCwd();
|
|
175
|
-
const preamble = buildIdentityPreamble({
|
|
176
|
-
agentId: agent,
|
|
177
|
-
agentType: agent,
|
|
178
|
-
workspace,
|
|
179
|
-
purpose: promptOptions.purpose,
|
|
180
|
-
systemPrompt: promptOptions.systemPrompt,
|
|
181
|
-
taskId: promptOptions.taskId,
|
|
182
|
-
peers: promptOptions.peers,
|
|
183
|
-
});
|
|
184
|
-
const input = promptOptions.input === undefined ? preamble : `${preamble}\n${promptOptions.input}`;
|
|
185
|
-
return { ...promptOptions, input };
|
|
186
|
-
}
|
|
187
169
|
}
|
|
188
170
|
|
|
189
171
|
function hasIdentityOptions(options: PromptOptions): boolean {
|
|
@@ -194,3 +176,36 @@ function hasIdentityOptions(options: PromptOptions): boolean {
|
|
|
194
176
|
(options.peers !== undefined && options.peers.length > 0)
|
|
195
177
|
);
|
|
196
178
|
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Shared command-build seam: identity preamble + shim dispatch. Both the
|
|
182
|
+
* one-shot path (`AiRunner.buildPromptCommand`) and the team-mode path
|
|
183
|
+
* (`TeamOrchestrator.startAgent`) resolve argv through this single function
|
|
184
|
+
* so equivalent `PromptOptions` produce equivalent argv (R5 parity).
|
|
185
|
+
*/
|
|
186
|
+
export function buildAgentCommand(
|
|
187
|
+
agent: AgentName,
|
|
188
|
+
promptOptions: PromptOptions,
|
|
189
|
+
context: { workspace: string },
|
|
190
|
+
): ShimCommand {
|
|
191
|
+
return getAgentShim(agent).getPromptCommand(applyIdentityPreamble(agent, promptOptions, context));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function applyIdentityPreamble(
|
|
195
|
+
agent: AgentName,
|
|
196
|
+
promptOptions: PromptOptions,
|
|
197
|
+
context: { workspace: string },
|
|
198
|
+
): PromptOptions {
|
|
199
|
+
if (!hasIdentityOptions(promptOptions)) return promptOptions;
|
|
200
|
+
const preamble = buildIdentityPreamble({
|
|
201
|
+
agentId: agent,
|
|
202
|
+
agentType: agent,
|
|
203
|
+
workspace: context.workspace,
|
|
204
|
+
purpose: promptOptions.purpose,
|
|
205
|
+
systemPrompt: promptOptions.systemPrompt,
|
|
206
|
+
taskId: promptOptions.taskId,
|
|
207
|
+
peers: promptOptions.peers,
|
|
208
|
+
});
|
|
209
|
+
const input = promptOptions.input === undefined ? preamble : `${preamble}\n${promptOptions.input}`;
|
|
210
|
+
return { ...promptOptions, input };
|
|
211
|
+
}
|
package/src/doctor-runner.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLogger, type Logger } from '@gobing-ai/ts-infra';
|
|
2
|
-
import { getProcessEnv, joinPath
|
|
2
|
+
import { createNodeFileSystem, type FileSystem, getProcessEnv, joinPath } from '@gobing-ai/ts-runtime';
|
|
3
3
|
import { AgentDetector, type DetectedAgent } from './agent-detector';
|
|
4
|
-
import { type AgentName, DISPLAY_ORDER,
|
|
4
|
+
import { type AgentName, DISPLAY_ORDER, resolveAgentName, TIER2_AGENTS } from './agents/shims';
|
|
5
5
|
import { type AgentRunResult, AiRunner } from './ai-runner';
|
|
6
6
|
|
|
7
7
|
/** Health-check result for one coding agent. */
|
|
@@ -20,6 +20,10 @@ export interface DoctorResult {
|
|
|
20
20
|
tier: 1 | 2;
|
|
21
21
|
/** Agent-specific channels or models when available. */
|
|
22
22
|
channels: string[];
|
|
23
|
+
/** True when the resolved canonical id is marked deprecated. */
|
|
24
|
+
deprecated?: boolean;
|
|
25
|
+
/** Canonical replacement when the resolved id is deprecated, if any. */
|
|
26
|
+
replacedBy?: AgentName;
|
|
23
27
|
/** Probe error when unavailable. */
|
|
24
28
|
error: string | null;
|
|
25
29
|
}
|
|
@@ -36,6 +40,8 @@ export interface DoctorRunnerOptions {
|
|
|
36
40
|
env?: Record<string, string | undefined>;
|
|
37
41
|
/** Logger for health-check diagnostics. Defaults to `getLogger('doctor')`. */
|
|
38
42
|
logger?: Logger;
|
|
43
|
+
/** Filesystem for auth-file checks. Defaults to `createNodeFileSystem()`; inject to test without disk. */
|
|
44
|
+
fileSystem?: FileSystem;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
@@ -61,6 +67,14 @@ const AUTH_PATTERNS: Partial<Record<AgentName, { positive: RegExp; negative: Reg
|
|
|
61
67
|
positive: /\S/,
|
|
62
68
|
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|unauthenticated|no[\s_-]+providers?/i,
|
|
63
69
|
},
|
|
70
|
+
omp: {
|
|
71
|
+
positive: /\S/,
|
|
72
|
+
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|unauthenticated|no[\s_-]+providers?/i,
|
|
73
|
+
},
|
|
74
|
+
hermes: {
|
|
75
|
+
positive: /(^|[^a-z])ok([^a-z]|$)|healthy|configured|ready/i,
|
|
76
|
+
negative: /not[\s_-]*(configured|healthy|ok)|unhealthy|missing[\s_-]+dependenc|error|failed/i,
|
|
77
|
+
},
|
|
64
78
|
};
|
|
65
79
|
|
|
66
80
|
/** True when a value is a defined, non-blank string. */
|
|
@@ -74,7 +88,7 @@ export class DoctorRunner {
|
|
|
74
88
|
private readonly runner: AiRunner;
|
|
75
89
|
private readonly timeout: number;
|
|
76
90
|
private readonly env: Record<string, string | undefined>;
|
|
77
|
-
private readonly fs
|
|
91
|
+
private readonly fs: FileSystem;
|
|
78
92
|
private readonly logger: Logger;
|
|
79
93
|
|
|
80
94
|
constructor(options: DoctorRunnerOptions = {}) {
|
|
@@ -82,6 +96,7 @@ export class DoctorRunner {
|
|
|
82
96
|
this.detector = options.agentDetector ?? new AgentDetector({ runner: this.runner });
|
|
83
97
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
84
98
|
this.env = options.env ?? getProcessEnv();
|
|
99
|
+
this.fs = options.fileSystem ?? createNodeFileSystem();
|
|
85
100
|
this.logger = options.logger ?? getLogger('doctor');
|
|
86
101
|
}
|
|
87
102
|
|
|
@@ -110,10 +125,10 @@ export class DoctorRunner {
|
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
private async buildResult(detected: DetectedAgent): Promise<DoctorResult> {
|
|
113
|
-
const
|
|
128
|
+
const canonical = resolveAgentName(detected.name) ?? null;
|
|
129
|
+
const tier = canonical !== null && TIER2_AGENTS.has(canonical) ? 2 : 1;
|
|
114
130
|
this.logger.debug('checking agent', { agent: detected.name, installed: detected.installed, tier });
|
|
115
|
-
const authenticated =
|
|
116
|
-
detected.installed && isAgentName(detected.name) ? await this.checkAuth(detected.name) : false;
|
|
131
|
+
const authenticated = detected.installed && canonical !== null ? await this.checkAuth(canonical) : false;
|
|
117
132
|
return {
|
|
118
133
|
agent: detected.name,
|
|
119
134
|
installed: detected.installed,
|
|
@@ -122,6 +137,8 @@ export class DoctorRunner {
|
|
|
122
137
|
usable: detected.installed && detected.version !== null && authenticated,
|
|
123
138
|
tier,
|
|
124
139
|
channels: detected.channels,
|
|
140
|
+
deprecated: detected.deprecated,
|
|
141
|
+
replacedBy: detected.replacedBy,
|
|
125
142
|
error: detected.error,
|
|
126
143
|
};
|
|
127
144
|
}
|
|
@@ -130,9 +147,12 @@ export class DoctorRunner {
|
|
|
130
147
|
const home = this.env.HOME || this.env.USERPROFILE || '';
|
|
131
148
|
if (agent === 'gemini') return this.geminiSettingsContainCredentials(home);
|
|
132
149
|
if (agent === 'codex') return this.checkCodexAuth(home);
|
|
133
|
-
// pi
|
|
134
|
-
// rather than mere presence (an empty export is not a usable credential).
|
|
135
|
-
if (
|
|
150
|
+
// pi and omp read provider keys from the environment; require a non-empty
|
|
151
|
+
// value rather than mere presence (an empty export is not a usable credential).
|
|
152
|
+
if (
|
|
153
|
+
(agent === 'pi' || agent === 'omp') &&
|
|
154
|
+
(isNonEmpty(this.env.GOOGLE_API_KEY) || isNonEmpty(this.env.ANTHROPIC_API_KEY))
|
|
155
|
+
)
|
|
136
156
|
return true;
|
|
137
157
|
return (await this.probeAuthOutput(agent)) === true;
|
|
138
158
|
}
|
package/src/slash-command.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function translateSlashCommand(agent: AgentName, input: string): string {
|
|
|
35
35
|
case 'codex':
|
|
36
36
|
return `$${plugin}-${command}${suffix}`;
|
|
37
37
|
case 'pi':
|
|
38
|
+
case 'omp':
|
|
38
39
|
return `/skill:${plugin}-${command}${suffix}`;
|
|
39
40
|
default:
|
|
40
41
|
return `/${plugin}-${command}${suffix}`;
|
package/src/team-orchestrator.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
|
|
|
2
2
|
import { EventBus } from '@gobing-ai/ts-infra';
|
|
3
3
|
import type { AgentSpec } from './agent-spec';
|
|
4
4
|
import { loadAgentSpecs } from './agent-spec';
|
|
5
|
-
import { type AgentName,
|
|
5
|
+
import { type AgentName, resolveAgentName } from './agents/shims';
|
|
6
|
+
import { buildAgentCommand } from './ai-runner';
|
|
6
7
|
import type { AgentEvents } from './events';
|
|
7
|
-
import { buildIdentityPreamble } from './identity';
|
|
8
8
|
import { formatMessage } from './messages';
|
|
9
9
|
import { TeamAgentProcess } from './team-agent-process';
|
|
10
10
|
|
|
@@ -35,38 +35,33 @@ export class TeamOrchestrator {
|
|
|
35
35
|
this.events = options.events ?? new EventBus<AgentEvents>();
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
loadSpecs(): AgentSpec[] {
|
|
39
|
-
this.specs = loadAgentSpecs(this.configDir);
|
|
38
|
+
async loadSpecs(): Promise<AgentSpec[]> {
|
|
39
|
+
this.specs = await loadAgentSpecs(this.configDir);
|
|
40
40
|
return [...this.specs];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
getSpec(id: string): AgentSpec | undefined {
|
|
44
|
-
if (this.specs.length === 0) this.loadSpecs();
|
|
43
|
+
async getSpec(id: string): Promise<AgentSpec | undefined> {
|
|
44
|
+
if (this.specs.length === 0) await this.loadSpecs();
|
|
45
45
|
return this.specs.find((spec) => spec.id === id);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async startAgent(id: string): Promise<TeamAgentProcess> {
|
|
49
|
-
const spec = this.requireSpec(id);
|
|
49
|
+
const spec = await this.requireSpec(id);
|
|
50
50
|
const agentType = this.requireAgentName(spec.type);
|
|
51
|
-
const peers = this.getPeerSpecs(spec.workspace, spec.id).map((peer) => ({
|
|
51
|
+
const peers = (await this.getPeerSpecs(spec.workspace, spec.id)).map((peer) => ({
|
|
52
52
|
id: peer.id,
|
|
53
53
|
type: peer.type,
|
|
54
54
|
purpose: peer.purpose,
|
|
55
55
|
}));
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
input: identityPreamble,
|
|
66
|
-
purpose: spec.purpose,
|
|
67
|
-
systemPrompt: typeof spec.config.systemPrompt === 'string' ? spec.config.systemPrompt : undefined,
|
|
68
|
-
peers,
|
|
69
|
-
});
|
|
56
|
+
const command = buildAgentCommand(
|
|
57
|
+
agentType,
|
|
58
|
+
{
|
|
59
|
+
purpose: spec.purpose,
|
|
60
|
+
systemPrompt: typeof spec.config.systemPrompt === 'string' ? spec.config.systemPrompt : undefined,
|
|
61
|
+
peers,
|
|
62
|
+
},
|
|
63
|
+
{ workspace: spec.workspace },
|
|
64
|
+
);
|
|
70
65
|
const process = this.processFactory({
|
|
71
66
|
spec,
|
|
72
67
|
command: [command.command, ...command.args],
|
|
@@ -108,14 +103,14 @@ export class TeamOrchestrator {
|
|
|
108
103
|
return new Map(this.running);
|
|
109
104
|
}
|
|
110
105
|
|
|
111
|
-
getAgentStatus(id: string): 'running' | 'stopped' | 'errored' | 'unknown' {
|
|
106
|
+
async getAgentStatus(id: string): Promise<'running' | 'stopped' | 'errored' | 'unknown'> {
|
|
112
107
|
const process = this.running.get(id);
|
|
113
|
-
if (process === undefined) return this.getSpec(id) === undefined ? 'unknown' : 'stopped';
|
|
108
|
+
if (process === undefined) return (await this.getSpec(id)) === undefined ? 'unknown' : 'stopped';
|
|
114
109
|
return process.getStatus();
|
|
115
110
|
}
|
|
116
111
|
|
|
117
|
-
getPeerSpecs(workspace: string, excludeId?: string): AgentSpec[] {
|
|
118
|
-
if (this.specs.length === 0) this.loadSpecs();
|
|
112
|
+
async getPeerSpecs(workspace: string, excludeId?: string): Promise<AgentSpec[]> {
|
|
113
|
+
if (this.specs.length === 0) await this.loadSpecs();
|
|
119
114
|
return this.specs.filter((spec) => spec.workspace === workspace && spec.id !== excludeId);
|
|
120
115
|
}
|
|
121
116
|
|
|
@@ -128,15 +123,16 @@ export class TeamOrchestrator {
|
|
|
128
123
|
return () => this.events.off(event, listener);
|
|
129
124
|
}
|
|
130
125
|
|
|
131
|
-
private requireSpec(id: string): AgentSpec {
|
|
132
|
-
const spec = this.getSpec(id);
|
|
126
|
+
private async requireSpec(id: string): Promise<AgentSpec> {
|
|
127
|
+
const spec = await this.getSpec(id);
|
|
133
128
|
if (spec === undefined) throw new Error(`Agent spec not found: ${id}`);
|
|
134
129
|
return spec;
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
private requireAgentName(type: string): AgentName {
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
const canonical = resolveAgentName(type);
|
|
134
|
+
if (canonical === undefined) throw new Error(`Unsupported agent type: ${type}`);
|
|
135
|
+
return canonical;
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
private async injectPendingMessages(process: TeamAgentProcess): Promise<boolean> {
|