clawmini 0.0.2 → 0.0.3

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 (102) hide show
  1. package/.github/workflows/ci.yml +59 -0
  2. package/README.md +4 -2
  3. package/dist/adapter-discord/index.d.mts.map +1 -1
  4. package/dist/adapter-discord/index.mjs +13 -4
  5. package/dist/adapter-discord/index.mjs.map +1 -1
  6. package/dist/cli/index.mjs +7 -6
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/lite.mjs +16 -10
  9. package/dist/cli/lite.mjs.map +1 -1
  10. package/dist/daemon/index.mjs +590 -401
  11. package/dist/daemon/index.mjs.map +1 -1
  12. package/dist/{fetch-BjZVyU3Z.mjs → fetch-Cn1XNyiO.mjs} +1 -1
  13. package/dist/{fetch-BjZVyU3Z.mjs.map → fetch-Cn1XNyiO.mjs.map} +1 -1
  14. package/dist/lite-oSYSvaOr.mjs +164 -0
  15. package/dist/lite-oSYSvaOr.mjs.map +1 -0
  16. package/dist/web/_app/immutable/chunks/{CAZeqksE.js → 8YNcRyEk.js} +1 -1
  17. package/dist/web/_app/immutable/chunks/{B3YcEpQV.js → DQoygso7.js} +1 -1
  18. package/dist/web/_app/immutable/entry/{app.ZuicLpkH.js → app.DO5eYwVz.js} +2 -2
  19. package/dist/web/_app/immutable/entry/start.D48mVn1m.js +1 -0
  20. package/dist/web/_app/immutable/nodes/{0.BB1CjKco.js → 0.B-0CcADM.js} +1 -1
  21. package/dist/web/_app/immutable/nodes/{1.CdSgEHu9.js → 1.FixKgvRO.js} +1 -1
  22. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.CKp7Wkn8.js → dist/web/_app/immutable/nodes/3.ncP0xLO6.js} +1 -1
  23. package/dist/web/_app/immutable/nodes/{4.FyeoMY-Y.js → 4.CQYJEgv8.js} +1 -1
  24. package/dist/web/_app/immutable/nodes/{5.D6mVN7l7.js → 5.BpJUN6QH.js} +1 -1
  25. package/dist/web/_app/version.json +1 -1
  26. package/dist/web/index.html +6 -6
  27. package/dist/{workspace-BC1ahx4R.mjs → workspace-DjoNjhW0.mjs} +12 -42
  28. package/dist/workspace-DjoNjhW0.mjs.map +1 -0
  29. package/docs/15_lite_fetch_pending/development_log.md +31 -0
  30. package/docs/15_lite_fetch_pending/notes.md +48 -0
  31. package/docs/15_lite_fetch_pending/prd.md +39 -0
  32. package/docs/15_lite_fetch_pending/questions.md +3 -0
  33. package/docs/15_lite_fetch_pending/tickets.md +42 -0
  34. package/docs/CHECKS.md +2 -2
  35. package/eslint.config.js +12 -0
  36. package/package.json +3 -2
  37. package/src/adapter-discord/client.ts +1 -1
  38. package/src/adapter-discord/index.ts +22 -5
  39. package/src/cli/client.ts +8 -3
  40. package/src/cli/e2e/adapter-discord.test.ts +2 -2
  41. package/src/cli/e2e/daemon.test.ts +2 -1
  42. package/src/cli/e2e/export-lite-func.test.ts +41 -13
  43. package/src/cli/e2e/fallbacks.test.ts +4 -0
  44. package/src/cli/lite.ts +24 -6
  45. package/src/daemon/api/agent-router.ts +191 -0
  46. package/src/daemon/{router.test.ts → api/index.test.ts} +101 -34
  47. package/src/daemon/api/index.ts +4 -0
  48. package/src/daemon/{router-policy-request.test.ts → api/policy-request.test.ts} +27 -13
  49. package/src/daemon/api/router-utils.ts +159 -0
  50. package/src/daemon/api/trpc.ts +30 -0
  51. package/src/daemon/api/user-router.ts +221 -0
  52. package/src/daemon/index.ts +3 -3
  53. package/src/daemon/message-interruption.test.ts +17 -10
  54. package/src/daemon/message-typing.test.ts +1 -1
  55. package/src/daemon/message.ts +260 -239
  56. package/src/daemon/observation.test.ts +1 -1
  57. package/src/daemon/queue.test.ts +28 -0
  58. package/src/daemon/queue.ts +30 -15
  59. package/src/daemon/request-store.test.ts +4 -4
  60. package/src/daemon/request-store.ts +3 -1
  61. package/src/shared/workspace.ts +4 -5
  62. package/templates/debug/settings.json +5 -0
  63. package/templates/environments/macos/env.json +1 -1
  64. package/templates/environments/macos-proxy/env.json +1 -1
  65. package/templates/gemini-claw/.gemini/hooks/insert-pending.sh +9 -0
  66. package/templates/gemini-claw/.gemini/settings.json +14 -1
  67. package/templates/gemini-claw/.gemini/system.md +2 -0
  68. package/web/.svelte-kit/generated/server/internal.js +1 -1
  69. package/web/.svelte-kit/output/client/.vite/manifest.json +26 -26
  70. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CAZeqksE.js → 8YNcRyEk.js} +1 -1
  71. package/web/.svelte-kit/output/client/_app/immutable/chunks/{B3YcEpQV.js → DQoygso7.js} +1 -1
  72. package/web/.svelte-kit/output/client/_app/immutable/entry/{app.ZuicLpkH.js → app.DO5eYwVz.js} +2 -2
  73. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +1 -0
  74. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.BB1CjKco.js → 0.B-0CcADM.js} +1 -1
  75. package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.CdSgEHu9.js → 1.FixKgvRO.js} +1 -1
  76. package/{dist/web/_app/immutable/nodes/3.CKp7Wkn8.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.ncP0xLO6.js} +1 -1
  77. package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.FyeoMY-Y.js → 4.CQYJEgv8.js} +1 -1
  78. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.D6mVN7l7.js → 5.BpJUN6QH.js} +1 -1
  79. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  80. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  81. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  82. package/web/.svelte-kit/output/server/manifest.js +1 -1
  83. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  84. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  85. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  86. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  87. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  88. package/dist/chats-BcbxvPlj.mjs +0 -29
  89. package/dist/chats-BcbxvPlj.mjs.map +0 -1
  90. package/dist/chats-CpRQrNHj.mjs +0 -91
  91. package/dist/chats-CpRQrNHj.mjs.map +0 -1
  92. package/dist/fs-B5wW0oaH.mjs +0 -14
  93. package/dist/fs-B5wW0oaH.mjs.map +0 -1
  94. package/dist/lite-DBUuHsX0.mjs +0 -80
  95. package/dist/lite-DBUuHsX0.mjs.map +0 -1
  96. package/dist/policy-utils-BvfOK6Ih.mjs +0 -114
  97. package/dist/policy-utils-BvfOK6Ih.mjs.map +0 -1
  98. package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
  99. package/dist/web/_app/immutable/entry/start.DuQwh4Nz.js +0 -1
  100. package/dist/workspace-BC1ahx4R.mjs.map +0 -1
  101. package/src/daemon/router.ts +0 -510
  102. package/web/.svelte-kit/output/client/_app/immutable/entry/start.DuQwh4Nz.js +0 -1
