@agentmeshhq/agent 0.2.0 → 0.2.1

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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/__tests__/orphan-process.test.d.ts +11 -0
  4. package/dist/__tests__/orphan-process.test.js +286 -0
  5. package/dist/__tests__/orphan-process.test.js.map +1 -0
  6. package/dist/__tests__/runner.test.js +16 -0
  7. package/dist/__tests__/runner.test.js.map +1 -1
  8. package/dist/__tests__/watchdog.test.js +138 -12
  9. package/dist/__tests__/watchdog.test.js.map +1 -1
  10. package/dist/cli/index.js +0 -0
  11. package/dist/cli/status.js +11 -0
  12. package/dist/cli/status.js.map +1 -1
  13. package/dist/cli/stop.js +7 -2
  14. package/dist/cli/stop.js.map +1 -1
  15. package/dist/config/schema.d.ts +4 -2
  16. package/dist/core/daemon/assignment-message.d.ts +12 -0
  17. package/dist/core/daemon/assignment-message.js +36 -0
  18. package/dist/core/daemon/assignment-message.js.map +1 -0
  19. package/dist/core/daemon/bootstrap.d.ts +35 -0
  20. package/dist/core/daemon/bootstrap.js +52 -0
  21. package/dist/core/daemon/bootstrap.js.map +1 -0
  22. package/dist/core/daemon/crash-log.d.ts +16 -0
  23. package/dist/core/daemon/crash-log.js +24 -0
  24. package/dist/core/daemon/crash-log.js.map +1 -0
  25. package/dist/core/daemon/health-policy.d.ts +21 -0
  26. package/dist/core/daemon/health-policy.js +32 -0
  27. package/dist/core/daemon/health-policy.js.map +1 -0
  28. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  29. package/dist/core/daemon/sandbox-config.js +17 -0
  30. package/dist/core/daemon/sandbox-config.js.map +1 -0
  31. package/dist/core/daemon/state.d.ts +33 -0
  32. package/dist/core/daemon/state.js +77 -0
  33. package/dist/core/daemon/state.js.map +1 -0
  34. package/dist/core/daemon/tmux-session.d.ts +17 -0
  35. package/dist/core/daemon/tmux-session.js +34 -0
  36. package/dist/core/daemon/tmux-session.js.map +1 -0
  37. package/dist/core/daemon/workspace.d.ts +10 -0
  38. package/dist/core/daemon/workspace.js +51 -0
  39. package/dist/core/daemon/workspace.js.map +1 -0
  40. package/dist/core/daemon.d.ts +0 -6
  41. package/dist/core/daemon.js +123 -244
  42. package/dist/core/daemon.js.map +1 -1
  43. package/dist/core/injector.js +6 -0
  44. package/dist/core/injector.js.map +1 -1
  45. package/dist/core/runner/build.d.ts +9 -0
  46. package/dist/core/runner/build.js +53 -0
  47. package/dist/core/runner/build.js.map +1 -0
  48. package/dist/core/runner/detect.d.ts +5 -0
  49. package/dist/core/runner/detect.js +14 -0
  50. package/dist/core/runner/detect.js.map +1 -0
  51. package/dist/core/runner/index.d.ts +5 -0
  52. package/dist/core/runner/index.js +5 -0
  53. package/dist/core/runner/index.js.map +1 -0
  54. package/dist/core/runner/model.d.ts +5 -0
  55. package/dist/core/runner/model.js +7 -0
  56. package/dist/core/runner/model.js.map +1 -0
  57. package/dist/core/runner/opencode-models.d.ts +15 -0
  58. package/dist/core/runner/opencode-models.js +70 -0
  59. package/dist/core/runner/opencode-models.js.map +1 -0
  60. package/dist/core/runner/types.d.ts +19 -0
  61. package/dist/core/runner/types.js +8 -0
  62. package/dist/core/runner/types.js.map +1 -0
  63. package/dist/core/runner.d.ts +5 -47
  64. package/dist/core/runner.js +5 -167
  65. package/dist/core/runner.js.map +1 -1
  66. package/dist/core/tmux-runtime.d.ts +13 -0
  67. package/dist/core/tmux-runtime.js +72 -0
  68. package/dist/core/tmux-runtime.js.map +1 -0
  69. package/dist/core/tmux.d.ts +7 -1
  70. package/dist/core/tmux.js +75 -45
  71. package/dist/core/tmux.js.map +1 -1
  72. package/dist/core/watchdog.d.ts +18 -1
  73. package/dist/core/watchdog.js +78 -29
  74. package/dist/core/watchdog.js.map +1 -1
  75. package/package.json +30 -11
  76. package/dist/cli/inbox.d.ts +0 -5
  77. package/dist/cli/inbox.js +0 -123
  78. package/dist/cli/inbox.js.map +0 -1
  79. package/dist/cli/issue.d.ts +0 -42
  80. package/dist/cli/issue.js +0 -297
  81. package/dist/cli/issue.js.map +0 -1
  82. package/dist/cli/ready.d.ts +0 -5
  83. package/dist/cli/ready.js +0 -131
  84. package/dist/cli/ready.js.map +0 -1
  85. package/dist/cli/sync.d.ts +0 -8
  86. package/dist/cli/sync.js +0 -154
  87. package/dist/cli/sync.js.map +0 -1
  88. package/dist/core/issue-cache.d.ts +0 -44
  89. package/dist/core/issue-cache.js +0 -75
  90. package/dist/core/issue-cache.js.map +0 -1
  91. package/src/__tests__/context.test.ts +0 -464
  92. package/src/__tests__/injector.test.ts +0 -29
  93. package/src/__tests__/jwt.test.ts +0 -112
  94. package/src/__tests__/loader.test.ts +0 -239
  95. package/src/__tests__/runner.test.ts +0 -104
  96. package/src/__tests__/sandbox.test.ts +0 -435
  97. package/src/__tests__/watchdog.test.ts +0 -368
  98. package/src/cli/attach.ts +0 -22
  99. package/src/cli/build.ts +0 -145
  100. package/src/cli/config.ts +0 -148
  101. package/src/cli/context.ts +0 -231
  102. package/src/cli/deploy.ts +0 -155
  103. package/src/cli/index.ts +0 -376
  104. package/src/cli/init.ts +0 -75
  105. package/src/cli/list.ts +0 -70
  106. package/src/cli/local.ts +0 -183
  107. package/src/cli/logs.ts +0 -64
  108. package/src/cli/migrate.ts +0 -212
  109. package/src/cli/nudge.ts +0 -81
  110. package/src/cli/restart.ts +0 -59
  111. package/src/cli/slack.ts +0 -70
  112. package/src/cli/start.ts +0 -118
  113. package/src/cli/status.ts +0 -91
  114. package/src/cli/stop.ts +0 -48
  115. package/src/cli/test.ts +0 -143
  116. package/src/cli/token.ts +0 -188
  117. package/src/cli/whoami.ts +0 -142
  118. package/src/config/loader.ts +0 -121
  119. package/src/config/schema.ts +0 -68
  120. package/src/context/handoff.ts +0 -122
  121. package/src/context/index.ts +0 -8
  122. package/src/context/schema.ts +0 -111
  123. package/src/context/storage.ts +0 -197
  124. package/src/core/daemon.ts +0 -1317
  125. package/src/core/heartbeat.ts +0 -129
  126. package/src/core/injector.ts +0 -292
  127. package/src/core/registry.ts +0 -159
  128. package/src/core/runner.ts +0 -225
  129. package/src/core/sandbox.ts +0 -547
  130. package/src/core/session-id.ts +0 -111
  131. package/src/core/tmux.ts +0 -405
  132. package/src/core/watchdog.ts +0 -238
  133. package/src/core/websocket.ts +0 -94
  134. package/src/index.ts +0 -10
  135. package/src/utils/jwt.ts +0 -87
  136. package/tsconfig.json +0 -8
  137. package/vitest.config.ts +0 -12
