@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.
Files changed (93) hide show
  1. package/dist/agent-discovery.d.ts +7 -3
  2. package/dist/agent-discovery.js +9 -1
  3. package/dist/agent-workspace.d.ts +62 -0
  4. package/dist/agent-workspace.js +140 -10
  5. package/dist/config.d.ts +49 -1
  6. package/dist/config.js +57 -1
  7. package/dist/control-channel.d.ts +1 -4
  8. package/dist/control-channel.js +1 -4
  9. package/dist/daemon-config-map.d.ts +29 -12
  10. package/dist/daemon-config-map.js +105 -8
  11. package/dist/daemon.d.ts +2 -0
  12. package/dist/daemon.js +52 -5
  13. package/dist/doctor.d.ts +27 -1
  14. package/dist/doctor.js +22 -1
  15. package/dist/gateway/cli-resolver.d.ts +34 -0
  16. package/dist/gateway/cli-resolver.js +74 -0
  17. package/dist/gateway/dispatcher.d.ts +66 -1
  18. package/dist/gateway/dispatcher.js +583 -56
  19. package/dist/gateway/gateway.d.ts +29 -1
  20. package/dist/gateway/gateway.js +10 -0
  21. package/dist/gateway/index.d.ts +2 -0
  22. package/dist/gateway/index.js +2 -0
  23. package/dist/gateway/policy-resolver.d.ts +57 -0
  24. package/dist/gateway/policy-resolver.js +123 -0
  25. package/dist/gateway/runtimes/acp-stream.d.ts +99 -0
  26. package/dist/gateway/runtimes/acp-stream.js +394 -0
  27. package/dist/gateway/runtimes/codex.js +7 -0
  28. package/dist/gateway/runtimes/hermes-agent.d.ts +83 -0
  29. package/dist/gateway/runtimes/hermes-agent.js +180 -0
  30. package/dist/gateway/runtimes/ndjson-stream.d.ts +7 -2
  31. package/dist/gateway/runtimes/ndjson-stream.js +16 -3
  32. package/dist/gateway/runtimes/openclaw-acp.d.ts +44 -0
  33. package/dist/gateway/runtimes/openclaw-acp.js +500 -0
  34. package/dist/gateway/runtimes/registry.d.ts +4 -0
  35. package/dist/gateway/runtimes/registry.js +22 -0
  36. package/dist/gateway/transcript-paths.d.ts +30 -0
  37. package/dist/gateway/transcript-paths.js +114 -0
  38. package/dist/gateway/transcript.d.ts +123 -0
  39. package/dist/gateway/transcript.js +147 -0
  40. package/dist/gateway/types.d.ts +31 -0
  41. package/dist/index.js +286 -27
  42. package/dist/mention-scan.d.ts +22 -0
  43. package/dist/mention-scan.js +35 -0
  44. package/dist/provision.d.ts +73 -3
  45. package/dist/provision.js +373 -12
  46. package/dist/system-context.d.ts +5 -4
  47. package/dist/system-context.js +35 -5
  48. package/dist/turn-text.js +20 -1
  49. package/dist/url-utils.d.ts +9 -0
  50. package/dist/url-utils.js +18 -0
  51. package/dist/user-auth.js +0 -2
  52. package/dist/working-memory.js +1 -1
  53. package/package.json +2 -1
  54. package/src/__tests__/agent-workspace.test.ts +93 -0
  55. package/src/__tests__/daemon-config-map.test.ts +79 -0
  56. package/src/__tests__/openclaw-acp.test.ts +234 -0
  57. package/src/__tests__/policy-resolver.test.ts +124 -0
  58. package/src/__tests__/policy-updated-handler.test.ts +144 -0
  59. package/src/__tests__/provision.test.ts +160 -0
  60. package/src/__tests__/system-context.test.ts +52 -0
  61. package/src/__tests__/url-utils.test.ts +37 -0
  62. package/src/agent-discovery.ts +12 -4
  63. package/src/agent-workspace.ts +173 -9
  64. package/src/config.ts +132 -4
  65. package/src/control-channel.ts +1 -4
  66. package/src/daemon-config-map.ts +156 -12
  67. package/src/daemon.ts +66 -5
  68. package/src/doctor.ts +49 -2
  69. package/src/gateway/__tests__/dispatcher.test.ts +440 -2
  70. package/src/gateway/__tests__/hermes-agent-adapter.test.ts +302 -0
  71. package/src/gateway/__tests__/transcript.test.ts +496 -0
  72. package/src/gateway/cli-resolver.ts +92 -0
  73. package/src/gateway/dispatcher.ts +681 -58
  74. package/src/gateway/gateway.ts +46 -0
  75. package/src/gateway/index.ts +25 -0
  76. package/src/gateway/policy-resolver.ts +171 -0
  77. package/src/gateway/runtimes/acp-stream.ts +535 -0
  78. package/src/gateway/runtimes/codex.ts +7 -0
  79. package/src/gateway/runtimes/hermes-agent.ts +206 -0
  80. package/src/gateway/runtimes/ndjson-stream.ts +16 -3
  81. package/src/gateway/runtimes/openclaw-acp.ts +606 -0
  82. package/src/gateway/runtimes/registry.ts +24 -0
  83. package/src/gateway/transcript-paths.ts +145 -0
  84. package/src/gateway/transcript.ts +300 -0
  85. package/src/gateway/types.ts +32 -0
  86. package/src/index.ts +295 -30
  87. package/src/mention-scan.ts +38 -0
  88. package/src/provision.ts +446 -20
  89. package/src/system-context.ts +41 -9
  90. package/src/turn-text.ts +22 -1
  91. package/src/url-utils.ts +17 -0
  92. package/src/user-auth.ts +0 -2
  93. package/src/working-memory.ts +1 -1