@@ -1,10 +1,6 @@
1
- import { C as CronJobSchema, _ as readSettings, a as getAgent, c as getSettingsPath, g as readPolicies, h as readEnvironment, i as getActiveEnvironmentInfo, l as getSocketPath, m as readChatSettings, o as getClawminiDir, p as readAgentSessionSettings, s as getEnvironmentPath, u as getWorkspaceRoot, w as SettingsSchema, x as writeChatSettings, y as writeAgentSessionSettings } from "../workspace-BC1ahx4R.mjs";
2
- import { n as pathIsInsideDir } from "../fs-B5wW0oaH.mjs";
3
- import { l as listChats, o as getDefaultChatId, s as getMessages } from "../chats-CpRQrNHj.mjs";
4
- import { n as exportLiteToEnvironment } from "../lite-DBUuHsX0.mjs";
5
- import { a as daemonEvents, i as DAEMON_EVENT_TYPING, o as emitTyping, r as DAEMON_EVENT_MESSAGE_APPENDED, t as appendMessage } from "../chats-BcbxvPlj.mjs";
6
- import { n as executeSafe, r as interpolateArgs, t as createSnapshot } from "../policy-utils-BvfOK6Ih.mjs";
7
- import fs from "node:fs";
1
+ import { C as CronJobSchema, S as pathIsInsideDir, _ as readSettings, a as getAgent, b as writeChatSettings, c as getSettingsPath, g as readPolicies, h as readEnvironment, i as getActiveEnvironmentInfo, l as getSocketPath, m as readChatSettings, o as getClawminiDir, p as readAgentSessionSettings, s as getEnvironmentPath, u as getWorkspaceRoot, v as writeAgentSessionSettings, w as SettingsSchema } from "../workspace-DjoNjhW0.mjs";
2
+ import { d as getMessages$1, n as exportLiteToEnvironment, o as appendMessage$1, p as listChats, u as getDefaultChatId } from "../lite-oSYSvaOr.mjs";
3
+ import fs, { constants } from "node:fs";
8
4
  import path from "node:path";
9
5
  import { execSync, spawn } from "node:child_process";
10
6
  import fs$1 from "node:fs/promises";
@@ -13,126 +9,52 @@ import http from "node:http";
13
9
  import net from "node:net";
14
10
  import { createHTTPHandler } from "@trpc/server/adapters/standalone";
15
11
  import { TRPCError, initTRPC } from "@trpc/server";
12
+ import schedule from "node-schedule";
13
+ import { EventEmitter, on } from "node:events";
14
+ import crypto$1, { randomBytes, randomUUID } from "node:crypto";
16
15
  import fs$2 from "fs/promises";
17
16
  import path$1 from "path";
18
17
  import { randomInt } from "crypto";
19
- import crypto$1, { randomUUID } from "node:crypto";
20
- import schedule from "node-schedule";
21
18
 
22
- //#region src/daemon/request-store.ts
23
- const PolicyRequestSchema = z.object({
24
- id: z.string(),
25
- commandName: z.string(),
26
- args: z.array(z.string()),
27
- fileMappings: z.record(z.string(), z.string()),
28
- state: z.enum([
29
- "Pending",
30
- "Approved",
31
- "Rejected"
32
- ]),
33
- createdAt: z.number(),
34
- rejectionReason: z.string().optional(),
35
- chatId: z.string(),
36
- agentId: z.string()
37
- });
38
- function isENOENT(err) {
39
- return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
40
- }
41
- var RequestStore = class {
42
- baseDir;
43
- constructor(startDir = process.cwd()) {
44
- this.baseDir = path$1.join(getClawminiDir(startDir), "tmp", "requests");
45
- }
46
- async init() {
47
- await fs$2.mkdir(this.baseDir, { recursive: true });
48
- }
49
- getFilePath(id) {
50
- return path$1.join(this.baseDir, `${id}.json`);
51
- }
52
- async save(request) {
53
- await this.init();
54
- const filePath = this.getFilePath(request.id);
55
- await fs$2.writeFile(filePath, JSON.stringify(request, null, 2), "utf8");
56
- }
57
- async load(id) {
58
- const normalizedId = normalizePolicyId(id);
59
- const filePath = this.getFilePath(normalizedId);
60
- try {
61
- const data = await fs$2.readFile(filePath, "utf8");
62
- return PolicyRequestSchema.parse(JSON.parse(data));
63
- } catch (err) {
64
- if (isENOENT(err)) return null;
65
- const msg = err instanceof Error ? err.message : String(err);
66
- console.warn(`Failed to parse request file ${filePath}:`, msg);
67
- return null;
68
- }
69
- }
70
- async list() {
71
- await this.init();
72
- const requests = [];
73
- try {
74
- const files = await fs$2.readdir(this.baseDir);
75
- for (const file of files) {
76
- if (!file.endsWith(".json")) continue;
77
- const id = path$1.basename(file, ".json");
78
- const req = await this.load(id);
79
- if (req) requests.push(req);
80
- }
81
- } catch (err) {
82
- if (!isENOENT(err)) throw err;
83
- }
84
- return requests.sort((a, b) => b.createdAt - a.createdAt);
19
+ //#region src/daemon/api/trpc.ts
20
+ const t = initTRPC.context().create();
21
+ const router = t.router;
22
+ const publicProcedure = t.procedure;
23
+ const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
24
+ if (ctx.isApiServer) {
25
+ if (!ctx.tokenPayload) throw new TRPCError({
26
+ code: "UNAUTHORIZED",
27
+ message: "Missing or invalid token"
28
+ });
85
29
  }
86
- };
87
- function generateRandomAlphaNumericString(length) {
88
- const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
89
- let result = "";
90
- for (let i = 0; i < length; i++) result += characters[Math.floor(randomInt(36))];
91
- return result;
30
+ return next({ ctx: {
31
+ ...ctx,
32
+ tokenPayload: ctx.tokenPayload
33
+ } });
34
+ });
35
+ const apiProcedure = t.procedure.use(apiAuthMiddleware);
36
+
37
+ //#endregion
38
+ //#region src/daemon/events.ts
39
+ const daemonEvents = new EventEmitter();
40
+ const DAEMON_EVENT_MESSAGE_APPENDED = "message-appended";
41
+ const DAEMON_EVENT_TYPING = "typing";
42
+ function emitMessageAppended(chatId, message) {
43
+ daemonEvents.emit(DAEMON_EVENT_MESSAGE_APPENDED, {
44
+ chatId,
45
+ message
46
+ });
92
47
  }
93
- function normalizePolicyId(id) {
94
- return id.toLocaleUpperCase().trim();
48
+ function emitTyping(chatId) {
49
+ daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId });
95
50
  }
96
51
 
97
52
  //#endregion
