@borgee/agents-host 0.1.6 → 0.1.8
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 +290 -50
- package/dist/agents-host-supervisor.d.ts +54 -0
- package/dist/agents-host-supervisor.js +313 -0
- package/dist/agents-host.d.ts +4 -4
- package/dist/agents-host.js +5 -4
- package/dist/cli-args.d.ts +35 -12
- package/dist/cli-args.js +223 -33
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +109 -26
- package/dist/config.d.ts +12 -11
- package/dist/config.js +80 -31
- package/dist/local-config.d.ts +51 -0
- package/dist/local-config.js +772 -0
- package/dist/providers/copilot/cli-client.d.ts +4 -0
- package/dist/providers/copilot/cli-client.js +56 -3
- package/dist/providers/create-provider.js +3 -1
- package/dist/run.d.ts +27 -7
- package/dist/run.js +48 -10
- package/dist/types.d.ts +61 -5
- package/package.json +4 -2
|
@@ -7,6 +7,7 @@ interface CopilotAcpRuntime {
|
|
|
7
7
|
methods: typeof methods;
|
|
8
8
|
protocolVersion: typeof PROTOCOL_VERSION;
|
|
9
9
|
cwd: string;
|
|
10
|
+
idleSessionTtlMs: number;
|
|
10
11
|
shutdownGracePeriodMs: number;
|
|
11
12
|
shutdownForceKillWaitMs: number;
|
|
12
13
|
}
|
|
@@ -50,6 +51,9 @@ export declare class CopilotCliClient {
|
|
|
50
51
|
private invalidateSession;
|
|
51
52
|
private closeSession;
|
|
52
53
|
private rejectQueuedTurnsAfterSessionTaint;
|
|
54
|
+
private clearIdleTimer;
|
|
55
|
+
private reconcileIdleChannelState;
|
|
56
|
+
private evictIdleChannel;
|
|
53
57
|
private shutdownBackend;
|
|
54
58
|
private waitForPendingSessionStarts;
|
|
55
59
|
private waitForPendingSessionCloses;
|
|
@@ -2,6 +2,7 @@ import { Readable, Writable } from 'node:stream';
|
|
|
2
2
|
import spawn from 'cross-spawn';
|
|
3
3
|
import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
|
|
4
4
|
const SESSION_TAINTED_ERRORS = new WeakSet();
|
|
5
|
+
const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
|
|
5
6
|
const DEFAULT_SHUTDOWN_GRACE_PERIOD_MS = 250;
|
|
6
7
|
const DEFAULT_SHUTDOWN_FORCE_KILL_WAIT_MS = 250;
|
|
7
8
|
const QUEUED_TURN_DROPPED_MESSAGE = 'Copilot ACP session was reset after a failed turn; queued turns were dropped instead of replaying them on a fresh session';
|
|
@@ -12,6 +13,7 @@ const DEFAULT_RUNTIME = {
|
|
|
12
13
|
methods,
|
|
13
14
|
protocolVersion: PROTOCOL_VERSION,
|
|
14
15
|
cwd: process.cwd(),
|
|
16
|
+
idleSessionTtlMs: DEFAULT_IDLE_SESSION_TTL_MS,
|
|
15
17
|
shutdownGracePeriodMs: DEFAULT_SHUTDOWN_GRACE_PERIOD_MS,
|
|
16
18
|
shutdownForceKillWaitMs: DEFAULT_SHUTDOWN_FORCE_KILL_WAIT_MS,
|
|
17
19
|
};
|
|
@@ -98,6 +100,7 @@ export class CopilotCliClient {
|
|
|
98
100
|
throw this.fatalError;
|
|
99
101
|
}
|
|
100
102
|
const state = this.getOrCreateChannelState(channelId);
|
|
103
|
+
this.clearIdleTimer(state);
|
|
101
104
|
const turn = createDeferredTurn(prompt);
|
|
102
105
|
state.queue.push(turn);
|
|
103
106
|
this.processChannelQueue(channelId, state);
|
|
@@ -182,6 +185,7 @@ export class CopilotCliClient {
|
|
|
182
185
|
let state = this.channels.get(channelId);
|
|
183
186
|
if (!state) {
|
|
184
187
|
state = {
|
|
188
|
+
idleTimerGeneration: 0,
|
|
185
189
|
processing: false,
|
|
186
190
|
queue: [],
|
|
187
191
|
};
|
|
@@ -228,6 +232,7 @@ export class CopilotCliClient {
|
|
|
228
232
|
}
|
|
229
233
|
finally {
|
|
230
234
|
state.processing = false;
|
|
235
|
+
this.reconcileIdleChannelState(channelId, state);
|
|
231
236
|
}
|
|
232
237
|
})();
|
|
233
238
|
}
|
|
@@ -274,9 +279,6 @@ export class CopilotCliClient {
|
|
|
274
279
|
if (state.sessionPromise === sessionPromise) {
|
|
275
280
|
state.sessionPromise = undefined;
|
|
276
281
|
}
|
|
277
|
-
if (!state.session && state.queue.length === 0 && !state.activeTurn) {
|
|
278
|
-
this.channels.delete(channelId);
|
|
279
|
-
}
|
|
280
282
|
}
|
|
281
283
|
}
|
|
282
284
|
async runTurn(session, prompt) {
|
|
@@ -332,6 +334,7 @@ export class CopilotCliClient {
|
|
|
332
334
|
this.fatalError = error;
|
|
333
335
|
this.rejectFatalPromise(error);
|
|
334
336
|
for (const [channelId, state] of this.channels.entries()) {
|
|
337
|
+
this.clearIdleTimer(state);
|
|
335
338
|
state.activeTurn?.reject(error);
|
|
336
339
|
for (const turn of state.queue) {
|
|
337
340
|
turn.reject(error);
|
|
@@ -351,6 +354,7 @@ export class CopilotCliClient {
|
|
|
351
354
|
this.shutdownPromise = this.shutdownBackend(error);
|
|
352
355
|
}
|
|
353
356
|
invalidateSession(state, session) {
|
|
357
|
+
this.clearIdleTimer(state);
|
|
354
358
|
if (state.session === session) {
|
|
355
359
|
state.session = undefined;
|
|
356
360
|
}
|
|
@@ -393,6 +397,55 @@ export class CopilotCliClient {
|
|
|
393
397
|
}
|
|
394
398
|
state.queue.length = 0;
|
|
395
399
|
}
|
|
400
|
+
clearIdleTimer(state) {
|
|
401
|
+
state.idleTimerGeneration += 1;
|
|
402
|
+
if (!state.idleTimer) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
clearTimeout(state.idleTimer);
|
|
406
|
+
state.idleTimer = undefined;
|
|
407
|
+
}
|
|
408
|
+
reconcileIdleChannelState(channelId, state) {
|
|
409
|
+
if (this.channels.get(channelId) !== state) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (this.disposing || this.fatalError || this.backendClosed) {
|
|
413
|
+
this.clearIdleTimer(state);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (state.activeTurn || state.queue.length > 0 || state.sessionPromise || state.processing) {
|
|
417
|
+
this.clearIdleTimer(state);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (!state.session) {
|
|
421
|
+
this.clearIdleTimer(state);
|
|
422
|
+
this.channels.delete(channelId);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.clearIdleTimer(state);
|
|
426
|
+
const generation = state.idleTimerGeneration;
|
|
427
|
+
state.idleTimer = setTimeout(() => {
|
|
428
|
+
this.evictIdleChannel(channelId, state, generation);
|
|
429
|
+
}, this.runtime.idleSessionTtlMs);
|
|
430
|
+
}
|
|
431
|
+
evictIdleChannel(channelId, state, generation) {
|
|
432
|
+
if (this.disposing || this.fatalError || this.backendClosed) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (this.channels.get(channelId) !== state || state.idleTimerGeneration !== generation) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
state.idleTimer = undefined;
|
|
439
|
+
if (state.activeTurn || state.queue.length > 0 || state.sessionPromise || state.processing) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const session = state.session;
|
|
443
|
+
state.session = undefined;
|
|
444
|
+
if (session) {
|
|
445
|
+
this.closeSession(session);
|
|
446
|
+
}
|
|
447
|
+
this.channels.delete(channelId);
|
|
448
|
+
}
|
|
396
449
|
async shutdownBackend(error) {
|
|
397
450
|
if (this.backendClosed) {
|
|
398
451
|
return;
|
|
@@ -9,7 +9,9 @@ export function createProvider(config) {
|
|
|
9
9
|
return new ClaudeProviderAdapter(cli);
|
|
10
10
|
}
|
|
11
11
|
case 'copilot': {
|
|
12
|
-
const cli = new CopilotCliClient(config.copilotCommand, config.copilotArgs
|
|
12
|
+
const cli = new CopilotCliClient(config.copilotCommand, config.copilotArgs, {
|
|
13
|
+
idleSessionTtlMs: config.copilotSessionTtlMinutes * 60 * 1000,
|
|
14
|
+
});
|
|
13
15
|
return new CopilotProviderAdapter(cli);
|
|
14
16
|
}
|
|
15
17
|
default:
|
package/dist/run.d.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { parseGenerateConfigSpec, resolveLocalConfigLayout } from './local-config.js';
|
|
2
|
+
import type { LocalConfigGenerateResult, LocalConfigGenerateSpec, LocalConfigSnapshot } from './types.js';
|
|
3
|
+
export interface RunMainOptions {
|
|
4
|
+
configPath?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ValidateLocalConfigDeps {
|
|
7
|
+
loadSnapshot?: (configPath: string) => Promise<LocalConfigSnapshot>;
|
|
8
|
+
logger?: Pick<Console, 'log'>;
|
|
9
|
+
}
|
|
10
|
+
export interface PrintLocalConfigLayoutDeps {
|
|
11
|
+
resolveLayout?: (rootPath: string) => ReturnType<typeof resolveLocalConfigLayout>;
|
|
12
|
+
writer?: Pick<Console, 'log'>;
|
|
13
|
+
}
|
|
14
|
+
export interface DescribeLocalConfigDeps {
|
|
15
|
+
loadSpec?: (configPath: string) => Promise<LocalConfigGenerateSpec>;
|
|
16
|
+
writer?: Pick<Console, 'log'>;
|
|
17
|
+
}
|
|
18
|
+
export interface GenerateLocalConfigDeps {
|
|
19
|
+
parseSpec?: (value: unknown, sourceLabel: string) => ReturnType<typeof parseGenerateConfigSpec>;
|
|
20
|
+
materialize?: (rootPath: string, spec: ReturnType<typeof parseGenerateConfigSpec>) => Promise<LocalConfigGenerateResult>;
|
|
21
|
+
writer?: Pick<Console, 'log'>;
|
|
22
|
+
}
|
|
23
|
+
export declare function validateLocalConfig(configPath: string, deps?: ValidateLocalConfigDeps): Promise<void>;
|
|
24
|
+
export declare function printLocalConfigLayout(rootPath: string, deps?: PrintLocalConfigLayoutDeps): Promise<void>;
|
|
25
|
+
export declare function describeLocalConfig(configPath: string, deps?: DescribeLocalConfigDeps): Promise<void>;
|
|
26
|
+
export declare function generateLocalConfig(rootPath: string, specJson: string, sourceLabelOrDeps?: string | GenerateLocalConfigDeps, deps?: GenerateLocalConfigDeps): Promise<void>;
|
|
27
|
+
export declare function runMain(options?: RunMainOptions): Promise<void>;
|
package/dist/run.js
CHANGED
|
@@ -1,14 +1,51 @@
|
|
|
1
|
-
import { loadConfigFromEnv } from './config.js';
|
|
2
1
|
import { AgentsHost } from './agents-host.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { AgentsHostSupervisor } from './agents-host-supervisor.js';
|
|
3
|
+
import { loadConfigFromEnv } from './config.js';
|
|
4
|
+
import { loadLocalConfigGenerateSpec, loadLocalConfigSnapshot, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
|
|
5
|
+
export async function validateLocalConfig(configPath, deps = {}) {
|
|
6
|
+
const loadSnapshot = deps.loadSnapshot ?? loadLocalConfigSnapshot;
|
|
7
|
+
const logger = deps.logger ?? console;
|
|
8
|
+
const snapshot = await loadSnapshot(configPath);
|
|
9
|
+
logger.log('[agents-host] local-config validation OK');
|
|
10
|
+
logger.log(`[agents-host] host config: ${snapshot.hostConfigPath}`);
|
|
11
|
+
logger.log(`[agents-host] agents dir: ${snapshot.agentsDir}`);
|
|
12
|
+
logger.log(`[agents-host] managed agents: ${snapshot.agents.length}`);
|
|
13
|
+
for (const agent of snapshot.agents) {
|
|
14
|
+
logger.log(`[agents-host] managed agent key=${agent.key} provider=${agent.config.agent.provider} sourcePath=${agent.sourcePath}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function printLocalConfigLayout(rootPath, deps = {}) {
|
|
18
|
+
const resolveLayout = deps.resolveLayout ?? resolveLocalConfigLayout;
|
|
19
|
+
const writer = deps.writer ?? console;
|
|
20
|
+
writer.log(JSON.stringify(resolveLayout(rootPath), null, 2));
|
|
21
|
+
}
|
|
22
|
+
export async function describeLocalConfig(configPath, deps = {}) {
|
|
23
|
+
const loadSpec = deps.loadSpec ?? loadLocalConfigGenerateSpec;
|
|
24
|
+
const writer = deps.writer ?? console;
|
|
25
|
+
writer.log(JSON.stringify(await loadSpec(configPath), null, 2));
|
|
26
|
+
}
|
|
27
|
+
export async function generateLocalConfig(rootPath, specJson, sourceLabelOrDeps = '--spec-json', deps = {}) {
|
|
28
|
+
const sourceLabel = typeof sourceLabelOrDeps === 'string' ? sourceLabelOrDeps : '--spec-json';
|
|
29
|
+
const resolvedDeps = typeof sourceLabelOrDeps === 'string' ? deps : sourceLabelOrDeps;
|
|
30
|
+
const parseSpec = resolvedDeps.parseSpec ?? parseGenerateConfigSpec;
|
|
31
|
+
const materialize = resolvedDeps.materialize ?? materializeLocalConfig;
|
|
32
|
+
const writer = resolvedDeps.writer ?? console;
|
|
33
|
+
let parsedJson;
|
|
34
|
+
try {
|
|
35
|
+
parsedJson = JSON.parse(specJson);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
throw new Error(`Invalid ${sourceLabel}: ${message}`);
|
|
40
|
+
}
|
|
41
|
+
const spec = parseSpec(parsedJson, sourceLabel);
|
|
42
|
+
const result = await materialize(rootPath, spec);
|
|
43
|
+
writer.log(JSON.stringify(result, null, 2));
|
|
44
|
+
}
|
|
45
|
+
export async function runMain(options = {}) {
|
|
46
|
+
const host = options.configPath
|
|
47
|
+
? new AgentsHostSupervisor(options.configPath)
|
|
48
|
+
: new AgentsHost(loadConfigFromEnv());
|
|
12
49
|
const shutdown = (signal) => {
|
|
13
50
|
console.log(`[agents-host] received ${signal}, shutting down`);
|
|
14
51
|
host
|
|
@@ -19,4 +56,5 @@ export async function runMain() {
|
|
|
19
56
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
20
57
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
21
58
|
await host.start();
|
|
59
|
+
console.log('[agents-host] ready');
|
|
22
60
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,15 +4,11 @@ export interface ProviderCommandConfig {
|
|
|
4
4
|
claudeArgs: string[];
|
|
5
5
|
copilotCommand: string;
|
|
6
6
|
copilotArgs: string[];
|
|
7
|
+
copilotSessionTtlMinutes: number;
|
|
7
8
|
}
|
|
8
9
|
export interface ProviderRuntimeConfig extends ProviderCommandConfig {
|
|
9
10
|
provider: ProviderKind;
|
|
10
11
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Single hosted-agent configuration. The minimal agents host supports exactly
|
|
13
|
-
* one Borgee agent per process — running multiple agents means running
|
|
14
|
-
* multiple processes with different env vars (see README).
|
|
15
|
-
*/
|
|
16
12
|
export interface HostedAgentConfig {
|
|
17
13
|
agentApiKey: string;
|
|
18
14
|
agentName: string;
|
|
@@ -22,6 +18,66 @@ export interface AgentsHostConfig extends ProviderCommandConfig {
|
|
|
22
18
|
borgeeBaseUrl: string;
|
|
23
19
|
agent: HostedAgentConfig;
|
|
24
20
|
}
|
|
21
|
+
export interface LocalHostConfigFile {
|
|
22
|
+
borgeeBaseUrl: string;
|
|
23
|
+
agentsDir?: string;
|
|
24
|
+
defaults?: Partial<ProviderCommandConfig>;
|
|
25
|
+
}
|
|
26
|
+
export interface LocalHostGenerateConfigFile {
|
|
27
|
+
borgeeBaseUrl: string;
|
|
28
|
+
defaults?: Partial<ProviderCommandConfig>;
|
|
29
|
+
}
|
|
30
|
+
export interface LocalAgentConfigFile extends Partial<ProviderCommandConfig> {
|
|
31
|
+
key: string;
|
|
32
|
+
name: string;
|
|
33
|
+
apiKey: string;
|
|
34
|
+
provider: ProviderKind;
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ManagedAgentConfigSnapshot {
|
|
38
|
+
key: string;
|
|
39
|
+
sourcePath: string;
|
|
40
|
+
config: AgentsHostConfig;
|
|
41
|
+
}
|
|
42
|
+
export interface LocalConfigSnapshot {
|
|
43
|
+
/** Stable caller-facing paths, suitable for validation output and publication watches. */
|
|
44
|
+
hostConfigPath: string;
|
|
45
|
+
hostConfigDir: string;
|
|
46
|
+
agentsDir: string;
|
|
47
|
+
/** Managed stable root when the snapshot comes from generate-config publication. */
|
|
48
|
+
managedRootPath?: string;
|
|
49
|
+
/** Resolved paths pinned before config files are read. */
|
|
50
|
+
resolvedHostConfigPath: string;
|
|
51
|
+
resolvedHostConfigDir: string;
|
|
52
|
+
resolvedAgentsDir: string;
|
|
53
|
+
agents: ManagedAgentConfigSnapshot[];
|
|
54
|
+
}
|
|
55
|
+
export interface LocalConfigGenerateSpec {
|
|
56
|
+
host: LocalHostGenerateConfigFile;
|
|
57
|
+
agents: LocalAgentConfigFile[];
|
|
58
|
+
}
|
|
59
|
+
export interface LocalConfigGenerateWarning {
|
|
60
|
+
code: 'PRUNE_SUPERSEDED_GENERATIONS_FAILED';
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
export interface LocalConfigGenerateResult {
|
|
64
|
+
root: string;
|
|
65
|
+
hostConfigPath: string;
|
|
66
|
+
agentsDir: string;
|
|
67
|
+
generatedAgents: Array<{
|
|
68
|
+
key: string;
|
|
69
|
+
path: string;
|
|
70
|
+
provider: ProviderKind;
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Agent config paths removed from the active authoritative set. Publication
|
|
75
|
+
* switches the whole set atomically rather than deleting these paths first.
|
|
76
|
+
*/
|
|
77
|
+
prunedAgentConfigPaths: string[];
|
|
78
|
+
/** Non-fatal maintenance problems encountered after publication. */
|
|
79
|
+
warnings: LocalConfigGenerateWarning[];
|
|
80
|
+
}
|
|
25
81
|
export interface ChannelMessageEvent {
|
|
26
82
|
type?: string;
|
|
27
83
|
channel_id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgee/agents-host",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@agentclientprotocol/sdk": "^1.2.1",
|
|
25
25
|
"cross-spawn": "^7.0.6",
|
|
26
|
+
"yaml": "^2.8.1",
|
|
26
27
|
"@borgee/plugin-sdk": "0.1.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"build": "tsc",
|
|
44
45
|
"typecheck": "tsc --noEmit",
|
|
45
46
|
"pretest": "pnpm --filter @borgee/plugin-sdk build",
|
|
46
|
-
"test": "vitest run"
|
|
47
|
+
"test": "vitest run --testTimeout=10000",
|
|
48
|
+
"pretypecheck": "pnpm --filter @borgee/plugin-sdk build"
|
|
47
49
|
}
|
|
48
50
|
}
|