@@ -6,10 +6,11 @@
6
6
  * `RuntimeRunOptions.systemContext`. This module composes the daemon's
7
7
  * system-context string from:
8
8
  *
9
- * 1. `[BotCord Scene: Owner Chat]` (owner-trust turns only)
10
- * 2. `[BotCord Working Memory]`
11
- * 3. `[BotCord Room Context]` (group rooms, via optional async fetcher)
12
- * 4. `[BotCord Cross-Room Awareness]` (optional activity tracker)
9
+ * 1. `[BotCord Identity]` (read fresh from workspace/identity.md each turn)
10
+ * 2. `[BotCord Scene: Owner Chat]` (owner-trust turns only)
11
+ * 3. `[BotCord Working Memory]`
12
+ * 4. `[BotCord Room Context]` (group rooms, via optional async fetcher)
13
+ * 5. `[BotCord Cross-Room Awareness]` (optional activity tracker)
13
14
  *
14
15
  * Behavior:
15
16
  * - Working memory is loaded fresh per turn, so a `memory set` from another
@@ -26,6 +27,7 @@ import type { GatewayInboundMessage, SystemContextBuilder } from "./gateway/inde
26
27
  import type { ActivityTracker } from "./activity-tracker.js";
27
28
  import { buildCrossRoomDigest } from "./cross-room.js";
28
29
  import { buildWorkingMemoryPrompt, readWorkingMemory } from "./working-memory.js";
30
+ import { readIdentity } from "./agent-workspace.js";
29
31
  import { classifyActivitySender } from "./sender-classify.js";
30
32
  import { log } from "./log.js";
31
33
 
@@ -86,6 +88,31 @@ function safeReadWorkingMemory(agentId: string) {
86
88
  }
87
89
  }
88
90
 
