@jungjaehoon/mama-os 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +41 -7
  2. package/dist/api/graph-api.js +1 -1
  3. package/dist/api/graph-api.js.map +1 -1
  4. package/dist/cli/config/config-manager.d.ts.map +1 -1
  5. package/dist/cli/config/config-manager.js +60 -15
  6. package/dist/cli/config/config-manager.js.map +1 -1
  7. package/dist/cli/config/types.d.ts +16 -4
  8. package/dist/cli/config/types.d.ts.map +1 -1
  9. package/dist/cli/config/types.js.map +1 -1
  10. package/dist/gateways/slack.d.ts.map +1 -1
  11. package/dist/gateways/slack.js +0 -10
  12. package/dist/gateways/slack.js.map +1 -1
  13. package/dist/multi-agent/agent-process-manager.d.ts +3 -1
  14. package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
  15. package/dist/multi-agent/agent-process-manager.js +21 -12
  16. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  17. package/dist/multi-agent/background-task-manager.d.ts +2 -2
  18. package/dist/multi-agent/background-task-manager.js +2 -2
  19. package/dist/multi-agent/council-engine.d.ts +60 -0
  20. package/dist/multi-agent/council-engine.d.ts.map +1 -0
  21. package/dist/multi-agent/council-engine.js +284 -0
  22. package/dist/multi-agent/council-engine.js.map +1 -0
  23. package/dist/multi-agent/multi-agent-base.d.ts +17 -9
  24. package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
  25. package/dist/multi-agent/multi-agent-base.js +109 -30
  26. package/dist/multi-agent/multi-agent-base.js.map +1 -1
  27. package/dist/multi-agent/multi-agent-discord.d.ts +3 -35
  28. package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
  29. package/dist/multi-agent/multi-agent-discord.js +57 -300
  30. package/dist/multi-agent/multi-agent-discord.js.map +1 -1
  31. package/dist/multi-agent/multi-agent-slack.d.ts +0 -25
  32. package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
  33. package/dist/multi-agent/multi-agent-slack.js +95 -234
  34. package/dist/multi-agent/multi-agent-slack.js.map +1 -1
  35. package/dist/multi-agent/shared-context.d.ts.map +1 -1
  36. package/dist/multi-agent/shared-context.js +4 -4
  37. package/dist/multi-agent/shared-context.js.map +1 -1
  38. package/dist/multi-agent/system-reminder.d.ts +1 -1
  39. package/dist/multi-agent/system-reminder.js +1 -1
  40. package/dist/multi-agent/types.d.ts +11 -15
  41. package/dist/multi-agent/types.d.ts.map +1 -1
  42. package/dist/multi-agent/types.js +1 -3
  43. package/dist/multi-agent/types.js.map +1 -1
  44. package/dist/multi-agent/ultrawork-state.d.ts +57 -0
  45. package/dist/multi-agent/ultrawork-state.d.ts.map +1 -0
  46. package/dist/multi-agent/ultrawork-state.js +191 -0
  47. package/dist/multi-agent/ultrawork-state.js.map +1 -0
  48. package/dist/multi-agent/ultrawork.d.ts +37 -19
  49. package/dist/multi-agent/ultrawork.d.ts.map +1 -1
  50. package/dist/multi-agent/ultrawork.js +587 -41
  51. package/dist/multi-agent/ultrawork.js.map +1 -1
  52. package/dist/multi-agent/workflow-engine.d.ts.map +1 -1
  53. package/dist/multi-agent/workflow-engine.js +39 -14
  54. package/dist/multi-agent/workflow-engine.js.map +1 -1
  55. package/dist/multi-agent/workflow-types.d.ts +69 -0
  56. package/dist/multi-agent/workflow-types.d.ts.map +1 -1
  57. package/dist/onboarding/complete-autonomous-prompt.d.ts +1 -1
  58. package/dist/onboarding/complete-autonomous-prompt.d.ts.map +1 -1
  59. package/dist/onboarding/complete-autonomous-prompt.js +27 -10
  60. package/dist/onboarding/complete-autonomous-prompt.js.map +1 -1
  61. package/dist/onboarding/phase-7-integrations.d.ts.map +1 -1
  62. package/dist/onboarding/phase-7-integrations.js +23 -3
  63. package/dist/onboarding/phase-7-integrations.js.map +1 -1
  64. package/dist/onboarding/phase-9-finalization.d.ts.map +1 -1
  65. package/dist/onboarding/phase-9-finalization.js +33 -0
  66. package/dist/onboarding/phase-9-finalization.js.map +1 -1
  67. package/package.json +1 -1
  68. package/templates/personas/architect.md +70 -0
  69. package/templates/personas/conductor.md +302 -0
  70. package/templates/personas/developer.md +20 -7
  71. package/templates/personas/pm.md +49 -33
  72. package/templates/personas/reviewer.md +18 -5
  73. package/dist/multi-agent/pr-review-poller.d.ts +0 -197
  74. package/dist/multi-agent/pr-review-poller.d.ts.map +0 -1
  75. package/dist/multi-agent/pr-review-poller.js +0 -972
  76. package/dist/multi-agent/pr-review-poller.js.map +0 -1
  77. package/templates/personas/sisyphus-builtin-en.md +0 -161
  78. package/templates/personas/sisyphus.md +0 -218
