jinzd-ai-cli 0.4.12 → 0.4.14
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 +2 -3
- package/README.zh-CN.md +2 -3
- package/dist/{chunk-Z5IRVRGL.js → chunk-244SVJXW.js} +1 -1
- package/dist/{chunk-3HJSWTKO.js → chunk-QW2UGW6D.js} +9 -1
- package/dist/{chunk-JPCRXQ2E.js → chunk-ROMSAKP6.js} +12 -3
- package/dist/{chunk-A3S7PUMM.js → chunk-WIWNSN7U.js} +23 -4
- package/dist/{hub-J5ZOVM4Q.js → hub-QZXQ6JYS.js} +1 -1
- package/dist/index.js +6 -6
- package/dist/{run-tests-4WPBBNWG.js → run-tests-QDMBYWZA.js} +1 -1
- package/dist/{run-tests-EUOAU3RZ.js → run-tests-YU52BWHE.js} +1 -1
- package/dist/{server-PWWT4P52.js → server-KVHJCPUO.js} +327 -20
- package/dist/{task-orchestrator-A5X2GUSH.js → task-orchestrator-EGJZA6Y4.js} +33 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
- **PWA Support** — Install Web UI as a desktop/mobile app, accessible over LAN
|
|
35
35
|
- **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected
|
|
36
36
|
- **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
|
|
37
|
-
- **
|
|
37
|
+
- **37 REPL Commands** — Session management, checkpointing, code review, scaffolding, and more
|
|
38
38
|
- **GitHub Actions CI/CD** — Automated testing on Node 20/22 + npm publish on release tags
|
|
39
39
|
- **Cross-Platform** — Windows, macOS, Linux
|
|
40
40
|
|
|
@@ -172,7 +172,7 @@ AI autonomously invokes these 16 tools during conversations:
|
|
|
172
172
|
|
|
173
173
|
**Multi-line input**: Use `\` at end of line for continuation, or paste multi-line content directly (auto-detected and merged).
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Type `/help` in the REPL to see all 37 commands.
|
|
176
176
|
|
|
177
177
|
## CLI Parameters
|
|
178
178
|
|
|
@@ -356,7 +356,6 @@ npm run test:watch # Watch mode
|
|
|
356
356
|
|
|
357
357
|
## Documentation
|
|
358
358
|
|
|
359
|
-
- [Full Usage Guide](USAGE.md) — Comprehensive reference for all features
|
|
360
359
|
- [Chinese README](README.zh-CN.md) — 中文说明文档
|
|
361
360
|
|
|
362
361
|
## License
|
package/README.zh-CN.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
- **PWA 支持** — Web UI 可安装为桌面/移动应用,支持局域网访问
|
|
27
27
|
- **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入
|
|
28
28
|
- **无头模式** — `aicli -p "提示词"` 用于 CI/CD 管道和脚本
|
|
29
|
-
- **
|
|
29
|
+
- **37 个 REPL 命令** — 会话管理、检查点、代码审查、脚手架等
|
|
30
30
|
- **GitHub Actions CI/CD** — Node 20/22 自动测试 + Release tag 自动发布 npm
|
|
31
31
|
- **跨平台** — Windows、macOS、Linux
|
|
32
32
|
|
|
@@ -164,7 +164,7 @@ AI 在对话中可自主调用 16 个工具:
|
|
|
164
164
|
|
|
165
165
|
**多行输入**:行末加 `\` 续行,或直接粘贴多行内容(自动检测合并)。
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
在 REPL 中输入 `/help` 查看全部 37 个命令。
|
|
168
168
|
|
|
169
169
|
## CLI 参数
|
|
170
170
|
|
|
@@ -369,7 +369,6 @@ npm run test:watch # 监听模式
|
|
|
369
369
|
|
|
370
370
|
## 文档
|
|
371
371
|
|
|
372
|
-
- [完整使用说明](USAGE.md) — 所有功能的详细参考
|
|
373
372
|
- [English README](README.md) — English documentation
|
|
374
373
|
|
|
375
374
|
## License
|
|
@@ -6,13 +6,14 @@ import { platform } from "os";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/core/constants.ts
|
|
9
|
-
var VERSION = "0.4.
|
|
9
|
+
var VERSION = "0.4.14";
|
|
10
10
|
var APP_NAME = "ai-cli";
|
|
11
11
|
var CONFIG_DIR_NAME = ".aicli";
|
|
12
12
|
var CONFIG_FILE_NAME = "config.json";
|
|
13
13
|
var HISTORY_DIR_NAME = "history";
|
|
14
14
|
var PLUGINS_DIR_NAME = "plugins";
|
|
15
15
|
var SKILLS_DIR_NAME = "skills";
|
|
16
|
+
var CUSTOM_COMMANDS_DIR_NAME = "commands";
|
|
16
17
|
var CONTEXT_FILE_CANDIDATES = ["AICLI.md", "CLAUDE.md"];
|
|
17
18
|
var MEMORY_FILE_NAME = "memory.md";
|
|
18
19
|
var MEMORY_MAX_CHARS = 1e4;
|
|
@@ -84,6 +85,9 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
|
|
|
84
85
|
- Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
|
|
85
86
|
- Project context files (CLAUDE.md, AICLI.md) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
|
|
86
87
|
- If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.`;
|
|
88
|
+
var AUTHOR = "Jin Zhengdong";
|
|
89
|
+
var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
|
|
90
|
+
var DESCRIPTION = "Cross-platform REPL-style AI conversation tool with multi-provider and agentic tool calling support";
|
|
87
91
|
|
|
88
92
|
// src/tools/builtin/run-tests.ts
|
|
89
93
|
var IS_WINDOWS = platform() === "win32";
|
|
@@ -447,6 +451,7 @@ export {
|
|
|
447
451
|
HISTORY_DIR_NAME,
|
|
448
452
|
PLUGINS_DIR_NAME,
|
|
449
453
|
SKILLS_DIR_NAME,
|
|
454
|
+
CUSTOM_COMMANDS_DIR_NAME,
|
|
450
455
|
CONTEXT_FILE_CANDIDATES,
|
|
451
456
|
MEMORY_FILE_NAME,
|
|
452
457
|
MEMORY_MAX_CHARS,
|
|
@@ -463,6 +468,9 @@ export {
|
|
|
463
468
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
464
469
|
SUBAGENT_ALLOWED_TOOLS,
|
|
465
470
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
471
|
+
AUTHOR,
|
|
472
|
+
AUTHOR_EMAIL,
|
|
473
|
+
DESCRIPTION,
|
|
466
474
|
executeTests,
|
|
467
475
|
runTestsTool
|
|
468
476
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
EnvLoader,
|
|
4
4
|
schemaToJsonSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WIWNSN7U.js";
|
|
6
6
|
import {
|
|
7
7
|
APP_NAME,
|
|
8
8
|
CONFIG_DIR_NAME,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
MCP_TOOL_PREFIX,
|
|
16
16
|
PLUGINS_DIR_NAME,
|
|
17
17
|
VERSION
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-244SVJXW.js";
|
|
19
19
|
|
|
20
20
|
// src/config/config-manager.ts
|
|
21
21
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -277,7 +277,8 @@ ${err}`
|
|
|
277
277
|
if (rawValue === "true") value = true;
|
|
278
278
|
else if (rawValue === "false") value = false;
|
|
279
279
|
else if (rawValue !== "" && !isNaN(Number(rawValue))) value = Number(rawValue);
|
|
280
|
-
|
|
280
|
+
const draft = JSON.parse(JSON.stringify(this.config));
|
|
281
|
+
let current = draft;
|
|
281
282
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
282
283
|
const key = keys[i];
|
|
283
284
|
if (current[key] == null || typeof current[key] !== "object") {
|
|
@@ -286,6 +287,12 @@ ${err}`
|
|
|
286
287
|
current = current[key];
|
|
287
288
|
}
|
|
288
289
|
current[keys[keys.length - 1]] = value;
|
|
290
|
+
const result = ConfigSchema.safeParse(draft);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
const firstErr = result.error.errors[0];
|
|
293
|
+
throw new ConfigError(`Invalid config value for "${path}": ${firstErr?.message ?? "validation failed"}`);
|
|
294
|
+
}
|
|
295
|
+
this.config = result.data;
|
|
289
296
|
this.save();
|
|
290
297
|
}
|
|
291
298
|
/** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
|
|
@@ -2535,6 +2542,7 @@ var McpClient = class {
|
|
|
2535
2542
|
config;
|
|
2536
2543
|
process = null;
|
|
2537
2544
|
nextId = 1;
|
|
2545
|
+
// M8: wraps at MAX_SAFE_INTEGER via getNextId()
|
|
2538
2546
|
connected = false;
|
|
2539
2547
|
serverInfo = null;
|
|
2540
2548
|
/** stderr 收集(最多保留最后 2KB,用于错误报告) */
|
|
@@ -2667,6 +2675,7 @@ var McpClient = class {
|
|
|
2667
2675
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
2668
2676
|
}
|
|
2669
2677
|
const id = this.nextId++;
|
|
2678
|
+
if (this.nextId > Number.MAX_SAFE_INTEGER - 1) this.nextId = 1;
|
|
2670
2679
|
const request = {
|
|
2671
2680
|
jsonrpc: "2.0",
|
|
2672
2681
|
id,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
7
7
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
8
8
|
runTestsTool
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-244SVJXW.js";
|
|
10
10
|
|
|
11
11
|
// src/tools/builtin/bash.ts
|
|
12
12
|
import { execSync } from "child_process";
|
|
@@ -534,6 +534,13 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
534
534
|
if (BINARY_EXTENSIONS.has(ext)) {
|
|
535
535
|
return `[Binary file: ${filePath} (${ext})]
|
|
536
536
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const fstat = statSync2(normalizedPath);
|
|
540
|
+
if (fstat.size > MAX_FILE_BYTES) {
|
|
541
|
+
return `[File too large: ${filePath} | ${(fstat.size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_FILE_BYTES / 1024 / 1024} MB limit]`;
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
537
544
|
}
|
|
538
545
|
const buf = readFileSync2(normalizedPath);
|
|
539
546
|
if (encoding === "base64") {
|
|
@@ -1003,6 +1010,10 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
1003
1010
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
1004
1011
|
if (!pattern) throw new Error("pattern is required");
|
|
1005
1012
|
if (!existsSync6(rootPath)) throw new Error(`Path not found: ${rootPath}`);
|
|
1013
|
+
const MAX_PATTERN_LENGTH = 1e3;
|
|
1014
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
1015
|
+
throw new Error(`Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
1016
|
+
}
|
|
1006
1017
|
let regex;
|
|
1007
1018
|
try {
|
|
1008
1019
|
regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
|
|
@@ -1386,11 +1397,11 @@ ${stderr}`);
|
|
|
1386
1397
|
// src/tools/builtin/web-fetch.ts
|
|
1387
1398
|
import { promises as dnsPromises } from "dns";
|
|
1388
1399
|
function htmlToText(html) {
|
|
1400
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
1389
1401
|
const HTML_REGEX_LIMIT = 2e5;
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
1402
|
+
if (text.length > HTML_REGEX_LIMIT) {
|
|
1403
|
+
text = text.slice(0, HTML_REGEX_LIMIT);
|
|
1392
1404
|
}
|
|
1393
|
-
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
1394
1405
|
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, lvl, content) => {
|
|
1395
1406
|
const prefix = "#".repeat(Number(lvl));
|
|
1396
1407
|
return `
|
|
@@ -1848,6 +1859,14 @@ var EnvLoader = class {
|
|
|
1848
1859
|
if (val) return val;
|
|
1849
1860
|
}
|
|
1850
1861
|
const dynamicEnvVar = `AICLI_API_KEY_${providerId.toUpperCase().replace(/-/g, "_")}`;
|
|
1862
|
+
if (fixedEnvVar && fixedEnvVar !== dynamicEnvVar) {
|
|
1863
|
+
const fixedVal = process.env[fixedEnvVar];
|
|
1864
|
+
const dynVal = process.env[dynamicEnvVar];
|
|
1865
|
+
if (fixedVal && dynVal && fixedVal !== dynVal) {
|
|
1866
|
+
process.stderr.write(`[warn] env var collision: ${fixedEnvVar} and ${dynamicEnvVar} have different values for provider "${providerId}". Using ${fixedEnvVar}.
|
|
1867
|
+
`);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1851
1870
|
return process.env[dynamicEnvVar] || void 0;
|
|
1852
1871
|
}
|
|
1853
1872
|
static getDefaultProvider() {
|
|
@@ -381,7 +381,7 @@ ${content}`);
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
384
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
384
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-EGJZA6Y4.js");
|
|
385
385
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
386
386
|
let interrupted = false;
|
|
387
387
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
saveDevState,
|
|
24
24
|
sessionHasMeaningfulContent,
|
|
25
25
|
setupProxy
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ROMSAKP6.js";
|
|
27
27
|
import {
|
|
28
28
|
ToolRegistry,
|
|
29
29
|
askUserContext,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
theme,
|
|
39
39
|
truncateOutput,
|
|
40
40
|
undoStack
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-WIWNSN7U.js";
|
|
42
42
|
import {
|
|
43
43
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
44
44
|
AUTHOR,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
REPO_URL,
|
|
59
59
|
SKILLS_DIR_NAME,
|
|
60
60
|
VERSION
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-244SVJXW.js";
|
|
62
62
|
|
|
63
63
|
// src/index.ts
|
|
64
64
|
import { program } from "commander";
|
|
@@ -1914,7 +1914,7 @@ ${hint}` : "")
|
|
|
1914
1914
|
description: "Run project tests and show structured report",
|
|
1915
1915
|
usage: "/test [command|filter]",
|
|
1916
1916
|
async execute(args, _ctx) {
|
|
1917
|
-
const { executeTests } = await import("./run-tests-
|
|
1917
|
+
const { executeTests } = await import("./run-tests-YU52BWHE.js");
|
|
1918
1918
|
const argStr = args.join(" ").trim();
|
|
1919
1919
|
let testArgs = {};
|
|
1920
1920
|
if (argStr) {
|
|
@@ -5528,7 +5528,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5528
5528
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5529
5529
|
process.exit(1);
|
|
5530
5530
|
}
|
|
5531
|
-
const { startWebServer } = await import("./server-
|
|
5531
|
+
const { startWebServer } = await import("./server-KVHJCPUO.js");
|
|
5532
5532
|
await startWebServer({ port, host: options.host });
|
|
5533
5533
|
});
|
|
5534
5534
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5761,7 +5761,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5761
5761
|
}),
|
|
5762
5762
|
config.get("customProviders")
|
|
5763
5763
|
);
|
|
5764
|
-
const { startHub } = await import("./hub-
|
|
5764
|
+
const { startHub } = await import("./hub-QZXQ6JYS.js");
|
|
5765
5765
|
await startHub(
|
|
5766
5766
|
{
|
|
5767
5767
|
topic: topic ?? "",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
renderDiff,
|
|
19
19
|
runHook,
|
|
20
20
|
setupProxy
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-ROMSAKP6.js";
|
|
22
22
|
import {
|
|
23
23
|
AuthManager
|
|
24
24
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -32,19 +32,24 @@ import {
|
|
|
32
32
|
spawnAgentContext,
|
|
33
33
|
truncateOutput,
|
|
34
34
|
undoStack
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-WIWNSN7U.js";
|
|
36
36
|
import {
|
|
37
37
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
38
|
+
AUTHOR,
|
|
39
|
+
AUTHOR_EMAIL,
|
|
38
40
|
CONTEXT_FILE_CANDIDATES,
|
|
41
|
+
CUSTOM_COMMANDS_DIR_NAME,
|
|
39
42
|
DEFAULT_MAX_TOKENS,
|
|
43
|
+
DESCRIPTION,
|
|
40
44
|
MCP_PROJECT_CONFIG_NAME,
|
|
41
45
|
MEMORY_FILE_NAME,
|
|
42
46
|
MEMORY_MAX_CHARS,
|
|
43
47
|
PLAN_MODE_READONLY_TOOLS,
|
|
44
48
|
PLAN_MODE_SYSTEM_ADDON,
|
|
49
|
+
PLUGINS_DIR_NAME,
|
|
45
50
|
SKILLS_DIR_NAME,
|
|
46
51
|
VERSION
|
|
47
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-244SVJXW.js";
|
|
48
53
|
|
|
49
54
|
// src/web/server.ts
|
|
50
55
|
import express from "express";
|
|
@@ -57,7 +62,7 @@ import { networkInterfaces } from "os";
|
|
|
57
62
|
// src/web/tool-executor-web.ts
|
|
58
63
|
import { randomUUID } from "crypto";
|
|
59
64
|
import { existsSync, readFileSync } from "fs";
|
|
60
|
-
var ToolExecutorWeb = class {
|
|
65
|
+
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
61
66
|
constructor(registry, ws) {
|
|
62
67
|
this.registry = registry;
|
|
63
68
|
this.ws = ws;
|
|
@@ -71,6 +76,10 @@ var ToolExecutorWeb = class {
|
|
|
71
76
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
72
77
|
/** Pending batch confirm promises */
|
|
73
78
|
pendingBatchConfirms = /* @__PURE__ */ new Map();
|
|
79
|
+
/** M7 fix: timers for confirm cleanup if client crashes */
|
|
80
|
+
pendingTimers = /* @__PURE__ */ new Map();
|
|
81
|
+
static CONFIRM_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
82
|
+
// 5 minutes
|
|
74
83
|
/** Publicly readable by SessionHandler to check if confirm is active */
|
|
75
84
|
confirming = false;
|
|
76
85
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
@@ -86,10 +95,19 @@ var ToolExecutorWeb = class {
|
|
|
86
95
|
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
87
96
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
88
97
|
}
|
|
98
|
+
/** Clear M7 timeout timer for a requestId */
|
|
99
|
+
clearPendingTimer(requestId) {
|
|
100
|
+
const timer = this.pendingTimers.get(requestId);
|
|
101
|
+
if (timer) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
this.pendingTimers.delete(requestId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
89
106
|
/** Resolve a pending confirm from client response */
|
|
90
107
|
resolveConfirm(requestId, approved) {
|
|
91
108
|
const resolve3 = this.pendingConfirms.get(requestId);
|
|
92
109
|
if (resolve3) {
|
|
110
|
+
this.clearPendingTimer(requestId);
|
|
93
111
|
this.pendingConfirms.delete(requestId);
|
|
94
112
|
this.confirming = false;
|
|
95
113
|
resolve3(approved);
|
|
@@ -99,6 +117,7 @@ var ToolExecutorWeb = class {
|
|
|
99
117
|
resolveBatchConfirm(requestId, decision) {
|
|
100
118
|
const resolve3 = this.pendingBatchConfirms.get(requestId);
|
|
101
119
|
if (resolve3) {
|
|
120
|
+
this.clearPendingTimer(requestId);
|
|
102
121
|
this.pendingBatchConfirms.delete(requestId);
|
|
103
122
|
this.confirming = false;
|
|
104
123
|
if (decision === "all" || decision === "none") {
|
|
@@ -190,6 +209,11 @@ var ToolExecutorWeb = class {
|
|
|
190
209
|
this.send(msg);
|
|
191
210
|
return new Promise((resolve3) => {
|
|
192
211
|
this.pendingConfirms.set(requestId, resolve3);
|
|
212
|
+
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
213
|
+
if (this.pendingConfirms.has(requestId)) {
|
|
214
|
+
this.resolveConfirm(requestId, false);
|
|
215
|
+
}
|
|
216
|
+
}, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
|
|
193
217
|
});
|
|
194
218
|
}
|
|
195
219
|
/** WebSocket-based batch confirm */
|
|
@@ -210,6 +234,11 @@ var ToolExecutorWeb = class {
|
|
|
210
234
|
this.send(msg);
|
|
211
235
|
return new Promise((resolve3) => {
|
|
212
236
|
this.pendingBatchConfirms.set(requestId, resolve3);
|
|
237
|
+
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
238
|
+
if (this.pendingBatchConfirms.has(requestId)) {
|
|
239
|
+
this.resolveBatchConfirm(requestId, "none");
|
|
240
|
+
}
|
|
241
|
+
}, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
|
|
213
242
|
});
|
|
214
243
|
}
|
|
215
244
|
async execute(call) {
|
|
@@ -451,6 +480,8 @@ var SessionHandler = class _SessionHandler {
|
|
|
451
480
|
pendingAskUser = /* @__PURE__ */ new Map();
|
|
452
481
|
/** Active system prompt from context files */
|
|
453
482
|
activeSystemPrompt;
|
|
483
|
+
/** Directories added via /add-dir */
|
|
484
|
+
addedDirs = /* @__PURE__ */ new Set();
|
|
454
485
|
constructor(ws, shared) {
|
|
455
486
|
this.ws = ws;
|
|
456
487
|
this.config = shared.config;
|
|
@@ -1054,27 +1085,25 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
1054
1085
|
message: [
|
|
1055
1086
|
"\u{1F4D6} Available Web UI commands:",
|
|
1056
1087
|
"",
|
|
1088
|
+
" /help \u2014 Show this help message",
|
|
1089
|
+
" /about \u2014 Version & author info",
|
|
1057
1090
|
" /provider <id> \u2014 Switch AI provider",
|
|
1058
1091
|
" /model <id> \u2014 Switch model",
|
|
1059
1092
|
" /clear \u2014 Clear conversation & start new session",
|
|
1060
1093
|
" /compact [hint] \u2014 Compress conversation history",
|
|
1061
1094
|
" /think [on|off] \u2014 Toggle extended thinking mode",
|
|
1062
1095
|
" /plan [enter|exit] \u2014 Toggle read-only planning mode",
|
|
1063
|
-
" /session new
|
|
1064
|
-
" /
|
|
1065
|
-
" /
|
|
1066
|
-
" /session delete <id> \u2014 Delete a session",
|
|
1096
|
+
" /session new|list|load|delete <id> \u2014 Session management",
|
|
1097
|
+
" /system [prompt|clear] \u2014 Set/view/reset system prompt",
|
|
1098
|
+
" /context [reload] \u2014 Show/reload context layers",
|
|
1067
1099
|
" /status \u2014 Show session info & token usage",
|
|
1068
1100
|
" /cost \u2014 Show cumulative token usage",
|
|
1069
|
-
" /
|
|
1070
|
-
" /
|
|
1071
|
-
" /
|
|
1072
|
-
" /skill
|
|
1073
|
-
" /
|
|
1074
|
-
" /
|
|
1075
|
-
" /memory add <text> \u2014 Add entry to persistent memory",
|
|
1076
|
-
" /memory clear \u2014 Clear persistent memory",
|
|
1077
|
-
" /yolo [on|off] \u2014 Toggle session auto-approve (skip confirmations)",
|
|
1101
|
+
" /config [show|get|set] \u2014 View/modify configuration",
|
|
1102
|
+
" /tools \u2014 Show tools, MCP servers & skills",
|
|
1103
|
+
" /export [md|json] \u2014 Export conversation",
|
|
1104
|
+
" /skill [name|off|list|reload] \u2014 Manage agent skills",
|
|
1105
|
+
" /memory [show|add|clear] \u2014 Persistent memory management",
|
|
1106
|
+
" /yolo [on|off] \u2014 Toggle auto-approve (skip confirmations)",
|
|
1078
1107
|
" /search <keyword> \u2014 Search across all session histories",
|
|
1079
1108
|
" /undo [list|<n>] \u2014 Undo file operations",
|
|
1080
1109
|
" /diff [--stats] \u2014 Show file modifications in this session",
|
|
@@ -1083,8 +1112,13 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
1083
1112
|
" /review [--staged] \u2014 AI code review from git diff",
|
|
1084
1113
|
" /test [command] \u2014 Run project tests",
|
|
1085
1114
|
" /init [--force] \u2014 Generate AICLI.md by scanning project",
|
|
1115
|
+
" /scaffold <desc> \u2014 Generate project scaffolding with AI",
|
|
1116
|
+
" /add-dir [path|remove] \u2014 Add/remove directory from AI context",
|
|
1117
|
+
" /mcp [reconnect] \u2014 Show/manage MCP servers",
|
|
1118
|
+
" /plugins \u2014 Show loaded plugins",
|
|
1119
|
+
" /commands \u2014 List custom commands",
|
|
1086
1120
|
" /doctor \u2014 Health check (API keys, config, MCP)",
|
|
1087
|
-
" /
|
|
1121
|
+
" /bug \u2014 Generate bug report template",
|
|
1088
1122
|
"",
|
|
1089
1123
|
"\u{1F4A1} Tips:",
|
|
1090
1124
|
" \u2022 Change provider/model with the dropdowns above",
|
|
@@ -1448,7 +1482,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1448
1482
|
case "test": {
|
|
1449
1483
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1450
1484
|
try {
|
|
1451
|
-
const { executeTests } = await import("./run-tests-
|
|
1485
|
+
const { executeTests } = await import("./run-tests-YU52BWHE.js");
|
|
1452
1486
|
const argStr = args.join(" ").trim();
|
|
1453
1487
|
let testArgs = {};
|
|
1454
1488
|
if (argStr) {
|
|
@@ -1536,6 +1570,264 @@ Use /context reload to load it.` });
|
|
|
1536
1570
|
this.send({ type: "info", message: lines.join("\n") });
|
|
1537
1571
|
break;
|
|
1538
1572
|
}
|
|
1573
|
+
// ── /about ──────────────────────────────────────────────────────
|
|
1574
|
+
case "about": {
|
|
1575
|
+
const providerList = this.providers.listAll();
|
|
1576
|
+
const toolCount = this.toolRegistry.getDefinitions().length;
|
|
1577
|
+
const mcpTools = this.mcpManager?.getTotalToolCount() ?? 0;
|
|
1578
|
+
const lines = [
|
|
1579
|
+
`\u{1F916} **ai-cli** v${VERSION}`,
|
|
1580
|
+
"",
|
|
1581
|
+
`${DESCRIPTION}`,
|
|
1582
|
+
`Author: ${AUTHOR} <${AUTHOR_EMAIL}>`,
|
|
1583
|
+
"",
|
|
1584
|
+
`**Providers:** ${providerList.length} (${providerList.map((p) => p.id).join(", ")})`,
|
|
1585
|
+
`**Tools:** ${toolCount + mcpTools} (${toolCount} built-in${mcpTools > 0 ? ` + ${mcpTools} MCP` : ""})`,
|
|
1586
|
+
`**REPL Commands:** 37`,
|
|
1587
|
+
"",
|
|
1588
|
+
`GitHub: https://github.com/jinzhengdong/ai-cli`
|
|
1589
|
+
];
|
|
1590
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
1591
|
+
break;
|
|
1592
|
+
}
|
|
1593
|
+
// ── /system ─────────────────────────────────────────────────────
|
|
1594
|
+
case "system": {
|
|
1595
|
+
const text = args.join(" ").trim();
|
|
1596
|
+
if (!text) {
|
|
1597
|
+
const current = this.activeSystemPrompt;
|
|
1598
|
+
if (current) {
|
|
1599
|
+
const preview = current.length > 500 ? current.slice(0, 500) + "..." : current;
|
|
1600
|
+
this.send({ type: "info", message: `\u{1F4CB} Current system prompt (${current.length} chars):
|
|
1601
|
+
|
|
1602
|
+
${preview}` });
|
|
1603
|
+
} else {
|
|
1604
|
+
this.send({ type: "info", message: "No custom system prompt set. Using default.\nUsage: /system <prompt>" });
|
|
1605
|
+
}
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
if (text === "clear" || text === "reset") {
|
|
1609
|
+
this.activeSystemPrompt = this.loadContextFiles();
|
|
1610
|
+
this.send({ type: "info", message: "\u2713 System prompt reset to default (context files)." });
|
|
1611
|
+
} else {
|
|
1612
|
+
this.activeSystemPrompt = text;
|
|
1613
|
+
this.send({ type: "info", message: `\u2713 System prompt set (${text.length} chars).` });
|
|
1614
|
+
}
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
// ── /config ─────────────────────────────────────────────────────
|
|
1618
|
+
case "config": {
|
|
1619
|
+
const sub = args[0];
|
|
1620
|
+
if (sub === "show" || !sub) {
|
|
1621
|
+
this.send({ type: "info", message: `\u2699\uFE0F Configuration:
|
|
1622
|
+
\`\`\`json
|
|
1623
|
+
${this.config.toFormattedJSON()}
|
|
1624
|
+
\`\`\`` });
|
|
1625
|
+
} else if (sub === "get" && args[1]) {
|
|
1626
|
+
const val = this.config.getByPath(args[1]);
|
|
1627
|
+
const display = typeof val === "object" ? JSON.stringify(val, null, 2) : String(val ?? "undefined");
|
|
1628
|
+
this.send({ type: "info", message: `${args[1]} = ${display}` });
|
|
1629
|
+
} else if (sub === "set" && args[1] && args[2] !== void 0) {
|
|
1630
|
+
try {
|
|
1631
|
+
this.config.setByPath(args[1], args.slice(2).join(" "));
|
|
1632
|
+
this.send({ type: "info", message: `\u2713 ${args[1]} = ${args.slice(2).join(" ")}` });
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
this.send({ type: "error", message: `Config error: ${err.message}` });
|
|
1635
|
+
}
|
|
1636
|
+
} else {
|
|
1637
|
+
this.send({ type: "info", message: "Usage: /config [show] | /config get <key> | /config set <key> <value>" });
|
|
1638
|
+
}
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
// ── /context ────────────────────────────────────────────────────
|
|
1642
|
+
case "context": {
|
|
1643
|
+
const sub = args[0];
|
|
1644
|
+
if (sub === "reload") {
|
|
1645
|
+
this.activeSystemPrompt = this.loadContextFiles();
|
|
1646
|
+
this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
|
|
1647
|
+
break;
|
|
1648
|
+
}
|
|
1649
|
+
const configDir = this.config.getConfigDir();
|
|
1650
|
+
const cwd = process.cwd();
|
|
1651
|
+
const gitRoot = getGitRoot(cwd);
|
|
1652
|
+
const projectRoot = gitRoot ?? cwd;
|
|
1653
|
+
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
1654
|
+
const checkLayer = (label, dir) => {
|
|
1655
|
+
for (const name2 of CONTEXT_FILE_CANDIDATES) {
|
|
1656
|
+
const fullPath = join2(dir, name2);
|
|
1657
|
+
if (existsSync3(fullPath)) {
|
|
1658
|
+
try {
|
|
1659
|
+
const size = statSync(fullPath).size;
|
|
1660
|
+
layers.push(` \u2713 ${label}: ${fullPath} (${size} bytes)`);
|
|
1661
|
+
return;
|
|
1662
|
+
} catch {
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
layers.push(` \u2013 ${label}: (none)`);
|
|
1667
|
+
};
|
|
1668
|
+
checkLayer("Global", configDir);
|
|
1669
|
+
checkLayer("Project", projectRoot);
|
|
1670
|
+
if (resolve(cwd) !== resolve(projectRoot)) {
|
|
1671
|
+
checkLayer("Subdir", cwd);
|
|
1672
|
+
}
|
|
1673
|
+
layers.push("");
|
|
1674
|
+
layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
|
|
1675
|
+
layers.push("Use /context reload to refresh.");
|
|
1676
|
+
this.send({ type: "info", message: layers.join("\n") });
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
// ── /mcp ────────────────────────────────────────────────────────
|
|
1680
|
+
case "mcp": {
|
|
1681
|
+
if (!this.mcpManager) {
|
|
1682
|
+
this.send({ type: "info", message: "MCP not configured. Add mcpServers to config.json." });
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
const sub = args[0];
|
|
1686
|
+
if (sub === "reconnect") {
|
|
1687
|
+
const targetId = args[1];
|
|
1688
|
+
this.send({ type: "info", message: "\u{1F504} Reconnecting MCP servers..." });
|
|
1689
|
+
try {
|
|
1690
|
+
await this.mcpManager.reconnectAll();
|
|
1691
|
+
this.send({ type: "info", message: "\u2713 MCP reconnect complete." });
|
|
1692
|
+
} catch (err) {
|
|
1693
|
+
this.send({ type: "error", message: `Reconnect failed: ${err.message}` });
|
|
1694
|
+
}
|
|
1695
|
+
this.sendToolsList();
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
const statuses = this.mcpManager.getStatus();
|
|
1699
|
+
if (statuses.length === 0) {
|
|
1700
|
+
this.send({ type: "info", message: "No MCP servers configured." });
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
const lines = [`\u{1F50C} **MCP Servers (${statuses.length}):**`, ""];
|
|
1704
|
+
for (const s of statuses) {
|
|
1705
|
+
const state = s.connected ? `\u2713 connected \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `\u2717 disconnected${s.error ? ` \xB7 ${s.error}` : ""}`;
|
|
1706
|
+
lines.push(` ${s.serverId}: ${state}`);
|
|
1707
|
+
}
|
|
1708
|
+
lines.push("");
|
|
1709
|
+
lines.push("Use /mcp reconnect to reconnect.");
|
|
1710
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
// ── /scaffold ───────────────────────────────────────────────────
|
|
1714
|
+
case "scaffold": {
|
|
1715
|
+
const description = args.join(" ").trim();
|
|
1716
|
+
if (!description) {
|
|
1717
|
+
this.send({ type: "error", message: "Usage: /scaffold <project description>" });
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
this.send({ type: "info", message: "\u{1F3D7}\uFE0F Generating project scaffold..." });
|
|
1721
|
+
const scaffoldPrompt = `Please scaffold a project based on this description: ${description}
|
|
1722
|
+
|
|
1723
|
+
Create the necessary files and directory structure. Use write_file and bash tools to set up the project.`;
|
|
1724
|
+
await this.handleChat(scaffoldPrompt);
|
|
1725
|
+
break;
|
|
1726
|
+
}
|
|
1727
|
+
// ── /add-dir ────────────────────────────────────────────────────
|
|
1728
|
+
case "add-dir": {
|
|
1729
|
+
const sub = args[0]?.trim();
|
|
1730
|
+
if (!sub) {
|
|
1731
|
+
this.send({ type: "info", message: this.addedDirs.size > 0 ? `\u{1F4C1} Added directories:
|
|
1732
|
+
${[...this.addedDirs].map((d) => ` \u2022 ${d}`).join("\n")}
|
|
1733
|
+
|
|
1734
|
+
Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path> | /add-dir remove" });
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
if (sub === "remove" || sub === "clear") {
|
|
1738
|
+
this.addedDirs.clear();
|
|
1739
|
+
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
const dirPath = resolve(sub);
|
|
1743
|
+
if (!existsSync3(dirPath)) {
|
|
1744
|
+
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
1745
|
+
break;
|
|
1746
|
+
}
|
|
1747
|
+
if (!statSync(dirPath).isDirectory()) {
|
|
1748
|
+
this.send({ type: "error", message: `Not a directory: ${dirPath}` });
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
this.addedDirs.add(dirPath);
|
|
1752
|
+
this.send({ type: "info", message: `\u2713 Added directory: ${dirPath}
|
|
1753
|
+
It will be included in AI context for subsequent messages.` });
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1756
|
+
// ── /commands ───────────────────────────────────────────────────
|
|
1757
|
+
case "commands": {
|
|
1758
|
+
const configDir = this.config.getConfigDir();
|
|
1759
|
+
const commandsDir = join2(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
1760
|
+
if (!existsSync3(commandsDir)) {
|
|
1761
|
+
this.send({ type: "info", message: `No custom commands directory.
|
|
1762
|
+
Create: ${commandsDir}/ with .md files.` });
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
try {
|
|
1766
|
+
const files = readdirSync(commandsDir).filter((f) => f.endsWith(".md"));
|
|
1767
|
+
if (files.length === 0) {
|
|
1768
|
+
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
1769
|
+
Add .md files to create commands.` });
|
|
1770
|
+
} else {
|
|
1771
|
+
const lines = [`\u{1F4CB} **Custom Commands (${files.length}):**`, `Dir: ${commandsDir}`, ""];
|
|
1772
|
+
for (const f of files) {
|
|
1773
|
+
const name2 = f.replace(/\.md$/, "");
|
|
1774
|
+
lines.push(` /${name2}`);
|
|
1775
|
+
}
|
|
1776
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
1777
|
+
}
|
|
1778
|
+
} catch {
|
|
1779
|
+
this.send({ type: "info", message: `Cannot read: ${commandsDir}` });
|
|
1780
|
+
}
|
|
1781
|
+
break;
|
|
1782
|
+
}
|
|
1783
|
+
// ── /plugins ────────────────────────────────────────────────────
|
|
1784
|
+
case "plugins": {
|
|
1785
|
+
const configDir = this.config.getConfigDir();
|
|
1786
|
+
const pluginsDir = join2(configDir, PLUGINS_DIR_NAME);
|
|
1787
|
+
const pluginTools = this.toolRegistry.listPluginTools();
|
|
1788
|
+
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
1789
|
+
if (pluginTools.length === 0) {
|
|
1790
|
+
lines.push("No plugins loaded.");
|
|
1791
|
+
lines.push("Add .js files to the plugins directory and set allowPlugins:true in config.");
|
|
1792
|
+
} else {
|
|
1793
|
+
for (const t of pluginTools) {
|
|
1794
|
+
lines.push(` \u2022 ${t.name} \u2014 ${t.description}`);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1800
|
+
// ── /bug ────────────────────────────────────────────────────────
|
|
1801
|
+
case "bug": {
|
|
1802
|
+
const session = this.sessions.current;
|
|
1803
|
+
const lines = [
|
|
1804
|
+
"\u{1F41B} **Bug Report Template**",
|
|
1805
|
+
"",
|
|
1806
|
+
"**Environment:**",
|
|
1807
|
+
` ai-cli version: ${VERSION}`,
|
|
1808
|
+
` Node.js: ${process.version}`,
|
|
1809
|
+
` OS: ${process.platform} ${process.arch}`,
|
|
1810
|
+
` Provider: ${this.currentProvider}`,
|
|
1811
|
+
` Model: ${this.currentModel}`,
|
|
1812
|
+
` Session: ${session?.id.slice(0, 8) ?? "none"} (${session?.messages.length ?? 0} messages)`,
|
|
1813
|
+
"",
|
|
1814
|
+
"**Description:**",
|
|
1815
|
+
"(Describe the bug)",
|
|
1816
|
+
"",
|
|
1817
|
+
"**Steps to reproduce:**",
|
|
1818
|
+
"1. ...",
|
|
1819
|
+
"",
|
|
1820
|
+
"**Expected behavior:**",
|
|
1821
|
+
"...",
|
|
1822
|
+
"",
|
|
1823
|
+
"**Actual behavior:**",
|
|
1824
|
+
"...",
|
|
1825
|
+
"",
|
|
1826
|
+
"Report at: https://github.com/jinzhengdong/ai-cli/issues"
|
|
1827
|
+
];
|
|
1828
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1539
1831
|
default:
|
|
1540
1832
|
this.send({ type: "error", message: `Unknown command: /${name}. Type /help for available commands.` });
|
|
1541
1833
|
}
|
|
@@ -1735,12 +2027,27 @@ Use /context reload to load it.` });
|
|
|
1735
2027
|
buildSystemPrompt() {
|
|
1736
2028
|
const skillContent = this.skillManager?.getActivePromptContent();
|
|
1737
2029
|
const activeSkill = skillContent && this.skillManager?.getActive() ? { name: this.skillManager.getActive().meta.name, content: skillContent } : void 0;
|
|
1738
|
-
|
|
2030
|
+
let prompt = buildSystemPrompt({
|
|
1739
2031
|
activeSystemPrompt: this.activeSystemPrompt,
|
|
1740
2032
|
activeSkill,
|
|
1741
2033
|
planMode: this.planMode,
|
|
1742
2034
|
configDir: this.config.getConfigDir()
|
|
1743
2035
|
});
|
|
2036
|
+
if (this.addedDirs.size > 0) {
|
|
2037
|
+
const MAX_DIR_CONTEXT = 4e4;
|
|
2038
|
+
let dirContext = "\n\n--- Added Directory Context ---\n";
|
|
2039
|
+
let totalLen = 0;
|
|
2040
|
+
for (const dir of this.addedDirs) {
|
|
2041
|
+
if (totalLen > MAX_DIR_CONTEXT) break;
|
|
2042
|
+
dirContext += `
|
|
2043
|
+
[Directory: ${dir}]
|
|
2044
|
+
`;
|
|
2045
|
+
dirContext += this.scanDirTree(dir, 2, 40) + "\n";
|
|
2046
|
+
totalLen = dirContext.length;
|
|
2047
|
+
}
|
|
2048
|
+
prompt += dirContext;
|
|
2049
|
+
}
|
|
2050
|
+
return prompt;
|
|
1744
2051
|
}
|
|
1745
2052
|
getModelParams() {
|
|
1746
2053
|
const allParams = this.config.get("modelParams");
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WIWNSN7U.js";
|
|
8
8
|
import {
|
|
9
9
|
SUBAGENT_ALLOWED_TOOLS
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-244SVJXW.js";
|
|
11
11
|
|
|
12
12
|
// src/hub/task-orchestrator.ts
|
|
13
13
|
import { createInterface } from "readline";
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
|
|
15
15
|
import { join } from "path";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
|
|
@@ -266,7 +266,9 @@ var TaskOrchestrator = class {
|
|
|
266
266
|
}
|
|
267
267
|
saveState(plan) {
|
|
268
268
|
try {
|
|
269
|
-
|
|
269
|
+
const tmpPath = this.stateFilePath + ".tmp";
|
|
270
|
+
writeFileSync(tmpPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
271
|
+
renameSync(tmpPath, this.stateFilePath);
|
|
270
272
|
} catch {
|
|
271
273
|
}
|
|
272
274
|
}
|
|
@@ -357,13 +359,29 @@ Example:
|
|
|
357
359
|
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
358
360
|
throw new Error("Empty task list");
|
|
359
361
|
}
|
|
362
|
+
const validRoleIds = new Set(roles.map((r) => r.id));
|
|
363
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
364
|
+
const t = parsed[i];
|
|
365
|
+
if (typeof t !== "object" || t === null) {
|
|
366
|
+
throw new Error(`Task #${i + 1}: not an object`);
|
|
367
|
+
}
|
|
368
|
+
if (typeof t.description !== "string" || !t.description.trim()) {
|
|
369
|
+
throw new Error(`Task #${i + 1}: missing or empty "description"`);
|
|
370
|
+
}
|
|
371
|
+
if (typeof t.assignee !== "string" || !t.assignee.trim()) {
|
|
372
|
+
throw new Error(`Task #${i + 1}: missing or empty "assignee"`);
|
|
373
|
+
}
|
|
374
|
+
if (t.dependencies != null && !Array.isArray(t.dependencies)) {
|
|
375
|
+
throw new Error(`Task #${i + 1}: "dependencies" must be an array`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
360
378
|
const roleMap = new Map(roles.map((r) => [r.id, r.name]));
|
|
361
379
|
const tasks = parsed.map((t, i) => ({
|
|
362
|
-
id: t.id
|
|
380
|
+
id: typeof t.id === "number" ? t.id : i + 1,
|
|
363
381
|
description: t.description,
|
|
364
382
|
assignee: t.assignee,
|
|
365
383
|
assigneeName: roleMap.get(t.assignee) ?? t.assignee,
|
|
366
|
-
dependencies: t.dependencies
|
|
384
|
+
dependencies: Array.isArray(t.dependencies) ? t.dependencies : [],
|
|
367
385
|
status: "pending"
|
|
368
386
|
}));
|
|
369
387
|
return { goal, tasks };
|
|
@@ -428,7 +446,15 @@ Example:
|
|
|
428
446
|
if (!nextTask) {
|
|
429
447
|
const pending = plan.tasks.filter((t) => t.status === "pending");
|
|
430
448
|
if (pending.length === 0) break;
|
|
431
|
-
|
|
449
|
+
const failedIds = new Set(plan.tasks.filter((t) => t.status === "failed").map((t) => t.id));
|
|
450
|
+
const hasCycle = pending.every(
|
|
451
|
+
(t) => t.dependencies.some((d) => !completedIds.has(d) && !failedIds.has(d) && pending.some((p) => p.id === d))
|
|
452
|
+
);
|
|
453
|
+
if (hasCycle) {
|
|
454
|
+
console.log(chalk.red(` \u2717 Circular dependency detected among ${pending.length} task(s):`));
|
|
455
|
+
} else {
|
|
456
|
+
console.log(chalk.red(` \u2717 Cannot proceed: ${pending.length} task(s) have unmet dependencies.`));
|
|
457
|
+
}
|
|
432
458
|
for (const t of pending) {
|
|
433
459
|
const unmet = t.dependencies.filter((d) => !completedIds.has(d));
|
|
434
460
|
console.log(chalk.dim(` Task #${t.id}: waiting on [${unmet.join(", ")}]`));
|