clawmini 0.0.1 → 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 (103) hide show
  1. package/.github/workflows/ci.yml +59 -0
  2. package/README.md +61 -76
  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 +8 -6
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/lite.mjs +64 -10
  9. package/dist/cli/lite.mjs.map +1 -1
  10. package/dist/daemon/index.mjs +732 -251
  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/{COekwvP2.js → 8YNcRyEk.js} +1 -1
  17. package/dist/web/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
  18. package/dist/web/_app/immutable/entry/{app.B-vZe7PN.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.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
  21. package/dist/web/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
  22. package/dist/web/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
  23. package/dist/web/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
  24. package/dist/web/_app/immutable/nodes/{5.BJl7oM3b.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-CSgfo_2J.mjs → workspace-DjoNjhW0.mjs} +21 -40
  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/docs/CLI_REFERENCE.md +35 -0
  36. package/docs/guides/sandbox_policies.md +12 -5
  37. package/eslint.config.js +12 -0
  38. package/package.json +3 -2
  39. package/src/adapter-discord/client.ts +1 -1
  40. package/src/adapter-discord/index.ts +22 -5
  41. package/src/cli/client.ts +8 -3
  42. package/src/cli/e2e/adapter-discord.test.ts +2 -2
  43. package/src/cli/e2e/daemon.test.ts +2 -1
  44. package/src/cli/e2e/export-lite-func.test.ts +41 -13
  45. package/src/cli/e2e/fallbacks.test.ts +4 -0
  46. package/src/cli/lite.ts +24 -6
  47. package/src/daemon/api/agent-router.ts +191 -0
  48. package/src/daemon/{router.test.ts → api/index.test.ts} +101 -34
  49. package/src/daemon/api/index.ts +4 -0
  50. package/src/daemon/{router-policy-request.test.ts → api/policy-request.test.ts} +27 -13
  51. package/src/daemon/api/router-utils.ts +159 -0
  52. package/src/daemon/api/trpc.ts +30 -0
  53. package/src/daemon/api/user-router.ts +221 -0
  54. package/src/daemon/index.ts +3 -3
  55. package/src/daemon/message-interruption.test.ts +17 -10
  56. package/src/daemon/message-typing.test.ts +1 -1
  57. package/src/daemon/message.ts +260 -239
  58. package/src/daemon/observation.test.ts +1 -1
  59. package/src/daemon/queue.test.ts +28 -0
  60. package/src/daemon/queue.ts +30 -15
  61. package/src/daemon/request-store.test.ts +4 -4
  62. package/src/daemon/request-store.ts +3 -1
  63. package/src/shared/workspace.ts +4 -5
  64. package/templates/debug/settings.json +5 -0
  65. package/templates/environments/macos/env.json +1 -1
  66. package/templates/environments/macos-proxy/env.json +1 -1
  67. package/templates/gemini-claw/.gemini/hooks/insert-pending.sh +9 -0
  68. package/templates/gemini-claw/.gemini/settings.json +14 -1
  69. package/templates/gemini-claw/.gemini/system.md +2 -0
  70. package/web/.svelte-kit/ambient.d.ts +2 -6
  71. package/web/.svelte-kit/generated/server/internal.js +1 -1
  72. package/web/.svelte-kit/output/client/.vite/manifest.json +29 -29
  73. package/web/.svelte-kit/output/client/_app/immutable/chunks/{COekwvP2.js → 8YNcRyEk.js} +1 -1
  74. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
  75. package/web/.svelte-kit/output/client/_app/immutable/entry/{app.B-vZe7PN.js → app.DO5eYwVz.js} +2 -2
  76. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +1 -0
  77. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
  78. package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
  79. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
  80. package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
  81. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BJl7oM3b.js → 5.BpJUN6QH.js} +1 -1
  82. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  83. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  84. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  85. package/web/.svelte-kit/output/server/manifest.js +1 -1
  86. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  87. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  88. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  89. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  90. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  91. package/dist/chats-DKgTeU7i.mjs +0 -91
  92. package/dist/chats-DKgTeU7i.mjs.map +0 -1
  93. package/dist/chats-Zd_HXDHx.mjs +0 -29
  94. package/dist/chats-Zd_HXDHx.mjs.map +0 -1
  95. package/dist/fs-B5wW0oaH.mjs +0 -14
  96. package/dist/fs-B5wW0oaH.mjs.map +0 -1
  97. package/dist/lite-Dl7WXyaH.mjs +0 -80
  98. package/dist/lite-Dl7WXyaH.mjs.map +0 -1
  99. package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
  100. package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +0 -1
  101. package/dist/workspace-CSgfo_2J.mjs.map +0 -1
  102. package/src/daemon/router.ts +0 -510
  103. package/web/.svelte-kit/output/client/_app/immutable/entry/start.oP1AgKhs.js +0 -1
