@borgee/agents-host 0.2.1 → 0.2.26

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.
Files changed (78) hide show
  1. package/README.md +184 -21
  2. package/dist/agents-host-supervisor.d.ts +7 -5
  3. package/dist/agents-host-supervisor.js +24 -4
  4. package/dist/agents-host.d.ts +89 -15
  5. package/dist/agents-host.js +2099 -142
  6. package/dist/chat/chat-control-plane.d.ts +14 -3
  7. package/dist/chat/sdk-chat-control-plane.d.ts +16 -4
  8. package/dist/chat/sdk-chat-control-plane.js +69 -9
  9. package/dist/cli-args.d.ts +46 -5
  10. package/dist/cli-args.js +313 -32
  11. package/dist/cli.d.ts +9 -0
  12. package/dist/cli.js +112 -5
  13. package/dist/compatibility-gates.d.ts +35 -0
  14. package/dist/compatibility-gates.js +127 -0
  15. package/dist/config.d.ts +1 -0
  16. package/dist/config.js +23 -5
  17. package/dist/connections-state-store.d.ts +81 -0
  18. package/dist/connections-state-store.js +228 -0
  19. package/dist/context/injection.d.ts +109 -0
  20. package/dist/context/injection.js +350 -0
  21. package/dist/context/prompt.d.ts +4 -1
  22. package/dist/context/prompt.js +170 -1
  23. package/dist/context/turn-preparation.d.ts +9 -0
  24. package/dist/context/turn-preparation.js +106 -0
  25. package/dist/debug.d.ts +44 -0
  26. package/dist/debug.js +135 -0
  27. package/dist/gateway/localhost-gateway.d.ts +52 -0
  28. package/dist/gateway/localhost-gateway.js +857 -0
  29. package/dist/index.js +7 -5
  30. package/dist/local-config.d.ts +4 -1
  31. package/dist/local-config.js +24 -7
  32. package/dist/managed-daemon-log.d.ts +34 -0
  33. package/dist/managed-daemon-log.js +261 -0
  34. package/dist/managed-daemon.d.ts +220 -0
  35. package/dist/managed-daemon.js +1601 -0
  36. package/dist/policy/authorization-audit.d.ts +63 -0
  37. package/dist/policy/authorization-audit.js +94 -0
  38. package/dist/policy/copilot-permission.d.ts +15 -0
  39. package/dist/policy/copilot-permission.js +193 -0
  40. package/dist/policy/gateway-authorization.d.ts +42 -0
  41. package/dist/policy/gateway-authorization.js +162 -0
  42. package/dist/providers/awaiting-user.d.ts +12 -0
  43. package/dist/providers/awaiting-user.js +151 -0
  44. package/dist/providers/claude/adapter.d.ts +3 -1
  45. package/dist/providers/claude/adapter.js +8 -12
  46. package/dist/providers/claude/cli-client.d.ts +12 -5
  47. package/dist/providers/claude/cli-client.js +184 -37
  48. package/dist/providers/claude/session-store.d.ts +24 -0
  49. package/dist/providers/claude/session-store.js +65 -12
  50. package/dist/providers/codex/adapter.d.ts +11 -0
  51. package/dist/providers/codex/adapter.js +19 -0
  52. package/dist/providers/codex/cli-client.d.ts +103 -0
  53. package/dist/providers/codex/cli-client.js +1133 -0
  54. package/dist/providers/codex/project-doc.d.ts +3 -0
  55. package/dist/providers/codex/project-doc.js +66 -0
  56. package/dist/providers/codex/session-store.d.ts +38 -0
  57. package/dist/providers/codex/session-store.js +150 -0
  58. package/dist/providers/copilot/adapter.d.ts +3 -1
  59. package/dist/providers/copilot/adapter.js +8 -12
  60. package/dist/providers/copilot/cli-client.d.ts +20 -2
  61. package/dist/providers/copilot/cli-client.js +251 -71
  62. package/dist/providers/copilot/session-store.d.ts +24 -0
  63. package/dist/providers/copilot/session-store.js +65 -12
  64. package/dist/providers/create-provider.d.ts +11 -2
  65. package/dist/providers/create-provider.js +131 -12
  66. package/dist/run.d.ts +1 -0
  67. package/dist/run.js +5 -2
  68. package/dist/state-paths.d.ts +13 -1
  69. package/dist/state-paths.js +84 -3
  70. package/dist/task-thread-resolution.d.ts +10 -0
  71. package/dist/task-thread-resolution.js +48 -0
  72. package/dist/types.d.ts +174 -1
  73. package/dist/visible-mentions.d.ts +3 -0
  74. package/dist/visible-mentions.js +15 -0
  75. package/package.json +19 -17
  76. package/skills/borgee-agent/SKILL.md +33 -0
  77. package/skills/borgee-agent/borgee-agent.mjs +473 -0
  78. package/skills/borgee-agent/borgee-agent.py +409 -0
