@sylix/coworker 1.5.3 → 2.0.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 (93) hide show
  1. package/README.md +57 -164
  2. package/dist/agents/SubagentLoader.d.ts +59 -0
  3. package/dist/agents/SubagentLoader.d.ts.map +1 -0
  4. package/dist/agents/SubagentLoader.js +140 -0
  5. package/dist/agents/SubagentLoader.js.map +1 -0
  6. package/dist/agents/SubagentRunner.d.ts +15 -0
  7. package/dist/agents/SubagentRunner.d.ts.map +1 -0
  8. package/dist/agents/SubagentRunner.js +116 -0
  9. package/dist/agents/SubagentRunner.js.map +1 -0
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/index.js +24 -51
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/commands/slash/config.d.ts.map +1 -1
  14. package/dist/commands/slash/config.js +15 -0
  15. package/dist/commands/slash/config.js.map +1 -1
  16. package/dist/commands/slash/context.d.ts.map +1 -1
  17. package/dist/commands/slash/context.js +31 -0
  18. package/dist/commands/slash/context.js.map +1 -1
  19. package/dist/commands/slash/core.d.ts.map +1 -1
  20. package/dist/commands/slash/core.js +33 -4
  21. package/dist/commands/slash/core.js.map +1 -1
  22. package/dist/commands/slash/registry.d.ts.map +1 -1
  23. package/dist/commands/slash/registry.js +2 -0
  24. package/dist/commands/slash/registry.js.map +1 -1
  25. package/dist/commands/slash/session.d.ts.map +1 -1
  26. package/dist/commands/slash/session.js +97 -44
  27. package/dist/commands/slash/session.js.map +1 -1
  28. package/dist/commands/slash/snapshot.d.ts +3 -0
  29. package/dist/commands/slash/snapshot.d.ts.map +1 -0
  30. package/dist/commands/slash/snapshot.js +144 -0
  31. package/dist/commands/slash/snapshot.js.map +1 -0
  32. package/dist/config/SettingsManager.d.ts +2 -0
  33. package/dist/config/SettingsManager.d.ts.map +1 -1
  34. package/dist/config/SettingsManager.js +7 -0
  35. package/dist/config/SettingsManager.js.map +1 -1
  36. package/dist/core/CoWorkerAgent.d.ts +32 -0
  37. package/dist/core/CoWorkerAgent.d.ts.map +1 -1
  38. package/dist/core/CoWorkerAgent.js +414 -71
  39. package/dist/core/CoWorkerAgent.js.map +1 -1
  40. package/dist/hooks/HookEngine.d.ts +115 -0
  41. package/dist/hooks/HookEngine.d.ts.map +1 -0
  42. package/dist/hooks/HookEngine.js +220 -0
  43. package/dist/hooks/HookEngine.js.map +1 -0
  44. package/dist/memory/AutoMemory.d.ts +55 -0
  45. package/dist/memory/AutoMemory.d.ts.map +1 -0
  46. package/dist/memory/AutoMemory.js +185 -0
  47. package/dist/memory/AutoMemory.js.map +1 -0
  48. package/dist/permissions/PermissionInterceptor.d.ts +55 -7
  49. package/dist/permissions/PermissionInterceptor.d.ts.map +1 -1
  50. package/dist/permissions/PermissionInterceptor.js +170 -22
  51. package/dist/permissions/PermissionInterceptor.js.map +1 -1
  52. package/dist/session/SessionManager.d.ts +53 -10
  53. package/dist/session/SessionManager.d.ts.map +1 -1
  54. package/dist/session/SessionManager.js +127 -55
  55. package/dist/session/SessionManager.js.map +1 -1
  56. package/dist/session/db.d.ts +81 -0
  57. package/dist/session/db.d.ts.map +1 -0
  58. package/dist/session/db.js +268 -0
  59. package/dist/session/db.js.map +1 -0
  60. package/dist/skills/HookAndSkillManager.d.ts +7 -5
  61. package/dist/skills/HookAndSkillManager.d.ts.map +1 -1
  62. package/dist/skills/HookAndSkillManager.js +30 -56
  63. package/dist/skills/HookAndSkillManager.js.map +1 -1
  64. package/dist/snapshot/SnapshotManager.d.ts +94 -0
  65. package/dist/snapshot/SnapshotManager.d.ts.map +1 -0
  66. package/dist/snapshot/SnapshotManager.js +260 -0
  67. package/dist/snapshot/SnapshotManager.js.map +1 -0
  68. package/dist/tools/LSPTool.d.ts +18 -0
  69. package/dist/tools/LSPTool.d.ts.map +1 -0
  70. package/dist/tools/LSPTool.js +137 -0
  71. package/dist/tools/LSPTool.js.map +1 -0
  72. package/dist/tools/NativeTools.d.ts +4 -3
  73. package/dist/tools/NativeTools.d.ts.map +1 -1
  74. package/dist/tools/NativeTools.js +66 -19
  75. package/dist/tools/NativeTools.js.map +1 -1
  76. package/dist/tools/Schemas.d.ts +69 -0
  77. package/dist/tools/Schemas.d.ts.map +1 -1
  78. package/dist/tools/Schemas.js +32 -0
  79. package/dist/tools/Schemas.js.map +1 -1
  80. package/dist/utils/contextManager.d.ts +1 -1
  81. package/dist/utils/contextManager.d.ts.map +1 -1
  82. package/dist/utils/output.d.ts +3 -6
  83. package/dist/utils/output.d.ts.map +1 -1
  84. package/dist/utils/output.js +44 -39
  85. package/dist/utils/output.js.map +1 -1
  86. package/dist/utils/systemPrompt.d.ts.map +1 -1
  87. package/dist/utils/systemPrompt.js +1 -0
  88. package/dist/utils/systemPrompt.js.map +1 -1
  89. package/dist/utils/update.d.ts +9 -0
  90. package/dist/utils/update.d.ts.map +1 -0
  91. package/dist/utils/update.js +108 -0
  92. package/dist/utils/update.js.map +1 -0
  93. package/package.json +3 -1
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -18,6 +51,11 @@ const node_fetch_1 = __importDefault(require("node-fetch"));
18
51
  const ContextReader_1 = require("./ContextReader");
