jinzd-ai-cli 0.4.216 → 0.4.217
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 +3 -2
- package/dist/{batch-C6HCXZIG.js → batch-YCOVKTXD.js} +2 -2
- package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
- package/dist/{chunk-6HLKCSB3.js → chunk-524WZOKS.js} +1 -1
- package/dist/{chunk-GY3C3C2Y.js → chunk-KOPUCJXM.js} +3 -3
- package/dist/{chunk-TCOC4AUI.js → chunk-MGBMNCHG.js} +279 -54
- package/dist/{chunk-PNTLA3MN.js → chunk-OUAZOE5U.js} +2 -2
- package/dist/{chunk-OLN7VUZA.js → chunk-QAYOI57M.js} +3 -1
- package/dist/{chunk-77HDCGTN.js → chunk-SNJAOXFT.js} +1 -1
- package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
- package/dist/{chunk-VW7Y27WW.js → chunk-VTH7BLXK.js} +1 -1
- package/dist/{chunk-WAI3WPV2.js → chunk-WKOQ5CYC.js} +1 -1
- package/dist/{chunk-TJ6GGN4B.js → chunk-WZ3VKLF3.js} +1 -1
- package/dist/{chunk-7YWS3JLU.js → chunk-XJGEQIYS.js} +3 -1
- package/dist/{ci-6ZTFO3LX.js → ci-52RZIYWB.js} +4 -4
- package/dist/{ci-format-CLZ6QJRL.js → ci-format-73UXKE65.js} +2 -2
- package/dist/{constants-APSORFOH.js → constants-DIXAD35W.js} +3 -1
- package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-7GOQPULZ.js} +4 -4
- package/dist/electron-server.js +562 -318
- package/dist/{hub-ZGHQWNWE.js → hub-GIGBITZN.js} +1 -1
- package/dist/index.js +129 -69
- package/dist/{pr-JOL3IAGV.js → pr-KPQ5RPKB.js} +4 -4
- package/dist/{run-tests-4PKSIVK5.js → run-tests-K7QR5QN4.js} +1 -1
- package/dist/{run-tests-ZRK4TQUN.js → run-tests-ZDSA3QES.js} +2 -2
- package/dist/{server-O3XHT56X.js → server-22YF3U34.js} +5 -4
- package/dist/{server-FXUF5P64.js → server-QT3SC2KI.js} +129 -86
- package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-BHQQCVTY.js} +5 -4
- package/dist/{usage-X7MJX4YD.js → usage-WZZFSFLM.js} +2 -2
- package/dist/web/client/app.js +53 -2
- package/dist/web/client/index.html +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
- **Semantic Code Search** *(v0.4.77+)* — `search_code` tool finds code by meaning, not name. Local sentence embeddings (multilingual MiniLM, 117 MB one-time download) score symbols by cosine similarity against natural-language queries in English or Chinese ("where are users authenticated", "哪里做了速率限制"). No API key, runs on CPU. Manage with `/index semantic-rebuild|semantic-clear`
|
|
34
34
|
- **MCP Server Mode** *(v0.4.84+)* — `aicli mcp-serve` reverses ai-cli into an MCP server (JSON-RPC 2.0 over stdio), exposing its 28 built-in tools (incl. `find_symbol` / `search_code` / `run_tests`) to Claude Desktop / Cursor / any MCP client. Opt-in destructive-tool allow, `--tools` whitelist, `--cwd` override
|
|
35
35
|
- **Session Sensitive-Data Redaction** *(v0.4.88+)* — unified redactor scrubs `password=` / `api_key` / bearer tokens / OpenAI-style keys from every message **before it hits disk**. Query text is redacted too, so secrets never reach embeddings or logs. `/security status` + `/security scan` to audit
|
|
36
|
-
- **Human-like Long-Term Memory** *(v0.4.89+, B4)* — semantic index over every past chat session + `recall_memory` AI tool + `/memory rebuild|refresh|status|recall` commands. AI is prompted to auto-recall when it sees "last time" / "之前" / ambiguous references. Reuses the same MiniLM embedder as semantic code search
|
|
36
|
+
- **Human-like Long-Term Memory** *(v0.4.89+, B4)* — semantic index over every past chat session + `recall_memory` AI tool + `/memory rebuild|refresh|status|recall` commands. AI is prompted to auto-recall when it sees "last time" / "之前" / ambiguous references. Reuses the same MiniLM embedder as semantic code search
|
|
37
|
+
- **Governed Persistent Memory** *(v0.4.217+)* — `save_memory` and `/memory add` write auditable `memory.jsonl` entries with id/scope/source/sensitivity/approval/expiry; low-risk entries auto-approve, sensitive entries stay pending until `/memory approve <id>`, and project-scoped memories only inject inside the same project
|
|
37
38
|
- **Web UI Memory Panel** *(v0.4.90+, B4)* — new 🧠 Memory sidebar tab with semantic search across past chats; each hit has **➕ Inject** (quotes the snippet into the chat input as a markdown blockquote so you can review/edit before sending — no silent context injection) and **↗ Load** (jumps to source session). Bulk "Inject top 3" for recall bundles
|
|
38
39
|
- **Streaming Tool Use** — Real-time streaming of AI reasoning and tool calls as they happen
|
|
39
40
|
- **Sub-Agents** — Delegate complex subtasks to isolated child agents with independent tool loops
|
|
@@ -170,7 +171,7 @@ AI autonomously invokes these 27 tools during conversations:
|
|
|
170
171
|
| `run_tests` | safe | Auto-detect and run project tests (JUnit XML parsing) |
|
|
171
172
|
| `spawn_agent` | safe | Delegate subtasks to named isolated agents (`agent`: explorer/worker/reviewer/security/tester) |
|
|
172
173
|
| `ask_user` | safe | Pause and ask the user a question |
|
|
173
|
-
| `save_memory` | safe | Persist
|
|
174
|
+
| `save_memory` | safe | Persist governed memory across sessions; sensitive entries require `/memory approve <id>` |
|
|
174
175
|
| `write_todos` | safe | Task breakdown with live progress rendering |
|
|
175
176
|
| `save_last_response` | write | Save AI response to file |
|
|
176
177
|
| `task_create` | write | Start a command running in the background |
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WKOQ5CYC.js";
|
|
5
5
|
import "./chunk-TZQHYZKT.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-XJGEQIYS.js";
|
|
7
7
|
import {
|
|
8
8
|
atomicWriteFileSync
|
|
9
9
|
} from "./chunk-IW3Q7AE5.js";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
evaluateCiGate,
|
|
4
4
|
normalizeCiThresholds
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WZ3VKLF3.js";
|
|
6
6
|
import {
|
|
7
7
|
buildReviewPrompt,
|
|
8
8
|
buildSecurityReviewPrompt,
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
} from "./chunk-QMXC327F.js";
|
|
14
14
|
import {
|
|
15
15
|
ConfigManager
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-WKOQ5CYC.js";
|
|
17
17
|
import {
|
|
18
18
|
VERSION
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-XJGEQIYS.js";
|
|
20
20
|
|
|
21
21
|
// src/cli/ci.ts
|
|
22
22
|
import { execFileSync } from "child_process";
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
} from "./chunk-T2NL5ZIA.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VTH7BLXK.js";
|
|
9
9
|
import {
|
|
10
10
|
runTool
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SNJAOXFT.js";
|
|
12
12
|
import {
|
|
13
13
|
getDangerLevel,
|
|
14
14
|
isFileWriteTool,
|
|
@@ -23,10 +23,15 @@ import {
|
|
|
23
23
|
CONFIG_DIR_NAME,
|
|
24
24
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
25
25
|
MEMORY_FILE_NAME,
|
|
26
|
+
MEMORY_MAX_CHARS,
|
|
27
|
+
MEMORY_STORE_FILE_NAME,
|
|
26
28
|
SUBAGENT_ALLOWED_TOOLS,
|
|
27
29
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
28
30
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
29
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-XJGEQIYS.js";
|
|
32
|
+
import {
|
|
33
|
+
getGitRoot
|
|
34
|
+
} from "./chunk-HOSJZMQS.js";
|
|
30
35
|
import {
|
|
31
36
|
fileCheckpoints
|
|
32
37
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -34,6 +39,10 @@ import {
|
|
|
34
39
|
loadChatIndex,
|
|
35
40
|
searchChatMemory
|
|
36
41
|
} from "./chunk-TB4W4Y4T.js";
|
|
42
|
+
import {
|
|
43
|
+
DEFAULT_PATTERNS,
|
|
44
|
+
redactString
|
|
45
|
+
} from "./chunk-SLSWPBK3.js";
|
|
37
46
|
import {
|
|
38
47
|
indexProject
|
|
39
48
|
} from "./chunk-K3CF65QH.js";
|
|
@@ -2412,7 +2421,7 @@ var ToolExecutor = class {
|
|
|
2412
2421
|
rl.resume();
|
|
2413
2422
|
process.stdout.write(prompt);
|
|
2414
2423
|
this.confirming = true;
|
|
2415
|
-
return new Promise((
|
|
2424
|
+
return new Promise((resolve8) => {
|
|
2416
2425
|
let completed = false;
|
|
2417
2426
|
const cleanup = (result) => {
|
|
2418
2427
|
if (completed) return;
|
|
@@ -2422,7 +2431,7 @@ var ToolExecutor = class {
|
|
|
2422
2431
|
rl.pause();
|
|
2423
2432
|
rlAny.output = savedOutput;
|
|
2424
2433
|
this.confirming = false;
|
|
2425
|
-
|
|
2434
|
+
resolve8(result);
|
|
2426
2435
|
};
|
|
2427
2436
|
const onLine = (line) => {
|
|
2428
2437
|
const trimmed = line.trim();
|
|
@@ -2596,7 +2605,7 @@ var ToolExecutor = class {
|
|
|
2596
2605
|
rl.resume();
|
|
2597
2606
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
2598
2607
|
this.confirming = true;
|
|
2599
|
-
return new Promise((
|
|
2608
|
+
return new Promise((resolve8) => {
|
|
2600
2609
|
let completed = false;
|
|
2601
2610
|
const cleanup = (answer) => {
|
|
2602
2611
|
if (completed) return;
|
|
@@ -2606,7 +2615,7 @@ var ToolExecutor = class {
|
|
|
2606
2615
|
rl.pause();
|
|
2607
2616
|
rlAny.output = savedOutput;
|
|
2608
2617
|
this.confirming = false;
|
|
2609
|
-
|
|
2618
|
+
resolve8(answer === "y");
|
|
2610
2619
|
};
|
|
2611
2620
|
const onLine = (line) => {
|
|
2612
2621
|
const trimmed = line.trim();
|
|
@@ -3855,7 +3864,7 @@ var runInteractiveTool = {
|
|
|
3855
3864
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
3856
3865
|
};
|
|
3857
3866
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
3858
|
-
return new Promise((
|
|
3867
|
+
return new Promise((resolve8) => {
|
|
3859
3868
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
3860
3869
|
cwd: process.cwd(),
|
|
3861
3870
|
env,
|
|
@@ -3888,22 +3897,22 @@ var runInteractiveTool = {
|
|
|
3888
3897
|
setTimeout(writeNextLine, 400);
|
|
3889
3898
|
const timer = setTimeout(() => {
|
|
3890
3899
|
child.kill();
|
|
3891
|
-
|
|
3900
|
+
resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
3892
3901
|
${buildOutput(stdout, stderr)}`);
|
|
3893
3902
|
}, timeout);
|
|
3894
3903
|
child.on("close", (code) => {
|
|
3895
3904
|
clearTimeout(timer);
|
|
3896
3905
|
const output = buildOutput(stdout, stderr);
|
|
3897
3906
|
if (code !== 0 && code !== null) {
|
|
3898
|
-
|
|
3907
|
+
resolve8(`${prefixWarnings}Exit code ${code}:
|
|
3899
3908
|
${output}`);
|
|
3900
3909
|
} else {
|
|
3901
|
-
|
|
3910
|
+
resolve8(`${prefixWarnings}${output || "(no output)"}`);
|
|
3902
3911
|
}
|
|
3903
3912
|
});
|
|
3904
3913
|
child.on("error", (err) => {
|
|
3905
3914
|
clearTimeout(timer);
|
|
3906
|
-
|
|
3915
|
+
resolve8(
|
|
3907
3916
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
3908
3917
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
3909
3918
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -4571,26 +4580,236 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
4571
4580
|
};
|
|
4572
4581
|
|
|
4573
4582
|
// src/tools/builtin/save-memory.ts
|
|
4574
|
-
import {
|
|
4575
|
-
import { join as join6 } from "path";
|
|
4583
|
+
import { join as join7 } from "path";
|
|
4576
4584
|
import { homedir as homedir3 } from "os";
|
|
4577
|
-
|
|
4578
|
-
|
|
4585
|
+
|
|
4586
|
+
// src/memory/persistent-memory.ts
|
|
4587
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
4588
|
+
import { randomUUID, createHash as createHash2 } from "crypto";
|
|
4589
|
+
import { dirname as dirname4, join as join6, resolve as resolve6 } from "path";
|
|
4590
|
+
function memoryStorePath(configDir) {
|
|
4591
|
+
return join6(configDir, MEMORY_STORE_FILE_NAME);
|
|
4592
|
+
}
|
|
4593
|
+
function memoryMarkdownPath(configDir) {
|
|
4594
|
+
return join6(configDir, MEMORY_FILE_NAME);
|
|
4595
|
+
}
|
|
4596
|
+
function getProjectKey(cwd = process.cwd()) {
|
|
4597
|
+
return resolve6(getGitRoot(cwd) ?? cwd);
|
|
4598
|
+
}
|
|
4599
|
+
function nowIso() {
|
|
4600
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4601
|
+
}
|
|
4602
|
+
function isValidScope(value) {
|
|
4603
|
+
return value === "personal" || value === "project" || value === "session" || value === "team";
|
|
4604
|
+
}
|
|
4605
|
+
function isValidSensitivity(value) {
|
|
4606
|
+
return value === "low" || value === "medium" || value === "high";
|
|
4579
4607
|
}
|
|
4580
|
-
function
|
|
4581
|
-
|
|
4582
|
-
const
|
|
4583
|
-
|
|
4608
|
+
function normalizeEntry(value) {
|
|
4609
|
+
if (!value || typeof value !== "object") return null;
|
|
4610
|
+
const raw = value;
|
|
4611
|
+
const content = typeof raw.content === "string" ? raw.content.trim() : "";
|
|
4612
|
+
if (!content) return null;
|
|
4613
|
+
const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
|
|
4614
|
+
return {
|
|
4615
|
+
id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
|
|
4616
|
+
content,
|
|
4617
|
+
scope: isValidScope(raw.scope) ? raw.scope : "personal",
|
|
4618
|
+
sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
|
|
4619
|
+
sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
|
|
4620
|
+
createdAt,
|
|
4621
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
|
|
4622
|
+
expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
|
|
4623
|
+
sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
|
|
4624
|
+
approved: raw.approved === true,
|
|
4625
|
+
redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
function legacyEntriesFromMarkdown(configDir) {
|
|
4629
|
+
const file = memoryMarkdownPath(configDir);
|
|
4630
|
+
if (!existsSync11(file)) return [];
|
|
4631
|
+
const content = readFileSync8(file, "utf-8").trim();
|
|
4632
|
+
if (!content) return [];
|
|
4633
|
+
const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
|
|
4634
|
+
const chunks = parts.length > 1 ? parts : [content];
|
|
4635
|
+
return chunks.map((chunk, index) => {
|
|
4636
|
+
const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
|
|
4637
|
+
const body = (match ? match[2] : chunk).trim();
|
|
4638
|
+
const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
|
|
4639
|
+
const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
|
|
4640
|
+
const id = "legacy-" + createHash2("sha1").update(`${index}
|
|
4641
|
+
${chunk}`).digest("hex").slice(0, 12);
|
|
4642
|
+
return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
|
|
4643
|
+
}).filter((entry) => Boolean(entry));
|
|
4644
|
+
}
|
|
4645
|
+
function loadMemoryEntries(configDir) {
|
|
4646
|
+
const file = memoryStorePath(configDir);
|
|
4647
|
+
if (!existsSync11(file)) return legacyEntriesFromMarkdown(configDir);
|
|
4648
|
+
const text = readFileSync8(file, "utf-8").trim();
|
|
4649
|
+
if (!text) return [];
|
|
4650
|
+
const entries = [];
|
|
4651
|
+
for (const line of text.split("\n")) {
|
|
4652
|
+
const trimmed = line.trim();
|
|
4653
|
+
if (!trimmed) continue;
|
|
4654
|
+
try {
|
|
4655
|
+
const entry = normalizeEntry(JSON.parse(trimmed));
|
|
4656
|
+
if (entry) entries.push(entry);
|
|
4657
|
+
} catch {
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
return entries;
|
|
4661
|
+
}
|
|
4662
|
+
function saveMemoryEntries(configDir, entries) {
|
|
4663
|
+
mkdirSync4(configDir, { recursive: true });
|
|
4664
|
+
const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
4665
|
+
atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
|
|
4666
|
+
syncLegacyMarkdown(configDir, entries);
|
|
4667
|
+
}
|
|
4668
|
+
function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
|
|
4669
|
+
return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
|
|
4670
|
+
}
|
|
4671
|
+
function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
|
|
4672
|
+
if (entry.scope !== "project") return true;
|
|
4673
|
+
return entry.sourceProject === getProjectKey(cwd);
|
|
4674
|
+
}
|
|
4675
|
+
function listMemoryEntries(configDir, options = {}) {
|
|
4676
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4677
|
+
return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
|
|
4678
|
+
}
|
|
4679
|
+
function addMemoryEntry(configDir, content, options = {}) {
|
|
4680
|
+
const original = content.trim();
|
|
4681
|
+
if (!original) throw new Error("memory content is required");
|
|
4682
|
+
const redact = options.redact ?? true;
|
|
4683
|
+
const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
|
|
4684
|
+
const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
|
|
4685
|
+
const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
|
|
4686
|
+
const scope = options.scope ?? "personal";
|
|
4687
|
+
const timestamp = nowIso();
|
|
4688
|
+
const entry = {
|
|
4689
|
+
id: randomUUID(),
|
|
4690
|
+
content: redacted.redacted.trim(),
|
|
4691
|
+
scope,
|
|
4692
|
+
sourceSession: options.sourceSession,
|
|
4693
|
+
sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
|
|
4694
|
+
createdAt: timestamp,
|
|
4695
|
+
updatedAt: timestamp,
|
|
4696
|
+
expiresAt: options.expiresAt,
|
|
4697
|
+
sensitivity,
|
|
4698
|
+
approved,
|
|
4699
|
+
redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
|
|
4700
|
+
};
|
|
4701
|
+
const entries = loadMemoryEntries(configDir);
|
|
4702
|
+
entries.push(entry);
|
|
4703
|
+
saveMemoryEntries(configDir, entries);
|
|
4704
|
+
return entry;
|
|
4705
|
+
}
|
|
4706
|
+
function updateMemoryApproval(configDir, idPrefix, approved) {
|
|
4707
|
+
const entries = loadMemoryEntries(configDir);
|
|
4708
|
+
const entry = findUnique(entries, idPrefix);
|
|
4709
|
+
entry.approved = approved;
|
|
4710
|
+
entry.updatedAt = nowIso();
|
|
4711
|
+
saveMemoryEntries(configDir, entries);
|
|
4712
|
+
return entry;
|
|
4713
|
+
}
|
|
4714
|
+
function deleteMemoryEntry(configDir, idPrefix) {
|
|
4715
|
+
const entries = loadMemoryEntries(configDir);
|
|
4716
|
+
const entry = findUnique(entries, idPrefix);
|
|
4717
|
+
saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
|
|
4718
|
+
return entry;
|
|
4719
|
+
}
|
|
4720
|
+
function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
|
|
4721
|
+
const entries = loadMemoryEntries(configDir);
|
|
4722
|
+
const entry = findUnique(entries, idPrefix);
|
|
4723
|
+
entry.expiresAt = expiresAt;
|
|
4724
|
+
entry.updatedAt = nowIso();
|
|
4725
|
+
saveMemoryEntries(configDir, entries);
|
|
4726
|
+
return entry;
|
|
4727
|
+
}
|
|
4728
|
+
function exportMemoryEntries(configDir, cwd = process.cwd()) {
|
|
4729
|
+
return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
|
|
4730
|
+
}
|
|
4731
|
+
function findUnique(entries, idPrefix) {
|
|
4732
|
+
const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
|
|
4733
|
+
if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
|
|
4734
|
+
if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
|
|
4735
|
+
return matches[0];
|
|
4736
|
+
}
|
|
4737
|
+
function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
|
|
4738
|
+
mkdirSync4(dirname4(memoryMarkdownPath(configDir)), { recursive: true });
|
|
4739
|
+
const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
4740
|
+
const markdown = active.map((entry) => {
|
|
4741
|
+
const date = entry.createdAt.replace("T", " ").slice(0, 19);
|
|
4742
|
+
const source = [
|
|
4743
|
+
`id:${entry.id.slice(0, 8)}`,
|
|
4744
|
+
`scope:${entry.scope}`,
|
|
4745
|
+
entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
|
|
4746
|
+
entry.sourceProject ? `project:${entry.sourceProject}` : "",
|
|
4747
|
+
`sensitivity:${entry.sensitivity}`
|
|
4748
|
+
].filter(Boolean).join(" \xB7 ");
|
|
4749
|
+
return `## ${date}
|
|
4750
|
+
<!-- ${source} -->
|
|
4751
|
+
${entry.content}
|
|
4752
|
+
`;
|
|
4753
|
+
}).join("\n");
|
|
4754
|
+
atomicWriteFileSync(memoryMarkdownPath(configDir), markdown);
|
|
4755
|
+
}
|
|
4756
|
+
function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
|
|
4757
|
+
const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
4758
|
+
if (entries.length === 0) return null;
|
|
4759
|
+
let content = entries.map((entry) => {
|
|
4760
|
+
const source = [
|
|
4761
|
+
`id ${entry.id.slice(0, 8)}`,
|
|
4762
|
+
`scope ${entry.scope}`,
|
|
4763
|
+
entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
|
|
4764
|
+
entry.sourceProject ? `project ${entry.sourceProject}` : "",
|
|
4765
|
+
`sensitivity ${entry.sensitivity}`
|
|
4766
|
+
].filter(Boolean).join(" \xB7 ");
|
|
4767
|
+
return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
|
|
4768
|
+
Source: ${source}
|
|
4769
|
+
${entry.content}`;
|
|
4770
|
+
}).join("\n\n");
|
|
4771
|
+
if (content.length > MEMORY_MAX_CHARS) {
|
|
4772
|
+
content = content.slice(-MEMORY_MAX_CHARS);
|
|
4773
|
+
const firstEntry = content.indexOf("\n## ");
|
|
4774
|
+
if (firstEntry !== -1) content = content.slice(firstEntry + 1);
|
|
4775
|
+
}
|
|
4776
|
+
return { content, entryCount: entries.length };
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
// src/tools/builtin/save-memory.ts
|
|
4780
|
+
function getConfigDir() {
|
|
4781
|
+
return join7(homedir3(), CONFIG_DIR_NAME);
|
|
4782
|
+
}
|
|
4783
|
+
function parseScope(value) {
|
|
4784
|
+
return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
|
|
4785
|
+
}
|
|
4786
|
+
function parseSensitivity(value) {
|
|
4787
|
+
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
4584
4788
|
}
|
|
4585
4789
|
var saveMemoryTool = {
|
|
4586
4790
|
definition: {
|
|
4587
4791
|
name: "save_memory",
|
|
4588
|
-
description: "Save important information to persistent memory
|
|
4792
|
+
description: "Save important information to governed persistent memory. Low-risk memories are approved automatically; sensitive entries are stored as pending and require /memory approve <id>. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
|
|
4589
4793
|
parameters: {
|
|
4590
4794
|
content: {
|
|
4591
4795
|
type: "string",
|
|
4592
4796
|
description: "The information to save to persistent memory. Keep it concise and actionable.",
|
|
4593
4797
|
required: true
|
|
4798
|
+
},
|
|
4799
|
+
scope: {
|
|
4800
|
+
type: "string",
|
|
4801
|
+
description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
|
|
4802
|
+
required: false
|
|
4803
|
+
},
|
|
4804
|
+
sensitivity: {
|
|
4805
|
+
type: "string",
|
|
4806
|
+
description: "Sensitivity: low, medium, or high. Low-risk entries may be auto-approved; medium/high remain pending unless explicitly approved.",
|
|
4807
|
+
required: false
|
|
4808
|
+
},
|
|
4809
|
+
expiresAt: {
|
|
4810
|
+
type: "string",
|
|
4811
|
+
description: "Optional ISO timestamp after which this memory should no longer be injected.",
|
|
4812
|
+
required: false
|
|
4594
4813
|
}
|
|
4595
4814
|
},
|
|
4596
4815
|
dangerous: false
|
|
@@ -4598,20 +4817,16 @@ var saveMemoryTool = {
|
|
|
4598
4817
|
async execute(args) {
|
|
4599
4818
|
const content = String(args["content"] ?? "").trim();
|
|
4600
4819
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
4601
|
-
const
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
${
|
|
4610
|
-
`;
|
|
4611
|
-
const previous = existsSync11(memoryPath) ? readFileSync8(memoryPath, "utf-8") : "";
|
|
4612
|
-
atomicWriteFileSync(memoryPath, previous + entry);
|
|
4613
|
-
const byteSize = statSync6(memoryPath).size;
|
|
4614
|
-
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
4820
|
+
const entry = addMemoryEntry(getConfigDir(), content, {
|
|
4821
|
+
scope: parseScope(args["scope"]) ?? "personal",
|
|
4822
|
+
sensitivity: parseSensitivity(args["sensitivity"]),
|
|
4823
|
+
expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
|
|
4824
|
+
cwd: process.cwd(),
|
|
4825
|
+
redact: true
|
|
4826
|
+
});
|
|
4827
|
+
const status = entry.approved ? "approved and active" : "pending approval";
|
|
4828
|
+
const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
|
|
4829
|
+
return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
|
|
4615
4830
|
}
|
|
4616
4831
|
};
|
|
4617
4832
|
|
|
@@ -4655,7 +4870,7 @@ function promptUser(rl, question) {
|
|
|
4655
4870
|
console.log();
|
|
4656
4871
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
4657
4872
|
process.stdout.write(chalk4.cyan("> "));
|
|
4658
|
-
return new Promise((
|
|
4873
|
+
return new Promise((resolve8) => {
|
|
4659
4874
|
let completed = false;
|
|
4660
4875
|
const cleanup = (answer) => {
|
|
4661
4876
|
if (completed) return;
|
|
@@ -4665,7 +4880,7 @@ function promptUser(rl, question) {
|
|
|
4665
4880
|
rl.pause();
|
|
4666
4881
|
rlAny.output = savedOutput;
|
|
4667
4882
|
askUserContext.prompting = false;
|
|
4668
|
-
|
|
4883
|
+
resolve8(answer);
|
|
4669
4884
|
};
|
|
4670
4885
|
const onLine = (line) => {
|
|
4671
4886
|
cleanup(line);
|
|
@@ -4898,7 +5113,7 @@ function formatResults2(query, data, _requested) {
|
|
|
4898
5113
|
|
|
4899
5114
|
// src/agents/agent-config.ts
|
|
4900
5115
|
import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
4901
|
-
import { join as
|
|
5116
|
+
import { join as join8 } from "path";
|
|
4902
5117
|
import { homedir as homedir4 } from "os";
|
|
4903
5118
|
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
4904
5119
|
"read_file",
|
|
@@ -4997,7 +5212,7 @@ function loadAgentDir(dir, source) {
|
|
|
4997
5212
|
const out = [];
|
|
4998
5213
|
for (const file of readdirSync6(dir)) {
|
|
4999
5214
|
if (!file.endsWith(".json")) continue;
|
|
5000
|
-
const path3 =
|
|
5215
|
+
const path3 = join8(dir, file);
|
|
5001
5216
|
try {
|
|
5002
5217
|
const parsed = JSON.parse(readFileSync9(path3, "utf-8"));
|
|
5003
5218
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
@@ -5009,8 +5224,8 @@ function loadAgentDir(dir, source) {
|
|
|
5009
5224
|
}
|
|
5010
5225
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
5011
5226
|
return {
|
|
5012
|
-
user:
|
|
5013
|
-
project:
|
|
5227
|
+
user: join8(configDir ?? join8(homedir4(), CONFIG_DIR_NAME), "agents"),
|
|
5228
|
+
project: join8(cwd, CONFIG_DIR_NAME, "agents")
|
|
5014
5229
|
};
|
|
5015
5230
|
}
|
|
5016
5231
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -5052,7 +5267,7 @@ var nextId = 1;
|
|
|
5052
5267
|
var preferredAgentName = "worker";
|
|
5053
5268
|
var runs = [];
|
|
5054
5269
|
var MAX_RUNS = 50;
|
|
5055
|
-
function
|
|
5270
|
+
function nowIso2() {
|
|
5056
5271
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5057
5272
|
}
|
|
5058
5273
|
function startAgentRun(input) {
|
|
@@ -5061,7 +5276,7 @@ function startAgentRun(input) {
|
|
|
5061
5276
|
agentName: input.agentName,
|
|
5062
5277
|
task: input.task,
|
|
5063
5278
|
status: "running",
|
|
5064
|
-
startedAt:
|
|
5279
|
+
startedAt: nowIso2(),
|
|
5065
5280
|
toolNames: [],
|
|
5066
5281
|
agentIndex: input.agentIndex,
|
|
5067
5282
|
stopRequested: false
|
|
@@ -5079,7 +5294,7 @@ function finishAgentRun(runId, status, summary, error) {
|
|
|
5079
5294
|
const run = runs.find((r) => r.id === runId);
|
|
5080
5295
|
if (!run) return;
|
|
5081
5296
|
run.status = status;
|
|
5082
|
-
run.finishedAt =
|
|
5297
|
+
run.finishedAt = nowIso2();
|
|
5083
5298
|
if (summary) run.summary = summary;
|
|
5084
5299
|
if (error) run.error = error;
|
|
5085
5300
|
}
|
|
@@ -5505,7 +5720,7 @@ var spawnAgentTool = {
|
|
|
5505
5720
|
|
|
5506
5721
|
// src/tools/builtin/task-manager.ts
|
|
5507
5722
|
import { spawn as spawn3 } from "child_process";
|
|
5508
|
-
import { randomUUID } from "crypto";
|
|
5723
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
5509
5724
|
import { platform as platform4 } from "os";
|
|
5510
5725
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
5511
5726
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -5538,7 +5753,7 @@ function createTask(command, description) {
|
|
|
5538
5753
|
}
|
|
5539
5754
|
}
|
|
5540
5755
|
}
|
|
5541
|
-
const id =
|
|
5756
|
+
const id = randomUUID2().slice(0, 8);
|
|
5542
5757
|
const { shell, args } = shellInvocation(command);
|
|
5543
5758
|
const proc = spawn3(shell, args, {
|
|
5544
5759
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5721,13 +5936,13 @@ var taskStopTool = {
|
|
|
5721
5936
|
// src/tools/builtin/git-tools.ts
|
|
5722
5937
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5723
5938
|
import { existsSync as existsSync13 } from "fs";
|
|
5724
|
-
import { join as
|
|
5939
|
+
import { join as join9 } from "path";
|
|
5725
5940
|
function assertGitRepo(cwd) {
|
|
5726
5941
|
let dir = cwd;
|
|
5727
5942
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
5728
5943
|
while (dir && dir !== root) {
|
|
5729
|
-
if (existsSync13(
|
|
5730
|
-
const parent =
|
|
5944
|
+
if (existsSync13(join9(dir, ".git"))) return;
|
|
5945
|
+
const parent = join9(dir, "..");
|
|
5731
5946
|
if (parent === dir) break;
|
|
5732
5947
|
dir = parent;
|
|
5733
5948
|
}
|
|
@@ -5994,7 +6209,7 @@ ${commitOutput.trim()}`;
|
|
|
5994
6209
|
// src/tools/builtin/notebook-edit.ts
|
|
5995
6210
|
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
5996
6211
|
import { writeFile } from "fs/promises";
|
|
5997
|
-
import { resolve as
|
|
6212
|
+
import { resolve as resolve7, extname as extname2 } from "path";
|
|
5998
6213
|
var notebookEditTool = {
|
|
5999
6214
|
definition: {
|
|
6000
6215
|
name: "notebook_edit",
|
|
@@ -6043,7 +6258,7 @@ var notebookEditTool = {
|
|
|
6043
6258
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
6044
6259
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
6045
6260
|
}
|
|
6046
|
-
const absPath =
|
|
6261
|
+
const absPath = resolve7(filePath);
|
|
6047
6262
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
6048
6263
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
6049
6264
|
}
|
|
@@ -6468,7 +6683,7 @@ function estimateToolDefinitionTokens(def) {
|
|
|
6468
6683
|
// src/tools/registry.ts
|
|
6469
6684
|
import { pathToFileURL } from "url";
|
|
6470
6685
|
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync7 } from "fs";
|
|
6471
|
-
import { join as
|
|
6686
|
+
import { join as join10 } from "path";
|
|
6472
6687
|
var ToolRegistry = class {
|
|
6473
6688
|
tools = /* @__PURE__ */ new Map();
|
|
6474
6689
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -6655,12 +6870,12 @@ var ToolRegistry = class {
|
|
|
6655
6870
|
process.stderr.write(
|
|
6656
6871
|
`
|
|
6657
6872
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
6658
|
-
` + files.map((f) => ` + ${
|
|
6873
|
+
` + files.map((f) => ` + ${join10(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
6659
6874
|
);
|
|
6660
6875
|
let loaded = 0;
|
|
6661
6876
|
for (const file of files) {
|
|
6662
6877
|
try {
|
|
6663
|
-
const fileUrl = pathToFileURL(
|
|
6878
|
+
const fileUrl = pathToFileURL(join10(pluginsDir, file)).href;
|
|
6664
6879
|
const mod = await import(fileUrl);
|
|
6665
6880
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
6666
6881
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -6702,6 +6917,16 @@ export {
|
|
|
6702
6917
|
getRecentlyDeniedAutoActions,
|
|
6703
6918
|
clearRecentlyDeniedAutoActions,
|
|
6704
6919
|
defaultActionClassifier,
|
|
6920
|
+
memoryStorePath,
|
|
6921
|
+
memoryMarkdownPath,
|
|
6922
|
+
isMemoryExpired,
|
|
6923
|
+
listMemoryEntries,
|
|
6924
|
+
addMemoryEntry,
|
|
6925
|
+
updateMemoryApproval,
|
|
6926
|
+
deleteMemoryEntry,
|
|
6927
|
+
expireMemoryEntry,
|
|
6928
|
+
exportMemoryEntries,
|
|
6929
|
+
formatMemoryForPrompt,
|
|
6705
6930
|
isInterrupted,
|
|
6706
6931
|
requestInterrupt,
|
|
6707
6932
|
resetInterrupt,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
truncateForPersist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MGBMNCHG.js";
|
|
5
5
|
import {
|
|
6
6
|
APP_NAME,
|
|
7
7
|
CONFIG_DIR_NAME,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
MCP_PROTOCOL_VERSION,
|
|
14
14
|
MCP_TOOL_PREFIX,
|
|
15
15
|
VERSION
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XJGEQIYS.js";
|
|
17
17
|
import {
|
|
18
18
|
atomicWriteFileSync
|
|
19
19
|
} from "./chunk-IW3Q7AE5.js";
|
|
@@ -6,7 +6,7 @@ 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.217";
|
|
10
10
|
var APP_NAME = "ai-cli";
|
|
11
11
|
var CONFIG_DIR_NAME = ".aicli";
|
|
12
12
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -17,6 +17,7 @@ var CUSTOM_COMMANDS_DIR_NAME = "commands";
|
|
|
17
17
|
var CONTEXT_FILE_CANDIDATES = ["AICLI.override.md", "AGENTS.override.md", "AICLI.md", "CLAUDE.md", "AGENTS.md"];
|
|
18
18
|
var CONTEXT_FILE_MAX_BYTES = 32 * 1024;
|
|
19
19
|
var MEMORY_FILE_NAME = "memory.md";
|
|
20
|
+
var MEMORY_STORE_FILE_NAME = "memory.jsonl";
|
|
20
21
|
var MEMORY_MAX_CHARS = 1e4;
|
|
21
22
|
var DEV_STATE_FILE_NAME = "dev-state.md";
|
|
22
23
|
var DEFAULT_MAX_TOKENS = 8192;
|
|
@@ -552,6 +553,7 @@ export {
|
|
|
552
553
|
CONTEXT_FILE_CANDIDATES,
|
|
553
554
|
CONTEXT_FILE_MAX_BYTES,
|
|
554
555
|
MEMORY_FILE_NAME,
|
|
556
|
+
MEMORY_STORE_FILE_NAME,
|
|
555
557
|
MEMORY_MAX_CHARS,
|
|
556
558
|
DEV_STATE_FILE_NAME,
|
|
557
559
|
DEFAULT_MAX_TOKENS,
|