clearclaw 0.7.0 → 0.8.0

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/index.js CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { execFileSync } from "node:child_process";
3
2
  import readline from "node:readline";
4
3
  import { Config } from "./config.js";
5
4
  import log, { initLogger } from "./logger.js";
6
- import { createEngineMap, ENGINE_NAMES, engineCommand } from "./engine/registry.js";
5
+ import { createEngineMap, ENGINE_NAMES, resolveEnginePath } from "./engine/registry.js";
7
6
  import { TelegramChannel } from "./channel/telegram.js";
8
7
  import { SlackChannel } from "./channel/slack.js";
9
8
  import { Orchestrator } from "./orchestrator.js";
@@ -41,9 +40,8 @@ async function runDaemon() {
41
40
  log.warn({ err }, "[pairing] failed to send pairing message");
42
41
  });
43
42
  };
44
- const channel = createChannel(config.channel, (userId) => config.isAuthorized(userId), onUnauthorizedDM);
45
- const enginePaths = Object.fromEntries(Object.entries(config.engines).map(([name, entry]) => [name, entry.path]));
46
- await new Orchestrator({ channel, engines: createEngineMap(enginePaths), config }).start();
43
+ const channel = createChannel(config.channel, (userId) => config.isAuthorized(userId), onUnauthorizedDM, () => config.listAuthorizedUserIds());
44
+ await new Orchestrator({ channel, engines: createEngineMap(config.engines), config }).start();
47
45
  }
48
46
  async function runApprove(args) {
49
47
  const code = args[0];
@@ -58,6 +56,8 @@ async function runApprove(args) {
58
56
  process.exit(1);
59
57
  }
60
58
  config.approveUser(pairing.userId, pairing.userName);
59
+ // Pairings only originate from a root DM, so this chat is home's destination.
60
+ config.connectHomeWorkspace(pairing.chatId);
61
61
  console.log(`Approved ${pairing.userName} (${pairing.userId})`);
62
62
  }
63
63
  const SETUP_TIMEOUT_MS = 5 * 60 * 1000;
@@ -89,6 +89,7 @@ async function runSetup() {
89
89
  channelConfig = { type: "telegram", botToken };
90
90
  }
91
91
  let engineName;
92
+ let resolvedPath;
92
93
  const engineList = ENGINE_NAMES.join("/");