@@ -0,0 +1,127 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE = 'claude-provider-v2';
3
+ export const COPILOT_PROVIDER_V2_COMPATIBILITY_GATE = 'copilot-provider-v2';
4
+ export const CODEX_PROVIDER_COMPATIBILITY_GATE = 'codex-provider';
5
+ export const CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE = 'connections-state-layer';
6
+ export const CONTEXT_INJECTION_COMPATIBILITY_GATE = 'context-injection';
7
+ export const SKILL_RUNTIME_COMPATIBILITY_GATE = 'skill-runtime';
8
+ export const LOCALHOST_GATEWAY_COMPATIBILITY_GATE = 'localhost-gateway';
9
+ export const COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE = 'collaboration-skill-first';
10
+ export const TOKEN_BINDING_COMPATIBILITY_GATE = 'token-binding';
11
+ export const POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE = 'policy-audit-enforcement';
12
+ export const MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE = 'managed-runtime-convergence';
13
+ export const COMPATIBILITY_GATES_ENV = 'AGENTS_HOST_INTERNAL_COMPATIBILITY_GATES';
14
+ export const INTERNAL_POLICY_MODE_ENV = 'AGENTS_HOST_INTERNAL_POLICY_MODE';
15
+ export const INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV = 'AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS';
16
+ export const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
17
+ const VALID_PROVIDER_IMPLEMENTATION_ENTRIES = '"claude:v1", "claude:v2", "copilot:v1", or "copilot:v2"';
18
+ const PROVIDER_IMPLEMENTATION_COMPATIBILITY_GATES = {
19
+ claude: CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE,
20
+ copilot: COPILOT_PROVIDER_V2_COMPATIBILITY_GATE,
21
+ };
22
+ function sortUniqueStrings(values) {
23
+ return [...new Set([...values].map((value) => value.trim()).filter((value) => value.length > 0))]
24
+ .sort((left, right) => left.localeCompare(right));
25
+ }
26
+ function isInternalProviderImplementationPath(value) {
27
+ return value === 'v1' || value === 'v2';
28
+ }
29
+ function isInternalProviderImplementationKey(value) {
30
+ return value === 'claude' || value === 'copilot';
31
+ }
32
+ function createInvalidProviderImplementationEntryError(assignment) {
33
+ return new Error(`Invalid ${INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV} entry "${assignment}". Expected ${VALID_PROVIDER_IMPLEMENTATION_ENTRIES}.`);
34
+ }
35
+ export function parseCompatibilityGates(rawValue) {
36
+ if (!rawValue || rawValue.trim().length === 0) {
37
+ return new Set();
38
+ }
39
+ const gates = new Set();
40
+ for (const gateRaw of rawValue.split(',')) {
41
+ const gate = gateRaw.trim();
42
+ if (gate.length === 0) {
43
+ continue;
44
+ }
45
+ gates.add(gate);
46
+ }
47
+ return gates;
48
+ }
49
+ export function resolveInternalCompatibilityGates() {
50
+ return parseCompatibilityGates(process.env[COMPATIBILITY_GATES_ENV]);
51
+ }
52
+ export function normalizeInternalProviderImplementationOverrides(rawValue) {
53
+ if (!rawValue || rawValue.trim().length === 0) {
54
+ return [];
55
+ }
56
+ const implementationsByProvider = new Map();
57
+ for (const assignmentRaw of rawValue.split(',')) {
58
+ const assignment = assignmentRaw.trim();
59
+ if (assignment.length === 0) {
60
+ continue;
61
+ }
62
+ const [providerRaw, implementationRaw, ...extraParts] = assignment.split(':');
63
+ const provider = providerRaw?.trim();
64
+ const implementation = implementationRaw?.trim();
65
+ if (extraParts.length > 0 || !provider || !implementation) {
66
+ throw createInvalidProviderImplementationEntryError(assignment);
67
+ }
68
+ if (!isInternalProviderImplementationKey(provider)) {
69
+ throw createInvalidProviderImplementationEntryError(assignment);
70
+ }
71
+ if (!isInternalProviderImplementationPath(implementation)) {
72
+ throw createInvalidProviderImplementationEntryError(assignment);
73
+ }
74
+ implementationsByProvider.set(provider, implementation);
75
+ }
76
+ return sortUniqueStrings([...implementationsByProvider.entries()].map(([provider, implementation]) => `${provider}:${implementation}`));
77
+ }
78
+ export function parseInternalProviderImplementationOverrides(rawValue) {
79
+ const implementationOverrides = {};
80
+ for (const assignment of normalizeInternalProviderImplementationOverrides(rawValue)) {
81
+ const [provider, implementation] = assignment.split(':');
82
+ implementationOverrides[provider] = implementation;
83
+ }
84
+ return implementationOverrides;
85
+ }
86
+ export function resolveManagedRuntimeProviderImplementationOverrides(env = process.env, compatibilityGates = resolveManagedRuntimeCompatibilityGates(env)) {
87
+ const enabledGates = new Set(compatibilityGates);
88
+ return normalizeInternalProviderImplementationOverrides(env[INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV]).filter((assignment) => {
89
+ const [provider] = assignment.split(':');
90
+ return enabledGates.has(PROVIDER_IMPLEMENTATION_COMPATIBILITY_GATES[provider]);
91
+ });
92
+ }
93
+ export function resolveManagedRuntimeCompatibilityGates(env = process.env) {
94
+ const compatibilityGates = new Set(parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV]));
95
+ if (compatibilityGates.has(MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE)) {
96
+ compatibilityGates.add(CONNECTIONS_STATE_LAYER_COMPATIBILITY_GATE);
97
+ compatibilityGates.add(TOKEN_BINDING_COMPATIBILITY_GATE);
98
+ }
99
+ return sortUniqueStrings(compatibilityGates);
100
+ }
101
+ export function resolveInternalPolicyMode(gateEnabled, env = process.env) {
102
+ if (!gateEnabled) {
103
+ return 'audit-only';
104
+ }
105
+ const rawValue = env[INTERNAL_POLICY_MODE_ENV]?.trim();
106
+ if (rawValue === 'enforce') {
107
+ return 'enforce';
108
+ }
109
+ return 'audit-only';
110
+ }
111
+ export function resolveManagedRuntimeSettingsSnapshot(env = process.env) {
112
+ const compatibilityGates = resolveManagedRuntimeCompatibilityGates(env);
113
+ return {
114
+ schemaVersion: MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION,
115
+ compatibilityGates,
116
+ providerImplementationOverrides: resolveManagedRuntimeProviderImplementationOverrides(env, compatibilityGates),
117
+ internalPolicyMode: resolveInternalPolicyMode(compatibilityGates.includes(POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE), env),
118
+ };
119
+ }
120
+ export function serializeManagedRuntimeSettingsSnapshot(snapshot) {
121
+ return JSON.stringify(snapshot);
122
+ }
123
+ export function createManagedRuntimeSettingsFingerprint(snapshot) {
124
+ return createHash('sha256')
125
+ .update(serializeManagedRuntimeSettingsSnapshot(snapshot))
126
+ .digest('hex');
127
+ }
package/dist/config.d.ts CHANGED
@@ -8,5 +8,6 @@ export declare function parseArgs(value: string): string[];
8
8
  export declare function optionalStringArray(value: unknown, fieldName: string, sourceLabel: string): string[] | undefined;