@@ -5,22 +5,65 @@
5
5
  * Manages autonomous multi-step work sessions that combine
6
6
  * delegation and task continuation for extended workflows.
7
7
  *
8
- * UltraWork sessions allow a Tier 1 lead agent to autonomously
9
- * orchestrate work by delegating tasks, continuing incomplete
10
- * responses, and coordinating with other agents.
8
+ * Supports two modes:
9
+ * 1. **Phased Loop** (Ralph Loop, default): Plan -> Build -> Retrospective
10
+ * - File-based state persist for crash recovery
11
+ * - Council integration at plan and retrospective phases
12
+ * - Structured task execution from plan
13
+ * 2. **Freeform Loop** (legacy): Lead agent freely delegates and continues
11
14
  *
12
15
  * Constraints:
13
16
  * - max_duration (default 30 min)
14
17
  * - max_steps (default 20)
15
18
  * - Lead agent must be Tier 1 with can_delegate
16
19
  */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
17
53
  Object.defineProperty(exports, "__esModule", { value: true });
18
54
  exports.UltraWorkManager = void 0;
19
55
  const tool_permission_manager_js_1 = require("./tool-permission-manager.js");
20
56
  const delegation_manager_js_1 = require("./delegation-manager.js");
21
57
  const task_continuation_js_1 = require("./task-continuation.js");
58
+ const ultrawork_state_js_1 = require("./ultrawork-state.js");
59
+ const os = __importStar(require("os"));
60
+ const path = __importStar(require("path"));
22
61
  /** Default timeout for executeCallback (5 minutes) */
23
62
  const DEFAULT_EXECUTE_TIMEOUT = 300000;
63
+ /** Default stall threshold — if response is too short, likely stalled */
64
+ const STALL_MIN_LENGTH = 20;
65
+ /** Max consecutive stalls before forcing a re-prompt */
66
+ const MAX_CONSECUTIVE_STALLS = 2;
24
67
  /**
25
68
  * Default trigger keywords for UltraWork
26
69
  */
