oh-my-claude-sisyphus 3.5.7 → 3.6.0

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 (112) hide show
  1. package/agents/executor-high.md +2 -0
  2. package/agents/executor-low.md +2 -0
  3. package/agents/executor.md +2 -0
  4. package/agents/templates/base-agent.md +9 -0
  5. package/commands/cancel.md +8 -8
  6. package/commands/swarm.md +350 -148
  7. package/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts +7 -0
  8. package/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts.map +1 -0
  9. package/dist/__tests__/hooks/auto-slash-command/executor.test.js +374 -0
  10. package/dist/__tests__/hooks/auto-slash-command/executor.test.js.map +1 -0
  11. package/dist/__tests__/hooks/learner/bridge.test.d.ts +11 -0
  12. package/dist/__tests__/hooks/learner/bridge.test.d.ts.map +1 -0
  13. package/dist/__tests__/hooks/learner/bridge.test.js +199 -0
  14. package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -0
  15. package/dist/__tests__/hooks.test.js +10 -9
  16. package/dist/__tests__/hooks.test.js.map +1 -1
  17. package/dist/__tests__/installer.test.js +1 -1
  18. package/dist/agents/codex-agents.d.ts +20 -0
  19. package/dist/agents/codex-agents.d.ts.map +1 -0
  20. package/dist/agents/codex-agents.js +36 -0
  21. package/dist/agents/codex-agents.js.map +1 -0
  22. package/dist/agents/preamble.d.ts +14 -0
  23. package/dist/agents/preamble.d.ts.map +1 -0
  24. package/dist/agents/preamble.js +26 -0
  25. package/dist/agents/preamble.js.map +1 -0
  26. package/dist/hooks/autopilot/__tests__/cancel.test.js +14 -4
  27. package/dist/hooks/autopilot/__tests__/cancel.test.js.map +1 -1
  28. package/dist/hooks/autopilot/__tests__/state.test.js +1 -0
  29. package/dist/hooks/autopilot/__tests__/state.test.js.map +1 -1
  30. package/dist/hooks/autopilot/__tests__/summary.test.js +38 -3
  31. package/dist/hooks/autopilot/__tests__/summary.test.js.map +1 -1
  32. package/dist/hooks/autopilot/state.d.ts +1 -1
  33. package/dist/hooks/autopilot/state.d.ts.map +1 -1
  34. package/dist/hooks/autopilot/state.js +15 -8
  35. package/dist/hooks/autopilot/state.js.map +1 -1
  36. package/dist/hooks/index.d.ts +2 -0
  37. package/dist/hooks/index.d.ts.map +1 -1
  38. package/dist/hooks/index.js +7 -0
  39. package/dist/hooks/index.js.map +1 -1
  40. package/dist/hooks/learner/bridge.d.ts +71 -0
  41. package/dist/hooks/learner/bridge.d.ts.map +1 -0
  42. package/dist/hooks/learner/bridge.js +426 -0
  43. package/dist/hooks/learner/bridge.js.map +1 -0
  44. package/dist/hooks/mode-registry/index.d.ts +135 -0
  45. package/dist/hooks/mode-registry/index.d.ts.map +1 -0
  46. package/dist/hooks/mode-registry/index.js +445 -0
  47. package/dist/hooks/mode-registry/index.js.map +1 -0
  48. package/dist/hooks/mode-registry/types.d.ts +31 -0
  49. package/dist/hooks/mode-registry/types.d.ts.map +1 -0
  50. package/dist/hooks/mode-registry/types.js +7 -0
  51. package/dist/hooks/mode-registry/types.js.map +1 -0
  52. package/dist/hooks/ralph/loop.js +6 -6
  53. package/dist/hooks/ralph/loop.js.map +1 -1
  54. package/dist/hooks/skill-bridge.cjs +349 -0
  55. package/dist/hooks/swarm/__tests__/claiming.test.d.ts +2 -0
  56. package/dist/hooks/swarm/__tests__/claiming.test.d.ts.map +1 -0
  57. package/dist/hooks/swarm/__tests__/claiming.test.js +170 -0
  58. package/dist/hooks/swarm/__tests__/claiming.test.js.map +1 -0
  59. package/dist/hooks/swarm/__tests__/index.test.d.ts +2 -0
  60. package/dist/hooks/swarm/__tests__/index.test.d.ts.map +1 -0
  61. package/dist/hooks/swarm/__tests__/index.test.js +157 -0
  62. package/dist/hooks/swarm/__tests__/index.test.js.map +1 -0
  63. package/dist/hooks/swarm/__tests__/mode-registry.test.d.ts +2 -0
  64. package/dist/hooks/swarm/__tests__/mode-registry.test.d.ts.map +1 -0
  65. package/dist/hooks/swarm/__tests__/mode-registry.test.js +177 -0
  66. package/dist/hooks/swarm/__tests__/mode-registry.test.js.map +1 -0
  67. package/dist/hooks/swarm/claiming.d.ts +101 -0
  68. package/dist/hooks/swarm/claiming.d.ts.map +1 -0
  69. package/dist/hooks/swarm/claiming.js +460 -0
  70. package/dist/hooks/swarm/claiming.js.map +1 -0
  71. package/dist/hooks/swarm/index.d.ts +221 -0
  72. package/dist/hooks/swarm/index.d.ts.map +1 -0
  73. package/dist/hooks/swarm/index.js +413 -0
  74. package/dist/hooks/swarm/index.js.map +1 -0
  75. package/dist/hooks/swarm/state.d.ts +94 -0
  76. package/dist/hooks/swarm/state.d.ts.map +1 -0
  77. package/dist/hooks/swarm/state.js +530 -0
  78. package/dist/hooks/swarm/state.js.map +1 -0
  79. package/dist/hooks/swarm/types.d.ts +116 -0
  80. package/dist/hooks/swarm/types.d.ts.map +1 -0
  81. package/dist/hooks/swarm/types.js +22 -0
  82. package/dist/hooks/swarm/types.js.map +1 -0
  83. package/dist/hooks/ultrapilot/decomposer.d.ts +141 -0
  84. package/dist/hooks/ultrapilot/decomposer.d.ts.map +1 -0
  85. package/dist/hooks/ultrapilot/decomposer.js +377 -0
  86. package/dist/hooks/ultrapilot/decomposer.js.map +1 -0
  87. package/dist/hooks/ultrapilot/index.d.ts +31 -0
  88. package/dist/hooks/ultrapilot/index.d.ts.map +1 -1
  89. package/dist/hooks/ultrapilot/index.js +43 -2
  90. package/dist/hooks/ultrapilot/index.js.map +1 -1
  91. package/dist/hooks/ultrapilot/state.d.ts +1 -1
  92. package/dist/hooks/ultrapilot/state.d.ts.map +1 -1
  93. package/dist/hooks/ultrapilot/state.js +7 -0
  94. package/dist/hooks/ultrapilot/state.js.map +1 -1
  95. package/dist/hooks/ultraqa/index.js +5 -5
  96. package/dist/hooks/ultraqa/index.js.map +1 -1
  97. package/dist/hooks/ultrawork/index.js +3 -3
  98. package/dist/hooks/ultrawork/index.js.map +1 -1
  99. package/dist/installer/index.d.ts +1 -1
  100. package/dist/installer/index.js +1 -1
  101. package/package.json +6 -2
  102. package/scripts/build-skill-bridge.mjs +32 -0
  103. package/scripts/skill-injector.mjs +77 -26
  104. package/skills/autopilot/SKILL.md +18 -0
  105. package/skills/cancel/SKILL.md +166 -141
  106. package/skills/ecomode/SKILL.md +14 -0
  107. package/skills/pipeline/SKILL.md +13 -0
  108. package/skills/ralph/SKILL.md +22 -1
  109. package/skills/swarm/SKILL.md +521 -197
  110. package/skills/ultrapilot/SKILL.md +82 -13
  111. package/skills/ultraqa/SKILL.md +13 -0
  112. package/skills/ultrawork/SKILL.md +14 -0
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hooks/learner/bridge.ts
21
+ var bridge_exports = {};
22
+ __export(bridge_exports, {
23
+ PROJECT_SKILLS_SUBDIR: () => PROJECT_SKILLS_SUBDIR,
24
+ SKILL_EXTENSION: () => SKILL_EXTENSION,
25
+ USER_SKILLS_DIR: () => USER_SKILLS_DIR,
26
+ findSkillFiles: () => findSkillFiles,
27
+ getInjectedSkillPaths: () => getInjectedSkillPaths,
28
+ markSkillsInjected: () => markSkillsInjected,
29
+ matchSkillsForInjection: () => matchSkillsForInjection,
30
+ parseSkillFile: () => parseSkillFile
31
+ });
32
+ module.exports = __toCommonJS(bridge_exports);
33
+ var import_fs = require("fs");
34
+ var import_path = require("path");
35
+ var import_os = require("os");
36
+ var USER_SKILLS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "skills", "omc-learned");
37
+ var PROJECT_SKILLS_SUBDIR = ".omc/skills";
38
+ var SKILL_EXTENSION = ".md";
39
+ var SESSION_TTL_MS = 60 * 60 * 1e3;
40
+ var STATE_FILE = ".omc/state/skill-sessions.json";
41
+ function getStateFilePath(projectRoot) {
42
+ return (0, import_path.join)(projectRoot, STATE_FILE);
43
+ }
44
+ function readSessionState(projectRoot) {
45
+ const stateFile = getStateFilePath(projectRoot);
46
+ try {
47
+ if ((0, import_fs.existsSync)(stateFile)) {
48
+ const content = (0, import_fs.readFileSync)(stateFile, "utf-8");
49
+ return JSON.parse(content);
50
+ }
51
+ } catch {
52
+ }
53
+ return { sessions: {} };
54
+ }
55
+ function writeSessionState(projectRoot, state) {
56
+ const stateFile = getStateFilePath(projectRoot);
57
+ try {
58
+ (0, import_fs.mkdirSync)((0, import_path.dirname)(stateFile), { recursive: true });
59
+ (0, import_fs.writeFileSync)(stateFile, JSON.stringify(state, null, 2), "utf-8");
60
+ } catch {
61
+ }
62
+ }
63
+ function getInjectedSkillPaths(sessionId, projectRoot) {
64
+ const state = readSessionState(projectRoot);
65
+ const session = state.sessions[sessionId];
66
+ if (!session) return [];
67
+ if (Date.now() - session.timestamp > SESSION_TTL_MS) {
68
+ return [];
69
+ }
70
+ return session.injectedPaths;
71
+ }
72
+ function markSkillsInjected(sessionId, paths, projectRoot) {
73
+ const state = readSessionState(projectRoot);
74
+ const now = Date.now();
75
+ for (const [id, session] of Object.entries(state.sessions)) {
76
+ if (now - session.timestamp > SESSION_TTL_MS) {
77
+ delete state.sessions[id];
78
+ }
79
+ }
80
+ const existing = state.sessions[sessionId]?.injectedPaths ?? [];
81
+ state.sessions[sessionId] = {
82
+ injectedPaths: [.../* @__PURE__ */ new Set([...existing, ...paths])],
83
+ timestamp: now
84
+ };
85
+ writeSessionState(projectRoot, state);
86
+ }
87
+ function findSkillFilesRecursive(dir, results) {
88
+ if (!(0, import_fs.existsSync)(dir)) return;
89
+ try {
90
+ const entries = (0, import_fs.readdirSync)(dir, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ const fullPath = (0, import_path.join)(dir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ findSkillFilesRecursive(fullPath, results);
95
+ } else if (entry.isFile() && entry.name.endsWith(SKILL_EXTENSION)) {
96
+ results.push(fullPath);
97
+ }
98
+ }
99
+ } catch {
100
+ }
101
+ }
102
+ function safeRealpathSync(filePath) {
103
+ try {
104
+ return (0, import_fs.realpathSync)(filePath);
105
+ } catch {
106
+ return filePath;
107
+ }
108
+ }
109
+ function findSkillFiles(projectRoot) {
110
+ const candidates = [];
111
+ const seenRealPaths = /* @__PURE__ */ new Set();
112
+ const projectSkillsDir = (0, import_path.join)(projectRoot, PROJECT_SKILLS_SUBDIR);
113
+ const projectFiles = [];
114
+ findSkillFilesRecursive(projectSkillsDir, projectFiles);
115
+ for (const filePath of projectFiles) {
116
+ const realPath = safeRealpathSync(filePath);
117
+ if (seenRealPaths.has(realPath)) continue;
118
+ seenRealPaths.add(realPath);
119
+ candidates.push({
120
+ path: filePath,
121
+ realPath,
122
+ scope: "project"
123
+ });
124
+ }
125
+ const userFiles = [];
126
+ findSkillFilesRecursive(USER_SKILLS_DIR, userFiles);
127
+ for (const filePath of userFiles) {
128
+ const realPath = safeRealpathSync(filePath);
129
+ if (seenRealPaths.has(realPath)) continue;
130
+ seenRealPaths.add(realPath);
131
+ candidates.push({
132
+ path: filePath,
133
+ realPath,
134
+ scope: "user"
135
+ });
136
+ }
137
+ return candidates;
138
+ }
139
+ function parseSkillFile(content) {
140
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
141
+ const match = content.match(frontmatterRegex);
142
+ if (!match) {
143
+ return {
144
+ metadata: {},
145
+ content: content.trim(),
146
+ valid: true,
147
+ errors: []
148
+ };
149
+ }
150
+ const yamlContent = match[1];
151
+ const body = match[2].trim();
152
+ const errors = [];
153
+ try {
154
+ const metadata = parseYamlMetadata(yamlContent);
155
+ return {
156
+ metadata,
157
+ content: body,
158
+ valid: true,
159
+ errors
160
+ };
161
+ } catch (e) {
162
+ return {
163
+ metadata: {},
164
+ content: body,
165
+ valid: false,
166
+ errors: [`YAML parse error: ${e}`]
167
+ };
168
+ }
169
+ }
170
+ function parseYamlMetadata(yamlContent) {
171
+ const lines = yamlContent.split("\n");
172
+ const metadata = {};
173
+ let i = 0;
174
+ while (i < lines.length) {
175
+ const line = lines[i];
176
+ const colonIndex = line.indexOf(":");
177
+ if (colonIndex === -1) {
178
+ i++;
179
+ continue;
180
+ }
181
+ const key = line.slice(0, colonIndex).trim();
182
+ const rawValue = line.slice(colonIndex + 1).trim();
183
+ switch (key) {
184
+ case "id":
185
+ metadata.id = parseStringValue(rawValue);
186
+ break;
187
+ case "name":
188
+ metadata.name = parseStringValue(rawValue);
189
+ break;
190
+ case "description":
191
+ metadata.description = parseStringValue(rawValue);
192
+ break;
193
+ case "model":
194
+ metadata.model = parseStringValue(rawValue);
195
+ break;
196
+ case "agent":
197
+ metadata.agent = parseStringValue(rawValue);
198
+ break;
199
+ case "matching":
200
+ metadata.matching = parseStringValue(rawValue);
201
+ break;
202
+ case "triggers":
203
+ case "tags": {
204
+ const { value, consumed } = parseArrayValue(rawValue, lines, i);
205
+ if (key === "triggers") {
206
+ metadata.triggers = Array.isArray(value) ? value : value ? [value] : [];
207
+ } else {
208
+ metadata.tags = Array.isArray(value) ? value : value ? [value] : [];
209
+ }
210
+ i += consumed - 1;
211
+ break;
212
+ }
213
+ }
214
+ i++;
215
+ }
216
+ return metadata;
217
+ }
218
+ function parseStringValue(value) {
219
+ if (!value) return "";
220
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
221
+ return value.slice(1, -1);
222
+ }
223
+ return value;
224
+ }
225
+ function parseArrayValue(rawValue, lines, currentIndex) {
226
+ if (rawValue.startsWith("[")) {
227
+ const content = rawValue.slice(1, rawValue.lastIndexOf("]")).trim();
228
+ if (!content) return { value: [], consumed: 1 };
229
+ const items = content.split(",").map((s) => parseStringValue(s.trim())).filter(Boolean);
230
+ return { value: items, consumed: 1 };
231
+ }
232
+ if (!rawValue || rawValue === "") {
233
+ const items = [];
234
+ let consumed = 1;
235
+ for (let j = currentIndex + 1; j < lines.length; j++) {
236
+ const nextLine = lines[j];
237
+ const arrayMatch = nextLine.match(/^\s+-\s*(.*)$/);
238
+ if (arrayMatch) {
239
+ const itemValue = parseStringValue(arrayMatch[1].trim());
240
+ if (itemValue) items.push(itemValue);
241
+ consumed++;
242
+ } else if (nextLine.trim() === "") {
243
+ consumed++;
244
+ } else {
245
+ break;
246
+ }
247
+ }
248
+ if (items.length > 0) {
249
+ return { value: items, consumed };
250
+ }
251
+ }
252
+ return { value: parseStringValue(rawValue), consumed: 1 };
253
+ }
254
+ function levenshteinDistance(str1, str2) {
255
+ const m = str1.length;
256
+ const n = str2.length;
257
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
258
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
259
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
260
+ for (let i = 1; i <= m; i++) {
261
+ for (let j = 1; j <= n; j++) {
262
+ if (str1[i - 1] === str2[j - 1]) {
263
+ dp[i][j] = dp[i - 1][j - 1];
264
+ } else {
265
+ dp[i][j] = 1 + Math.min(
266
+ dp[i - 1][j],
267
+ dp[i][j - 1],
268
+ dp[i - 1][j - 1]
269
+ );
270
+ }
271
+ }
272
+ }
273
+ return dp[m][n];
274
+ }
275
+ function fuzzyMatchTrigger(prompt, trigger) {
276
+ const words = prompt.split(/\s+/).filter((w) => w.length > 0);
277
+ for (const word of words) {
278
+ if (word === trigger) return 100;
279
+ if (word.includes(trigger) || trigger.includes(word)) {
280
+ return 80;
281
+ }
282
+ }
283
+ let bestScore = 0;
284
+ for (const word of words) {
285
+ const distance = levenshteinDistance(word, trigger);
286
+ const maxLen = Math.max(word.length, trigger.length);
287
+ const similarity = maxLen > 0 ? (maxLen - distance) / maxLen * 100 : 0;
288
+ bestScore = Math.max(bestScore, similarity);
289
+ }
290
+ return Math.round(bestScore);
291
+ }
292
+ function matchSkillsForInjection(prompt, projectRoot, sessionId, options = {}) {
293
+ const { fuzzyThreshold = 60, maxResults = 5 } = options;
294
+ const promptLower = prompt.toLowerCase();
295
+ const alreadyInjected = new Set(getInjectedSkillPaths(sessionId, projectRoot));
296
+ const candidates = findSkillFiles(projectRoot);
297
+ const matches = [];
298
+ for (const candidate of candidates) {
299
+ if (alreadyInjected.has(candidate.path)) continue;
300
+ try {
301
+ const content = (0, import_fs.readFileSync)(candidate.path, "utf-8");
302
+ const parsed = parseSkillFile(content);
303
+ if (!parsed) continue;
304
+ const triggers = parsed.metadata.triggers ?? [];
305
+ if (triggers.length === 0) continue;
306
+ const useFuzzy = parsed.metadata.matching === "fuzzy";
307
+ let totalScore = 0;
308
+ for (const trigger of triggers) {
309
+ const triggerLower = trigger.toLowerCase();
310
+ if (promptLower.includes(triggerLower)) {
311
+ totalScore += 10;
312
+ continue;
313
+ }
314
+ if (useFuzzy) {
315
+ const fuzzyScore = fuzzyMatchTrigger(promptLower, triggerLower);
316
+ if (fuzzyScore >= fuzzyThreshold) {
317
+ totalScore += Math.round(fuzzyScore / 10);
318
+ }
319
+ }
320
+ }
321
+ if (totalScore > 0) {
322
+ const name = parsed.metadata.name || (0, import_path.basename)(candidate.path, SKILL_EXTENSION);
323
+ matches.push({
324
+ path: candidate.path,
325
+ name,
326
+ content: parsed.content,
327
+ score: totalScore,
328
+ scope: candidate.scope,
329
+ triggers,
330
+ matching: parsed.metadata.matching
331
+ });
332
+ }
333
+ } catch {
334
+ }
335
+ }
336
+ matches.sort((a, b) => b.score - a.score);
337
+ return matches.slice(0, maxResults);
338
+ }
339
+ // Annotate the CommonJS export names for ESM import in node:
340
+ 0 && (module.exports = {
341
+ PROJECT_SKILLS_SUBDIR,
342
+ SKILL_EXTENSION,
343
+ USER_SKILLS_DIR,
344
+ findSkillFiles,
345
+ getInjectedSkillPaths,
346
+ markSkillsInjected,
347
+ matchSkillsForInjection,
348
+ parseSkillFile
349
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=claiming.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claiming.test.d.ts","sourceRoot":"","sources":["../../../../src/hooks/swarm/__tests__/claiming.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,170 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtempSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { initDb, closeDb, initSession, addTasks, getHeartbeats } from '../state.js';
6
+ import { claimTask, cleanupStaleClaims, heartbeat } from '../claiming.js';
7
+ describe('Swarm Claiming', () => {
8
+ let testDir;
9
+ beforeEach(async () => {
10
+ testDir = mkdtempSync(join(tmpdir(), 'swarm-claiming-test-'));
11
+ await initDb(testDir);
12
+ });
13
+ afterEach(() => {
14
+ closeDb();
15
+ rmSync(testDir, { recursive: true, force: true });
16
+ });
17
+ describe('claimTask', () => {
18
+ it('should successfully claim a pending task', () => {
19
+ initSession('test-session', 3);
20
+ addTasks([
21
+ { id: 'task-1', description: 'First task' }
22
+ ]);
23
+ const result = claimTask('agent-1');
24
+ expect(result.success).toBe(true);
25
+ expect(result.taskId).toBe('task-1');
26
+ expect(result.description).toBe('First task');
27
+ });
28
+ it('should allow sequential claims for different tasks', () => {
29
+ initSession('test-session', 3);
30
+ addTasks([
31
+ { id: 'task-1', description: 'First task' },
32
+ { id: 'task-2', description: 'Second task' },
33
+ { id: 'task-3', description: 'Third task' }
34
+ ]);
35
+ const result1 = claimTask('agent-1');
36
+ const result2 = claimTask('agent-2');
37
+ const result3 = claimTask('agent-3');
38
+ expect(result1.success).toBe(true);
39
+ expect(result1.taskId).toBe('task-1');
40
+ expect(result2.success).toBe(true);
41
+ expect(result2.taskId).toBe('task-2');
42
+ expect(result3.success).toBe(true);
43
+ expect(result3.taskId).toBe('task-3');
44
+ });
45
+ it('should handle duplicate claims correctly when only one task available', () => {
46
+ initSession('test-session', 2);
47
+ addTasks([
48
+ { id: 'task-1', description: 'Only task' }
49
+ ]);
50
+ const result1 = claimTask('agent-1');
51
+ const result2 = claimTask('agent-2');
52
+ expect(result1.success).toBe(true);
53
+ expect(result1.taskId).toBe('task-1');
54
+ expect(result2.success).toBe(false);
55
+ expect(result2.taskId).toBeNull();
56
+ });
57
+ it('should fail with appropriate message when no pending tasks available', () => {
58
+ initSession('test-session', 2);
59
+ // No tasks added
60
+ const result = claimTask('agent-1');
61
+ expect(result.success).toBe(false);
62
+ expect(result.taskId).toBeNull();
63
+ expect(result.reason).toBe('No pending tasks available');
64
+ });
65
+ it('should update heartbeat when claiming a task', () => {
66
+ initSession('test-session', 1);
67
+ addTasks([
68
+ { id: 'task-1', description: 'First task' }
69
+ ]);
70
+ claimTask('agent-1');
71
+ const heartbeats = getHeartbeats();
72
+ expect(heartbeats).toHaveLength(1);
73
+ expect(heartbeats[0].agentId).toBe('agent-1');
74
+ expect(heartbeats[0].currentTaskId).toBe('task-1');
75
+ expect(heartbeats[0].lastHeartbeat).toBeGreaterThan(Date.now() - 1000);
76
+ });
77
+ });
78
+ describe('cleanupStaleClaims', () => {
79
+ it('should return 0 when no stale claims exist', () => {
80
+ initSession('test-session', 1);
81
+ addTasks([
82
+ { id: 'task-1', description: 'First task' }
83
+ ]);
84
+ const released = cleanupStaleClaims(5 * 60 * 1000); // 5 minutes
85
+ expect(released).toBe(0);
86
+ });
87
+ it('should release stale claim without heartbeat', () => {
88
+ vi.useFakeTimers();
89
+ const now = Date.now();
90
+ vi.setSystemTime(now);
91
+ initSession('test-session', 1);
92
+ addTasks([
93
+ { id: 'task-1', description: 'First task' }
94
+ ]);
95
+ // Claim task
96
+ claimTask('agent-1');
97
+ // Advance time by 6 minutes
98
+ vi.setSystemTime(now + 6 * 60 * 1000);
99
+ // Cleanup stale claims
100
+ const released = cleanupStaleClaims(5 * 60 * 1000);
101
+ expect(released).toBe(1);
102
+ vi.useRealTimers();
103
+ });
104
+ it('should not release claim with fresh heartbeat', () => {
105
+ vi.useFakeTimers();
106
+ const now = Date.now();
107
+ vi.setSystemTime(now);
108
+ initSession('test-session', 1);
109
+ addTasks([
110
+ { id: 'task-1', description: 'First task' }
111
+ ]);
112
+ // Claim task
113
+ claimTask('agent-1');
114
+ // Advance time by 3 minutes
115
+ vi.setSystemTime(now + 3 * 60 * 1000);
116
+ // Send heartbeat
117
+ heartbeat('agent-1');
118
+ // Advance time by another 3 minutes (6 minutes total, but heartbeat is only 3 minutes old)
119
+ vi.setSystemTime(now + 6 * 60 * 1000);
120
+ // Cleanup stale claims
121
+ const released = cleanupStaleClaims(5 * 60 * 1000);
122
+ expect(released).toBe(0);
123
+ vi.useRealTimers();
124
+ });
125
+ it('should release multiple stale claims', () => {
126
+ vi.useFakeTimers();
127
+ const now = Date.now();
128
+ vi.setSystemTime(now);
129
+ initSession('test-session', 3);
130
+ addTasks([
131
+ { id: 'task-1', description: 'First task' },
132
+ { id: 'task-2', description: 'Second task' },
133
+ { id: 'task-3', description: 'Third task' }
134
+ ]);
135
+ // All agents claim tasks
136
+ claimTask('agent-1');
137
+ claimTask('agent-2');
138
+ claimTask('agent-3');
139
+ // Advance time by 6 minutes
140
+ vi.setSystemTime(now + 6 * 60 * 1000);
141
+ // Cleanup stale claims
142
+ const released = cleanupStaleClaims(5 * 60 * 1000);
143
+ expect(released).toBe(3);
144
+ vi.useRealTimers();
145
+ });
146
+ it('should remove stale heartbeat records during cleanup', () => {
147
+ vi.useFakeTimers();
148
+ const now = Date.now();
149
+ vi.setSystemTime(now);
150
+ initSession('test-session', 1);
151
+ addTasks([
152
+ { id: 'task-1', description: 'First task' }
153
+ ]);
154
+ // Claim task
155
+ claimTask('agent-1');
156
+ // Verify heartbeat exists
157
+ let heartbeats = getHeartbeats();
158
+ expect(heartbeats).toHaveLength(1);
159
+ // Advance time by 6 minutes
160
+ vi.setSystemTime(now + 6 * 60 * 1000);
161
+ // Cleanup stale claims
162
+ cleanupStaleClaims(5 * 60 * 1000);
163
+ // Verify heartbeat was removed
164
+ heartbeats = getHeartbeats();
165
+ expect(heartbeats).toHaveLength(0);
166
+ vi.useRealTimers();
167
+ });
168
+ });
169
+ });
170
+ //# sourceMappingURL=claiming.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claiming.test.js","sourceRoot":"","sources":["../../../../src/hooks/swarm/__tests__/claiming.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../../src/hooks/swarm/__tests__/index.test.ts"],"names":[],"mappings":""}