9
9
  export declare function parseCopilotSessionTtlMinutesValue(value: unknown, sourceLabel: string): number;
10
10
  export declare function resolveProvider(rawValue: string, sourceLabel: string): ProviderKind;
11
+ export declare function assertProviderCompatibility(provider: ProviderKind, sourceLabel: string, env?: NodeJS.ProcessEnv): void;
11
12
  export declare function resolveProviderCommandConfig(overrides?: Partial<ProviderCommandConfig>): ProviderCommandConfig;
12
13
  export declare function loadConfigFromEnv(env?: NodeJS.ProcessEnv): AgentsHostConfig;
package/dist/config.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { resolveSingleAgentStateRoot } from './state-paths.js';
2
+ import { CODEX_PROVIDER_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, parseCompatibilityGates, } from './compatibility-gates.js';
2
3
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
3
4
  export const MAX_COPILOT_SESSION_TTL_MINUTES = MAX_TIMER_DELAY_MS / 60_000;
4
5
  export const DEFAULT_COPILOT_SESSION_TTL_MINUTES = 2 * 24 * 60;
5
6
  export const DEFAULT_PROVIDER_COMMAND_CONFIG = {
6
7
  claudeCommand: 'claude',
7
- claudeArgs: ['--print'],
8
+ claudeArgs: ['--print', '--permission-mode', 'bypassPermissions'],
9
+ codexCommand: 'codex-acp',
10
+ codexArgs: [],
8
11
  copilotCommand: 'copilot',
9
12
  copilotArgs: ['-s', '--no-color', '--allow-all-tools', '--output-format', 'text'],
10
13
  copilotSessionTtlMinutes: DEFAULT_COPILOT_SESSION_TTL_MINUTES,
@@ -57,15 +60,26 @@ export function parseCopilotSessionTtlMinutesValue(value, sourceLabel) {
57
60
  }
58
61
  export function resolveProvider(rawValue, sourceLabel) {
59
62
  const raw = rawValue.trim().toLowerCase();
60
- if (raw === 'claude' || raw === 'copilot') {
63
+ if (raw === 'claude' || raw === 'codex' || raw === 'copilot') {
61
64
  return raw;
62
65
  }
63
- throw new Error(`${sourceLabel}: expected "claude" or "copilot", got "${rawValue}"`);
66
+ throw new Error(`${sourceLabel}: expected "claude", "codex", or "copilot", got "${rawValue}"`);
67
+ }
68
+ export function assertProviderCompatibility(provider, sourceLabel, env = process.env) {
69
+ if (provider !== 'codex') {
70
+ return;
71
+ }
72
+ const compatibilityGates = parseCompatibilityGates(env[COMPATIBILITY_GATES_ENV]);
73
+ if (!compatibilityGates.has(CODEX_PROVIDER_COMPATIBILITY_GATE)) {
74
+ throw new Error(`${sourceLabel}: provider "codex" requires ${COMPATIBILITY_GATES_ENV} to include ${CODEX_PROVIDER_COMPATIBILITY_GATE}`);
75
+ }
64
76
  }
65
77
  export function resolveProviderCommandConfig(overrides = {}) {
66
78
  return {
67
79
  claudeCommand: overrides.claudeCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand,
68
80
  claudeArgs: [...(overrides.claudeArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs)],
81
+ codexCommand: overrides.codexCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.codexCommand,
82
+ codexArgs: [...(overrides.codexArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.codexArgs)],
69
83
  copilotCommand: overrides.copilotCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand,
70
84
  copilotArgs: [...(overrides.copilotArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs)],
71
85
  copilotSessionTtlMinutes: overrides.copilotSessionTtlMinutes ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
@@ -73,12 +87,16 @@ export function resolveProviderCommandConfig(overrides = {}) {
73
87
  }
74
88
  export function loadConfigFromEnv(env = process.env) {
75
89
  const provider = resolveProvider(envOr('RUNTIME_PROVIDER', 'claude', env), 'Unsupported RUNTIME_PROVIDER');
90
+ assertProviderCompatibility(provider, 'Unsupported RUNTIME_PROVIDER', env);
91
+ const agentApiKey = requireEnv('BORGEE_AGENT_API_KEY', env);
76
92
  return {
77
93
  borgeeBaseUrl: requireEnv('BORGEE_BASE_URL', env),
78
- stateRootDir: resolveSingleAgentStateRoot(env),
94
+ stateRootDir: resolveSingleAgentStateRoot(env, undefined, agentApiKey),
79
95
  ...resolveProviderCommandConfig({
80
96
  claudeCommand: envOr('CLAUDE_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand, env),
81
97
  claudeArgs: parseArgs(envOr('CLAUDE_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs.join(' '), env)),
98
+ codexCommand: envOr('CODEX_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.codexCommand, env),
99
+ codexArgs: parseArgs(envOr('CODEX_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.codexArgs.join(' '), env)),
82
100
  copilotCommand: envOr('COPILOT_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand, env),
83
101
  copilotArgs: parseArgs(envOr('COPILOT_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs.join(' '), env)),
84
102
  copilotSessionTtlMinutes: env.COPILOT_SESSION_TTL_MINUTES && env.COPILOT_SESSION_TTL_MINUTES.trim().length > 0
@@ -86,7 +104,7 @@ export function loadConfigFromEnv(env = process.env) {
86
104
  : DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
87
105
  }),
88
106
  agent: {
89
- agentApiKey: requireEnv('BORGEE_AGENT_API_KEY', env),
107
+ agentApiKey,
90
108
  agentName: envOr('BORGEE_AGENT_NAME', 'Assistant', env),
91
109
  provider,
92
110
  },
@@ -0,0 +1,81 @@
1
+ import { type DebugLogger } from './debug.js';
2
+ import type { ProviderKind } from './types.js';
3
+ export interface ChannelSessionStoreLike {
4
+ load(agentId: string): Promise<Record<string, string>>;
5
+ save(agentId: string, sessions: Record<string, string>): Promise<void>;
6
+ close?(): Promise<void> | void;
7
+ }
8
+ export interface ChannelTokenBinding {
9
+ token: string;
10
+ agentId: string;
11
+ channelId: string;
12
+ }
13
+ export interface ConnectionsStateStore {
14
+ loadProviderSessions(provider: ProviderKind, agentId: string): Record<string, string>;
15
+ saveProviderSessions(provider: ProviderKind, agentId: string, sessions: Record<string, string>): void;
16
+ hasTokenBinding(token: string): boolean;
17
+ loadTokenBinding(token: string): ChannelTokenBinding | null;
18
+ loadTokenBindingForChannel(agentId: string, channelId: string): ChannelTokenBinding | null;
19
+ replaceTokenBinding(binding: ChannelTokenBinding): void;
20
+ deleteTokenBinding(token: string): void;
21
+ revokeTokenBinding(token: string): void;
22
+ revokeAllTokenBindings(): void;
23
+ close(): void;
24
+ }
25
+ export interface SqliteConnectionsStateStoreOptions {
26
+ stateRootDir: string;
27
+ busyTimeoutMs?: number;
28
+ resolveDatabasePath?: (stateRootDir: string) => string;
29
+ openDatabase?: (path: string) => SqliteDatabaseLike;
30
+ }
31
+ export interface ConnectionsStateBackedChannelSessionStoreOptions {
32
+ provider: ProviderKind;
33
+ legacyStore: ChannelSessionStoreLike;
34
+ sqliteStateStore?: ConnectionsStateStore;
35
+ logger?: DebugLogger;
36
+ }
37
+ export interface ProviderConnectionsSessionStoreOptions {
38
+ provider: ProviderKind;
39
+ legacyStore: ChannelSessionStoreLike;
40
+ stateRootDir: string;
41
+ gateEnabled: boolean;
42
+ logger?: DebugLogger;
43
+ }
44
+ interface SqliteStatementLike {
45
+ run(...params: unknown[]): unknown;
46
+ get(...params: unknown[]): unknown;
47
+ all(...params: unknown[]): unknown[];
48
+ }
49
+ interface SqliteDatabaseLike {
50
+ exec(sql: string): void;
51
+ prepare(sql: string): SqliteStatementLike;
52
+ close(): void;
53
+ }
54
+ export declare class SqliteConnectionsStateStore implements ConnectionsStateStore {
55
+ private readonly options;
56
+ private database;
57
+ private closed;
58
+ constructor(options: SqliteConnectionsStateStoreOptions);
59
+ loadProviderSessions(provider: ProviderKind, agentId: string): Record<string, string>;
60
+ saveProviderSessions(provider: ProviderKind, agentId: string, sessions: Record<string, string>): void;
61
+ hasTokenBinding(token: string): boolean;
62
+ loadTokenBinding(token: string): ChannelTokenBinding | null;
63
+ loadTokenBindingForChannel(agentId: string, channelId: string): ChannelTokenBinding | null;
64
+ replaceTokenBinding(binding: ChannelTokenBinding): void;
65
+ deleteTokenBinding(token: string): void;
66
+ revokeTokenBinding(token: string): void;
67
+ revokeAllTokenBindings(): void;
68
+ close(): void;
69
+ private ensureDatabase;
70
+ private loadStoredTokenBinding;
71
+ }
72
+ export declare class ConnectionsStateBackedChannelSessionStore implements ChannelSessionStoreLike {
73
+ private readonly options;
74
+ private readonly logger;
75
+ constructor(options: ConnectionsStateBackedChannelSessionStoreOptions);
76
+ load(agentId: string): Promise<Record<string, string>>;
77
+ save(agentId: string, sessions: Record<string, string>): Promise<void>;
78
+ close(): void;
79
+ }
80
+ export declare function createProviderConnectionsSessionStore(options: ProviderConnectionsSessionStoreOptions): ChannelSessionStoreLike;
81
+ export {};
@@ -0,0 +1,228 @@
1
+ import { chmodSync, mkdirSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname } from 'node:path';
4
+ import { HostLogger, summarizeError } from './debug.js';
5
+ import { resolveConnectionsStatePath } from './state-paths.js';
6
+ const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
7
+ const PRIVATE_STATE_FILE_MODE = 0o600;
8
+ const require = createRequire(import.meta.url);
9
+ function loadDatabaseSync() {
10
+ const sqliteModule = require('node:sqlite');
11
+ if (typeof sqliteModule.DatabaseSync !== 'function') {
12
+ throw new Error('node:sqlite DatabaseSync is unavailable');
13
+ }
14
+ return sqliteModule.DatabaseSync;
15
+ }
16
+ export class SqliteConnectionsStateStore {
17
+ options;
18
+ database = null;
19
+ closed = false;
20
+ constructor(options) {
21
+ this.options = options;
22
+ }
23
+ loadProviderSessions(provider, agentId) {
24
+ const rows = this.ensureDatabase().prepare(`SELECT channel_id, session_id
25
+ FROM provider_channel_sessions
26
+ WHERE provider = ? AND agent_id = ?
27
+ ORDER BY channel_id ASC`).all(provider, agentId);
28
+ return Object.fromEntries(rows.map((row) => [row.channel_id, row.session_id]));
29
+ }
30
+ saveProviderSessions(provider, agentId, sessions) {
31
+ const database = this.ensureDatabase();
32
+ const entries = Object.entries(sessions).sort(([left], [right]) => left.localeCompare(right));
33
+ database.exec('BEGIN IMMEDIATE');
34
+ try {
35
+ database.prepare(`DELETE FROM provider_channel_sessions
36
+ WHERE provider = ? AND agent_id = ?`).run(provider, agentId);
37
+ if (entries.length > 0) {
38
+ const insert = database.prepare(`INSERT INTO provider_channel_sessions (provider, agent_id, channel_id, session_id)
39
+ VALUES (?, ?, ?, ?)`);
40
+ for (const [channelId, sessionId] of entries) {
41
+ insert.run(provider, agentId, channelId, sessionId);
42
+ }
43
+ }
44
+ database.exec('COMMIT');
45
+ }
46
+ catch (error) {
47
+ database.exec('ROLLBACK');
48
+ throw error;
49
+ }
50
+ }
51
+ hasTokenBinding(token) {
52
+ const row = this.ensureDatabase().prepare(`SELECT 1
53
+ FROM channel_token_bindings
54
+ WHERE token = ?
55
+ LIMIT 1`).get(token);
56
+ return row != null;
57
+ }
58
+ loadTokenBinding(token) {
59
+ const row = this.ensureDatabase().prepare(`SELECT token, agent_id, channel_id
60
+ FROM channel_token_bindings
61
+ WHERE token = ? AND revoked_at IS NULL`).get(token);
62
+ if (!row) {
63
+ return null;
64
+ }
65
+ return {
66
+ token: row.token,
67
+ agentId: row.agent_id,
68
+ channelId: row.channel_id,
69
+ };
70
+ }
71
+ loadTokenBindingForChannel(agentId, channelId) {
72
+ const row = this.ensureDatabase().prepare(`SELECT token, agent_id, channel_id
73
+ FROM channel_token_bindings
74
+ WHERE agent_id = ? AND channel_id = ? AND revoked_at IS NULL
75
+ ORDER BY rowid DESC
76
+ LIMIT 1`).get(agentId, channelId);
77
+ if (!row) {
78
+ return null;
79
+ }
80
+ return {
81
+ token: row.token,
82
+ agentId: row.agent_id,
83
+ channelId: row.channel_id,
84
+ };
85
+ }
86
+ replaceTokenBinding(binding) {
87
+ const database = this.ensureDatabase();
88
+ database.exec('BEGIN IMMEDIATE');
89
+ try {
90
+ const bindingByToken = this.loadStoredTokenBinding(binding.token);
91
+ if (bindingByToken) {
92
+ if (bindingByToken.revokedAt != null) {
93
+ throw new Error(`channel token ${binding.token} was revoked and cannot be reused`);
94
+ }
95
+ if (bindingByToken.agentId !== binding.agentId || bindingByToken.channelId !== binding.channelId) {
96
+ throw new Error(`channel token is already bound to ${bindingByToken.agentId}/${bindingByToken.channelId}`);
97
+ }
98
+ }
99
+ database.prepare(`UPDATE channel_token_bindings
100
+ SET revoked_at = unixepoch()
101
+ WHERE agent_id = ? AND channel_id = ? AND token <> ? AND revoked_at IS NULL`).run(binding.agentId, binding.channelId, binding.token);
102
+ database.prepare(`INSERT INTO channel_token_bindings (token, agent_id, channel_id, revoked_at)
103
+ VALUES (?, ?, ?, NULL)
104
+ ON CONFLICT(token) DO UPDATE SET
105
+ agent_id = excluded.agent_id,
106
+ channel_id = excluded.channel_id,
107
+ revoked_at = NULL`).run(binding.token, binding.agentId, binding.channelId);
108
+ database.exec('COMMIT');
109
+ }
110
+ catch (error) {
111
+ database.exec('ROLLBACK');
112
+ throw error;
113
+ }
114
+ }
115
+ deleteTokenBinding(token) {
116
+ this.ensureDatabase().prepare(`DELETE FROM channel_token_bindings
117
+ WHERE token = ?`).run(token);
118
+ }
119
+ revokeTokenBinding(token) {
120
+ this.ensureDatabase().prepare(`UPDATE channel_token_bindings
121
+ SET revoked_at = unixepoch()
122
+ WHERE token = ? AND revoked_at IS NULL`).run(token);
123
+ }
124
+ revokeAllTokenBindings() {
125
+ this.ensureDatabase().exec('UPDATE channel_token_bindings SET revoked_at = unixepoch() WHERE revoked_at IS NULL');
126
+ }
127
+ close() {
128
+ this.database?.close();
129
+ this.database = null;
130
+ this.closed = true;
131
+ }
132
+ ensureDatabase() {
133
+ if (this.closed) {
134
+ throw new Error('connections state store is closed');
135
+ }
136
+ if (this.database) {
137
+ return this.database;
138
+ }
139
+ const path = (this.options.resolveDatabasePath ?? resolveConnectionsStatePath)(this.options.stateRootDir);
140
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
141
+ const database = (this.options.openDatabase ?? ((databasePath) => new (loadDatabaseSync())(databasePath)))(path);
142
+ try {
143
+ chmodSync(path, PRIVATE_STATE_FILE_MODE);
144
+ database.exec(`PRAGMA busy_timeout = ${this.options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS}`);
145
+ database.exec('PRAGMA journal_mode = DELETE');
146
+ database.exec(`CREATE TABLE IF NOT EXISTS provider_channel_sessions (
147
+ provider TEXT NOT NULL,
148
+ agent_id TEXT NOT NULL,
149
+ channel_id TEXT NOT NULL,
150
+ session_id TEXT NOT NULL,
151
+ PRIMARY KEY (provider, agent_id, channel_id)
152
+ )`);
153
+ database.exec(`CREATE TABLE IF NOT EXISTS channel_token_bindings (
154
+ token TEXT PRIMARY KEY,
155
+ agent_id TEXT NOT NULL,
156
+ channel_id TEXT NOT NULL,
157
+ revoked_at INTEGER
158
+ )`);
159
+ const columns = database.prepare(`SELECT name
160
+ FROM pragma_table_info('channel_token_bindings')
161
+ WHERE name = 'revoked_at'
162
+ LIMIT 1`).all();
163
+ if (columns.length === 0) {
164
+ database.exec('ALTER TABLE channel_token_bindings ADD COLUMN revoked_at INTEGER');
165
+ }
166
+ }
167
+ catch (error) {
168
+ database.close();
169
+ throw error;
170
+ }
171
+ this.database = database;
172
+ return database;
173
+ }
174
+ loadStoredTokenBinding(token) {
175
+ const row = this.ensureDatabase().prepare(`SELECT token, agent_id, channel_id, revoked_at
176
+ FROM channel_token_bindings
177
+ WHERE token = ?`).get(token);
178
+ if (!row) {
179
+ return null;
180
+ }
181
+ return {
182
+ token: row.token,
183
+ agentId: row.agent_id,
184
+ channelId: row.channel_id,
185
+ revokedAt: row.revoked_at,
186
+ };
187
+ }
188
+ }
189
+ export class ConnectionsStateBackedChannelSessionStore {
190
+ options;
191
+ logger;
192
+ constructor(options) {
193
+ this.options = options;
194
+ this.logger = options.logger ?? new HostLogger();
195
+ }
196
+ async load(agentId) {
197
+ return this.options.legacyStore.load(agentId);
198
+ }
199
+ async save(agentId, sessions) {
200
+ await this.options.legacyStore.save(agentId, sessions);
201
+ if (!this.options.sqliteStateStore) {
202
+ return;
203
+ }
204
+ try {
205
+ this.options.sqliteStateStore.saveProviderSessions(this.options.provider, agentId, sessions);
206
+ }
207
+ catch (error) {
208
+ this.logger.error(`failed to mirror ${this.options.provider} session map into connections state`, {
209
+ provider: this.options.provider,
210
+ error: summarizeError(error),
211
+ });
212
+ }
213
+ }
214
+ close() {
215
+ this.options.sqliteStateStore?.close();
216
+ this.options.legacyStore.close?.();
217
+ }
218
+ }
219
+ export function createProviderConnectionsSessionStore(options) {
220
+ return new ConnectionsStateBackedChannelSessionStore({
221
+ provider: options.provider,
222
+ legacyStore: options.legacyStore,
223
+ sqliteStateStore: options.gateEnabled
224
+ ? new SqliteConnectionsStateStore({ stateRootDir: options.stateRootDir })
225
+ : undefined,
226
+ logger: options.logger,
227
+ });
228
+ }
@@ -0,0 +1,109 @@
1
+ import type { LocalhostGatewayBootstrapMetadata, ProviderCollaborationContext, SkillRuntimeBootstrapMetadata, TaskAssignmentThreadContext, TaskWorkspaceContext } from '../types.js';
2
+ export interface SkillRuntimeBootstrapPayload {
3
+ skillDirectoryPath: string;
4
+ nodeCliPath: string;
5
+ pythonCliPath: string;
6
+ }
7
+ export interface ChannelContextPayload {
8
+ schemaVersion: 1;
9
+ channelId: string;
10
+ skillRuntime?: SkillRuntimeBootstrapPayload;
11
+ localhostGateway?: LocalhostGatewayBootstrapMetadata;
12
+ taskAssignmentContext?: TaskAssignmentThreadContext;
13
+ taskWorkspace?: TaskWorkspaceContext;
14
+ }
15
+ export interface PreparedChannelContext {
16
+ directoryPath: string;
17
+ payload: ChannelContextPayload;
18
+ payloadPath?: string;
19
+ gatewayAuthPath?: string;
20
+ skillRuntime?: SkillRuntimeBootstrapMetadata;
21
+ localhostGateway?: LocalhostGatewayBootstrapMetadata;
22
+ taskWorkspace?: TaskWorkspaceContext;
23
+ }
24
+ export declare class ChannelContextPreparationError extends Error {
25
+ readonly partialContext: PreparedChannelContext;
26
+ constructor(message: string, partialContext: PreparedChannelContext, options?: ErrorOptions);
27
+ }
28
+ export interface ChannelContextStore {
29
+ prepare(input: {
30
+ channelId: string;
31
+ collaboration?: ProviderCollaborationContext;
32
+ incomingMessageType?: string;
33
+ incomingContent?: string;
34
+ }): Promise<PreparedChannelContext>;
35
+ prepare(channelId: string, options?: {
36
+ collaboration?: ProviderCollaborationContext;
37
+ incomingMessageType?: string;
38
+ incomingContent?: string;
39
+ }): Promise<PreparedChannelContext>;
40
+ }
41
+ export interface SkillAssetResolver {
42
+ resolve(): Promise<SkillRuntimeBootstrapMetadata>;
43
+ }
44
+ export interface LocalhostGatewayContextPublisher {
45
+ resolveBootstrap(channelId: string, options?: {
46
+ collaboration?: {
47
+ enabled: boolean;
48
+ turnExecutionId: string;
49
+ };
50
+ }): LocalhostGatewayBootstrapMetadata & {
51
+ token: string;
52
+ turnExecutionId?: string;
53
+ };
54
+ publishPayload(channelId: string, payloadPath: string, payload: ChannelContextPayload): void;
55
+ clearChannel(channelId: string): void;
56
+ }
57
+ interface ContextFileSystem {
58
+ mkdir(path: string, options?: {
59
+ mode?: number;
60
+ recursive?: boolean;
61
+ }): Promise<void>;
62
+ readdir(path: string): Promise<string[]>;
63
+ readFile(path: string, options: {
64
+ encoding: BufferEncoding;
65
+ } | BufferEncoding): Promise<string>;
66
+ writeFile(path: string, data: string, options?: {
67
+ encoding?: BufferEncoding;
68
+ mode?: number;
69
+ }): Promise<void>;
70
+ unlink(path: string): Promise<void>;
71
+ access(path: string): Promise<void>;
72
+ }
73
+ interface FileChannelContextStoreOptions {
74
+ fileSystem?: ContextFileSystem;
75
+ skillRuntimeEnabled?: boolean;
76
+ skillAssetResolver?: SkillAssetResolver;
77
+ localhostGateway?: LocalhostGatewayContextPublisher;
78
+ taskWorkspaceRootDir?: string;
79
+ }
80
+ export declare function extractTaskIdFromTaskAssignmentContent(incomingContent: string | undefined): string | undefined;
81
+ export declare function encodeChannelPathSegment(channelId: string): string;
82
+ export declare function resolveChannelContextDirectory(stateRootDir: string, channelId: string): string;
83
+ export declare function resolveChannelContextPayloadPath(stateRootDir: string, channelId: string): string;
84
+ export declare function resolveTaskWorkspaceRootDirectory(startupWorkspaceRootDir: string): string;
85
+ export declare function resolveTaskWorkspaceDirectory(startupWorkspaceRootDir: string, channelId: string, taskId: string): string;
86
+ export declare function resolveChannelContextGatewayAuthPath(stateRootDir: string, channelId: string, turnExecutionId?: string): string;
87
+ export declare function resolveGatewayAuthPathFromPayloadPath(payloadPath: string): string;
88
+ export declare function resolveBorgeeAgentSkillRuntimeAssets(moduleUrl: string, fileSystem?: Pick<ContextFileSystem, 'access'>): Promise<SkillRuntimeBootstrapMetadata>;
89
+ export declare class FileChannelContextStore implements ChannelContextStore {
90
+ private readonly stateRootDir;
91
+ private readonly fileSystem;
92
+ private readonly skillRuntimeEnabled;
93
+ private readonly skillAssetResolver;
94
+ private readonly localhostGateway?;
95
+ private readonly startupWorkspaceRootDir;
96
+ constructor(stateRootDir: string, options?: FileChannelContextStoreOptions);
97
+ prepare(inputOrChannelId: {
98
+ channelId: string;
99
+ collaboration?: ProviderCollaborationContext;
100
+ incomingMessageType?: string;
101
+ incomingContent?: string;
102
+ } | string, options?: {
103
+ collaboration?: ProviderCollaborationContext;
104
+ incomingMessageType?: string;
105
+ incomingContent?: string;
106
+ }): Promise<PreparedChannelContext>;
107
+ private resolveSkillRuntimeBestEffort;
108
+ }
109
+ export {};