@botcord/daemon 0.2.4 → 0.2.6
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/dist/agent-discovery.d.ts +7 -3
- package/dist/agent-discovery.js +9 -1
- package/dist/agent-workspace.d.ts +62 -0
- package/dist/agent-workspace.js +140 -10
- package/dist/config.d.ts +49 -1
- package/dist/config.js +57 -1
- package/dist/control-channel.d.ts +1 -4
- package/dist/control-channel.js +1 -4
- package/dist/daemon-config-map.d.ts +29 -12
- package/dist/daemon-config-map.js +105 -8
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +52 -5
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.js +22 -1
- package/dist/gateway/cli-resolver.d.ts +34 -0
- package/dist/gateway/cli-resolver.js +74 -0
- package/dist/gateway/dispatcher.d.ts +66 -1
- package/dist/gateway/dispatcher.js +583 -56
- package/dist/gateway/gateway.d.ts +29 -1
- package/dist/gateway/gateway.js +10 -0
- package/dist/gateway/index.d.ts +2 -0
- package/dist/gateway/index.js +2 -0
- package/dist/gateway/policy-resolver.d.ts +57 -0
- package/dist/gateway/policy-resolver.js +123 -0
- package/dist/gateway/runtimes/acp-stream.d.ts +99 -0
- package/dist/gateway/runtimes/acp-stream.js +394 -0
- package/dist/gateway/runtimes/codex.js +7 -0
- package/dist/gateway/runtimes/hermes-agent.d.ts +83 -0
- package/dist/gateway/runtimes/hermes-agent.js +180 -0
- package/dist/gateway/runtimes/ndjson-stream.d.ts +7 -2
- package/dist/gateway/runtimes/ndjson-stream.js +16 -3
- package/dist/gateway/runtimes/openclaw-acp.d.ts +44 -0
- package/dist/gateway/runtimes/openclaw-acp.js +500 -0
- package/dist/gateway/runtimes/registry.d.ts +4 -0
- package/dist/gateway/runtimes/registry.js +22 -0
- package/dist/gateway/transcript-paths.d.ts +30 -0
- package/dist/gateway/transcript-paths.js +114 -0
- package/dist/gateway/transcript.d.ts +123 -0
- package/dist/gateway/transcript.js +147 -0
- package/dist/gateway/types.d.ts +31 -0
- package/dist/index.js +286 -27
- package/dist/mention-scan.d.ts +22 -0
- package/dist/mention-scan.js +35 -0
- package/dist/provision.d.ts +73 -3
- package/dist/provision.js +373 -12
- package/dist/system-context.d.ts +5 -4
- package/dist/system-context.js +35 -5
- package/dist/turn-text.js +20 -1
- package/dist/url-utils.d.ts +9 -0
- package/dist/url-utils.js +18 -0
- package/dist/user-auth.js +0 -2
- package/dist/working-memory.js +1 -1
- package/package.json +2 -1
- package/src/__tests__/agent-workspace.test.ts +93 -0
- package/src/__tests__/daemon-config-map.test.ts +79 -0
- package/src/__tests__/openclaw-acp.test.ts +234 -0
- package/src/__tests__/policy-resolver.test.ts +124 -0
- package/src/__tests__/policy-updated-handler.test.ts +144 -0
- package/src/__tests__/provision.test.ts +160 -0
- package/src/__tests__/system-context.test.ts +52 -0
- package/src/__tests__/url-utils.test.ts +37 -0
- package/src/agent-discovery.ts +12 -4
- package/src/agent-workspace.ts +173 -9
- package/src/config.ts +132 -4
- package/src/control-channel.ts +1 -4
- package/src/daemon-config-map.ts +156 -12
- package/src/daemon.ts +66 -5
- package/src/doctor.ts +49 -2
- package/src/gateway/__tests__/dispatcher.test.ts +440 -2
- package/src/gateway/__tests__/hermes-agent-adapter.test.ts +302 -0
- package/src/gateway/__tests__/transcript.test.ts +496 -0
- package/src/gateway/cli-resolver.ts +92 -0
- package/src/gateway/dispatcher.ts +681 -58
- package/src/gateway/gateway.ts +46 -0
- package/src/gateway/index.ts +25 -0
- package/src/gateway/policy-resolver.ts +171 -0
- package/src/gateway/runtimes/acp-stream.ts +535 -0
- package/src/gateway/runtimes/codex.ts +7 -0
- package/src/gateway/runtimes/hermes-agent.ts +206 -0
- package/src/gateway/runtimes/ndjson-stream.ts +16 -3
- package/src/gateway/runtimes/openclaw-acp.ts +606 -0
- package/src/gateway/runtimes/registry.ts +24 -0
- package/src/gateway/transcript-paths.ts +145 -0
- package/src/gateway/transcript.ts +300 -0
- package/src/gateway/types.ts +32 -0
- package/src/index.ts +295 -30
- package/src/mention-scan.ts +38 -0
- package/src/provision.ts +446 -20
- package/src/system-context.ts +41 -9
- package/src/turn-text.ts +22 -1
- package/src/url-utils.ts +17 -0
- package/src/user-auth.ts +0 -2
- package/src/working-memory.ts +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ChannelBackoffOptions } from "./channel-manager.js";
|
|
2
2
|
import { type RuntimeFactory } from "./dispatcher.js";
|
|
3
3
|
import { type GatewayLogger } from "./log.js";
|
|
4
|
-
import
|
|
4
|
+
import { type TranscriptWriter } from "./transcript.js";
|
|
5
|
+
import type { ChannelAdapter, GatewayChannelConfig, GatewayConfig, GatewayInboundMessage, GatewayRoute, GatewayRuntimeSnapshot, InboundObserver, OutboundObserver, SystemContextBuilder, UserTurnBuilder } from "./types.js";
|
|
5
6
|
/** Constructor options for `Gateway`. */
|
|
6
7
|
export interface GatewayBootOptions {
|
|
7
8
|
config: GatewayConfig;
|
|
@@ -35,6 +36,33 @@ export interface GatewayBootOptions {
|
|
|
35
36
|
* bookkeeping like loop-risk tracking.
|
|
36
37
|
*/
|
|
37
38
|
onOutbound?: OutboundObserver;
|
|
39
|
+
/**
|
|
40
|
+
* Optional attention gate (PR3, design §4.2). Forwarded to the dispatcher
|
|
41
|
+
* verbatim — see {@link Dispatcher} for semantics. Returning `false` skips
|
|
42
|
+
* the runtime turn while preserving ack + onInbound side effects.
|
|
43
|
+
*/
|
|
44
|
+
attentionGate?: (message: GatewayInboundMessage) => Promise<boolean> | boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the per-agent hub URL for an inbound message. Forwarded to the
|
|
47
|
+
* dispatcher as `RuntimeRunOptions.hubUrl` so spawned CLI subprocesses
|
|
48
|
+
* (`BOTCORD_HUB`) target the correct hub for the owning agent.
|
|
49
|
+
*/
|
|
50
|
+
resolveHubUrl?: (accountId: string) => string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Persistent NDJSON transcript writer (design §3 / §6). Optional — when
|
|
53
|
+
* omitted the dispatcher uses a noop writer. Pass `transcriptEnabled` plus
|
|
54
|
+
* `transcriptRootDir` to let the gateway construct one for you.
|
|
55
|
+
*/
|
|
56
|
+
transcript?: TranscriptWriter;
|
|
57
|
+
/**
|
|
58
|
+
* Tri-state convenience: if `transcript` is not provided, the gateway
|
|
59
|
+
* constructs a writer using this flag plus `transcriptRootDir`. Use
|
|
60
|
+
* {@link resolveTranscriptEnabled} to combine `BOTCORD_TRANSCRIPT` env with
|
|
61
|
+
* the persistent daemon-config flag.
|
|
62
|
+
*/
|
|
63
|
+
transcriptEnabled?: boolean;
|
|
64
|
+
/** Root directory for transcript files. Defaults to `~/.botcord/agents`. */
|
|
65
|
+
transcriptRootDir?: string;
|
|
38
66
|
}
|
|
39
67
|
/**
|
|
40
68
|
* Top-level gateway bootstrap. Wires `ChannelManager` → `Dispatcher` →
|
package/dist/gateway/gateway.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Dispatcher } from "./dispatcher.js";
|
|
|
3
3
|
import { consoleLogger } from "./log.js";
|
|
4
4
|
import { createRuntime } from "./runtimes/registry.js";
|
|
5
5
|
import { DEFAULT_SESSION_STORE_MAX_ENTRY_AGE_MS, SessionStore } from "./session-store.js";
|
|
6
|
+
import { createTranscriptWriter, } from "./transcript.js";
|
|
6
7
|
/** Default runtime factory: delegates to the built-in registry; ignores extraArgs at construction. */
|
|
7
8
|
const defaultRuntimeFactory = (runtimeId) => createRuntime(runtimeId);
|
|
8
9
|
/**
|
|
@@ -53,6 +54,12 @@ export class Gateway {
|
|
|
53
54
|
maxEntryAgeMs: opts.sessionStoreMaxEntryAgeMs ?? DEFAULT_SESSION_STORE_MAX_ENTRY_AGE_MS,
|
|
54
55
|
});
|
|
55
56
|
const runtimeFactory = opts.createRuntime ?? defaultRuntimeFactory;
|
|
57
|
+
const transcript = opts.transcript
|
|
58
|
+
?? createTranscriptWriter({
|
|
59
|
+
log: this.log,
|
|
60
|
+
enabled: opts.transcriptEnabled === true,
|
|
61
|
+
rootDir: opts.transcriptRootDir,
|
|
62
|
+
});
|
|
56
63
|
this.dispatcher = new Dispatcher({
|
|
57
64
|
config: this.config,
|
|
58
65
|
channels: this.channelMap,
|
|
@@ -65,6 +72,9 @@ export class Gateway {
|
|
|
65
72
|
composeUserTurn: opts.composeUserTurn,
|
|
66
73
|
onOutbound: opts.onOutbound,
|
|
67
74
|
managedRoutes: this.managedRoutes,
|
|
75
|
+
attentionGate: opts.attentionGate,
|
|
76
|
+
resolveHubUrl: opts.resolveHubUrl,
|
|
77
|
+
transcript,
|
|
68
78
|
});
|
|
69
79
|
this.channelManager = new ChannelManager({
|
|
70
80
|
config: this.config,
|
package/dist/gateway/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { resolveRoute, matchesRoute } from "./router.js";
|
|
|
8
8
|
export { ChannelManager, type ChannelManagerOptions, type ChannelBackoffOptions } from "./channel-manager.js";
|
|
9
9
|
export { Dispatcher, type DispatcherOptions, type RuntimeFactory } from "./dispatcher.js";
|
|
10
10
|
export { Gateway, type GatewayBootOptions } from "./gateway.js";
|
|
11
|
+
export { createTranscriptWriter, resolveTranscriptEnabled, defaultTranscriptRoot, truncateTextField, TRANSCRIPT_TEXT_LIMIT, TRANSCRIPT_FILE_LIMIT, type TranscriptWriter, type TranscriptRecord, type InboundTranscriptRecord, type DispatchedTranscriptRecord, type ComposeFailedTranscriptRecord, type OutboundTranscriptRecord, type TurnErrorTranscriptRecord, type AttentionSkippedTranscriptRecord, type DroppedTranscriptRecord, type DeliveryStatus, type DroppedReason, } from "./transcript.js";
|
|
12
|
+
export { safePathSegment, transcriptFilePath, transcriptRoomDir, transcriptAgentRoot, } from "./transcript-paths.js";
|
|
11
13
|
export { ClaudeCodeAdapter, probeClaude, resolveClaudeCommand, } from "./runtimes/claude-code.js";
|
|
12
14
|
export { CodexAdapter, probeCodex, resolveCodexCommand } from "./runtimes/codex.js";
|
|
13
15
|
export { GeminiAdapter, probeGemini, resolveGeminiCommand } from "./runtimes/gemini.js";
|
package/dist/gateway/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export { resolveRoute, matchesRoute } from "./router.js";
|
|
|
8
8
|
export { ChannelManager } from "./channel-manager.js";
|
|
9
9
|
export { Dispatcher } from "./dispatcher.js";
|
|
10
10
|
export { Gateway } from "./gateway.js";
|
|
11
|
+
export { createTranscriptWriter, resolveTranscriptEnabled, defaultTranscriptRoot, truncateTextField, TRANSCRIPT_TEXT_LIMIT, TRANSCRIPT_FILE_LIMIT, } from "./transcript.js";
|
|
12
|
+
export { safePathSegment, transcriptFilePath, transcriptRoomDir, transcriptAgentRoot, } from "./transcript-paths.js";
|
|
11
13
|
export { ClaudeCodeAdapter, probeClaude, resolveClaudeCommand, } from "./runtimes/claude-code.js";
|
|
12
14
|
export { CodexAdapter, probeCodex, resolveCodexCommand } from "./runtimes/codex.js";
|
|
13
15
|
export { GeminiAdapter, probeGemini, resolveGeminiCommand } from "./runtimes/gemini.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon-side per-agent attention-policy cache (PR3, design §5).
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher consults this resolver after `onInbound` fires and before
|
|
5
|
+
* the runtime turn enqueues. Cache layout:
|
|
6
|
+
*
|
|
7
|
+
* - `agent_id` → global default policy (seeded by
|
|
8
|
+
* `provision_agent` + `policy_updated{agent}`).
|
|
9
|
+
* - `agent_id:room_id` → genuine per-room override only — installed
|
|
10
|
+
* exclusively via `put` from a per-room
|
|
11
|
+
* `policy_updated` frame. Inheritance reads
|
|
12
|
+
* never write here.
|
|
13
|
+
*
|
|
14
|
+
* `resolve(agent, room)` checks the room key first, then falls back to the
|
|
15
|
+
* global key. This means a per-room override always wins, and the global
|
|
16
|
+
* propagates to every room without explicit fan-out (a global update only
|
|
17
|
+
* needs to refresh the agent_id entry).
|
|
18
|
+
*
|
|
19
|
+
* `invalidate(agent_id, room_id)` drops the matching room entry; the next
|
|
20
|
+
* resolve falls through to the global. `invalidate(agent_id)` drops every
|
|
21
|
+
* entry for that agent — both global and any room overrides — used when
|
|
22
|
+
* the agent is revoked or the cache must rebuild from scratch.
|
|
23
|
+
*/
|
|
24
|
+
import type { AttentionPolicy } from "@botcord/protocol-core";
|
|
25
|
+
/** Public surface — kept narrow so the dispatcher can mock easily in tests. */
|
|
26
|
+
export interface PolicyResolverLike {
|
|
27
|
+
resolve(agentId: string, roomId: string | null): Promise<AttentionPolicy>;
|
|
28
|
+
invalidate(agentId: string, roomId?: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Install (or replace) the cached policy entry for an agent / room. Used
|
|
31
|
+
* by the `policy_updated` control-frame handler to apply embedded policy
|
|
32
|
+
* payloads without forcing a refetch.
|
|
33
|
+
*/
|
|
34
|
+
put(agentId: string, roomId: string | null, policy: AttentionPolicy): void;
|
|
35
|
+
}
|
|
36
|
+
export interface PolicyResolverOptions {
|
|
37
|
+
/** Fetcher for the per-agent default. Returning `undefined` means "no policy known"; the resolver falls back to `mode=always`. */
|
|
38
|
+
fetchGlobal: (agentId: string) => Promise<AttentionPolicy | undefined>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional per-room fetcher. PR2 supplies this; PR3 leaves it
|
|
41
|
+
* unimplemented and the resolver collapses to the global policy.
|
|
42
|
+
*/
|
|
43
|
+
fetchEffective?: (agentId: string, roomId: string) => Promise<AttentionPolicy | undefined>;
|
|
44
|
+
/** Cache TTL in milliseconds. Defaults to 5 minutes. */
|
|
45
|
+
ttlMs?: number;
|
|
46
|
+
}
|
|
47
|
+
export declare class PolicyResolver implements PolicyResolverLike {
|
|
48
|
+
private readonly fetchGlobal;
|
|
49
|
+
private readonly fetchEffective?;
|
|
50
|
+
private readonly ttlMs;
|
|
51
|
+
private readonly cache;
|
|
52
|
+
constructor(opts: PolicyResolverOptions);
|
|
53
|
+
resolve(agentId: string, roomId: string | null): Promise<AttentionPolicy>;
|
|
54
|
+
private safeFetch;
|
|
55
|
+
invalidate(agentId: string, roomId?: string): void;
|
|
56
|
+
put(agentId: string, roomId: string | null, policy: AttentionPolicy): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon-side per-agent attention-policy cache (PR3, design §5).
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher consults this resolver after `onInbound` fires and before
|
|
5
|
+
* the runtime turn enqueues. Cache layout:
|
|
6
|
+
*
|
|
7
|
+
* - `agent_id` → global default policy (seeded by
|
|
8
|
+
* `provision_agent` + `policy_updated{agent}`).
|
|
9
|
+
* - `agent_id:room_id` → genuine per-room override only — installed
|
|
10
|
+
* exclusively via `put` from a per-room
|
|
11
|
+
* `policy_updated` frame. Inheritance reads
|
|
12
|
+
* never write here.
|
|
13
|
+
*
|
|
14
|
+
* `resolve(agent, room)` checks the room key first, then falls back to the
|
|
15
|
+
* global key. This means a per-room override always wins, and the global
|
|
16
|
+
* propagates to every room without explicit fan-out (a global update only
|
|
17
|
+
* needs to refresh the agent_id entry).
|
|
18
|
+
*
|
|
19
|
+
* `invalidate(agent_id, room_id)` drops the matching room entry; the next
|
|
20
|
+
* resolve falls through to the global. `invalidate(agent_id)` drops every
|
|
21
|
+
* entry for that agent — both global and any room overrides — used when
|
|
22
|
+
* the agent is revoked or the cache must rebuild from scratch.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
const FETCH_FAILED = Symbol("fetch_failed");
|
|
26
|
+
/**
|
|
27
|
+
* Force DM rooms (`rm_dm_*`) to `mode: "always"` per design §4.2 — UI never
|
|
28
|
+
* lets the user mute a DM, but a stale cache from before a UX bug is cheap
|
|
29
|
+
* to defend against here.
|
|
30
|
+
*/
|
|
31
|
+
function maybeForceDm(roomId, policy) {
|
|
32
|
+
if (roomId && roomId.startsWith("rm_dm_") && policy.mode !== "always") {
|
|
33
|
+
return { ...policy, mode: "always" };
|
|
34
|
+
}
|
|
35
|
+
return policy;
|
|
36
|
+
}
|
|
37
|
+
function defaultPolicy() {
|
|
38
|
+
return { mode: "always", keywords: [] };
|
|
39
|
+
}
|
|
40
|
+
export class PolicyResolver {
|
|
41
|
+
fetchGlobal;
|
|
42
|
+
fetchEffective;
|
|
43
|
+
ttlMs;
|
|
44
|
+
cache = new Map();
|
|
45
|
+
constructor(opts) {
|
|
46
|
+
this.fetchGlobal = opts.fetchGlobal;
|
|
47
|
+
this.fetchEffective = opts.fetchEffective;
|
|
48
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
49
|
+
}
|
|
50
|
+
async resolve(agentId, roomId) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
// 1. Per-room cache — populated either by a `policy_updated{room_id}`
|
|
53
|
+
// push (genuine override) or by a prior `fetchEffective` cold-start.
|
|
54
|
+
if (roomId) {
|
|
55
|
+
const roomHit = this.cache.get(cacheKey(agentId, roomId));
|
|
56
|
+
if (roomHit && roomHit.expiresAt > now)
|
|
57
|
+
return roomHit.policy;
|
|
58
|
+
}
|
|
59
|
+
// 2. If a per-room fetcher is wired, treat it as authoritative for cold
|
|
60
|
+
// rooms — it returns the override-merged effective policy and so must
|
|
61
|
+
// not be skipped just because the global cache is warm.
|
|
62
|
+
if (roomId && this.fetchEffective) {
|
|
63
|
+
const fetched = await this.safeFetch(() => this.fetchEffective(agentId, roomId));
|
|
64
|
+
if (fetched === FETCH_FAILED)
|
|
65
|
+
return defaultPolicy();
|
|
66
|
+
const policy = fetched ?? defaultPolicy();
|
|
67
|
+
this.cache.set(cacheKey(agentId, roomId), {
|
|
68
|
+
policy: maybeForceDm(roomId, policy),
|
|
69
|
+
expiresAt: now + this.ttlMs,
|
|
70
|
+
});
|
|
71
|
+
return maybeForceDm(roomId, policy);
|
|
72
|
+
}
|
|
73
|
+
// 3. No room override known — inherit from the cached agent-wide global.
|
|
74
|
+
// Without this layer, group messages collapsed to mode=always whenever
|
|
75
|
+
// the daemon ran without a per-room fetcher (the current production
|
|
76
|
+
// state), silently breaking global mention_only/muted.
|
|
77
|
+
const globalKey = cacheKey(agentId, null);
|
|
78
|
+
const globalHit = this.cache.get(globalKey);
|
|
79
|
+
if (globalHit && globalHit.expiresAt > now) {
|
|
80
|
+
return maybeForceDm(roomId, globalHit.policy);
|
|
81
|
+
}
|
|
82
|
+
// 4. Cold start for global.
|
|
83
|
+
const fetched = await this.safeFetch(() => this.fetchGlobal(agentId));
|
|
84
|
+
if (fetched === FETCH_FAILED)
|
|
85
|
+
return defaultPolicy();
|
|
86
|
+
const policy = fetched ?? defaultPolicy();
|
|
87
|
+
this.cache.set(globalKey, { policy, expiresAt: now + this.ttlMs });
|
|
88
|
+
return maybeForceDm(roomId, policy);
|
|
89
|
+
}
|
|
90
|
+
async safeFetch(fn) {
|
|
91
|
+
try {
|
|
92
|
+
return await fn();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Fail-open: a fetch error must not silence the agent. The caller
|
|
96
|
+
// returns the default policy without caching so the next resolve retries.
|
|
97
|
+
return FETCH_FAILED;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
invalidate(agentId, roomId) {
|
|
101
|
+
if (roomId !== undefined) {
|
|
102
|
+
this.cache.delete(cacheKey(agentId, roomId));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Drop every entry for this agent.
|
|
106
|
+
const prefix = agentId + ":";
|
|
107
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
108
|
+
if (key === agentId || key.startsWith(prefix)) {
|
|
109
|
+
this.cache.delete(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
put(agentId, roomId, policy) {
|
|
114
|
+
const key = cacheKey(agentId, roomId);
|
|
115
|
+
this.cache.set(key, {
|
|
116
|
+
policy: maybeForceDm(roomId, policy),
|
|
117
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function cacheKey(agentId, roomId) {
|
|
122
|
+
return roomId ? `${agentId}:${roomId}` : agentId;
|
|
123
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { RuntimeAdapter, RuntimeProbeResult, RuntimeRunOptions, RuntimeRunResult, StreamBlock } from "../types.js";
|
|
2
|
+
/** ACP protocol version this client targets. */
|
|
3
|
+
export declare const ACP_PROTOCOL_VERSION = 1;
|
|
4
|
+
export interface AcpInitializeResult {
|
|
5
|
+
protocolVersion?: number;
|
|
6
|
+
agentInfo?: {
|
|
7
|
+
name?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
};
|
|
10
|
+
agentCapabilities?: Record<string, unknown>;
|
|
11
|
+
authMethods?: Array<{
|
|
12
|
+
id?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}>;
|
|
16
|
+
[k: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface AcpPermissionOption {
|
|
19
|
+
optionId: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
/**
|
|
22
|
+
* ACP option kind. Common values: `allow_once`, `allow_always`,
|
|
23
|
+
* `reject_once`, `reject_always`. Treated as opaque by the base class —
|
|
24
|
+
* subclasses inspect `.kind` to pick the right outcome.
|
|
25
|
+
*/
|
|
26
|
+
kind?: string;
|
|
27
|
+
[k: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface AcpPermissionRequest {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
toolCall?: {
|
|
32
|
+
name?: string;
|
|
33
|
+
rawInput?: unknown;
|
|
34
|
+
[k: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
options: AcpPermissionOption[];
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export type AcpPermissionResponse = {
|
|
40
|
+
outcome: {
|
|
41
|
+
outcome: "selected";
|
|
42
|
+
optionId: string;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
outcome: {
|
|
46
|
+
outcome: "cancelled";
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export interface AcpUpdateParams {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
update: {
|
|
52
|
+
sessionUpdate?: string;
|
|
53
|
+
[k: string]: unknown;
|
|
54
|
+
};
|
|
55
|
+
[k: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
/** Hooks exposed to subclasses to react to inbound traffic during a turn. */
|
|
58
|
+
export interface AcpTurnHooks {
|
|
59
|
+
/** Called for each `session/update` notification. */
|
|
60
|
+
onUpdate(params: AcpUpdateParams, ctx: AcpUpdateCtx): void;
|
|
61
|
+
/** Called for `session/request_permission` requests. Must resolve to an outcome. */
|
|
62
|
+
onPermissionRequest(req: AcpPermissionRequest): Promise<AcpPermissionResponse>;
|
|
63
|
+
}
|
|
64
|
+
export interface AcpUpdateCtx {
|
|
65
|
+
/** Append to the turn's running assistant text. */
|
|
66
|
+
appendAssistantText(text: string): void;
|
|
67
|
+
/** Forward a normalized StreamBlock to `opts.onBlock`. */
|
|
68
|
+
emitBlock(block: StreamBlock): void;
|
|
69
|
+
/** 1-based sequence within this turn. */
|
|
70
|
+
seq: number;
|
|
71
|
+
}
|
|
72
|
+
export declare abstract class AcpRuntimeAdapter implements RuntimeAdapter {
|
|
73
|
+
abstract readonly id: string;
|
|
74
|
+
probe?(): RuntimeProbeResult;
|
|
75
|
+
protected abstract resolveBinary(opts: RuntimeRunOptions): string;
|
|
76
|
+
/** Argv tail (excluding the binary). ACP servers usually take none. */
|
|
77
|
+
protected buildArgs(_opts: RuntimeRunOptions): string[];
|
|
78
|
+
protected abstract spawnEnv(opts: RuntimeRunOptions): NodeJS.ProcessEnv;
|
|
79
|
+
/** Subclass hook: react to one `session/update` notification. */
|
|
80
|
+
protected abstract onUpdate(params: AcpUpdateParams, ctx: AcpUpdateCtx): void;
|
|
81
|
+
/** Subclass hook: respond to a `session/request_permission` request. */
|
|
82
|
+
protected abstract onPermissionRequest(req: AcpPermissionRequest, opts: RuntimeRunOptions): Promise<AcpPermissionResponse>;
|
|
83
|
+
/** Runtime-specific clientCapabilities sent on initialize. */
|
|
84
|
+
protected clientCapabilities(): Record<string, unknown>;
|
|
85
|
+
/** Runtime-specific clientInfo sent on initialize. */
|
|
86
|
+
protected clientInfo(): {
|
|
87
|
+
name: string;
|
|
88
|
+
version: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Hook invoked synchronously before spawn. Subclasses use this to write
|
|
92
|
+
* systemContext to disk (e.g. `<cwd>/AGENTS.md`).
|
|
93
|
+
*/
|
|
94
|
+
protected prepareTurn(_opts: RuntimeRunOptions): void;
|
|
95
|
+
/** cwd passed to ACP `session/new` / `session/load`. Typically `opts.cwd`. */
|
|
96
|
+
protected sessionCwd(opts: RuntimeRunOptions): string;
|
|
97
|
+
run(opts: RuntimeRunOptions): Promise<RuntimeRunResult>;
|
|
98
|
+
private withTimeout;
|
|
99
|
+
}
|