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.
- package/.github/workflows/ci.yml +59 -0
- package/README.md +61 -76
- package/dist/adapter-discord/index.d.mts.map +1 -1
- package/dist/adapter-discord/index.mjs +13 -4
- package/dist/adapter-discord/index.mjs.map +1 -1
- package/dist/cli/index.mjs +8 -6
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lite.mjs +64 -10
- package/dist/cli/lite.mjs.map +1 -1
- package/dist/daemon/index.mjs +732 -251
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{fetch-BjZVyU3Z.mjs → fetch-Cn1XNyiO.mjs} +1 -1
- package/dist/{fetch-BjZVyU3Z.mjs.map → fetch-Cn1XNyiO.mjs.map} +1 -1
- package/dist/lite-oSYSvaOr.mjs +164 -0
- package/dist/lite-oSYSvaOr.mjs.map +1 -0
- package/dist/web/_app/immutable/chunks/{COekwvP2.js → 8YNcRyEk.js} +1 -1
- package/dist/web/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
- package/dist/web/_app/immutable/entry/{app.B-vZe7PN.js → app.DO5eYwVz.js} +2 -2
- package/dist/web/_app/immutable/entry/start.D48mVn1m.js +1 -0
- package/dist/web/_app/immutable/nodes/{0.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
- package/dist/web/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
- package/dist/web/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
- package/dist/web/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
- package/dist/web/_app/immutable/nodes/{5.BJl7oM3b.js → 5.BpJUN6QH.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +6 -6
- package/dist/{workspace-CSgfo_2J.mjs → workspace-DjoNjhW0.mjs} +21 -40
- package/dist/workspace-DjoNjhW0.mjs.map +1 -0
- package/docs/15_lite_fetch_pending/development_log.md +31 -0
- package/docs/15_lite_fetch_pending/notes.md +48 -0
- package/docs/15_lite_fetch_pending/prd.md +39 -0
- package/docs/15_lite_fetch_pending/questions.md +3 -0
- package/docs/15_lite_fetch_pending/tickets.md +42 -0
- package/docs/CHECKS.md +2 -2
- package/docs/CLI_REFERENCE.md +35 -0
- package/docs/guides/sandbox_policies.md +12 -5
- package/eslint.config.js +12 -0
- package/package.json +3 -2
- package/src/adapter-discord/client.ts +1 -1
- package/src/adapter-discord/index.ts +22 -5
- package/src/cli/client.ts +8 -3
- package/src/cli/e2e/adapter-discord.test.ts +2 -2
- package/src/cli/e2e/daemon.test.ts +2 -1
- package/src/cli/e2e/export-lite-func.test.ts +41 -13
- package/src/cli/e2e/fallbacks.test.ts +4 -0
- package/src/cli/lite.ts +24 -6
- package/src/daemon/api/agent-router.ts +191 -0
- package/src/daemon/{router.test.ts → api/index.test.ts} +101 -34
- package/src/daemon/api/index.ts +4 -0
- package/src/daemon/{router-policy-request.test.ts → api/policy-request.test.ts} +27 -13
- package/src/daemon/api/router-utils.ts +159 -0
- package/src/daemon/api/trpc.ts +30 -0
- package/src/daemon/api/user-router.ts +221 -0
- package/src/daemon/index.ts +3 -3
- package/src/daemon/message-interruption.test.ts +17 -10
- package/src/daemon/message-typing.test.ts +1 -1
- package/src/daemon/message.ts +260 -239
- package/src/daemon/observation.test.ts +1 -1
- package/src/daemon/queue.test.ts +28 -0
- package/src/daemon/queue.ts +30 -15
- package/src/daemon/request-store.test.ts +4 -4
- package/src/daemon/request-store.ts +3 -1
- package/src/shared/workspace.ts +4 -5
- package/templates/debug/settings.json +5 -0
- package/templates/environments/macos/env.json +1 -1
- package/templates/environments/macos-proxy/env.json +1 -1
- package/templates/gemini-claw/.gemini/hooks/insert-pending.sh +9 -0
- package/templates/gemini-claw/.gemini/settings.json +14 -1
- package/templates/gemini-claw/.gemini/system.md +2 -0
- package/web/.svelte-kit/ambient.d.ts +2 -6
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +29 -29
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{COekwvP2.js → 8YNcRyEk.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{CSvS_NwK.js → DQoygso7.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/{app.B-vZe7PN.js → app.DO5eYwVz.js} +2 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.B5WFN0zw.js → 0.B-0CcADM.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.D1wtJb2k.js → 1.FixKgvRO.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.BB5wCoBf.js → 3.ncP0xLO6.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.Dr2jvAXK.js → 4.CQYJEgv8.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BJl7oM3b.js → 5.BpJUN6QH.js} +1 -1
- package/web/.svelte-kit/output/client/_app/version.json +1 -1
- package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
- package/web/.svelte-kit/output/server/manifest-full.js +1 -1
- package/web/.svelte-kit/output/server/manifest.js +1 -1
- package/web/.svelte-kit/output/server/nodes/0.js +1 -1
- package/web/.svelte-kit/output/server/nodes/1.js +1 -1
- package/web/.svelte-kit/output/server/nodes/3.js +1 -1
- package/web/.svelte-kit/output/server/nodes/4.js +1 -1
- package/web/.svelte-kit/output/server/nodes/5.js +1 -1
- package/dist/chats-DKgTeU7i.mjs +0 -91
- package/dist/chats-DKgTeU7i.mjs.map +0 -1
- package/dist/chats-Zd_HXDHx.mjs +0 -29
- package/dist/chats-Zd_HXDHx.mjs.map +0 -1
- package/dist/fs-B5wW0oaH.mjs +0 -14
- package/dist/fs-B5wW0oaH.mjs.map +0 -1
- package/dist/lite-Dl7WXyaH.mjs +0 -80
- package/dist/lite-Dl7WXyaH.mjs.map +0 -1
- package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
- package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +0 -1
- package/dist/workspace-CSgfo_2J.mjs.map +0 -1
- package/src/daemon/router.ts +0 -510
- 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;
|
package/src/daemon/index.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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:
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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(
|
|
64
|
-
|
|
65
|
-
|
|
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(
|
|
69
|
-
|
|
70
|
-
|
|
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 =
|
|
112
|
+
const queue = getMessageQueue('/test-interrupt-empty');
|
|
106
113
|
const state: RouterState = {
|
|
107
114
|
message: ' ',
|
|
108
115
|
messageId: 'mock-msg-id',
|