@@ -37,6 +80,7 @@ const DEFAULT_TRIGGER_KEYWORDS = [
37
80
  class UltraWorkManager {
38
81
  config;
39
82
  permissionManager;
83
+ stateManager = null;
40
84
  /** Active sessions per channel */
41
85
  sessions = new Map();
42
86
  /** Session counter for unique IDs */
@@ -44,6 +88,9 @@ class UltraWorkManager {
44
88
  constructor(config, permissionManager) {
45
89
  this.config = config;
46
90
  this.permissionManager = permissionManager ?? new tool_permission_manager_js_1.ToolPermissionManager();
91
+ if (config.persist_state !== false) {
92
+ this.stateManager = new ultrawork_state_js_1.UltraWorkStateManager(path.join(os.homedir(), '.mama', 'workspace', 'ultrawork'));
93
+ }
47
94
  }
48
95
  /**
49
96
  * Check if a message contains UltraWork trigger keywords.
@@ -58,7 +105,7 @@ class UltraWorkManager {
58
105
  /**
59
106
  * Start a new UltraWork session.
60
107
  */
61
- async startSession(channelId, leadAgentId, task, agents, executeCallback, notifyCallback) {
108
+ async startSession(channelId, leadAgentId, task, agents, executeCallback, notifyCallback, responseInterceptor) {
62
109
  // Validate lead agent
63
110
  const leadAgent = agents.find((a) => a.id === leadAgentId);
64
111
  if (!leadAgent) {
@@ -84,13 +131,18 @@ class UltraWorkManager {
84
131
  steps: [],
85
132
  };
86
133
  this.sessions.set(channelId, session);
134
+ // Persist session state
135
+ if (this.stateManager) {
136
+ await this.stateManager.createSession(session.id, task, agents.filter((a) => a.enabled !== false).map((a) => a.id));
137
+ }
138
+ const modeLabel = this.config.phased_loop !== false ? 'Phased (Plan->Build->Retro)' : 'Freeform';
87
139
  await notifyCallback(`**UltraWork Session Started** (${session.id})\n` +
88
140
  `Lead: **${leadAgent.display_name}**\n` +
141
+ `Mode: ${modeLabel}\n` +
89
142
  `Task: ${task.substring(0, 200)}${task.length > 200 ? '...' : ''}\n` +
90
143
  `Limits: ${session.maxSteps} steps, ${Math.round(session.maxDuration / 60000)} min`);
91
144
  // Run the autonomous loop in detached context (non-blocking)
92
- // This prevents blocking the Discord message handler for up to 30 minutes
93
- this.runSessionLoop(session, agents, executeCallback, notifyCallback).catch((err) => {
145
+ this.runSessionLoop(session, agents, executeCallback, notifyCallback, responseInterceptor).catch((err) => {
94
146
  console.error(`[UltraWork] Session ${session.id} loop error:`, err);
95
147
  session.active = false;
96
148
  this.sessions.delete(session.channelId);
@@ -145,15 +197,20 @@ class UltraWorkManager {
145
197
  updateConfig(config) {
146
198
  this.config = config;
147
199
  }
200
+ /**
201
+ * Get the state manager (for testing).
202
+ */
203
+ getStateManager() {
204
+ return this.stateManager;
205
+ }
206
+ /**
207
+ * Override state manager (for testing with temp dirs).
208
+ */
209
+ setStateManager(sm) {
210
+ this.stateManager = sm;
211
+ }
148
212
  /**
149
213
  * Execute callback with timeout protection.
150
- * Prevents long-running agent responses from blocking indefinitely.
151
- *
152
- * @param executeCallback - The callback to execute
153
- * @param agentId - Agent ID for error messages
154
- * @param prompt - Prompt to send to agent
155
- * @param timeoutMs - Timeout in milliseconds (default: 5 minutes)
156
- * @returns Promise that rejects on timeout
157
214
  */
158
215
  async executeWithTimeout(executeCallback, agentId, prompt, timeoutMs = DEFAULT_EXECUTE_TIMEOUT) {
159
216
  let timeoutHandle;
@@ -167,29 +224,424 @@ class UltraWorkManager {
167
224
  return result;
168
225
  }
169
226
  /**
170
- * Run the autonomous session loop.
171
- * Lead agent works on the task, delegating and continuing as needed.
227
+ * Run the autonomous session loop — dispatches to phased or freeform mode.
172
228
  */
173
- async runSessionLoop(session, agents, executeCallback, notifyCallback) {
229
+ async runSessionLoop(session, agents, executeCallback, notifyCallback, responseInterceptor) {
230
+ if (this.config.phased_loop !== false) {
231
+ await this.runPhasedLoop(session, agents, executeCallback, notifyCallback, responseInterceptor);
232
+ }
233
+ else {
234
+ await this.runFreeformLoop(session, agents, executeCallback, notifyCallback, responseInterceptor);
235
+ }
236
+ }
237
+ // ============================================================================
238
+ // Phase 1: Planning
239
+ // ============================================================================
240
+ async runPlanningPhase(session, agents, executeCallback, notifyCallback, responseInterceptor) {
241
+ await notifyCallback(`**Phase 1: Planning** - Creating implementation plan...`);
242
+ session.currentStep++;
243
+ const planPrompt = this.buildPlanningPrompt(session.task, agents);
244
+ const planResult = await this.executeWithTimeout(executeCallback, session.leadAgentId, planPrompt);
245
+ session.steps.push({
246
+ stepNumber: session.currentStep,
247
+ agentId: session.leadAgentId,
248
+ action: 'planning',
249
+ responseSummary: planResult.response.substring(0, 200),
250
+ isDelegation: false,
251
+ duration: planResult.duration ?? 0,
252
+ timestamp: Date.now(),
253
+ });
254
+ // Council check — if Conductor outputs council_plan, interceptor will handle it
255
+ let councilResult = null;
256
+ if (responseInterceptor) {
257
+ const intercepted = await responseInterceptor(planResult.response, session.channelId);
258
+ if (intercepted?.type === 'council') {
259
+ councilResult = intercepted.result;
260
+ await notifyCallback(councilResult);
261
+ session.currentStep++;
262
+ session.steps.push({
263
+ stepNumber: session.currentStep,
264
+ agentId: session.leadAgentId,
265
+ action: 'council_execution',
266
+ responseSummary: councilResult.substring(0, 200),
267
+ isDelegation: false,
268
+ duration: 0,
269
+ timestamp: Date.now(),
270
+ });
271
+ }
272
+ }
273
+ // Synthesize final plan (with council input if available)
274
+ let finalPlan;
275
+ if (councilResult) {
276
+ session.currentStep++;
277
+ const synthesisPrompt = `Based on the council discussion:\n---\n${councilResult}\n---\n\n` +
278
+ `Create the final IMPLEMENTATION_PLAN. Format:\n## Tasks\n1. [task description] - assigned to: [agent_id]\n2. ...\n\n` +
279
+ `Include acceptance criteria for each task. End with "PLAN_COMPLETE".`;
280
+ const synthesis = await this.executeWithTimeout(executeCallback, session.leadAgentId, synthesisPrompt);
281
+ finalPlan = synthesis.response;
282
+ session.steps.push({
283
+ stepNumber: session.currentStep,
284
+ agentId: session.leadAgentId,
285
+ action: 'plan_synthesis',
286
+ responseSummary: finalPlan.substring(0, 200),
287
+ isDelegation: false,
288
+ duration: synthesis.duration ?? 0,
289
+ timestamp: Date.now(),
290
+ });
291
+ }
292
+ else {
293
+ finalPlan = planResult.response;
294
+ }
295
+ // Persist plan
296
+ if (this.stateManager) {
297
+ await this.stateManager.savePlan(session.id, finalPlan);
298
+ }
299
+ return finalPlan;
300
+ }
301
+ // ============================================================================
302
+ // Phase 2: Building
303
+ // ============================================================================
304
+ async runBuildingPhase(session, plan, agents, executeCallback, notifyCallback, responseInterceptor) {
305
+ await notifyCallback(`**Phase 2: Building** - Executing plan...`);
306
+ const delegationManager = new delegation_manager_js_1.DelegationManager(agents, this.permissionManager);
307
+ const continuationEnforcer = new task_continuation_js_1.TaskContinuationEnforcer({
308
+ enabled: true,
309
+ max_retries: 3,
310
+ });
311
+ let consecutiveStalls = 0;
312
+ let currentPrompt = this.buildBuildingPrompt(plan, agents);
313
+ let currentAgentId = session.leadAgentId;
314
+ while (this.shouldContinue(session)) {
315
+ session.currentStep++;
316
+ const stepStart = Date.now();
317
+ try {
318
+ const result = await this.executeWithTimeout(executeCallback, currentAgentId, currentPrompt);
319
+ const stepDuration = Date.now() - stepStart;
320
+ // Stall detection
321
+ if (result.response.trim().length < STALL_MIN_LENGTH) {
322
+ consecutiveStalls++;
323
+ if (consecutiveStalls >= MAX_CONSECUTIVE_STALLS) {
324
+ consecutiveStalls = 0;
325
+ await notifyCallback(`Agent ${currentAgentId} appears stalled (${MAX_CONSECUTIVE_STALLS} short responses). Re-prompting...`);
326
+ currentPrompt =
327
+ `Your previous responses were too brief. The task is NOT complete yet.\n\n` +
328
+ `Original plan:\n${plan.substring(0, 1000)}\n\n` +
329
+ `Please continue executing the plan. When ALL tasks are done, respond with "BUILD_COMPLETE".`;
330
+ currentAgentId = session.leadAgentId;
331
+ session.steps.push({
332
+ stepNumber: session.currentStep,
333
+ agentId: currentAgentId,
334
+ action: 'stall_detected',
335
+ responseSummary: `Stalled: "${result.response.trim().substring(0, 100)}"`,
336
+ isDelegation: false,
337
+ duration: stepDuration,
338
+ timestamp: Date.now(),
339
+ });
340
+ continue;
341
+ }
342
+ }
343
+ else {
344
+ consecutiveStalls = 0;
345
+ }
346
+ // Council/workflow interceptor
347
+ if (responseInterceptor) {
348
+ const intercepted = await responseInterceptor(result.response, session.channelId);
349
+ if (intercepted) {
350
+ session.steps.push({
351
+ stepNumber: session.currentStep,
352
+ agentId: currentAgentId,
353
+ action: intercepted.type === 'council' ? 'council_execution' : 'workflow_execution',
354
+ responseSummary: intercepted.result.substring(0, 200),
355
+ isDelegation: false,
356
+ duration: Date.now() - stepStart,
357
+ timestamp: Date.now(),
358
+ });
359
+ await notifyCallback(intercepted.result);
360
+ currentPrompt =
361
+ `The ${intercepted.type} plan completed. Results:\n---\n${intercepted.result.substring(0, 1000)}\n---\n` +
362
+ `Continue executing the plan. When ALL tasks are done, respond with "BUILD_COMPLETE".`;
363
+ currentAgentId = session.leadAgentId;
364
+ continue;
365
+ }
366
+ }
367
+ // Delegation check
368
+ const delegationRequest = delegationManager.parseDelegation(currentAgentId, result.response);
369
+ if (delegationRequest) {
370
+ session.steps.push({
371
+ stepNumber: session.currentStep,
372
+ agentId: currentAgentId,
373
+ action: 'delegation',
374
+ responseSummary: delegationRequest.originalContent.substring(0, 200),
375
+ isDelegation: true,
376
+ duration: stepDuration,
377
+ timestamp: Date.now(),
378
+ });
379
+ const delegationResult = await delegationManager.executeDelegation(delegationRequest, executeCallback, notifyCallback);
380
+ if (delegationResult.success && delegationResult.response) {
381
+ session.currentStep++;
382
+ session.steps.push({
383
+ stepNumber: session.currentStep,
384
+ agentId: delegationRequest.toAgentId,
385
+ action: 'delegated_task',
386
+ responseSummary: delegationResult.response.substring(0, 200),
387
+ isDelegation: false,
388
+ duration: delegationResult.duration ?? 0,
389
+ timestamp: Date.now(),
390
+ });
391
+ // Persist step
392
+ if (this.stateManager) {
393
+ await this.stateManager.recordStep(session.id, {
394
+ stepNumber: session.currentStep,
395
+ agentId: delegationRequest.toAgentId,
396
+ action: 'delegated_task',
397
+ responseSummary: delegationResult.response.substring(0, 200),
398
+ isDelegation: false,
399
+ duration: delegationResult.duration ?? 0,
400
+ timestamp: Date.now(),
401
+ });
402
+ }
403
+ currentPrompt = this.buildContinuationAfterDelegation(delegationRequest.toAgentId, delegationResult.response);
404
+ currentAgentId = session.leadAgentId;
405
+ }
406
+ else {
407
+ currentPrompt = `Delegation to ${delegationRequest.toAgentId} failed: ${delegationResult.error}. Please continue the task yourself.`;
408
+ currentAgentId = session.leadAgentId;
409
+ }
410
+ }
411
+ else {
412
+ // No delegation — record step, check build completion
413
+ session.steps.push({
414
+ stepNumber: session.currentStep,
415
+ agentId: currentAgentId,
416
+ action: 'direct_work',
417
+ responseSummary: result.response.substring(0, 200),
418
+ isDelegation: false,
419
+ duration: stepDuration,
420
+ timestamp: Date.now(),
421
+ });
422
+ // Persist step
423
+ if (this.stateManager) {
424
+ await this.stateManager.recordStep(session.id, {
425
+ stepNumber: session.currentStep,
426
+ agentId: currentAgentId,
427
+ action: 'direct_work',
428
+ responseSummary: result.response.substring(0, 200),
429
+ isDelegation: false,
430
+ duration: stepDuration,
431
+ timestamp: Date.now(),
432
+ });
433
+ }
434
+ // Build-phase completion check
435
+ if (this.isBuildComplete(result.response)) {
436
+ return; // Move to retrospective
437
+ }
438
+ // Fallback: use continuation enforcer for "DONE" compat
439
+ const continuation = continuationEnforcer.analyzeResponse(currentAgentId, session.channelId, result.response);
440
+ if (continuation.isComplete) {
441
+ return; // Move to retrospective
442
+ }
443
+ if (continuation.maxRetriesReached) {
444
+ return; // Move to retrospective anyway
445
+ }
446
+ currentPrompt = continuationEnforcer.buildContinuationPrompt(result.response);
447
+ currentAgentId = session.leadAgentId;
448
+ }
449
+ }
450
+ catch (error) {
451
+ const errorMessage = error instanceof Error ? error.message : String(error);
452
+ session.steps.push({
453
+ stepNumber: session.currentStep,
454
+ agentId: currentAgentId,
455
+ action: 'error',
456
+ responseSummary: errorMessage.substring(0, 200),
457
+ isDelegation: false,
458
+ duration: Date.now() - stepStart,
459
+ timestamp: Date.now(),
460
+ });
461
+ currentPrompt = `An error occurred: ${errorMessage}. Please assess the situation and decide how to continue.`;
462
+ currentAgentId = session.leadAgentId;
463
+ }
464
+ }
465
+ }
466
+ // ============================================================================
467
+ // Phase 3: Retrospective
468
+ // ============================================================================
469
+ async runRetrospectivePhase(session, planFromPhase1, _agents, executeCallback, notifyCallback, responseInterceptor) {
470
+ await notifyCallback(`**Phase 3: Retrospective** - Reviewing results...`);
471
+ const steps = this.stateManager
472
+ ? await this.stateManager.loadProgress(session.id)
473
+ : session.steps;
474
+ const plan = this.stateManager
475
+ ? ((await this.stateManager.loadPlan(session.id)) ?? planFromPhase1)
476
+ : planFromPhase1;
477
+ session.currentStep++;
478
+ const retroPrompt = this.buildRetrospectivePrompt(plan, steps);
479
+ const retroResult = await this.executeWithTimeout(executeCallback, session.leadAgentId, retroPrompt);
480
+ session.steps.push({
481
+ stepNumber: session.currentStep,
482
+ agentId: session.leadAgentId,
483
+ action: 'retrospective',
484
+ responseSummary: retroResult.response.substring(0, 200),
485
+ isDelegation: false,
486
+ duration: retroResult.duration ?? 0,
487
+ timestamp: Date.now(),
488
+ });
489
+ // Council check
490
+ if (responseInterceptor) {
491
+ const intercepted = await responseInterceptor(retroResult.response, session.channelId);
492
+ if (intercepted?.type === 'council') {
493
+ await notifyCallback(intercepted.result);
494
+ session.currentStep++;
495
+ session.steps.push({
496
+ stepNumber: session.currentStep,
497
+ agentId: session.leadAgentId,
498
+ action: 'council_execution',
499
+ responseSummary: intercepted.result.substring(0, 200),
500
+ isDelegation: false,
501
+ duration: 0,
502
+ timestamp: Date.now(),
503
+ });
504
+ }
505
+ }
506
+ const isComplete = this.isRetroComplete(retroResult.response);
507
+ // Persist retrospective
508
+ if (this.stateManager) {
509
+ await this.stateManager.saveRetrospective(session.id, retroResult.response);
510
+ }
511
+ return { complete: isComplete, retro: retroResult.response };
512
+ }
513
+ // ============================================================================
514
+ // Phased Loop (Ralph Loop): Plan -> Build -> Retrospective
515
+ // ============================================================================
516
+ async runPhasedLoop(session, agents, executeCallback, notifyCallback, responseInterceptor) {
517
+ // Phase 1: Planning + Council
518
+ if (this.stateManager) {
519
+ await this.stateManager.updatePhase(session.id, 'planning');
520
+ }
521
+ const plan = await this.runPlanningPhase(session, agents, executeCallback, notifyCallback, responseInterceptor);
522
+ if (!this.shouldContinue(session)) {
523
+ this.endSession(session, notifyCallback);
524
+ return;
525
+ }
526
+ // Phase 2: Building
527
+ if (this.stateManager) {
528
+ await this.stateManager.updatePhase(session.id, 'building');
529
+ }
530
+ await this.runBuildingPhase(session, plan, agents, executeCallback, notifyCallback, responseInterceptor);
531
+ if (!this.shouldContinue(session)) {
532
+ this.endSession(session, notifyCallback);
533
+ return;
534
+ }
535
+ // Phase 3: Retrospective + Council
536
+ if (this.stateManager) {
537
+ await this.stateManager.updatePhase(session.id, 'retrospective');
538
+ }
539
+ const { complete } = await this.runRetrospectivePhase(session, plan, agents, executeCallback, notifyCallback, responseInterceptor);
540
+ if (!complete && this.shouldContinue(session)) {
541
+ // Incomplete → re-enter Build phase (max 1 retry)
542
+ await notifyCallback(`Retrospective found incomplete items. Re-entering Build phase...`);
543
+ if (this.stateManager) {
544
+ await this.stateManager.updatePhase(session.id, 'building');
545
+ }
546
+ await this.runBuildingPhase(session, plan, agents, executeCallback, notifyCallback, responseInterceptor);
547
+ // Re-run retrospective after retry Build phase
548
+ if (!this.shouldContinue(session)) {
549
+ // Session cancelled or limits exceeded
550
+ session.active = false;
551
+ this.sessions.delete(session.channelId);
552
+ await notifyCallback(`**UltraWork Session Ended** — limits exceeded or cancelled`);
553
+ return;
554
+ }
555
+ if (this.stateManager) {
556
+ await this.stateManager.updatePhase(session.id, 'retrospective');
557
+ }
558
+ const retryRetro = await this.runRetrospectivePhase(session, plan, agents, executeCallback, notifyCallback, responseInterceptor);
559
+ if (!retryRetro.complete) {
560
+ // Still incomplete after retry — end session with warning
561
+ if (this.stateManager) {
562
+ await this.stateManager.updatePhase(session.id, 'completed');
563
+ }
564
+ session.active = false;
565
+ this.sessions.delete(session.channelId);
566
+ await notifyCallback(`**UltraWork Session Complete** (${session.id}) — with incomplete items\n` +
567
+ `Phases: Plan -> Build -> Retro -> Build (retry) -> Retro\n` +
568
+ `Steps: ${session.currentStep} | Duration: ${Math.round((Date.now() - session.startTime) / 1000)}s`);
569
+ return;
570
+ }
571
+ }
572
+ // Complete
573
+ if (this.stateManager) {
574
+ await this.stateManager.updatePhase(session.id, 'completed');
575
+ }
576
+ session.active = false;
577
+ this.sessions.delete(session.channelId);
578
+ await notifyCallback(`**UltraWork Session Complete** (${session.id})\n` +
579
+ `Phases: Plan -> Build -> Retrospective\n` +
580
+ `Steps: ${session.currentStep} | Duration: ${Math.round((Date.now() - session.startTime) / 1000)}s`);
581
+ }
582
+ // ============================================================================
583
+ // Freeform Loop (Legacy)
584
+ // ============================================================================
585
+ async runFreeformLoop(session, agents, executeCallback, notifyCallback, responseInterceptor) {
174
586
  const delegationManager = new delegation_manager_js_1.DelegationManager(agents, this.permissionManager);
175
587
  const continuationEnforcer = new task_continuation_js_1.TaskContinuationEnforcer({
176
588
  enabled: true,
177
589
  max_retries: 3,
178
590
  });
179
- // Initial prompt for lead agent
591
+ let consecutiveStalls = 0;
180
592
  let currentPrompt = this.buildInitialPrompt(session.task, agents);
181
593
  let currentAgentId = session.leadAgentId;
182
594
  while (this.shouldContinue(session)) {
183
595
  session.currentStep++;
184
596
  const stepStart = Date.now();
185
597
  try {
186
- // Execute current agent's task with timeout protection
187
598
  const result = await this.executeWithTimeout(executeCallback, currentAgentId, currentPrompt);
188
599
  const stepDuration = Date.now() - stepStart;
189
- // Check for delegation in response
600
+ // Stall detection
601
+ if (result.response.trim().length < STALL_MIN_LENGTH) {
602
+ consecutiveStalls++;
603
+ if (consecutiveStalls >= MAX_CONSECUTIVE_STALLS) {
604
+ consecutiveStalls = 0;
605
+ await notifyCallback(`Agent ${currentAgentId} appears stalled (${MAX_CONSECUTIVE_STALLS} short responses). Re-prompting...`);
606
+ currentPrompt = `Your previous responses were too brief. The task is NOT complete yet.\n\nOriginal task: ${session.task}\n\nPlease take concrete action now. When fully done, respond with "DONE".`;
607
+ currentAgentId = session.leadAgentId;
608
+ session.steps.push({
609
+ stepNumber: session.currentStep,
610
+ agentId: currentAgentId,
611
+ action: 'stall_detected',
612
+ responseSummary: `Stalled: "${result.response.trim().substring(0, 100)}"`,
613
+ isDelegation: false,
614
+ duration: stepDuration,
615
+ timestamp: Date.now(),
616
+ });
617
+ continue;
618
+ }
619
+ }
620
+ else {
621
+ consecutiveStalls = 0;
622
+ }
623
+ // Council/workflow interceptor
624
+ if (responseInterceptor) {
625
+ const intercepted = await responseInterceptor(result.response, session.channelId);
626
+ if (intercepted) {
627
+ session.steps.push({
628
+ stepNumber: session.currentStep,
629
+ agentId: currentAgentId,
630
+ action: intercepted.type === 'council' ? 'council_execution' : 'workflow_execution',
631
+ responseSummary: intercepted.result.substring(0, 200),
632
+ isDelegation: false,
633
+ duration: Date.now() - stepStart,
634
+ timestamp: Date.now(),
635
+ });
636
+ await notifyCallback(intercepted.result);
637
+ currentPrompt = `The ${intercepted.type} plan completed. Results:\n---\n${intercepted.result.substring(0, 1000)}\n---\nContinue with the next step. When done, respond with "DONE".`;
638
+ currentAgentId = session.leadAgentId;
639
+ continue;
640
+ }
641
+ }
642
+ // Delegation check
190
643
  const delegationRequest = delegationManager.parseDelegation(currentAgentId, result.response);
191
644
  if (delegationRequest) {
192
- // Record lead agent's step
193
645
  session.steps.push({
194
646
  stepNumber: session.currentStep,
195
647
  agentId: currentAgentId,
@@ -199,10 +651,8 @@ class UltraWorkManager {
199
651
  duration: stepDuration,
200
652
  timestamp: Date.now(),
201
653
  });
202
- // Execute delegation
203
654
  const delegationResult = await delegationManager.executeDelegation(delegationRequest, executeCallback, notifyCallback);
204
655
  if (delegationResult.success && delegationResult.response) {
205
- // Increment again: delegation response counts as a separate step from the lead's request
206
656
  session.currentStep++;
207
657
  session.steps.push({
208
658
  stepNumber: session.currentStep,
@@ -213,18 +663,15 @@ class UltraWorkManager {
213
663
  duration: delegationResult.duration ?? 0,
214
664
  timestamp: Date.now(),
215
665
  });
216
- // Continue with lead agent, incorporating delegation result
217
666
  currentPrompt = this.buildContinuationAfterDelegation(delegationRequest.toAgentId, delegationResult.response);
218
667
  currentAgentId = session.leadAgentId;
219
668
  }
220
669
  else {
221
- // Delegation failed, let lead agent continue
222
670
  currentPrompt = `Delegation to ${delegationRequest.toAgentId} failed: ${delegationResult.error}. Please continue the task yourself.`;
223
671
  currentAgentId = session.leadAgentId;
224
672
  }
225
673
  }
226
674
  else {
227
- // No delegation - record step and check continuation
228
675
  session.steps.push({
229
676
  stepNumber: session.currentStep,
230
677
  agentId: currentAgentId,
@@ -234,10 +681,8 @@ class UltraWorkManager {
234
681
  duration: stepDuration,
235
682
  timestamp: Date.now(),
236
683
  });
237
- // Check if response is complete
238
684
  const continuation = continuationEnforcer.analyzeResponse(currentAgentId, session.channelId, result.response);
239
685
  if (continuation.isComplete) {
240
- // Task is done
241
686
  session.active = false;
242
687
  this.sessions.delete(session.channelId);
243
688
  await notifyCallback(`**UltraWork Session Complete** (${session.id})\n` +
@@ -245,14 +690,12 @@ class UltraWorkManager {
245
690
  break;
246
691
  }
247
692
  if (continuation.maxRetriesReached) {
248
- // Can't continue further
249
693
  session.active = false;
250
694
  this.sessions.delete(session.channelId);
251
695
  await notifyCallback(`**UltraWork Session Stopped** (${session.id}): Max continuation retries reached.\n` +
252
696
  `Steps: ${session.currentStep}`);
253
697
  break;
254
698
  }
255
- // Build continuation prompt
256
699
  currentPrompt = continuationEnforcer.buildContinuationPrompt(result.response);
257
700
  currentAgentId = session.leadAgentId;
258
701
  }
@@ -268,23 +711,101 @@ class UltraWorkManager {
268
711
  duration: Date.now() - stepStart,
269
712
  timestamp: Date.now(),
270
713
  });
271
- // Try to recover by sending error context to lead
272
714
  currentPrompt = `An error occurred: ${errorMessage}. Please assess the situation and decide how to continue.`;
273
715
  currentAgentId = session.leadAgentId;
274
716
  }
275
717
  }
276
718
  // Session limits reached
277
719
  if (session.active) {
278
- const reason = session.currentStep >= session.maxSteps ? 'max steps reached' : 'max duration reached';
279
- session.active = false;
280
- this.sessions.delete(session.channelId);
281
- await notifyCallback(`**UltraWork Session Ended** (${session.id}): ${reason}.\n` +
282
- `Steps: ${session.currentStep} | Duration: ${Math.round((Date.now() - session.startTime) / 1000)}s`);
720
+ this.endSession(session, notifyCallback);
283
721
  }
284
722
  }
285
- /**
286
- * Build the initial prompt for the lead agent.
287
- */
723
+ // ============================================================================
724
+ // Prompt builders
725
+ // ============================================================================
726
+ buildPlanningPrompt(task, agents) {
727
+ const agentList = agents
728
+ .filter((a) => a.enabled !== false)
729
+ .map((a) => `- **${a.display_name}** (ID: ${a.id}, Tier ${a.tier ?? 1})`)
730
+ .join('\n');
731
+ return `**UltraWork — Phase 1: Planning**
732
+
733
+ You are leading an autonomous work session. Before implementing, create a detailed plan.
734
+
735
+ **Task:** ${task}
736
+
737
+ **Available agents:**
738
+ ${agentList}
739
+
740
+ **Instructions:**
741
+ 1. Analyze the task requirements
742
+ 2. If multiple perspectives would help, start a council discussion:
743
+ \`\`\`council_plan
744
+ {"name":"plan_review","topic":"Review implementation approach for: ${task.substring(0, 100)}","agents":["developer","reviewer"],"rounds":1}
745
+ \`\`\`
746
+ 3. After gathering input, create a structured plan:
747
+
748
+ ## Implementation Plan
749
+ ### Task 1: [description]
750
+ - Assigned to: [agent_id]
751
+ - Acceptance criteria: [what defines "done"]
752
+ ### Task 2: ...
753
+
754
+ 4. End with "PLAN_COMPLETE" when the plan is ready.`;
755
+ }
756
+ buildBuildingPrompt(plan, agents) {
757
+ const agentList = agents
758
+ .filter((a) => a.enabled !== false)
759
+ .map((a) => `- ${a.display_name} (ID: ${a.id})`)
760
+ .join('\n');
761
+ return `**UltraWork — Phase 2: Building**
762
+
763
+ Execute the following plan. Delegate tasks to specialists.
764
+
765
+ **Plan:**
766
+ ---
767
+ ${plan.substring(0, 3000)}
768
+ ---
769
+
770
+ **Available agents:**
771
+ ${agentList}
772
+
773
+ **Instructions:**
774
+ - Execute tasks in order from the plan
775
+ - Delegate using: DELEGATE::{agent_id}::{task description with acceptance criteria}
776
+ - If a task fails or needs discussion, use council_plan for team input
777
+ - After ALL tasks are done, respond with "BUILD_COMPLETE"
778
+ - Do NOT skip any tasks from the plan`;
779
+ }
780
+ buildRetrospectivePrompt(plan, steps) {
781
+ const stepSummary = steps
782
+ .map((s) => `- Step ${s.stepNumber} [${s.action}] by ${s.agentId}: ${s.responseSummary.substring(0, 100)}`)
783
+ .join('\n');
784
+ return `**UltraWork — Phase 3: Retrospective**
785
+
786
+ Review the completed work against the original plan.
787
+
788
+ **Original Plan:**
789
+ ---
790
+ ${plan.substring(0, 2000)}
791
+ ---
792
+
793
+ **Completed Steps:**
794
+ ${stepSummary || '(no steps recorded)'}
795
+
796
+ **Instructions:**
797
+ 1. Compare completed work against the plan
798
+ 2. If team review would help, start a council discussion:
799
+ \`\`\`council_plan
800
+ {"name":"retrospective","topic":"Review completed work quality and identify gaps","agents":["developer","reviewer"],"rounds":1}
801
+ \`\`\`
802
+ 3. After council input, provide final assessment:
803
+ - What was completed successfully
804
+ - What needs additional work (if any)
805
+ - Lessons learned
806
+ 4. If ALL tasks are done satisfactorily: respond with "RETRO_COMPLETE"
807
+ 5. If tasks remain: respond with "RETRO_INCOMPLETE" and list remaining items`;
808
+ }
288
809
  buildInitialPrompt(task, agents) {
289
810
  const agentList = agents
290
811
  .filter((a) => a.enabled !== false)
@@ -307,9 +828,6 @@ ${agentList}
307
828
  - End your response with "DONE" when the overall task is complete
308
829
  - Stay focused on the task and be efficient`;
309
830
  }
310
- /**
311
- * Build continuation prompt after a delegation completes.
312
- */
313
831
  buildContinuationAfterDelegation(delegatedAgentId, response) {
314
832
  const summary = response.length > 500 ? response.substring(0, 500) + '...' : response;
315
833
  return `Agent ${delegatedAgentId} completed the delegated task. Their response:
@@ -318,6 +836,34 @@ ${summary}
318
836
  ---
319
837
  Continue with the next step of the overall task. When everything is done, respond with "DONE".`;
320
838
  }
839
+ // ============================================================================
840
+ // Completion markers
841
+ // ============================================================================
842
+ isBuildComplete(response) {
843
+ return /\bBUILD_COMPLETE\b/i.test(response);
844
+ }
845
+ isRetroComplete(response) {
846
+ return /\bRETRO_COMPLETE\b/i.test(response);
847
+ }
848
+ // ============================================================================
849
+ // Helpers
850
+ // ============================================================================
851
+ async endSession(session, notifyCallback) {
852
+ let reason;
853
+ if (!session.active) {
854
+ reason = 'cancelled';
855
+ }
856
+ else if (session.currentStep >= session.maxSteps) {
857
+ reason = 'max steps reached';
858
+ }
859
+ else {
860
+ reason = 'max duration reached';
861
+ }
862
+ session.active = false;
863
+ this.sessions.delete(session.channelId);
864
+ await notifyCallback(`**UltraWork Session Ended** (${session.id}): ${reason}.\n` +
865
+ `Steps: ${session.currentStep} | Duration: ${Math.round((Date.now() - session.startTime) / 1000)}s`);
866
+ }
321
867
  }
322
868
  exports.UltraWorkManager = UltraWorkManager;
323
869
  //# sourceMappingURL=ultrawork.js.map