98
- //#region src/daemon/policy-request-service.ts
99
- var PolicyRequestService = class {
100
- store;
101
- maxPending;
102
- agentDir;
103
- snapshotDir;
104
- constructor(store, agentDir, snapshotDir, maxPending = 100) {
105
- this.store = store;
106
- this.agentDir = agentDir;
107
- this.snapshotDir = snapshotDir;
108
- this.maxPending = maxPending;
109
- }
110
- async createRequest(commandName, args, fileMappings, chatId, agentId) {
111
- const allRequests = await this.store.list();
112
- if (allRequests.filter((r) => r.state === "Pending").length >= this.maxPending) throw new Error(`Maximum number of pending requests (${this.maxPending}) reached.`);
113
- const snapshotMappings = {};
114
- for (const [key, requestedPath] of Object.entries(fileMappings)) snapshotMappings[key] = await createSnapshot(requestedPath, this.agentDir, this.snapshotDir);
115
- let id = "";
116
- do
117
- id = generateRandomAlphaNumericString(3);
118
- while (allRequests.some((r) => r.id === id));
119
- const request = {
120
- id,
121
- commandName,
122
- args,
123
- fileMappings: snapshotMappings,
124
- state: "Pending",
125
- createdAt: Date.now(),
126
- chatId,
127
- agentId
128
- };
129
- await this.store.save(request);
130
- return request;
131
- }
132
- getInterpolatedArgs(request) {
133
- return interpolateArgs(request.args, request.fileMappings);
134
- }
135
- };
53
+ //#region src/daemon/chats.ts
54
+ async function appendMessage(id, message, startDir = process.cwd()) {
55
+ await appendMessage$1(id, message, startDir);
56
+ emitMessageAppended(id, message);
57
+ }
136
58
 
137
59
  //#endregion
138
60
  //#region src/daemon/queue.ts
@@ -170,35 +92,38 @@ var Queue = class {
170
92
  this.processNext().catch(() => {});
171
93
  }
172
94
  }
