@kkeve/suite 0.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 (63) hide show
  1. package/dist/agent/factory.d.ts +25 -0
  2. package/dist/agent/factory.js +99 -0
  3. package/dist/agent/index.d.ts +3 -0
  4. package/dist/agent/index.js +7 -0
  5. package/dist/agent/loader.d.ts +22 -0
  6. package/dist/agent/loader.js +117 -0
  7. package/dist/agent/types.d.ts +9 -0
  8. package/dist/agent/types.js +2 -0
  9. package/dist/agent-loop/complete-task.d.ts +28 -0
  10. package/dist/agent-loop/complete-task.js +70 -0
  11. package/dist/agent-loop/index.d.ts +16 -0
  12. package/dist/agent-loop/index.js +26 -0
  13. package/dist/agent-loop/loop.d.ts +42 -0
  14. package/dist/agent-loop/loop.js +282 -0
  15. package/dist/agent-loop/runtime.d.ts +40 -0
  16. package/dist/agent-loop/runtime.js +163 -0
  17. package/dist/agent-loop/types.d.ts +161 -0
  18. package/dist/agent-loop/types.js +105 -0
  19. package/dist/context/index.d.ts +8 -0
  20. package/dist/context/index.js +29 -0
  21. package/dist/context/tokenCalculation.d.ts +55 -0
  22. package/dist/context/tokenCalculation.js +95 -0
  23. package/dist/context/truncation.d.ts +67 -0
  24. package/dist/context/truncation.js +142 -0
  25. package/dist/context/types.d.ts +7 -0
  26. package/dist/context/types.js +27 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.js +45 -0
  29. package/dist/llm/OpenAIClient.d.ts +31 -0
  30. package/dist/llm/OpenAIClient.js +455 -0
  31. package/dist/llm/constants.d.ts +2 -0
  32. package/dist/llm/constants.js +6 -0
  33. package/dist/llm/errors.d.ts +25 -0
  34. package/dist/llm/errors.js +51 -0
  35. package/dist/llm/index.d.ts +49 -0
  36. package/dist/llm/index.js +158 -0
  37. package/dist/llm/types.d.ts +124 -0
  38. package/dist/llm/types.js +2 -0
  39. package/dist/llm/utils.d.ts +29 -0
  40. package/dist/llm/utils.js +284 -0
  41. package/dist/skill/frontmatter-parser.d.ts +50 -0
  42. package/dist/skill/frontmatter-parser.js +152 -0
  43. package/dist/skill/index.d.ts +12 -0
  44. package/dist/skill/index.js +17 -0
  45. package/dist/skill/loader.d.ts +96 -0
  46. package/dist/skill/loader.js +256 -0
  47. package/dist/tools/ask-user.d.ts +8 -0
  48. package/dist/tools/ask-user.js +47 -0
  49. package/dist/tools/bash.d.ts +47 -0
  50. package/dist/tools/bash.js +213 -0
  51. package/dist/tools/edit-file.d.ts +9 -0
  52. package/dist/tools/edit-file.js +76 -0
  53. package/dist/tools/glob.d.ts +8 -0
  54. package/dist/tools/glob.js +101 -0
  55. package/dist/tools/grep.d.ts +10 -0
  56. package/dist/tools/grep.js +76 -0
  57. package/dist/tools/index.d.ts +24 -0
  58. package/dist/tools/index.js +47 -0
  59. package/dist/tools/read-files.d.ts +8 -0
  60. package/dist/tools/read-files.js +81 -0
  61. package/dist/tools/write-file.d.ts +8 -0
  62. package/dist/tools/write-file.js +63 -0
  63. package/package.json +59 -0
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Loop — universal agent execution engine
4
+ *
5
+ * Reference: gemini-cli LocalAgentExecutor (agents/local-executor.ts L120-1518)
6
+ * Key adaptation: uses OpenAIClient.chat() instead of GeminiChat,
7
+ * decoupled tool execution via ToolRegistry, simplified timeout.
8
+ *
9
+ * Core loop:
10
+ * 1. Build messages (system prompt + user query)
11
+ * 2. While (!done):
12
+ * a. Check termination (max turns, timeout, abort)
13
+ * b. Call LLM via chatWithTools → get assistant message
14
+ * c. If no tool_calls → ERROR_NO_COMPLETE_TASK (or handle text-only response)
15
+ * d. For each tool_call:
16
+ * - If complete_task → done, extract result
17
+ * - Otherwise → execute via ToolRegistry → append tool response
18
+ * e. Emit events (thought_chunk, tool_call_start/end)
19
+ * 3. Return AgentResult with termination mode and output
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.AgentLoop = void 0;
23
+ const types_1 = require("./types");
24
+ const complete_task_1 = require("./complete-task");
25
+ const index_1 = require("../llm/index");
26
+ /**
27
+ * Universal agent execution engine.
28
+ *
29
+ * Usage:
30
+ * ```typescript
31
+ * const loop = new AgentLoop(llm, toolRegistry)
32
+ * const result = await loop.execute(
33
+ * { name: 'my-agent', systemPrompt: '...', query: 'Do X' },
34
+ * (event) => { }
35
+ */
36
+ const FILE_CONTENT_TRUNCATE = 80;
37
+ function truncateFileContent(data, toolName) {
38
+ if (!toolName)
39
+ return data;
40
+ const result = { ...data };
41
+ // 截断顶层 content(read_files 返回结果等)
42
+ if ((toolName === 'write_file' || toolName === 'edit_file') && typeof data.content === 'string' && data.content.length > FILE_CONTENT_TRUNCATE) {
43
+ result.content = data.content.slice(0, FILE_CONTENT_TRUNCATE) + `... (${data.content.length} chars)`;
44
+ }
45
+ // 截断 args.content(tool_call_start 中 write_file/edit_file 的参数)
46
+ if ((toolName === 'write_file' || toolName === 'edit_file') && data.args && typeof data.args.content === 'string' && data.args.content.length > FILE_CONTENT_TRUNCATE) {
47
+ result.args = { ...data.args };
48
+ result.args.content = data.args.content.slice(0, FILE_CONTENT_TRUNCATE) + `... (${data.args.content.length} chars)`;
49
+ }
50
+ if (toolName === 'read_files' && typeof data.result === 'string' && data.result.length > FILE_CONTENT_TRUNCATE) {
51
+ result.result = data.result.slice(0, FILE_CONTENT_TRUNCATE) + `... (${data.result.length} chars)`;
52
+ }
53
+ return result;
54
+ }
55
+ class AgentLoop {
56
+ llm;
57
+ toolRegistry;
58
+ constructor(llm, toolRegistry) {
59
+ this.llm = llm;
60
+ this.toolRegistry = toolRegistry;
61
+ }
62
+ /**
63
+ * Execute an agent definition with the given event callback and optional abort signal.
64
+ *
65
+ * Reference: gemini-cli LocalAgentExecutor.run() (L537-566) and runInternal() (L568-730)
66
+ */
67
+ async execute(definition, onEvent, signal) {
68
+ const startTime = Date.now();
69
+ const maxTurns = definition.maxTurns ?? types_1.DEFAULT_MAX_TURNS;
70
+ const maxTimeMs = (definition.maxTimeMinutes ?? types_1.DEFAULT_MAX_TIME_MINUTES) * 60 * 1000;
71
+ let turnCounter = 0;
72
+ let terminateReason = types_1.TM_ERROR;
73
+ let finalResult = null;
74
+ // 1. Build initial messages
75
+ const messages = [];
76
+ if (definition.systemPrompt) {
77
+ messages.push({ role: 'system', content: definition.systemPrompt });
78
+ }
79
+ const query = definition.query ?? 'Get Started!';
80
+ messages.push({ role: 'user', content: query });
81
+ // 2. Use a local tool registry copy to avoid mutating the shared one
82
+ const localRegistry = new types_1.ToolRegistry();
83
+ // Copy all tools from the shared registry
84
+ for (const name of this.toolRegistry.getAllToolNames()) {
85
+ const tool = this.toolRegistry.get(name);
86
+ if (tool)
87
+ localRegistry.register(tool);
88
+ }
89
+ // Ensure complete_task is registered (check if caller already registered one)
90
+ if (!localRegistry.has(types_1.COMPLETE_TASK_TOOL_NAME)) {
91
+ localRegistry.register((0, complete_task_1.createCompleteTaskTool)());
92
+ }
93
+ // Determine which tools are available for this agent
94
+ const toolsMap = definition.toolNames
95
+ ? localRegistry.getToolsMapSubset([...definition.toolNames, types_1.COMPLETE_TASK_TOOL_NAME])
96
+ : localRegistry.getToolsMap();
97
+ // 3. Emit start event
98
+ this.emitEvent(onEvent, 'agent_start', definition.name, {
99
+ agentName: definition.name,
100
+ maxTurns,
101
+ maxTimeMinutes: maxTimeMs / 60000,
102
+ toolNames: Object.keys(toolsMap),
103
+ });
104
+ // 4. Main loop (reference: gemini-cli runInternal L667-726)
105
+ try {
106
+ while (true) {
107
+ // 4a. Check termination conditions
108
+ const terminationCheck = this.checkTermination(turnCounter, maxTurns, startTime, maxTimeMs, signal);
109
+ if (terminationCheck) {
110
+ terminateReason = terminationCheck;
111
+ break;
112
+ }
113
+ // 4b. Call LLM
114
+ this.emitEvent(onEvent, 'thought_chunk', definition.name, {
115
+ turn: turnCounter,
116
+ message: `Turn ${turnCounter + 1}/${maxTurns}: Calling model...`,
117
+ });
118
+ const chatResult = await this.llm.chatWithTools(messages, toolsMap, signal);
119
+ // 4c. Process assistant response
120
+ const assistantMessage = chatResult.message;
121
+ messages.push(assistantMessage);
122
+ // 4d. Handle text-only response (no tool_calls)
123
+ if (!assistantMessage.tool_calls?.length) {
124
+ // The LLM responded with text instead of calling any tool.
125
+ // This means it didn't call complete_task → ERROR_NO_COMPLETE_TASK.
126
+ // But if there's meaningful text content, we can treat it as the final result.
127
+ const textContent = typeof assistantMessage.content === 'string'
128
+ ? assistantMessage.content
129
+ : null;
130
+ if (textContent) {
131
+ // The LLM gave a text answer but didn't follow the protocol.
132
+ // Treat it as a soft completion.
133
+ this.emitEvent(onEvent, 'thought_chunk', definition.name, {
134
+ message: 'Model returned text content without calling complete_task. Treating as final answer.',
135
+ });
136
+ finalResult = textContent;
137
+ terminateReason = types_1.TM_GOAL;
138
+ }
139
+ else {
140
+ terminateReason = types_1.TM_ERROR_NO_COMPLETE_TASK;
141
+ }
142
+ break;
143
+ }
144
+ // 4e. Process each tool_call
145
+ let taskCompleted = false;
146
+ for (const toolCall of assistantMessage.tool_calls) {
147
+ const toolName = toolCall.function.name;
148
+ let toolArgs;
149
+ try {
150
+ toolArgs = JSON.parse(toolCall.function.arguments);
151
+ }
152
+ catch {
153
+ // Handle malformed JSON (common with LLM outputs)
154
+ toolArgs = {};
155
+ }
156
+ // Check if this is the termination signal
157
+ if (toolName === types_1.COMPLETE_TASK_TOOL_NAME) {
158
+ finalResult = JSON.stringify(toolArgs);
159
+ taskCompleted = true;
160
+ // Append tool response to messages (for history completeness)
161
+ messages.push({
162
+ role: 'tool',
163
+ tool_call_id: toolCall.id,
164
+ name: types_1.COMPLETE_TASK_TOOL_NAME,
165
+ content: JSON.stringify({ result: finalResult, taskCompleted: true }),
166
+ });
167
+ break;
168
+ }
169
+ // Execute the tool via registry
170
+ this.emitEvent(onEvent, 'tool_call_start', definition.name, {
171
+ tool: toolName,
172
+ args: toolArgs,
173
+ turn: turnCounter,
174
+ }, toolName);
175
+ let toolResult;
176
+ try {
177
+ const toolContext = {
178
+ runtime: globalThis.__runtime ?? {},
179
+ cwd: definition.cwd ?? process.cwd(),
180
+ readOnlyPaths: definition.readOnlyPaths,
181
+ };
182
+ toolResult = await localRegistry.execute(toolName, toolArgs, toolContext);
183
+ }
184
+ catch (error) {
185
+ toolResult = { error: error.message || String(error), isError: true };
186
+ this.emitEvent(onEvent, 'agent_error', definition.name, {
187
+ tool: toolName,
188
+ error: error.message,
189
+ });
190
+ }
191
+ this.emitEvent(onEvent, 'tool_call_end', definition.name, {
192
+ tool: toolName,
193
+ result: typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult),
194
+ turn: turnCounter,
195
+ }, toolName);
196
+ // Append tool response to messages
197
+ messages.push({
198
+ role: 'tool',
199
+ tool_call_id: toolCall.id,
200
+ name: toolName,
201
+ content: typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult),
202
+ });
203
+ }
204
+ if (taskCompleted) {
205
+ terminateReason = types_1.TM_GOAL;
206
+ break;
207
+ }
208
+ turnCounter++;
209
+ }
210
+ }
211
+ catch (error) {
212
+ // Check if it's an abort error
213
+ if (error?.name === 'AbortError' || signal?.aborted) {
214
+ terminateReason = types_1.TM_ABORTED;
215
+ }
216
+ else {
217
+ terminateReason = types_1.TM_ERROR;
218
+ finalResult = null;
219
+ // Enhanced error logging for debugging
220
+ const errorDetail = error instanceof index_1.InvokeError
221
+ ? `[InvokeError type=${error.type} retryable=${error.retryable}] ${error.message}`
222
+ : error?.message || String(error);
223
+ console.error(`[AgentLoop] Agent "${definition.name}" terminated with ERROR: ${errorDetail}`);
224
+ if (error instanceof index_1.InvokeError && error.rawResponse) {
225
+ console.error(`[AgentLoop] InvokeError rawResponse:`, JSON.stringify(error.rawResponse).slice(0, 500));
226
+ }
227
+ this.emitEvent(onEvent, 'agent_error', definition.name, {
228
+ error: errorDetail,
229
+ errorType: error?.type || error?.constructor?.name || 'unknown',
230
+ phase: 'execution',
231
+ turn: turnCounter,
232
+ });
233
+ }
234
+ }
235
+ // 5. Emit end event
236
+ this.emitEvent(onEvent, 'agent_end', definition.name, {
237
+ terminateReason,
238
+ turnCount: turnCounter,
239
+ durationMs: Date.now() - startTime,
240
+ output: finalResult ?? undefined,
241
+ });
242
+ // 6. Return result
243
+ return {
244
+ success: terminateReason === types_1.TM_GOAL,
245
+ output: finalResult ?? undefined,
246
+ terminateReason,
247
+ turnCount: turnCounter,
248
+ durationMs: Date.now() - startTime,
249
+ error: terminateReason !== types_1.TM_GOAL
250
+ ? `Agent terminated: ${terminateReason}${terminateReason === types_1.TM_ERROR_NO_COMPLETE_TASK ? ' (model stopped calling tools without complete_task)' : ''}`
251
+ : undefined,
252
+ };
253
+ }
254
+ /**
255
+ * Check if the agent should terminate.
256
+ * Returns a terminate reason if termination is needed, null otherwise.
257
+ */
258
+ checkTermination(turnCounter, maxTurns, startTime, maxTimeMs, signal) {
259
+ if (turnCounter >= maxTurns) {
260
+ return types_1.TM_MAX_TURNS;
261
+ }
262
+ if (signal?.aborted) {
263
+ return types_1.TM_ABORTED;
264
+ }
265
+ if (Date.now() - startTime >= maxTimeMs) {
266
+ return types_1.TM_TIMEOUT;
267
+ }
268
+ return null;
269
+ }
270
+ /**
271
+ * Emit an event to the callback, if provided.
272
+ */
273
+ emitEvent(onEvent, type, streamId, data, toolName) {
274
+ if (onEvent) {
275
+ const processed = (type === 'tool_call_start' || type === 'tool_call_end')
276
+ ? truncateFileContent(data, toolName)
277
+ : data;
278
+ onEvent({ type, streamId, data: processed });
279
+ }
280
+ }
281
+ }
282
+ exports.AgentLoop = AgentLoop;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * RuntimeEnvironment — runtime environment detection
3
+ *
4
+ * Detects available system binaries (git/codegraph/playwright/node),
5
+ * and provides config paths (env).
6
+ *
7
+ * Tools (core/tools/*) get paths via ToolExecutionContext.runtime,
8
+ * instead of resolving binaries themselves.
9
+ *
10
+ * Design decisions:
11
+ * D02: Layer 1: RuntimeEnvironment provides binary paths
12
+ * Layer 2: Tool execute() gets paths from runtime
13
+ * Layer 3: Skill only ctx.tools.call(), no path awareness
14
+ * D04: Business-specific paths (keveWikiDir/reposBaseDir) removed from core.
15
+ * Agent package can add them in its own runtime extension.
16
+ */
17
+ export interface RuntimeEnvironment {
18
+ /** Git binary path */
19
+ gitBin: string;
20
+ /** Codegraph binary path */
21
+ codegraphBin: string;
22
+ /** Playwright binary path (usually in agent's node_modules/.bin) */
23
+ playwrightBin: string;
24
+ /** Node binary path */
25
+ nodeBin: string;
26
+ /** Package manager (pnpm/npm/yarn) */
27
+ packageManager: string;
28
+ /** Environment variables (tokens, configs) */
29
+ env: Record<string, string>;
30
+ }
31
+ /**
32
+ * Detect the runtime environment.
33
+ *
34
+ * Scans for binaries, loads env vars.
35
+ * Called once at startup (entry.ts) and passed to Pipeline → SkillContext.
36
+ *
37
+ * @param cwd - Starting directory (defaults to process.cwd())
38
+ * @param agentDir - Agent package directory for finding node_modules binaries
39
+ */
40
+ export declare function detectRuntimeEnvironment(cwd?: string, agentDir?: string): RuntimeEnvironment;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * RuntimeEnvironment — runtime environment detection
4
+ *
5
+ * Detects available system binaries (git/codegraph/playwright/node),
6
+ * and provides config paths (env).
7
+ *
8
+ * Tools (core/tools/*) get paths via ToolExecutionContext.runtime,
9
+ * instead of resolving binaries themselves.
10
+ *
11
+ * Design decisions:
12
+ * D02: Layer 1: RuntimeEnvironment provides binary paths
13
+ * Layer 2: Tool execute() gets paths from runtime
14
+ * Layer 3: Skill only ctx.tools.call(), no path awareness
15
+ * D04: Business-specific paths (keveWikiDir/reposBaseDir) removed from core.
16
+ * Agent package can add them in its own runtime extension.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.detectRuntimeEnvironment = detectRuntimeEnvironment;
53
+ const path = __importStar(require("node:path"));
54
+ const fs = __importStar(require("node:fs"));
55
+ const node_child_process_1 = require("node:child_process");
56
+ // ===== Detection =====
57
+ /**
58
+ * Find a binary on the system PATH.
59
+ * Returns the absolute path or the binary name if not found (will fail at exec time).
60
+ */
61
+ function findBinary(name) {
62
+ try {
63
+ return (0, node_child_process_1.execSync)(`which ${name} 2>/dev/null`, { encoding: 'utf-8' }).trim();
64
+ }
65
+ catch {
66
+ return name;
67
+ }
68
+ }
69
+ /**
70
+ * Detect package manager from lockfile
71
+ */
72
+ function detectPackageManager(projectDir) {
73
+ if (fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml')))
74
+ return 'pnpm';
75
+ if (fs.existsSync(path.join(projectDir, 'yarn.lock')))
76
+ return 'yarn';
77
+ if (fs.existsSync(path.join(projectDir, 'package-lock.json')))
78
+ return 'npm';
79
+ return 'pnpm';
80
+ }
81
+ /**
82
+ * Load environment variables from .env file (if present)
83
+ */
84
+ function loadEnvFile(envPath) {
85
+ const env = {};
86
+ if (!fs.existsSync(envPath))
87
+ return env;
88
+ try {
89
+ const content = fs.readFileSync(envPath, 'utf-8');
90
+ for (const line of content.split('\n')) {
91
+ const trimmed = line.trim();
92
+ if (!trimmed || trimmed.startsWith('#'))
93
+ continue;
94
+ const eqIndex = trimmed.indexOf('=');
95
+ if (eqIndex > 0) {
96
+ const key = trimmed.slice(0, eqIndex).trim();
97
+ let value = trimmed.slice(eqIndex + 1).trim();
98
+ if ((value.startsWith('"') && value.endsWith('"')) ||
99
+ (value.startsWith("'") && value.endsWith("'"))) {
100
+ value = value.slice(1, -1);
101
+ }
102
+ env[key] = value;
103
+ }
104
+ }
105
+ }
106
+ catch {
107
+ // Ignore .env read errors
108
+ }
109
+ return env;
110
+ }
111
+ /**
112
+ * Detect the runtime environment.
113
+ *
114
+ * Scans for binaries, loads env vars.
115
+ * Called once at startup (entry.ts) and passed to Pipeline → SkillContext.
116
+ *
117
+ * @param cwd - Starting directory (defaults to process.cwd())
118
+ * @param agentDir - Agent package directory for finding node_modules binaries
119
+ */
120
+ function detectRuntimeEnvironment(cwd, agentDir) {
121
+ const startDir = cwd ?? process.cwd();
122
+ const pkgDir = agentDir ?? findPkgDir(startDir);
123
+ // Find playwright binary: prefer agent's node_modules, fallback to global
124
+ const localPlaywright = path.join(pkgDir, 'node_modules', '.bin', 'playwright');
125
+ const playwrightBin = fs.existsSync(localPlaywright)
126
+ ? localPlaywright
127
+ : findBinary('playwright');
128
+ // Load .env from agent/pkg dir
129
+ const env = {
130
+ ...loadEnvFile(path.join(pkgDir, '.env')),
131
+ };
132
+ // Also inherit relevant process.env
133
+ for (const key of ['GITLAB_TOKEN', 'HOME', 'PATH', 'NODE_ENV']) {
134
+ if (process.env[key])
135
+ env[key] = process.env[key];
136
+ }
137
+ return {
138
+ gitBin: findBinary('git'),
139
+ codegraphBin: findBinary('codegraph'),
140
+ playwrightBin,
141
+ nodeBin: process.execPath,
142
+ packageManager: detectPackageManager(pkgDir),
143
+ env,
144
+ };
145
+ }
146
+ /**
147
+ * Walk up from startDir to find the nearest package directory
148
+ * (contains package.json with node_modules)
149
+ */
150
+ function findPkgDir(startDir) {
151
+ let dir = startDir;
152
+ for (let i = 0; i < 15; i++) {
153
+ const pkgPath = path.join(dir, 'package.json');
154
+ if (fs.existsSync(pkgPath) && fs.existsSync(path.join(dir, 'node_modules'))) {
155
+ return dir;
156
+ }
157
+ const parent = path.dirname(dir);
158
+ if (parent === dir)
159
+ break;
160
+ dir = parent;
161
+ }
162
+ return startDir;
163
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Agent Loop Core Types
3
+ *
4
+ * Reference: gemini-cli LocalAgentExecutor + AgentProtocol
5
+ * Adapted for OpenAI-compatible API (internal proxy at xxx)
6
+ *
7
+ * Key differences from gemini-cli:
8
+ * - LLM calls use OpenAIClient instead of GeminiChat
9
+ * - Tool definitions reuse core/llm/types.ts Tool interface
10
+ * - No Gemini-specific Content/Part types — use OpenAI Message format
11
+ * - Simplified timeout (no DeadlineTimer pause/resume — we don't wait for user confirmation)
12
+ */
13
+ import type { LLMClient, Tool } from '../llm/types';
14
+ import type { RuntimeEnvironment } from './runtime';
15
+ /** Default maximum number of conversational turns for an agent. */
16
+ export declare const DEFAULT_MAX_TURNS = 30;
17
+ /** Default maximum execution time for an agent in minutes. */
18
+ export declare const DEFAULT_MAX_TIME_MINUTES = 10;
19
+ /** Tool name for signaling task completion (reference: gemini-cli CompleteTaskTool) */
20
+ export declare const COMPLETE_TASK_TOOL_NAME = "complete_task";
21
+ /**
22
+ * Definition of an agent to execute via the loop.
23
+ * Reference: gemini-cli LocalAgentDefinition (agents/types.ts L214-285)
24
+ *
25
+ * Simplified from gemini-cli: removed kind, promptConfig, modelConfig, runConfig,
26
+ * workspaceDirectories, memoryInboxAccess, mcpServers, processOutput, onBeforeTurn.
27
+ * These features can be added incrementally as needed.
28
+ */
29
+ export interface AgentDefinition {
30
+ /** Unique identifier for the agent. */
31
+ name: string;
32
+ /** Human-readable description. */
33
+ description: string;
34
+ /** System prompt injected as the first message. Can include SKILL.md content. */
35
+ systemPrompt?: string;
36
+ /** The initial user query/message. Defaults to 'Get Started!' if not provided. */
37
+ query?: string;
38
+ /** Maximum number of conversational turns. Defaults to DEFAULT_MAX_TURNS (30). */
39
+ maxTurns?: number;
40
+ /** Maximum execution time in minutes. Defaults to DEFAULT_MAX_TIME_MINUTES (10). */
41
+ maxTimeMinutes?: number;
42
+ /** Tool names to restrict to. If omitted, all registered tools are available. */
43
+ toolNames?: string[];
44
+ /** Paths that are read-only — bash tool will block write operations targeting these directories */
45
+ readOnlyPaths?: string[];
46
+ /** Working directory for tool execution. Defaults to process.cwd() */
47
+ cwd?: string;
48
+ }
49
+ /**
50
+ * Execution context for the agent loop.
51
+ * Provides the LLM client, tool registry, and config needed to run an agent.
52
+ */
53
+ export interface AgentLoopContext {
54
+ /** LLM client for calling the model. Uses core/llm/OpenAIClient. */
55
+ llmClient: LLMClient;
56
+ /** Tool registry for executing tool calls from the model. */
57
+ toolRegistry: ToolRegistry;
58
+ /** Application configuration. */
59
+ config: Record<string, unknown>;
60
+ }
61
+ /**
62
+ * Callback for receiving agent events during execution.
63
+ * Events are emitted at key points: start, tool calls, thoughts, end, errors.
64
+ */
65
+ export type AgentEventCallback = (event: AgentEvent) => void;
66
+ /**
67
+ * Events emitted during agent execution.
68
+ * Reference: gemini-cli AgentEvents (agent/types.ts L104-131) and SubagentActivityEvent (agents/types.ts L84-89)
69
+ *
70
+ * Simplified from gemini-cli: merged multiple event interfaces into one,
71
+ * removed display-specific fields (DisplayContent, ToolDisplayFormat).
72
+ */
73
+ export interface AgentEvent {
74
+ type: 'agent_start' | 'tool_call_start' | 'tool_call_end' | 'thought_chunk' | 'agent_end' | 'agent_error';
75
+ /** Stream identifier (typically the agent name). */
76
+ streamId: string;
77
+ /** Event-specific data. */
78
+ data: Record<string, unknown>;
79
+ }
80
+ /**
81
+ * Describes how an agent's execution ended.
82
+ * Reference: gemini-cli AgentTerminateMode (agents/types.ts L24-31)
83
+ */
84
+ export declare const TM_GOAL = "GOAL";
85
+ export declare const TM_TIMEOUT = "TIMEOUT";
86
+ export declare const TM_MAX_TURNS = "MAX_TURNS";
87
+ export declare const TM_ABORTED = "ABORTED";
88
+ export declare const TM_ERROR = "ERROR";
89
+ export declare const TM_ERROR_NO_COMPLETE_TASK = "ERROR_NO_COMPLETE_TASK";
90
+ export type AgentTerminateMode = typeof TM_GOAL | typeof TM_TIMEOUT | typeof TM_MAX_TURNS | typeof TM_ABORTED | typeof TM_ERROR | typeof TM_ERROR_NO_COMPLETE_TASK;
91
+ /**
92
+ * The final result of an agent's execution.
93
+ * Reference: gemini-cli OutputObject (agents/types.ts L36-41)
94
+ */
95
+ export interface AgentResult {
96
+ /** Whether the agent completed successfully (GOAL termination). */
97
+ success: boolean;
98
+ /** The final output string from complete_task, if available. */
99
+ output?: string;
100
+ /** How the agent's execution ended. */
101
+ terminateReason: AgentTerminateMode;
102
+ /** Number of conversational turns completed. */
103
+ turnCount: number;
104
+ /** Total execution time in milliseconds. */
105
+ durationMs: number;
106
+ /** Error message if the agent failed. */
107
+ error?: string;
108
+ }
109
+ export interface ToolExecutionContext {
110
+ /** Runtime environment (binary paths, config dirs, env vars) */
111
+ runtime: RuntimeEnvironment;
112
+ /** Working directory for command execution */
113
+ cwd: string;
114
+ /** Timeout in milliseconds */
115
+ timeout?: number;
116
+ /** 🆕 Paths that are read-only — bash tool will block write operations to these directories */
117
+ readOnlyPaths?: string[];
118
+ }
119
+ /**
120
+ * A tool definition for the agent loop's registry.
121
+ * Compatible with core/llm/types.ts Tool interface.
122
+ */
123
+ export interface ToolDefinition {
124
+ /** Tool name (used as the key in OpenAI function_call). */
125
+ name: string;
126
+ /** Human-readable description sent to the LLM. */
127
+ description: string;
128
+ /** Zod schema for input validation and OpenAI parameter generation. */
129
+ inputSchema: z.ZodType<any>;
130
+ /** Execute the tool with validated arguments. */
131
+ execute: (args: any, context?: ToolExecutionContext) => Promise<any>;
132
+ /** Custom JSON Schema for the LLM (overrides zod conversion). */
133
+ customJsonSchema?: Record<string, any>;
134
+ }
135
+ import type * as z from 'zod/v4';
136
+ /**
137
+ * Tool registry for managing and executing tools during agent execution.
138
+ * Reference: gemini-cli ToolRegistry (tools/tool-registry.ts)
139
+ *
140
+ * Simplified: no message bus, no confirmation policy, no sort, no MCP integration.
141
+ * These can be added incrementally.
142
+ */
143
+ export declare class ToolRegistry {
144
+ private tools;
145
+ /** Register a tool definition. */
146
+ register(tool: ToolDefinition): void;
147
+ /** Unregister a tool by name. */
148
+ unregister(name: string): void;
149
+ /** Get all registered tool names. */
150
+ getAllToolNames(): string[];
151
+ /** Convert registered tools to the core/llm/types.ts Tool map format for LLM calls. */
152
+ getToolsMap(): Record<string, Tool>;
153
+ /** Get a subset of tools by name (for agent's toolNames restriction). */
154
+ getToolsMapSubset(names: string[]): Record<string, Tool>;
155
+ /** Execute a registered tool by name with the given arguments and optional context. */
156
+ execute(name: string, args: any, context?: ToolExecutionContext): Promise<any>;
157
+ /** Check if a tool is registered. */
158
+ has(name: string): boolean;
159
+ /** Get a tool definition by name. */
160
+ get(name: string): ToolDefinition | undefined;
161
+ }