@botcord/daemon 0.2.5 → 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 +4 -0
- package/dist/agent-discovery.js +8 -0
- package/dist/agent-workspace.d.ts +62 -0
- package/dist/agent-workspace.js +140 -8
- package/dist/config.d.ts +49 -1
- package/dist/config.js +57 -1
- package/dist/daemon-config-map.d.ts +27 -9
- 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 +31 -1
- package/dist/gateway/dispatcher.js +337 -29
- 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 +72 -1
- package/dist/provision.js +370 -7
- package/dist/system-context.d.ts +5 -4
- package/dist/system-context.js +35 -5
- package/dist/url-utils.d.ts +9 -0
- package/dist/url-utils.js +18 -0
- 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 +8 -0
- package/src/agent-workspace.ts +173 -7
- package/src/config.ts +132 -4
- package/src/daemon-config-map.ts +154 -9
- package/src/daemon.ts +66 -5
- package/src/doctor.ts +49 -2
- package/src/gateway/__tests__/dispatcher.test.ts +65 -0
- 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 +394 -26
- 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 +438 -9
- package/src/system-context.ts +41 -9
- package/src/url-utils.ts +17 -0
|
@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { agentCodexHomeDir, ensureAgentCodexHome } from "../../agent-workspace.js";
|
|
5
|
+
import { buildCliEnv } from "../cli-resolver.js";
|
|
5
6
|
import { NdjsonStreamAdapter } from "./ndjson-stream.js";
|
|
6
7
|
import { firstExistingPath, readCommandVersion, resolveCommandOnPath, } from "./probe.js";
|
|
7
8
|
const CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
@@ -188,8 +189,14 @@ export class CodexAdapter extends NdjsonStreamAdapter {
|
|
|
188
189
|
return ["exec", ...tail, "--", prompt];
|
|
189
190
|
}
|
|
190
191
|
spawnEnv(opts) {
|
|
192
|
+
const cliEnv = buildCliEnv({
|
|
193
|
+
hubUrl: opts.hubUrl,
|
|
194
|
+
accountId: opts.accountId,
|
|
195
|
+
basePath: process.env.PATH,
|
|
196
|
+
});
|
|
191
197
|
const env = {
|
|
192
198
|
...process.env,
|
|
199
|
+
...cliEnv,
|
|
193
200
|
// Keep JSONL free of ANSI codes regardless of user terminal settings.
|
|
194
201
|
FORCE_COLOR: "0",
|
|
195
202
|
NO_COLOR: "1",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AcpRuntimeAdapter, type AcpPermissionRequest, type AcpPermissionResponse, type AcpUpdateCtx, type AcpUpdateParams } from "./acp-stream.js";
|
|
2
|
+
import { type ProbeDeps } from "./probe.js";
|
|
3
|
+
import type { RuntimeProbeResult, RuntimeRunOptions } from "../types.js";
|
|
4
|
+
/** Resolve the `hermes-acp` executable on PATH. */
|
|
5
|
+
export declare function resolveHermesAcpCommand(deps?: ProbeDeps): string | null;
|
|
6
|
+
/** Probe whether `hermes-acp` is installed and report its version. */
|
|
7
|
+
export declare function probeHermesAgent(deps?: ProbeDeps): RuntimeProbeResult;
|
|
8
|
+
/**
|
|
9
|
+
* Hermes Agent adapter. Drives `hermes-acp` (the ACP stdio adapter shipped
|
|
10
|
+
* with `pip install "hermes-agent[acp]"`).
|
|
11
|
+
*
|
|
12
|
+
* ## systemContext injection
|
|
13
|
+
*
|
|
14
|
+
* Hermes discovers `AGENTS.md` from the spawn cwd upward. We point cwd at a
|
|
15
|
+
* runtime-private directory (`~/.botcord/agents/<id>/hermes-workspace/`) and
|
|
16
|
+
* write `<cwd>/AGENTS.md` from `opts.systemContext` before spawn. This is a
|
|
17
|
+
* **first-turn-only** injection: hermes persists the system prompt in the
|
|
18
|
+
* session DB and does not re-read AGENTS.md on continuation turns. The
|
|
19
|
+
* design doc tracks this as a known limitation; a follow-up PR to
|
|
20
|
+
* hermes-agent would expose a per-turn ephemeral prompt channel.
|
|
21
|
+
*
|
|
22
|
+
* ## Per-agent isolation
|
|
23
|
+
*
|
|
24
|
+
* - `HERMES_HOME` → `<agent-home>/hermes-home/` so `.env`, `state.db`,
|
|
25
|
+
* `skills/` per-agent are isolated from `~/.hermes`.
|
|
26
|
+
* - cwd → `<agent-home>/hermes-workspace/` (NOT the user-editable
|
|
27
|
+
* `<agent-home>/workspace/`) so each turn's daemon-rewritten AGENTS.md
|
|
28
|
+
* does not clobber files the user/agent edited.
|
|
29
|
+
*
|
|
30
|
+
* ## Permission policy (trustLevel → ACP outcome)
|
|
31
|
+
*
|
|
32
|
+
* `HERMES_INTERACTIVE=1` makes hermes route dangerous tool calls through the
|
|
33
|
+
* ACP `session/request_permission` reverse-call. We answer per trustLevel:
|
|
34
|
+
* - `owner` → always select an `allow_*` option
|
|
35
|
+
* - `trusted` → same; reasons go to the daemon log only
|
|
36
|
+
* - `public` → cancel (DeniedOutcome) for all writes/exec
|
|
37
|
+
*/
|
|
38
|
+
export declare class HermesAgentAdapter extends AcpRuntimeAdapter {
|
|
39
|
+
readonly id: "hermes-agent";
|
|
40
|
+
private readonly explicitBinary;
|
|
41
|
+
private resolvedBinary;
|
|
42
|
+
constructor(opts?: {
|
|
43
|
+
binary?: string;
|
|
44
|
+
});
|
|
45
|
+
probe(): RuntimeProbeResult;
|
|
46
|
+
protected resolveBinary(): string;
|
|
47
|
+
/**
|
|
48
|
+
* hermes-acp is invoked with no positional args — ACP is pure stdio
|
|
49
|
+
* JSON-RPC. We do not forward `opts.extraArgs` because hermes-acp does
|
|
50
|
+
* not accept CLI flags for runtime config; per-agent config goes in
|
|
51
|
+
* `<HERMES_HOME>/.env`.
|
|
52
|
+
*/
|
|
53
|
+
protected buildArgs(_opts: RuntimeRunOptions): string[];
|
|
54
|
+
protected spawnEnv(opts: RuntimeRunOptions): NodeJS.ProcessEnv;
|
|
55
|
+
protected sessionCwd(opts: RuntimeRunOptions): string;
|
|
56
|
+
/**
|
|
57
|
+
* Write systemContext to `<hermes-workspace>/AGENTS.md` atomically before
|
|
58
|
+
* spawn. NOTE: hermes only reads this file on the first turn of a session
|
|
59
|
+
* (see class-level docstring); subsequent turns keep the persisted
|
|
60
|
+
* system prompt and ignore filesystem changes.
|
|
61
|
+
*/
|
|
62
|
+
protected prepareTurn(opts: RuntimeRunOptions): void;
|
|
63
|
+
/** Spawn with the runtime-private hermes-workspace as cwd. */
|
|
64
|
+
run(opts: RuntimeRunOptions): Promise<import("../types.js").RuntimeRunResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Translate ACP `session/update` notifications into StreamBlocks +
|
|
67
|
+
* assistant text. We surface the common shapes that hermes emits:
|
|
68
|
+
* - `agent_message_chunk` / `user_message_chunk` content blocks
|
|
69
|
+
* - `tool_call` / `tool_call_update`
|
|
70
|
+
* - `agent_thought_chunk`
|
|
71
|
+
*
|
|
72
|
+
* Anything else is forwarded as `kind: "other"` so subclasses /
|
|
73
|
+
* downstream channels can introspect.
|
|
74
|
+
*/
|
|
75
|
+
protected onUpdate(params: AcpUpdateParams, ctx: AcpUpdateCtx): void;
|
|
76
|
+
/**
|
|
77
|
+
* trustLevel-driven policy. We pick the FIRST option whose `kind` matches
|
|
78
|
+
* our intent — `allow_*` for permit, otherwise cancel. ACP's
|
|
79
|
+
* DeniedOutcome carries no `optionId` / `reason` field; rationale lives
|
|
80
|
+
* in the daemon log.
|
|
81
|
+
*/
|
|
82
|
+
protected onPermissionRequest(req: AcpPermissionRequest, opts: RuntimeRunOptions): Promise<AcpPermissionResponse>;
|
|
83
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { agentHermesHomeDir, agentHermesWorkspaceDir, ensureAgentHermesWorkspace, } from "../../agent-workspace.js";
|
|
4
|
+
import { buildCliEnv } from "../cli-resolver.js";
|
|
5
|
+
import { AcpRuntimeAdapter, } from "./acp-stream.js";
|
|
6
|
+
import { readCommandVersion, resolveCommandOnPath } from "./probe.js";
|
|
7
|
+
/** Resolve the `hermes-acp` executable on PATH. */
|
|
8
|
+
export function resolveHermesAcpCommand(deps = {}) {
|
|
9
|
+
return resolveCommandOnPath("hermes-acp", deps);
|
|
10
|
+
}
|
|
11
|
+
/** Probe whether `hermes-acp` is installed and report its version. */
|
|
12
|
+
export function probeHermesAgent(deps = {}) {
|
|
13
|
+
const command = resolveHermesAcpCommand(deps);
|
|
14
|
+
if (!command)
|
|
15
|
+
return { available: false };
|
|
16
|
+
return {
|
|
17
|
+
available: true,
|
|
18
|
+
path: command,
|
|
19
|
+
version: readCommandVersion(command, [], deps) ?? undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hermes Agent adapter. Drives `hermes-acp` (the ACP stdio adapter shipped
|
|
24
|
+
* with `pip install "hermes-agent[acp]"`).
|
|
25
|
+
*
|
|
26
|
+
* ## systemContext injection
|
|
27
|
+
*
|
|
28
|
+
* Hermes discovers `AGENTS.md` from the spawn cwd upward. We point cwd at a
|
|
29
|
+
* runtime-private directory (`~/.botcord/agents/<id>/hermes-workspace/`) and
|
|
30
|
+
* write `<cwd>/AGENTS.md` from `opts.systemContext` before spawn. This is a
|
|
31
|
+
* **first-turn-only** injection: hermes persists the system prompt in the
|
|
32
|
+
* session DB and does not re-read AGENTS.md on continuation turns. The
|
|
33
|
+
* design doc tracks this as a known limitation; a follow-up PR to
|
|
34
|
+
* hermes-agent would expose a per-turn ephemeral prompt channel.
|
|
35
|
+
*
|
|
36
|
+
* ## Per-agent isolation
|
|
37
|
+
*
|
|
38
|
+
* - `HERMES_HOME` → `<agent-home>/hermes-home/` so `.env`, `state.db`,
|
|
39
|
+
* `skills/` per-agent are isolated from `~/.hermes`.
|
|
40
|
+
* - cwd → `<agent-home>/hermes-workspace/` (NOT the user-editable
|
|
41
|
+
* `<agent-home>/workspace/`) so each turn's daemon-rewritten AGENTS.md
|
|
42
|
+
* does not clobber files the user/agent edited.
|
|
43
|
+
*
|
|
44
|
+
* ## Permission policy (trustLevel → ACP outcome)
|
|
45
|
+
*
|
|
46
|
+
* `HERMES_INTERACTIVE=1` makes hermes route dangerous tool calls through the
|
|
47
|
+
* ACP `session/request_permission` reverse-call. We answer per trustLevel:
|
|
48
|
+
* - `owner` → always select an `allow_*` option
|
|
49
|
+
* - `trusted` → same; reasons go to the daemon log only
|
|
50
|
+
* - `public` → cancel (DeniedOutcome) for all writes/exec
|
|
51
|
+
*/
|
|
52
|
+
export class HermesAgentAdapter extends AcpRuntimeAdapter {
|
|
53
|
+
id = "hermes-agent";
|
|
54
|
+
explicitBinary;
|
|
55
|
+
resolvedBinary = null;
|
|
56
|
+
constructor(opts) {
|
|
57
|
+
super();
|
|
58
|
+
this.explicitBinary = opts?.binary ?? process.env.BOTCORD_HERMES_AGENT_BIN;
|
|
59
|
+
}
|
|
60
|
+
probe() {
|
|
61
|
+
return probeHermesAgent();
|
|
62
|
+
}
|
|
63
|
+
resolveBinary() {
|
|
64
|
+
if (this.explicitBinary)
|
|
65
|
+
return this.explicitBinary;
|
|
66
|
+
if (this.resolvedBinary)
|
|
67
|
+
return this.resolvedBinary;
|
|
68
|
+
this.resolvedBinary = resolveHermesAcpCommand() ?? "hermes-acp";
|
|
69
|
+
return this.resolvedBinary;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* hermes-acp is invoked with no positional args — ACP is pure stdio
|
|
73
|
+
* JSON-RPC. We do not forward `opts.extraArgs` because hermes-acp does
|
|
74
|
+
* not accept CLI flags for runtime config; per-agent config goes in
|
|
75
|
+
* `<HERMES_HOME>/.env`.
|
|
76
|
+
*/
|
|
77
|
+
buildArgs(_opts) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
spawnEnv(opts) {
|
|
81
|
+
const cliEnv = buildCliEnv({
|
|
82
|
+
hubUrl: opts.hubUrl,
|
|
83
|
+
accountId: opts.accountId,
|
|
84
|
+
basePath: process.env.PATH,
|
|
85
|
+
});
|
|
86
|
+
const env = {
|
|
87
|
+
...process.env,
|
|
88
|
+
...cliEnv,
|
|
89
|
+
// Keep ACP stdout free of ANSI codes regardless of terminal settings.
|
|
90
|
+
NO_COLOR: "1",
|
|
91
|
+
// Route dangerous tool calls through ACP request_permission.
|
|
92
|
+
HERMES_INTERACTIVE: "1",
|
|
93
|
+
};
|
|
94
|
+
if (opts.accountId) {
|
|
95
|
+
env.HERMES_HOME = agentHermesHomeDir(opts.accountId);
|
|
96
|
+
}
|
|
97
|
+
return env;
|
|
98
|
+
}
|
|
99
|
+
sessionCwd(opts) {
|
|
100
|
+
if (opts.accountId)
|
|
101
|
+
return agentHermesWorkspaceDir(opts.accountId);
|
|
102
|
+
return opts.cwd;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write systemContext to `<hermes-workspace>/AGENTS.md` atomically before
|
|
106
|
+
* spawn. NOTE: hermes only reads this file on the first turn of a session
|
|
107
|
+
* (see class-level docstring); subsequent turns keep the persisted
|
|
108
|
+
* system prompt and ignore filesystem changes.
|
|
109
|
+
*/
|
|
110
|
+
prepareTurn(opts) {
|
|
111
|
+
if (!opts.accountId)
|
|
112
|
+
return;
|
|
113
|
+
const { hermesWorkspace } = ensureAgentHermesWorkspace(opts.accountId);
|
|
114
|
+
const target = path.join(hermesWorkspace, "AGENTS.md");
|
|
115
|
+
const tmp = path.join(hermesWorkspace, `.AGENTS.md.${process.pid}.tmp`);
|
|
116
|
+
mkdirSync(hermesWorkspace, { recursive: true, mode: 0o700 });
|
|
117
|
+
writeFileSync(tmp, opts.systemContext ?? "", { mode: 0o600 });
|
|
118
|
+
renameSync(tmp, target);
|
|
119
|
+
}
|
|
120
|
+
/** Spawn with the runtime-private hermes-workspace as cwd. */
|
|
121
|
+
async run(opts) {
|
|
122
|
+
const effective = opts.accountId
|
|
123
|
+
? { ...opts, cwd: agentHermesWorkspaceDir(opts.accountId) }
|
|
124
|
+
: opts;
|
|
125
|
+
return super.run(effective);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Translate ACP `session/update` notifications into StreamBlocks +
|
|
129
|
+
* assistant text. We surface the common shapes that hermes emits:
|
|
130
|
+
* - `agent_message_chunk` / `user_message_chunk` content blocks
|
|
131
|
+
* - `tool_call` / `tool_call_update`
|
|
132
|
+
* - `agent_thought_chunk`
|
|
133
|
+
*
|
|
134
|
+
* Anything else is forwarded as `kind: "other"` so subclasses /
|
|
135
|
+
* downstream channels can introspect.
|
|
136
|
+
*/
|
|
137
|
+
onUpdate(params, ctx) {
|
|
138
|
+
const update = params.update ?? {};
|
|
139
|
+
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "";
|
|
140
|
+
let blockKind = "other";
|
|
141
|
+
if (kind === "agent_message_chunk") {
|
|
142
|
+
const content = update
|
|
143
|
+
.content;
|
|
144
|
+
if (content && content.type === "text" && typeof content.text === "string") {
|
|
145
|
+
ctx.appendAssistantText(content.text);
|
|
146
|
+
}
|
|
147
|
+
blockKind = "assistant_text";
|
|
148
|
+
}
|
|
149
|
+
else if (kind === "agent_thought_chunk") {
|
|
150
|
+
blockKind = "system";
|
|
151
|
+
}
|
|
152
|
+
else if (kind === "tool_call" || kind === "tool_call_update") {
|
|
153
|
+
blockKind = "tool_use";
|
|
154
|
+
}
|
|
155
|
+
else if (kind === "user_message_chunk") {
|
|
156
|
+
blockKind = "other";
|
|
157
|
+
}
|
|
158
|
+
ctx.emitBlock({ raw: params, kind: blockKind, seq: ctx.seq });
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* trustLevel-driven policy. We pick the FIRST option whose `kind` matches
|
|
162
|
+
* our intent — `allow_*` for permit, otherwise cancel. ACP's
|
|
163
|
+
* DeniedOutcome carries no `optionId` / `reason` field; rationale lives
|
|
164
|
+
* in the daemon log.
|
|
165
|
+
*/
|
|
166
|
+
async onPermissionRequest(req, opts) {
|
|
167
|
+
const options = Array.isArray(req.options) ? req.options : [];
|
|
168
|
+
const trust = opts.trustLevel;
|
|
169
|
+
if (trust === "owner" || trust === "trusted") {
|
|
170
|
+
const allow = options.find((o) => typeof o.kind === "string" && o.kind.startsWith("allow_")) ??
|
|
171
|
+
options[0];
|
|
172
|
+
if (allow?.optionId) {
|
|
173
|
+
return { outcome: { outcome: "selected", optionId: allow.optionId } };
|
|
174
|
+
}
|
|
175
|
+
return { outcome: { outcome: "cancelled" } };
|
|
176
|
+
}
|
|
177
|
+
// public: deny everything that requires explicit approval
|
|
178
|
+
return { outcome: { outcome: "cancelled" } };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -37,7 +37,12 @@ export declare abstract class NdjsonStreamAdapter implements RuntimeAdapter {
|
|
|
37
37
|
protected abstract resolveBinary(opts: RuntimeRunOptions): string;
|
|
38
38
|
protected abstract buildArgs(opts: RuntimeRunOptions): string[];
|
|
39
39
|
protected abstract handleEvent(obj: unknown, ctx: NdjsonEventCtx): void;
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Override to tweak env (FORCE_COLOR=0, NO_COLOR=1, etc). Subclasses that
|
|
42
|
+
* override should compose with the bundled-CLI env helper so spawned
|
|
43
|
+
* `botcord` invocations stay scoped to the right hub/agent — see
|
|
44
|
+
* {@link buildCliEnv}.
|
|
45
|
+
*/
|
|
46
|
+
protected spawnEnv(opts: RuntimeRunOptions): NodeJS.ProcessEnv;
|
|
42
47
|
run(opts: RuntimeRunOptions): Promise<RuntimeRunResult>;
|
|
43
48
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { buildCliEnv } from "../cli-resolver.js";
|
|
2
3
|
import { consoleLogger } from "../log.js";
|
|
3
4
|
const log = consoleLogger;
|
|
4
5
|
/**
|
|
@@ -22,9 +23,21 @@ const ASSISTANT_TEXT_CAP = 1 * 1024 * 1024;
|
|
|
22
23
|
const KILL_GRACE_MS = 5_000;
|
|
23
24
|
/** Base class for runtime adapters that drive a CLI emitting newline-delimited JSON. */
|
|
24
25
|
export class NdjsonStreamAdapter {
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Override to tweak env (FORCE_COLOR=0, NO_COLOR=1, etc). Subclasses that
|
|
28
|
+
* override should compose with the bundled-CLI env helper so spawned
|
|
29
|
+
* `botcord` invocations stay scoped to the right hub/agent — see
|
|
30
|
+
* {@link buildCliEnv}.
|
|
31
|
+
*/
|
|
32
|
+
spawnEnv(opts) {
|
|
33
|
+
return {
|
|
34
|
+
...process.env,
|
|
35
|
+
...buildCliEnv({
|
|
36
|
+
hubUrl: opts.hubUrl,
|
|
37
|
+
accountId: opts.accountId,
|
|
38
|
+
basePath: process.env.PATH,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
28
41
|
}
|
|
29
42
|
async run(opts) {
|
|
30
43
|
if (opts.signal.aborted) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type ProbeDeps } from "./probe.js";
|
|
3
|
+
import type { RuntimeAdapter, RuntimeProbeResult, RuntimeRunOptions, RuntimeRunResult } from "../types.js";
|
|
4
|
+
/** Test-only: drop all cached child processes. */
|
|
5
|
+
export declare function __resetOpenclawAcpPoolForTests(): void;
|
|
6
|
+
export declare function probeOpenclaw(deps?: ProbeDeps): RuntimeProbeResult;
|
|
7
|
+
interface SpawnDeps {
|
|
8
|
+
spawnFn?: typeof spawn;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* OpenClaw ACP runtime adapter.
|
|
12
|
+
*
|
|
13
|
+
* Spawns `openclaw acp --url <gateway> [--token <token>]` per
|
|
14
|
+
* `(accountId, gatewayName)` pair and reuses the process across turns. The
|
|
15
|
+
* child speaks JSON-RPC over stdio; we send `initialize` once, then
|
|
16
|
+
* `newSession` (with `_meta.sessionKey`) when the daemon has no persisted
|
|
17
|
+
* runtime session id, and `prompt` for each turn. Streaming `session/update`
|
|
18
|
+
* notifications are relayed to `onBlock`.
|
|
19
|
+
*
|
|
20
|
+
* Process-pool lifetime + abort/cancel semantics live at module scope; see
|
|
21
|
+
* `ACP_POOL` and `shutdownHandle` above.
|
|
22
|
+
*/
|
|
23
|
+
export declare class OpenclawAcpAdapter implements RuntimeAdapter {
|
|
24
|
+
readonly id: "openclaw-acp";
|
|
25
|
+
private readonly spawnFn;
|
|
26
|
+
constructor(deps?: SpawnDeps);
|
|
27
|
+
probe(): RuntimeProbeResult;
|
|
28
|
+
run(opts: RuntimeRunOptions): Promise<RuntimeRunResult>;
|
|
29
|
+
private acquireHandle;
|
|
30
|
+
private spawnAcpProcess;
|
|
31
|
+
private newSession;
|
|
32
|
+
private prompt;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the OpenClaw ACP `sessionKey` for a daemon turn. `accountId` is
|
|
36
|
+
* always included to prevent two daemon agents from colliding on the same
|
|
37
|
+
* gateway-side key (RFC §3.5.2 串号 防御).
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAcpSessionKey(args: {
|
|
40
|
+
openclawAgent: string;
|
|
41
|
+
accountId: string;
|
|
42
|
+
conversationKey: string;
|
|
43
|
+
}): string;
|
|
44
|
+
export {};
|