@@ -0,0 +1,221 @@
1
+ import { z } from 'zod';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { TRPCError } from '@trpc/server';
5
+ import { pathIsInsideDir } from '../../shared/utils/fs.js';
6
+ import { on } from 'node:events';
7
+ import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, DAEMON_EVENT_TYPING } from '../events.js';
8
+ import { getSettingsPath, readChatSettings, getWorkspaceRoot } from '../../shared/workspace.js';
9
+ import { CronJobSchema } from '../../shared/config.js';
10
+ import { handleUserMessage } from '../message.js';
11
+ import { getDefaultChatId, getMessages as fetchMessages } from '../chats.js';
12
+ import { runCommand } from '../utils/spawn.js';
13
+ import { apiProcedure, publicProcedure, router } from './trpc.js';
14
+ import {
15
+ getUniquePath,
16
+ resolveAgentDir,
17
+ getAgentFilesDir,
18
+ validateAttachments,
19
+ listCronJobsShared,
20
+ addCronJobShared,
21
+ deleteCronJobShared,
22
+ } from './router-utils.js';
23
+
24
+ export const sendMessage = apiProcedure
25
+ .input(
26
+ z.object({
27
+ type: z.literal('send-message'),
28
+ client: z.literal('cli'),
29
+ data: z.object({
30
+ message: z.string(),
31
+ chatId: z.string().optional(),
32
+ sessionId: z.string().optional(),
33
+ agentId: z.string().optional(),
34
+ noWait: z.boolean().optional(),
35
+ files: z.array(z.string()).optional(),
36
+ adapter: z.string().optional(),
37
+ }),
38
+ })
39
+ )
40
+ .mutation(async ({ input }) => {
41
+ let message = input.data.message;
42
+ const chatId = input.data.chatId ?? (await getDefaultChatId());
43
+ const noWait = input.data.noWait ?? false;
44
+ const sessionId = input.data.sessionId;
45
+ const agentId = input.data.agentId;
46
+ const settingsPath = getSettingsPath();
47
+
48
+ let settings;
49
+ try {
50
+ const settingsStr = await fs.readFile(settingsPath, 'utf8');
51
+ settings = JSON.parse(settingsStr);
52
+ } catch (err) {
53
+ throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
54
+ }
55
+
56
+ const files = input.data.files;
57
+ if (files && files.length > 0) {
58
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
59
+ const chatSettings = (await readChatSettings(chatId)) ?? {};
60
+ const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
61
+ const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
62
+ const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
63
+
64
+ const adapterNamespace = input.data.adapter || 'cli';
65
+ const targetDir = path.join(absoluteFilesDir, adapterNamespace);
66
+
67
+ if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) {
68
+ throw new TRPCError({
69
+ code: 'BAD_REQUEST',
70
+ message: 'Target directory must be within the workspace.',
71
+ });
72
+ }
73
+
74
+ await validateAttachments(files);
75
+
76
+ await fs.mkdir(targetDir, { recursive: true });
77
+
78
+ const finalPaths: string[] = [];
79
+ for (const file of files) {
80
+ const fileName = path.basename(file);
81
+ const targetPath = await getUniquePath(path.join(targetDir, fileName));
82
+
83
+ try {
84
+ await fs.rename(file, targetPath);
85
+ } catch {
86
+ await fs.copyFile(file, targetPath);
87
+ await fs.unlink(file);
88
+ }
89
+
90
+ finalPaths.push(path.relative(agentDir, targetPath));
91
+ }
92
+
93
+ const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join('\n')}`;
94
+ message = message ? `${message}\n\n${fileList}` : fileList;
95
+ }
96
+
97
+ await handleUserMessage(
98
+ chatId,
99
+ message,
100
+ settings,
101
+ undefined,
102
+ noWait,
103
+ (args) => runCommand({ ...args, logToTerminal: true }),
104
+ sessionId,
105
+ agentId
106
+ );
107
+
108
+ return { success: true };
109
+ });
110
+
111
+ export const getMessages = apiProcedure
112
+ .input(z.object({ chatId: z.string().optional(), limit: z.number().optional() }))
113
+ .query(async ({ input }) => {
114
+ const chatId = input.chatId ?? (await getDefaultChatId());
115
+ return fetchMessages(chatId, input.limit);
116
+ });
117
+
118
+ export const waitForMessages = apiProcedure
119
+ .input(
120
+ z.object({
121
+ chatId: z.string().optional(),
122
+ lastMessageId: z.string().optional(),
123
+ })
124
+ )
125
+ .subscription(async function* ({ input, signal }) {
126
+ const chatId = input.chatId ?? (await getDefaultChatId());
127
+
128
+ // 1. Check if there are already new messages
129
+ if (input.lastMessageId) {
130
+ const messages = await fetchMessages(chatId);
131
+ const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
132
+ if (lastIndex !== -1 && lastIndex < messages.length - 1) {
133
+ yield messages.slice(lastIndex + 1);
134
+ }
135
+ }
136
+
137
+ // 2. Listen for new messages
138
+ try {
139
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) {
140
+ if (event.chatId === chatId) {
141
+ yield [event.message];
142
+ }
143
+ }
144
+ } catch (err) {
145
+ if (err instanceof Error && err.name === 'AbortError') {
146
+ return;
147
+ }
148
+ throw err;
149
+ }
150
+ });
151
+
152
+ export const waitForTyping = apiProcedure
153
+ .input(
154
+ z.object({
155
+ chatId: z.string().optional(),
156
+ })
157
+ )
158
+ .subscription(async function* ({ input, signal }) {
159
+ const chatId = input.chatId ?? (await getDefaultChatId());
160
+
161
+ try {
162
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) {
163
+ if (event.chatId === chatId) {
164
+ yield event;
165
+ }
166
+ }
167
+ } catch (err) {
168
+ if (err instanceof Error && err.name === 'AbortError') {
169
+ return;
170
+ }
171
+ throw err;
172
+ }
173
+ });
174
+
175
+ export const ping = publicProcedure.query(() => {
176
+ return { status: 'ok' };
177
+ });
178
+
179
+ export const shutdown = publicProcedure.mutation(() => {
180
+ // Schedule a shutdown shortly after the response is sent
181
+ setTimeout(() => {
182
+ console.log('Shutting down daemon...');
183
+ process.kill(process.pid, 'SIGTERM');
184
+ }, 100);
185
+ return { success: true };
186
+ });
187
+
188
+ export const userListCronJobs = apiProcedure
189
+ .input(z.object({ chatId: z.string().optional() }))
190
+ .query(async ({ input }) => {
191
+ const chatId = input.chatId ?? (await getDefaultChatId());
192
+ return listCronJobsShared(chatId);
193
+ });
194
+
195
+ export const userAddCronJob = apiProcedure
196
+ .input(z.object({ chatId: z.string().optional(), job: CronJobSchema }))
197
+ .mutation(async ({ input }) => {
198
+ const chatId = input.chatId ?? (await getDefaultChatId());
199
+ return addCronJobShared(chatId, input.job);
200
+ });
201
+
202
+ export const userDeleteCronJob = apiProcedure
203
+ .input(z.object({ chatId: z.string().optional(), id: z.string() }))
204
+ .mutation(async ({ input }) => {
205
+ const chatId = input.chatId ?? (await getDefaultChatId());
206
+ return deleteCronJobShared(chatId, input.id);
207
+ });
208
+
209
+ export const userRouter = router({
210
+ sendMessage,
211
+ getMessages,
212
+ waitForMessages,
213
+ waitForTyping,
214
+ ping,
215
+ shutdown,
216
+ listCronJobs: userListCronJobs,
217
+ addCronJob: userAddCronJob,
218
+ deleteCronJob: userDeleteCronJob,
219
+ });
220
+
221
+ export type UserRouter = typeof userRouter;
@@ -3,7 +3,7 @@ import net from 'node:net';
3
3
  import fs from 'node:fs';
4
4
  import { execSync } from 'node:child_process';
5
5
  import { createHTTPHandler } from '@trpc/server/adapters/standalone';
6
- import { appRouter } from './router.js';
6
+ import { userRouter, agentRouter } from './api/index.js';
7
7
  import {
8
8
  getSocketPath,
9
9
  getClawminiDir,
@@ -114,7 +114,7 @@ export async function initDaemon() {
114
114
  });
115
115
 
116
116
  const handler = createHTTPHandler({
117
- router: appRouter,
117
+ router: userRouter,
118
118
  createContext: ({ req, res }) => ({ req, res, isApiServer: false }),
119
119
  });
120
120
 
@@ -153,7 +153,7 @@ export async function initDaemon() {
153
153
  let apiServer: http.Server | undefined;
154
154
  if (apiCtx) {
155
155
  const apiHandler = createHTTPHandler({
156
- router: appRouter,
156
+ router: agentRouter,
157
157
  createContext: ({ req, res }) => {
158
158
  let tokenPayload = null;
159
159
  const authHeader = req.headers.authorization;
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { executeDirectMessage } from './message.js';
3
- import { getQueue } from './queue.js';
3
+ import { getMessageQueue } from './queue.js';
4
4
  import type { RouterState } from './routers/types.js';
5
5
 
6
6
  vi.mock('./chats.js', () => ({
@@ -25,7 +25,7 @@ describe('Interruption flow in message handler', () => {
25
25
  });
26
26
 
27
27
  it('stops execution and clears queue when action is stop', async () => {
28
- const queue = getQueue('/test-interrupt-stop');
28
+ const queue = getMessageQueue('/test-interrupt-stop');
29
29
  const abortSpy = vi.spyOn(queue, 'abortCurrent');
30
30
  const clearSpy = vi.spyOn(queue, 'clear');
31
31
 
@@ -48,7 +48,7 @@ describe('Interruption flow in message handler', () => {
48
48
  });
49
49
 
50
50
  it('interrupts execution and batches pending tasks when action is interrupt', async () => {
51
- const queue = getQueue('/test-interrupt-batch');
51
+ const queue = getMessageQueue('/test-interrupt-batch');
52
52
  const abortSpy = vi.spyOn(queue, 'abortCurrent');
53
53
 
54
54
  // Block the queue with a running task so subsequent ones stay pending
@@ -60,14 +60,20 @@ describe('Interruption flow in message handler', () => {
60
60
 
61
61
  // Enqueue some dummy tasks with payloads
62
62
  queue
63
- .enqueue(async () => {
64
- await new Promise((r) => setTimeout(r, 100));
65
- }, 'pending 1')
63
+ .enqueue(
64
+ async () => {
65
+ await new Promise((r) => setTimeout(r, 100));
66
+ },
67
+ { text: 'pending 1', sessionId: 'test-session' }
68
+ )
66
69
  .catch(() => {});
67
70
  queue
68
- .enqueue(async () => {
69
- await new Promise((r) => setTimeout(r, 100));
70
- }, 'pending 2')
71
+ .enqueue(
72
+ async () => {
73
+ await new Promise((r) => setTimeout(r, 100));
74
+ },
75
+ { text: 'pending 2', sessionId: 'test-session' }
76
+ )
71
77
  .catch(() => {});
72
78
 
73
79
  const state: RouterState = {
@@ -75,6 +81,7 @@ describe('Interruption flow in message handler', () => {
75
81
  messageId: 'mock-msg-id',
76
82
  chatId: 'chat1',
77
83
  action: 'interrupt',
84
+ sessionId: 'test-session',
78
85
  };
79
86
 
80
87
  const runCommand = vi.fn().mockResolvedValue({ stdout: 'done', stderr: '', exitCode: 0 });
@@ -102,7 +109,7 @@ describe('Interruption flow in message handler', () => {
102
109
  });
103
110
 
104
111
  it('returns early when message is empty and no action is specified', async () => {
105
- const queue = getQueue('/test-interrupt-empty');
112
+ const queue = getMessageQueue('/test-interrupt-empty');
106
113
  const state: RouterState = {
107
114
  message: ' ',
108
115
  messageId: 'mock-msg-id',
@@ -17,7 +17,7 @@ vi.mock('../shared/chats.js', () => ({
17
17
  }));
18
18
 
19
19
  vi.mock('./queue.js', () => ({
20
- getQueue: vi.fn().mockReturnValue({
20
+ getMessageQueue: vi.fn().mockReturnValue({
21
21
  enqueue: (fn: any) => fn(),
22
22
  }),
23
23
  }));