jinzd-ai-cli 0.3.6 → 0.4.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.
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ToolRegistry,
4
+ getDangerLevel,
5
+ googleSearchContext,
6
+ truncateOutput
7
+ } from "./chunk-V74SAFQD.js";
8
+ import {
9
+ SUBAGENT_ALLOWED_TOOLS
10
+ } from "./chunk-TCGVS7O5.js";
11
+
12
+ // src/hub/task-orchestrator.ts
13
+ import { createInterface } from "readline";
14
+ import chalk from "chalk";
15
+
16
+ // src/hub/task-executor.ts
17
+ async function executeTask(options) {
18
+ const {
19
+ task,
20
+ role,
21
+ provider,
22
+ model,
23
+ maxRounds,
24
+ context,
25
+ configManager,
26
+ onToolCall,
27
+ onToolResult,
28
+ onRound
29
+ } = options;
30
+ const registry = new ToolRegistry();
31
+ for (const tool of registry.listAll()) {
32
+ if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
33
+ registry.unregister(tool.definition.name);
34
+ }
35
+ }
36
+ const toolDefs = registry.getDefinitions();
37
+ const contextSection = context ? `
38
+
39
+ ## Reference Documents
40
+ ${context}` : "";
41
+ const systemPrompt = `# Task Execution \u2014 Role: ${role.name}
42
+
43
+ ${role.persona}
44
+
45
+ ## Your Task
46
+ ${task}
47
+
48
+ ## Rules
49
+ 1. Focus exclusively on completing the task described above.
50
+ 2. Use tools to read, create, and modify files as needed.
51
+ 3. When done, provide a clear summary: what was done, which files were created/modified, and the result.
52
+ 4. Be efficient \u2014 minimize unnecessary tool calls.
53
+ 5. Destructive operations (rm -rf, etc.) will be automatically blocked.
54
+ 6. You cannot interact with users \u2014 work independently based on the task description.
55
+ 7. Use the same language as the task description.${contextSection}`;
56
+ const messages = [
57
+ { role: "user", content: `Please execute the following task:
58
+
59
+ ${task}`, timestamp: /* @__PURE__ */ new Date() }
60
+ ];
61
+ const extraMessages = [];
62
+ const totalUsage = { inputTokens: 0, outputTokens: 0 };
63
+ let finalContent = "";
64
+ let roundsUsed = 0;
65
+ const executor = new TaskToolExecutor(registry, onToolCall, onToolResult);
66
+ try {
67
+ for (let round = 0; round < maxRounds; round++) {
68
+ roundsUsed = round + 1;
69
+ executor.setRoundInfo(round + 1, maxRounds);
70
+ onRound?.(round + 1, maxRounds);
71
+ if (configManager) {
72
+ googleSearchContext.configManager = configManager;
73
+ }
74
+ const result = await provider.chatWithTools(
75
+ {
76
+ messages,
77
+ model,
78
+ systemPrompt,
79
+ stream: false,
80
+ temperature: 0.3,
81
+ maxTokens: 8192,
82
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
83
+ },
84
+ toolDefs
85
+ );
86
+ if (result.usage) {
87
+ totalUsage.inputTokens += result.usage.inputTokens;
88
+ totalUsage.outputTokens += result.usage.outputTokens;
89
+ }
90
+ if ("content" in result) {
91
+ finalContent = result.content;
92
+ break;
93
+ }
94
+ const toolResults = await executor.executeAll(result.toolCalls);
95
+ const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
96
+ const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
97
+ extraMessages.push(...newMsgs);
98
+ }
99
+ if (!finalContent) {
100
+ finalContent = `(Task agent reached maximum rounds (${maxRounds}) without final response)`;
101
+ }
102
+ } catch (err) {
103
+ const errMsg = err instanceof Error ? err.message : String(err);
104
+ finalContent = `(Task execution error: ${errMsg})`;
105
+ }
106
+ return {
107
+ content: finalContent,
108
+ success: !finalContent.startsWith("(Task"),
109
+ roundsUsed,
110
+ usage: totalUsage
111
+ };
112
+ }
113
+ var TaskToolExecutor = class {
114
+ constructor(registry, onToolCall, onToolResult) {
115
+ this.registry = registry;
116
+ this.onToolCall = onToolCall;
117
+ this.onToolResult = onToolResult;
118
+ }
119
+ round = 0;
120
+ totalRounds = 0;
121
+ setRoundInfo(current, total) {
122
+ this.round = current;
123
+ this.totalRounds = total;
124
+ }
125
+ async execute(call) {
126
+ const tool = this.registry.get(call.name);
127
+ if (!tool) {
128
+ return { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
129
+ }
130
+ const dangerLevel = getDangerLevel(call.name, call.arguments);
131
+ if (dangerLevel === "destructive") {
132
+ this.onToolResult?.(call.name, "BLOCKED: destructive operation", true);
133
+ return {
134
+ callId: call.id,
135
+ content: "Destructive operations are not allowed in task mode.",
136
+ isError: true
137
+ };
138
+ }
139
+ this.onToolCall?.(call, dangerLevel);
140
+ try {
141
+ const rawContent = await tool.execute(call.arguments);
142
+ const content = truncateOutput(rawContent, call.name);
143
+ this.onToolResult?.(call.name, rawContent, false);
144
+ return { callId: call.id, content, isError: false };
145
+ } catch (err) {
146
+ const message = err instanceof Error ? err.message : String(err);
147
+ this.onToolResult?.(call.name, message, true);
148
+ return { callId: call.id, content: message, isError: true };
149
+ }
150
+ }
151
+ async executeAll(calls) {
152
+ const results = [];
153
+ for (const call of calls) {
154
+ results.push(await this.execute(call));
155
+ }
156
+ return results;
157
+ }
158
+ };
159
+
160
+ // src/hub/task-orchestrator.ts
161
+ var TaskOrchestrator = class {
162
+ constructor(config, providers, configManager) {
163
+ this.config = config;
164
+ this.providers = providers;
165
+ this.configManager = configManager;
166
+ }
167
+ aborted = false;
168
+ abort() {
169
+ this.aborted = true;
170
+ }
171
+ /**
172
+ * Run the full task mode workflow.
173
+ */
174
+ async run(goal) {
175
+ const provider = this.providers.get(this.config.defaultProvider);
176
+ if (!provider) {
177
+ console.error(chalk.red(` \u2717 Provider "${this.config.defaultProvider}" not available.`));
178
+ return;
179
+ }
180
+ this.renderPhaseHeader("PLAN", "Architect is decomposing the goal into tasks...");
181
+ const plan = await this.generatePlan(goal, provider);
182
+ if (!plan || plan.tasks.length === 0) {
183
+ console.log(chalk.red(" \u2717 Failed to generate a task plan."));
184
+ return;
185
+ }
186
+ this.renderPlan(plan);
187
+ this.renderPhaseHeader("APPROVE", "Review the task plan");
188
+ const approved = await this.waitForApproval(plan);
189
+ if (!approved) {
190
+ console.log(chalk.yellow(" \u26A0 Task plan rejected. Exiting."));
191
+ return;
192
+ }
193
+ this.renderPhaseHeader("EXECUTE", "Agents are executing tasks...");
194
+ await this.executePlan(plan, provider);
195
+ this.renderPhaseHeader("REVIEW", "Generating project summary...");
196
+ await this.generateReview(plan, provider);
197
+ }
198
+ // ── Phase 1: Plan Generation ──────────────────────────────────────
199
+ async generatePlan(goal, provider) {
200
+ const roles = this.config.roles;
201
+ const roleList = roles.map((r) => `- ${r.id} (${r.name}): ${r.persona.slice(0, 100)}...`).join("\n");
202
+ const contextSection = this.config.context ? `
203
+
204
+ ## Reference Documents
205
+ ${this.config.context}` : "";
206
+ const systemPrompt = `You are a senior project planner. Your job is to decompose a high-level goal into concrete, executable tasks and assign each to the most appropriate team member.
207
+
208
+ ## Available Team Members
209
+ ${roleList}
210
+
211
+ ## Rules
212
+ 1. Break the goal into 5-20 concrete tasks (not too granular, not too broad)
213
+ 2. Each task should be completable by a single person using code tools (bash, file read/write, etc.)
214
+ 3. Assign each task to the most appropriate role based on their expertise
215
+ 4. Specify dependencies (which tasks must complete before this one can start)
216
+ 5. Order tasks logically \u2014 foundational work first
217
+ 6. Use the same language as the goal${contextSection}
218
+
219
+ ## Output Format
220
+ You MUST output ONLY a JSON array (no markdown fences, no explanation before/after). Each element:
221
+ {"id": 1, "description": "task description", "assignee": "role_id", "dependencies": []}
222
+
223
+ Example:
224
+ [
225
+ {"id": 1, "description": "Initialize project with package.json and TypeScript config", "assignee": "backend", "dependencies": []},
226
+ {"id": 2, "description": "Design database schema for users and courses", "assignee": "dba", "dependencies": []},
227
+ {"id": 3, "description": "Implement user authentication API", "assignee": "backend", "dependencies": [1, 2]}
228
+ ]`;
229
+ const messages = [
230
+ { role: "user", content: `Goal: ${goal}`, timestamp: /* @__PURE__ */ new Date() }
231
+ ];
232
+ try {
233
+ const response = await provider.chat({
234
+ messages,
235
+ model: this.config.defaultModel,
236
+ systemPrompt,
237
+ stream: false,
238
+ temperature: 0.3,
239
+ maxTokens: 8192
240
+ });
241
+ let jsonStr = response.content.trim();
242
+ const fenceMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
243
+ if (fenceMatch) {
244
+ jsonStr = fenceMatch[1].trim();
245
+ }
246
+ const parsed = JSON.parse(jsonStr);
247
+ if (!Array.isArray(parsed) || parsed.length === 0) {
248
+ throw new Error("Empty task list");
249
+ }
250
+ const roleMap = new Map(roles.map((r) => [r.id, r.name]));
251
+ const tasks = parsed.map((t, i) => ({
252
+ id: t.id ?? i + 1,
253
+ description: t.description,
254
+ assignee: t.assignee,
255
+ assigneeName: roleMap.get(t.assignee) ?? t.assignee,
256
+ dependencies: t.dependencies ?? [],
257
+ status: "pending"
258
+ }));
259
+ return { goal, tasks };
260
+ } catch (err) {
261
+ console.error(chalk.red(` \u2717 Plan generation failed: ${err.message}`));
262
+ return null;
263
+ }
264
+ }
265
+ // ── Phase 2: Human Approval ───────────────────────────────────────
266
+ async waitForApproval(plan) {
267
+ console.log();
268
+ console.log(chalk.bold(" Approve this plan?"));
269
+ console.log(chalk.dim(" y = approve and start execution"));
270
+ console.log(chalk.dim(" n = reject and exit"));
271
+ console.log(chalk.dim(' e = edit (remove tasks by number, e.g. "e 3,5,7")'));
272
+ console.log();
273
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
274
+ try {
275
+ while (true) {
276
+ const answer = await new Promise((resolve) => {
277
+ rl.question(chalk.green(" > "), resolve);
278
+ });
279
+ const trimmed = answer.trim().toLowerCase();
280
+ if (trimmed === "y" || trimmed === "yes") {
281
+ return true;
282
+ }
283
+ if (trimmed === "n" || trimmed === "no") {
284
+ return false;
285
+ }
286
+ if (trimmed.startsWith("e ") || trimmed.startsWith("e,")) {
287
+ const idsStr = trimmed.slice(1).trim().replace(/^,/, "");
288
+ const ids = idsStr.split(/[,\s]+/).map(Number).filter((n) => !isNaN(n));
289
+ const removeSet = new Set(ids);
290
+ plan.tasks = plan.tasks.filter((t) => !removeSet.has(t.id));
291
+ for (const t of plan.tasks) {
292
+ t.dependencies = t.dependencies.filter((d) => !removeSet.has(d));
293
+ }
294
+ console.log(chalk.dim(` Removed tasks: ${ids.join(", ")}`));
295
+ console.log();
296
+ this.renderPlan(plan);
297
+ console.log(chalk.dim(" y = approve / n = reject / e <ids> = edit more"));
298
+ continue;
299
+ }
300
+ console.log(chalk.dim(" Please enter y, n, or e <task_ids>"));
301
+ }
302
+ } finally {
303
+ rl.close();
304
+ }
305
+ }
306
+ // ── Phase 3: Task Execution ───────────────────────────────────────
307
+ async executePlan(plan, provider) {
308
+ const maxRoundsPerTask = this.config.maxToolRoundsPerTurn ?? 15;
309
+ const roleMap = new Map(this.config.roles.map((r) => [r.id, r]));
310
+ const completedIds = /* @__PURE__ */ new Set();
311
+ while (!this.aborted) {
312
+ const nextTask = plan.tasks.find(
313
+ (t) => t.status === "pending" && t.dependencies.every((d) => completedIds.has(d))
314
+ );
315
+ if (!nextTask) {
316
+ const pending = plan.tasks.filter((t) => t.status === "pending");
317
+ if (pending.length === 0) break;
318
+ console.log(chalk.red(` \u2717 Cannot proceed: ${pending.length} task(s) have unmet dependencies.`));
319
+ for (const t of pending) {
320
+ const unmet = t.dependencies.filter((d) => !completedIds.has(d));
321
+ console.log(chalk.dim(` Task #${t.id}: waiting on [${unmet.join(", ")}]`));
322
+ }
323
+ break;
324
+ }
325
+ nextTask.status = "running";
326
+ const role = roleMap.get(nextTask.assignee) ?? {
327
+ id: nextTask.assignee,
328
+ name: nextTask.assigneeName,
329
+ persona: `You are ${nextTask.assigneeName}, an expert in your domain.`
330
+ };
331
+ this.renderTaskStart(nextTask);
332
+ const result = await executeTask({
333
+ task: nextTask.description,
334
+ role,
335
+ provider,
336
+ model: this.config.defaultModel,
337
+ maxRounds: maxRoundsPerTask,
338
+ context: this.config.context,
339
+ configManager: this.configManager,
340
+ onToolCall: (call, danger) => {
341
+ const icon = danger === "write" ? chalk.yellow("\u270E") : chalk.cyan("\u2699");
342
+ console.log(chalk.dim(` ${icon} ${call.name}`));
343
+ },
344
+ onRound: (round, max) => {
345
+ if (round === 1 || round % 5 === 0) {
346
+ process.stdout.write(chalk.dim(` [round ${round}/${max}]\r`));
347
+ }
348
+ }
349
+ });
350
+ nextTask.status = result.success ? "done" : "failed";
351
+ nextTask.result = result.content;
352
+ nextTask.roundsUsed = result.roundsUsed;
353
+ nextTask.usage = result.usage;
354
+ if (result.success) {
355
+ completedIds.add(nextTask.id);
356
+ }
357
+ this.renderTaskEnd(nextTask, result);
358
+ if (this.aborted) {
359
+ console.log(chalk.yellow("\n \u26A0 Execution interrupted by user."));
360
+ break;
361
+ }
362
+ }
363
+ this.renderExecutionSummary(plan);
364
+ }
365
+ // ── Phase 4: Review ───────────────────────────────────────────────
366
+ async generateReview(plan, provider) {
367
+ const doneTasks = plan.tasks.filter((t) => t.status === "done");
368
+ const failedTasks = plan.tasks.filter((t) => t.status === "failed");
369
+ const pendingTasks = plan.tasks.filter((t) => t.status === "pending");
370
+ if (doneTasks.length === 0) {
371
+ console.log(chalk.yellow(" No tasks were completed \u2014 skipping review."));
372
+ return;
373
+ }
374
+ const transcript = plan.tasks.map((t) => {
375
+ const status = t.status === "done" ? "\u2713" : t.status === "failed" ? "\u2717" : "\u25CB";
376
+ const result = t.result ? `
377
+ Result: ${t.result.slice(0, 500)}` : "";
378
+ return `[${status}] #${t.id} ${t.description} (${t.assigneeName})${result}`;
379
+ }).join("\n\n");
380
+ const messages = [
381
+ {
382
+ role: "user",
383
+ content: `Please provide a structured review of the project execution.
384
+
385
+ ## Original Goal
386
+ ${plan.goal}
387
+
388
+ ## Task Execution Results
389
+ ${transcript}
390
+
391
+ ## Statistics
392
+ - Completed: ${doneTasks.length}/${plan.tasks.length}
393
+ - Failed: ${failedTasks.length}
394
+ - Pending: ${pendingTasks.length}
395
+
396
+ Please summarize:
397
+ 1. What was accomplished
398
+ 2. Key files created/modified
399
+ 3. Issues encountered
400
+ 4. Recommended next steps
401
+
402
+ Use the same language as the original goal.`,
403
+ timestamp: /* @__PURE__ */ new Date()
404
+ }
405
+ ];
406
+ try {
407
+ const response = await provider.chat({
408
+ messages,
409
+ model: this.config.defaultModel,
410
+ stream: false,
411
+ temperature: 0.3,
412
+ maxTokens: 4096
413
+ });
414
+ console.log();
415
+ console.log(chalk.bold.white(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
416
+ console.log(chalk.bold.white(" \u2551") + chalk.bold.green(" \u{1F4CB} Project Review ") + chalk.bold.white("\u2551"));
417
+ console.log(chalk.bold.white(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
418
+ console.log();
419
+ for (const line of response.content.split("\n")) {
420
+ console.log(" " + line);
421
+ }
422
+ console.log();
423
+ } catch (err) {
424
+ console.log(chalk.red(` \u2717 Review generation failed: ${err.message}`));
425
+ }
426
+ }
427
+ // ── Rendering ─────────────────────────────────────────────────────
428
+ renderPhaseHeader(phase, description) {
429
+ console.log();
430
+ console.log(chalk.bold.cyan(` \u2501\u2501\u2501\u2501 Phase: ${phase} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`));
431
+ console.log(chalk.dim(` ${description}`));
432
+ console.log();
433
+ }
434
+ renderPlan(plan) {
435
+ console.log(chalk.bold(` \u{1F4CB} Task Plan (${plan.tasks.length} tasks):`));
436
+ console.log();
437
+ for (const task of plan.tasks) {
438
+ const deps = task.dependencies.length > 0 ? chalk.dim(` [after: ${task.dependencies.join(", ")}]`) : "";
439
+ const roleColor = this.getRoleColor(task.assignee);
440
+ console.log(
441
+ ` ${chalk.bold(`#${task.id}`)} ${task.description}`
442
+ );
443
+ console.log(
444
+ ` ${roleColor(`\u2192 ${task.assigneeName}`)}${deps}`
445
+ );
446
+ }
447
+ console.log();
448
+ }
449
+ renderTaskStart(task) {
450
+ const roleColor = this.getRoleColor(task.assignee);
451
+ console.log();
452
+ console.log(chalk.bold(` \u250C\u2500\u2500 Task #${task.id}: ${task.description}`));
453
+ console.log(chalk.dim(` \u2502 Assigned to: `) + roleColor(task.assigneeName));
454
+ }
455
+ renderTaskEnd(task, result) {
456
+ if (task.status === "done") {
457
+ console.log(chalk.green(` \u2502 \u2713 Completed (${result.roundsUsed} rounds, ${result.usage.inputTokens + result.usage.outputTokens} tokens)`));
458
+ } else {
459
+ console.log(chalk.red(` \u2502 \u2717 Failed (${result.roundsUsed} rounds)`));
460
+ }
461
+ const lines = result.content.split("\n").slice(0, 5);
462
+ for (const line of lines) {
463
+ console.log(chalk.dim(` \u2502 ${line}`));
464
+ }
465
+ if (result.content.split("\n").length > 5) {
466
+ console.log(chalk.dim(` \u2502 ... (${result.content.split("\n").length - 5} more lines)`));
467
+ }
468
+ console.log(chalk.bold(` \u2514${"\u2500".repeat(60)}`));
469
+ }
470
+ renderExecutionSummary(plan) {
471
+ const done = plan.tasks.filter((t) => t.status === "done").length;
472
+ const failed = plan.tasks.filter((t) => t.status === "failed").length;
473
+ const pending = plan.tasks.filter((t) => t.status === "pending").length;
474
+ const totalTokens = plan.tasks.reduce((sum, t) => {
475
+ return sum + (t.usage ? t.usage.inputTokens + t.usage.outputTokens : 0);
476
+ }, 0);
477
+ console.log();
478
+ console.log(chalk.bold(" \u2500\u2500 Execution Summary \u2500\u2500"));
479
+ console.log(` ${chalk.green("\u2713")} Completed: ${done}`);
480
+ if (failed > 0) console.log(` ${chalk.red("\u2717")} Failed: ${failed}`);
481
+ if (pending > 0) console.log(` ${chalk.dim("\u25CB")} Pending: ${pending}`);
482
+ console.log(` ${chalk.dim("\u2299")} Total tokens: ${totalTokens.toLocaleString()}`);
483
+ console.log();
484
+ }
485
+ getRoleColor(roleId) {
486
+ const colors = {
487
+ architect: chalk.cyan,
488
+ dba: chalk.yellow,
489
+ frontend: chalk.green,
490
+ backend: chalk.blue,
491
+ security: chalk.red,
492
+ tester: chalk.magenta,
493
+ human: chalk.white
494
+ };
495
+ return colors[roleId] ?? chalk.white;
496
+ }
497
+ };
498
+ export {
499
+ TaskOrchestrator
500
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.3.6",
3
+ "version": "0.4.1",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -59,6 +59,7 @@
59
59
  "dist/hub-*.js",
60
60
  "dist/hub-server-*.js",
61
61
  "dist/agent-client-*.js",
62
+ "dist/task-orchestrator-*.js",
62
63
  "dist/web/",
63
64
  "README.md"
64
65
  ],