@@ -1,44 +0,0 @@
1
- /**
2
- * Issue Cache - Local storage for offline support
3
- */
4
- export interface Issue {
5
- issue_id: string;
6
- workspace_id: string;
7
- project_id: string | null;
8
- title: string;
9
- description: string | null;
10
- type: string;
11
- priority: string;
12
- status: string;
13
- assignee_agent_id: string | null;
14
- assignee_user_id: string | null;
15
- external_provider: string | null;
16
- external_ref: string | null;
17
- external_url: string | null;
18
- parent_issue_id: string | null;
19
- labels: string[];
20
- metadata: Record<string, unknown>;
21
- created_at: string;
22
- updated_at: string;
23
- synced_at: string | null;
24
- }
25
- export interface PendingChange {
26
- action: "create" | "update" | "delete";
27
- issueId?: string;
28
- data?: Partial<Issue>;
29
- queuedAt: string;
30
- }
31
- export interface IssueCache {
32
- workspace: string;
33
- lastSync: string | null;
34
- issues: Issue[];
35
- pendingChanges: PendingChange[];
36
- }
37
- export declare function loadCache(workspace: string): IssueCache;
38
- export declare function saveCache(cache: IssueCache): void;
39
- export declare function updateCacheIssues(workspace: string, issues: Issue[]): void;
40
- export declare function getCachedIssue(workspace: string, issueId: string): Issue | null;
41
- export declare function queueChange(workspace: string, change: PendingChange): void;
42
- export declare function clearPendingChanges(workspace: string): void;
43
- export declare function hasPendingChanges(workspace: string): boolean;
44
- export declare function getLastSyncTime(workspace: string): string | null;
@@ -1,75 +0,0 @@
1
- /**
2
- * Issue Cache - Local storage for offline support
3
- */
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { join } from "node:path";
6
- import { homedir } from "node:os";
7
- function getCacheDir() {
8
- return join(homedir(), ".agentmesh", "cache");
9
- }
10
- function getCachePath(workspace) {
11
- return join(getCacheDir(), `issues-${workspace}.json`);
12
- }
13
- function ensureCacheDir() {
14
- const dir = getCacheDir();
15
- if (!existsSync(dir)) {
16
- mkdirSync(dir, { recursive: true });
17
- }
18
- }
19
- export function loadCache(workspace) {
20
- const path = getCachePath(workspace);
21
- if (!existsSync(path)) {
22
- return {
23
- workspace,
24
- lastSync: null,
25
- issues: [],
26
- pendingChanges: [],
27
- };
28
- }
29
- try {
30
- const data = readFileSync(path, "utf-8");
31
- return JSON.parse(data);
32
- }
33
- catch {
34
- return {
35
- workspace,
36
- lastSync: null,
37
- issues: [],
38
- pendingChanges: [],
39
- };
40
- }
41
- }
42
- export function saveCache(cache) {
43
- ensureCacheDir();
44
- const path = getCachePath(cache.workspace);
45
- writeFileSync(path, JSON.stringify(cache, null, 2));
46
- }
47
- export function updateCacheIssues(workspace, issues) {
48
- const cache = loadCache(workspace);
49
- cache.issues = issues;
50
- cache.lastSync = new Date().toISOString();
51
- saveCache(cache);
52
- }
53
- export function getCachedIssue(workspace, issueId) {
54
- const cache = loadCache(workspace);
55
- return cache.issues.find((i) => i.issue_id === issueId) || null;
56
- }
57
- export function queueChange(workspace, change) {
58
- const cache = loadCache(workspace);
59
- cache.pendingChanges.push(change);
60
- saveCache(cache);
61
- }
62
- export function clearPendingChanges(workspace) {
63
- const cache = loadCache(workspace);
64
- cache.pendingChanges = [];
65
- saveCache(cache);
66
- }
67
- export function hasPendingChanges(workspace) {
68
- const cache = loadCache(workspace);
69
- return cache.pendingChanges.length > 0;
70
- }
71
- export function getLastSyncTime(workspace) {
72
- const cache = loadCache(workspace);
73
- return cache.lastSync;
74
- }
75
- //# sourceMappingURL=issue-cache.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"issue-cache.js","sourceRoot":"","sources":["../../src/core/issue-cache.ts"],"names":[],"mappings":"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"}
@@ -1,464 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
- import {
5
- extractHandoffContext,
6
- formatHandoffContextSummary,
7
- parseHandoffContext,
8
- serializeHandoffContext,
9
- } from "../context/handoff.js";
10
- import { type AgentContext, CONTEXT_VERSION, createEmptyContext } from "../context/schema.js";
11
- import {
12
- deleteContext,
13
- exportContext,
14
- getContextPath,
15
- importContext,
16
- listContexts,
17
- loadContext,
18
- loadOrCreateContext,
19
- saveContext,
20
- updateContext,
21
- } from "../context/storage.js";
22
-
23
- // Mock fs module
24
- vi.mock("node:fs");
25
-
26
- const mockFs = vi.mocked(fs);
27
-
28
- describe("Context Schema", () => {
29
- describe("createEmptyContext", () => {
30
- it("creates context with correct version", () => {
31
- const context = createEmptyContext("agent-123", "test-agent");
32
- expect(context.version).toBe(CONTEXT_VERSION);
33
- });
34
-
35
- it("creates context with agent info", () => {
36
- const context = createEmptyContext("agent-123", "test-agent");
37
- expect(context.agentId).toBe("agent-123");
38
- expect(context.agentName).toBe("test-agent");
39
- });
40
-
41
- it("creates context with empty conversation", () => {
42
- const context = createEmptyContext("agent-123", "test-agent");
43
- expect(context.conversation.messageCount).toBe(0);
44
- expect(context.conversation.topics).toEqual([]);
45
- expect(context.conversation.accomplishments).toEqual([]);
46
- expect(context.conversation.recentMessages).toEqual([]);
47
- });
48
-
49
- it("creates context with working state", () => {
50
- const context = createEmptyContext("agent-123", "test-agent");
51
- expect(context.workingState.workdir).toBe(process.cwd());
52
- expect(context.workingState.recentFiles).toEqual([]);
53
- expect(context.workingState.openFiles).toEqual([]);
54
- });
55
-
56
- it("creates context with empty tasks", () => {
57
- const context = createEmptyContext("agent-123", "test-agent");
58
- expect(context.tasks.tasks).toEqual([]);
59
- expect(context.tasks.currentGoal).toBeUndefined();
60
- });
61
- });
62
- });
63
-
64
- describe("Context Storage", () => {
65
- const testAgentId = "test-agent-123";
66
- const testContext: AgentContext = {
67
- version: CONTEXT_VERSION,
68
- agentId: testAgentId,
69
- agentName: "test-agent",
70
- savedAt: "2026-02-24T10:00:00.000Z",
71
- conversation: {
72
- messageCount: 5,
73
- topics: ["testing", "context"],
74
- accomplishments: ["wrote tests"],
75
- recentMessages: [],
76
- },
77
- workingState: {
78
- workdir: "/home/user/project",
79
- recentFiles: ["file1.ts", "file2.ts"],
80
- openFiles: ["file1.ts"],
81
- gitBranch: "main",
82
- },
83
- tasks: {
84
- tasks: [{ content: "test task", status: "in_progress", priority: "high" }],
85
- currentGoal: "implement context",
86
- },
87
- custom: { key: "value" },
88
- };
89
-
90
- beforeEach(() => {
91
- vi.clearAllMocks();
92
- mockFs.existsSync.mockReturnValue(true);
93
- });
94
-
95
- afterEach(() => {
96
- vi.resetAllMocks();
97
- });
98
-
99
- describe("getContextPath", () => {
100
- it("returns correct path for agent ID", () => {
101
- const contextPath = getContextPath("agent-123");
102
- expect(contextPath).toContain("agent-123.json");
103
- expect(contextPath).toContain(".agentmesh/context");
104
- });
105
- });
106
-
107
- describe("saveContext", () => {
108
- it("creates context directory if needed", () => {
109
- mockFs.existsSync.mockReturnValue(false);
110
- mockFs.mkdirSync.mockReturnValue(undefined);
111
- mockFs.writeFileSync.mockReturnValue(undefined);
112
-
113
- saveContext(testContext);
114
-
115
- expect(mockFs.mkdirSync).toHaveBeenCalledWith(expect.stringContaining(".agentmesh/context"), {
116
- recursive: true,
117
- });
118
- });
119
-
120
- it("writes context to file", () => {
121
- mockFs.writeFileSync.mockReturnValue(undefined);
122
-
123
- saveContext(testContext);
124
-
125
- expect(mockFs.writeFileSync).toHaveBeenCalledWith(
126
- expect.stringContaining(`${testAgentId}.json`),
127
- expect.any(String),
128
- );
129
- });
130
-
131
- it("updates savedAt timestamp", () => {
132
- mockFs.writeFileSync.mockReturnValue(undefined);
133
- const before = new Date().toISOString();
134
-
135
- saveContext(testContext);
136
-
137
- const writtenData = JSON.parse(mockFs.writeFileSync.mock.calls[0][1] as string);
138
- expect(new Date(writtenData.savedAt).getTime()).toBeGreaterThanOrEqual(
139
- new Date(before).getTime(),
140
- );
141
- });
142
- });
143
-
144
- describe("loadContext", () => {
145
- it("returns null if file does not exist", () => {
146
- mockFs.existsSync.mockReturnValue(false);
147
-
148
- const result = loadContext("nonexistent");
149
-
150
- expect(result).toBeNull();
151
- });
152
-
153
- it("loads and parses context file", () => {
154
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
155
-
156
- const result = loadContext(testAgentId);
157
-
158
- expect(result).toEqual(testContext);
159
- });
160
-
161
- it("returns null for version mismatch", () => {
162
- const oldContext = { ...testContext, version: 0 };
163
- mockFs.readFileSync.mockReturnValue(JSON.stringify(oldContext));
164
-
165
- const result = loadContext(testAgentId);
166
-
167
- expect(result).toBeNull();
168
- });
169
-
170
- it("returns null for invalid JSON", () => {
171
- mockFs.readFileSync.mockReturnValue("invalid json");
172
-
173
- const result = loadContext(testAgentId);
174
-
175
- expect(result).toBeNull();
176
- });
177
- });
178
-
179
- describe("loadOrCreateContext", () => {
180
- it("returns existing context if found", () => {
181
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
182
-
183
- const result = loadOrCreateContext(testAgentId, "test-agent");
184
-
185
- expect(result).toEqual(testContext);
186
- });
187
-
188
- it("creates new context if not found", () => {
189
- mockFs.existsSync.mockReturnValue(false);
190
-
191
- const result = loadOrCreateContext("new-agent", "new-name");
192
-
193
- expect(result.agentId).toBe("new-agent");
194
- expect(result.agentName).toBe("new-name");
195
- expect(result.version).toBe(CONTEXT_VERSION);
196
- });
197
- });
198
-
199
- describe("deleteContext", () => {
200
- it("deletes context file if exists", () => {
201
- mockFs.unlinkSync.mockReturnValue(undefined);
202
-
203
- const result = deleteContext(testAgentId);
204
-
205
- expect(result).toBe(true);
206
- expect(mockFs.unlinkSync).toHaveBeenCalled();
207
- });
208
-
209
- it("returns false if file does not exist", () => {
210
- mockFs.existsSync.mockReturnValue(false);
211
-
212
- const result = deleteContext("nonexistent");
213
-
214
- expect(result).toBe(false);
215
- });
216
- });
217
-
218
- describe("listContexts", () => {
219
- it("returns list of saved contexts", () => {
220
- mockFs.readdirSync.mockReturnValue(["agent1.json", "agent2.json"] as unknown as ReturnType<
221
- typeof fs.readdirSync
222
- >);
223
- mockFs.readFileSync.mockImplementation((filePath) => {
224
- const id = path.basename(filePath as string, ".json");
225
- return JSON.stringify({
226
- ...testContext,
227
- agentId: id,
228
- savedAt: id === "agent1" ? "2026-02-24T11:00:00.000Z" : "2026-02-24T10:00:00.000Z",
229
- });
230
- });
231
-
232
- const result = listContexts();
233
-
234
- expect(result).toHaveLength(2);
235
- expect(result[0].agentId).toBe("agent1"); // More recent first
236
- });
237
-
238
- it("filters non-json files", () => {
239
- mockFs.readdirSync.mockReturnValue([
240
- "agent1.json",
241
- "readme.md",
242
- ".gitkeep",
243
- ] as unknown as ReturnType<typeof fs.readdirSync>);
244
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
245
-
246
- const result = listContexts();
247
-
248
- expect(result).toHaveLength(1);
249
- });
250
- });
251
-
252
- describe("updateContext", () => {
253
- it("updates context fields", () => {
254
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
255
- mockFs.writeFileSync.mockReturnValue(undefined);
256
-
257
- const result = updateContext(testAgentId, {
258
- conversation: {
259
- ...testContext.conversation,
260
- messageCount: 10,
261
- },
262
- });
263
-
264
- expect(result?.conversation.messageCount).toBe(10);
265
- });
266
-
267
- it("returns null if context not found", () => {
268
- mockFs.existsSync.mockReturnValue(false);
269
-
270
- const result = updateContext("nonexistent", {});
271
-
272
- expect(result).toBeNull();
273
- });
274
-
275
- it("preserves version and agentId", () => {
276
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
277
- mockFs.writeFileSync.mockReturnValue(undefined);
278
-
279
- const result = updateContext(testAgentId, {
280
- agentName: "new-name",
281
- });
282
-
283
- expect(result?.version).toBe(CONTEXT_VERSION);
284
- expect(result?.agentId).toBe(testAgentId);
285
- });
286
- });
287
-
288
- describe("exportContext", () => {
289
- it("exports context to file", () => {
290
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
291
- mockFs.writeFileSync.mockReturnValue(undefined);
292
-
293
- const result = exportContext(testAgentId, "/tmp/export.json");
294
-
295
- expect(result).toBe(true);
296
- expect(mockFs.writeFileSync).toHaveBeenCalledWith("/tmp/export.json", expect.any(String));
297
- });
298
-
299
- it("returns false if context not found", () => {
300
- mockFs.existsSync.mockReturnValue(false);
301
-
302
- const result = exportContext("nonexistent", "/tmp/export.json");
303
-
304
- expect(result).toBe(false);
305
- });
306
- });
307
-
308
- describe("importContext", () => {
309
- it("imports context from file", () => {
310
- mockFs.readFileSync.mockReturnValue(JSON.stringify(testContext));
311
- mockFs.writeFileSync.mockReturnValue(undefined);
312
-
313
- const result = importContext("/tmp/import.json");
314
-
315
- expect(result).not.toBeNull();
316
- expect(result?.agentId).toBe(testAgentId);
317
- });
318
-
319
- it("returns null if file not found", () => {
320
- mockFs.existsSync.mockReturnValue(false);
321
-
322
- const result = importContext("/tmp/nonexistent.json");
323
-
324
- expect(result).toBeNull();
325
- });
326
-
327
- it("returns null for invalid context", () => {
328
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ invalid: "context" }));
329
-
330
- const result = importContext("/tmp/invalid.json");
331
-
332
- expect(result).toBeNull();
333
- });
334
- });
335
- });
336
-
337
- describe("Handoff Context", () => {
338
- const testContext: AgentContext = {
339
- version: CONTEXT_VERSION,
340
- agentId: "test-agent-123",
341
- agentName: "test-agent",
342
- savedAt: "2026-02-24T10:00:00.000Z",
343
- conversation: {
344
- messageCount: 5,
345
- topics: ["testing", "context", "handoff"],
346
- accomplishments: ["wrote tests", "added feature"],
347
- recentMessages: [],
348
- },
349
- workingState: {
350
- workdir: "/home/user/project",
351
- recentFiles: ["file1.ts", "file2.ts"],
352
- openFiles: ["file1.ts"],
353
- gitBranch: "feature/context",
354
- },
355
- tasks: {
356
- tasks: [
357
- { content: "task in progress", status: "in_progress", priority: "high" },
358
- { content: "pending task", status: "pending", priority: "medium" },
359
- { content: "completed task", status: "completed", priority: "low" },
360
- ],
361
- currentGoal: "implement context persistence",
362
- },
363
- custom: { key: "value" },
364
- };
365
-
366
- describe("extractHandoffContext", () => {
367
- it("extracts workdir and git branch", () => {
368
- const handoff = extractHandoffContext(testContext);
369
- expect(handoff.workdir).toBe("/home/user/project");
370
- expect(handoff.gitBranch).toBe("feature/context");
371
- });
372
-
373
- it("extracts current goal", () => {
374
- const handoff = extractHandoffContext(testContext);
375
- expect(handoff.currentGoal).toBe("implement context persistence");
376
- });
377
-
378
- it("only includes active tasks", () => {
379
- const handoff = extractHandoffContext(testContext);
380
- expect(handoff.activeTasks).toHaveLength(2);
381
- expect(handoff.activeTasks[0].content).toBe("task in progress");
382
- expect(handoff.activeTasks[1].content).toBe("pending task");
383
- });
384
-
385
- it("includes recent accomplishments", () => {
386
- const handoff = extractHandoffContext(testContext);
387
- expect(handoff.recentAccomplishments).toContain("wrote tests");
388
- });
389
-
390
- it("includes topics", () => {
391
- const handoff = extractHandoffContext(testContext);
392
- expect(handoff.topics).toContain("testing");
393
- });
394
-
395
- it("includes custom context if present", () => {
396
- const handoff = extractHandoffContext(testContext);
397
- expect(handoff.custom).toEqual({ key: "value" });
398
- });
399
- });
400
-
401
- describe("serializeHandoffContext", () => {
402
- it("serializes to JSON string", () => {
403
- const handoff = extractHandoffContext(testContext);
404
- const serialized = serializeHandoffContext(handoff);
405
- expect(typeof serialized).toBe("string");
406
- expect(JSON.parse(serialized)).toEqual(handoff);
407
- });
408
- });
409
-
410
- describe("parseHandoffContext", () => {
411
- it("parses valid JSON", () => {
412
- const handoff = extractHandoffContext(testContext);
413
- const serialized = serializeHandoffContext(handoff);
414
- const parsed = parseHandoffContext(serialized);
415
- expect(parsed).toEqual(handoff);
416
- });
417
-
418
- it("returns null for invalid JSON", () => {
419
- const parsed = parseHandoffContext("invalid json");
420
- expect(parsed).toBeNull();
421
- });
422
- });
423
-
424
- describe("formatHandoffContextSummary", () => {
425
- it("formats workdir", () => {
426
- const handoff = extractHandoffContext(testContext);
427
- const summary = formatHandoffContextSummary(handoff);
428
- expect(summary).toContain("Working directory: /home/user/project");
429
- });
430
-
431
- it("formats git branch", () => {
432
- const handoff = extractHandoffContext(testContext);
433
- const summary = formatHandoffContextSummary(handoff);
434
- expect(summary).toContain("Git branch: feature/context");
435
- });
436
-
437
- it("formats current goal", () => {
438
- const handoff = extractHandoffContext(testContext);
439
- const summary = formatHandoffContextSummary(handoff);
440
- expect(summary).toContain("Current goal: implement context persistence");
441
- });
442
-
443
- it("formats active tasks", () => {
444
- const handoff = extractHandoffContext(testContext);
445
- const summary = formatHandoffContextSummary(handoff);
446
- expect(summary).toContain("Active tasks:");
447
- expect(summary).toContain("> task in progress");
448
- expect(summary).toContain("- pending task");
449
- });
450
-
451
- it("formats accomplishments", () => {
452
- const handoff = extractHandoffContext(testContext);
453
- const summary = formatHandoffContextSummary(handoff);
454
- expect(summary).toContain("Recent accomplishments:");
455
- expect(summary).toContain("- wrote tests");
456
- });
457
-
458
- it("formats topics", () => {
459
- const handoff = extractHandoffContext(testContext);
460
- const summary = formatHandoffContextSummary(handoff);
461
- expect(summary).toContain("Topics:");
462
- });
463
- });
464
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { handleWebSocketEvent } from "../core/injector.js";
3
-
4
- const { sendKeysMock } = vi.hoisted(() => ({
5
- sendKeysMock: vi.fn(),
6
- }));
7
-
8
- vi.mock("../core/tmux.js", () => ({
9
- sendKeys: sendKeysMock,
10
- }));
11
-
12
- describe("injector slack message handling", () => {
13
- it("injects Slack message event into agent terminal", () => {
14
- handleWebSocketEvent("concierge", {
15
- type: "slack.message",
16
- user: "U123",
17
- channel: "C123",
18
- text: "Can you check deployment status?",
19
- ts: "1730000000.1234",
20
- });
21
-
22
- expect(sendKeysMock).toHaveBeenCalledTimes(1);
23
- expect(sendKeysMock.mock.calls[0]?.[0]).toBe("concierge");
24
- const injected = String(sendKeysMock.mock.calls[0]?.[1] ?? "");
25
- // The injector formats as: [Slack from {user} in {channel}] {text}
26
- expect(injected).toContain("[Slack from U123 in C123]");
27
- expect(injected).toContain("Can you check deployment status?");
28
- });
29
- });