91
+ /**
92
+ * Read identity.md and wrap it as a system-context block. Placed before
93
+ * every other block so the agent answers "who are you" from this file
94
+ * rather than from the underlying CLI's default persona ("I am Claude
95
+ * Code"). Re-read every turn so dashboard reconcile (`applyAgentIdentity`)
96
+ * and self-edits take effect immediately, mirroring working-memory
97
+ * semantics.
98
+ */
99
+ function buildIdentityPrompt(agentId: string): string | null {
100
+ let raw: string | null = null;
101
+ try {
102
+ raw = readIdentity(agentId);
103
+ } catch (err) {
104
+ log.warn("identity read failed", { agentId, err: String(err) });
105
+ return null;
106
+ }
107
+ if (!raw) return null;
108
+ return [
109
+ "[BotCord Identity]",
110
+ "Your persistent identity card. The fields below are the source of truth — when asked who you are, what you do, or what you will / will not do, answer from this block, not from the underlying CLI's default persona.",
111
+ "",
112
+ raw.trim(),
113
+ ].join("\n");
114
+ }
115
+
89
116
  /**
90
117
  * Build a {@link SystemContextBuilder} for the gateway dispatcher.
91
118
  *
@@ -97,10 +124,13 @@ export function createDaemonSystemContextBuilder(
97
124
  deps: SystemContextDeps,
98
125
  ): (message: GatewayInboundMessage) => Promise<string | undefined> | string | undefined {
99
126
  const gatherSyncBlocks = (message: GatewayInboundMessage): {
127
+ identity: string | null;
100
128
  ownerScene: string | null;
101
129
  memory: string | null;
102
130
  digest: string | null;
103
131
  } => {
132
+ const identity = buildIdentityPrompt(deps.agentId);
133
+
104
134
  const ownerScene =
105
135
  classifyActivitySender(message).kind === "owner"
106
136
  ? buildOwnerChatSceneContext()
@@ -118,7 +148,7 @@ export function createDaemonSystemContextBuilder(
118
148
  }) || null
119
149
  : null;
120
150
 
121
- return { ownerScene, memory, digest };
151
+ return { identity, ownerScene, memory, digest };
122
152
  };
123
153
 
124
154
  const assemble = (parts: Array<string | null | undefined>): string | undefined => {
@@ -144,11 +174,12 @@ export function createDaemonSystemContextBuilder(
144
174
 
145
175
  if (!deps.roomContextBuilder) {
146
176
  const syncBuilder = (message: GatewayInboundMessage): string | undefined => {
147
- const { ownerScene, memory, digest } = gatherSyncBlocks(message);
177
+ const { identity, ownerScene, memory, digest } = gatherSyncBlocks(message);
148
178
  // Loop-risk sits at the end so its "reply NO_REPLY unless…" guidance
149
179
  // is the last thing the model sees before the user turn body.
180
+ // Identity sits at the very front so it frames every other block.
150
181
  const loopRisk = runLoopRisk(message);
151
- return assemble([ownerScene, memory, digest, loopRisk]);
182
+ return assemble([identity, ownerScene, memory, digest, loopRisk]);
152
183
  };
153
184
  // Compile-time witness that the narrower sync signature still satisfies
154
185
  // `SystemContextBuilder` (which allows async). Prevents the two contracts
@@ -162,11 +193,12 @@ export function createDaemonSystemContextBuilder(
162
193
  const asyncBuilder = async (
163
194
  message: GatewayInboundMessage,
164
195
  ): Promise<string | undefined> => {
165
- const { ownerScene, memory, digest } = gatherSyncBlocks(message);
196
+ const { identity, ownerScene, memory, digest } = gatherSyncBlocks(message);
166
197
  // Room context landing order: after owner-scene / memory, before digest —
167
198
  // "what room am I in" belongs with the session's own identity, while the
168
199
  // cross-room digest deliberately describes OTHER rooms and should stay
169
200
  // last so it doesn't get confused with the current room.
201
+ // Identity stays at the very front; see syncBuilder for rationale.
170
202
  let roomBlock: string | null = null;
171
203
  try {
172
204
  roomBlock = await roomBuilder(message);
@@ -178,7 +210,7 @@ export function createDaemonSystemContextBuilder(
178
210
  });
179
211
  }
180
212
  const loopRisk = runLoopRisk(message);
181
- return assemble([ownerScene, memory, roomBlock, digest, loopRisk]);
213
+ return assemble([identity, ownerScene, memory, roomBlock, digest, loopRisk]);
182
214
  };
183
215
  const _typecheck: SystemContextBuilder = asyncBuilder;
184
216
  void _typecheck;
package/src/turn-text.ts CHANGED
@@ -34,6 +34,18 @@ const DIRECT_HINT =
34
34
  '[If the conversation has naturally concluded or no response is needed, ' +
35
35
  'reply with exactly "NO_REPLY" and nothing else.]';
36
36
 
37
+ /**
38
+ * Reminder appended to every wrapped (non-owner-chat) inbound message. The
39
+ * dispatcher discards `result.text` for any room that is not `rm_oc_*`, so
40
+ * the agent must call the `botcord_send` tool (or the `botcord send` CLI
41
+ * via Bash) to actually deliver a reply. Plain assistant text in those
42
+ * rooms is logged and dropped.
43
+ */
44
+ const NON_OWNER_REPLY_HINT =
45
+ "[This room is NOT owner-chat. Plain text output WILL NOT be sent. " +
46
+ "To reply, call the `botcord_send` tool, or run " +
47
+ '`botcord send --room <room_id> --text "..."` via Bash.]';
48
+
37
49
  /**
38
50
  * Read the BotCord envelope type from a raw inbound message. Returns
39
51
  * `undefined` when the message didn't come from the BotCord channel or the
@@ -169,6 +181,8 @@ export function composeBotCordUserTurn(msg: GatewayInboundMessage): string {
169
181
  `</${tag}>`,
170
182
  "",
171
183
  hint,
184
+ "",
185
+ NON_OWNER_REPLY_HINT,
172
186
  ];
173
187
  if (contactRequestHint) {
174
188
  lines.push("", contactRequestHint);
@@ -223,7 +237,14 @@ function composeBatchedTurn(
223
237
  }
224
238
 
225
239
  const hint = isGroup ? GROUP_HINT : DIRECT_HINT;
226
- const lines: string[] = [header.join(" | "), blocks.join("\n"), "", hint];
240
+ const lines: string[] = [
241
+ header.join(" | "),
242
+ blocks.join("\n"),
243
+ "",
244
+ hint,
245
+ "",
246
+ NON_OWNER_REPLY_HINT,
247
+ ];
227
248
 
228
249
  if (contactRequestSenders.length > 0) {
229
250
  // Dedup + list — multiple distinct senders show as "A, B".
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Append a `next` query param to a URL. Used by the device-code flow to
3
+ * encode a post-auth redirect target into the Hub-issued verification URL,
4
+ * so the dashboard knows where to send the user after they click Authorize.
5
+ *
6
+ * Falls back to returning the original URL string if parsing fails — the
7
+ * device-code flow keeps working, just without the redirect convenience.
8
+ */
9
+ export function appendNextParam(url: string, next: string): string {
10
+ try {
11
+ const u = new URL(url);
12
+ u.searchParams.set("next", next);
13
+ return u.toString();
14
+ } catch {
15
+ return url;
16
+ }
17
+ }
package/src/user-auth.ts CHANGED
@@ -5,8 +5,6 @@
5
5
  * `~/.botcord/credentials/*.json`), the user-auth record is singular —
6
6
  * the daemon only logs in as *one* user at a time. Stored at
7
7
  * `~/.botcord/daemon/user-auth.json` with `0600` permissions.
8
- *
9
- * See `docs/daemon-control-plane-plan.md` §6–§7.
10
8
  */
11
9
  import {
12
10
  chmodSync,
@@ -2,7 +2,7 @@
2
2
  * Working memory — persistent, account-scoped notes injected into every turn.
3
3
  *
4
4
  * Stored at `~/.botcord/agents/{agentId}/state/working-memory.json` (the
5
- * per-agent state dir owned by the daemon; see docs/daemon-agent-workspace-plan.md §8).
5
+ * per-agent state dir owned by the daemon).
6
6
  *
7
7
  * Ported from plugin/src/memory.ts (dropping workspace + OpenClaw runtime
8
8
  * branches) and plugin/src/memory-protocol.ts (prompt builder).