@borgee/agents-host 0.2.68 → 0.2.74
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 +6 -6
- package/dist/agents-host-supervisor.d.ts +1 -0
- package/dist/agents-host-supervisor.js +3 -1
- package/dist/context/claude-file-brief.js +2 -1
- package/dist/context/prompt.js +4 -1
- package/dist/context/task-channel-boundary.d.ts +1 -0
- package/dist/context/task-channel-boundary.js +5 -0
- package/dist/local-config.d.ts +7 -0
- package/dist/local-config.js +158 -12
- package/dist/managed-daemon.js +12 -5
- package/dist/plugin-sdk.js +90 -6
- package/dist/plugin-sdk.js.map +2 -2
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ and resumable in that case.
|
|
|
140
140
|
|
|
141
141
|
Each managed runtime root keeps:
|
|
142
142
|
|
|
143
|
-
- `agents-host.yaml
|
|
143
|
+
- `.generations/<uuid>/` as validated immutable config generations; Unix and macOS publish `current`, `agents-host.yaml`, and `agents/` symlinks, while Windows publishes the active generation through the atomically replaced ordinary `current.json` pointer and resolves the generation internally without requiring Developer Mode or elevation
|
|
144
144
|
- `managed-runtime-settings.json` as the persisted managed runtime surface snapshot
|
|
145
145
|
- `daemon.log` as the detached managed daemon stdout/stderr log file
|
|
146
146
|
- `.state/` as the per-agent runtime state base
|
|
@@ -302,8 +302,7 @@ printf '%s' '{"host":{"borgeeBaseUrl":"https://borgee.example.com"},"agents":[{"
|
|
|
302
302
|
- if that post-publication pruning fails, the apply remains successful and its
|
|
303
303
|
JSON summary includes a `warnings` entry with code
|
|
304
304
|
`PRUNE_SUPERSEDED_GENERATIONS_FAILED`
|
|
305
|
-
- it uses a dedicated managed root: `<root>/agents-host.yaml` and
|
|
306
|
-
`<root>/agents` are stable links to the active private generation
|
|
305
|
+
- it uses a dedicated managed root: Unix and macOS expose `<root>/agents-host.yaml` and `<root>/agents` as stable links to the active private generation, while Windows uses `<root>/current.json` as the stable publication point and resolves the active generation internally
|
|
307
306
|
- it replaces stale supported agent config files as part of that atomic
|
|
308
307
|
full-set publication, never by pruning the active set first
|
|
309
308
|
- it creates and normalizes the managed root and generation directories to
|
|
@@ -349,12 +348,13 @@ The spec shape is:
|
|
|
349
348
|
always targets `<root>/agents`, and validation is re-run after generation using
|
|
350
349
|
the same local-config loader as `start --config` / `validate --config`. Start
|
|
351
350
|
the generated layout with `agents-host start --config <root>/agents-host.yaml`;
|
|
352
|
-
that
|
|
351
|
+
the managed loader treats that path as the stable logical entrypoint even when
|
|
352
|
+
Windows does not materialize a filesystem alias there.
|
|
353
353
|
The loader preserves that stable path for validation output and publication
|
|
354
354
|
watches, while pinning resolved host and agents-directory paths before reading.
|
|
355
355
|
Managed snapshots hold a short-lived reader lease until the read finishes, so
|
|
356
|
-
each snapshot uses one generation even if publication changes `current`
|
|
357
|
-
concurrently.
|
|
356
|
+
each snapshot uses one generation even if publication changes `current` or
|
|
357
|
+
`current.json` concurrently.
|
|
358
358
|
|
|
359
359
|
The supervisor loads:
|
|
360
360
|
|
|
@@ -17,6 +17,7 @@ export interface AgentsHostSupervisorDeps {
|
|
|
17
17
|
watchPath?: (path: string, onEvent: (event?: WatchEventInfo) => void) => WatchHandle;
|
|
18
18
|
logger?: LoggerLike;
|
|
19
19
|
debug?: boolean;
|
|
20
|
+
platform?: NodeJS.Platform;
|
|
20
21
|
}
|
|
21
22
|
export declare class AgentsHostSupervisor {
|
|
22
23
|
private readonly hostConfigPath;
|
|
@@ -26,7 +26,9 @@ export class AgentsHostSupervisor {
|
|
|
26
26
|
constructor(hostConfigPath, deps = {}) {
|
|
27
27
|
this.hostConfigPath = resolve(hostConfigPath);
|
|
28
28
|
this.createHost = deps.createHost ?? ((config, runtimeOptions) => new AgentsHost(config, { runtimeOptions }));
|
|
29
|
-
this.loadSnapshot =
|
|
29
|
+
this.loadSnapshot =
|
|
30
|
+
deps.loadSnapshot ??
|
|
31
|
+
((configPath) => loadLocalConfigSnapshot(configPath, { platform: deps.platform }));
|
|
30
32
|
this.watchPath =
|
|
31
33
|
deps.watchPath ??
|
|
32
34
|
((path, onEvent) => watch(path, (eventType, filename) => {
|
|
@@ -2,6 +2,7 @@ import { buildCollaborationCapabilityDeclarationSummaryLines, } from './collabor
|
|
|
2
2
|
import { MAIN_SESSION_DELEGATION_LINES } from './main-session-delegation.js';
|
|
3
3
|
import { buildResolvedWorkspaceGuidanceLines } from './resolved-workspace.js';
|
|
4
4
|
import { buildSkillManualLines, buildSkillManualReadLine } from './skill-manual.js';
|
|
5
|
+
import { PARENT_CHANNEL_TASK_BOUNDARY_LINES } from './task-channel-boundary.js';
|
|
5
6
|
function buildStableSkillLines(context) {
|
|
6
7
|
if (!context?.skillRuntime) {
|
|
7
8
|
return [];
|
|
@@ -16,7 +17,7 @@ function buildStableGatewayLines(context) {
|
|
|
16
17
|
}
|
|
17
18
|
const lines = [`Gateway credential file: ${context.gatewayCredentialPath}`];
|
|
18
19
|
if (context.runtimeSurface?.task.currentThread === 'parent-channel') {
|
|
19
|
-
lines.push('This channel is a parent channel, not a task thread.', 'For parent-channel task operations, use the packaged borgee-agent task rail through this gateway credential file.', 'Do not invent a bare task or bare borgee command.');
|
|
20
|
+
lines.push('This channel is a parent channel, not a task thread.', ...PARENT_CHANNEL_TASK_BOUNDARY_LINES, 'For parent-channel task operations, use the packaged borgee-agent task rail through this gateway credential file.', 'Do not invent a bare task or bare borgee command.');
|
|
20
21
|
if (context.runtimeSurface.task.currentTaskShorthand === 'requires-task-id') {
|
|
21
22
|
lines.push('In this parent channel, task get, task update, task history, and task property commands require an explicit task id.');
|
|
22
23
|
}
|
package/dist/context/prompt.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildCollaborationOutcomeSummaryLines } from './collaboration-outcome.j
|
|
|
6
6
|
import { MAIN_SESSION_DELEGATION_LINES } from './main-session-delegation.js';
|
|
7
7
|
import { buildResolvedWorkspaceGuidanceLines } from './resolved-workspace.js';
|
|
8
8
|
import { buildSkillManualLines, buildSkillManualReadLine } from './skill-manual.js';
|
|
9
|
+
import { PARENT_CHANNEL_TASK_BOUNDARY_LINES } from './task-channel-boundary.js';
|
|
9
10
|
import { buildTaskThreadCollaborationSummaryLines } from './task-thread-collaboration.js';
|
|
10
11
|
function providerLabel(provider) {
|
|
11
12
|
if (provider === 'copilot') {
|
|
@@ -46,6 +47,7 @@ function buildLocalhostGatewayPromptLines(context) {
|
|
|
46
47
|
: 'This turn runs in a parent channel, not a task thread.');
|
|
47
48
|
if (context.runtimeSurface?.task.currentThread === 'parent-channel'
|
|
48
49
|
&& (context.collaborationTurnMode ?? 'ordinary') === 'ordinary') {
|
|
50
|
+
lines.push(...PARENT_CHANNEL_TASK_BOUNDARY_LINES);
|
|
49
51
|
if (context.runtimeSurface.task.parentChannelTaskCollection === 'available') {
|
|
50
52
|
lines.push('Task list and task create are available in this parent channel.');
|
|
51
53
|
lines.push(`For parent-channel task operations on this turn, read the packaged borgee-agent manual at ${context.skillRuntime.skillMarkdownPath} and follow that command grammar exactly.`);
|
|
@@ -132,6 +134,7 @@ function buildClaudeOrdinaryGatewayTaskLines(context) {
|
|
|
132
134
|
const lines = [
|
|
133
135
|
`Gateway credential file for this turn: ${context.gatewayCredentialPath}`,
|
|
134
136
|
'This turn runs in a parent channel, not a task thread.',
|
|
137
|
+
...PARENT_CHANNEL_TASK_BOUNDARY_LINES,
|
|
135
138
|
'Task list and task create are available in this parent channel.',
|
|
136
139
|
`For parent-channel task operations on this turn, read the packaged borgee-agent manual at ${context.skillRuntime.skillMarkdownPath} and follow that command grammar exactly.`,
|
|
137
140
|
'For task creation and task updates in this parent channel, use the packaged borgee-agent task rail through this gateway credential file.',
|
|
@@ -169,7 +172,7 @@ function buildClaudeProjectedBriefTurnDeltaLines(context) {
|
|
|
169
172
|
lines.push(...taskLines);
|
|
170
173
|
}
|
|
171
174
|
else {
|
|
172
|
-
lines.push(`Gateway credential file for this turn: ${context.gatewayCredentialPath}`, 'This turn runs in a parent channel, not a task thread.');
|
|
175
|
+
lines.push(`Gateway credential file for this turn: ${context.gatewayCredentialPath}`, 'This turn runs in a parent channel, not a task thread.', ...PARENT_CHANNEL_TASK_BOUNDARY_LINES);
|
|
173
176
|
if (context.runtimeSurface.task.parentChannelTaskCollection === 'available') {
|
|
174
177
|
lines.push('Task list and task create are available in this parent channel.');
|
|
175
178
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PARENT_CHANNEL_TASK_BOUNDARY_LINES: readonly ["This parent channel is limited to creating and assigning tasks, coordinating them, and giving brief confirmations.", "After a task is created and assigned, do not start or continue its implementation, tool calls, or work progress in this parent channel; leave that work to the assigned task thread.", "Never work on the same task in the parent channel and its task thread at the same time."];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const PARENT_CHANNEL_TASK_BOUNDARY_LINES = [
|
|
2
|
+
'This parent channel is limited to creating and assigning tasks, coordinating them, and giving brief confirmations.',
|
|
3
|
+
'After a task is created and assigned, do not start or continue its implementation, tool calls, or work progress in this parent channel; leave that work to the assigned task thread.',
|
|
4
|
+
'Never work on the same task in the parent channel and its task thread at the same time.',
|
|
5
|
+
];
|
package/dist/local-config.d.ts
CHANGED
|
@@ -41,14 +41,21 @@ export declare function loadLocalConfigSnapshot(hostConfigPath: string, deps?: {
|
|
|
41
41
|
fileSystem?: LocalConfigFileSystem;
|
|
42
42
|
acquireManagedGenerationLease?: boolean;
|
|
43
43
|
env?: NodeJS.ProcessEnv;
|
|
44
|
+
platform?: NodeJS.Platform;
|
|
44
45
|
}): Promise<LocalConfigSnapshot>;
|
|
45
46
|
export declare function loadLocalConfigGenerateSpec(hostConfigPath: string, deps?: {
|
|
46
47
|
fileSystem?: LocalConfigFileSystem;
|
|
47
48
|
acquireManagedGenerationLease?: boolean;
|
|
48
49
|
env?: NodeJS.ProcessEnv;
|
|
50
|
+
platform?: NodeJS.Platform;
|
|
49
51
|
}): Promise<LocalConfigGenerateSpec>;
|
|
52
|
+
export declare function hasManagedLocalConfig(rootPath: string, deps?: {
|
|
53
|
+
fileSystem?: ManagedLocalConfigFileSystem;
|
|
54
|
+
platform?: NodeJS.Platform;
|
|
55
|
+
}): Promise<boolean>;
|
|
50
56
|
export declare function materializeLocalConfig(rootPath: string, spec: LocalConfigGenerateSpec, deps?: {
|
|
51
57
|
fileSystem?: ManagedLocalConfigFileSystem;
|
|
52
58
|
env?: NodeJS.ProcessEnv;
|
|
59
|
+
platform?: NodeJS.Platform;
|
|
53
60
|
}): Promise<LocalConfigGenerateResult>;
|
|
54
61
|
export {};
|
package/dist/local-config.js
CHANGED
|
@@ -10,6 +10,8 @@ export const DEFAULT_LOCAL_AGENTS_DIRNAME = 'agents';
|
|
|
10
10
|
const DEFAULT_AGENTS_DIR = './agents';
|
|
11
11
|
const MANAGED_GENERATIONS_DIRNAME = '.generations';
|
|
12
12
|
const MANAGED_CURRENT_LINK_NAME = 'current';
|
|
13
|
+
const MANAGED_CURRENT_POINTER_FILENAME = 'current.json';
|
|
14
|
+
const MANAGED_CURRENT_POINTER_SCHEMA_VERSION = 1;
|
|
13
15
|
export const MANAGED_WRITE_LOCK_DIRNAME = '.generate-config.lock';
|
|
14
16
|
const MANAGED_READERS_DIRNAME = '.readers';
|
|
15
17
|
const MANAGED_HOLDER_METADATA_FILENAME = '.holder.json';
|
|
@@ -391,10 +393,106 @@ function isDirectManagedGeneration(root, target) {
|
|
|
391
393
|
const resolvedTarget = resolve(root, target);
|
|
392
394
|
return (dirname(resolvedTarget) === generationsDir && relative(generationsDir, resolvedTarget) !== '');
|
|
393
395
|
}
|
|
394
|
-
|
|
396
|
+
function parseManagedCurrentPointer(raw, pointerPath) {
|
|
397
|
+
let parsed;
|
|
398
|
+
try {
|
|
399
|
+
parsed = JSON.parse(raw);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
throw new Error(`Invalid managed config pointer ${pointerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
403
|
+
}
|
|
404
|
+
if (!isRecord(parsed) ||
|
|
405
|
+
Object.keys(parsed).sort().join(',') !== 'generation,schemaVersion' ||
|
|
406
|
+
parsed.schemaVersion !== MANAGED_CURRENT_POINTER_SCHEMA_VERSION ||
|
|
407
|
+
typeof parsed.generation !== 'string' ||
|
|
408
|
+
!MANAGED_GENERATION_NAME_PATTERN.test(parsed.generation) ||
|
|
409
|
+
isAbsolute(parsed.generation) ||
|
|
410
|
+
parsed.generation.includes('/') ||
|
|
411
|
+
parsed.generation.includes('\\')) {
|
|
412
|
+
throw new Error(`Invalid managed config pointer ${pointerPath}: expected schemaVersion ${MANAGED_CURRENT_POINTER_SCHEMA_VERSION} and one direct generation identifier`);
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
schemaVersion: MANAGED_CURRENT_POINTER_SCHEMA_VERSION,
|
|
416
|
+
generation: parsed.generation,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
async function resolvePointerGeneration(fileSystem, root) {
|
|
420
|
+
const pointerPath = join(root, MANAGED_CURRENT_POINTER_FILENAME);
|
|
421
|
+
const pointerStatus = await pathStatus(fileSystem, pointerPath);
|
|
422
|
+
if (!pointerStatus) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
if (pointerStatus.isDirectory || pointerStatus.isSymbolicLink) {
|
|
426
|
+
throw new Error(`Managed config pointer must be an ordinary file: ${pointerPath}`);
|
|
427
|
+
}
|
|
428
|
+
const pointer = parseManagedCurrentPointer(await fileSystem.readFile(pointerPath), pointerPath);
|
|
429
|
+
const generationsDir = join(root, MANAGED_GENERATIONS_DIRNAME);
|
|
430
|
+
const generationPath = resolve(generationsDir, pointer.generation);
|
|
431
|
+
if (dirname(generationPath) !== generationsDir ||
|
|
432
|
+
relative(generationsDir, generationPath) !== pointer.generation) {
|
|
433
|
+
throw new Error(`Managed config pointer generation must be a direct child of ${generationsDir}`);
|
|
434
|
+
}
|
|
435
|
+
const generationStatus = await pathStatus(fileSystem, generationPath);
|
|
436
|
+
if (!generationStatus?.isDirectory || generationStatus.isSymbolicLink) {
|
|
437
|
+
throw new Error(`Managed config pointer target is not a directory: ${generationPath}`);
|
|
438
|
+
}
|
|
439
|
+
return generationPath;
|
|
440
|
+
}
|
|
441
|
+
async function resolveLegacyCurrentGeneration(fileSystem, root) {
|
|
442
|
+
const generationsDir = join(root, MANAGED_GENERATIONS_DIRNAME);
|
|
443
|
+
const currentPath = join(root, MANAGED_CURRENT_LINK_NAME);
|
|
444
|
+
const currentStatus = await pathStatus(fileSystem, currentPath);
|
|
445
|
+
if (!currentStatus) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
if (!currentStatus.isSymbolicLink) {
|
|
449
|
+
throw new Error(`Managed config path must be a symbolic link: ${currentPath}`);
|
|
450
|
+
}
|
|
451
|
+
const currentTarget = await fileSystem.readLink(currentPath);
|
|
452
|
+
if (!isDirectManagedGeneration(root, currentTarget)) {
|
|
453
|
+
throw new Error(`Managed config current generation must be inside ${generationsDir}`);
|
|
454
|
+
}
|
|
455
|
+
const currentGenerationPath = resolve(root, currentTarget);
|
|
456
|
+
const generationStatus = await pathStatus(fileSystem, currentGenerationPath);
|
|
457
|
+
if (!generationStatus?.isDirectory || generationStatus.isSymbolicLink) {
|
|
458
|
+
throw new Error(`Managed config current generation is not a directory: ${currentGenerationPath}`);
|
|
459
|
+
}
|
|
460
|
+
return currentGenerationPath;
|
|
461
|
+
}
|
|
462
|
+
async function validateOptionalLegacyAliases(fileSystem, root) {
|
|
463
|
+
const aliases = [
|
|
464
|
+
[
|
|
465
|
+
join(root, DEFAULT_LOCAL_HOST_CONFIG_FILENAME),
|
|
466
|
+
`${MANAGED_CURRENT_LINK_NAME}/${DEFAULT_LOCAL_HOST_CONFIG_FILENAME}`,
|
|
467
|
+
],
|
|
468
|
+
[
|
|
469
|
+
join(root, DEFAULT_LOCAL_AGENTS_DIRNAME),
|
|
470
|
+
`${MANAGED_CURRENT_LINK_NAME}/${DEFAULT_LOCAL_AGENTS_DIRNAME}`,
|
|
471
|
+
],
|
|
472
|
+
];
|
|
473
|
+
for (const [path, target] of aliases) {
|
|
474
|
+
const status = await pathStatus(fileSystem, path);
|
|
475
|
+
if (status && (!status.isSymbolicLink || (await fileSystem.readLink(path)) !== target)) {
|
|
476
|
+
throw new Error(`Managed config path has an unexpected value: ${path}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async function prepareManagedRoot(fileSystem, root, platform) {
|
|
395
481
|
await ensureDirectory(fileSystem, root, MANAGED_ROOT_MODE);
|
|
396
482
|
const generationsDir = join(root, MANAGED_GENERATIONS_DIRNAME);
|
|
397
483
|
await ensureDirectory(fileSystem, generationsDir, MANAGED_ROOT_MODE);
|
|
484
|
+
if (platform === 'win32') {
|
|
485
|
+
await validateOptionalLegacyAliases(fileSystem, root);
|
|
486
|
+
const pointerGeneration = await resolvePointerGeneration(fileSystem, root);
|
|
487
|
+
const legacyGeneration = pointerGeneration
|
|
488
|
+
? undefined
|
|
489
|
+
: await resolveLegacyCurrentGeneration(fileSystem, root);
|
|
490
|
+
const currentGenerationPath = pointerGeneration ?? legacyGeneration;
|
|
491
|
+
if (currentGenerationPath) {
|
|
492
|
+
await fileSystem.chmod(currentGenerationPath, MANAGED_ROOT_MODE);
|
|
493
|
+
}
|
|
494
|
+
return { currentGenerationPath };
|
|
495
|
+
}
|
|
398
496
|
const currentPath = join(root, MANAGED_CURRENT_LINK_NAME);
|
|
399
497
|
const currentStatus = await pathStatus(fileSystem, currentPath);
|
|
400
498
|
const hostAliasPath = join(root, DEFAULT_LOCAL_HOST_CONFIG_FILENAME);
|
|
@@ -483,7 +581,7 @@ function supportsManagedGenerationLeases(fileSystem) {
|
|
|
483
581
|
'chmod' in fileSystem &&
|
|
484
582
|
'readLink' in fileSystem);
|
|
485
583
|
}
|
|
486
|
-
async function managedRootForHostConfigPath(fileSystem, hostConfigPath) {
|
|
584
|
+
async function managedRootForHostConfigPath(fileSystem, hostConfigPath, platform) {
|
|
487
585
|
if (hostConfigPath === join(dirname(hostConfigPath), DEFAULT_LOCAL_HOST_CONFIG_FILENAME)) {
|
|
488
586
|
const generationPath = dirname(hostConfigPath);
|
|
489
587
|
const generationsDir = dirname(generationPath);
|
|
@@ -497,6 +595,11 @@ async function managedRootForHostConfigPath(fileSystem, hostConfigPath) {
|
|
|
497
595
|
return undefined;
|
|
498
596
|
}
|
|
499
597
|
const root = dirname(hostConfigPath);
|
|
598
|
+
if (platform === 'win32') {
|
|
599
|
+
const pointerStatus = await pathStatus(fileSystem, join(root, MANAGED_CURRENT_POINTER_FILENAME));
|
|
600
|
+
const currentStatus = await pathStatus(fileSystem, join(root, MANAGED_CURRENT_LINK_NAME));
|
|
601
|
+
return pointerStatus || currentStatus ? root : undefined;
|
|
602
|
+
}
|
|
500
603
|
const currentPath = join(root, MANAGED_CURRENT_LINK_NAME);
|
|
501
604
|
const currentStatus = await pathStatus(fileSystem, currentPath);
|
|
502
605
|
if (!currentStatus?.isSymbolicLink) {
|
|
@@ -504,7 +607,7 @@ async function managedRootForHostConfigPath(fileSystem, hostConfigPath) {
|
|
|
504
607
|
}
|
|
505
608
|
return isDirectManagedGeneration(root, await fileSystem.readLink(currentPath)) ? root : undefined;
|
|
506
609
|
}
|
|
507
|
-
async function resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath) {
|
|
610
|
+
async function resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath, platform) {
|
|
508
611
|
const resolveHostConfigPath = async () => resolve(fileSystem.realPath
|
|
509
612
|
? await fileSystem.realPath(absoluteHostConfigPath)
|
|
510
613
|
: absoluteHostConfigPath);
|
|
@@ -512,14 +615,20 @@ async function resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConf
|
|
|
512
615
|
if (!supportsManagedGenerationLeases(fileSystem)) {
|
|
513
616
|
return { resolvedHostConfigPath: await resolveHostConfigPath(), release: noLease };
|
|
514
617
|
}
|
|
515
|
-
const root = await managedRootForHostConfigPath(fileSystem, absoluteHostConfigPath);
|
|
618
|
+
const root = await managedRootForHostConfigPath(fileSystem, absoluteHostConfigPath, platform);
|
|
516
619
|
if (!root) {
|
|
517
620
|
return { resolvedHostConfigPath: await resolveHostConfigPath(), release: noLease };
|
|
518
621
|
}
|
|
519
622
|
const releaseWriteLock = await acquireManagedWriteLock(fileSystem, root);
|
|
520
623
|
let leasePath;
|
|
521
624
|
try {
|
|
522
|
-
const
|
|
625
|
+
const activeGenerationPath = platform === 'win32'
|
|
626
|
+
? ((await resolvePointerGeneration(fileSystem, root)) ??
|
|
627
|
+
(await resolveLegacyCurrentGeneration(fileSystem, root)))
|
|
628
|
+
: undefined;
|
|
629
|
+
const resolvedHostConfigPath = activeGenerationPath
|
|
630
|
+
? join(activeGenerationPath, DEFAULT_LOCAL_HOST_CONFIG_FILENAME)
|
|
631
|
+
: await resolveHostConfigPath();
|
|
523
632
|
const generationPath = dirname(resolvedHostConfigPath);
|
|
524
633
|
const generationsDir = join(root, MANAGED_GENERATIONS_DIRNAME);
|
|
525
634
|
if (dirname(generationPath) !== generationsDir ||
|
|
@@ -660,7 +769,7 @@ export async function loadLocalConfigSnapshot(hostConfigPath, deps = {}) {
|
|
|
660
769
|
: absoluteHostConfigPath),
|
|
661
770
|
release: async () => undefined,
|
|
662
771
|
}
|
|
663
|
-
: await resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath);
|
|
772
|
+
: await resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath, deps.platform ?? process.platform);
|
|
664
773
|
try {
|
|
665
774
|
const { resolvedHostConfigPath } = managedGeneration;
|
|
666
775
|
const hostConfigDir = dirname(absoluteHostConfigPath);
|
|
@@ -718,7 +827,7 @@ export async function loadLocalConfigGenerateSpec(hostConfigPath, deps = {}) {
|
|
|
718
827
|
: absoluteHostConfigPath),
|
|
719
828
|
release: async () => undefined,
|
|
720
829
|
}
|
|
721
|
-
: await resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath);
|
|
830
|
+
: await resolveHostConfigWithGenerationLease(fileSystem, absoluteHostConfigPath, deps.platform ?? process.platform);
|
|
722
831
|
try {
|
|
723
832
|
const { resolvedHostConfigPath } = managedGeneration;
|
|
724
833
|
const hostConfigRecord = await loadParsedDocument(fileSystem, resolvedHostConfigPath, 'host config');
|
|
@@ -755,9 +864,20 @@ export async function loadLocalConfigGenerateSpec(hostConfigPath, deps = {}) {
|
|
|
755
864
|
await managedGeneration.release();
|
|
756
865
|
}
|
|
757
866
|
}
|
|
867
|
+
export async function hasManagedLocalConfig(rootPath, deps = {}) {
|
|
868
|
+
const fileSystem = deps.fileSystem ?? nodeFileSystem;
|
|
869
|
+
const root = resolve(rootPath);
|
|
870
|
+
const platform = deps.platform ?? process.platform;
|
|
871
|
+
if (platform === 'win32') {
|
|
872
|
+
return ((await resolvePointerGeneration(fileSystem, root)) !== undefined ||
|
|
873
|
+
(await resolveLegacyCurrentGeneration(fileSystem, root)) !== undefined);
|
|
874
|
+
}
|
|
875
|
+
return (await pathStatus(fileSystem, join(root, DEFAULT_LOCAL_HOST_CONFIG_FILENAME))) !== undefined;
|
|
876
|
+
}
|
|
758
877
|
export async function materializeLocalConfig(rootPath, spec, deps = {}) {
|
|
759
878
|
const fileSystem = deps.fileSystem ?? nodeFileSystem;
|
|
760
879
|
const layout = resolveLocalConfigLayout(rootPath);
|
|
880
|
+
const platform = deps.platform ?? process.platform;
|
|
761
881
|
const desiredAgentFiles = new Map();
|
|
762
882
|
for (const agent of spec.agents) {
|
|
763
883
|
desiredAgentFiles.set(toGeneratedAgentConfigPath(layout.agentsDir, agent.key), agent);
|
|
@@ -768,8 +888,10 @@ export async function materializeLocalConfig(rootPath, spec, deps = {}) {
|
|
|
768
888
|
await ensureDirectory(fileSystem, layout.root, MANAGED_ROOT_MODE);
|
|
769
889
|
const releaseWriteLock = await acquireManagedWriteLock(fileSystem, layout.root);
|
|
770
890
|
try {
|
|
771
|
-
const { currentGenerationPath } = await prepareManagedRoot(fileSystem, layout.root);
|
|
772
|
-
const priorAgentConfigPaths = await listSupportedConfigPaths(fileSystem,
|
|
891
|
+
const { currentGenerationPath } = await prepareManagedRoot(fileSystem, layout.root, platform);
|
|
892
|
+
const priorAgentConfigPaths = (await listSupportedConfigPaths(fileSystem, currentGenerationPath
|
|
893
|
+
? join(currentGenerationPath, DEFAULT_LOCAL_AGENTS_DIRNAME)
|
|
894
|
+
: layout.agentsDir)).map((path) => join(layout.agentsDir, path.slice(dirname(path).length + 1)));
|
|
773
895
|
const generationName = randomUUID();
|
|
774
896
|
const generationsDir = join(layout.root, MANAGED_GENERATIONS_DIRNAME);
|
|
775
897
|
const generationPath = join(generationsDir, generationName);
|
|
@@ -792,9 +914,33 @@ export async function materializeLocalConfig(rootPath, spec, deps = {}) {
|
|
|
792
914
|
acquireManagedGenerationLease: false,
|
|
793
915
|
env: deps.env,
|
|
794
916
|
});
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
917
|
+
if (platform === 'win32') {
|
|
918
|
+
const nextCurrentPath = join(layout.root, `.${MANAGED_CURRENT_POINTER_FILENAME}-${generationName}`);
|
|
919
|
+
await fileSystem.writeFile(nextCurrentPath, `${JSON.stringify({
|
|
920
|
+
schemaVersion: MANAGED_CURRENT_POINTER_SCHEMA_VERSION,
|
|
921
|
+
generation: generationName,
|
|
922
|
+
})}\n`);
|
|
923
|
+
await fileSystem.chmod(nextCurrentPath, MANAGED_CONFIG_MODE);
|
|
924
|
+
try {
|
|
925
|
+
await fileSystem.rename(nextCurrentPath, join(layout.root, MANAGED_CURRENT_POINTER_FILENAME));
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
try {
|
|
929
|
+
await fileSystem.removeFile(nextCurrentPath);
|
|
930
|
+
}
|
|
931
|
+
catch (cleanupError) {
|
|
932
|
+
if (!isNotFoundError(cleanupError)) {
|
|
933
|
+
throw new AggregateError([error, cleanupError], 'Managed config pointer publication and cleanup both failed');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
throw error;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
const nextCurrentPath = join(layout.root, `.${MANAGED_CURRENT_LINK_NAME}-${generationName}`);
|
|
941
|
+
await fileSystem.symlink(join(MANAGED_GENERATIONS_DIRNAME, generationName), nextCurrentPath);
|
|
942
|
+
await fileSystem.rename(nextCurrentPath, join(layout.root, MANAGED_CURRENT_LINK_NAME));
|
|
943
|
+
}
|
|
798
944
|
published = true;
|
|
799
945
|
try {
|
|
800
946
|
await pruneSupersededGenerations(fileSystem, generationsDir, new Set([generationPath, currentGenerationPath].filter((path) => path !== undefined)));
|
package/dist/managed-daemon.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from './pro
|
|
|
9
9
|
import { resolveChannelWorkspaceDirectory, resolveChannelWorkspaceRootDirectory, resolveManagedWorkspaceCollectionRoot, resolveTaskThreadScratchWorkspaceDirectory, } from './context/injection.js';
|
|
10
10
|
import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, CURRENT_AGENTS_HOST_PACKAGE_VERSION, createManagedRuntimeSettingsFingerprint, INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV, INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV, INTERNAL_POLICY_MODE_ENV, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE, MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION, resolveDisabledDefaultCompatibilityGates, resolveManagedRuntimeSettingsSnapshot, serializeManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
|
|
11
11
|
import { DEFAULT_PROVIDER_COMMAND_CONFIG, hasLegacyClaudeOneShotArgs, loadConfigFromEnv, } from './config.js';
|
|
12
|
-
import { loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
|
|
12
|
+
import { hasManagedLocalConfig, loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
|
|
13
13
|
import { normalizeManagedRuntimeKey, resolveManagedBootstrapLockPath, resolveManagedDaemonEndpoint, resolveManagedDaemonLogPath, resolveManagedRuntimeRoot, resolveManagedStateRoot, resolveManagedRuntimeSettingsPath, } from './state-paths.js';
|
|
14
14
|
const MANAGED_ROOT_MODE = 0o700;
|
|
15
15
|
const MANAGED_CONFIG_MODE = 0o600;
|
|
@@ -1130,7 +1130,7 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
|
|
|
1130
1130
|
});
|
|
1131
1131
|
if (options.apiKey !== undefined
|
|
1132
1132
|
&& currentRuntimeSettings.persistedSettingsSnapshot == null
|
|
1133
|
-
&& await
|
|
1133
|
+
&& await hasManagedLocalConfig(resolved.rootPath)) {
|
|
1134
1134
|
throw new Error(`Managed runtime root ${resolved.rootPath} predates managed-runtime-settings.json; run \`agents-host start-managed ${options.serverUrl}\` once to migrate it before updating an individual agent`);
|
|
1135
1135
|
}
|
|
1136
1136
|
const explicitManagedAgentOverrides = collectExplicitManagedAgentEnvOverrides(options.processEnv ?? process.env, options.env);
|
|
@@ -1628,9 +1628,16 @@ export class ManagedAgentsHostDaemon {
|
|
|
1628
1628
|
this.endpoint = resolveManagedDaemonEndpoint(this.rootPath, deps.platform);
|
|
1629
1629
|
this.createSupervisor =
|
|
1630
1630
|
deps.createSupervisor ??
|
|
1631
|
-
((configPath, daemonDebug) => new AgentsHostSupervisor(configPath, {
|
|
1632
|
-
|
|
1633
|
-
|
|
1631
|
+
((configPath, daemonDebug) => new AgentsHostSupervisor(configPath, {
|
|
1632
|
+
debug: daemonDebug,
|
|
1633
|
+
platform: deps.platform,
|
|
1634
|
+
}));
|
|
1635
|
+
this.loadSpec =
|
|
1636
|
+
deps.loadSpec ??
|
|
1637
|
+
((configPath) => loadLocalConfigGenerateSpec(configPath, { platform: deps.platform }));
|
|
1638
|
+
this.materialize =
|
|
1639
|
+
deps.materialize ??
|
|
1640
|
+
((managedRootPath, spec) => materializeLocalConfig(managedRootPath, spec, { platform: deps.platform }));
|
|
1634
1641
|
const managedRuntimeSettings = resolveDesiredManagedRuntimeSettings(process.env);
|
|
1635
1642
|
this.managedRuntimeFingerprint = managedRuntimeSettings.fingerprint;
|
|
1636
1643
|
this.logger = deps.logger ?? console;
|
package/dist/plugin-sdk.js
CHANGED
|
@@ -3814,6 +3814,47 @@ var BppTransport = class {
|
|
|
3814
3814
|
getResolvedAgentId() {
|
|
3815
3815
|
return this.currentAgentId();
|
|
3816
3816
|
}
|
|
3817
|
+
/** Fire-and-forget metadata-only lifecycle rail. Callers durably retry until ACK. */
|
|
3818
|
+
sendExecutionTelemetry(frame) {
|
|
3819
|
+
const base = {
|
|
3820
|
+
agent_id: this.currentAgentId(),
|
|
3821
|
+
execution_id: frame.executionId,
|
|
3822
|
+
telemetry_context: frame.telemetryContext,
|
|
3823
|
+
event_id: frame.eventId
|
|
3824
|
+
};
|
|
3825
|
+
switch (frame.type) {
|
|
3826
|
+
case "execution_started":
|
|
3827
|
+
this.send({
|
|
3828
|
+
type: frame.type,
|
|
3829
|
+
...base,
|
|
3830
|
+
started_at: frame.startedAt
|
|
3831
|
+
});
|
|
3832
|
+
break;
|
|
3833
|
+
case "execution_usage":
|
|
3834
|
+
this.send({
|
|
3835
|
+
type: frame.type,
|
|
3836
|
+
...base,
|
|
3837
|
+
usage_granularity: frame.usageGranularity,
|
|
3838
|
+
...frame.callIndex === void 0 ? {} : { call_index: frame.callIndex },
|
|
3839
|
+
provider: frame.provider,
|
|
3840
|
+
model: frame.model,
|
|
3841
|
+
input_tokens: frame.inputTokens,
|
|
3842
|
+
output_tokens: frame.outputTokens,
|
|
3843
|
+
cache_read_tokens: frame.cacheReadTokens,
|
|
3844
|
+
cache_write_tokens: frame.cacheWriteTokens,
|
|
3845
|
+
observed_at: frame.observedAt
|
|
3846
|
+
});
|
|
3847
|
+
break;
|
|
3848
|
+
case "execution_finished":
|
|
3849
|
+
this.send({
|
|
3850
|
+
type: frame.type,
|
|
3851
|
+
...base,
|
|
3852
|
+
succeeded: frame.succeeded,
|
|
3853
|
+
ended_at: frame.endedAt
|
|
3854
|
+
});
|
|
3855
|
+
break;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3817
3858
|
async connect(ctx) {
|
|
3818
3859
|
this.ctx = ctx;
|
|
3819
3860
|
this.closed = false;
|
|
@@ -3991,7 +4032,12 @@ var BppTransport = class {
|
|
|
3991
4032
|
plugin_id: ctx.pluginId ?? "",
|
|
3992
4033
|
token: "",
|
|
3993
4034
|
version: PROTOCOL_VERSION,
|
|
3994
|
-
capabilities: JSON.stringify([
|
|
4035
|
+
capabilities: JSON.stringify([
|
|
4036
|
+
"semantic_action",
|
|
4037
|
+
"inbound_message",
|
|
4038
|
+
"read_file",
|
|
4039
|
+
"execution_telemetry"
|
|
4040
|
+
])
|
|
3995
4041
|
};
|
|
3996
4042
|
this.sendTo(ws, connectFrame);
|
|
3997
4043
|
try {
|
|
@@ -4069,6 +4115,11 @@ var BppTransport = class {
|
|
|
4069
4115
|
case "inbound_message":
|
|
4070
4116
|
this.handleInbound(env);
|
|
4071
4117
|
break;
|
|
4118
|
+
case "execution_telemetry_ack":
|
|
4119
|
+
this.handlers?.onExecutionTelemetryAck?.({
|
|
4120
|
+
eventId: env.event_id
|
|
4121
|
+
});
|
|
4122
|
+
break;
|
|
4072
4123
|
case "semantic_action_result":
|
|
4073
4124
|
this.handleActionResult(env);
|
|
4074
4125
|
break;
|
|
@@ -4260,7 +4311,12 @@ var BppTransport = class {
|
|
|
4260
4311
|
const specific = this.stopTurnHandler;
|
|
4261
4312
|
const generic = this.serverRequestHandler;
|
|
4262
4313
|
try {
|
|
4263
|
-
const answer = specific ? await specific(channelId) : generic ? await generic(STOP_TURN_ACTION, data) :
|
|
4314
|
+
const answer = specific ? await specific(channelId) : generic ? await generic(STOP_TURN_ACTION, data) : (
|
|
4315
|
+
// No handler is not the same as "unsupported": nothing was aborted,
|
|
4316
|
+
// which is what false already says, and the rail carries no third
|
|
4317
|
+
// value to draw that distinction with.
|
|
4318
|
+
{ aborted: false }
|
|
4319
|
+
);
|
|
4264
4320
|
this.send({ type: "response", id, data: this.asStopTurnResult(answer) });
|
|
4265
4321
|
} catch (err) {
|
|
4266
4322
|
this.logger?.warn("bpp.stop_turn_handler_failed", err);
|
|
@@ -4345,7 +4401,10 @@ var BppTransport = class {
|
|
|
4345
4401
|
frame.paths = action.activity.paths;
|
|
4346
4402
|
break;
|
|
4347
4403
|
case "plan":
|
|
4348
|
-
frame.plan = action.activity.entries.map((entry) => ({
|
|
4404
|
+
frame.plan = action.activity.entries.map((entry) => ({
|
|
4405
|
+
label: entry.label,
|
|
4406
|
+
status: entry.status
|
|
4407
|
+
}));
|
|
4349
4408
|
break;
|
|
4350
4409
|
case "output":
|
|
4351
4410
|
frame.stream = action.activity.stream;
|
|
@@ -4467,7 +4526,11 @@ function encodeActionPayload(action) {
|
|
|
4467
4526
|
case "delete_message":
|
|
4468
4527
|
return { message_id: action.messageId };
|
|
4469
4528
|
case "react":
|
|
4470
|
-
return {
|
|
4529
|
+
return {
|
|
4530
|
+
message_id: action.messageId,
|
|
4531
|
+
emoji: action.emoji,
|
|
4532
|
+
removed: action.removed ?? false
|
|
4533
|
+
};
|
|
4471
4534
|
case "create_dm":
|
|
4472
4535
|
return { user_id: action.userId };
|
|
4473
4536
|
case "get_me":
|
|
@@ -4484,6 +4547,7 @@ function encodeActionPayload(action) {
|
|
|
4484
4547
|
return {};
|
|
4485
4548
|
case "create_channel":
|
|
4486
4549
|
return {
|
|
4550
|
+
guild_id: action.guildId,
|
|
4487
4551
|
name: action.name,
|
|
4488
4552
|
topic: action.topic,
|
|
4489
4553
|
visibility: action.visibility,
|
|
@@ -4652,9 +4716,23 @@ function mapInbound(frame) {
|
|
|
4652
4716
|
kind,
|
|
4653
4717
|
channelId: frame.channel_id,
|
|
4654
4718
|
channelType: frame.channel_type,
|
|
4719
|
+
...frame.conversation_context ? {
|
|
4720
|
+
conversationContext: {
|
|
4721
|
+
guildId: frame.conversation_context.guild_id,
|
|
4722
|
+
topLevelChannelId: frame.conversation_context.top_level_channel_id,
|
|
4723
|
+
...frame.conversation_context.thread_id ? { threadId: frame.conversation_context.thread_id } : {},
|
|
4724
|
+
...frame.conversation_context.task_id ? { taskId: frame.conversation_context.task_id } : {}
|
|
4725
|
+
}
|
|
4726
|
+
} : {},
|
|
4655
4727
|
authorName: frame.author_name,
|
|
4656
4728
|
createdAt: frame.created_at
|
|
4657
4729
|
};
|
|
4730
|
+
if (frame.execution_id && frame.telemetry_context) {
|
|
4731
|
+
base.executionGrant = {
|
|
4732
|
+
executionId: frame.execution_id,
|
|
4733
|
+
telemetryContext: frame.telemetry_context
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4658
4736
|
switch (kind) {
|
|
4659
4737
|
case "message":
|
|
4660
4738
|
case "mention": {
|
|
@@ -4842,7 +4920,8 @@ var Client = class {
|
|
|
4842
4920
|
message: /* @__PURE__ */ new Set(),
|
|
4843
4921
|
configUpdate: /* @__PURE__ */ new Set(),
|
|
4844
4922
|
permissionDenied: /* @__PURE__ */ new Set(),
|
|
4845
|
-
connectionState: /* @__PURE__ */ new Set()
|
|
4923
|
+
connectionState: /* @__PURE__ */ new Set(),
|
|
4924
|
+
executionTelemetryAck: /* @__PURE__ */ new Set()
|
|
4846
4925
|
};
|
|
4847
4926
|
constructor(opts, transport) {
|
|
4848
4927
|
this.opts = opts;
|
|
@@ -4914,6 +4993,7 @@ var Client = class {
|
|
|
4914
4993
|
async createChannel(input) {
|
|
4915
4994
|
return await this.t.perform({
|
|
4916
4995
|
op: "create_channel",
|
|
4996
|
+
guildId: input.guildId,
|
|
4917
4997
|
name: input.name,
|
|
4918
4998
|
topic: input.topic,
|
|
4919
4999
|
visibility: input.visibility,
|
|
@@ -4955,6 +5035,9 @@ var Client = class {
|
|
|
4955
5035
|
activity: input.activity
|
|
4956
5036
|
}).catch((err) => this.logger.warn("reportActivity failed", err));
|
|
4957
5037
|
}
|
|
5038
|
+
reportExecutionTelemetry(frame) {
|
|
5039
|
+
this.t.sendExecutionTelemetry?.(frame);
|
|
5040
|
+
}
|
|
4958
5041
|
reportTurnActivity(input) {
|
|
4959
5042
|
return new TurnActivityReporter({
|
|
4960
5043
|
...input,
|
|
@@ -5076,7 +5159,8 @@ var Client = class {
|
|
|
5076
5159
|
onStateChange: (s) => {
|
|
5077
5160
|
this._state = s;
|
|
5078
5161
|
this.emit("connectionState", s);
|
|
5079
|
-
}
|
|
5162
|
+
},
|
|
5163
|
+
onExecutionTelemetryAck: (ack) => this.emit("executionTelemetryAck", ack)
|
|
5080
5164
|
};
|
|
5081
5165
|
}
|
|
5082
5166
|
context() {
|