19
52
  const systemPrompt_1 = require("../utils/systemPrompt");
20
53
  const contextManager_1 = require("../utils/contextManager");
54
+ const SnapshotManager_1 = require("../snapshot/SnapshotManager");
55
+ const HookEngine_1 = require("../hooks/HookEngine");
56
+ const SubagentLoader_1 = require("../agents/SubagentLoader");
57
+ const AutoMemory_1 = require("../memory/AutoMemory");
58
+ const readline = __importStar(require("readline"));
21
59
  // No fixed tool turn limit - let the model decide when it's done.
22
60
  /**
23
61
  * THE SDK LAYER — Real Agentic Tool Loop for CoWorker v1.5.0.
@@ -25,9 +63,14 @@ const contextManager_1 = require("../utils/contextManager");
25
63
  class CoWorkerAgent {
26
64
  constructor(options) {
27
65
  this.systemPrompt = '';
66
+ this.protocol = 'build';
67
+ this.recentToolCalls = [];
68
+ this.DOOM_LOOP_THRESHOLD = 3;
69
+ this.MAX_RETRIES = 3;
70
+ this.RETRY_BASE_DELAY_MS = 1000;
28
71
  this.model = options.model || 'helix-1.2';
29
72
  this.endpoint = options.endpoint || 'https://ollama-ubuntu.sylixide.com';
30
- this.ollamaModel = 'glm-4.7-flash:q4_K_M';
73
+ this.ollamaModel = 'qwen3.5:35b-a3b-q4_K_M';
31
74
  this.cwd = options.cwd || process.cwd();
32
75
  this.tasks = new TaskEngine_1.TaskEngine();
33
76
  this.sessions = new SessionManager_1.SessionManager((0, crypto_1.randomUUID)());
@@ -35,37 +78,118 @@ class CoWorkerAgent {
35
78
  this.hooks = new HookAndSkillManager_1.HookAndSkillManager();
36
79
  this.settings = new SettingsManager_1.SettingsManager();
37
80
  this.permissions = new PermissionInterceptor_1.PermissionInterceptor();
81
+ this.snapshots = new SnapshotManager_1.SnapshotManager(options.cwd || process.cwd());
82
+ this.hookEngine = new HookEngine_1.HookEngine();
83
+ this.subagentLoader = new SubagentLoader_1.SubagentLoader();
84
+ this.autoMemory = new AutoMemory_1.AutoMemory();
85
+ // Lazy initialize LSP to save resources if not used
86
+ this.lspClient = null;
38
87
  }
39
88
  async boot() {
40
89
  await this.hooks.loadSkillsFromDirectory();
90
+ this.hooks.registerHooks(this.hookEngine);
91
+ // Initialize the snapshot system
92
+ await this.snapshots.init();
93
+ // Load subagent definitions
94
+ await this.subagentLoader.load();
41
95
  // Inject Memory Hierarchy
42
96
  this.systemPrompt = await (0, systemPrompt_1.getSystemPrompt)(this.cwd);
97
+ // Load auto-memory (persistent cross-session learnings)
98
+ const autoMem = await this.autoMemory.load(this.cwd);
99
+ if (autoMem) {
100
+ this.systemPrompt += `\n\n## Auto-Learned Context (from previous sessions)\n${autoMem}`;
101
+ }
43
102
  this.systemPrompt += `\n\n## Available Skills\n${this.hooks.getSkillsSummary()}`;
103
+ // List available subagents in system prompt
104
+ const agents = this.subagentLoader.list();
105
+ if (agents.length > 0) {
106
+ this.systemPrompt += `\n\n## Available Subagents\n`;
107
+ for (const a of agents) {
108
+ this.systemPrompt += `- **${a.name}**: ${a.description} (tools: ${a.tools?.join(', ') || 'all'}, max ${a.maxTurns} turns)\n`;
109
+ }
110
+ }
44
111
  await this.sessions.loadCheckpoint();
45
112
  const config = await this.settings.loadSettings();
113
+ // Initialize Advanced Permission System (Claude Code Parity)
46
114
  this.permissions = new PermissionInterceptor_1.PermissionInterceptor({
115
+ mode: 'default', // TODO: allow override from settings
116
+ rules: config.rules,
47
117
  allowedTools: config.allow,
48
118
  deniedTools: config.deny
49
119
  });
120
+ // Load hooks from settings
121
+ if (config.hooks) {
122
+ this.hookEngine.loadFromConfig(config.hooks);
123
+ }
50
124
  for (const [name, server] of Object.entries(config.mcpServers)) {
51
125
  await this.mcp.connectServer(name, server.command, server.args);
52
126
  }
127
+ // Fire SessionStart event
128
+ await this.hookEngine.fire('SessionStart', {
129
+ event: 'SessionStart',
130
+ sessionId: this.sessions.currentSessionId,
131
+ cwd: this.cwd,
132
+ });
53
133
  }
54
134
  async *chat(prompt) {
55
135
  await this.sessions.rollupMemory();
136
+ // Fire UserPromptSubmit hook (can modify or block prompt)
137
+ const promptHook = await this.hookEngine.fireBlocking('UserPromptSubmit', {
138
+ event: 'UserPromptSubmit',
139
+ prompt,
140
+ cwd: this.cwd,
141
+ });
142
+ if (!promptHook.allow) {
143
+ yield `\n [WARNING] Prompt blocked by hook: ${promptHook.reason || 'no reason'}\n`;
144
+ return;
145
+ }
146
+ if (promptHook.modifiedPrompt) {
147
+ prompt = promptHook.modifiedPrompt;
148
+ }
149
+ // Protocol Handling
150
+ if (prompt.startsWith('/explore ')) {
151
+ this.protocol = 'explore';
152
+ prompt = prompt.replace('/explore ', '');
153
+ }
154
+ else if (prompt.startsWith('/plan ')) {
155
+ this.protocol = 'plan';
156
+ prompt = prompt.replace('/plan ', '');
157
+ }
158
+ else {
159
+ this.protocol = 'build';
160
+ }
161
+ // Update permission mode based on protocol
162
+ if (this.protocol === 'explore') {
163
+ this.permissions['settings'].mode = 'explore';
164
+ }
165
+ else if (this.protocol === 'plan') {
166
+ this.permissions['settings'].mode = 'plan';
167
+ }
168
+ else {
169
+ this.permissions['settings'].mode = 'default';
170
+ }
56
171
  // ── Context Management ──
57
172
  // 1. Prune large tool results
58
173
  this.sessions.state.messages = (0, contextManager_1.pruneToolResults)(this.sessions.state.messages);
59
- // 2. Auto-Compact if needed
174
+ // 2. Auto-Compact if needed (Context 2.0)
60
175
  const usage = (0, contextManager_1.getContextUsage)(this.sessions.state.messages);
61
176
  if (usage >= contextManager_1.AUTO_COMPACT_THRESHOLD) {
62
- yield `\n ${output_1.theme.brand('')} context window at ${Math.floor(usage * 100)}% — compressing...\n`;
63
- const summary = await this.summarizeConversation(this.sessions.state.messages);
64
- const saved = (0, contextManager_1.estimateTokens)(this.sessions.state.messages) - (0, contextManager_1.estimateTokens)([{ role: 'assistant', content: summary }]);
65
- this.sessions.state.messages = [
66
- { role: 'assistant', content: `[conversation compressed]\n\n${summary}` }
67
- ];
68
- yield ` ${output_1.theme.success('✓')} compressed · saved ~${saved.toLocaleString()} tokens\n`;
177
+ await this.hookEngine.fire('PreCompact', { event: 'PreCompact', cwd: this.cwd });
178
+ yield `\n [SYSTEM] context window at ${Math.floor(usage * 100)}% — optimizing memory...\n`;
179
+ const result = await this.sessions.performTwoPhaseCompaction(async (content) => this.summarizeChunk(content), 10 // Keep last 10 messages raw
180
+ );
181
+ if (result.savedTokens > 0) {
182
+ yield ` [DONE] optimized · saved ~${result.savedTokens.toLocaleString()} tokens\n`;
183
+ }
184
+ else {
185
+ // Fallback: if tool pruning wasn't enough, do a full summary
186
+ const summary = await this.summarizeConversation(this.sessions.state.messages);
187
+ await this.sessions.compactMessages(summary);
188
+ yield ` [DONE] deep compression completed\n`;
189
+ }
190
+ await this.hookEngine.fire('PostCompact', { event: 'PostCompact', cwd: this.cwd });
191
+ // AUTO-REPLAY
192
+ yield ` [RETRY] resuming session...\n`;
69
193
  }
70
194
  const isSimpleGreeting = /^(hi|hey|hello|thanks|ok|cool|great)$/i.test(prompt.trim());
71
195
  const liveContext = isSimpleGreeting ? '' : await ContextReader_1.ContextReader.getProjectContext(this.cwd);
@@ -79,6 +203,17 @@ class CoWorkerAgent {
79
203
  if (skillPrompt)
80
204
  fullSystemPrompt += skillPrompt;
81
205
  }
206
+ if (this.protocol === 'explore') {
207
+ fullSystemPrompt += `\n\n## PROTOCOL: EXPLORE (Read-Only)
208
+ - You are in READ-ONLY mode.
209
+ - You may NOT use: executeWrite, executeEdit, executeMultiEdit, executeBash (for mutations).
210
+ - Focus on understanding the codebase, architecture, and flows.`;
211
+ }
212
+ else if (this.protocol === 'plan') {
213
+ fullSystemPrompt += `\n\n## PROTOCOL: PLAN (Strict Planning)
214
+ - You MUST use TodoWrite to plan your actions first.
215
+ - Do NOT modify any code until the plan is finalized and approved.`;
216
+ }
82
217
  const messages = [
83
218
  { role: 'system', content: fullSystemPrompt },
84
219
  ...this.sessions.state.messages,
@@ -111,7 +246,7 @@ class CoWorkerAgent {
111
246
  catch {
112
247
  const errMsg = `[ToolError] Failed to parse arguments for ${toolName}`;
113
248
  messages.push({ role: 'tool', content: errMsg, tool_call_id: toolId });
114
- yield `\n ${errMsg}\n`;
249
+ yield `\n [ERROR] ${errMsg}\n`;
115
250
  continue;
116
251
  }
117
252
  const mutationTools = ['executeWrite', 'run_terminal_command', 'executeBash'];
@@ -124,8 +259,47 @@ class CoWorkerAgent {
124
259
  continue;
125
260
  }
126
261
  }
127
- await this.hooks.firePreToolHook(toolName, args);
128
- yield `\n ◈ ${output_1.theme.brand(toolName)}` + (args.path || args.dir || args.filePath || args.pattern ? output_1.theme.dim(` \u00b7 ${args.path || args.dir || args.filePath || args.pattern}`) : '') + '\n';
262
+ // ── Doom Loop Detection ──
263
+ // If the same tool is called 3+ times with identical args, BLOCK and ask permission
264
+ const currentCall = { toolName, args };
265
+ this.recentToolCalls.push(currentCall);
266
+ if (this.recentToolCalls.length > this.DOOM_LOOP_THRESHOLD) {
267
+ this.recentToolCalls.shift();
268
+ }
269
+ if (this.recentToolCalls.length === this.DOOM_LOOP_THRESHOLD) {
270
+ const lastThree = this.recentToolCalls;
271
+ const isDoomLoop = lastThree.every(c => c.toolName === toolName && JSON.stringify(c.args) === JSON.stringify(args));
272
+ if (isDoomLoop) {
273
+ yield `\n ${output_1.theme.warning('⚠')} DOOM LOOP DETECTED — ${output_1.theme.dim(toolName)} ×${this.DOOM_LOOP_THRESHOLD}\n`;
274
+ yield ` ${output_1.theme.dim('The agent is repeating the same tool call. This usually indicates it is stuck.')}\n`;
275
+ // BLOCK execution and ask user permission to continue
276
+ const shouldContinue = await this.askUserDoomLoop(toolName);
277
+ if (!shouldContinue) {
278
+ const blocked = `[DOOM LOOP BLOCKED] User stopped repeated ${toolName} calls. Try a different approach.`;
279
+ messages.push({ role: 'tool', content: blocked, tool_call_id: toolId });
280
+ yield ` [BLOCKED] ${output_1.theme.warning('Blocked — agent must try a different approach')}\n`;
281
+ this.recentToolCalls = [];
282
+ continue;
283
+ }
284
+ this.recentToolCalls = [];
285
+ }
286
+ }
287
+ // Capture working tree state BEFORE tool execution for undo
288
+ await this.snapshots.track(this.cwd, `Before ${toolName}`);
289
+ // Fire PreToolUse hook (can block)
290
+ const preHook = await this.hookEngine.fireBlocking('PreToolUse', {
291
+ event: 'PreToolUse',
292
+ toolName,
293
+ args,
294
+ cwd: this.cwd,
295
+ });
296
+ if (!preHook.allow) {
297
+ const blocked = `[Hook Blocked] ${preHook.reason || 'denied by hook'}`;
298
+ messages.push({ role: 'tool', content: blocked, tool_call_id: toolId });
299
+ yield `\n ✗ ${blocked}\n`;
300
+ continue;
301
+ }
302
+ yield `\n [TOOL] ${output_1.theme.brand(toolName)}` + (args.path || args.dir || args.filePath || args.pattern ? output_1.theme.dim(` : ${args.path || args.dir || args.filePath || args.pattern}`) : '') + '\n';
129
303
  let result;
130
304
  try {
131
305
  result = await this.executeToolLocally(toolName, args);
@@ -176,72 +350,154 @@ class CoWorkerAgent {
176
350
  yield ` ${output_1.theme.dim('\u2514')} ${output_1.theme.dim(line.substring(0, 80))}\n`;
177
351
  yield ` ${output_1.theme.dim(`...and ${resultLines.length - 2} more lines`)}\n`;
178
352
  }
353
+ // Fire PostToolUse hooks
354
+ await this.hookEngine.fire('PostToolUse', {
355
+ event: 'PostToolUse',
356
+ toolName,
357
+ args,
358
+ result: processedResult,
359
+ cwd: this.cwd,
360
+ });
179
361
  }
180
362
  }
181
- }
182
- async *callLLMStreamUnified(messages, collected) {
183
- const url = `${this.endpoint}/v1/chat/completions`;
184
- const res = await (0, node_fetch_1.default)(url, {
185
- method: 'POST',
186
- headers: { 'Content-Type': 'application/json' },
187
- body: JSON.stringify({
188
- model: this.ollamaModel,
189
- messages,
190
- tools: Schemas_1.CoWorkerToolSchemas,
191
- tool_choice: 'auto',
192
- stream: true,
193
- options: { num_ctx: contextManager_1.CONTEXT_LIMIT, num_predict: 4096, temperature: 0.7 }
194
- })
363
+ // ── Turn Completion ──
364
+ // Fire SessionEnd and extract learnings for AutoMemory
365
+ await this.hookEngine.fire('SessionEnd', {
366
+ event: 'SessionEnd',
367
+ sessionId: this.sessions.currentSessionId,
368
+ cwd: this.cwd,
195
369
  });
196
- if (!res.ok)
197
- throw new Error(`LLM call failed (${res.status}): ${await res.text()}`);
198
- const nodeStream = res.body;
199
- if (!nodeStream)
200
- throw new Error('No response body');
201
- let buffer = '';
202
- const toolCallMap = new Map();
203
- for await (const rawChunk of nodeStream) {
204
- buffer += rawChunk.toString();
205
- const lines = buffer.split('\n');
206
- buffer = lines.pop() || '';
207
- for (const line of lines) {
208
- if (!line.trim().startsWith('data: '))
209
- continue;
210
- const data = line.trim().slice(6).trim();
211
- if (data === '[DONE]') {
212
- if (toolCallMap.size > 0)
213
- collected.tool_calls = Array.from(toolCallMap.values());
214
- return;
370
+ // Optionally extract learnings if the conversation was substantial (>2 turns)
371
+ if (this.sessions.getMessageCount() > 4) {
372
+ const learningPrompt = this.autoMemory.getExtractionPrompt();
373
+ const learningMessages = [
374
+ ...this.sessions.state.messages,
375
+ { role: 'user', content: learningPrompt }
376
+ ];
377
+ try {
378
+ const learningContent = { content: '', tool_calls: [] };
379
+ // Run extraction in background (don't yield to user)
380
+ for await (const _ of this.callLLMStreamUnified(learningMessages, learningContent)) { }
381
+ if (learningContent.content.trim()) {
382
+ await this.autoMemory.append(this.cwd, learningContent.content.trim());
383
+ if (process.env.CW_VERBOSE)
384
+ console.log(`[AutoMemory] Persisted learnings for ${this.cwd}`);
215
385
  }
216
- try {
217
- const parsed = JSON.parse(data);
218
- const content = parsed.choices?.[0]?.delta?.content || '';
219
- if (content) {
220
- collected.content += content;
221
- yield content;
386
+ }
387
+ catch (e) {
388
+ if (process.env.CW_VERBOSE)
389
+ console.warn(`[AutoMemory] Extraction failed: ${e.message}`);
390
+ }
391
+ }
392
+ }
393
+ /**
394
+ * Retryable HTTP status codes (rate limits, server errors, network issues).
395
+ */
396
+ isRetryableError(status) {
397
+ return status === 429 || status === 500 || status === 502 || status === 503 || status === 504 || status === 0;
398
+ }
399
+ async sleep(ms, signal) {
400
+ return new Promise((resolve, reject) => {
401
+ const timeout = setTimeout(resolve, ms);
402
+ signal?.addEventListener('abort', () => {
403
+ clearTimeout(timeout);
404
+ reject(new Error('sleep aborted'));
405
+ });
406
+ });
407
+ }
408
+ async *callLLMStreamUnified(messages, collected, signal) {
409
+ let attempt = 0;
410
+ while (true) {
411
+ attempt++;
412
+ try {
413
+ const url = `${this.endpoint}/v1/chat/completions`;
414
+ const res = await (0, node_fetch_1.default)(url, {
415
+ method: 'POST',
416
+ headers: { 'Content-Type': 'application/json' },
417
+ body: JSON.stringify({
418
+ model: this.ollamaModel,
419
+ messages,
420
+ tools: Schemas_1.CoWorkerToolSchemas,
421
+ tool_choice: 'auto',
422
+ stream: true,
423
+ options: { num_ctx: contextManager_1.CONTEXT_LIMIT, num_predict: 4096, temperature: 0.7 }
424
+ }),
425
+ signal,
426
+ });
427
+ if (!res.ok) {
428
+ const errorText = await res.text().catch(() => '');
429
+ const status = res.status;
430
+ if (this.isRetryableError(status) && attempt < this.MAX_RETRIES) {
431
+ const delay = this.RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
432
+ yield `\n ${output_1.theme.warning('⚠')} ${status} error — retrying in ${delay / 1000}s... (${attempt}/${this.MAX_RETRIES})\n`;
433
+ await this.sleep(delay, signal);
434
+ continue;
222
435
  }
223
- const deltas = parsed.choices?.[0]?.delta?.tool_calls;
224
- if (deltas) {
225
- for (const tc of deltas) {
226
- const idx = tc.index ?? 0;
227
- if (!toolCallMap.has(idx)) {
228
- toolCallMap.set(idx, { id: tc.id || `call_${(0, crypto_1.randomUUID)().substring(0, 8)}`, function: { name: '', arguments: '' } });
436
+ throw new Error(`LLM call failed (${status}): ${errorText}`);
437
+ }
438
+ const nodeStream = res.body;
439
+ if (!nodeStream)
440
+ throw new Error('No response body');
441
+ let buffer = '';
442
+ const toolCallMap = new Map();
443
+ for await (const rawChunk of nodeStream) {
444
+ buffer += rawChunk.toString();
445
+ const lines = buffer.split('\n');
446
+ buffer = lines.pop() || '';
447
+ for (const line of lines) {
448
+ if (!line.trim().startsWith('data: '))
449
+ continue;
450
+ const data = line.trim().slice(6).trim();
451
+ if (data === '[DONE]') {
452
+ if (toolCallMap.size > 0)
453
+ collected.tool_calls = Array.from(toolCallMap.values());
454
+ return;
455
+ }
456
+ try {
457
+ const parsed = JSON.parse(data);
458
+ const content = parsed.choices?.[0]?.delta?.content || '';
459
+ if (content) {
460
+ collected.content += content;
461
+ yield content;
462
+ }
463
+ const deltas = parsed.choices?.[0]?.delta?.tool_calls;
464
+ if (deltas) {
465
+ for (const tc of deltas) {
466
+ const idx = tc.index ?? 0;
467
+ if (!toolCallMap.has(idx)) {
468
+ toolCallMap.set(idx, { id: tc.id || `call_${(0, crypto_1.randomUUID)().substring(0, 8)}`, function: { name: '', arguments: '' } });
469
+ }
470
+ const existing = toolCallMap.get(idx);
471
+ if (tc.id)
472
+ existing.id = tc.id;
473
+ if (tc.function?.name)
474
+ existing.function.name += tc.function.name;
475
+ if (tc.function?.arguments)
476
+ existing.function.arguments += tc.function.arguments;
477
+ }
229
478
  }
230
- const existing = toolCallMap.get(idx);
231
- if (tc.id)
232
- existing.id = tc.id;
233
- if (tc.function?.name)
234
- existing.function.name += tc.function.name;
235
- if (tc.function?.arguments)
236
- existing.function.arguments += tc.function.arguments;
237
479
  }
480
+ catch { }
238
481
  }
239
482
  }
240
- catch { }
483
+ if (toolCallMap.size > 0 && collected.tool_calls.length === 0) {
484
+ collected.tool_calls = Array.from(toolCallMap.values());
485
+ }
486
+ return; // success
487
+ }
488
+ catch (e) {
489
+ if (e.message === 'sleep aborted')
490
+ return;
491
+ if (e.message.includes('fetch failed') || e.message.includes('network') || this.isRetryableError(0)) {
492
+ if (attempt < this.MAX_RETRIES) {
493
+ const delay = this.RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
494
+ yield `\n ${output_1.theme.warning('⚠')} Network error — retrying in ${delay / 1000}s... (${attempt}/${this.MAX_RETRIES})\n`;
495
+ await this.sleep(delay, signal);
496
+ continue;
497
+ }
498
+ }
499
+ throw e; // non-retryable or max retries reached
241
500
  }
242
- }
243
- if (toolCallMap.size > 0 && collected.tool_calls.length === 0) {
244
- collected.tool_calls = Array.from(toolCallMap.values());
245
501
  }
246
502
  }
247
503
  async executeToolLocally(toolName, args) {
@@ -269,7 +525,25 @@ class CoWorkerAgent {
269
525
  case 'TodoWrite': return NativeTools_1.NativeTools.executeTodoWrite(args.todos);
270
526
  case 'BashOutput': return NativeTools_1.NativeTools.executeBashOutput(args.pid);
271
527
  case 'KillBash': return NativeTools_1.NativeTools.executeKillShell(args.pid);
272
- case 'executeBrowser': return NativeTools_1.NativeTools.executeBrowser(args.script);
528
+ case 'executeBrowser':
529
+ return await NativeTools_1.NativeTools.executeBrowser(args.script);
530
+ // ── LSP Tools (Vision 2.0) ──
531
+ case 'LSP_Definition':
532
+ case 'LSP_References': {
533
+ if (!this.lspClient) {
534
+ const { LSPClient } = await Promise.resolve().then(() => __importStar(require('../tools/LSPTool')));
535
+ this.lspClient = new LSPClient(this.cwd);
536
+ await this.lspClient.start();
537
+ }
538
+ if (toolName === 'LSP_Definition') {
539
+ const res = await this.lspClient.gotoDefinition(args.file_path || args.filePath, args.line, args.character);
540
+ return JSON.stringify(res || { error: 'No definition found' });
541
+ }
542
+ else {
543
+ const res = await this.lspClient.findReferences(args.file_path || args.filePath, args.line, args.character);
544
+ return JSON.stringify(res || { error: 'No references found' });
545
+ }
546
+ }
273
547
  // ═══════════════════════════════════════════════════════════════════
274
548
  // BACKWARD COMPATIBILITY / LEGACY
275
549
  // ═══════════════════════════════════════════════════════════════════
@@ -328,9 +602,51 @@ class CoWorkerAgent {
328
602
  return `${prompt} (Refer to the previous context or error logs)`;
329
603
  return prompt;
330
604
  }
605
+ /**
606
+ * Summarize a specific chunk of tool output (for Context 2.0).
607
+ */
608
+ async summarizeChunk(content) {
609
+ const url = `${this.endpoint}/v1/chat/completions`;
610
+ const prompt = `Summarize the following tool output in 50 words or less. Focus on the final result and any errors:\n\n${content}`;
611
+ try {
612
+ const res = await (0, node_fetch_1.default)(url, {
613
+ method: 'POST',
614
+ headers: { 'Content-Type': 'application/json' },
615
+ body: JSON.stringify({
616
+ model: this.ollamaModel,
617
+ messages: [{ role: 'user', content: prompt }],
618
+ options: { num_ctx: 4096, num_predict: 200, temperature: 0.2 }
619
+ })
620
+ });
621
+ if (!res.ok)
622
+ return '[summarization failed]';
623
+ const data = await res.json();
624
+ return (data.choices?.[0]?.message?.content || '[no summary]').trim();
625
+ }
626
+ catch {
627
+ return '[summarization error]';
628
+ }
629
+ }
331
630
  async summarizeConversation(messages) {
332
631
  const url = `${this.endpoint}/v1/chat/completions`;
333
- const prompt = `Summarize the preceding conversation concisely, retaining all critical technical decisions, file paths, and task progress. Stay under 1000 words.`;
632
+ const prompt = `You are a conversation compaction agent. Summarize the conversation into the following structured format. Be concise but preserve ALL critical information:
633
+
634
+ ## Goal
635
+ [What the user is trying to accomplish — the primary objective]
636
+
637
+ ## Instructions
638
+ [Any specific user instructions, preferences, or constraints mentioned]
639
+
640
+ ## Discoveries
641
+ [Key technical facts learned during the conversation — file paths, APIs, patterns, architecture decisions]
642
+
643
+ ## Accomplished
644
+ [What work has been completed, what is in progress, and what remains]
645
+
646
+ ## Relevant Files / Directories
647
+ [List of files and directories that are relevant to the ongoing work]
648
+
649
+ Stay under 800 words. Focus on actionable information.`;
334
650
  try {
335
651
  const res = await (0, node_fetch_1.default)(url, {
336
652
  method: 'POST',
@@ -350,7 +666,34 @@ class CoWorkerAgent {
350
666
  return 'Summary error occurred.';
351
667
  }
352
668
  }
669
+ /**
670
+ * Ask the user whether to continue after a doom loop detection.
671
+ * Blocks execution until user responds.
672
+ */
673
+ async askUserDoomLoop(toolName) {
674
+ if (!process.stdin.isTTY) {
675
+ // Headless mode — auto-block doom loops
676
+ return false;
677
+ }
678
+ return new Promise((resolve) => {
679
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
680
+ rl.question(` ${output_1.theme.warning('Continue anyway?')} ${output_1.theme.dim('[y/N]')} `, (answer) => {
681
+ rl.close();
682
+ resolve(answer.trim().toLowerCase() === 'y');
683
+ });
684
+ });
685
+ }
686
+ /**
687
+ * Resolve the Ollama model name from the CoWorker model alias.
688
+ */
689
+ resolveOllamaModel(model) {
690
+ const modelMap = {
691
+ 'helix-1.2': 'qwen3.5:35b-a3b-q4_K_M',
692
+ 'helix-1.0': 'qwen3.5:35b-a3b-q4_K_M',
693
+ 'helix-fast': 'qwen3.5:35b-a3b-q4_K_M',
694
+ };
695
+ return modelMap[model] || model;
696
+ }
353
697
  }
354
698
  exports.CoWorkerAgent = CoWorkerAgent;
355
- // Removed legacy system prompt logic and moved to src/utils/systemPrompt.ts
356
699
  //# sourceMappingURL=CoWorkerAgent.js.map