173
- abortCurrent() {
95
+ abortCurrent(predicate) {
174
96
  if (this.currentController) {
175
- const error = /* @__PURE__ */ new Error("Task aborted");
176
- error.name = "AbortError";
177
- this.currentController.abort(error);
97
+ if (!predicate || this.currentPayload !== void 0 && predicate(this.currentPayload)) {
98
+ const error = /* @__PURE__ */ new Error("Task aborted");
99
+ error.name = "AbortError";
100
+ this.currentController.abort(error);
101
+ }
178
102
  }
179
103
  }
180
104
  getCurrentPayload() {
181
105
  return this.currentPayload;
182
106
  }
183
- clear(reason = "Task cleared") {
184
- const tasksToClear = [...this.pending];
185
- this.pending = [];
107
+ clear(reason = "Task cleared", predicate) {
108
+ const tasksToClear = predicate ? this.pending.filter((p) => p.payload !== void 0 && predicate(p.payload)) : [...this.pending];
109
+ if (predicate) this.pending = this.pending.filter((p) => !(p.payload !== void 0 && predicate(p.payload)));
110
+ else this.pending = [];
186
111
  for (const { reject } of tasksToClear) {
187
112
  const error = new Error(reason);
188
113
  error.name = "AbortError";
189
114
  reject(error);
190
115
  }
191
116
  }
192
- extractPending() {
193
- const extracted = this.pending.map((p) => p.payload).filter((p) => p !== void 0);
194
- this.clear("Task extracted for batching");
117
+ extractPending(predicate) {
118
+ const extracted = this.pending.map((p) => p.payload).filter((p) => p !== void 0 && (!predicate || predicate(p)));
119
+ this.clear("Task extracted for batching", predicate);
195
120
  return extracted;
196
121
  }
197
122
  };
198
- const directoryQueues = /* @__PURE__ */ new Map();
199
- function getQueue(dir) {
200
- if (!directoryQueues.has(dir)) directoryQueues.set(dir, new Queue());
201
- return directoryQueues.get(dir);
123
+ const messageQueues = /* @__PURE__ */ new Map();
124
+ function getMessageQueue(dir) {
125
+ if (!messageQueues.has(dir)) messageQueues.set(dir, new Queue());
126
+ return messageQueues.get(dir);
202
127
  }
203
128
 
204
129
  //#endregion
@@ -274,6 +199,181 @@ const slashStop = createSlashActionRouter("stop", "stop", "Stopping current task
274
199
  //#region src/daemon/routers/slash-interrupt.ts
275
200
  const slashInterrupt = createSlashActionRouter("interrupt", "interrupt", "Interrupting current task...");
276
201
 
202
+ //#endregion
203
+ //#region src/daemon/request-store.ts
204
+ const PolicyRequestSchema = z.object({
205
+ id: z.string(),
206
+ commandName: z.string(),
207
+ args: z.array(z.string()),
208
+ fileMappings: z.record(z.string(), z.string()),
209
+ state: z.enum([
210
+ "Pending",
211
+ "Approved",
212
+ "Rejected"
213
+ ]),
214
+ createdAt: z.number(),
215
+ rejectionReason: z.string().optional(),
216
+ chatId: z.string(),
217
+ agentId: z.string()
218
+ });
219
+ function isENOENT(err) {
220
+ return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
221
+ }
222
+ var RequestStore = class {
223
+ baseDir;
224
+ constructor(startDir = process.cwd()) {
225
+ this.baseDir = path$1.join(getClawminiDir(startDir), "tmp", "requests");
226
+ }
227
+ async init() {
228
+ await fs$2.mkdir(this.baseDir, { recursive: true });
229
+ }
230
+ getFilePath(id) {
231
+ return path$1.join(this.baseDir, `${id}.json`);
232
+ }
233
+ async save(request) {
234
+ await this.init();
235
+ const normalizedId = normalizePolicyId(request.id);
236
+ request.id = normalizedId;
237
+ const filePath = this.getFilePath(normalizedId);
238
+ await fs$2.writeFile(filePath, JSON.stringify(request, null, 2), "utf8");
239
+ }
240
+ async load(id) {
241
+ const normalizedId = normalizePolicyId(id);
242
+ const filePath = this.getFilePath(normalizedId);
243
+ try {
244
+ const data = await fs$2.readFile(filePath, "utf8");
245
+ return PolicyRequestSchema.parse(JSON.parse(data));
246
+ } catch (err) {
247
+ if (isENOENT(err)) return null;
248
+ const msg = err instanceof Error ? err.message : String(err);
249
+ console.warn(`Failed to parse request file ${filePath}:`, msg);
250
+ return null;
251
+ }
252
+ }
253
+ async list() {
254
+ await this.init();
255
+ const requests = [];
256
+ try {
257
+ const files = await fs$2.readdir(this.baseDir);
258
+ for (const file of files) {
259
+ if (!file.endsWith(".json")) continue;
260
+ const id = path$1.basename(file, ".json");
261
+ const req = await this.load(id);
262
+ if (req) requests.push(req);
263
+ }
264
+ } catch (err) {
265
+ if (!isENOENT(err)) throw err;
266
+ }
267
+ return requests.sort((a, b) => b.createdAt - a.createdAt);
268
+ }
269
+ };
270
+ function generateRandomAlphaNumericString(length) {
271
+ const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
272
+ let result = "";
273
+ for (let i = 0; i < length; i++) result += characters[Math.floor(randomInt(36))];
274
+ return result;
275
+ }
276
+ function normalizePolicyId(id) {
277
+ return id.toLocaleUpperCase().trim();
278
+ }
279
+
280
+ //#endregion
281
+ //#region src/daemon/policy-utils.ts
282
+ const MAX_SNAPSHOT_SIZE = 5 * 1024 * 1024;
283
+ async function createSnapshot(requestedPath, agentDir, snapshotDir) {
284
+ let realAgentDir;
285
+ try {
286
+ realAgentDir = await fs$1.realpath(agentDir);
287
+ } catch (err) {
288
+ throw new Error(`Agent directory not found or cannot be resolved: ${agentDir}`, { cause: err });
289
+ }
290
+ const resolvedRequestedPath = path.resolve(realAgentDir, requestedPath);
291
+ if (!pathIsInsideDir(resolvedRequestedPath, realAgentDir, { allowSameDir: true })) throw new Error(`Security Error: Path resolves outside the allowed agent directory: ${resolvedRequestedPath}`);
292
+ let stat;
293
+ try {
294
+ stat = await fs$1.lstat(resolvedRequestedPath);
295
+ } catch (err) {
296
+ throw new Error(`File not found or cannot be accessed: ${requestedPath}`, { cause: err });
297
+ }
298
+ if (stat.isSymbolicLink()) throw new Error(`Security Error: Symlinks are not allowed: ${requestedPath}`);
299
+ if (!stat.isFile()) throw new Error(`Requested path is not a file: ${requestedPath}`);
300
+ if (stat.size > MAX_SNAPSHOT_SIZE) throw new Error(`File exceeds maximum snapshot size of 5MB: ${requestedPath}`);
301
+ const ext = path.extname(resolvedRequestedPath);
302
+ const base = path.basename(resolvedRequestedPath, ext);
303
+ await fs$1.mkdir(snapshotDir, { recursive: true });
304
+ let snapshotPath;
305
+ while (true) {
306
+ const snapshotFileName = `${base}_${randomBytes(8).toString("hex")}${ext}`;
307
+ snapshotPath = path.join(snapshotDir, snapshotFileName);
308
+ try {
309
+ await fs$1.copyFile(resolvedRequestedPath, snapshotPath, constants.COPYFILE_EXCL);
310
+ break;
311
+ } catch (err) {
312
+ if (err instanceof Error && "code" in err && err.code === "EEXIST") continue;
313
+ throw err;
314
+ }
315
+ }
316
+ return snapshotPath;
317
+ }
318
+ function interpolateArgs(args, snapshots) {
319
+ return args.map((arg) => {
320
+ let interpolated = arg;
321
+ for (const [key, snapshotPath] of Object.entries(snapshots)) {
322
+ const variable = `{{${key}}}`;
323
+ interpolated = interpolated.replaceAll(variable, snapshotPath);
324
+ }
325
+ return interpolated;
326
+ });
327
+ }
328
+ function executeSafe(command, args, options) {
329
+ return new Promise((resolve) => {
330
+ const p = spawn(command, args, {
331
+ shell: false,
332
+ cwd: options?.cwd,
333
+ env: options?.env
334
+ });
335
+ let stdout = "";
336
+ let stderr = "";
337
+ if (p.stdout) p.stdout.on("data", (data) => {
338
+ stdout += data.toString();
339
+ });
340
+ if (p.stderr) p.stderr.on("data", (data) => {
341
+ stderr += data.toString();
342
+ });
343
+ p.on("close", (code) => {
344
+ resolve({
345
+ stdout,
346
+ stderr,
347
+ exitCode: code ?? 1
348
+ });
349
+ });
350
+ p.on("error", (err) => {
351
+ resolve({
352
+ stdout: "",
353
+ stderr: err.toString(),
354
+ exitCode: 1
355
+ });
356
+ });
357
+ });
358
+ }
359
+ async function generateRequestPreview(request) {
360
+ let previewContent = `Sandbox Policy Request: ${request.commandName}\n`;
361
+ previewContent += `ID: ${request.id}\n`;
362
+ if (request.args.length > 0) previewContent += `Args: ${request.args.join(" ")}\n`;
363
+ for (const [name, snapPath] of Object.entries(request.fileMappings)) {
364
+ previewContent += `File [${name}]:\n`;
365
+ try {
366
+ let content = await fs$1.readFile(snapPath, "utf8");
367
+ if (content.length > 500) content = content.substring(0, 500) + "\n... (truncated)\n";
368
+ previewContent += content;
369
+ } catch (e) {
370
+ previewContent += `<Error reading file: ${e.message}>\n`;
371
+ }
372
+ }
373
+ previewContent += `\nUse /approve ${request.id} or /reject ${request.id} [reason]`;
374
+ return previewContent;
375
+ }
376
+
277
377
  //#endregion
278
378
  //#region src/daemon/routers/slash-policies.ts
279
379
  async function loadAndValidateRequest(id, state) {
@@ -533,6 +633,9 @@ function calculateDelay(attempt, baseDelayMs, isFallback = false) {
533
633
  const delay = baseDelayMs * Math.pow(2, effectiveAttempt - 1);
534
634
  return Math.min(delay, 15e3);
535
635
  }
636
+ function formatPendingMessages(payloads) {
637
+ return payloads.map((text) => `<message>\n${text}\n</message>`).join("\n\n");
638
+ }
536
639
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
537
640
  async function resolveSessionState(chatId, cwd, sessionId, overrideAgentId) {
538
641
  const chatSettings = await readChatSettings(chatId, cwd);
@@ -633,18 +736,21 @@ async function executeDirectMessage(chatId, state, settings, cwd, runCommand, no
633
736
  ...state.reply.includes("NO_REPLY_NECESSARY") ? { level: "verbose" } : {}
634
737
  });
635
738
  if (!state.message.trim() && state.action !== "stop" && state.action !== "interrupt") return;
636
- const queue = getQueue(cwd);
739
+ const queue = getMessageQueue(cwd);
637
740
  if (state.action === "stop") {
638
741
  queue.abortCurrent();
639
742
  queue.clear();
640
743
  return;
641
744
  }
642
745
  if (state.action === "interrupt") {
746
+ const targetSessionId = state.sessionId || "default";
747
+ const isMatchingSession = (p) => p.sessionId === targetSessionId;
643
748
  const currentPayload = queue.getCurrentPayload();
644
- queue.abortCurrent();
645
- const extracted = queue.extractPending();
646
- const payloads = currentPayload ? [currentPayload, ...extracted] : extracted;
647
- if (payloads.length > 0) state.message = `${payloads.map((text) => `<message>\n${text}\n</message>`).join("\n\n")}\n\n<message>\n${state.message}\n</message>`.trim();
749
+ const currentMatches = currentPayload ? isMatchingSession(currentPayload) : false;
750
+ const extracted = queue.extractPending(isMatchingSession);
751
+ queue.abortCurrent(isMatchingSession);
752
+ const payloads = currentMatches && currentPayload ? [currentPayload, ...extracted] : extracted;
753
+ if (payloads.length > 0) state.message = `${formatPendingMessages(payloads.map((p) => p.text))}\n\n<message>\n${state.message}\n</message>`.trim();
648
754
  }
649
755
  if (!state.message.trim()) return;
650
756
  const routerEnv = state.env ?? {};
@@ -807,8 +913,15 @@ async function executeDirectMessage(chatId, state, settings, cwd, runCommand, no
807
913
  if (success) break;
808
914
  }
809
915
  if (lastLogMsg) await appendMessage(chatId, lastLogMsg);
810
- }, state.message);
811
- if (!noWait) await taskPromise;
916
+ }, {
917
+ text: state.message,
918
+ sessionId: state.sessionId || "default"
919
+ });
920
+ if (!noWait) try {
921
+ await taskPromise;
922
+ } catch (err) {
923
+ if (!(err instanceof Error && err.name === "AbortError")) throw err;
924
+ }
812
925
  else taskPromise.catch((err) => {
813
926
  if (err.name !== "AbortError") console.error("Task execution error:", err);
814
927
  });
@@ -1018,23 +1131,7 @@ var CronManager = class {
1018
1131
  const cronManager = new CronManager();
1019
1132
 
1020
1133
  //#endregion
1021
- //#region src/daemon/router.ts
1022
- const t = initTRPC.context().create();
1023
- const router = t.router;
1024
- const publicProcedure = t.procedure;
1025
- const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
1026
- if (ctx.isApiServer) {
1027
- if (!ctx.tokenPayload) throw new TRPCError({
1028
- code: "UNAUTHORIZED",
1029
- message: "Missing or invalid token"
1030
- });
1031
- }
1032
- return next({ ctx: {
1033
- ...ctx,
1034
- tokenPayload: ctx.tokenPayload
1035
- } });
1036
- });
1037
- const apiProcedure = t.procedure.use(apiAuthMiddleware);
1134
+ //#region src/daemon/api/router-utils.ts
1038
1135
  async function getUniquePath(p) {
1039
1136
  let currentPath = p;
1040
1137
  let counter = 1;
@@ -1060,16 +1157,6 @@ async function resolveAgentDir(agentId, workspaceRoot) {
1060
1157
  }
1061
1158
  return workspaceRoot;
1062
1159
  }