93
94
  while (true) {
94
95
  engineName = (await ask(`Default engine [${engineList}]: `)).trim().toLowerCase();
@@ -96,23 +97,14 @@ async function runSetup() {
96
97
  console.log(` Please enter one of: ${engineList}`);
97
98
  continue;
98
99
  }
99
- const cmd = engineCommand(engineName);
100
- if (cmd) {
101
- try {
102
- execFileSync("which", [cmd], { stdio: "ignore" });
103
- }
104
- catch {
105
- console.log(` "${cmd}" not found on PATH. Install it first or choose another engine.`);
106
- continue;
107
- }
100
+ try {
101
+ resolvedPath = resolveEnginePath(engineName);
102
+ break;
103
+ }
104
+ catch (err) {
105
+ console.log(` ${err.message}`);
108
106
  }
109
- break;
110
107
  }
111
- // Resolve executable path for the chosen engine
112
- const cmd = engineCommand(engineName);
113
- const resolvedPath = cmd
114
- ? execFileSync("which", [cmd], { encoding: "utf-8" }).trim()
115
- : engineName; // SDK-based engines don't have a standalone binary
116
108
  const config = new Config();
117
109
  config.setChannel(channelConfig);
118
110
  config.setEngines([
@@ -120,11 +112,12 @@ async function runSetup() {
120
112
  ]);
121
113
  console.log("Saved to ~/.clearclaw/config.json\n");
122
114
  config.resolve();
115
+ config.ensureHomeWorkspace();
123
116
  let resolveFirstDM;
124
117
  const firstDM = new Promise((r) => {
125
118
  resolveFirstDM = r;
126
119
  });
127
- channel = createChannel(config.channel, (userId) => config.isAuthorized(userId), (chatId, user) => resolveFirstDM({ chatId, user }));
120
+ channel = createChannel(config.channel, (userId) => config.isAuthorized(userId), (chatId, user) => resolveFirstDM({ chatId, user }), () => config.listAuthorizedUserIds());
128
121
  console.log("Connecting...");
129
122
  await channel.connect();
130
123
  console.log("Connected! Waiting for a DM — message the bot from your chat app.\n");
@@ -148,6 +141,8 @@ async function runSetup() {
148
141
  return;
149
142
  }
150
143
  config.approveUser(pairing.userId, pairing.userName);
144
+ // Pairings only originate from a root DM, so this chat is home's destination.
145
+ config.connectHomeWorkspace(pairing.chatId);
151
146
  console.log(` Approved ${pairing.userName} (${pairing.userId})`);
152
147
  await channel.sendMessage(chatId, "You're approved! The bot is being set up — you'll be able to chat soon.");
153
148
  console.log("\nSetup complete! Start the daemon:\n clearclaw daemon\n");
@@ -158,9 +153,9 @@ async function runSetup() {
158
153
  rl.close();
159
154
  }
160
155
  }
161
- function createChannel(ch, isAuthorized, onUnauthorizedDM) {
156
+ function createChannel(ch, isAuthorized, onUnauthorizedDM, authorizedUserIds) {
162
157
  if (ch.type === "slack") {
163
- return new SlackChannel(ch.botToken, ch.appToken, isAuthorized, onUnauthorizedDM);
158
+ return new SlackChannel(ch.botToken, ch.appToken, isAuthorized, onUnauthorizedDM, authorizedUserIds);
164
159
  }
165
160
  return new TelegramChannel(ch.botToken, isAuthorized, onUnauthorizedDM);
166
161
  }
@@ -1,5 +1,5 @@
1
1
  import type { Config } from "./config.js";
2
- import type { Channel, Engine } from "./types.js";
2
+ import type { Channel, Engine, InboundMessage, MessageOrigin } from "./types.js";
3
3
  export interface OrchestratorOpts {
4
4
  channel: Channel;
5
5
  engines: Map<string, Engine>;
@@ -10,22 +10,49 @@ export declare class Orchestrator {
10
10
  private engines;
11
11
  private config;
12
12
  private chats;
13
- private tasks;
13
+ private creatingWorkspaces;
14
+ private scheduler;
15
+ private planUsage;
14
16
  constructor(opts: OrchestratorOpts);
15
17
  private chat;
16
18
  private engineFor;
17
19
  start(): Promise<void>;
18
20
  stop(): Promise<void>;
19
- /** Effective behavior: tasks→assistant, workspace→explicit setting or home→assistant / project→relay. */
21
+ /** Deliver a synthetic message to a named workspace and trigger its turn. */
22
+ deliverToWorkspace(workspaceName: string, origin: MessageOrigin, text: string): boolean;
23
+ /**
24
+ * Everything creation can reject without touching the platform. Runs before the
25
+ * confirmation, so the user is never asked to approve a doomed spawn, and again
26
+ * inside creation, where it is the authoritative check.
27
+ */
28
+ private validateNewWorkspace;
29
+ /** Shared creation path for standalone projects and peers, with optional manual binding. */
30
+ private createWorkspace;
31
+ /** Grouping is best-effort: it never fails a lifecycle operation, but it is never silent either. */
32
+ private groupChat;
33
+ private deliverInitialBrief;
34
+ /** An unbound chat can connect without starting a temporary agent or sharing home's session. */
35
+ private connectWorkspace;
36
+ private peerRuntime;
37
+ /** Effective behavior: Explicit setting or home→assistant / project→relay. */
20
38
  private effectiveBehavior;
21
- /** Enqueue a message and drain — immediately for relay, debounced for assistant/task. */
39
+ /** Permission mode a chat runs in until /mode picks one. */
40
+ private baseMode;
41
+ /** Enqueue a message and drain — immediately for relay, debounced for assistant. */
22
42
  private enqueueMessage;
23
43
  private scheduleDebounce;
24
44
  private processQueuedMessages;
25
45
  private executeTurn;
46
+ private persistSessionId;
47
+ /** Native selection works even when the workspace's engine cannot run. */
48
+ private selectEngine;
26
49
  private routeMessage;
27
50
  private routeEngineEvent;
51
+ private routeTextChunk;
52
+ private publishTextSegment;
53
+ private flushTextSegment;
28
54
  private buildMcpTools;
29
55
  private handlePermission;
30
56
  private updateStatusMessage;
31
57
  }
58
+ export declare function buildPrompt(messages: InboundMessage[]): string;