@sylix/coworker 1.5.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +0 -9
- 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.js +4 -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 +446 -74
- 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/scripts/check-playwright-paths.d.ts +2 -0
- package/dist/scripts/check-playwright-paths.d.ts.map +1 -0
- package/dist/scripts/check-playwright-paths.js +23 -0
- package/dist/scripts/check-playwright-paths.js.map +1 -0
- package/dist/scripts/check-require-resolve.d.ts +2 -0
- package/dist/scripts/check-require-resolve.d.ts.map +1 -0
- package/dist/scripts/check-require-resolve.js +19 -0
- package/dist/scripts/check-require-resolve.js.map +1 -0
- package/dist/scripts/test-sylix.d.ts +2 -0
- package/dist/scripts/test-sylix.d.ts.map +1 -0
- package/dist/scripts/test-sylix.js +80 -0
- package/dist/scripts/test-sylix.js.map +1 -0
- package/dist/services/BrowserService.d.ts +1 -0
- package/dist/services/BrowserService.d.ts.map +1 -1
- package/dist/services/BrowserService.js +4 -2
- package/dist/services/BrowserService.js.map +1 -1
- package/dist/services/browser/sandbox/playwright-internals.d.ts.map +1 -1
- package/dist/services/browser/sandbox/playwright-internals.js +20 -0
- package/dist/services/browser/sandbox/playwright-internals.js.map +1 -1
- package/dist/services/browser/sandbox/quickjs-sandbox.d.ts +1 -0
- package/dist/services/browser/sandbox/quickjs-sandbox.d.ts.map +1 -1
- package/dist/services/browser/sandbox/quickjs-sandbox.js +12 -3
- package/dist/services/browser/sandbox/quickjs-sandbox.js.map +1 -1
- package/dist/services/browser/sandbox/script-runner-quickjs.d.ts +4 -1
- package/dist/services/browser/sandbox/script-runner-quickjs.d.ts.map +1 -1
- package/dist/services/browser/sandbox/script-runner-quickjs.js +3 -0
- package/dist/services/browser/sandbox/script-runner-quickjs.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 +80 -26
- 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 -5
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +46 -62
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/systemPrompt.d.ts.map +1 -1
- package/dist/utils/systemPrompt.js +13 -2
- package/dist/utils/systemPrompt.js.map +1 -1
- package/dist/utils/welcome.d.ts.map +1 -1
- package/dist/utils/welcome.js +7 -3
- package/dist/utils/welcome.js.map +1 -1
- 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);
|
|
@@ -133,11 +307,40 @@ class CoWorkerAgent {
|
|
|
133
307
|
catch (e) {
|
|
134
308
|
result = `[ToolError] ${toolName} crashed: ${e.message}`;
|
|
135
309
|
}
|
|
136
|
-
|
|
137
|
-
|
|
310
|
+
let processedResult = result;
|
|
311
|
+
let screenshots = [];
|
|
312
|
+
// Try to parse as JSON for rich results (e.g. Browser with screenshots)
|
|
313
|
+
try {
|
|
314
|
+
const parsed = JSON.parse(result);
|
|
315
|
+
if (parsed && typeof parsed === 'object' && ('stdout' in parsed || 'screenshots' in parsed)) {
|
|
316
|
+
processedResult = parsed.stdout || parsed.error || (parsed.success ? '(success)' : '(failed)');
|
|
317
|
+
if (parsed.screenshots)
|
|
318
|
+
screenshots = parsed.screenshots;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Not JSON, use raw result string
|
|
323
|
+
}
|
|
324
|
+
const toolMsgContent = [{ type: 'text', text: processedResult }];
|
|
325
|
+
for (const base64 of screenshots) {
|
|
326
|
+
toolMsgContent.push({
|
|
327
|
+
type: 'image_url',
|
|
328
|
+
image_url: { url: `data:image/png;base64,${base64}` }
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const toolMsg = {
|
|
332
|
+
role: 'tool',
|
|
333
|
+
content: screenshots.length > 0 ? toolMsgContent : processedResult,
|
|
334
|
+
tool_call_id: toolId
|
|
335
|
+
};
|
|
138
336
|
messages.push(toolMsg);
|
|
139
337
|
await this.sessions.appendMessage(toolMsg);
|
|
140
|
-
|
|
338
|
+
await this.hooks.firePostToolHook(toolName, result, args);
|
|
339
|
+
const displayResult = typeof processedResult === 'string' ? processedResult : JSON.stringify(processedResult);
|
|
340
|
+
const resultLines = displayResult.split('\n').filter(l => l.trim());
|
|
341
|
+
if (screenshots.length > 0) {
|
|
342
|
+
yield ` ${output_1.theme.dim('\u2514')} ${output_1.theme.brand(`Captured ${screenshots.length} screenshot(s)`)}\n`;
|
|
343
|
+
}
|
|
141
344
|
if (resultLines.length <= 2) {
|
|
142
345
|
for (const line of resultLines)
|
|
143
346
|
yield ` ${output_1.theme.dim('\u2514')} ${output_1.theme.dim(line.substring(0, 80))}\n`;
|
|
@@ -147,72 +350,154 @@ class CoWorkerAgent {
|
|
|
147
350
|
yield ` ${output_1.theme.dim('\u2514')} ${output_1.theme.dim(line.substring(0, 80))}\n`;
|
|
148
351
|
yield ` ${output_1.theme.dim(`...and ${resultLines.length - 2} more lines`)}\n`;
|
|
149
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
|
+
});
|
|
150
361
|
}
|
|
151
362
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
body: JSON.stringify({
|
|
159
|
-
model: this.ollamaModel,
|
|
160
|
-
messages,
|
|
161
|
-
tools: Schemas_1.CoWorkerToolSchemas,
|
|
162
|
-
tool_choice: 'auto',
|
|
163
|
-
stream: true,
|
|
164
|
-
options: { num_ctx: contextManager_1.CONTEXT_LIMIT, num_predict: 4096, temperature: 0.7 }
|
|
165
|
-
})
|
|
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,
|
|
166
369
|
});
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (data === '[DONE]') {
|
|
183
|
-
if (toolCallMap.size > 0)
|
|
184
|
-
collected.tool_calls = Array.from(toolCallMap.values());
|
|
185
|
-
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}`);
|
|
186
385
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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;
|
|
193
435
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
}
|
|
200
478
|
}
|
|
201
|
-
const existing = toolCallMap.get(idx);
|
|
202
|
-
if (tc.id)
|
|
203
|
-
existing.id = tc.id;
|
|
204
|
-
if (tc.function?.name)
|
|
205
|
-
existing.function.name += tc.function.name;
|
|
206
|
-
if (tc.function?.arguments)
|
|
207
|
-
existing.function.arguments += tc.function.arguments;
|
|
208
479
|
}
|
|
480
|
+
catch { }
|
|
209
481
|
}
|
|
210
482
|
}
|
|
211
|
-
|
|
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
|
|
212
500
|
}
|
|
213
|
-
}
|
|
214
|
-
if (toolCallMap.size > 0 && collected.tool_calls.length === 0) {
|
|
215
|
-
collected.tool_calls = Array.from(toolCallMap.values());
|
|
216
501
|
}
|
|
217
502
|
}
|
|
218
503
|
async executeToolLocally(toolName, args) {
|
|
@@ -240,7 +525,25 @@ class CoWorkerAgent {
|
|
|
240
525
|
case 'TodoWrite': return NativeTools_1.NativeTools.executeTodoWrite(args.todos);
|
|
241
526
|
case 'BashOutput': return NativeTools_1.NativeTools.executeBashOutput(args.pid);
|
|
242
527
|
case 'KillBash': return NativeTools_1.NativeTools.executeKillShell(args.pid);
|
|
243
|
-
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
|
+
}
|
|
244
547
|
// ═══════════════════════════════════════════════════════════════════
|
|
245
548
|
// BACKWARD COMPATIBILITY / LEGACY
|
|
246
549
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -299,9 +602,51 @@ class CoWorkerAgent {
|
|
|
299
602
|
return `${prompt} (Refer to the previous context or error logs)`;
|
|
300
603
|
return prompt;
|
|
301
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
|
+
}
|
|
302
630
|
async summarizeConversation(messages) {
|
|
303
631
|
const url = `${this.endpoint}/v1/chat/completions`;
|
|
304
|
-
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.`;
|
|
305
650
|
try {
|
|
306
651
|
const res = await (0, node_fetch_1.default)(url, {
|
|
307
652
|
method: 'POST',
|
|
@@ -321,7 +666,34 @@ class CoWorkerAgent {
|
|
|
321
666
|
return 'Summary error occurred.';
|
|
322
667
|
}
|
|
323
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
|
+
}
|
|
324
697
|
}
|
|
325
698
|
exports.CoWorkerAgent = CoWorkerAgent;
|
|
326
|
-
// Removed legacy system prompt logic and moved to src/utils/systemPrompt.ts
|
|
327
699
|
//# sourceMappingURL=CoWorkerAgent.js.map
|