@agentmeshhq/agent 0.1.17 → 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 (143) 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 +2 -1
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/start.d.ts +2 -1
  13. package/dist/cli/start.js +6 -3
  14. package/dist/cli/start.js.map +1 -1
  15. package/dist/cli/status.js +11 -0
  16. package/dist/cli/status.js.map +1 -1
  17. package/dist/cli/stop.js +7 -2
  18. package/dist/cli/stop.js.map +1 -1
  19. package/dist/config/schema.d.ts +4 -2
  20. package/dist/core/daemon/assignment-message.d.ts +12 -0
  21. package/dist/core/daemon/assignment-message.js +36 -0
  22. package/dist/core/daemon/assignment-message.js.map +1 -0
  23. package/dist/core/daemon/bootstrap.d.ts +35 -0
  24. package/dist/core/daemon/bootstrap.js +52 -0
  25. package/dist/core/daemon/bootstrap.js.map +1 -0
  26. package/dist/core/daemon/crash-log.d.ts +16 -0
  27. package/dist/core/daemon/crash-log.js +24 -0
  28. package/dist/core/daemon/crash-log.js.map +1 -0
  29. package/dist/core/daemon/health-policy.d.ts +21 -0
  30. package/dist/core/daemon/health-policy.js +32 -0
  31. package/dist/core/daemon/health-policy.js.map +1 -0
  32. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  33. package/dist/core/daemon/sandbox-config.js +17 -0
  34. package/dist/core/daemon/sandbox-config.js.map +1 -0
  35. package/dist/core/daemon/state.d.ts +33 -0
  36. package/dist/core/daemon/state.js +77 -0
  37. package/dist/core/daemon/state.js.map +1 -0
  38. package/dist/core/daemon/tmux-session.d.ts +17 -0
  39. package/dist/core/daemon/tmux-session.js +34 -0
  40. package/dist/core/daemon/tmux-session.js.map +1 -0
  41. package/dist/core/daemon/workspace.d.ts +10 -0
  42. package/dist/core/daemon/workspace.js +51 -0
  43. package/dist/core/daemon/workspace.js.map +1 -0
  44. package/dist/core/daemon.d.ts +4 -7
  45. package/dist/core/daemon.js +143 -259
  46. package/dist/core/daemon.js.map +1 -1
  47. package/dist/core/injector.js +6 -0
  48. package/dist/core/injector.js.map +1 -1
  49. package/dist/core/registry.js +1 -1
  50. package/dist/core/registry.js.map +1 -1
  51. package/dist/core/runner/build.d.ts +9 -0
  52. package/dist/core/runner/build.js +53 -0
  53. package/dist/core/runner/build.js.map +1 -0
  54. package/dist/core/runner/detect.d.ts +5 -0
  55. package/dist/core/runner/detect.js +14 -0
  56. package/dist/core/runner/detect.js.map +1 -0
  57. package/dist/core/runner/index.d.ts +5 -0
  58. package/dist/core/runner/index.js +5 -0
  59. package/dist/core/runner/index.js.map +1 -0
  60. package/dist/core/runner/model.d.ts +5 -0
  61. package/dist/core/runner/model.js +7 -0
  62. package/dist/core/runner/model.js.map +1 -0
  63. package/dist/core/runner/opencode-models.d.ts +15 -0
  64. package/dist/core/runner/opencode-models.js +70 -0
  65. package/dist/core/runner/opencode-models.js.map +1 -0
  66. package/dist/core/runner/types.d.ts +19 -0
  67. package/dist/core/runner/types.js +8 -0
  68. package/dist/core/runner/types.js.map +1 -0
  69. package/dist/core/runner.d.ts +5 -47
  70. package/dist/core/runner.js +5 -167
  71. package/dist/core/runner.js.map +1 -1
  72. package/dist/core/tmux-runtime.d.ts +13 -0
  73. package/dist/core/tmux-runtime.js +72 -0
  74. package/dist/core/tmux-runtime.js.map +1 -0
  75. package/dist/core/tmux.d.ts +7 -1
  76. package/dist/core/tmux.js +75 -45
  77. package/dist/core/tmux.js.map +1 -1
  78. package/dist/core/watchdog.d.ts +18 -1
  79. package/dist/core/watchdog.js +78 -29
  80. package/dist/core/watchdog.js.map +1 -1
  81. package/package.json +30 -11
  82. package/dist/cli/inbox.d.ts +0 -5
  83. package/dist/cli/inbox.js +0 -123
  84. package/dist/cli/inbox.js.map +0 -1
  85. package/dist/cli/issue.d.ts +0 -42
  86. package/dist/cli/issue.js +0 -297
  87. package/dist/cli/issue.js.map +0 -1
  88. package/dist/cli/ready.d.ts +0 -5
  89. package/dist/cli/ready.js +0 -131
  90. package/dist/cli/ready.js.map +0 -1
  91. package/dist/cli/sync.d.ts +0 -8
  92. package/dist/cli/sync.js +0 -154
  93. package/dist/cli/sync.js.map +0 -1
  94. package/dist/core/issue-cache.d.ts +0 -44
  95. package/dist/core/issue-cache.js +0 -75
  96. package/dist/core/issue-cache.js.map +0 -1
  97. package/src/__tests__/context.test.ts +0 -464
  98. package/src/__tests__/injector.test.ts +0 -29
  99. package/src/__tests__/jwt.test.ts +0 -112
  100. package/src/__tests__/loader.test.ts +0 -239
  101. package/src/__tests__/runner.test.ts +0 -104
  102. package/src/__tests__/sandbox.test.ts +0 -435
  103. package/src/__tests__/watchdog.test.ts +0 -368
  104. package/src/cli/attach.ts +0 -22
  105. package/src/cli/build.ts +0 -145
  106. package/src/cli/config.ts +0 -148
  107. package/src/cli/context.ts +0 -231
  108. package/src/cli/deploy.ts +0 -155
  109. package/src/cli/index.ts +0 -375
  110. package/src/cli/init.ts +0 -75
  111. package/src/cli/list.ts +0 -70
  112. package/src/cli/local.ts +0 -183
  113. package/src/cli/logs.ts +0 -64
  114. package/src/cli/migrate.ts +0 -212
  115. package/src/cli/nudge.ts +0 -81
  116. package/src/cli/restart.ts +0 -59
  117. package/src/cli/slack.ts +0 -70
  118. package/src/cli/start.ts +0 -115
  119. package/src/cli/status.ts +0 -91
  120. package/src/cli/stop.ts +0 -48
  121. package/src/cli/test.ts +0 -143
  122. package/src/cli/token.ts +0 -188
  123. package/src/cli/whoami.ts +0 -142
  124. package/src/config/loader.ts +0 -121
  125. package/src/config/schema.ts +0 -68
  126. package/src/context/handoff.ts +0 -122
  127. package/src/context/index.ts +0 -8
  128. package/src/context/schema.ts +0 -111
  129. package/src/context/storage.ts +0 -197
  130. package/src/core/daemon.ts +0 -1308
  131. package/src/core/heartbeat.ts +0 -129
  132. package/src/core/injector.ts +0 -292
  133. package/src/core/registry.ts +0 -159
  134. package/src/core/runner.ts +0 -225
  135. package/src/core/sandbox.ts +0 -547
  136. package/src/core/session-id.ts +0 -111
  137. package/src/core/tmux.ts +0 -405
  138. package/src/core/watchdog.ts +0 -238
  139. package/src/core/websocket.ts +0 -94
  140. package/src/index.ts +0 -10
  141. package/src/utils/jwt.ts +0 -87
  142. package/tsconfig.json +0 -8
  143. 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
- });