@borgee/agents-host 0.2.33 → 0.2.44
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 +28 -7
- package/dist/agents-host.d.ts +19 -0
- package/dist/agents-host.js +163 -44
- package/dist/chat/chat-control-plane.d.ts +9 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +10 -1
- package/dist/chat/sdk-chat-control-plane.js +9 -0
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.js +5 -0
- package/dist/compatibility-gates.d.ts +1 -0
- package/dist/compatibility-gates.js +2 -0
- package/dist/config.d.ts +4 -1
- package/dist/config.js +21 -3
- package/dist/context/injection.d.ts +5 -3
- package/dist/context/injection.js +45 -29
- package/dist/context/main-session-delegation.d.ts +1 -0
- package/dist/context/main-session-delegation.js +6 -0
- package/dist/context/prompt.js +25 -35
- package/dist/context/skill-manual.d.ts +13 -0
- package/dist/context/skill-manual.js +18 -0
- package/dist/context/turn-preparation.js +13 -4
- package/dist/gateway/localhost-gateway.js +75 -1
- package/dist/hosted-turn-content.d.ts +15 -0
- package/dist/hosted-turn-content.js +50 -0
- package/dist/local-config.js +10 -1
- package/dist/managed-daemon.d.ts +3 -2
- package/dist/managed-daemon.js +82 -29
- package/dist/plugin-sdk.js +58 -1
- package/dist/plugin-sdk.js.map +2 -2
- package/dist/policy/gateway-authorization.d.ts +18 -3
- package/dist/policy/gateway-authorization.js +33 -1
- package/dist/providers/claude/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +13 -1
- package/dist/providers/claude/cli-client.d.ts +36 -3
- package/dist/providers/claude/cli-client.js +225 -37
- package/dist/providers/codex/adapter.d.ts +3 -1
- package/dist/providers/codex/adapter.js +13 -1
- package/dist/providers/codex/cli-client.d.ts +35 -3
- package/dist/providers/codex/cli-client.js +212 -31
- package/dist/providers/codex/project-doc.js +16 -29
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +13 -1
- package/dist/providers/copilot/cli-client.d.ts +34 -2
- package/dist/providers/copilot/cli-client.js +196 -18
- package/dist/providers/create-provider.d.ts +1 -1
- package/dist/providers/create-provider.js +30 -14
- package/dist/providers/idle-backend-shutdown.d.ts +16 -0
- package/dist/providers/idle-backend-shutdown.js +53 -0
- package/dist/providers/provider-adapter.d.ts +35 -0
- package/dist/providers/provider-adapter.js +44 -1
- package/dist/state-paths.d.ts +9 -1
- package/dist/state-paths.js +22 -3
- package/dist/types.d.ts +40 -2
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +133 -35
- package/skills/borgee-agent/references/errors.md +38 -0
- package/skills/borgee-agent/references/task-properties.md +30 -0
- package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
- package/skills/borgee-agent/borgee-agent.mjs +0 -507
- package/skills/borgee-agent/borgee-agent.py +0 -438
package/dist/cli-args.d.ts
CHANGED
|
@@ -85,4 +85,4 @@ export declare function parseCleanupManagedArgs(argv: string[]): ParsedCleanupMa
|
|
|
85
85
|
export declare function parseApplyManagedArgs(argv: string[]): ParsedApplyManagedArgs;
|
|
86
86
|
/** `update` always installs the latest published release; it takes no options. */
|
|
87
87
|
export declare function assertNoUpdateArgs(argv: string[]): void;
|
|
88
|
-
export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n agents-host update\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)\n --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n update Update the globally installed @borgee/agents-host to the latest release\n\nStartup update check:\n Startup commands print an advisory notice on stderr when a newer release is published.\n Set AGENTS_HOST_DISABLE_UPDATE_CHECK=1 to turn that check off.\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n agents-host update\n";
|
|
88
|
+
export declare const USAGE = "Usage:\n agents-host start <serverUrl> <apiKey> [options]\n agents-host start-managed <serverUrl>\n agents-host start-managed <serverUrl> <apiKey> [agent-options]\n agents-host describe-managed <serverUrl>\n agents-host cleanup-managed <serverUrl> [--purge]\n agents-host apply-managed <serverUrl> --stdin\n agents-host apply-managed <serverUrl> --spec-json <json>\n agents-host log <serverUrl> [--lines <n>] [--follow]\n agents-host log <serverUrl> --path\n agents-host start --config <path-to-host-config> [--debug]\n agents-host validate --config <path-to-host-config>\n agents-host describe --config <path-to-host-config>\n agents-host print-layout --root <dir>\n agents-host generate-config --root <dir> --stdin\n agents-host generate-config --root <dir> --spec-json <json>\n agents-host update\n\nForeground start options:\n --debug Enable host/provider debug logs (or set AGENTS_HOST_DEBUG=1)\n\nSingle-agent agent options:\n --name <name> Display name (default: Assistant)\n --provider <claude|codex|copilot>\n Runtime provider (default: claude)\n --claude-command <cmd> Local Claude ACP adapter command (default: claude-agent-acp)\n --claude-args <args> Local Claude ACP adapter args (empty on the shipped bundled path)\n --codex-command <cmd> Local Codex ACP adapter command (default: codex-acp)\n --codex-args <args> Local Codex ACP adapter args\n --copilot-command <cmd> Local Copilot CLI command (default: copilot)\n --copilot-args <args> Ignored by the Copilot ACP prototype\n --copilot-session-ttl-minutes <minutes>\n Idle session TTL for Copilot ACP sessions (default: 2880)\n --provider-idle-shutdown-minutes <minutes>\n Shut the shared ACP adapter process down after this much idle\n time and re-spawn it on the next turn; 0 keeps it resident\n (default: 10)\n\nCommand summary:\n start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)\n start --config <path> Start agents + supervisor + watchers from a host config file\n start-managed <serverUrl> Restart or resume an existing per-serverUrl managed daemon\n start-managed <serverUrl> <apiKey>\n Upsert one agent into the per-serverUrl local daemon and exit after reconcile\n describe-managed <serverUrl> Print managed-runtime spec JSON for machine callers\n cleanup-managed <serverUrl> Stop one managed-runtime daemon locally; --purge also removes its runtime root\n apply-managed <serverUrl> Apply one managed-runtime full-set spec for machine callers\n log <serverUrl> Print the managed daemon log tail for one server runtime\n validate --config <path> Validate local-config files without starting agents or watchers\n describe --config <path> Print the current managed full-set spec as JSON\n print-layout --root <dir> Print the canonical default local-config layout as JSON\n generate-config --root <dir> --stdin Materialize canonical local-config files from stdin\n generate-config --root <dir> --spec-json <json>\n Compatibility input; JSON is exposed in process arguments\n update Update the globally installed @borgee/agents-host to the latest release\n\nStartup update check:\n Startup commands print an advisory notice on stderr when a newer release is published.\n Set AGENTS_HOST_DISABLE_UPDATE_CHECK=1 to turn that check off.\n\nExamples:\n agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot --debug\n agents-host start-managed https://borgee.example.com\n agents-host start-managed https://borgee.example.com bgr_xxxxxxxx --provider copilot\n agents-host describe-managed https://borgee.example.com\n agents-host cleanup-managed https://borgee.example.com --purge\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host apply-managed https://borgee.example.com --stdin\n agents-host log https://borgee.example.com --lines 200 --follow\n agents-host log https://borgee.example.com --path\n agents-host start --config ./agents-host.yaml --debug\n agents-host validate --config ./agents-host.yaml\n agents-host describe --config ./agents-host.yaml\n agents-host print-layout --root ./runtime-root\n printf '%s' '{\"host\":{\"borgeeBaseUrl\":\"https://borgee.example.com\"},\"agents\":[{\"key\":\"cp1\",\"name\":\"Copilot\",\"apiKey\":\"bgr_xxx\",\"provider\":\"copilot\"}]}' | agents-host generate-config --root ./runtime-root --stdin\n agents-host update\n";
|
package/dist/cli-args.js
CHANGED
|
@@ -15,6 +15,7 @@ export const CLI_FLAG_TO_ENV = {
|
|
|
15
15
|
'copilot-command': 'COPILOT_COMMAND',
|
|
16
16
|
'copilot-args': 'COPILOT_ARGS',
|
|
17
17
|
'copilot-session-ttl-minutes': 'COPILOT_SESSION_TTL_MINUTES',
|
|
18
|
+
'provider-idle-shutdown-minutes': 'PROVIDER_IDLE_SHUTDOWN_MINUTES',
|
|
18
19
|
};
|
|
19
20
|
const SINGLE_AGENT_FLAG_NAMES = new Set(Object.keys(CLI_FLAG_TO_ENV));
|
|
20
21
|
const FLAGS_ALLOWING_DASH_PREFIX_VALUE = new Set(['claude-args', 'codex-args', 'copilot-args']);
|
|
@@ -512,6 +513,10 @@ Single-agent agent options:
|
|
|
512
513
|
--copilot-args <args> Ignored by the Copilot ACP prototype
|
|
513
514
|
--copilot-session-ttl-minutes <minutes>
|
|
514
515
|
Idle session TTL for Copilot ACP sessions (default: 2880)
|
|
516
|
+
--provider-idle-shutdown-minutes <minutes>
|
|
517
|
+
Shut the shared ACP adapter process down after this much idle
|
|
518
|
+
time and re-spawn it on the next turn; 0 keeps it resident
|
|
519
|
+
(default: 10)
|
|
515
520
|
|
|
516
521
|
Command summary:
|
|
517
522
|
start <serverUrl> <apiKey> Start one foreground hosted agent (legacy-compatible behavior)
|
|
@@ -13,6 +13,7 @@ export declare const COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE =
|
|
|
13
13
|
export declare const TOKEN_BINDING_COMPATIBILITY_GATE = "token-binding";
|
|
14
14
|
export declare const POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE = "policy-audit-enforcement";
|
|
15
15
|
export declare const MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE = "managed-runtime-convergence";
|
|
16
|
+
export declare const PROVIDER_IDLE_SHUTDOWN_COMPATIBILITY_GATE = "provider-idle-shutdown";
|
|
16
17
|
export declare const COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES";
|
|
17
18
|
export declare const INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES";
|
|
18
19
|
export declare const INTERNAL_POLICY_MODE_ENV = "AGENTS_HOST_INTERNAL_POLICY_MODE";
|
|
@@ -14,6 +14,7 @@ export const COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE = 'collab
|
|
|
14
14
|
export const TOKEN_BINDING_COMPATIBILITY_GATE = 'token-binding';
|
|
15
15
|
export const POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE = 'policy-audit-enforcement';
|
|
16
16
|
export const MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE = 'managed-runtime-convergence';
|
|
17
|
+
export const PROVIDER_IDLE_SHUTDOWN_COMPATIBILITY_GATE = 'provider-idle-shutdown';
|
|
17
18
|
export const COMPATIBILITY_GATES_ENV = 'AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES';
|
|
18
19
|
export const INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV = 'AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES';
|
|
19
20
|
export const INTERNAL_POLICY_MODE_ENV = 'AGENTS_HOST_INTERNAL_POLICY_MODE';
|
|
@@ -30,6 +31,7 @@ export const DEFAULT_INTERNAL_COMPATIBILITY_GATES = Object.freeze([
|
|
|
30
31
|
LOCALHOST_GATEWAY_COMPATIBILITY_GATE,
|
|
31
32
|
MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE,
|
|
32
33
|
POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE,
|
|
34
|
+
PROVIDER_IDLE_SHUTDOWN_COMPATIBILITY_GATE,
|
|
33
35
|
SKILL_RUNTIME_COMPATIBILITY_GATE,
|
|
34
36
|
TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE,
|
|
35
37
|
TOKEN_BINDING_COMPATIBILITY_GATE,
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AgentsHostConfig, ProviderCommandConfig, ProviderKind } from './types.js';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const MAX_TIMER_DELAY_MINUTES: number;
|
|
3
3
|
export declare const DEFAULT_COPILOT_SESSION_TTL_MINUTES: number;
|
|
4
|
+
export declare const PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES = 0;
|
|
5
|
+
export declare const DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES = 10;
|
|
4
6
|
export declare const DEFAULT_CLAUDE_COMMAND = "claude-agent-acp";
|
|
5
7
|
export declare const LEGACY_CLAUDE_COMMAND = "claude";
|
|
6
8
|
export declare const LEGACY_CLAUDE_ONE_SHOT_ARGS: readonly ["--print", "--permission-mode", "bypassPermissions"];
|
|
@@ -10,6 +12,7 @@ export declare function optionalNonEmptyString(value: unknown, fieldName: string
|
|
|
10
12
|
export declare function parseArgs(value: string): string[];
|
|
11
13
|
export declare function optionalStringArray(value: unknown, fieldName: string, sourceLabel: string): string[] | undefined;
|
|
12
14
|
export declare function parseCopilotSessionTtlMinutesValue(value: unknown, sourceLabel: string): number;
|
|
15
|
+
export declare function parseProviderIdleShutdownMinutesValue(value: unknown, sourceLabel: string): number;
|
|
13
16
|
export declare function resolveProvider(rawValue: string, sourceLabel: string): ProviderKind;
|
|
14
17
|
export declare function assertProviderCompatibility(provider: ProviderKind, sourceLabel: string, env?: NodeJS.ProcessEnv): void;
|
|
15
18
|
export declare function isLegacyClaudeCompatibilityAlias(command: string, args: string[]): boolean;
|
package/dist/config.js
CHANGED
|
@@ -2,8 +2,10 @@ import { basename } from 'node:path';
|
|
|
2
2
|
import { resolveSingleAgentStateRoot } from './state-paths.js';
|
|
3
3
|
import { CODEX_PROVIDER_COMPATIBILITY_GATE, resolveInternalCompatibilityGates, } from './compatibility-gates.js';
|
|
4
4
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
5
|
-
export const
|
|
5
|
+
export const MAX_TIMER_DELAY_MINUTES = MAX_TIMER_DELAY_MS / 60_000;
|
|
6
6
|
export const DEFAULT_COPILOT_SESSION_TTL_MINUTES = 2 * 24 * 60;
|
|
7
|
+
export const PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES = 0;
|
|
8
|
+
export const DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES = 10;
|
|
7
9
|
export const DEFAULT_CLAUDE_COMMAND = 'claude-agent-acp';
|
|
8
10
|
export const LEGACY_CLAUDE_COMMAND = 'claude';
|
|
9
11
|
export const LEGACY_CLAUDE_ONE_SHOT_ARGS = ['--print', '--permission-mode', 'bypassPermissions'];
|
|
@@ -15,6 +17,7 @@ export const DEFAULT_PROVIDER_COMMAND_CONFIG = {
|
|
|
15
17
|
copilotCommand: 'copilot',
|
|
16
18
|
copilotArgs: ['-s', '--no-color', '--allow-all-tools', '--output-format', 'text'],
|
|
17
19
|
copilotSessionTtlMinutes: DEFAULT_COPILOT_SESSION_TTL_MINUTES,
|
|
20
|
+
providerIdleShutdownMinutes: DEFAULT_PROVIDER_IDLE_SHUTDOWN_MINUTES,
|
|
18
21
|
};
|
|
19
22
|
function requireEnv(name, env) {
|
|
20
23
|
return requireNonEmptyString(env[name], `Missing required environment variable: ${name}`);
|
|
@@ -57,8 +60,18 @@ export function parseCopilotSessionTtlMinutesValue(value, sourceLabel) {
|
|
|
57
60
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
58
61
|
throw new Error(`Invalid COPILOT_SESSION_TTL_MINUTES in ${sourceLabel}: expected a positive number`);
|
|
59
62
|
}
|
|
60
|
-
if (parsed >
|
|
61
|
-
throw new Error(`Invalid COPILOT_SESSION_TTL_MINUTES in ${sourceLabel}: must be <= ${
|
|
63
|
+
if (parsed > MAX_TIMER_DELAY_MINUTES) {
|
|
64
|
+
throw new Error(`Invalid COPILOT_SESSION_TTL_MINUTES in ${sourceLabel}: must be <= ${MAX_TIMER_DELAY_MINUTES.toFixed(2)} minutes to fit within the Node.js timer limit`);
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
export function parseProviderIdleShutdownMinutesValue(value, sourceLabel) {
|
|
69
|
+
const parsed = typeof value === 'number' ? value : Number(String(value).trim());
|
|
70
|
+
if (!Number.isFinite(parsed) || parsed < PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES) {
|
|
71
|
+
throw new Error(`Invalid PROVIDER_IDLE_SHUTDOWN_MINUTES in ${sourceLabel}: expected a non-negative number, where ${PROVIDER_IDLE_SHUTDOWN_DISABLED_MINUTES} keeps the shared adapter process resident`);
|
|
72
|
+
}
|
|
73
|
+
if (parsed > MAX_TIMER_DELAY_MINUTES) {
|
|
74
|
+
throw new Error(`Invalid PROVIDER_IDLE_SHUTDOWN_MINUTES in ${sourceLabel}: must be <= ${MAX_TIMER_DELAY_MINUTES.toFixed(2)} minutes to fit within the Node.js timer limit`);
|
|
62
75
|
}
|
|
63
76
|
return parsed;
|
|
64
77
|
}
|
|
@@ -119,6 +132,8 @@ export function resolveProviderCommandConfig(overrides = {}) {
|
|
|
119
132
|
copilotCommand: overrides.copilotCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand,
|
|
120
133
|
copilotArgs: [...(overrides.copilotArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs)],
|
|
121
134
|
copilotSessionTtlMinutes: overrides.copilotSessionTtlMinutes ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
|
|
135
|
+
providerIdleShutdownMinutes: overrides.providerIdleShutdownMinutes
|
|
136
|
+
?? DEFAULT_PROVIDER_COMMAND_CONFIG.providerIdleShutdownMinutes,
|
|
122
137
|
};
|
|
123
138
|
}
|
|
124
139
|
export function loadConfigFromEnv(env = process.env) {
|
|
@@ -135,6 +150,9 @@ export function loadConfigFromEnv(env = process.env) {
|
|
|
135
150
|
copilotSessionTtlMinutes: env.COPILOT_SESSION_TTL_MINUTES && env.COPILOT_SESSION_TTL_MINUTES.trim().length > 0
|
|
136
151
|
? parseCopilotSessionTtlMinutesValue(env.COPILOT_SESSION_TTL_MINUTES, 'environment variable COPILOT_SESSION_TTL_MINUTES')
|
|
137
152
|
: DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
|
|
153
|
+
providerIdleShutdownMinutes: env.PROVIDER_IDLE_SHUTDOWN_MINUTES && env.PROVIDER_IDLE_SHUTDOWN_MINUTES.trim().length > 0
|
|
154
|
+
? parseProviderIdleShutdownMinutesValue(env.PROVIDER_IDLE_SHUTDOWN_MINUTES, 'environment variable PROVIDER_IDLE_SHUTDOWN_MINUTES')
|
|
155
|
+
: DEFAULT_PROVIDER_COMMAND_CONFIG.providerIdleShutdownMinutes,
|
|
138
156
|
});
|
|
139
157
|
assertProviderCommandCompatibility(provider, providerConfig, 'environment variables');
|
|
140
158
|
return {
|
|
@@ -21,7 +21,7 @@ export interface PreparedChannelContext {
|
|
|
21
21
|
directoryPath: string;
|
|
22
22
|
payload: ChannelContextPayload;
|
|
23
23
|
payloadPath?: string;
|
|
24
|
-
|
|
24
|
+
gatewayCredentialPath?: string;
|
|
25
25
|
skillRuntime?: SkillRuntimeBootstrapMetadata;
|
|
26
26
|
localhostGateway?: LocalhostGatewayBootstrapMetadata;
|
|
27
27
|
taskWorkspace?: TaskWorkspaceContext;
|
|
@@ -83,6 +83,7 @@ interface ContextFileSystem {
|
|
|
83
83
|
mode?: number;
|
|
84
84
|
}): Promise<void>;
|
|
85
85
|
unlink(path: string): Promise<void>;
|
|
86
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
86
87
|
access(path: string): Promise<void>;
|
|
87
88
|
}
|
|
88
89
|
interface FileChannelContextStoreOptions {
|
|
@@ -98,8 +99,9 @@ export declare function resolveChannelContextDirectory(stateRootDir: string, cha
|
|
|
98
99
|
export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
|
|
99
100
|
export declare function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir: string): string;
|
|
100
101
|
export declare function resolveTaskWorkspaceDirectory(startupWorkspaceRootDir: string, channelId: string, taskId: string): string;
|
|
101
|
-
export declare function
|
|
102
|
-
export declare function
|
|
102
|
+
export declare function resolveChannelGatewayCredentialPath(stateRootDir: string, channelId: string): string;
|
|
103
|
+
export declare function resolveGatewayCredentialPathFromPayloadPath(payloadPath: string): string;
|
|
104
|
+
export declare function isGatewayCredentialSidecarBasename(basename: string): boolean;
|
|
103
105
|
export declare function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl: string, fileSystem?: Pick<ContextFileSystem, 'access'>): Promise<SkillRuntimeBootstrapMetadata>;
|
|
104
106
|
export declare class FileChannelContextStore implements ChannelContextStore {
|
|
105
107
|
private readonly stateRootDir;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
const CHANNEL_CONTEXT_ROOT_DIRNAME = 'channel-context';
|
|
5
6
|
const CHANNEL_CONTEXT_PAYLOAD_FILENAME = 'context.json';
|
|
6
|
-
const
|
|
7
|
+
const CHANNEL_GATEWAY_CREDENTIAL_FILENAME = '.borgee-agent-gateway.json';
|
|
7
8
|
const TASK_WORKSPACE_ROOT_DIRNAME = '.borgee-task-workspaces';
|
|
8
9
|
export class ChannelContextPreparationError extends Error {
|
|
9
10
|
partialContext;
|
|
@@ -29,6 +30,9 @@ const DEFAULT_FILE_SYSTEM = {
|
|
|
29
30
|
async unlink(path) {
|
|
30
31
|
await fs.unlink(path);
|
|
31
32
|
},
|
|
33
|
+
async rename(oldPath, newPath) {
|
|
34
|
+
await fs.rename(oldPath, newPath);
|
|
35
|
+
},
|
|
32
36
|
async access(path) {
|
|
33
37
|
await fs.access(path);
|
|
34
38
|
},
|
|
@@ -45,7 +49,7 @@ function toSkillRuntimePayload(skillRuntime) {
|
|
|
45
49
|
pythonCliPath: skillRuntime.pythonCliPath,
|
|
46
50
|
};
|
|
47
51
|
}
|
|
48
|
-
function
|
|
52
|
+
function buildBorgeeAgentGatewayCredential(channelId, localhostGateway) {
|
|
49
53
|
if (!localhostGateway) {
|
|
50
54
|
return undefined;
|
|
51
55
|
}
|
|
@@ -53,6 +57,7 @@ function buildLocalhostGatewayAuthPayload(channelId, localhostGateway) {
|
|
|
53
57
|
schemaVersion: 1,
|
|
54
58
|
channelId,
|
|
55
59
|
localhostGateway: {
|
|
60
|
+
baseUrl: localhostGateway.baseUrl,
|
|
56
61
|
token: localhostGateway.token,
|
|
57
62
|
},
|
|
58
63
|
};
|
|
@@ -144,17 +149,28 @@ export function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir) {
|
|
|
144
149
|
export function resolveTaskWorkspaceDirectory(startupWorkspaceRootDir, channelId, taskId) {
|
|
145
150
|
return join(resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir), encodeChannelPathSegment(channelId), encodeChannelPathSegment(taskId));
|
|
146
151
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return join(
|
|
152
|
+
export function resolveChannelGatewayCredentialPath(stateRootDir, channelId) {
|
|
153
|
+
return join(resolveChannelContextDirectory(stateRootDir, channelId), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
154
|
+
}
|
|
155
|
+
export function resolveGatewayCredentialPathFromPayloadPath(payloadPath) {
|
|
156
|
+
return join(dirname(payloadPath), CHANNEL_GATEWAY_CREDENTIAL_FILENAME);
|
|
152
157
|
}
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Every basename a gateway credential can carry in a channel context directory, including the ones
|
|
160
|
+
* this host no longer writes. The directory holds at most one live credential — the token is
|
|
161
|
+
* reissued every turn — and this sweep is the only thing that ever unlinks the others, so a name
|
|
162
|
+
* dropped from this list leaves a 0600 file behind that still looks live.
|
|
163
|
+
*/
|
|
164
|
+
const GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES = [
|
|
165
|
+
'.borgee-agent-gateway.',
|
|
166
|
+
'.borgee-agent-bootstrap.',
|
|
167
|
+
'.localhost-gateway-auth.',
|
|
168
|
+
];
|
|
169
|
+
export function isGatewayCredentialSidecarBasename(basename) {
|
|
170
|
+
return basename.endsWith('.json')
|
|
171
|
+
&& GATEWAY_CREDENTIAL_SIDECAR_BASENAME_PREFIXES.some((prefix) => basename.startsWith(prefix));
|
|
155
172
|
}
|
|
156
|
-
|
|
157
|
-
async function pruneLegacyGatewayAuthSidecars(fileSystem, directoryPath) {
|
|
173
|
+
async function pruneGatewayCredentialSidecars(fileSystem, directoryPath, keepBasename) {
|
|
158
174
|
let entries;
|
|
159
175
|
try {
|
|
160
176
|
entries = await fileSystem.readdir(directoryPath);
|
|
@@ -167,8 +183,7 @@ async function pruneLegacyGatewayAuthSidecars(fileSystem, directoryPath) {
|
|
|
167
183
|
throw error;
|
|
168
184
|
}
|
|
169
185
|
await Promise.all(entries
|
|
170
|
-
.filter((entry) => entry
|
|
171
|
-
&& entry.endsWith('.json'))
|
|
186
|
+
.filter((entry) => isGatewayCredentialSidecarBasename(entry) && entry !== keepBasename)
|
|
172
187
|
.map(async (entry) => {
|
|
173
188
|
await fileSystem.unlink(join(directoryPath, entry)).catch((error) => {
|
|
174
189
|
if (error.code !== 'ENOENT') {
|
|
@@ -196,8 +211,8 @@ export async function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl, fileSystem
|
|
|
196
211
|
const packageRootPath = await resolvePackageRootFromModuleUrl(moduleUrl, fileSystem);
|
|
197
212
|
const skillDirectoryPath = join(packageRootPath, 'skills', BORGEE_AGENT_SKILL_DIRNAME);
|
|
198
213
|
const skillMarkdownPath = join(skillDirectoryPath, 'SKILL.md');
|
|
199
|
-
const nodeCliPath = join(skillDirectoryPath, 'borgee-agent.mjs');
|
|
200
|
-
const pythonCliPath = join(skillDirectoryPath, 'borgee-agent.py');
|
|
214
|
+
const nodeCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.mjs');
|
|
215
|
+
const pythonCliPath = join(skillDirectoryPath, 'scripts', 'borgee-agent.py');
|
|
201
216
|
await Promise.all([
|
|
202
217
|
fileSystem.access(skillDirectoryPath),
|
|
203
218
|
fileSystem.access(skillMarkdownPath),
|
|
@@ -249,7 +264,7 @@ export class FileChannelContextStore {
|
|
|
249
264
|
const auxiliaryCollaborationEnabled = collaborationCommandsEnabled && collaborationRoutesAllowedForTurnMode;
|
|
250
265
|
const directoryPath = resolveChannelContextDirectory(this.stateRootDir, input.channelId);
|
|
251
266
|
const payloadPath = resolveChannelContextPayloadPath(this.stateRootDir, input.channelId);
|
|
252
|
-
const
|
|
267
|
+
const gatewayCredentialPath = resolveGatewayCredentialPathFromPayloadPath(payloadPath);
|
|
253
268
|
const existingTaskAssignmentContext = await readExistingTaskAssignmentContext(payloadPath, this.fileSystem);
|
|
254
269
|
const skillRuntime = this.skillRuntimeEnabled
|
|
255
270
|
? await this.resolveSkillRuntimeBestEffort()
|
|
@@ -292,15 +307,15 @@ export class FileChannelContextStore {
|
|
|
292
307
|
...(taskWorkspace ? { taskWorkspace } : {}),
|
|
293
308
|
}
|
|
294
309
|
: undefined);
|
|
295
|
-
const
|
|
310
|
+
const gatewayCredential = buildBorgeeAgentGatewayCredential(input.channelId, issuedLocalhostGateway);
|
|
296
311
|
let taskWorkspaceMaterialized = false;
|
|
297
312
|
let payloadWritten = false;
|
|
298
|
-
let
|
|
313
|
+
let gatewayCredentialWritten = false;
|
|
299
314
|
const preparedContext = {
|
|
300
315
|
directoryPath,
|
|
301
316
|
payload,
|
|
302
317
|
payloadPath,
|
|
303
|
-
|
|
318
|
+
gatewayCredentialPath: gatewayCredential ? gatewayCredentialPath : undefined,
|
|
304
319
|
skillRuntime,
|
|
305
320
|
localhostGateway,
|
|
306
321
|
taskWorkspace,
|
|
@@ -311,19 +326,20 @@ export class FileChannelContextStore {
|
|
|
311
326
|
await this.fileSystem.mkdir(taskWorkspace.rootPath, { recursive: true, mode: 0o700 });
|
|
312
327
|
taskWorkspaceMaterialized = true;
|
|
313
328
|
}
|
|
314
|
-
await
|
|
329
|
+
await pruneGatewayCredentialSidecars(this.fileSystem, directoryPath, gatewayCredential ? CHANNEL_GATEWAY_CREDENTIAL_FILENAME : undefined);
|
|
315
330
|
await this.fileSystem.writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
316
331
|
payloadWritten = true;
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
if (gatewayCredential) {
|
|
333
|
+
// Replaced atomically: a CLI invocation left over from an earlier turn must read either
|
|
334
|
+
// the old credential or the new one, never a missing or half-written file, because the
|
|
335
|
+
// documented correction for a rotated token is a 401 and not a usage error. The staging
|
|
336
|
+
// name is itself a credential sidecar, so a crashed run's leftover is swept above.
|
|
337
|
+
const stagingCredentialPath = join(directoryPath, `.borgee-agent-gateway.${randomUUID()}.json`);
|
|
338
|
+
await this.fileSystem.writeFile(stagingCredentialPath, `${JSON.stringify(gatewayCredential, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
339
|
+
await this.fileSystem.rename(stagingCredentialPath, gatewayCredentialPath);
|
|
340
|
+
gatewayCredentialWritten = true;
|
|
320
341
|
}
|
|
321
342
|
else {
|
|
322
|
-
await this.fileSystem.unlink(gatewayAuthPath).catch((error) => {
|
|
323
|
-
if (error.code !== 'ENOENT') {
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
343
|
this.localhostGateway?.clearChannel(input.channelId);
|
|
328
344
|
}
|
|
329
345
|
if (issuedLocalhostGateway) {
|
|
@@ -336,7 +352,7 @@ export class FileChannelContextStore {
|
|
|
336
352
|
directoryPath,
|
|
337
353
|
payload,
|
|
338
354
|
payloadPath: payloadWritten ? payloadPath : undefined,
|
|
339
|
-
|
|
355
|
+
gatewayCredentialPath: gatewayCredentialWritten ? gatewayCredentialPath : undefined,
|
|
340
356
|
skillRuntime: payloadWritten ? skillRuntime : undefined,
|
|
341
357
|
localhostGateway: payloadWritten ? localhostGateway : undefined,
|
|
342
358
|
taskWorkspace: taskWorkspaceMaterialized ? taskWorkspace : undefined,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MAIN_SESSION_DELEGATION_LINES: readonly string[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const MAIN_SESSION_DELEGATION_LINES = [
|
|
2
|
+
'This session is your coordination surface: read the channel, answer the human, break work down, and keep task state current here.',
|
|
3
|
+
'Delegate the actual work to subagents — research, code edits, long builds and test runs, and broad repository sweeps belong in a delegated worker instead of running inline here.',
|
|
4
|
+
'Delegation changes who executes, not who owns: the work still belongs to this thread, and you still report the result here yourself.',
|
|
5
|
+
'Stay responsive while delegated work runs, so a new incoming message never waits behind a long local operation.',
|
|
6
|
+
];
|
package/dist/context/prompt.js
CHANGED
|
@@ -2,6 +2,8 @@ import { AWAITING_USER_CONTROL_PREFIX } from '../providers/awaiting-user.js';
|
|
|
2
2
|
import { buildAttentionSummaryLines } from './attention.js';
|
|
3
3
|
import { buildCollaborationCapabilityDeclarationSummaryLines, buildMissedCollaborationDiagnosticSummaryLines, } from './collaboration-capabilities-diagnostics.js';
|
|
4
4
|
import { buildCollaborationOutcomeSummaryLines } from './collaboration-outcome.js';
|
|
5
|
+
import { MAIN_SESSION_DELEGATION_LINES } from './main-session-delegation.js';
|
|
6
|
+
import { buildSkillManualLines } from './skill-manual.js';
|
|
5
7
|
import { buildTaskThreadCollaborationSummaryLines } from './task-thread-collaboration.js';
|
|
6
8
|
function providerLabel(provider) {
|
|
7
9
|
if (provider === 'copilot') {
|
|
@@ -12,50 +14,39 @@ function providerLabel(provider) {
|
|
|
12
14
|
}
|
|
13
15
|
return 'Claude';
|
|
14
16
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
// Claude and Codex name the manual once on their session-scoped surfaces. Copilot has no
|
|
18
|
+
// session-scoped surface — this prompt is its only model-facing text — so for Copilot alone the
|
|
19
|
+
// pointer rides every turn.
|
|
20
|
+
function buildSkillManualPromptLines(provider, context) {
|
|
21
|
+
if (provider !== 'copilot' || !context?.skillRuntime || !context.gatewayCredentialPath) {
|
|
17
22
|
return [];
|
|
18
23
|
}
|
|
19
|
-
return [
|
|
20
|
-
'',
|
|
21
|
-
'Read-only local skill runtime bootstrap is available for this turn.',
|
|
22
|
-
`Channel context payload: ${context.channelContextPayloadPath}`,
|
|
23
|
-
`Skill guide: ${context.skillRuntime.skillMarkdownPath}`,
|
|
24
|
-
`Node CLI: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
|
|
25
|
-
`Python CLI: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
|
|
26
|
-
'These CLIs are local-only, may only access the loopback gateway described below, must not mutate files or the local environment, and may only send auxiliary collaboration messages when that gateway surface is explicitly advertised below.',
|
|
27
|
-
];
|
|
24
|
+
return ['', ...buildSkillManualLines(context.skillRuntime)];
|
|
28
25
|
}
|
|
26
|
+
// Only the facts that change from turn to turn: which credential file authorizes this turn, which
|
|
27
|
+
// task grammar the thread shape licenses, and whether the collaboration commands can be reached at
|
|
28
|
+
// all. Everything else a model needs to invoke the CLI is in the manual, which does not change.
|
|
29
29
|
function buildLocalhostGatewayPromptLines(context) {
|
|
30
30
|
if (!context?.localhostGateway) {
|
|
31
31
|
return [];
|
|
32
32
|
}
|
|
33
|
-
if (!context.
|
|
33
|
+
if (!context.gatewayCredentialPath) {
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
36
|
-
const lines = [
|
|
37
|
-
'',
|
|
38
|
-
'A loopback-only localhost gateway is available for this turn.',
|
|
39
|
-
'Use the packaged local CLI with the existing --context payload to access the documented gateway surface.',
|
|
40
|
-
`Gateway auth sidecar for this turn: ${context.gatewayAuthPath}`,
|
|
41
|
-
];
|
|
42
|
-
const isTaskAssignmentThread = context.taskAssignmentContext?.active === true;
|
|
36
|
+
const lines = ['', `Gateway credential file for this turn: ${context.gatewayCredentialPath}`];
|
|
43
37
|
if (!context.skillRuntime) {
|
|
44
38
|
return lines;
|
|
45
39
|
}
|
|
46
|
-
lines.push(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
lines.push(context.taskAssignmentContext?.active === true
|
|
41
|
+
? 'This turn runs inside a task assignment thread.'
|
|
42
|
+
: 'This turn runs in a parent channel, not a task thread.');
|
|
43
|
+
const collaborationLive = context.localhostGateway.collaboration?.enabled === true
|
|
44
|
+
&& (context.collaborationTurnMode ?? 'ordinary') === 'ordinary';
|
|
45
|
+
if (!collaborationLive) {
|
|
46
|
+
lines.push('Collaboration commands are not available this turn.');
|
|
52
47
|
}
|
|
53
|
-
if (context.
|
|
54
|
-
(context.
|
|
55
|
-
const turnExecutionArgument = context.collaborationTurnExecutionId
|
|
56
|
-
? ` --turn-execution-id ${context.collaborationTurnExecutionId}`
|
|
57
|
-
: '';
|
|
58
|
-
lines.push('Auxiliary collaboration commands are available for this turn. Use them only for short targeted escalation, mention, or reply notices, never for the main final answer body.', 'When you need the host-private in-flight draft for this live collaboration turn, use the dedicated read-draft command rather than inspecting public channel messages.', 'When --list-users is listed below, it is a read-only command for this turn and may be executed directly without asking for permission.', 'When the user explicitly asks you to mention, ping, notify, or send a short note to another visible participant, use the dedicated send-mention command directly and include a visible <@targetId> token in the body.', 'If the user asks you to @ another agent now, send the short auxiliary mention first, then confirm what you sent.', 'If the user says "the other agent" and only one other visible agent is present, resolve that target from --list-users and then use --send-mention with that participant id.', 'If collaboration with another visible agent would help, use these auxiliary commands to send a short targeted mention or reply-thread note yourself rather than asking AgentsHost to orchestrate a special protocol.', `Node private draft snapshot: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --read-draft`, `Node user directory: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-users`, `Node auxiliary mention: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-mention user-id --body "Need review from <@user-id>"`, `Node reply thread notice: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-message --body "Following up here" --reply-to message-id`, `Python private draft snapshot: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --read-draft`, `Python user directory: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath} --list-users`, `Python auxiliary mention: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-mention user-id --body "Need review from <@user-id>"`, `Python reply thread notice: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --auth-path ${context.gatewayAuthPath}${turnExecutionArgument} --send-message --body "Following up here" --reply-to message-id`);
|
|
48
|
+
else if (context.collaborationTurnExecutionId) {
|
|
49
|
+
lines.push(`Pass --turn-execution-id ${context.collaborationTurnExecutionId} on send, mention and draft.`);
|
|
59
50
|
}
|
|
60
51
|
return lines;
|
|
61
52
|
}
|
|
@@ -76,7 +67,6 @@ function buildTaskAssignmentPromptLines(params) {
|
|
|
76
67
|
];
|
|
77
68
|
if (taskAssignmentContext?.currentTaskId) {
|
|
78
69
|
lines.push(`Current assigned task id: ${taskAssignmentContext.currentTaskId}.`);
|
|
79
|
-
lines.push('Inside this task thread, the packaged Node and Python CLIs may omit --task-id for --get-task and --update-task because the current task id is already persisted in the injected thread context.');
|
|
80
70
|
if (params.promptContext?.taskWorkspace) {
|
|
81
71
|
lines.push(`Task-scoped writable workspace root: ${params.promptContext.taskWorkspace.rootPath}.`);
|
|
82
72
|
lines.push('This writable workspace is local to agents-host for the current task thread. It does not imply that the target repository has already been checked out there.');
|
|
@@ -84,9 +74,8 @@ function buildTaskAssignmentPromptLines(params) {
|
|
|
84
74
|
}
|
|
85
75
|
else if (taskAssignmentContext?.active === true) {
|
|
86
76
|
lines.push('No current task id is persisted in the injected thread context for this task thread.');
|
|
87
|
-
lines.push('Inside this task thread, shorthand --get-task/--update-task calls without --task-id use an agents-host local fallback that scans visible parent-channel tasks for this thread; pass --task-id explicitly if that fallback cannot resolve a unique task.');
|
|
88
77
|
}
|
|
89
|
-
lines.push('
|
|
78
|
+
lines.push('Return your normal final response in this thread, and do not move the main work back to the parent channel.');
|
|
90
79
|
return lines;
|
|
91
80
|
}
|
|
92
81
|
function buildInboundMetadataLines(params) {
|
|
@@ -221,6 +210,7 @@ export function buildPrompt(params) {
|
|
|
221
210
|
'Be concise, helpful, and honest about uncertainty.',
|
|
222
211
|
'Do not claim to have performed actions you did not actually perform.',
|
|
223
212
|
'Keep the visible reply text concise.',
|
|
213
|
+
...MAIN_SESSION_DELEGATION_LINES,
|
|
224
214
|
...buildTurnControlPromptLines(params.promptContext),
|
|
225
215
|
`If the user asks who you are, what powers you, or which backend/provider you use, mention that you are currently running on ${providerLabel(params.provider)}.`,
|
|
226
216
|
`Channel: ${params.channelId}`,
|
|
@@ -245,7 +235,7 @@ export function buildPrompt(params) {
|
|
|
245
235
|
const lines = buildMissedCollaborationDiagnosticSummaryLines(params.promptContext?.missedCollaborationDiagnostic);
|
|
246
236
|
return lines.length > 0 ? ['', ...lines] : [];
|
|
247
237
|
})(),
|
|
248
|
-
...
|
|
238
|
+
...buildSkillManualPromptLines(params.provider, params.promptContext),
|
|
249
239
|
...buildLocalhostGatewayPromptLines(params.promptContext),
|
|
250
240
|
'',
|
|
251
241
|
`New message from ${params.incomingAuthorId}:`,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SkillRuntimeBootstrapMetadata } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What every model-facing surface says about the packaged CLI: what it is, where its manual is, and
|
|
4
|
+
* that the manual has to be opened before acting. Nothing else about the CLI is stated on a host
|
|
5
|
+
* surface — the manual is self-contained, so the invocation grammar, the command inventory, the
|
|
6
|
+
* limits and the credential-file rules have one home that cannot drift from the implementations.
|
|
7
|
+
*
|
|
8
|
+
* The authorization sentence is written against the credential file rather than against "this turn"
|
|
9
|
+
* because the surfaces disagree about scope: the per-turn prompt is turn-scoped while Claude's
|
|
10
|
+
* session append and Codex's projected document are session-scoped, and only the per-turn prompt
|
|
11
|
+
* knows whether a credential file exists.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildSkillManualLines(skillRuntime: SkillRuntimeBootstrapMetadata): string[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every model-facing surface says about the packaged CLI: what it is, where its manual is, and
|
|
3
|
+
* that the manual has to be opened before acting. Nothing else about the CLI is stated on a host
|
|
4
|
+
* surface — the manual is self-contained, so the invocation grammar, the command inventory, the
|
|
5
|
+
* limits and the credential-file rules have one home that cannot drift from the implementations.
|
|
6
|
+
*
|
|
7
|
+
* The authorization sentence is written against the credential file rather than against "this turn"
|
|
8
|
+
* because the surfaces disagree about scope: the per-turn prompt is turn-scoped while Claude's
|
|
9
|
+
* session append and Codex's projected document are session-scoped, and only the per-turn prompt
|
|
10
|
+
* knows whether a credential file exists.
|
|
11
|
+
*/
|
|
12
|
+
export function buildSkillManualLines(skillRuntime) {
|
|
13
|
+
return [
|
|
14
|
+
"A packaged local CLI reads this Borgee channel and acts on its tasks: channel history, visible participants, this channel's tasks and their properties, and short auxiliary mentions.",
|
|
15
|
+
`Read its manual at ${skillRuntime.skillMarkdownPath} before you act on this channel; you cannot form a working invocation without it.`,
|
|
16
|
+
'Every command the manual documents is already authorized on a turn whose prompt names a gateway credential file. Run them directly and do not ask the user for permission first.',
|
|
17
|
+
];
|
|
18
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HostLogger, summarizeError } from '../debug.js';
|
|
2
|
+
import { buildHostedIncomingContentText, buildHostedTurnContentParts } from '../hosted-turn-content.js';
|
|
2
3
|
import { ChannelContextPreparationError, } from './injection.js';
|
|
3
4
|
import { buildPrompt } from './prompt.js';
|
|
4
5
|
function providerLabel(provider) {
|
|
@@ -24,7 +25,7 @@ function toPromptContext(channelContext, input) {
|
|
|
24
25
|
...(channelContext
|
|
25
26
|
? {
|
|
26
27
|
channelContextPayloadPath: channelContext.payloadPath,
|
|
27
|
-
|
|
28
|
+
gatewayCredentialPath: channelContext.gatewayCredentialPath,
|
|
28
29
|
skillRuntime: channelContext.skillRuntime,
|
|
29
30
|
localhostGateway: channelContext.localhostGateway,
|
|
30
31
|
taskAssignmentContext: channelContext.payload.taskAssignmentContext,
|
|
@@ -75,6 +76,12 @@ export class ProviderTurnPreparer {
|
|
|
75
76
|
this.logger = logger;
|
|
76
77
|
}
|
|
77
78
|
async prepare(input) {
|
|
79
|
+
const incomingParts = input.incomingParts ?? buildHostedTurnContentParts({
|
|
80
|
+
text: input.incomingContent,
|
|
81
|
+
});
|
|
82
|
+
const incomingContent = incomingParts.length > 0
|
|
83
|
+
? buildHostedIncomingContentText(incomingParts)
|
|
84
|
+
: input.incomingContent;
|
|
78
85
|
let channelContext;
|
|
79
86
|
if (this.channelContextStore) {
|
|
80
87
|
try {
|
|
@@ -97,8 +104,8 @@ export class ProviderTurnPreparer {
|
|
|
97
104
|
...(input.incomingMessageType !== undefined
|
|
98
105
|
? { incomingMessageType: input.incomingMessageType }
|
|
99
106
|
: {}),
|
|
100
|
-
...(input.incomingMessageType !== undefined &&
|
|
101
|
-
? { incomingContent
|
|
107
|
+
...(input.incomingMessageType !== undefined && incomingContent !== undefined
|
|
108
|
+
? { incomingContent }
|
|
102
109
|
: {}),
|
|
103
110
|
};
|
|
104
111
|
channelContext = await this.channelContextStore.prepare(prepareInput);
|
|
@@ -116,6 +123,8 @@ export class ProviderTurnPreparer {
|
|
|
116
123
|
const promptContext = toPromptContext(channelContext, input);
|
|
117
124
|
return {
|
|
118
125
|
channelId: input.channelId,
|
|
126
|
+
incomingContent,
|
|
127
|
+
incomingParts,
|
|
119
128
|
incomingEventKind: input.incomingEventKind,
|
|
120
129
|
incomingMessageType: input.incomingMessageType,
|
|
121
130
|
prompt: buildPrompt({
|
|
@@ -123,7 +132,7 @@ export class ProviderTurnPreparer {
|
|
|
123
132
|
provider: input.provider,
|
|
124
133
|
channelId: input.channelId,
|
|
125
134
|
incomingAuthorId: input.incomingAuthorId,
|
|
126
|
-
incomingContent
|
|
135
|
+
incomingContent,
|
|
127
136
|
incomingEventKind: input.incomingEventKind,
|
|
128
137
|
incomingMessageType: input.incomingMessageType,
|
|
129
138
|
promptContext,
|