@gobing-ai/ts-ai-runner 0.3.19 → 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/dist/ai-runner.js
CHANGED
|
@@ -50,7 +50,9 @@ export class AiRunner {
|
|
|
50
50
|
}
|
|
51
51
|
/** Build an agent prompt command without executing it. */
|
|
52
52
|
buildPromptCommand(agent, promptOptions, options = {}) {
|
|
53
|
-
return
|
|
53
|
+
return buildAgentCommand(agent, promptOptions, {
|
|
54
|
+
workspace: options.cwd ?? this.defaultCwd ?? getProcessCwd(),
|
|
55
|
+
});
|
|
54
56
|
}
|
|
55
57
|
/** Run an agent authentication command, or return null when unsupported. */
|
|
56
58
|
runAuthCommand(agent, options = {}) {
|
|
@@ -93,22 +95,6 @@ export class AiRunner {
|
|
|
93
95
|
durationMs: result.durationMs,
|
|
94
96
|
};
|
|
95
97
|
}
|
|
96
|
-
withIdentityPreamble(agent, promptOptions, options) {
|
|
97
|
-
if (!hasIdentityOptions(promptOptions))
|
|
98
|
-
return promptOptions;
|
|
99
|
-
const workspace = options.cwd ?? this.defaultCwd ?? getProcessCwd();
|
|
100
|
-
const preamble = buildIdentityPreamble({
|
|
101
|
-
agentId: agent,
|
|
102
|
-
agentType: agent,
|
|
103
|
-
workspace,
|
|
104
|
-
purpose: promptOptions.purpose,
|
|
105
|
-
systemPrompt: promptOptions.systemPrompt,
|
|
106
|
-
taskId: promptOptions.taskId,
|
|
107
|
-
peers: promptOptions.peers,
|
|
108
|
-
});
|
|
109
|
-
const input = promptOptions.input === undefined ? preamble : `${preamble}\n${promptOptions.input}`;
|
|
110
|
-
return { ...promptOptions, input };
|
|
111
|
-
}
|
|
112
98
|
}
|
|
113
99
|
function hasIdentityOptions(options) {
|
|
114
100
|
return (options.purpose !== undefined ||
|
|
@@ -116,3 +102,27 @@ function hasIdentityOptions(options) {
|
|
|
116
102
|
options.taskId !== undefined ||
|
|
117
103
|
(options.peers !== undefined && options.peers.length > 0));
|
|
118
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Shared command-build seam: identity preamble + shim dispatch. Both the
|
|
107
|
+
* one-shot path (`AiRunner.buildPromptCommand`) and the team-mode path
|
|
108
|
+
* (`TeamOrchestrator.startAgent`) resolve argv through this single function
|
|
109
|
+
* so equivalent `PromptOptions` produce equivalent argv (R5 parity).
|
|
110
|
+
*/
|
|
111
|
+
export function buildAgentCommand(agent, promptOptions, context) {
|
|
112
|
+
return getAgentShim(agent).getPromptCommand(applyIdentityPreamble(agent, promptOptions, context));
|
|
113
|
+
}
|
|
114
|
+
function applyIdentityPreamble(agent, promptOptions, context) {
|
|
115
|
+
if (!hasIdentityOptions(promptOptions))
|
|
116
|
+
return promptOptions;
|
|
117
|
+
const preamble = buildIdentityPreamble({
|
|
118
|
+
agentId: agent,
|
|
119
|
+
agentType: agent,
|
|
120
|
+
workspace: context.workspace,
|
|
121
|
+
purpose: promptOptions.purpose,
|
|
122
|
+
systemPrompt: promptOptions.systemPrompt,
|
|
123
|
+
taskId: promptOptions.taskId,
|
|
124
|
+
peers: promptOptions.peers,
|
|
125
|
+
});
|
|
126
|
+
const input = promptOptions.input === undefined ? preamble : `${preamble}\n${promptOptions.input}`;
|
|
127
|
+
return { ...promptOptions, input };
|
|
128
|
+
}
|
package/dist/doctor-runner.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Logger } from '@gobing-ai/ts-infra';
|
|
2
|
+
import { type FileSystem } from '@gobing-ai/ts-runtime';
|
|
2
3
|
import { AgentDetector } from './agent-detector';
|
|
4
|
+
import { type AgentName } from './agents/shims';
|
|
3
5
|
import { AiRunner } from './ai-runner';
|
|
4
6
|
/** Health-check result for one coding agent. */
|
|
5
7
|
export interface DoctorResult {
|
|
@@ -17,6 +19,10 @@ export interface DoctorResult {
|
|
|
17
19
|
tier: 1 | 2;
|
|
18
20
|
/** Agent-specific channels or models when available. */
|
|
19
21
|
channels: string[];
|
|
22
|
+
/** True when the resolved canonical id is marked deprecated. */
|
|
23
|
+
deprecated?: boolean;
|
|
24
|
+
/** Canonical replacement when the resolved id is deprecated, if any. */
|
|
25
|
+
replacedBy?: AgentName;
|
|
20
26
|
/** Probe error when unavailable. */
|
|
21
27
|
error: string | null;
|
|
22
28
|
}
|
|
@@ -32,6 +38,8 @@ export interface DoctorRunnerOptions {
|
|
|
32
38
|
env?: Record<string, string | undefined>;
|
|
33
39
|
/** Logger for health-check diagnostics. Defaults to `getLogger('doctor')`. */
|
|
34
40
|
logger?: Logger;
|
|
41
|
+
/** Filesystem for auth-file checks. Defaults to `createNodeFileSystem()`; inject to test without disk. */
|
|
42
|
+
fileSystem?: FileSystem;
|
|
35
43
|
}
|
|
36
44
|
/** Runs installation and auth health checks for supported coding agents. */
|
|
37
45
|
export declare class DoctorRunner {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAwB,KAAK,UAAU,EAA2B,MAAM,uBAAuB,CAAC;AACvG,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,KAAK,SAAS,EAAiD,MAAM,gBAAgB,CAAC;AAC/F,OAAO,EAAuB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D,gDAAgD;AAChD,MAAM,WAAW,YAAY;IACzB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+CAA+C;IAC/C,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACZ,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC,uBAAuB;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qBAAqB;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAwCD,4EAA4E;AAC5E,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,GAAE,mBAAwB;IAS7C,kDAAkD;IAC5C,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAkBvC,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAIpC,WAAW;YAmBX,SAAS;YAcT,cAAc;YASd,gCAAgC;YAQhC,eAAe;IAY7B,yDAAyD;YAC3C,eAAe;CAKhC"}
|
package/dist/doctor-runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLogger } from '@gobing-ai/ts-infra';
|
|
2
|
-
import { getProcessEnv, joinPath
|
|
2
|
+
import { createNodeFileSystem, getProcessEnv, joinPath } from '@gobing-ai/ts-runtime';
|
|
3
3
|
import { AgentDetector } from './agent-detector.js';
|
|
4
|
-
import { DISPLAY_ORDER,
|
|
4
|
+
import { DISPLAY_ORDER, resolveAgentName, TIER2_AGENTS } from './agents/shims.js';
|
|
5
5
|
import { AiRunner } from './ai-runner.js';
|
|
6
6
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
7
7
|
const AUTH_PATTERNS = {
|
|
@@ -25,6 +25,14 @@ const AUTH_PATTERNS = {
|
|
|
25
25
|
positive: /\S/,
|
|
26
26
|
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|unauthenticated|no[\s_-]+providers?/i,
|
|
27
27
|
},
|
|
28
|
+
omp: {
|
|
29
|
+
positive: /\S/,
|
|
30
|
+
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|unauthenticated|no[\s_-]+providers?/i,
|
|
31
|
+
},
|
|
32
|
+
hermes: {
|
|
33
|
+
positive: /(^|[^a-z])ok([^a-z]|$)|healthy|configured|ready/i,
|
|
34
|
+
negative: /not[\s_-]*(configured|healthy|ok)|unhealthy|missing[\s_-]+dependenc|error|failed/i,
|
|
35
|
+
},
|
|
28
36
|
};
|
|
29
37
|
/** True when a value is a defined, non-blank string. */
|
|
30
38
|
function isNonEmpty(value) {
|
|
@@ -36,13 +44,14 @@ export class DoctorRunner {
|
|
|
36
44
|
runner;
|
|
37
45
|
timeout;
|
|
38
46
|
env;
|
|
39
|
-
fs
|
|
47
|
+
fs;
|
|
40
48
|
logger;
|
|
41
49
|
constructor(options = {}) {
|
|
42
50
|
this.runner = options.runner ?? new AiRunner();
|
|
43
51
|
this.detector = options.agentDetector ?? new AgentDetector({ runner: this.runner });
|
|
44
52
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
45
53
|
this.env = options.env ?? getProcessEnv();
|
|
54
|
+
this.fs = options.fileSystem ?? createNodeFileSystem();
|
|
46
55
|
this.logger = options.logger ?? getLogger('doctor');
|
|
47
56
|
}
|
|
48
57
|
/** Run a health check on all supported agents. */
|
|
@@ -62,9 +71,10 @@ export class DoctorRunner {
|
|
|
62
71
|
return this.buildResult(await this.detector.detectOne(agent));
|
|
63
72
|
}
|
|
64
73
|
async buildResult(detected) {
|
|
65
|
-
const
|
|
74
|
+
const canonical = resolveAgentName(detected.name) ?? null;
|
|
75
|
+
const tier = canonical !== null && TIER2_AGENTS.has(canonical) ? 2 : 1;
|
|
66
76
|
this.logger.debug('checking agent', { agent: detected.name, installed: detected.installed, tier });
|
|
67
|
-
const authenticated = detected.installed &&
|
|
77
|
+
const authenticated = detected.installed && canonical !== null ? await this.checkAuth(canonical) : false;
|
|
68
78
|
return {
|
|
69
79
|
agent: detected.name,
|
|
70
80
|
installed: detected.installed,
|
|
@@ -73,6 +83,8 @@ export class DoctorRunner {
|
|
|
73
83
|
usable: detected.installed && detected.version !== null && authenticated,
|
|
74
84
|
tier,
|
|
75
85
|
channels: detected.channels,
|
|
86
|
+
deprecated: detected.deprecated,
|
|
87
|
+
replacedBy: detected.replacedBy,
|
|
76
88
|
error: detected.error,
|
|
77
89
|
};
|
|
78
90
|
}
|
|
@@ -82,9 +94,10 @@ export class DoctorRunner {
|
|
|
82
94
|
return this.geminiSettingsContainCredentials(home);
|
|
83
95
|
if (agent === 'codex')
|
|
84
96
|
return this.checkCodexAuth(home);
|
|
85
|
-
// pi
|
|
86
|
-
// rather than mere presence (an empty export is not a usable credential).
|
|
87
|
-
if (agent === 'pi'
|
|
97
|
+
// pi and omp read provider keys from the environment; require a non-empty
|
|
98
|
+
// value rather than mere presence (an empty export is not a usable credential).
|
|
99
|
+
if ((agent === 'pi' || agent === 'omp') &&
|
|
100
|
+
(isNonEmpty(this.env.GOOGLE_API_KEY) || isNonEmpty(this.env.ANTHROPIC_API_KEY)))
|
|
88
101
|
return true;
|
|
89
102
|
return (await this.probeAuthOutput(agent)) === true;
|
|
90
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../src/slash-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAShD,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../src/slash-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAShD,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB7E"}
|
package/dist/slash-command.js
CHANGED
|
@@ -21,15 +21,15 @@ export declare class TeamOrchestrator {
|
|
|
21
21
|
private readonly processFactory;
|
|
22
22
|
private readonly events;
|
|
23
23
|
constructor(configDir: string, inbox: InboxMessageDao, options?: TeamOrchestratorOptions);
|
|
24
|
-
loadSpecs(): AgentSpec[]
|
|
25
|
-
getSpec(id: string): AgentSpec | undefined
|
|
24
|
+
loadSpecs(): Promise<AgentSpec[]>;
|
|
25
|
+
getSpec(id: string): Promise<AgentSpec | undefined>;
|
|
26
26
|
startAgent(id: string): Promise<TeamAgentProcess>;
|
|
27
27
|
stopAgent(id: string): Promise<void>;
|
|
28
28
|
restartAgent(id: string): Promise<TeamAgentProcess>;
|
|
29
29
|
sendMessage(fromId: string | null, toId: string, body: string, inReplyTo?: string): Promise<string>;
|
|
30
30
|
getRunningAgents(): Map<string, TeamAgentProcess>;
|
|
31
|
-
getAgentStatus(id: string): 'running' | 'stopped' | 'errored' | 'unknown'
|
|
32
|
-
getPeerSpecs(workspace: string, excludeId?: string): AgentSpec[]
|
|
31
|
+
getAgentStatus(id: string): Promise<'running' | 'stopped' | 'errored' | 'unknown'>;
|
|
32
|
+
getPeerSpecs(workspace: string, excludeId?: string): Promise<AgentSpec[]>;
|
|
33
33
|
stopAll(): Promise<void>;
|
|
34
34
|
on<K extends keyof AgentEvents>(event: K, listener: AgentEvents[K]): () => void;
|
|
35
35
|
private requireSpec;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team-orchestrator.d.ts","sourceRoot":"","sources":["../src/team-orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"team-orchestrator.d.ts","sourceRoot":"","sources":["../src/team-orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,gBAAgB,CAAC;AAE5G,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACpC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAOrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAP1B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,EACvC,OAAO,GAAE,uBAA4B;IAMnC,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAKjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAKnD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiCjD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAKnD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQzG,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAI3C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAMlF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;YAKjE,WAAW;IAMzB,OAAO,CAAC,gBAAgB;YAMV,qBAAqB;YAIrB,UAAU;CAa3B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventBus } from '@gobing-ai/ts-infra';
|
|
2
2
|
import { loadAgentSpecs } from './agent-spec.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { resolveAgentName } from './agents/shims.js';
|
|
4
|
+
import { buildAgentCommand } from './ai-runner.js';
|
|
5
5
|
import { formatMessage } from './messages.js';
|
|
6
6
|
import { TeamAgentProcess } from './team-agent-process.js';
|
|
7
7
|
/**
|
|
@@ -21,37 +21,28 @@ export class TeamOrchestrator {
|
|
|
21
21
|
this.processFactory = options.processFactory ?? ((processOptions) => new TeamAgentProcess(processOptions));
|
|
22
22
|
this.events = options.events ?? new EventBus();
|
|
23
23
|
}
|
|
24
|
-
loadSpecs() {
|
|
25
|
-
this.specs = loadAgentSpecs(this.configDir);
|
|
24
|
+
async loadSpecs() {
|
|
25
|
+
this.specs = await loadAgentSpecs(this.configDir);
|
|
26
26
|
return [...this.specs];
|
|
27
27
|
}
|
|
28
|
-
getSpec(id) {
|
|
28
|
+
async getSpec(id) {
|
|
29
29
|
if (this.specs.length === 0)
|
|
30
|
-
this.loadSpecs();
|
|
30
|
+
await this.loadSpecs();
|
|
31
31
|
return this.specs.find((spec) => spec.id === id);
|
|
32
32
|
}
|
|
33
33
|
async startAgent(id) {
|
|
34
|
-
const spec = this.requireSpec(id);
|
|
34
|
+
const spec = await this.requireSpec(id);
|
|
35
35
|
const agentType = this.requireAgentName(spec.type);
|
|
36
|
-
const peers = this.getPeerSpecs(spec.workspace, spec.id).map((peer) => ({
|
|
36
|
+
const peers = (await this.getPeerSpecs(spec.workspace, spec.id)).map((peer) => ({
|
|
37
37
|
id: peer.id,
|
|
38
38
|
type: peer.type,
|
|
39
39
|
purpose: peer.purpose,
|
|
40
40
|
}));
|
|
41
|
-
const
|
|
42
|
-
agentId: spec.id,
|
|
43
|
-
agentType: spec.type,
|
|
44
|
-
workspace: spec.workspace,
|
|
41
|
+
const command = buildAgentCommand(agentType, {
|
|
45
42
|
purpose: spec.purpose,
|
|
46
43
|
systemPrompt: typeof spec.config.systemPrompt === 'string' ? spec.config.systemPrompt : undefined,
|
|
47
44
|
peers,
|
|
48
|
-
});
|
|
49
|
-
const command = getAgentShim(agentType).getPromptCommand({
|
|
50
|
-
input: identityPreamble,
|
|
51
|
-
purpose: spec.purpose,
|
|
52
|
-
systemPrompt: typeof spec.config.systemPrompt === 'string' ? spec.config.systemPrompt : undefined,
|
|
53
|
-
peers,
|
|
54
|
-
});
|
|
45
|
+
}, { workspace: spec.workspace });
|
|
55
46
|
const process = this.processFactory({
|
|
56
47
|
spec,
|
|
57
48
|
command: [command.command, ...command.args],
|
|
@@ -89,15 +80,15 @@ export class TeamOrchestrator {
|
|
|
89
80
|
getRunningAgents() {
|
|
90
81
|
return new Map(this.running);
|
|
91
82
|
}
|
|
92
|
-
getAgentStatus(id) {
|
|
83
|
+
async getAgentStatus(id) {
|
|
93
84
|
const process = this.running.get(id);
|
|
94
85
|
if (process === undefined)
|
|
95
|
-
return this.getSpec(id) === undefined ? 'unknown' : 'stopped';
|
|
86
|
+
return (await this.getSpec(id)) === undefined ? 'unknown' : 'stopped';
|
|
96
87
|
return process.getStatus();
|
|
97
88
|
}
|
|
98
|
-
getPeerSpecs(workspace, excludeId) {
|
|
89
|
+
async getPeerSpecs(workspace, excludeId) {
|
|
99
90
|
if (this.specs.length === 0)
|
|
100
|
-
this.loadSpecs();
|
|
91
|
+
await this.loadSpecs();
|
|
101
92
|
return this.specs.filter((spec) => spec.workspace === workspace && spec.id !== excludeId);
|
|
102
93
|
}
|
|
103
94
|
async stopAll() {
|
|
@@ -107,16 +98,17 @@ export class TeamOrchestrator {
|
|
|
107
98
|
this.events.on(event, listener);
|
|
108
99
|
return () => this.events.off(event, listener);
|
|
109
100
|
}
|
|
110
|
-
requireSpec(id) {
|
|
111
|
-
const spec = this.getSpec(id);
|
|
101
|
+
async requireSpec(id) {
|
|
102
|
+
const spec = await this.getSpec(id);
|
|
112
103
|
if (spec === undefined)
|
|
113
104
|
throw new Error(`Agent spec not found: ${id}`);
|
|
114
105
|
return spec;
|
|
115
106
|
}
|
|
116
107
|
requireAgentName(type) {
|
|
117
|
-
|
|
108
|
+
const canonical = resolveAgentName(type);
|
|
109
|
+
if (canonical === undefined)
|
|
118
110
|
throw new Error(`Unsupported agent type: ${type}`);
|
|
119
|
-
return
|
|
111
|
+
return canonical;
|
|
120
112
|
}
|
|
121
113
|
async injectPendingMessages(process) {
|
|
122
114
|
return this.flushInbox(process, 'startup stdin injection failed');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-ai-runner",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
4
4
|
"description": "@gobing-ai/ts-ai-runner — Coding-agent shims, detection, doctor checks, and prompt execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-ai-runner-v<version> && git push --tags' && exit 1"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@gobing-ai/ts-db": "^0.3.
|
|
51
|
-
"@gobing-ai/ts-infra": "^0.3.
|
|
52
|
-
"@gobing-ai/ts-runtime": "^0.3.
|
|
50
|
+
"@gobing-ai/ts-db": "^0.3.21",
|
|
51
|
+
"@gobing-ai/ts-infra": "^0.3.21",
|
|
52
|
+
"@gobing-ai/ts-runtime": "^0.3.21"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/bun": "1.3.14"
|
package/src/agent-detector.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type AgentName, DISPLAY_ORDER, getAgentShim,
|
|
1
|
+
import { type AgentName, DISPLAY_ORDER, getAgentShim, resolveAgentName } from './agents/shims';
|
|
2
2
|
import { type AgentRunResult, AiRunner } from './ai-runner';
|
|
3
3
|
|
|
4
4
|
/** Installation/version probe result for one coding agent. */
|
|
5
5
|
export interface DetectedAgent {
|
|
6
|
-
/**
|
|
6
|
+
/** Canonical agent identifier (alias input is resolved to its canonical id). */
|
|
7
7
|
name: AgentName | string;
|
|
8
8
|
/** Whether the agent CLI was found and returned a parseable version. */
|
|
9
9
|
installed: boolean;
|
|
@@ -13,6 +13,10 @@ export interface DetectedAgent {
|
|
|
13
13
|
channels: string[];
|
|
14
14
|
/** Detection error when unavailable. */
|
|
15
15
|
error: string | null;
|
|
16
|
+
/** True when the resolved canonical id is marked deprecated. */
|
|
17
|
+
deprecated?: boolean;
|
|
18
|
+
/** Canonical replacement when the resolved id is deprecated, if any. */
|
|
19
|
+
replacedBy?: AgentName;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/** Constructor options for AgentDetector. */
|
|
@@ -35,49 +39,56 @@ export class AgentDetector {
|
|
|
35
39
|
this.runner = options.runner ?? new AiRunner();
|
|
36
40
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
/** Probe all bundled agents in display order. */
|
|
42
|
+
/** Probe all bundled canonical agents in display order. */
|
|
40
43
|
async detectAll(): Promise<DetectedAgent[]> {
|
|
41
44
|
return await Promise.all(DISPLAY_ORDER.map((agent) => this.detectOne(agent)));
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
/** Probe one agent by
|
|
47
|
+
/** Probe one agent by canonical id or alias. */
|
|
45
48
|
async detectOne(agent: string): Promise<DetectedAgent> {
|
|
46
|
-
|
|
49
|
+
const canonical = resolveAgentName(agent);
|
|
50
|
+
if (canonical === undefined) return unavailable(agent, `Unknown agent: ${agent}`);
|
|
47
51
|
try {
|
|
48
|
-
return this.parseResult(
|
|
52
|
+
return this.parseResult(
|
|
53
|
+
canonical,
|
|
54
|
+
await this.runner.runVersionCommand(canonical, { timeout: this.timeout }),
|
|
55
|
+
);
|
|
49
56
|
} catch (error) {
|
|
50
57
|
return unavailable(agent, error instanceof Error ? error.message : String(error));
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
private parseResult(
|
|
55
|
-
const command = getAgentShim(
|
|
61
|
+
private parseResult(canonical: AgentName, result: AgentRunResult): DetectedAgent {
|
|
62
|
+
const command = getAgentShim(canonical).command;
|
|
56
63
|
const output = `${result.stdout}\n${result.stderr}`.trim();
|
|
57
64
|
const lower = output.toLowerCase();
|
|
58
65
|
if (lower.includes('command not found') || lower.includes('enoent') || lower.includes('not recognized')) {
|
|
59
|
-
return unavailable(
|
|
66
|
+
return unavailable(canonical, `${command}: command not found`);
|
|
60
67
|
}
|
|
61
68
|
if (result.signal !== undefined) {
|
|
62
|
-
return unavailable(
|
|
69
|
+
return unavailable(canonical, `Terminated by signal: ${result.signal}`);
|
|
63
70
|
}
|
|
64
71
|
if (result.exitCode === null) {
|
|
65
|
-
return unavailable(
|
|
72
|
+
return unavailable(canonical, 'Process did not produce an exit code');
|
|
66
73
|
}
|
|
67
74
|
if (result.exitCode !== 0) {
|
|
68
|
-
return unavailable(
|
|
75
|
+
return unavailable(
|
|
76
|
+
canonical,
|
|
77
|
+
`Non-zero exit code: ${result.exitCode}. stderr: ${result.stderr.slice(0, 200)}`,
|
|
78
|
+
);
|
|
69
79
|
}
|
|
70
80
|
const match = VERSION_PATTERN.exec(output);
|
|
71
81
|
if (match?.groups?.version === undefined) {
|
|
72
|
-
return unavailable(
|
|
82
|
+
return unavailable(canonical, 'Could not parse version output');
|
|
73
83
|
}
|
|
74
84
|
const version = output.split('\n')[0]?.trim() || match.groups.version;
|
|
75
85
|
return {
|
|
76
|
-
name:
|
|
86
|
+
name: canonical,
|
|
77
87
|
installed: true,
|
|
78
88
|
version,
|
|
79
|
-
channels: this.detectChannels(
|
|
89
|
+
channels: this.detectChannels(canonical, output),
|
|
80
90
|
error: null,
|
|
91
|
+
...deprecationOf(canonical),
|
|
81
92
|
};
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -86,8 +97,16 @@ export class AgentDetector {
|
|
|
86
97
|
return [];
|
|
87
98
|
}
|
|
88
99
|
}
|
|
89
|
-
|
|
90
100
|
/** Build an "unavailable" detection result for an agent with the given error. */
|
|
91
101
|
function unavailable(name: AgentName | string, error: string): DetectedAgent {
|
|
92
|
-
return { name, installed: false, version: null, channels: [], error };
|
|
102
|
+
return { name, installed: false, version: null, channels: [], error, ...deprecationOf(name) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Spread deprecation fields when the name resolves to a deprecated canonical id. */
|
|
106
|
+
function deprecationOf(name: AgentName | string): Partial<DetectedAgent> {
|
|
107
|
+
const canonical = resolveAgentName(name);
|
|
108
|
+
if (canonical === undefined) return {};
|
|
109
|
+
const shim = getAgentShim(canonical);
|
|
110
|
+
if (shim.deprecated === undefined) return {};
|
|
111
|
+
return { deprecated: true, replacedBy: shim.deprecated.replacedBy };
|
|
93
112
|
}
|
package/src/agent-spec.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
basenamePath,
|
|
3
|
+
createNodeFileSystem,
|
|
4
|
+
type FileSystem,
|
|
3
5
|
joinPath,
|
|
4
|
-
NodeSyncFileSystem,
|
|
5
6
|
parseYamlObject,
|
|
6
|
-
type SyncFileSystem,
|
|
7
7
|
stringifyYamlObject,
|
|
8
8
|
} from '@gobing-ai/ts-runtime';
|
|
9
9
|
|
|
@@ -36,11 +36,13 @@ export function validateAgentId(id: string): string {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Load and validate all YAML agent spec files from `configDir`. Throws `ValueError` on parse failures or duplicate IDs. */
|
|
39
|
-
export function loadAgentSpecs(configDir: string, fs:
|
|
40
|
-
const entries = safeReadDir(configDir, fs)
|
|
39
|
+
export async function loadAgentSpecs(configDir: string, fs: FileSystem = createNodeFileSystem()): Promise<AgentSpec[]> {
|
|
40
|
+
const entries = (await safeReadDir(configDir, fs))
|
|
41
41
|
.filter((entry) => entry.endsWith('.yaml') || entry.endsWith('.yml'))
|
|
42
42
|
.sort();
|
|
43
|
-
const specs =
|
|
43
|
+
const specs = await Promise.all(
|
|
44
|
+
entries.map(async (entry) => parseAgentSpec(await fs.readFile(joinPath(configDir, entry)), entry)),
|
|
45
|
+
);
|
|
44
46
|
const seen = new Set<string>();
|
|
45
47
|
for (const spec of specs) {
|
|
46
48
|
validateAgentId(spec.id);
|
|
@@ -54,26 +56,26 @@ export function loadAgentSpecs(configDir: string, fs: SyncFileSystem = new NodeS
|
|
|
54
56
|
export async function saveAgentSpec(
|
|
55
57
|
spec: AgentSpec,
|
|
56
58
|
configDir: string,
|
|
57
|
-
fs:
|
|
59
|
+
fs: FileSystem = createNodeFileSystem(),
|
|
58
60
|
): Promise<void> {
|
|
59
61
|
validateAgentId(spec.id);
|
|
60
|
-
fs.
|
|
61
|
-
fs.writeFile(joinPath(configDir, `${spec.id}.yaml`), serializeAgentSpec(spec));
|
|
62
|
+
await fs.ensureDir(configDir);
|
|
63
|
+
await fs.writeFile(joinPath(configDir, `${spec.id}.yaml`), serializeAgentSpec(spec));
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/** Remove the YAML file for agent `id` from `configDir`. Does nothing if the file doesn't exist. */
|
|
65
67
|
export async function deleteAgentSpec(
|
|
66
68
|
id: string,
|
|
67
69
|
configDir: string,
|
|
68
|
-
fs:
|
|
70
|
+
fs: FileSystem = createNodeFileSystem(),
|
|
69
71
|
): Promise<void> {
|
|
70
72
|
validateAgentId(id);
|
|
71
|
-
fs.
|
|
73
|
+
await fs.deleteFile(joinPath(configDir, `${id}.yaml`));
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
function safeReadDir(configDir: string, fs:
|
|
76
|
+
async function safeReadDir(configDir: string, fs: FileSystem = createNodeFileSystem()): Promise<string[]> {
|
|
75
77
|
try {
|
|
76
|
-
return fs.readDir(configDir);
|
|
78
|
+
return await fs.readDir(configDir);
|
|
77
79
|
} catch (error) {
|
|
78
80
|
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
79
81
|
return [];
|