@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.
- package/README.md +57 -164
- package/dist/agents/SubagentLoader.d.ts +59 -0
- package/dist/agents/SubagentLoader.d.ts.map +1 -0
- package/dist/agents/SubagentLoader.js +140 -0
- package/dist/agents/SubagentLoader.js.map +1 -0
- package/dist/agents/SubagentRunner.d.ts +15 -0
- package/dist/agents/SubagentRunner.d.ts.map +1 -0
- package/dist/agents/SubagentRunner.js +116 -0
- package/dist/agents/SubagentRunner.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +24 -51
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/slash/config.d.ts.map +1 -1
- package/dist/commands/slash/config.js +15 -0
- package/dist/commands/slash/config.js.map +1 -1
- package/dist/commands/slash/context.d.ts.map +1 -1
- package/dist/commands/slash/context.js +31 -0
- package/dist/commands/slash/context.js.map +1 -1
- package/dist/commands/slash/core.d.ts.map +1 -1
- package/dist/commands/slash/core.js +33 -4
- package/dist/commands/slash/core.js.map +1 -1
- package/dist/commands/slash/registry.d.ts.map +1 -1
- package/dist/commands/slash/registry.js +2 -0
- package/dist/commands/slash/registry.js.map +1 -1
- package/dist/commands/slash/session.d.ts.map +1 -1
- package/dist/commands/slash/session.js +97 -44
- package/dist/commands/slash/session.js.map +1 -1
- package/dist/commands/slash/snapshot.d.ts +3 -0
- package/dist/commands/slash/snapshot.d.ts.map +1 -0
- package/dist/commands/slash/snapshot.js +144 -0
- package/dist/commands/slash/snapshot.js.map +1 -0
- package/dist/config/SettingsManager.d.ts +2 -0
- package/dist/config/SettingsManager.d.ts.map +1 -1
- package/dist/config/SettingsManager.js +7 -0
- package/dist/config/SettingsManager.js.map +1 -1
- package/dist/core/CoWorkerAgent.d.ts +32 -0
- package/dist/core/CoWorkerAgent.d.ts.map +1 -1
- package/dist/core/CoWorkerAgent.js +414 -71
- package/dist/core/CoWorkerAgent.js.map +1 -1
- package/dist/hooks/HookEngine.d.ts +115 -0
- package/dist/hooks/HookEngine.d.ts.map +1 -0
- package/dist/hooks/HookEngine.js +220 -0
- package/dist/hooks/HookEngine.js.map +1 -0
- package/dist/memory/AutoMemory.d.ts +55 -0
- package/dist/memory/AutoMemory.d.ts.map +1 -0
- package/dist/memory/AutoMemory.js +185 -0
- package/dist/memory/AutoMemory.js.map +1 -0
- package/dist/permissions/PermissionInterceptor.d.ts +55 -7
- package/dist/permissions/PermissionInterceptor.d.ts.map +1 -1
- package/dist/permissions/PermissionInterceptor.js +170 -22
- package/dist/permissions/PermissionInterceptor.js.map +1 -1
- package/dist/session/SessionManager.d.ts +53 -10
- package/dist/session/SessionManager.d.ts.map +1 -1
- package/dist/session/SessionManager.js +127 -55
- package/dist/session/SessionManager.js.map +1 -1
- package/dist/session/db.d.ts +81 -0
- package/dist/session/db.d.ts.map +1 -0
- package/dist/session/db.js +268 -0
- package/dist/session/db.js.map +1 -0
- package/dist/skills/HookAndSkillManager.d.ts +7 -5
- package/dist/skills/HookAndSkillManager.d.ts.map +1 -1
- package/dist/skills/HookAndSkillManager.js +30 -56
- package/dist/skills/HookAndSkillManager.js.map +1 -1
- package/dist/snapshot/SnapshotManager.d.ts +94 -0
- package/dist/snapshot/SnapshotManager.d.ts.map +1 -0
- package/dist/snapshot/SnapshotManager.js +260 -0
- package/dist/snapshot/SnapshotManager.js.map +1 -0
- package/dist/tools/LSPTool.d.ts +18 -0
- package/dist/tools/LSPTool.d.ts.map +1 -0
- package/dist/tools/LSPTool.js +137 -0
- package/dist/tools/LSPTool.js.map +1 -0
- package/dist/tools/NativeTools.d.ts +4 -3
- package/dist/tools/NativeTools.d.ts.map +1 -1
- package/dist/tools/NativeTools.js +66 -19
- package/dist/tools/NativeTools.js.map +1 -1
- package/dist/tools/Schemas.d.ts +69 -0
- package/dist/tools/Schemas.d.ts.map +1 -1
- package/dist/tools/Schemas.js +32 -0
- package/dist/tools/Schemas.js.map +1 -1
- package/dist/utils/contextManager.d.ts +1 -1
- package/dist/utils/contextManager.d.ts.map +1 -1
- package/dist/utils/output.d.ts +3 -6
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +44 -39
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/systemPrompt.d.ts.map +1 -1
- package/dist/utils/systemPrompt.js +1 -0
- package/dist/utils/systemPrompt.js.map +1 -1
- package/dist/utils/update.d.ts +9 -0
- package/dist/utils/update.d.ts.map +1 -0
- package/dist/utils/update.js +108 -0
- package/dist/utils/update.js.map +1 -0
- 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 = '
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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 (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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':
|
|
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 = `
|
|
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
|