@botcord/daemon 0.2.55 → 0.2.57
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/daemon.js +25 -1
- package/dist/diagnostics.d.ts +30 -0
- package/dist/diagnostics.js +286 -0
- package/dist/gateway/gateway.d.ts +6 -0
- package/dist/gateway/gateway.js +8 -0
- package/dist/gateway/runtimes/claude-code.js +79 -5
- package/dist/gateway/runtimes/codex.js +67 -8
- package/dist/index.js +16 -1
- package/dist/provision.js +68 -0
- package/dist/working-memory.js +5 -0
- package/package.json +1 -1
- package/src/__tests__/diagnostics.test.ts +46 -0
- package/src/__tests__/provision.test.ts +45 -0
- package/src/__tests__/working-memory.test.ts +9 -1
- package/src/daemon.ts +25 -1
- package/src/diagnostics.ts +348 -0
- package/src/gateway/__tests__/claude-code-adapter.test.ts +35 -0
- package/src/gateway/__tests__/codex-adapter.test.ts +80 -4
- package/src/gateway/gateway.ts +9 -0
- package/src/gateway/runtimes/claude-code.ts +76 -4
- package/src/gateway/runtimes/codex.ts +66 -11
- package/src/index.ts +17 -1
- package/src/provision.ts +86 -0
- package/src/working-memory.ts +5 -0
|
@@ -371,7 +371,7 @@ process.stdout.write(JSON.stringify({type:"item.completed", item:{id:"i0", type:
|
|
|
371
371
|
expect(argv).toContain('approval_policy="never"');
|
|
372
372
|
});
|
|
373
373
|
|
|
374
|
-
it("extraArgs `-s read-only`
|
|
374
|
+
it("extraArgs `-s read-only` is converted to resume-compatible sandbox config", async () => {
|
|
375
375
|
const adapter = new CodexAdapter({ binary: echoScript() });
|
|
376
376
|
const ctrl = new AbortController();
|
|
377
377
|
const res = await adapter.run({
|
|
@@ -384,11 +384,87 @@ process.stdout.write(JSON.stringify({type:"item.completed", item:{id:"i0", type:
|
|
|
384
384
|
extraArgs: ["-s", "read-only"],
|
|
385
385
|
});
|
|
386
386
|
const argv = JSON.parse(res.text) as string[];
|
|
387
|
-
|
|
388
|
-
expect(argv.
|
|
389
|
-
expect(argv[argv.indexOf("-s") + 1]).toBe("read-only");
|
|
387
|
+
expect(argv).not.toContain("-s");
|
|
388
|
+
expect(argv).toContain('sandbox_mode="read-only"');
|
|
390
389
|
expect(argv).not.toContain('sandbox_mode="workspace-write"');
|
|
391
390
|
expect(argv).not.toContain('sandbox_mode="danger-full-access"');
|
|
392
391
|
});
|
|
392
|
+
|
|
393
|
+
it("extraArgs `--sandbox=value` is converted on resume too", async () => {
|
|
394
|
+
const adapter = new CodexAdapter({ binary: echoScript() });
|
|
395
|
+
const ctrl = new AbortController();
|
|
396
|
+
const res = await adapter.run({
|
|
397
|
+
text: "x",
|
|
398
|
+
sessionId: "01234567-89ab-7def-8123-456789abcdef",
|
|
399
|
+
accountId: "ag_test",
|
|
400
|
+
cwd: tmpRoot,
|
|
401
|
+
signal: ctrl.signal,
|
|
402
|
+
trustLevel: "public",
|
|
403
|
+
extraArgs: ["--sandbox=workspace-write"],
|
|
404
|
+
});
|
|
405
|
+
const argv = JSON.parse(res.text) as string[];
|
|
406
|
+
expect(argv[0]).toBe("exec");
|
|
407
|
+
expect(argv[1]).toBe("resume");
|
|
408
|
+
expect(argv).not.toContain("--sandbox=workspace-write");
|
|
409
|
+
expect(argv).toContain('sandbox_mode="workspace-write"');
|
|
410
|
+
expect(argv).not.toContain('sandbox_mode="danger-full-access"');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("maps legacy Codex --full-auto to the current bypass flag", async () => {
|
|
414
|
+
const adapter = new CodexAdapter({ binary: echoScript() });
|
|
415
|
+
const ctrl = new AbortController();
|
|
416
|
+
const res = await adapter.run({
|
|
417
|
+
text: "x",
|
|
418
|
+
sessionId: null,
|
|
419
|
+
accountId: "ag_test",
|
|
420
|
+
cwd: tmpRoot,
|
|
421
|
+
signal: ctrl.signal,
|
|
422
|
+
trustLevel: "public",
|
|
423
|
+
extraArgs: ["--full-auto"],
|
|
424
|
+
});
|
|
425
|
+
const argv = JSON.parse(res.text) as string[];
|
|
426
|
+
expect(argv).not.toContain("--full-auto");
|
|
427
|
+
expect(argv).toContain("--dangerously-bypass-approvals-and-sandbox");
|
|
428
|
+
expect(argv).not.toContain('sandbox_mode="danger-full-access"');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("drops inherited Claude --permission-mode extraArgs and their values", async () => {
|
|
432
|
+
const adapter = new CodexAdapter({ binary: echoScript() });
|
|
433
|
+
const ctrl = new AbortController();
|
|
434
|
+
const res = await adapter.run({
|
|
435
|
+
text: "x",
|
|
436
|
+
sessionId: null,
|
|
437
|
+
accountId: "ag_test",
|
|
438
|
+
cwd: tmpRoot,
|
|
439
|
+
signal: ctrl.signal,
|
|
440
|
+
trustLevel: "public",
|
|
441
|
+
extraArgs: ["--permission-mode", "bypassPermissions", "--model", "gpt-5.2"],
|
|
442
|
+
});
|
|
443
|
+
const argv = JSON.parse(res.text) as string[];
|
|
444
|
+
expect(argv).not.toContain("--permission-mode");
|
|
445
|
+
expect(argv).not.toContain("bypassPermissions");
|
|
446
|
+
expect(argv).toContain("--model");
|
|
447
|
+
expect(argv[argv.indexOf("--model") + 1]).toBe("gpt-5.2");
|
|
448
|
+
expect(argv).toContain('sandbox_mode="danger-full-access"');
|
|
449
|
+
expect(argv).toContain('approval_policy="never"');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("drops inherited Claude --permission-mode=value extraArgs", async () => {
|
|
453
|
+
const adapter = new CodexAdapter({ binary: echoScript() });
|
|
454
|
+
const ctrl = new AbortController();
|
|
455
|
+
const res = await adapter.run({
|
|
456
|
+
text: "x",
|
|
457
|
+
sessionId: null,
|
|
458
|
+
accountId: "ag_test",
|
|
459
|
+
cwd: tmpRoot,
|
|
460
|
+
signal: ctrl.signal,
|
|
461
|
+
trustLevel: "public",
|
|
462
|
+
extraArgs: ["--permission-mode=bypassPermissions"],
|
|
463
|
+
});
|
|
464
|
+
const argv = JSON.parse(res.text) as string[];
|
|
465
|
+
expect(argv).not.toContain("--permission-mode=bypassPermissions");
|
|
466
|
+
expect(argv).toContain('sandbox_mode="danger-full-access"');
|
|
467
|
+
expect(argv).toContain('approval_policy="never"');
|
|
468
|
+
});
|
|
393
469
|
});
|
|
394
470
|
});
|
package/src/gateway/gateway.ts
CHANGED
|
@@ -261,4 +261,13 @@ export class Gateway {
|
|
|
261
261
|
const idx = this.config.channels.findIndex((c) => c.id === id);
|
|
262
262
|
if (idx >= 0) this.config.channels.splice(idx, 1);
|
|
263
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Inject a daemon-internal inbound message into the normal dispatcher.
|
|
267
|
+
* Control-plane wakeups use this path so scheduled turns share the same
|
|
268
|
+
* routing, queueing, transcript, and runtime behavior as channel messages.
|
|
269
|
+
*/
|
|
270
|
+
async injectInbound(message: GatewayInboundMessage): Promise<void> {
|
|
271
|
+
await this.dispatcher.handle({ message });
|
|
272
|
+
}
|
|
264
273
|
}
|
|
@@ -32,6 +32,77 @@ function invalidClaudeSessionIdError(): string {
|
|
|
32
32
|
return "claude-code: invalid sessionId (expected non-control text not starting with '-')";
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const CLAUDE_FOREIGN_FLAGS_WITH_VALUE = new Set([
|
|
36
|
+
"--color",
|
|
37
|
+
"--config",
|
|
38
|
+
"--disable",
|
|
39
|
+
"--enable",
|
|
40
|
+
"--image",
|
|
41
|
+
"--local-provider",
|
|
42
|
+
"--output-last-message",
|
|
43
|
+
"--output-schema",
|
|
44
|
+
"--profile",
|
|
45
|
+
"--sandbox",
|
|
46
|
+
"-i",
|
|
47
|
+
"-o",
|
|
48
|
+
"-p",
|
|
49
|
+
"-s",
|
|
50
|
+
]);
|
|
51
|
+
const CLAUDE_FOREIGN_BOOLEAN_FLAGS = new Set([
|
|
52
|
+
"--all",
|
|
53
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
54
|
+
"--ephemeral",
|
|
55
|
+
"--full-auto",
|
|
56
|
+
"--ignore-rules",
|
|
57
|
+
"--ignore-user-config",
|
|
58
|
+
"--json",
|
|
59
|
+
"--last",
|
|
60
|
+
"--oss",
|
|
61
|
+
"--print",
|
|
62
|
+
"--skip-git-repo-check",
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
function extraFlagName(arg: string): string {
|
|
66
|
+
if (!arg.startsWith("-")) return arg;
|
|
67
|
+
const eq = arg.indexOf("=");
|
|
68
|
+
return eq === -1 ? arg : arg.slice(0, eq);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nextExtraValue(args: string[], index: number): string | undefined {
|
|
72
|
+
const next = args[index + 1];
|
|
73
|
+
if (typeof next !== "string") return undefined;
|
|
74
|
+
if (!next.startsWith("-")) return next;
|
|
75
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sanitizeClaudeExtraArgs(extraArgs: string[] | undefined): string[] {
|
|
79
|
+
if (!extraArgs?.length) return [];
|
|
80
|
+
const out: string[] = [];
|
|
81
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
82
|
+
const arg = extraArgs[i];
|
|
83
|
+
const name = extraFlagName(arg);
|
|
84
|
+
|
|
85
|
+
if (arg === "-c") {
|
|
86
|
+
const value = nextExtraValue(extraArgs, i);
|
|
87
|
+
if (value !== undefined) i += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (name === "--config" || name === "--sandbox") {
|
|
91
|
+
if (!arg.includes("=") && nextExtraValue(extraArgs, i) !== undefined) i += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (CLAUDE_FOREIGN_FLAGS_WITH_VALUE.has(name)) {
|
|
95
|
+
if (!arg.includes("=") && nextExtraValue(extraArgs, i) !== undefined) i += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (CLAUDE_FOREIGN_BOOLEAN_FLAGS.has(name)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
out.push(arg);
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
35
106
|
/** Resolve the Claude Code CLI path on PATH or the macOS desktop bundle fallback. */
|
|
36
107
|
export function resolveClaudeCommand(deps: ProbeDeps = {}): string | null {
|
|
37
108
|
const onPath = resolveCommandOnPath("claude", deps);
|
|
@@ -95,11 +166,12 @@ export class ClaudeCodeAdapter extends NdjsonStreamAdapter {
|
|
|
95
166
|
}
|
|
96
167
|
|
|
97
168
|
protected buildArgs(opts: RuntimeRunOptions): string[] {
|
|
169
|
+
const extraArgs = sanitizeClaudeExtraArgs(opts.extraArgs);
|
|
98
170
|
const args = ["-p", opts.text, "--output-format", "stream-json", "--verbose"];
|
|
99
171
|
// Headless `-p` mode does not load project `.claude/` by default, so
|
|
100
172
|
// per-agent skills seeded at `<workspace>/.claude/skills/` are invisible
|
|
101
173
|
// unless we opt in. `extraArgs` wins so operators can still override.
|
|
102
|
-
if (!
|
|
174
|
+
if (!extraArgs.some((a) => a.startsWith("--setting-sources"))) {
|
|
103
175
|
args.push("--setting-sources", "project");
|
|
104
176
|
}
|
|
105
177
|
if (opts.sessionId) {
|
|
@@ -112,16 +184,16 @@ export class ClaudeCodeAdapter extends NdjsonStreamAdapter {
|
|
|
112
184
|
// MCP) because there is no prompt relay back to the user yet. Default to
|
|
113
185
|
// bypassPermissions for every trust tier; operators who need a stricter
|
|
114
186
|
// posture can still override with route/defaultRoute extraArgs.
|
|
115
|
-
if (!
|
|
187
|
+
if (!extraArgs.some((a) => a.startsWith("--permission-mode"))) {
|
|
116
188
|
args.push("--permission-mode", "bypassPermissions");
|
|
117
189
|
}
|
|
118
190
|
// Claude Code's `--append-system-prompt` is applied per invocation and NOT
|
|
119
191
|
// persisted in the resumed session transcript — ideal for memory / digest
|
|
120
192
|
// content that should re-evaluate every turn.
|
|
121
|
-
if (opts.systemContext && !
|
|
193
|
+
if (opts.systemContext && !extraArgs.includes("--append-system-prompt")) {
|
|
122
194
|
args.push("--append-system-prompt", opts.systemContext);
|
|
123
195
|
}
|
|
124
|
-
if (
|
|
196
|
+
if (extraArgs.length) args.push(...extraArgs);
|
|
125
197
|
return args;
|
|
126
198
|
}
|
|
127
199
|
|
|
@@ -15,6 +15,69 @@ import type { RuntimeProbeResult, RuntimeRunOptions, StreamBlock } from "../type
|
|
|
15
15
|
const CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
16
16
|
/** Codex UUIDv7 / v4 session ids are 36-char dashed hex; reject anything else to keep argv safe. */
|
|
17
17
|
const CODEX_SESSION_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
18
|
+
const CODEX_FOREIGN_EXTRA_FLAGS_WITH_VALUE = new Set([
|
|
19
|
+
"--append-system-prompt",
|
|
20
|
+
"--permission-mode",
|
|
21
|
+
]);
|
|
22
|
+
const CODEX_SANDBOX_MODES = new Set(["read-only", "workspace-write", "danger-full-access"]);
|
|
23
|
+
|
|
24
|
+
function extraFlagName(arg: string): string {
|
|
25
|
+
if (!arg.startsWith("-")) return arg;
|
|
26
|
+
const eq = arg.indexOf("=");
|
|
27
|
+
return eq === -1 ? arg : arg.slice(0, eq);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function nextExtraValue(args: string[], index: number): string | undefined {
|
|
31
|
+
const next = args[index + 1];
|
|
32
|
+
if (typeof next !== "string") return undefined;
|
|
33
|
+
if (!next.startsWith("-")) return next;
|
|
34
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sanitizeCodexExtraArgs(extraArgs: string[] | undefined): string[] {
|
|
38
|
+
if (!extraArgs?.length) return [];
|
|
39
|
+
const out: string[] = [];
|
|
40
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
41
|
+
const arg = extraArgs[i];
|
|
42
|
+
const name = extraFlagName(arg);
|
|
43
|
+
if (CODEX_FOREIGN_EXTRA_FLAGS_WITH_VALUE.has(name)) {
|
|
44
|
+
if (!arg.includes("=") && nextExtraValue(extraArgs, i) !== undefined) i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (name === "-s" || name === "--sandbox") {
|
|
48
|
+
const value = arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : nextExtraValue(extraArgs, i);
|
|
49
|
+
if (!arg.includes("=") && value !== undefined) i += 1;
|
|
50
|
+
if (value && CODEX_SANDBOX_MODES.has(value)) {
|
|
51
|
+
out.push("-c", `sandbox_mode="${value}"`);
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === "--full-auto") {
|
|
56
|
+
out.push("--dangerously-bypass-approvals-and-sandbox");
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out.push(arg);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasCodexSandboxOverride(args: string[]): boolean {
|
|
65
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
66
|
+
const arg = args[i];
|
|
67
|
+
if (
|
|
68
|
+
arg === "--dangerously-bypass-approvals-and-sandbox" ||
|
|
69
|
+
arg.startsWith("-c sandbox_mode=") ||
|
|
70
|
+
arg.startsWith("-csandbox_mode=") ||
|
|
71
|
+
arg.startsWith("--config=sandbox_mode=")
|
|
72
|
+
) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if ((arg === "-c" || arg === "--config") && args[i + 1]?.startsWith("sandbox_mode=")) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
18
81
|
|
|
19
82
|
/** Resolve the Codex CLI executable via PATH or macOS desktop bundle. */
|
|
20
83
|
export function resolveCodexCommand(deps: ProbeDeps = {}): string | null {
|
|
@@ -167,6 +230,7 @@ export class CodexAdapter extends NdjsonStreamAdapter {
|
|
|
167
230
|
*/
|
|
168
231
|
protected buildArgs(opts: RuntimeRunOptions): string[] {
|
|
169
232
|
const tail: string[] = [];
|
|
233
|
+
const extraArgs = sanitizeCodexExtraArgs(opts.extraArgs);
|
|
170
234
|
|
|
171
235
|
// Sandbox / approval policy. Expressed as `-c` overrides because
|
|
172
236
|
// `codex exec resume` rejects `-s` / `--full-auto`. `-c` works on both
|
|
@@ -177,16 +241,7 @@ export class CodexAdapter extends NdjsonStreamAdapter {
|
|
|
177
241
|
// relay back to the user yet. Default to bypassing both approvals and the
|
|
178
242
|
// sandbox for every trust tier; operators who need a stricter posture can
|
|
179
243
|
// still override with route/defaultRoute extraArgs.
|
|
180
|
-
const hasSandboxOverride =
|
|
181
|
-
opts.extraArgs?.some(
|
|
182
|
-
(a) =>
|
|
183
|
-
a === "-s" ||
|
|
184
|
-
a.startsWith("--sandbox") ||
|
|
185
|
-
a === "--full-auto" ||
|
|
186
|
-
a === "--dangerously-bypass-approvals-and-sandbox" ||
|
|
187
|
-
a.startsWith("-c sandbox_mode=") ||
|
|
188
|
-
a.startsWith("-csandbox_mode="),
|
|
189
|
-
) ?? false;
|
|
244
|
+
const hasSandboxOverride = hasCodexSandboxOverride(extraArgs);
|
|
190
245
|
if (!hasSandboxOverride) {
|
|
191
246
|
tail.push(
|
|
192
247
|
"-c",
|
|
@@ -196,7 +251,7 @@ export class CodexAdapter extends NdjsonStreamAdapter {
|
|
|
196
251
|
);
|
|
197
252
|
}
|
|
198
253
|
tail.push("--skip-git-repo-check", "--json");
|
|
199
|
-
if (
|
|
254
|
+
if (extraArgs.length) tail.push(...extraArgs);
|
|
200
255
|
|
|
201
256
|
// `--` separates flags from positionals so a prompt starting with `-`
|
|
202
257
|
// can never be parsed as an option. `systemContext` is NOT prepended to
|
package/src/index.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
updateWorkingMemory,
|
|
58
58
|
DEFAULT_SECTION,
|
|
59
59
|
} from "./working-memory.js";
|
|
60
|
+
import { createDiagnosticBundle } from "./diagnostics.js";
|
|
60
61
|
import { resolveStartAuthAction } from "./start-auth.js";
|
|
61
62
|
import {
|
|
62
63
|
discoverLocalOpenclawGateways,
|
|
@@ -131,7 +132,9 @@ Commands:
|
|
|
131
132
|
route list
|
|
132
133
|
route remove --room <rm_xxx>|--prefix <rm_xxx>
|
|
133
134
|
config Print resolved config
|
|
134
|
-
doctor [--json]
|
|
135
|
+
doctor [--json] [--bundle] Scan local runtimes (${ADAPTER_LIST});
|
|
136
|
+
--bundle also writes a zip under
|
|
137
|
+
~/.botcord/diagnostics/
|
|
135
138
|
memory get [--agent <ag_xxx>] [--json] Show current working memory
|
|
136
139
|
memory set [--agent <ag_xxx>] --goal <text>
|
|
137
140
|
Pin/update the agent's work goal
|
|
@@ -164,6 +167,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
164
167
|
"f",
|
|
165
168
|
"follow",
|
|
166
169
|
"json",
|
|
170
|
+
"bundle",
|
|
167
171
|
"help",
|
|
168
172
|
"h",
|
|
169
173
|
"mentioned",
|
|
@@ -1342,6 +1346,18 @@ const fsFileReader: DoctorFileReader = {
|
|
|
1342
1346
|
};
|
|
1343
1347
|
|
|
1344
1348
|
async function cmdDoctor(args: ParsedArgs): Promise<void> {
|
|
1349
|
+
if (args.flags.bundle === true) {
|
|
1350
|
+
const bundle = await createDiagnosticBundle();
|
|
1351
|
+
if (args.flags.json === true) {
|
|
1352
|
+
console.log(JSON.stringify({ bundle }, null, 2));
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
console.log(`diagnostic bundle written: ${bundle.path}`);
|
|
1356
|
+
console.log(`size: ${bundle.sizeBytes} bytes`);
|
|
1357
|
+
console.log("Send this zip file to the BotCord developer/support contact.");
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1345
1361
|
const entries: import("./doctor.js").DoctorRuntimeEntry[] = detectRuntimes();
|
|
1346
1362
|
// Doctor should not hard-fail when no config exists yet; channel probes
|
|
1347
1363
|
// simply produce an empty list in that case.
|
package/src/provision.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type UpdateAgentParams,
|
|
29
29
|
} from "@botcord/protocol-core";
|
|
30
30
|
import type { Gateway } from "./gateway/index.js";
|
|
31
|
+
import type { GatewayInboundMessage } from "./gateway/index.js";
|
|
31
32
|
import type { PolicyResolverLike } from "./gateway/policy-resolver.js";
|
|
32
33
|
import type { PolicyUpdatedParams } from "@botcord/protocol-core";
|
|
33
34
|
import type {
|
|
@@ -398,6 +399,10 @@ export function createProvisioner(opts: ProvisionerOptions): (
|
|
|
398
399
|
return { ok: true, result };
|
|
399
400
|
}
|
|
400
401
|
|
|
402
|
+
case "wake_agent": {
|
|
403
|
+
return handleWakeAgent(gateway, frame.params);
|
|
404
|
+
}
|
|
405
|
+
|
|
401
406
|
default:
|
|
402
407
|
daemonLog.warn("provision.dispatch: unknown frame type", {
|
|
403
408
|
type: frame.type,
|
|
@@ -411,6 +416,87 @@ export function createProvisioner(opts: ProvisionerOptions): (
|
|
|
411
416
|
};
|
|
412
417
|
}
|
|
413
418
|
|
|
419
|
+
interface WakeAgentParams {
|
|
420
|
+
agent_id?: string;
|
|
421
|
+
agentId?: string;
|
|
422
|
+
message?: string;
|
|
423
|
+
run_id?: string;
|
|
424
|
+
runId?: string;
|
|
425
|
+
schedule_id?: string;
|
|
426
|
+
scheduleId?: string;
|
|
427
|
+
dedupe_key?: string;
|
|
428
|
+
dedupeKey?: string;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function handleWakeAgent(gateway: Gateway, raw: unknown): Promise<AckBody> {
|
|
432
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
433
|
+
return {
|
|
434
|
+
ok: false,
|
|
435
|
+
error: { code: "bad_params", message: "wake_agent params must be an object" },
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const params = raw as WakeAgentParams;
|
|
439
|
+
const agentId = params.agent_id || params.agentId;
|
|
440
|
+
const message = params.message;
|
|
441
|
+
if (!agentId || typeof agentId !== "string") {
|
|
442
|
+
return {
|
|
443
|
+
ok: false,
|
|
444
|
+
error: { code: "bad_params", message: "wake_agent requires params.agent_id" },
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (!message || typeof message !== "string") {
|
|
448
|
+
return {
|
|
449
|
+
ok: false,
|
|
450
|
+
error: { code: "bad_params", message: "wake_agent requires params.message" },
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const channels = gateway.snapshot().channels;
|
|
455
|
+
if (!channels[agentId]) {
|
|
456
|
+
return {
|
|
457
|
+
ok: false,
|
|
458
|
+
error: { code: "agent_not_loaded", message: `agent ${agentId} is not loaded in daemon gateway` },
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const runId = params.run_id || params.runId || `wake-${Date.now()}`;
|
|
463
|
+
const scheduleId = params.schedule_id || params.scheduleId;
|
|
464
|
+
const dedupeKey = params.dedupe_key || params.dedupeKey;
|
|
465
|
+
const conversationId = `rm_schedule_${agentId}`;
|
|
466
|
+
const msg: GatewayInboundMessage = {
|
|
467
|
+
id: runId,
|
|
468
|
+
channel: agentId,
|
|
469
|
+
accountId: agentId,
|
|
470
|
+
conversation: {
|
|
471
|
+
id: conversationId,
|
|
472
|
+
kind: "direct",
|
|
473
|
+
title: "BotCord Scheduler",
|
|
474
|
+
threadId: scheduleId ?? null,
|
|
475
|
+
},
|
|
476
|
+
sender: {
|
|
477
|
+
id: "hub",
|
|
478
|
+
name: "BotCord Scheduler",
|
|
479
|
+
kind: "system",
|
|
480
|
+
},
|
|
481
|
+
text: message,
|
|
482
|
+
raw: {
|
|
483
|
+
source_type: "botcord_schedule",
|
|
484
|
+
schedule_id: scheduleId,
|
|
485
|
+
run_id: runId,
|
|
486
|
+
dedupe_key: dedupeKey,
|
|
487
|
+
},
|
|
488
|
+
mentioned: true,
|
|
489
|
+
receivedAt: Date.now(),
|
|
490
|
+
trace: {
|
|
491
|
+
id: runId,
|
|
492
|
+
streamable: false,
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
await gateway.injectInbound(msg);
|
|
497
|
+
return { ok: true, result: { agent_id: agentId } };
|
|
498
|
+
}
|
|
499
|
+
|
|
414
500
|
// W8: hand-written runtime validator for the third-party gateway frame
|
|
415
501
|
// params. Rejects malformed payloads with a structured `bad_params` ack
|
|
416
502
|
// before they hit the per-handler logic, so an attacker can't smuggle a
|
package/src/working-memory.ts
CHANGED
|
@@ -307,6 +307,11 @@ export function buildWorkingMemoryPrompt(opts: {
|
|
|
307
307
|
"- sections: named buckets (contacts, pending_tasks, preferences, etc.).",
|
|
308
308
|
"- Updating one section never touches others. Empty content deletes a section.",
|
|
309
309
|
"",
|
|
310
|
+
"For cross-room work, update memory before or immediately after delegating:",
|
|
311
|
+
"- If you accept a request in one room and continue it in another, record a `pending_tasks` entry with source room id/name, target room id/name, requested deliverable, current status, and where to report completion.",
|
|
312
|
+
"- When a delegated room replies or delivers an artifact, consult `pending_tasks` before deciding `NO_REPLY`; if it matches a pending handoff, acknowledge, update status, and send the promised follow-up to the source room when appropriate.",
|
|
313
|
+
"- Remove or mark the entry done once the source room has been updated.",
|
|
314
|
+
"",
|
|
310
315
|
"Only update when something meaningful changes. Keep each section tight.",
|
|
311
316
|
];
|
|
312
317
|
|