1063
- async function resolveAndCheckChatId(ctx, inputChatId) {
1064
- const chatId = inputChatId ?? (ctx.isApiServer && ctx.tokenPayload ? ctx.tokenPayload.chatId : await getDefaultChatId());
1065
- if (ctx.isApiServer && ctx.tokenPayload) {
1066
- if (ctx.tokenPayload.chatId !== chatId) throw new TRPCError({
1067
- code: "FORBIDDEN",
1068
- message: "Token not authorized for this chat"
1069
- });
1070
- }
1071
- return chatId;
1072
- }
1073
1160
  async function getAgentFilesDir(agentId, chatId, settings, workspaceRoot) {
1074
1161
  const chatSettings = await readChatSettings(chatId) ?? {};
1075
1162
  const targetAgentId = agentId ?? chatSettings.defaultAgent ?? "default";
@@ -1084,8 +1171,6 @@ async function getAgentFilesDir(agentId, chatId, settings, workspaceRoot) {
1084
1171
  return path.resolve(agentDir, agentFilesDir);
1085
1172
  }
1086
1173
  async function validateAttachments(files) {
1087
- const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
1088
- const { getClawminiDir } = await import("../workspace-BC1ahx4R.mjs").then((n) => n.v);
1089
1174
  const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp");
1090
1175
  for (const file of files) {
1091
1176
  const absoluteFile = path.resolve(process.cwd(), file);
@@ -1104,7 +1189,6 @@ async function validateAttachments(files) {
1104
1189
  }
1105
1190
  }
1106
1191
  async function validateLogFile(file, agentDir, workspaceRoot) {
1107
- const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
1108
1192
  const resolvedPath = path.resolve(agentDir, file);
1109
1193
  if (!pathIsInsideDir(resolvedPath, agentDir, { allowSameDir: true })) throw new TRPCError({
1110
1194
  code: "BAD_REQUEST",
@@ -1120,244 +1204,349 @@ async function validateLogFile(file, agentDir, workspaceRoot) {
1120
1204
  }
1121
1205
  return path.relative(workspaceRoot, resolvedPath);
1122
1206
  }
1123
- const AppRouter = router({
1124
- sendMessage: apiProcedure.input(z.object({
1125
- type: z.literal("send-message"),
1126
- client: z.literal("cli"),
1127
- data: z.object({
1128
- message: z.string(),
1129
- chatId: z.string().optional(),
1130
- sessionId: z.string().optional(),
1131
- agentId: z.string().optional(),
1132
- noWait: z.boolean().optional(),
1133
- files: z.array(z.string()).optional(),
1134
- adapter: z.string().optional()
1135
- })
1136
- })).mutation(async ({ input, ctx }) => {
1137
- let message = input.data.message;
1138
- const chatId = await resolveAndCheckChatId(ctx, input.data.chatId);
1139
- const noWait = input.data.noWait ?? false;
1140
- const sessionId = input.data.sessionId;
1141
- const agentId = input.data.agentId;
1142
- const settingsPath = getSettingsPath();
1143
- let settings;
1144
- try {
1145
- const settingsStr = await fs$1.readFile(settingsPath, "utf8");
1146
- settings = JSON.parse(settingsStr);
1147
- } catch (err) {
1148
- throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
1149
- }
1150
- const files = input.data.files;
1151
- if (files && files.length > 0) {
1152
- const workspaceRoot = getWorkspaceRoot(process.cwd());
1153
- const chatSettings = await readChatSettings(chatId) ?? {};
1154
- const agentDir = await resolveAgentDir(agentId ?? chatSettings.defaultAgent ?? "default", workspaceRoot);
1155
- const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
1156
- const adapterNamespace = input.data.adapter || "cli";
1157
- const targetDir = path.join(absoluteFilesDir, adapterNamespace);
1158
- const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
1159
- if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) throw new TRPCError({
1160
- code: "BAD_REQUEST",
1161
- message: "Target directory must be within the workspace."
1162
- });
1163
- await validateAttachments(files);
1164
- await fs$1.mkdir(targetDir, { recursive: true });
1165
- const finalPaths = [];
1166
- for (const file of files) {
1167
- const fileName = path.basename(file);
1168
- const targetPath = await getUniquePath(path.join(targetDir, fileName));
1169
- try {
1170
- await fs$1.rename(file, targetPath);
1171
- } catch {
1172
- await fs$1.copyFile(file, targetPath);
1173
- await fs$1.unlink(file);
1174
- }
1175
- finalPaths.push(path.relative(agentDir, targetPath));
1176
- }
1177
- const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join("\n")}`;
1178
- message = message ? `${message}\n\n${fileList}` : fileList;
1179
- }
1180
- await handleUserMessage(chatId, message, settings, void 0, noWait, (args) => runCommand({
1181
- ...args,
1182
- logToTerminal: true
1183
- }), sessionId, agentId);
1184
- return { success: true };
1185
- }),
1186
- getMessages: apiProcedure.input(z.object({
1187
- chatId: z.string().optional(),
1188
- limit: z.number().optional()
1189
- })).query(async ({ input, ctx }) => {
1190
- return getMessages(await resolveAndCheckChatId(ctx, input.chatId), input.limit);
1191
- }),
1192
- waitForMessages: apiProcedure.input(z.object({
1193
- chatId: z.string().optional(),
1194
- lastMessageId: z.string().optional()
1195
- })).subscription(async function* ({ input, ctx, signal }) {
1196
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1197
- if (input.lastMessageId) {
1198
- const messages = await getMessages(chatId);
1199
- const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
1200
- if (lastIndex !== -1 && lastIndex < messages.length - 1) yield messages.slice(lastIndex + 1);
1201
- }
1202
- const { on } = await import("node:events");
1203
- try {
1204
- for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) if (event.chatId === chatId) yield [event.message];
1205
- } catch (err) {
1206
- if (err instanceof Error && err.name === "AbortError") return;
1207
- throw err;
1208
- }
1209
- }),
1210
- waitForTyping: apiProcedure.input(z.object({ chatId: z.string().optional() })).subscription(async function* ({ input, ctx, signal }) {
1211
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1212
- const { on } = await import("node:events");
1213
- try {
1214
- for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) if (event.chatId === chatId) yield event;
1215
- } catch (err) {
1216
- if (err instanceof Error && err.name === "AbortError") return;
1217
- throw err;
1218
- }
1219
- }),
1220
- ping: publicProcedure.query(() => {
1221
- return { status: "ok" };
1222
- }),
1223
- shutdown: publicProcedure.mutation(() => {
1224
- setTimeout(() => {
1225
- console.log("Shutting down daemon...");
1226
- process.kill(process.pid, "SIGTERM");
1227
- }, 100);
1228
- return { success: true };
1229
- }),
1230
- logMessage: apiProcedure.input(z.object({
1231
- chatId: z.string().optional(),
1232
- message: z.string().optional(),
1233
- files: z.array(z.string()).optional()
1234
- })).mutation(async ({ input, ctx }) => {
1235
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1236
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1237
- const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
1238
- const filePaths = [];
1239
- if (input.files && input.files.length > 0) {
1240
- const workspaceRoot = getWorkspaceRoot(process.cwd());
1241
- const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
1242
- for (const file of input.files) {
1243
- const validPath = await validateLogFile(file, agentDir, workspaceRoot);
1244
- filePaths.push(validPath);
1245
- }
1246
- }
1247
- const filesArgStr = filePaths.map((p) => ` --file ${p}`).join("");
1248
- const logMsg = {
1249
- id,
1250
- messageId: id,
1251
- role: "log",
1252
- source: "router",
1253
- content: input.message || "",
1254
- stderr: "",
1255
- timestamp,
1256
- command: `clawmini-lite log${filesArgStr}`,
1257
- cwd: process.cwd(),
1258
- exitCode: 0,
1259
- ...filePaths.length > 0 ? { files: filePaths } : {}
1260
- };
1261
- await import("../chats-BcbxvPlj.mjs").then((n) => n.n).then((m) => m.appendMessage(chatId, logMsg));
1262
- return { success: true };
1263
- }),
1264
- listCronJobs: apiProcedure.input(z.object({ chatId: z.string().optional() })).query(async ({ input, ctx }) => {
1265
- return (await readChatSettings(await resolveAndCheckChatId(ctx, input.chatId)))?.jobs ?? [];
1266
- }),
1267
- addCronJob: apiProcedure.input(z.object({
1268
- chatId: z.string().optional(),
1269
- job: CronJobSchema
1270
- })).mutation(async ({ input, ctx }) => {
1271
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1272
- const settings = await readChatSettings(chatId) || {};
1273
- const cronJobs = settings.jobs ?? [];
1274
- const existingIndex = cronJobs.findIndex((j) => j.id === input.job.id);
1275
- if (existingIndex >= 0) cronJobs[existingIndex] = input.job;
1276
- else cronJobs.push(input.job);
1277
- settings.jobs = cronJobs;
1207
+ async function listCronJobsShared(chatId) {
1208
+ return (await readChatSettings(chatId))?.jobs ?? [];
1209
+ }
1210
+ async function addCronJobShared(chatId, job) {
1211
+ const settings = await readChatSettings(chatId) || {};
1212
+ const cronJobs = settings.jobs ?? [];
1213
+ const existingIndex = cronJobs.findIndex((j) => j.id === job.id);
1214
+ if (existingIndex >= 0) cronJobs[existingIndex] = job;
1215
+ else cronJobs.push(job);
1216
+ settings.jobs = cronJobs;
1217
+ await writeChatSettings(chatId, settings);
1218
+ cronManager.scheduleJob(chatId, job);
1219
+ return { success: true };
1220
+ }
1221
+ async function deleteCronJobShared(chatId, id) {
1222
+ const settings = await readChatSettings(chatId);
1223
+ if (!settings || !settings.jobs) return {
1224
+ success: true,
1225
+ deleted: false
1226
+ };
1227
+ const initialLength = settings.jobs.length;
1228
+ settings.jobs = settings.jobs.filter((j) => j.id !== id);
1229
+ if (settings.jobs.length !== initialLength) {
1278
1230
  await writeChatSettings(chatId, settings);
1279
- cronManager.scheduleJob(chatId, input.job);
1280
- return { success: true };
1281
- }),
1282
- deleteCronJob: apiProcedure.input(z.object({
1283
- chatId: z.string().optional(),
1284
- id: z.string()
1285
- })).mutation(async ({ input, ctx }) => {
1286
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1287
- const settings = await readChatSettings(chatId);
1288
- if (!settings || !settings.jobs) return {
1289
- success: true,
1290
- deleted: false
1291
- };
1292
- const initialLength = settings.jobs.length;
1293
- settings.jobs = settings.jobs.filter((j) => j.id !== input.id);
1294
- if (settings.jobs.length !== initialLength) {
1295
- await writeChatSettings(chatId, settings);
1296
- cronManager.unscheduleJob(chatId, input.id);
1297
- return {
1298
- success: true,
1299
- deleted: true
1300
- };
1301
- }
1231
+ cronManager.unscheduleJob(chatId, id);
1302
1232
  return {
1303
1233
  success: true,
1304
- deleted: false
1305
- };
1306
- }),
1307
- listPolicies: apiProcedure.query(async () => {
1308
- return await readPolicies();
1309
- }),
1310
- executePolicyHelp: apiProcedure.input(z.object({ commandName: z.string() })).query(async ({ input }) => {
1311
- const policy = (await readPolicies())?.policies?.[input.commandName];
1312
- if (!policy) throw new TRPCError({
1313
- code: "NOT_FOUND",
1314
- message: `Policy not found: ${input.commandName}`
1315
- });
1316
- if (!policy.allowHelp) return {
1317
- stdout: "",
1318
- stderr: "This command does not support --help\n",
1319
- exitCode: 1
1234
+ deleted: true
1320
1235
  };
1321
- const { executeSafe } = await import("../policy-utils-BvfOK6Ih.mjs").then((n) => n.i);
1322
- const fullArgs = [...policy.args || [], "--help"];
1323
- const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, { cwd: getWorkspaceRoot() });
1324
- return {
1325
- stdout,
1326
- stderr,
1327
- exitCode
1328
- };
1329
- }),
1330
- createPolicyRequest: apiProcedure.input(z.object({
1331
- commandName: z.string(),
1332
- args: z.array(z.string()),
1333
- fileMappings: z.record(z.string(), z.string()),
1334
- chatId: z.string().optional()
1335
- })).mutation(async ({ input, ctx }) => {
1236
+ }
1237
+ return {
1238
+ success: true,
1239
+ deleted: false
1240
+ };
1241
+ }
1242
+
1243
+ //#endregion
1244
+ //#region src/daemon/api/user-router.ts
1245
+ const sendMessage = apiProcedure.input(z.object({
1246
+ type: z.literal("send-message"),
1247
+ client: z.literal("cli"),
1248
+ data: z.object({
1249
+ message: z.string(),
1250
+ chatId: z.string().optional(),
1251
+ sessionId: z.string().optional(),
1252
+ agentId: z.string().optional(),
1253
+ noWait: z.boolean().optional(),
1254
+ files: z.array(z.string()).optional(),
1255
+ adapter: z.string().optional()
1256
+ })
1257
+ })).mutation(async ({ input }) => {
1258
+ let message = input.data.message;
1259
+ const chatId = input.data.chatId ?? await getDefaultChatId();
1260
+ const noWait = input.data.noWait ?? false;
1261
+ const sessionId = input.data.sessionId;
1262
+ const agentId = input.data.agentId;
1263
+ const settingsPath = getSettingsPath();
1264
+ let settings;
1265
+ try {
1266
+ const settingsStr = await fs$1.readFile(settingsPath, "utf8");
1267
+ settings = JSON.parse(settingsStr);
1268
+ } catch (err) {
1269
+ throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
1270
+ }
1271
+ const files = input.data.files;
1272
+ if (files && files.length > 0) {
1336
1273
  const workspaceRoot = getWorkspaceRoot(process.cwd());
1337
- const snapshotDir = path.join(getClawminiDir(process.cwd()), "tmp", "snapshots");
1338
- const service = new PolicyRequestService(new RequestStore(process.cwd()), await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), snapshotDir);
1339
- const chatId = await resolveAndCheckChatId(ctx, input.chatId);
1340
- const agentId = ctx.tokenPayload?.agentId ?? "unknown";
1341
- const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId);
1342
- const { generateRequestPreview } = await import("../policy-utils-BvfOK6Ih.mjs").then((n) => n.i);
1343
- const previewContent = await generateRequestPreview(request);
1344
- const logMsg = {
1345
- id: (await import("node:crypto")).randomUUID(),
1346
- messageId: (await import("node:crypto")).randomUUID(),
1347
- role: "log",
1348
- source: "router",
1349
- content: previewContent,
1350
- stderr: "",
1351
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1352
- command: "policy-request",
1353
- cwd: process.cwd(),
1354
- exitCode: 0
1274
+ const chatSettings = await readChatSettings(chatId) ?? {};
1275
+ const agentDir = await resolveAgentDir(agentId ?? chatSettings.defaultAgent ?? "default", workspaceRoot);
1276
+ const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
1277
+ const adapterNamespace = input.data.adapter || "cli";
1278
+ const targetDir = path.join(absoluteFilesDir, adapterNamespace);
1279
+ if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) throw new TRPCError({
1280
+ code: "BAD_REQUEST",
1281
+ message: "Target directory must be within the workspace."
1282
+ });
1283
+ await validateAttachments(files);
1284
+ await fs$1.mkdir(targetDir, { recursive: true });
1285
+ const finalPaths = [];
1286
+ for (const file of files) {
1287
+ const fileName = path.basename(file);
1288
+ const targetPath = await getUniquePath(path.join(targetDir, fileName));
1289
+ try {
1290
+ await fs$1.rename(file, targetPath);
1291
+ } catch {
1292
+ await fs$1.copyFile(file, targetPath);
1293
+ await fs$1.unlink(file);
1294
+ }
1295
+ finalPaths.push(path.relative(agentDir, targetPath));
1296
+ }
1297
+ const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join("\n")}`;
1298
+ message = message ? `${message}\n\n${fileList}` : fileList;
1299
+ }
1300
+ await handleUserMessage(chatId, message, settings, void 0, noWait, (args) => runCommand({
1301
+ ...args,
1302
+ logToTerminal: true
1303
+ }), sessionId, agentId);
1304
+ return { success: true };
1305
+ });
1306
+ const getMessages = apiProcedure.input(z.object({
1307
+ chatId: z.string().optional(),
1308
+ limit: z.number().optional()
1309
+ })).query(async ({ input }) => {
1310
+ return getMessages$1(input.chatId ?? await getDefaultChatId(), input.limit);
1311
+ });
1312
+ const waitForMessages = apiProcedure.input(z.object({
1313
+ chatId: z.string().optional(),
1314
+ lastMessageId: z.string().optional()
1315
+ })).subscription(async function* ({ input, signal }) {
1316
+ const chatId = input.chatId ?? await getDefaultChatId();
1317
+ if (input.lastMessageId) {
1318
+ const messages = await getMessages$1(chatId);
1319
+ const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
1320
+ if (lastIndex !== -1 && lastIndex < messages.length - 1) yield messages.slice(lastIndex + 1);
1321
+ }
1322
+ try {
1323
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) if (event.chatId === chatId) yield [event.message];
1324
+ } catch (err) {
1325
+ if (err instanceof Error && err.name === "AbortError") return;
1326
+ throw err;
1327
+ }
1328
+ });
1329
+ const waitForTyping = apiProcedure.input(z.object({ chatId: z.string().optional() })).subscription(async function* ({ input, signal }) {
1330
+ const chatId = input.chatId ?? await getDefaultChatId();
1331
+ try {
1332
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) if (event.chatId === chatId) yield event;
1333
+ } catch (err) {
1334
+ if (err instanceof Error && err.name === "AbortError") return;
1335
+ throw err;
1336
+ }
1337
+ });
1338
+ const ping = publicProcedure.query(() => {
1339
+ return { status: "ok" };
1340
+ });
1341
+ const shutdown = publicProcedure.mutation(() => {
1342
+ setTimeout(() => {
1343
+ console.log("Shutting down daemon...");
1344
+ process.kill(process.pid, "SIGTERM");
1345
+ }, 100);
1346
+ return { success: true };
1347
+ });
1348
+ const userListCronJobs = apiProcedure.input(z.object({ chatId: z.string().optional() })).query(async ({ input }) => {
1349
+ return listCronJobsShared(input.chatId ?? await getDefaultChatId());
1350
+ });
1351
+ const userAddCronJob = apiProcedure.input(z.object({
1352
+ chatId: z.string().optional(),
1353
+ job: CronJobSchema
1354
+ })).mutation(async ({ input }) => {
1355
+ return addCronJobShared(input.chatId ?? await getDefaultChatId(), input.job);
1356
+ });
1357
+ const userDeleteCronJob = apiProcedure.input(z.object({
1358
+ chatId: z.string().optional(),
1359
+ id: z.string()
1360
+ })).mutation(async ({ input }) => {
1361
+ return deleteCronJobShared(input.chatId ?? await getDefaultChatId(), input.id);
1362
+ });
1363
+ const userRouter = router({
1364
+ sendMessage,
1365
+ getMessages,
1366
+ waitForMessages,
1367
+ waitForTyping,
1368
+ ping,
1369
+ shutdown,
1370
+ listCronJobs: userListCronJobs,
1371
+ addCronJob: userAddCronJob,
1372
+ deleteCronJob: userDeleteCronJob
1373
+ });
1374
+
1375
+ //#endregion
1376
+ //#region src/daemon/policy-request-service.ts
1377
+ var PolicyRequestService = class {
1378
+ store;
1379
+ maxPending;
1380
+ agentDir;
1381
+ snapshotDir;
1382
+ constructor(store, agentDir, snapshotDir, maxPending = 100) {
1383
+ this.store = store;
1384
+ this.agentDir = agentDir;
1385
+ this.snapshotDir = snapshotDir;
1386
+ this.maxPending = maxPending;
1387
+ }
1388
+ async createRequest(commandName, args, fileMappings, chatId, agentId) {
1389
+ const allRequests = await this.store.list();
1390
+ if (allRequests.filter((r) => r.state === "Pending").length >= this.maxPending) throw new Error(`Maximum number of pending requests (${this.maxPending}) reached.`);
1391
+ const snapshotMappings = {};
1392
+ for (const [key, requestedPath] of Object.entries(fileMappings)) snapshotMappings[key] = await createSnapshot(requestedPath, this.agentDir, this.snapshotDir);
1393
+ let id = "";
1394
+ do
1395
+ id = generateRandomAlphaNumericString(3);
1396
+ while (allRequests.some((r) => r.id === id));
1397
+ const request = {
1398
+ id,
1399
+ commandName,
1400
+ args,
1401
+ fileMappings: snapshotMappings,
1402
+ state: "Pending",
1403
+ createdAt: Date.now(),
1404
+ chatId,
1405
+ agentId
1355
1406
  };
1356
- await import("../chats-BcbxvPlj.mjs").then((n) => n.n).then((m) => m.appendMessage(chatId, logMsg));
1407
+ await this.store.save(request);
1357
1408
  return request;
1358
- })
1409
+ }
1410
+ getInterpolatedArgs(request) {
1411
+ return interpolateArgs(request.args, request.fileMappings);
1412
+ }
1413
+ };
1414
+
1415
+ //#endregion
1416
+ //#region src/daemon/api/agent-router.ts
1417
+ const logMessage = apiProcedure.input(z.object({
1418
+ message: z.string().optional(),
1419
+ files: z.array(z.string()).optional()
1420
+ })).mutation(async ({ input, ctx }) => {
1421
+ if (!ctx.tokenPayload) throw new TRPCError({
1422
+ code: "UNAUTHORIZED",
1423
+ message: "Missing token"
1424
+ });
1425
+ const chatId = ctx.tokenPayload.chatId;
1426
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1427
+ const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
1428
+ const filePaths = [];
1429
+ if (input.files && input.files.length > 0) {
1430
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
1431
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
1432
+ for (const file of input.files) {
1433
+ const validPath = await validateLogFile(file, agentDir, workspaceRoot);
1434
+ filePaths.push(validPath);
1435
+ }
1436
+ }
1437
+ const filesArgStr = filePaths.map((p) => ` --file ${p}`).join("");
1438
+ await appendMessage(chatId, {
1439
+ id,
1440
+ messageId: id,
1441
+ role: "log",
1442
+ source: "router",
1443
+ content: input.message || "",
1444
+ stderr: "",
1445
+ timestamp,
1446
+ command: `clawmini-lite log${filesArgStr}`,
1447
+ cwd: process.cwd(),
1448
+ exitCode: 0,
1449
+ ...filePaths.length > 0 ? { files: filePaths } : {}
1450
+ });
1451
+ return { success: true };
1452
+ });
1453
+ const agentListCronJobs = apiProcedure.query(async ({ ctx }) => {
1454
+ if (!ctx.tokenPayload) throw new TRPCError({
1455
+ code: "UNAUTHORIZED",
1456
+ message: "Missing token"
1457
+ });
1458
+ const chatId = ctx.tokenPayload.chatId;
1459
+ return listCronJobsShared(chatId);
1460
+ });
1461
+ const agentAddCronJob = apiProcedure.input(z.object({ job: CronJobSchema })).mutation(async ({ input, ctx }) => {
1462
+ if (!ctx.tokenPayload) throw new TRPCError({
1463
+ code: "UNAUTHORIZED",
1464
+ message: "Missing token"
1465
+ });
1466
+ const chatId = ctx.tokenPayload.chatId;
1467
+ return addCronJobShared(chatId, {
1468
+ ...input.job,
1469
+ agentId: ctx.tokenPayload.agentId
1470
+ });
1471
+ });
1472
+ const agentDeleteCronJob = apiProcedure.input(z.object({ id: z.string() })).mutation(async ({ input, ctx }) => {
1473
+ if (!ctx.tokenPayload) throw new TRPCError({
1474
+ code: "UNAUTHORIZED",
1475
+ message: "Missing token"
1476
+ });
1477
+ const chatId = ctx.tokenPayload.chatId;
1478
+ return deleteCronJobShared(chatId, input.id);
1479
+ });
1480
+ const listPolicies = apiProcedure.query(async () => {
1481
+ return await readPolicies();
1482
+ });
1483
+ const executePolicyHelp = apiProcedure.input(z.object({ commandName: z.string() })).query(async ({ input }) => {
1484
+ const policy = (await readPolicies())?.policies?.[input.commandName];
1485
+ if (!policy) throw new TRPCError({
1486
+ code: "NOT_FOUND",
1487
+ message: `Policy not found: ${input.commandName}`
1488
+ });
1489
+ if (!policy.allowHelp) return {
1490
+ stdout: "",
1491
+ stderr: "This command does not support --help\n",
1492
+ exitCode: 1
1493
+ };
1494
+ const fullArgs = [...policy.args || [], "--help"];
1495
+ const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, { cwd: getWorkspaceRoot() });
1496
+ return {
1497
+ stdout,
1498
+ stderr,
1499
+ exitCode
1500
+ };
1501
+ });
1502
+ const createPolicyRequest = apiProcedure.input(z.object({
1503
+ commandName: z.string(),
1504
+ args: z.array(z.string()),
1505
+ fileMappings: z.record(z.string(), z.string())
1506
+ })).mutation(async ({ input, ctx }) => {
1507
+ if (!ctx.tokenPayload) throw new TRPCError({
1508
+ code: "UNAUTHORIZED",
1509
+ message: "Missing token"
1510
+ });
1511
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
1512
+ const snapshotDir = path.join(getClawminiDir(process.cwd()), "tmp", "snapshots");
1513
+ const service = new PolicyRequestService(new RequestStore(process.cwd()), await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), snapshotDir);
1514
+ const chatId = ctx.tokenPayload.chatId;
1515
+ const agentId = ctx.tokenPayload.agentId;
1516
+ const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId);
1517
+ const previewContent = await generateRequestPreview(request);
1518
+ await appendMessage(chatId, {
1519
+ id: randomUUID(),
1520
+ messageId: randomUUID(),
1521
+ role: "log",
1522
+ source: "router",
1523
+ content: previewContent,
1524
+ stderr: "",
1525
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1526
+ command: "policy-request",
1527
+ cwd: process.cwd(),
1528
+ exitCode: 0
1529
+ });
1530
+ return request;
1531
+ });
1532
+ const fetchPendingMessages = apiProcedure.mutation(async ({ ctx }) => {
1533
+ const queue = getMessageQueue(process.cwd());
1534
+ const targetSessionId = ctx.tokenPayload?.sessionId || "default";
1535
+ const extracted = queue.extractPending((p) => p.sessionId === targetSessionId);
1536
+ if (extracted.length === 0) return { messages: "" };
1537
+ return { messages: formatPendingMessages(extracted.map((p) => p.text)) };
1538
+ });
1539
+ const agentRouter = router({
1540
+ logMessage,
1541
+ listCronJobs: agentListCronJobs,
1542
+ addCronJob: agentAddCronJob,
1543
+ deleteCronJob: agentDeleteCronJob,
1544
+ listPolicies,
1545
+ executePolicyHelp,
1546
+ createPolicyRequest,
1547
+ fetchPendingMessages,
1548
+ ping
1359
1549
  });
1360
- const appRouter = AppRouter;
1361
1550
 
1362
1551
  //#endregion
1363
1552
  //#region src/daemon/index.ts
@@ -1431,7 +1620,7 @@ async function initDaemon() {
1431
1620
  readyPromiseResolve = resolve;
1432
1621
  });
1433
1622
  const handler = createHTTPHandler({
1434
- router: appRouter,
1623
+ router: userRouter,
1435
1624
  createContext: ({ req, res }) => ({
1436
1625
  req,
1437
1626
  res,
@@ -1464,7 +1653,7 @@ async function initDaemon() {
1464
1653
  let apiServer;
1465
1654
  if (apiCtx) {
1466
1655
  const apiHandler = createHTTPHandler({
1467
- router: appRouter,
1656
+ router: agentRouter,
1468
1657
  createContext: ({ req, res }) => {
1469
1658
  let tokenPayload = null;
1470
1659
  const authHeader = req.headers.authorization;