@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.
Files changed (84) hide show
  1. package/dist/agent-discovery.d.ts +4 -0
  2. package/dist/agent-discovery.js +8 -0
  3. package/dist/agent-workspace.d.ts +62 -0
  4. package/dist/agent-workspace.js +140 -8
  5. package/dist/config.d.ts +49 -1
  6. package/dist/config.js +57 -1
  7. package/dist/daemon-config-map.d.ts +27 -9
  8. package/dist/daemon-config-map.js +105 -8
  9. package/dist/daemon.d.ts +2 -0
  10. package/dist/daemon.js +52 -5
  11. package/dist/doctor.d.ts +27 -1
  12. package/dist/doctor.js +22 -1
  13. package/dist/gateway/cli-resolver.d.ts +34 -0
  14. package/dist/gateway/cli-resolver.js +74 -0
  15. package/dist/gateway/dispatcher.d.ts +31 -1
  16. package/dist/gateway/dispatcher.js +337 -29
  17. package/dist/gateway/gateway.d.ts +29 -1
  18. package/dist/gateway/gateway.js +10 -0
  19. package/dist/gateway/index.d.ts +2 -0
  20. package/dist/gateway/index.js +2 -0
  21. package/dist/gateway/policy-resolver.d.ts +57 -0
  22. package/dist/gateway/policy-resolver.js +123 -0
  23. package/dist/gateway/runtimes/acp-stream.d.ts +99 -0
  24. package/dist/gateway/runtimes/acp-stream.js +394 -0
  25. package/dist/gateway/runtimes/codex.js +7 -0
  26. package/dist/gateway/runtimes/hermes-agent.d.ts +83 -0
  27. package/dist/gateway/runtimes/hermes-agent.js +180 -0
  28. package/dist/gateway/runtimes/ndjson-stream.d.ts +7 -2
  29. package/dist/gateway/runtimes/ndjson-stream.js +16 -3
  30. package/dist/gateway/runtimes/openclaw-acp.d.ts +44 -0
  31. package/dist/gateway/runtimes/openclaw-acp.js +500 -0
  32. package/dist/gateway/runtimes/registry.d.ts +4 -0
  33. package/dist/gateway/runtimes/registry.js +22 -0
  34. package/dist/gateway/transcript-paths.d.ts +30 -0
  35. package/dist/gateway/transcript-paths.js +114 -0
  36. package/dist/gateway/transcript.d.ts +123 -0
  37. package/dist/gateway/transcript.js +147 -0
  38. package/dist/gateway/types.d.ts +31 -0
  39. package/dist/index.js +286 -27
  40. package/dist/mention-scan.d.ts +22 -0
  41. package/dist/mention-scan.js +35 -0
  42. package/dist/provision.d.ts +72 -1
  43. package/dist/provision.js +370 -7
  44. package/dist/system-context.d.ts +5 -4
  45. package/dist/system-context.js +35 -5
  46. package/dist/url-utils.d.ts +9 -0
  47. package/dist/url-utils.js +18 -0
  48. package/package.json +2 -1
  49. package/src/__tests__/agent-workspace.test.ts +93 -0
  50. package/src/__tests__/daemon-config-map.test.ts +79 -0
  51. package/src/__tests__/openclaw-acp.test.ts +234 -0
  52. package/src/__tests__/policy-resolver.test.ts +124 -0
  53. package/src/__tests__/policy-updated-handler.test.ts +144 -0
  54. package/src/__tests__/provision.test.ts +160 -0
  55. package/src/__tests__/system-context.test.ts +52 -0
  56. package/src/__tests__/url-utils.test.ts +37 -0
  57. package/src/agent-discovery.ts +8 -0
  58. package/src/agent-workspace.ts +173 -7
  59. package/src/config.ts +132 -4
  60. package/src/daemon-config-map.ts +154 -9
  61. package/src/daemon.ts +66 -5
  62. package/src/doctor.ts +49 -2
  63. package/src/gateway/__tests__/dispatcher.test.ts +65 -0
  64. package/src/gateway/__tests__/hermes-agent-adapter.test.ts +302 -0
  65. package/src/gateway/__tests__/transcript.test.ts +496 -0
  66. package/src/gateway/cli-resolver.ts +92 -0
  67. package/src/gateway/dispatcher.ts +394 -26
  68. package/src/gateway/gateway.ts +46 -0
  69. package/src/gateway/index.ts +25 -0
  70. package/src/gateway/policy-resolver.ts +171 -0
  71. package/src/gateway/runtimes/acp-stream.ts +535 -0
  72. package/src/gateway/runtimes/codex.ts +7 -0
  73. package/src/gateway/runtimes/hermes-agent.ts +206 -0
  74. package/src/gateway/runtimes/ndjson-stream.ts +16 -3
  75. package/src/gateway/runtimes/openclaw-acp.ts +606 -0
  76. package/src/gateway/runtimes/registry.ts +24 -0
  77. package/src/gateway/transcript-paths.ts +145 -0
  78. package/src/gateway/transcript.ts +300 -0
  79. package/src/gateway/types.ts +32 -0
  80. package/src/index.ts +295 -30
  81. package/src/mention-scan.ts +38 -0
  82. package/src/provision.ts +438 -9
  83. package/src/system-context.ts +41 -9
  84. 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
- /** Override to tweak env (FORCE_COLOR=0, NO_COLOR=1, etc). */
41
- protected spawnEnv(_opts: RuntimeRunOptions): NodeJS.ProcessEnv;
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
- /** Override to tweak env (FORCE_COLOR=0, NO_COLOR=1, etc). */
26
- spawnEnv(_opts) {
27
- return process.env;
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 {};