@gobing-ai/ts-ai-runner 0.3.1 → 0.3.3
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 +336 -32
- package/dist/agent-detector.d.ts +1 -0
- package/dist/agent-detector.d.ts.map +1 -1
- package/dist/agent-detector.js +13 -5
- package/dist/agent-spec.d.ts +6 -0
- package/dist/agent-spec.d.ts.map +1 -1
- package/dist/agent-spec.js +12 -8
- package/dist/ai-runner.d.ts +18 -2
- package/dist/ai-runner.d.ts.map +1 -1
- package/dist/ai-runner.js +52 -6
- package/dist/doctor-runner.d.ts +7 -0
- package/dist/doctor-runner.d.ts.map +1 -1
- package/dist/doctor-runner.js +69 -15
- package/dist/events.d.ts +38 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +0 -0
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/messages.d.ts +4 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +4 -0
- package/dist/team-agent-process.d.ts +12 -4
- package/dist/team-agent-process.d.ts.map +1 -1
- package/dist/team-agent-process.js +31 -19
- package/dist/team-orchestrator.d.ts +13 -8
- package/dist/team-orchestrator.d.ts.map +1 -1
- package/dist/team-orchestrator.js +32 -25
- package/package.json +4 -4
- package/src/agent-detector.ts +14 -5
- package/src/agent-spec.ts +20 -8
- package/src/ai-runner.ts +75 -13
- package/src/doctor-runner.ts +77 -16
- package/src/events.ts +25 -0
- package/src/identity.ts +3 -0
- package/src/index.ts +2 -1
- package/src/messages.ts +6 -0
- package/src/team-agent-process.ts +36 -21
- package/src/team-orchestrator.ts +36 -25
- package/dist/message-service.d.ts +0 -13
- package/dist/message-service.d.ts.map +0 -1
- package/dist/message-service.js +0 -27
- package/src/message-service.ts +0 -33
package/dist/ai-runner.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
1
|
+
import { type EventBus, type Logger } from '@gobing-ai/ts-infra';
|
|
2
|
+
import { type ProcessExecutor, type TracerPort } from '@gobing-ai/ts-runtime';
|
|
3
|
+
import { type AgentName, type PromptOptions, type ShimCommand } from './agents/shims';
|
|
4
|
+
import type { AgentEvents, AiRunnerProcessEvents } from './events';
|
|
3
5
|
/** Result returned by every AI runner dispatch method. */
|
|
4
6
|
export interface AgentRunResult {
|
|
5
7
|
/** Process exit code; null indicates signal or timeout termination. */
|
|
@@ -28,12 +30,22 @@ export interface AiRunnerOptions {
|
|
|
28
30
|
defaultCwd?: string;
|
|
29
31
|
/** Default timeout in milliseconds. */
|
|
30
32
|
defaultTimeout?: number;
|
|
33
|
+
/** Logger for invocation diagnostics. Defaults to `getLogger('ai-runner')`. */
|
|
34
|
+
logger?: Logger;
|
|
35
|
+
/** Event bus receiving process-level observability from the default executor. */
|
|
36
|
+
processEvents?: EventBus<AiRunnerProcessEvents>;
|
|
37
|
+
/** Event bus receiving agent-level invocation observability. */
|
|
38
|
+
events?: EventBus<AgentEvents>;
|
|
39
|
+
/** Tracer adapter for the default executor. Defaults to `ts-infra` traceAsync. */
|
|
40
|
+
tracer?: TracerPort;
|
|
31
41
|
}
|
|
32
42
|
/** Dispatches coding-agent CLI commands through pure command shims. */
|
|
33
43
|
export declare class AiRunner {
|
|
34
44
|
private readonly processExecutor;
|
|
35
45
|
private readonly defaultCwd;
|
|
36
46
|
private readonly defaultTimeout;
|
|
47
|
+
private readonly logger;
|
|
48
|
+
private readonly events;
|
|
37
49
|
constructor(options?: AiRunnerOptions);
|
|
38
50
|
/** Run an agent help command. */
|
|
39
51
|
runHelpCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
@@ -41,6 +53,10 @@ export declare class AiRunner {
|
|
|
41
53
|
runVersionCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
42
54
|
/** Run an agent prompt command. */
|
|
43
55
|
runPromptCommand(agent: AgentName, promptOptions: PromptOptions, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
56
|
+
/** Translate a Claude-style slash command and run it as a prompt command. */
|
|
57
|
+
runSlashCommand(agent: AgentName, input: string, promptOptions: PromptOptions, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
58
|
+
/** Build an agent prompt command without executing it. */
|
|
59
|
+
buildPromptCommand(agent: AgentName, promptOptions: PromptOptions, options?: AgentRunOptions): ShimCommand;
|
|
44
60
|
/** Run an agent authentication command, or return null when unsupported. */
|
|
45
61
|
runAuthCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult> | null;
|
|
46
62
|
private invoke;
|
package/dist/ai-runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAc,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAGH,KAAK,eAAe,EAEpB,KAAK,UAAU,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAInE,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC3B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC5B,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC5B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,aAAa,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAChD,gEAAgE;IAChE,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/B,kFAAkF;IAClF,MAAM,CAAC,EAAE,UAAU,CAAC;CACvB;AAED,uEAAuE;AACvE,qBAAa,QAAQ;IACjB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAE/C,OAAO,GAAE,eAAoB;IAuBzC,iCAAiC;IACjC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAIxF,oCAAoC;IACpC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAI3F,mCAAmC;IACnC,gBAAgB,CACZ,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;IAI1B,6EAA6E;IAC7E,eAAe,CACX,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;IAI1B,0DAA0D;IAC1D,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,eAAoB,GAAG,WAAW;IAI9G,4EAA4E;IAC5E,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;YAKjF,MAAM;IA2CpB,OAAO,CAAC,oBAAoB;CAmB/B"}
|
package/dist/ai-runner.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLogger, traceAsync } from '@gobing-ai/ts-infra';
|
|
2
|
+
import { getProcessCwd, NodeProcessExecutor, } from '@gobing-ai/ts-runtime';
|
|
2
3
|
import { getAgentShim } from './agents/shims.js';
|
|
3
4
|
import { buildIdentityPreamble } from './identity.js';
|
|
5
|
+
import { translateSlashCommand } from './slash-command.js';
|
|
4
6
|
/** Dispatches coding-agent CLI commands through pure command shims. */
|
|
5
7
|
export class AiRunner {
|
|
6
8
|
processExecutor;
|
|
7
9
|
defaultCwd;
|
|
8
10
|
defaultTimeout;
|
|
11
|
+
logger;
|
|
12
|
+
events;
|
|
9
13
|
constructor(options = {}) {
|
|
10
|
-
this.processExecutor =
|
|
14
|
+
this.processExecutor =
|
|
15
|
+
options.processExecutor ??
|
|
16
|
+
new NodeProcessExecutor({
|
|
17
|
+
...(options.processEvents !== undefined
|
|
18
|
+
? {
|
|
19
|
+
events: {
|
|
20
|
+
emit: (event, detail) => {
|
|
21
|
+
void options.processEvents?.emit(event, detail);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
: {}),
|
|
26
|
+
tracer: options.tracer ?? {
|
|
27
|
+
traceAsync: async (name, fn) => await traceAsync(name, (span) => fn(span)),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
11
30
|
this.defaultCwd = options.defaultCwd;
|
|
12
31
|
this.defaultTimeout = options.defaultTimeout;
|
|
32
|
+
this.logger = options.logger ?? getLogger('ai-runner');
|
|
33
|
+
this.events = options.events;
|
|
13
34
|
}
|
|
14
35
|
/** Run an agent help command. */
|
|
15
36
|
runHelpCommand(agent, options = {}) {
|
|
@@ -21,8 +42,15 @@ export class AiRunner {
|
|
|
21
42
|
}
|
|
22
43
|
/** Run an agent prompt command. */
|
|
23
44
|
runPromptCommand(agent, promptOptions, options = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
return this.invoke(agent, 'prompt', this.buildPromptCommand(agent, promptOptions, options), options, false);
|
|
46
|
+
}
|
|
47
|
+
/** Translate a Claude-style slash command and run it as a prompt command. */
|
|
48
|
+
runSlashCommand(agent, input, promptOptions, options = {}) {
|
|
49
|
+
return this.runPromptCommand(agent, { ...promptOptions, input: translateSlashCommand(agent, input) }, options);
|
|
50
|
+
}
|
|
51
|
+
/** Build an agent prompt command without executing it. */
|
|
52
|
+
buildPromptCommand(agent, promptOptions, options = {}) {
|
|
53
|
+
return getAgentShim(agent).getPromptCommand(this.withIdentityPreamble(agent, promptOptions, options));
|
|
26
54
|
}
|
|
27
55
|
/** Run an agent authentication command, or return null when unsupported. */
|
|
28
56
|
runAuthCommand(agent, options = {}) {
|
|
@@ -30,15 +58,33 @@ export class AiRunner {
|
|
|
30
58
|
return command === null ? null : this.invoke(agent, 'auth', command, options, true);
|
|
31
59
|
}
|
|
32
60
|
async invoke(agent, operation, command, options, forceBuffered) {
|
|
61
|
+
const label = `ai-runner.${agent}.${operation}`;
|
|
62
|
+
this.logger.debug('invoke', { label, command: command.command, args: command.args.join(' ') });
|
|
63
|
+
void this.events?.emit('agent.invoke.start', { agent, operation, label });
|
|
33
64
|
const result = await this.processExecutor.run({
|
|
34
65
|
command: command.command,
|
|
35
66
|
args: command.args,
|
|
36
|
-
label
|
|
67
|
+
label,
|
|
37
68
|
rejectOnError: false,
|
|
38
69
|
forceBuffered,
|
|
39
70
|
cwd: options.cwd ?? this.defaultCwd,
|
|
40
71
|
timeout: options.timeout ?? this.defaultTimeout,
|
|
41
72
|
});
|
|
73
|
+
if (result.exitCode !== 0) {
|
|
74
|
+
this.logger.error('invoke exited non-zero', {
|
|
75
|
+
label,
|
|
76
|
+
exitCode: result.exitCode,
|
|
77
|
+
signal: result.signal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
void this.events?.emit('agent.invoke.exit', {
|
|
81
|
+
agent,
|
|
82
|
+
operation,
|
|
83
|
+
label,
|
|
84
|
+
exitCode: result.exitCode,
|
|
85
|
+
...(result.signal !== undefined ? { signal: result.signal } : {}),
|
|
86
|
+
durationMs: result.durationMs,
|
|
87
|
+
});
|
|
42
88
|
return {
|
|
43
89
|
exitCode: result.exitCode,
|
|
44
90
|
stdout: result.stdout,
|
|
@@ -50,7 +96,7 @@ export class AiRunner {
|
|
|
50
96
|
withIdentityPreamble(agent, promptOptions, options) {
|
|
51
97
|
if (!hasIdentityOptions(promptOptions))
|
|
52
98
|
return promptOptions;
|
|
53
|
-
const workspace = options.cwd ?? this.defaultCwd ??
|
|
99
|
+
const workspace = options.cwd ?? this.defaultCwd ?? getProcessCwd();
|
|
54
100
|
const preamble = buildIdentityPreamble({
|
|
55
101
|
agentId: agent,
|
|
56
102
|
agentType: agent,
|
package/dist/doctor-runner.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Logger } from '@gobing-ai/ts-infra';
|
|
1
2
|
import { AgentDetector } from './agent-detector';
|
|
2
3
|
import { AiRunner } from './ai-runner';
|
|
3
4
|
/** Health-check result for one coding agent. */
|
|
@@ -29,6 +30,8 @@ export interface DoctorRunnerOptions {
|
|
|
29
30
|
timeout?: number;
|
|
30
31
|
/** Environment map for file/env auth checks. */
|
|
31
32
|
env?: Record<string, string | undefined>;
|
|
33
|
+
/** Logger for health-check diagnostics. Defaults to `getLogger('doctor')`. */
|
|
34
|
+
logger?: Logger;
|
|
32
35
|
}
|
|
33
36
|
/** Runs installation and auth health checks for supported coding agents. */
|
|
34
37
|
export declare class DoctorRunner {
|
|
@@ -37,6 +40,7 @@ export declare class DoctorRunner {
|
|
|
37
40
|
private readonly timeout;
|
|
38
41
|
private readonly env;
|
|
39
42
|
private readonly fs;
|
|
43
|
+
private readonly logger;
|
|
40
44
|
constructor(options?: DoctorRunnerOptions);
|
|
41
45
|
/** Run a health check on all supported agents. */
|
|
42
46
|
runAll(): Promise<DoctorResult[]>;
|
|
@@ -44,6 +48,9 @@ export declare class DoctorRunner {
|
|
|
44
48
|
runOne(agent: string): Promise<DoctorResult>;
|
|
45
49
|
private buildResult;
|
|
46
50
|
private checkAuth;
|
|
51
|
+
private checkCodexAuth;
|
|
52
|
+
private geminiSettingsContainCredentials;
|
|
53
|
+
private probeAuthOutput;
|
|
47
54
|
/** True when the path exists and has a non-zero size. */
|
|
48
55
|
private hasNonEmptyFile;
|
|
49
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"
|
|
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;AAE7D,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAErE,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,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;CACnB;AAgCD,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,CAAwB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,GAAE,mBAAwB;IAQ7C,kDAAkD;IAC5C,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAkBvC,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAIpC,WAAW;YAiBX,SAAS;YAWT,cAAc;YASd,gCAAgC;YAQhC,eAAe;IAY7B,yDAAyD;YAC3C,eAAe;CAKhC"}
|
package/dist/doctor-runner.js
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getProcessEnv, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
1
|
+
import { getLogger } from '@gobing-ai/ts-infra';
|
|
2
|
+
import { getProcessEnv, joinPath, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
4
3
|
import { AgentDetector } from './agent-detector.js';
|
|
5
|
-
import { isAgentName, TIER2_AGENTS } from './agents/shims.js';
|
|
4
|
+
import { DISPLAY_ORDER, isAgentName, TIER2_AGENTS } from './agents/shims.js';
|
|
6
5
|
import { AiRunner } from './ai-runner.js';
|
|
7
6
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
7
|
+
const AUTH_PATTERNS = {
|
|
8
|
+
claude: {
|
|
9
|
+
positive: /authenticated|logged[\s_-]*in|"loggedIn"\s*:\s*true/i,
|
|
10
|
+
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|logged[\s_-]*out|unauthenticated|"loggedIn"\s*:\s*false/i,
|
|
11
|
+
},
|
|
12
|
+
codex: {
|
|
13
|
+
positive: /logged[\s_-]*in|authenticated/i,
|
|
14
|
+
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|logged[\s_-]*out|unauthenticated/i,
|
|
15
|
+
},
|
|
16
|
+
opencode: {
|
|
17
|
+
positive: /configured|available/i,
|
|
18
|
+
negative: /not[\s_-]*configured|no[\s_-]+providers?[\s_-]+available|unavailable/i,
|
|
19
|
+
},
|
|
20
|
+
openclaw: {
|
|
21
|
+
positive: /(^|[^a-z])ok([^a-z]|$)|healthy/i,
|
|
22
|
+
negative: /not[\s_-]*healthy|unhealthy|not[\s_-]*ok/i,
|
|
23
|
+
},
|
|
24
|
+
pi: {
|
|
25
|
+
positive: /\S/,
|
|
26
|
+
negative: /not[\s_-]*authenticated|not[\s_-]*logged[\s_-]*in|unauthenticated|no[\s_-]+providers?/i,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
8
29
|
/** True when a value is a defined, non-blank string. */
|
|
9
30
|
function isNonEmpty(value) {
|
|
10
31
|
return value !== undefined && value.trim().length > 0;
|
|
@@ -16,16 +37,25 @@ export class DoctorRunner {
|
|
|
16
37
|
timeout;
|
|
17
38
|
env;
|
|
18
39
|
fs = new NodeFileSystem();
|
|
40
|
+
logger;
|
|
19
41
|
constructor(options = {}) {
|
|
20
42
|
this.runner = options.runner ?? new AiRunner();
|
|
21
43
|
this.detector = options.agentDetector ?? new AgentDetector({ runner: this.runner });
|
|
22
44
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
23
45
|
this.env = options.env ?? getProcessEnv();
|
|
46
|
+
this.logger = options.logger ?? getLogger('doctor');
|
|
24
47
|
}
|
|
25
48
|
/** Run a health check on all supported agents. */
|
|
26
49
|
async runAll() {
|
|
27
50
|
const detected = await this.detector.detectAll();
|
|
28
|
-
|
|
51
|
+
const byName = new Map(detected.map((agent) => [agent.name, agent]));
|
|
52
|
+
return await Promise.all(DISPLAY_ORDER.map((agent) => this.buildResult(byName.get(agent) ?? {
|
|
53
|
+
name: agent,
|
|
54
|
+
installed: false,
|
|
55
|
+
version: null,
|
|
56
|
+
channels: [],
|
|
57
|
+
error: `Unknown agent: ${agent}`,
|
|
58
|
+
})));
|
|
29
59
|
}
|
|
30
60
|
/** Run a health check on one agent. */
|
|
31
61
|
async runOne(agent) {
|
|
@@ -33,6 +63,7 @@ export class DoctorRunner {
|
|
|
33
63
|
}
|
|
34
64
|
async buildResult(detected) {
|
|
35
65
|
const tier = TIER2_AGENTS.has(detected.name) ? 2 : 1;
|
|
66
|
+
this.logger.debug('checking agent', { agent: detected.name, installed: detected.installed, tier });
|
|
36
67
|
const authenticated = detected.installed && isAgentName(detected.name) ? await this.checkAuth(detected.name) : false;
|
|
37
68
|
return {
|
|
38
69
|
agent: detected.name,
|
|
@@ -46,23 +77,46 @@ export class DoctorRunner {
|
|
|
46
77
|
};
|
|
47
78
|
}
|
|
48
79
|
async checkAuth(agent) {
|
|
49
|
-
|
|
50
|
-
// file as authenticated. An empty/zero-byte file is a stale-credential
|
|
51
|
-
// false positive, so existence alone is insufficient.
|
|
80
|
+
const home = this.env.HOME || this.env.USERPROFILE || '';
|
|
52
81
|
if (agent === 'gemini')
|
|
53
|
-
return this.
|
|
54
|
-
if (agent === 'codex'
|
|
55
|
-
return
|
|
82
|
+
return this.geminiSettingsContainCredentials(home);
|
|
83
|
+
if (agent === 'codex')
|
|
84
|
+
return this.checkCodexAuth(home);
|
|
56
85
|
// pi reads provider keys from the environment; require a non-empty value
|
|
57
86
|
// rather than mere presence (an empty export is not a usable credential).
|
|
58
87
|
if (agent === 'pi' && (isNonEmpty(this.env.GOOGLE_API_KEY) || isNonEmpty(this.env.ANTHROPIC_API_KEY)))
|
|
59
88
|
return true;
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
return (await this.probeAuthOutput(agent)) === true;
|
|
90
|
+
}
|
|
91
|
+
async checkCodexAuth(home) {
|
|
92
|
+
const probeStatus = await this.probeAuthOutput('codex');
|
|
93
|
+
if (probeStatus !== null)
|
|
94
|
+
return probeStatus;
|
|
95
|
+
return ((await this.hasNonEmptyFile(joinPath(home, '.codex', 'auth.json'))) ||
|
|
96
|
+
(await this.hasNonEmptyFile(joinPath(home, '.codex', 'auth'))));
|
|
97
|
+
}
|
|
98
|
+
async geminiSettingsContainCredentials(home) {
|
|
99
|
+
try {
|
|
100
|
+
return /auth|token|key/i.test(await this.fs.readFile(joinPath(home, '.gemini', 'settings.json')));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
62
103
|
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async probeAuthOutput(agent) {
|
|
107
|
+
const command = this.runner.runAuthCommand(agent, { timeout: this.timeout });
|
|
108
|
+
const patterns = AUTH_PATTERNS[agent];
|
|
109
|
+
if (command === null || patterns === undefined)
|
|
110
|
+
return null;
|
|
63
111
|
const result = await command;
|
|
64
|
-
|
|
65
|
-
|
|
112
|
+
if (result.exitCode !== 0)
|
|
113
|
+
return false;
|
|
114
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
115
|
+
if (patterns.negative.test(output))
|
|
116
|
+
return false;
|
|
117
|
+
if (patterns.positive.test(output))
|
|
118
|
+
return true;
|
|
119
|
+
return null;
|
|
66
120
|
}
|
|
67
121
|
/** True when the path exists and has a non-zero size. */
|
|
68
122
|
async hasNonEmptyFile(path) {
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProcessEvents } from '@gobing-ai/ts-runtime';
|
|
2
|
+
/** Typed event map for agent-runner observability. All events prefixed `agent.`. */
|
|
3
|
+
export type AgentEvents = {
|
|
4
|
+
/** Emitted immediately before an agent CLI invocation starts. */
|
|
5
|
+
'agent.invoke.start': (data: {
|
|
6
|
+
agent: string;
|
|
7
|
+
operation: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}) => void;
|
|
10
|
+
/** Emitted after an agent CLI invocation exits. */
|
|
11
|
+
'agent.invoke.exit': (data: {
|
|
12
|
+
agent: string;
|
|
13
|
+
operation: string;
|
|
14
|
+
label: string;
|
|
15
|
+
exitCode: number | null;
|
|
16
|
+
signal?: string;
|
|
17
|
+
durationMs: number;
|
|
18
|
+
}) => void;
|
|
19
|
+
/** Emitted when a long-running team agent process starts. */
|
|
20
|
+
'agent.started': (data: {
|
|
21
|
+
agentId: string;
|
|
22
|
+
agentType: string;
|
|
23
|
+
pid: number | null;
|
|
24
|
+
}) => void;
|
|
25
|
+
/** Emitted when a long-running team agent process stops. */
|
|
26
|
+
'agent.stopped': (data: {
|
|
27
|
+
agentId: string;
|
|
28
|
+
exitCode: number | null;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** Emitted when a message is sent to a team agent process. */
|
|
31
|
+
'agent.message.sent': (data: {
|
|
32
|
+
agentId: string;
|
|
33
|
+
ok: boolean;
|
|
34
|
+
}) => void;
|
|
35
|
+
};
|
|
36
|
+
/** Event map for process-level observability emitted by AiRunner-owned executors. */
|
|
37
|
+
export type AiRunnerProcessEvents = ProcessEvents;
|
|
38
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,oFAAoF;AACpF,MAAM,MAAM,WAAW,GAAG;IACtB,iEAAiE;IACjE,oBAAoB,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1F,mDAAmD;IACnD,mBAAmB,EAAE,CAAC,IAAI,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACtB,KAAK,IAAI,CAAC;IACX,6DAA6D;IAC7D,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5F,4DAA4D;IAC5D,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9E,8DAA8D;IAC9D,oBAAoB,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC"}
|
package/dist/events.js
ADDED
|
File without changes
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SyncProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
/** Context used to construct the identity preamble injected into agent prompts. */
|
|
2
3
|
export interface IdentityContext {
|
|
3
4
|
agentId: string;
|
|
4
5
|
agentType: string;
|
|
@@ -16,6 +17,8 @@ export interface IdentityContext {
|
|
|
16
17
|
gitDirty?: boolean;
|
|
17
18
|
guardrails?: string[];
|
|
18
19
|
}
|
|
20
|
+
/** Build a human-readable identity preamble string that describes the agent, its task, peers, guardrails, and git context. */
|
|
19
21
|
export declare function buildIdentityPreamble(ctx: IdentityContext): string;
|
|
22
|
+
/** Query git for the current branch name and dirty file count in `workspacePath`. Returns a pre-formatted "Git context" block, or `null` if git is unavailable or the directory is not a repo. */
|
|
20
23
|
export declare function getGitContext(workspacePath: string, executor?: SyncProcessExecutor): string | null;
|
|
21
24
|
//# sourceMappingURL=identity.d.ts.map
|
package/dist/identity.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CA8ClE;AAED,wBAAgB,aAAa,CACzB,aAAa,EAAE,MAAM,EACrB,QAAQ,GAAE,mBAAkD,GAC7D,MAAM,GAAG,IAAI,CAYf"}
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,8HAA8H;AAC9H,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CA8ClE;AAED,kMAAkM;AAClM,wBAAgB,aAAa,CACzB,aAAa,EAAE,MAAM,EACrB,QAAQ,GAAE,mBAAkD,GAC7D,MAAM,GAAG,IAAI,CAYf"}
|
package/dist/identity.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BunSyncProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
/** Build a human-readable identity preamble string that describes the agent, its task, peers, guardrails, and git context. */
|
|
2
3
|
export function buildIdentityPreamble(ctx) {
|
|
3
4
|
const sections = [
|
|
4
5
|
`You are agent \`${ctx.agentId}\` (${ctx.agentType}) in workspace \`${ctx.workspace}\`.`,
|
|
@@ -31,6 +32,7 @@ export function buildIdentityPreamble(ctx) {
|
|
|
31
32
|
}
|
|
32
33
|
return `${sections.join('\n\n')}\n`;
|
|
33
34
|
}
|
|
35
|
+
/** Query git for the current branch name and dirty file count in `workspacePath`. Returns a pre-formatted "Git context" block, or `null` if git is unavailable or the directory is not a repo. */
|
|
34
36
|
export function getGitContext(workspacePath, executor = new BunSyncProcessExecutor()) {
|
|
35
37
|
const git = Bun.which('git');
|
|
36
38
|
if (git === null)
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ export * from './agent-spec';
|
|
|
3
3
|
export * from './agents/shims';
|
|
4
4
|
export * from './ai-runner';
|
|
5
5
|
export * from './doctor-runner';
|
|
6
|
+
export * from './events';
|
|
6
7
|
export * from './identity';
|
|
7
|
-
export * from './
|
|
8
|
+
export * from './messages';
|
|
8
9
|
export * from './slash-command';
|
|
9
10
|
export * from './team-agent-process';
|
|
10
11
|
export * from './team-orchestrator';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,9 @@ export * from './agent-spec.js';
|
|
|
3
3
|
export * from './agents/shims.js';
|
|
4
4
|
export * from './ai-runner.js';
|
|
5
5
|
export * from './doctor-runner.js';
|
|
6
|
+
export * from './events.js';
|
|
6
7
|
export * from './identity.js';
|
|
7
|
-
export * from './
|
|
8
|
+
export * from './messages.js';
|
|
8
9
|
export * from './slash-command.js';
|
|
9
10
|
export * from './team-agent-process.js';
|
|
10
11
|
export * from './team-orchestrator.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAEvD"}
|
package/dist/messages.js
ADDED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Logger } from '@gobing-ai/ts-infra';
|
|
3
|
+
import { ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
3
4
|
import type { AgentSpec } from './agent-spec';
|
|
5
|
+
/** Options for spawning a team agent subprocess. */
|
|
4
6
|
export interface AgentProcessOptions {
|
|
5
7
|
spec: AgentSpec;
|
|
6
8
|
command: string[];
|
|
7
9
|
env?: Record<string, string>;
|
|
8
10
|
cwd?: string;
|
|
9
|
-
|
|
11
|
+
processExecutor?: ProcessExecutor;
|
|
12
|
+
logger?: Logger;
|
|
10
13
|
}
|
|
11
14
|
type ProcessStatus = 'running' | 'stopped' | 'errored';
|
|
15
|
+
/**
|
|
16
|
+
* Manages the lifecycle of a single agent subprocess — start, stop, message send, and stdout/stderr subscription.
|
|
17
|
+
* The identity preamble is built by `TeamOrchestrator` and baked into `command` before the process is constructed.
|
|
18
|
+
*/
|
|
12
19
|
export declare class TeamAgentProcess {
|
|
13
20
|
readonly agentId: string;
|
|
14
|
-
readonly identityPreamble: string;
|
|
15
21
|
private readonly command;
|
|
16
22
|
private readonly env;
|
|
17
23
|
private readonly cwd;
|
|
18
|
-
private readonly
|
|
24
|
+
private readonly processExecutor;
|
|
25
|
+
private readonly logger;
|
|
19
26
|
private subprocess;
|
|
20
27
|
private status;
|
|
21
28
|
private exitCode;
|
|
@@ -31,6 +38,7 @@ export declare class TeamAgentProcess {
|
|
|
31
38
|
getPid(): number | null;
|
|
32
39
|
getExitCode(): number | null;
|
|
33
40
|
private pipe;
|
|
41
|
+
private warn;
|
|
34
42
|
}
|
|
35
43
|
export {};
|
|
36
44
|
//# sourceMappingURL=team-agent-process.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,
|
|
1
|
+
{"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAoB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;gBAErD,OAAO,EAAE,mBAAmB;IASlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAerD,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAOvD,SAAS,IAAI,aAAa;IAI1B,MAAM,IAAI,MAAM,GAAG,IAAI;IAIvB,WAAW,IAAI,MAAM,GAAG,IAAI;YAId,IAAI;IAiBlB,OAAO,CAAC,IAAI;CAOf"}
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getLogger } from '@gobing-ai/ts-infra';
|
|
3
|
+
import { ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
4
|
+
/**
|
|
5
|
+
* Manages the lifecycle of a single agent subprocess — start, stop, message send, and stdout/stderr subscription.
|
|
6
|
+
* The identity preamble is built by `TeamOrchestrator` and baked into `command` before the process is constructed.
|
|
7
|
+
*/
|
|
4
8
|
export class TeamAgentProcess {
|
|
5
9
|
agentId;
|
|
6
|
-
identityPreamble;
|
|
7
10
|
command;
|
|
8
11
|
env;
|
|
9
12
|
cwd;
|
|
10
|
-
|
|
13
|
+
processExecutor;
|
|
14
|
+
logger;
|
|
11
15
|
subprocess = null;
|
|
12
16
|
status = 'stopped';
|
|
13
17
|
exitCode = null;
|
|
14
18
|
subscribers = new Set();
|
|
15
19
|
constructor(options) {
|
|
16
20
|
this.agentId = options.spec.id;
|
|
17
|
-
this.identityPreamble = buildIdentityPreamble({
|
|
18
|
-
agentId: options.spec.id,
|
|
19
|
-
agentType: options.spec.type,
|
|
20
|
-
workspace: options.spec.workspace,
|
|
21
|
-
purpose: options.spec.purpose,
|
|
22
|
-
systemPrompt: typeof options.spec.config.systemPrompt === 'string' ? options.spec.config.systemPrompt : undefined,
|
|
23
|
-
});
|
|
24
21
|
this.command = options.command;
|
|
25
22
|
this.env = options.env;
|
|
26
23
|
this.cwd = options.cwd ?? options.spec.workspace;
|
|
27
|
-
this.
|
|
24
|
+
this.processExecutor = options.processExecutor ?? new ProcessExecutor();
|
|
25
|
+
this.logger = options.logger ?? getLogger('team-agent');
|
|
28
26
|
}
|
|
29
27
|
async start() {
|
|
30
28
|
if (this.status === 'running')
|
|
@@ -32,9 +30,10 @@ export class TeamAgentProcess {
|
|
|
32
30
|
const [command, ...args] = this.command;
|
|
33
31
|
if (command === undefined)
|
|
34
32
|
throw new Error(`${this.agentId}: command must not be empty`);
|
|
35
|
-
this.subprocess = this.
|
|
33
|
+
this.subprocess = this.processExecutor.runStreaming({
|
|
36
34
|
command,
|
|
37
35
|
args,
|
|
36
|
+
label: `team-agent.${this.agentId}`,
|
|
38
37
|
...(this.cwd !== undefined ? { cwd: this.cwd } : {}),
|
|
39
38
|
...(this.env !== undefined ? { env: this.env } : {}),
|
|
40
39
|
});
|
|
@@ -59,11 +58,13 @@ export class TeamAgentProcess {
|
|
|
59
58
|
try {
|
|
60
59
|
process.endStdin();
|
|
61
60
|
}
|
|
62
|
-
catch {
|
|
63
|
-
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.warn('stdin close failed', 'stop.endStdin', error);
|
|
64
63
|
}
|
|
65
64
|
process.kill('SIGTERM');
|
|
66
|
-
const timeout =
|
|
65
|
+
const timeout = new Promise((resolve) => {
|
|
66
|
+
setTimeout(() => resolve('timeout'), 5000);
|
|
67
|
+
});
|
|
67
68
|
const result = await Promise.race([process.exited, timeout]);
|
|
68
69
|
if (result === 'timeout') {
|
|
69
70
|
process.kill('SIGKILL');
|
|
@@ -76,13 +77,16 @@ export class TeamAgentProcess {
|
|
|
76
77
|
this.subprocess = null;
|
|
77
78
|
}
|
|
78
79
|
async send(message) {
|
|
79
|
-
if (this.status !== 'running' || this.subprocess === null)
|
|
80
|
+
if (this.status !== 'running' || this.subprocess === null) {
|
|
81
|
+
this.warn('send skipped because process is not running', 'send.notRunning');
|
|
80
82
|
return { ok: false };
|
|
83
|
+
}
|
|
81
84
|
try {
|
|
82
85
|
this.subprocess.writeStdin(`${message}\n`);
|
|
83
86
|
return { ok: true };
|
|
84
87
|
}
|
|
85
|
-
catch {
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.warn('stdin write failed', 'send.writeStdin', error);
|
|
86
90
|
this.status = 'errored';
|
|
87
91
|
return { ok: false };
|
|
88
92
|
}
|
|
@@ -114,7 +118,8 @@ export class TeamAgentProcess {
|
|
|
114
118
|
subscriber(buffer);
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
|
-
catch {
|
|
121
|
+
catch (error) {
|
|
122
|
+
this.warn('stream pipe failed', 'pipe', error);
|
|
118
123
|
if (this.status === 'running')
|
|
119
124
|
this.status = 'errored';
|
|
120
125
|
}
|
|
@@ -122,4 +127,11 @@ export class TeamAgentProcess {
|
|
|
122
127
|
reader.releaseLock();
|
|
123
128
|
}
|
|
124
129
|
}
|
|
130
|
+
warn(message, op, error) {
|
|
131
|
+
this.logger.warn(message, {
|
|
132
|
+
agentId: this.agentId,
|
|
133
|
+
op,
|
|
134
|
+
...(error !== undefined ? { error: error instanceof Error ? error.message : String(error) } : {}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
125
137
|
}
|