jinzd-ai-cli 0.4.70 → 0.4.72
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/dist/{chunk-3YT2DUUT.js → chunk-57HHY5ZX.js} +2 -2
- package/dist/{chunk-ND3O5NQU.js → chunk-73UI5AH7.js} +1 -1
- package/dist/{chunk-YJKPARSH.js → chunk-OKNKH5D7.js} +1 -1
- package/dist/{chunk-IVTWWDWZ.js → chunk-XH65H3BT.js} +155 -4
- package/dist/{hub-BGO4X72R.js → hub-NQADIYTS.js} +1 -1
- package/dist/index.js +6 -6
- package/dist/{run-tests-NPRCZYN3.js → run-tests-4565WOUK.js} +1 -1
- package/dist/{run-tests-RFEETSJP.js → run-tests-X2P5BOWV.js} +1 -1
- package/dist/{server-WYL3OD5N.js → server-Y4CT5IJJ.js} +24 -4
- package/dist/{task-orchestrator-EB2XPY5S.js → task-orchestrator-R33SWTHO.js} +2 -2
- package/dist/web/client/app.js +114 -2
- package/dist/web/client/index.html +14 -0
- package/dist/web/client/style.css +58 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RateLimitError,
|
|
9
9
|
schemaToJsonSchema,
|
|
10
10
|
truncateForPersist
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XH65H3BT.js";
|
|
12
12
|
import {
|
|
13
13
|
APP_NAME,
|
|
14
14
|
CONFIG_DIR_NAME,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
MCP_TOOL_PREFIX,
|
|
22
22
|
PLUGINS_DIR_NAME,
|
|
23
23
|
VERSION
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-73UI5AH7.js";
|
|
25
25
|
|
|
26
26
|
// src/config/config-manager.ts
|
|
27
27
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
11
11
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
12
12
|
runTestsTool
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-73UI5AH7.js";
|
|
14
14
|
|
|
15
15
|
// src/tools/builtin/bash.ts
|
|
16
16
|
import { execSync } from "child_process";
|
|
@@ -1783,6 +1783,118 @@ Important: For long content (over 500 lines or 3000 chars), you MUST split into
|
|
|
1783
1783
|
|
|
1784
1784
|
// src/tools/builtin/edit-file.ts
|
|
1785
1785
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
1786
|
+
|
|
1787
|
+
// src/tools/builtin/patch-apply.ts
|
|
1788
|
+
function parseUnifiedDiff(patch) {
|
|
1789
|
+
const lines = patch.split("\n");
|
|
1790
|
+
const hunks = [];
|
|
1791
|
+
let current = null;
|
|
1792
|
+
const headerRe = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/;
|
|
1793
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1794
|
+
const line = lines[i];
|
|
1795
|
+
const m = line.match(headerRe);
|
|
1796
|
+
if (m) {
|
|
1797
|
+
if (current) hunks.push(current);
|
|
1798
|
+
current = {
|
|
1799
|
+
oldStart: parseInt(m[1], 10) || 1,
|
|
1800
|
+
oldLines: [],
|
|
1801
|
+
newLines: [],
|
|
1802
|
+
header: line
|
|
1803
|
+
};
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (!current) continue;
|
|
1807
|
+
if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) {
|
|
1808
|
+
hunks.push(current);
|
|
1809
|
+
current = null;
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (line.startsWith("\\")) continue;
|
|
1813
|
+
const tag = line[0];
|
|
1814
|
+
const body = line.slice(1);
|
|
1815
|
+
if (tag === " " || tag === void 0) {
|
|
1816
|
+
current.oldLines.push(body ?? "");
|
|
1817
|
+
current.newLines.push(body ?? "");
|
|
1818
|
+
} else if (tag === "-") {
|
|
1819
|
+
current.oldLines.push(body);
|
|
1820
|
+
} else if (tag === "+") {
|
|
1821
|
+
current.newLines.push(body);
|
|
1822
|
+
} else {
|
|
1823
|
+
current.oldLines.push(line);
|
|
1824
|
+
current.newLines.push(line);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
if (current) hunks.push(current);
|
|
1828
|
+
return hunks;
|
|
1829
|
+
}
|
|
1830
|
+
function locate(haystack, needle, hintLine1Based, searchWindow = 200) {
|
|
1831
|
+
if (needle.length === 0) return { index: Math.max(0, hintLine1Based - 1), mode: "exact" };
|
|
1832
|
+
const hint = Math.max(0, hintLine1Based - 1);
|
|
1833
|
+
const matchExactAt = (start) => {
|
|
1834
|
+
if (start < 0 || start + needle.length > haystack.length) return false;
|
|
1835
|
+
for (let j = 0; j < needle.length; j++) {
|
|
1836
|
+
if (haystack[start + j] !== needle[j]) return false;
|
|
1837
|
+
}
|
|
1838
|
+
return true;
|
|
1839
|
+
};
|
|
1840
|
+
const matchWsAt = (start) => {
|
|
1841
|
+
if (start < 0 || start + needle.length > haystack.length) return false;
|
|
1842
|
+
for (let j = 0; j < needle.length; j++) {
|
|
1843
|
+
if ((haystack[start + j] ?? "").trim() !== needle[j].trim()) return false;
|
|
1844
|
+
}
|
|
1845
|
+
return true;
|
|
1846
|
+
};
|
|
1847
|
+
if (matchExactAt(hint)) return { index: hint, mode: "exact" };
|
|
1848
|
+
for (let d = 1; d <= searchWindow; d++) {
|
|
1849
|
+
if (matchExactAt(hint - d)) return { index: hint - d, mode: "fuzzy" };
|
|
1850
|
+
if (matchExactAt(hint + d)) return { index: hint + d, mode: "fuzzy" };
|
|
1851
|
+
}
|
|
1852
|
+
for (let d = 0; d <= searchWindow; d++) {
|
|
1853
|
+
if (matchWsAt(hint - d)) return { index: hint - d, mode: "ws" };
|
|
1854
|
+
if (d > 0 && matchWsAt(hint + d)) return { index: hint + d, mode: "ws" };
|
|
1855
|
+
}
|
|
1856
|
+
for (let i = 0; i <= haystack.length - needle.length; i++) {
|
|
1857
|
+
if (matchWsAt(i)) return { index: i, mode: "ws-global" };
|
|
1858
|
+
}
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
function applyUnifiedPatch(content, hunks, options = {}) {
|
|
1862
|
+
const stopOnError = options.stopOnError !== false;
|
|
1863
|
+
const lines = content.split("\n");
|
|
1864
|
+
let working = lines.slice();
|
|
1865
|
+
let offset = 0;
|
|
1866
|
+
const reports = [];
|
|
1867
|
+
let appliedCount = 0;
|
|
1868
|
+
for (let i = 0; i < hunks.length; i++) {
|
|
1869
|
+
const h = hunks[i];
|
|
1870
|
+
const hint = h.oldStart + offset;
|
|
1871
|
+
const loc = locate(working, h.oldLines, hint);
|
|
1872
|
+
if (!loc) {
|
|
1873
|
+
reports.push({
|
|
1874
|
+
index: i,
|
|
1875
|
+
ok: false,
|
|
1876
|
+
detail: `Hunk @${h.header} \u2014 context not found near line ${h.oldStart} (${h.oldLines.length} old lines).`
|
|
1877
|
+
});
|
|
1878
|
+
if (stopOnError) {
|
|
1879
|
+
return { ok: false, content: void 0, reports, appliedCount };
|
|
1880
|
+
}
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
const before = working.slice(0, loc.index);
|
|
1884
|
+
const after = working.slice(loc.index + h.oldLines.length);
|
|
1885
|
+
working = before.concat(h.newLines, after);
|
|
1886
|
+
offset += h.newLines.length - h.oldLines.length;
|
|
1887
|
+
appliedCount++;
|
|
1888
|
+
reports.push({
|
|
1889
|
+
index: i,
|
|
1890
|
+
ok: true,
|
|
1891
|
+
detail: `Applied at line ${loc.index + 1} (${loc.mode}${loc.index + 1 === hint ? "" : `, drift ${loc.index + 1 - hint}`}): -${h.oldLines.length} +${h.newLines.length}`
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
return { ok: true, content: working.join("\n"), reports, appliedCount };
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// src/tools/builtin/edit-file.ts
|
|
1786
1898
|
function similarityScore(a, b) {
|
|
1787
1899
|
if (a === b) return 1;
|
|
1788
1900
|
if (a.length < 2 || b.length < 2) return 0;
|
|
@@ -1948,11 +2060,12 @@ function parseEditsArg(raw) {
|
|
|
1948
2060
|
var editFileTool = {
|
|
1949
2061
|
definition: {
|
|
1950
2062
|
name: "edit_file",
|
|
1951
|
-
description: `Precisely edit file contents.
|
|
2063
|
+
description: `Precisely edit file contents. Five modes:
|
|
1952
2064
|
1. String replace (most common): Provide old_str and new_str to replace an exact match. old_str must appear exactly once (unless replace_all is true).
|
|
1953
2065
|
2. Line insert: Provide insert_after_line (1-based) and insert_content.
|
|
1954
2066
|
3. Line delete: Provide delete_from_line and delete_to_line (inclusive).
|
|
1955
2067
|
4. Batch edits: Provide edits=[{old_str, new_str, ignore_whitespace?, replace_all?}, ...] to apply MULTIPLE edits in ONE call \u2014 saves tool rounds/tokens when refactoring a file. Edits are applied sequentially in-memory; by default any failure rolls back ALL edits (set stop_on_error=false to apply successful ones and report failures).
|
|
2068
|
+
5. Unified diff patch: Provide patch=<unified-diff string> with one or more '@@ -a,b +c,d @@' hunks. Use this for MANY scattered small changes in a LARGE file \u2014 most compact format (context lines are shared between old/new). Supports line-number drift (\xB1200 lines) and whitespace-tolerant fallback. Format each hunk as standard unified diff: ' ' context / '-' remove / '+' add. File headers (---/+++) are optional and ignored.
|
|
1956
2069
|
Optional ignore_whitespace: true ignores indentation differences during matching.
|
|
1957
2070
|
Optional replace_all: true replaces ALL occurrences of old_str.
|
|
1958
2071
|
Note: Path can be absolute or relative to cwd.`,
|
|
@@ -2009,7 +2122,12 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2009
2122
|
},
|
|
2010
2123
|
stop_on_error: {
|
|
2011
2124
|
type: "boolean",
|
|
2012
|
-
description: "[Batch mode] If true (default), any failing edit rolls back the whole
|
|
2125
|
+
description: "[Batch mode / Patch mode] If true (default), any failing edit/hunk rolls back the whole operation and writes nothing. If false, successful edits are written and failed ones are reported.",
|
|
2126
|
+
required: false
|
|
2127
|
+
},
|
|
2128
|
+
patch: {
|
|
2129
|
+
type: "string",
|
|
2130
|
+
description: '[Patch mode] A unified-diff string with one or more "@@ -oldStart,oldLines +newStart,newLines @@" hunks. Most compact for many scattered small changes. Tolerates line-number drift (\xB1200 lines) and whitespace differences.',
|
|
2013
2131
|
required: false
|
|
2014
2132
|
},
|
|
2015
2133
|
encoding: {
|
|
@@ -2027,6 +2145,39 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2027
2145
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
2028
2146
|
if (!existsSync5(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
2029
2147
|
const original = readFileSync4(filePath, encoding);
|
|
2148
|
+
if (args["patch"] !== void 0) {
|
|
2149
|
+
const patchText = String(args["patch"] ?? "");
|
|
2150
|
+
const stopOnError = args["stop_on_error"] !== false;
|
|
2151
|
+
if (!patchText.trim()) {
|
|
2152
|
+
throw new ToolError("edit_file", "patch is empty");
|
|
2153
|
+
}
|
|
2154
|
+
const hunks = parseUnifiedDiff(patchText);
|
|
2155
|
+
if (hunks.length === 0) {
|
|
2156
|
+
throw new ToolError(
|
|
2157
|
+
"edit_file",
|
|
2158
|
+
'patch contained no hunks. Expected unified diff format with "@@ -a,b +c,d @@" headers.'
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
const res = applyUnifiedPatch(original, hunks, { stopOnError });
|
|
2162
|
+
const lines = [];
|
|
2163
|
+
if (!res.ok) {
|
|
2164
|
+
lines.push(`ERROR: Patch aborted \u2014 ${res.appliedCount}/${hunks.length} hunks applied, then a hunk failed. No changes written (stop_on_error=true).`);
|
|
2165
|
+
} else if (res.appliedCount < hunks.length) {
|
|
2166
|
+
lines.push(`Partial success: ${res.appliedCount}/${hunks.length} hunks applied to ${filePath} (stop_on_error=false).`);
|
|
2167
|
+
} else {
|
|
2168
|
+
lines.push(`Successfully applied ${res.appliedCount}/${hunks.length} hunk(s) to ${filePath}.`);
|
|
2169
|
+
}
|
|
2170
|
+
lines.push("");
|
|
2171
|
+
for (const r of res.reports) {
|
|
2172
|
+
lines.push(r.ok ? ` \u2713 #${r.index + 1}: ${r.detail}` : ` \u2717 #${r.index + 1}: ${r.detail}`);
|
|
2173
|
+
}
|
|
2174
|
+
if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
|
|
2175
|
+
undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
|
|
2176
|
+
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2177
|
+
writeFileSync3(filePath, res.content, encoding);
|
|
2178
|
+
}
|
|
2179
|
+
return lines.join("\n");
|
|
2180
|
+
}
|
|
2030
2181
|
if (args["edits"] !== void 0) {
|
|
2031
2182
|
const edits = parseEditsArg(args["edits"]);
|
|
2032
2183
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -2140,7 +2291,7 @@ Please read the file first and use exact text.`;
|
|
|
2140
2291
|
}
|
|
2141
2292
|
throw new ToolError(
|
|
2142
2293
|
"edit_file",
|
|
2143
|
-
"No operation specified. Provide one of: (old_str + new_str), (insert_after_line + insert_content), (delete_from_line + delete_to_line),
|
|
2294
|
+
"No operation specified. Provide one of: (old_str + new_str), (insert_after_line + insert_content), (delete_from_line + delete_to_line), edits=[...], or patch=<unified diff>."
|
|
2144
2295
|
);
|
|
2145
2296
|
}
|
|
2146
2297
|
};
|
|
@@ -385,7 +385,7 @@ ${content}`);
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
388
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
388
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-R33SWTHO.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
saveDevState,
|
|
32
32
|
sessionHasMeaningfulContent,
|
|
33
33
|
setupProxy
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-57HHY5ZX.js";
|
|
35
35
|
import {
|
|
36
36
|
ToolExecutor,
|
|
37
37
|
ToolRegistry,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
spawnAgentContext,
|
|
48
48
|
theme,
|
|
49
49
|
undoStack
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-XH65H3BT.js";
|
|
51
51
|
import {
|
|
52
52
|
fileCheckpoints
|
|
53
53
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
SKILLS_DIR_NAME,
|
|
73
73
|
VERSION,
|
|
74
74
|
buildUserIdentityPrompt
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-73UI5AH7.js";
|
|
76
76
|
|
|
77
77
|
// src/index.ts
|
|
78
78
|
import { program } from "commander";
|
|
@@ -2267,7 +2267,7 @@ ${hint}` : "")
|
|
|
2267
2267
|
usage: "/test [command|filter]",
|
|
2268
2268
|
async execute(args, ctx) {
|
|
2269
2269
|
try {
|
|
2270
|
-
const { executeTests } = await import("./run-tests-
|
|
2270
|
+
const { executeTests } = await import("./run-tests-4565WOUK.js");
|
|
2271
2271
|
const argStr = args.join(" ").trim();
|
|
2272
2272
|
let testArgs = {};
|
|
2273
2273
|
if (argStr) {
|
|
@@ -6139,7 +6139,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
6139
6139
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
6140
6140
|
process.exit(1);
|
|
6141
6141
|
}
|
|
6142
|
-
const { startWebServer } = await import("./server-
|
|
6142
|
+
const { startWebServer } = await import("./server-Y4CT5IJJ.js");
|
|
6143
6143
|
await startWebServer({ port, host: options.host });
|
|
6144
6144
|
});
|
|
6145
6145
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -6372,7 +6372,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
6372
6372
|
}),
|
|
6373
6373
|
config.get("customProviders")
|
|
6374
6374
|
);
|
|
6375
|
-
const { startHub } = await import("./hub-
|
|
6375
|
+
const { startHub } = await import("./hub-NQADIYTS.js");
|
|
6376
6376
|
await startHub(
|
|
6377
6377
|
{
|
|
6378
6378
|
topic: topic ?? "",
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
persistToolRound,
|
|
22
22
|
rebuildExtraMessages,
|
|
23
23
|
setupProxy
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-57HHY5ZX.js";
|
|
25
25
|
import {
|
|
26
26
|
AuthManager
|
|
27
27
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
spawnAgentContext,
|
|
43
43
|
truncateOutput,
|
|
44
44
|
undoStack
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-XH65H3BT.js";
|
|
46
46
|
import "./chunk-4BKXL7SM.js";
|
|
47
47
|
import {
|
|
48
48
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
SKILLS_DIR_NAME,
|
|
63
63
|
VERSION,
|
|
64
64
|
buildUserIdentityPrompt
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-73UI5AH7.js";
|
|
66
66
|
|
|
67
67
|
// src/web/server.ts
|
|
68
68
|
import express from "express";
|
|
@@ -1948,7 +1948,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1948
1948
|
case "test": {
|
|
1949
1949
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1950
1950
|
try {
|
|
1951
|
-
const { executeTests } = await import("./run-tests-
|
|
1951
|
+
const { executeTests } = await import("./run-tests-4565WOUK.js");
|
|
1952
1952
|
const argStr = args.join(" ").trim();
|
|
1953
1953
|
let testArgs = {};
|
|
1954
1954
|
if (argStr) {
|
|
@@ -3010,6 +3010,26 @@ async function startWebServer(options = {}) {
|
|
|
3010
3010
|
res.json({ sessions: [] });
|
|
3011
3011
|
}
|
|
3012
3012
|
});
|
|
3013
|
+
app.get("/api/sessions/:id/replay", requireAuth, (req, res) => {
|
|
3014
|
+
const id = req.params.id;
|
|
3015
|
+
if (!/^[a-f0-9-]{36}$/i.test(id)) {
|
|
3016
|
+
res.status(400).json({ error: "Invalid session id" });
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
try {
|
|
3020
|
+
const authUser = req._authUser;
|
|
3021
|
+
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
3022
|
+
const filePath = join3(histDir, `${id}.json`);
|
|
3023
|
+
if (!existsSync4(filePath)) {
|
|
3024
|
+
res.status(404).json({ error: "Session not found" });
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const data = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
3028
|
+
res.json({ session: data });
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3013
3033
|
app.get("/api/file-content", requireAuth, (req, res) => {
|
|
3014
3034
|
const filePath = req.query.path;
|
|
3015
3035
|
if (!filePath) {
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XH65H3BT.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import {
|
|
10
10
|
SUBAGENT_ALLOWED_TOOLS
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-73UI5AH7.js";
|
|
12
12
|
|
|
13
13
|
// src/hub/task-orchestrator.ts
|
|
14
14
|
import { createInterface } from "readline";
|
package/dist/web/client/app.js
CHANGED
|
@@ -965,7 +965,7 @@ function renderFilteredSessions(filter) {
|
|
|
965
965
|
<div class="flex items-center gap-1">
|
|
966
966
|
${checkbox}
|
|
967
967
|
<div class="session-title flex-1">${escapeHtml(title)}</div>
|
|
968
|
-
${batchSelectMode ? '' : `<button class="session-delete-btn opacity-0 hover:opacity-100 text-error text-xs px-1 flex-shrink-0" data-delete-id="${s.id}" title="Delete session">×</button>`}
|
|
968
|
+
${batchSelectMode ? '' : `<button class="session-replay-btn opacity-0 hover:opacity-100 text-xs px-1 flex-shrink-0" data-replay-id="${s.id}" title="Replay session">🎬</button><button class="session-delete-btn opacity-0 hover:opacity-100 text-error text-xs px-1 flex-shrink-0" data-delete-id="${s.id}" title="Delete session">×</button>`}
|
|
969
969
|
</div>
|
|
970
970
|
<div class="session-meta">${s.messageCount} msgs · ${timeStr}</div>
|
|
971
971
|
</div>`;
|
|
@@ -976,7 +976,7 @@ function renderFilteredSessions(filter) {
|
|
|
976
976
|
let clickTimer = null;
|
|
977
977
|
|
|
978
978
|
el.addEventListener('click', (e) => {
|
|
979
|
-
if (e.target.closest('.session-delete-btn') || e.target.closest('.session-batch-cb') || e.target.closest('.session-rename-input')) return;
|
|
979
|
+
if (e.target.closest('.session-delete-btn') || e.target.closest('.session-replay-btn') || e.target.closest('.session-batch-cb') || e.target.closest('.session-rename-input')) return;
|
|
980
980
|
if (batchSelectMode) {
|
|
981
981
|
const cb = el.querySelector('.session-batch-cb');
|
|
982
982
|
if (cb) { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }
|
|
@@ -1032,9 +1032,121 @@ function renderFilteredSessions(filter) {
|
|
|
1032
1032
|
}
|
|
1033
1033
|
});
|
|
1034
1034
|
});
|
|
1035
|
+
|
|
1036
|
+
sessionListEl.querySelectorAll('.session-replay-btn').forEach(btn => {
|
|
1037
|
+
btn.addEventListener('click', (e) => {
|
|
1038
|
+
e.stopPropagation();
|
|
1039
|
+
const id = btn.dataset.replayId;
|
|
1040
|
+
if (id) openReplay(id);
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1035
1043
|
}
|
|
1036
1044
|
}
|
|
1037
1045
|
|
|
1046
|
+
// ── Session Replay (B1) ─────────────────────────────────
|
|
1047
|
+
async function openReplay(sessionId) {
|
|
1048
|
+
const modal = document.getElementById('replay-modal');
|
|
1049
|
+
const metaEl = document.getElementById('replay-meta');
|
|
1050
|
+
const usageEl = document.getElementById('replay-usage');
|
|
1051
|
+
const timelineEl = document.getElementById('replay-timeline');
|
|
1052
|
+
if (!modal || !metaEl || !usageEl || !timelineEl) return;
|
|
1053
|
+
|
|
1054
|
+
metaEl.textContent = 'Loading…';
|
|
1055
|
+
usageEl.textContent = '';
|
|
1056
|
+
timelineEl.innerHTML = '';
|
|
1057
|
+
modal.showModal();
|
|
1058
|
+
|
|
1059
|
+
try {
|
|
1060
|
+
const headers = {};
|
|
1061
|
+
if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
|
|
1062
|
+
const resp = await fetch('/api/sessions/' + encodeURIComponent(sessionId) + '/replay', { headers });
|
|
1063
|
+
if (!resp.ok) {
|
|
1064
|
+
const body = await resp.json().catch(() => ({}));
|
|
1065
|
+
metaEl.textContent = 'Failed: ' + (body.error || resp.status);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const { session } = await resp.json();
|
|
1069
|
+
renderReplay(session, metaEl, usageEl, timelineEl);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
metaEl.textContent = 'Failed to load: ' + (err && err.message ? err.message : err);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function renderReplay(session, metaEl, usageEl, timelineEl) {
|
|
1076
|
+
const created = session.created ? new Date(session.created).toLocaleString() : '-';
|
|
1077
|
+
const updated = session.updated ? new Date(session.updated).toLocaleString() : '-';
|
|
1078
|
+
const title = session.title || 'Untitled';
|
|
1079
|
+
metaEl.innerHTML =
|
|
1080
|
+
`<div><b>${escapeHtml(title)}</b></div>` +
|
|
1081
|
+
`<div>${escapeHtml(session.provider || '?')} / ${escapeHtml(session.model || '?')} · ${escapeHtml(session.id || '')}</div>` +
|
|
1082
|
+
`<div>Created ${created} · Updated ${updated} · ${(session.messages || []).length} messages</div>`;
|
|
1083
|
+
|
|
1084
|
+
const tu = session.tokenUsage || {};
|
|
1085
|
+
const total = (tu.inputTokens || 0) + (tu.outputTokens || 0);
|
|
1086
|
+
usageEl.innerHTML =
|
|
1087
|
+
`<span class="badge badge-sm badge-ghost">total ${total}</span> ` +
|
|
1088
|
+
`<span class="badge badge-sm badge-ghost">in ${tu.inputTokens || 0}</span> ` +
|
|
1089
|
+
`<span class="badge badge-sm badge-ghost">out ${tu.outputTokens || 0}</span> ` +
|
|
1090
|
+
`<span class="badge badge-sm badge-ghost">cache-write ${tu.cacheCreationTokens || 0}</span> ` +
|
|
1091
|
+
`<span class="badge badge-sm badge-ghost">cache-read ${tu.cacheReadTokens || 0}</span>`;
|
|
1092
|
+
|
|
1093
|
+
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
1094
|
+
timelineEl.innerHTML = messages.map((m, i) => renderReplayStep(m, i)).join('');
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function renderReplayStep(m, idx) {
|
|
1098
|
+
const role = m.role || 'user';
|
|
1099
|
+
const ts = m.timestamp ? new Date(m.timestamp).toLocaleTimeString() : '';
|
|
1100
|
+
const isError = !!m.isError;
|
|
1101
|
+
const cls = 'replay-step role-' + role + (isError ? ' error' : '');
|
|
1102
|
+
const roleTag = role + (m.toolName ? ' · ' + m.toolName : '') + (isError ? ' · ERROR' : '');
|
|
1103
|
+
|
|
1104
|
+
// Extract text content
|
|
1105
|
+
let text = '';
|
|
1106
|
+
if (typeof m.content === 'string') {
|
|
1107
|
+
text = m.content;
|
|
1108
|
+
} else if (Array.isArray(m.content)) {
|
|
1109
|
+
text = m.content
|
|
1110
|
+
.map(p => p && p.type === 'text' ? (p.text || '') : (p && p.type === 'image_url' ? '[image]' : ''))
|
|
1111
|
+
.join('');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
let body = '';
|
|
1115
|
+
if (text) {
|
|
1116
|
+
const isTool = role === 'tool';
|
|
1117
|
+
const bodyCls = isTool ? 'replay-step-body' : 'replay-step-body text-body';
|
|
1118
|
+
body += `<div class="${bodyCls}">${escapeHtml(text)}</div>`;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Tool calls (assistant asking to invoke tools)
|
|
1122
|
+
if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
|
|
1123
|
+
body += m.toolCalls.map(tc => {
|
|
1124
|
+
const name = tc.name || tc.function?.name || '(unknown)';
|
|
1125
|
+
let args = tc.arguments ?? tc.function?.arguments ?? {};
|
|
1126
|
+
if (typeof args === 'string') {
|
|
1127
|
+
try { args = JSON.parse(args); } catch { /* keep as string */ }
|
|
1128
|
+
}
|
|
1129
|
+
const argsStr = typeof args === 'string' ? args : JSON.stringify(args, null, 2);
|
|
1130
|
+
return `<div class="replay-tool-block"><div><span class="tool-name">→ ${escapeHtml(name)}</span> <span class="opacity-60">${escapeHtml(tc.id || '')}</span></div><pre>${escapeHtml(argsStr)}</pre></div>`;
|
|
1131
|
+
}).join('');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Reasoning content (thinking models)
|
|
1135
|
+
if (m.reasoningContent) {
|
|
1136
|
+
body += `<details class="replay-tool-block"><summary class="opacity-70">💭 reasoning</summary><pre>${escapeHtml(m.reasoningContent)}</pre></details>`;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return `<div class="${cls}">
|
|
1140
|
+
<div class="replay-step-header">
|
|
1141
|
+
<span class="opacity-50">#${idx + 1}</span>
|
|
1142
|
+
<span class="role-tag">${escapeHtml(roleTag)}</span>
|
|
1143
|
+
<span class="opacity-50">${ts}</span>
|
|
1144
|
+
${m.toolCallId ? `<span class="opacity-40">↳ ${escapeHtml(m.toolCallId)}</span>` : ''}
|
|
1145
|
+
</div>
|
|
1146
|
+
${body || '<div class="opacity-40 text-xs">(empty)</div>'}
|
|
1147
|
+
</div>`;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1038
1150
|
function startSessionRename(itemEl, titleEl) {
|
|
1039
1151
|
const sessionId = itemEl.dataset.sessionId;
|
|
1040
1152
|
const currentTitle = titleEl.textContent.trim();
|
|
@@ -249,6 +249,20 @@
|
|
|
249
249
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
250
250
|
</dialog>
|
|
251
251
|
|
|
252
|
+
<!-- ── Session Replay Modal (B1) ───────────────────────── -->
|
|
253
|
+
<dialog id="replay-modal" class="modal">
|
|
254
|
+
<div class="modal-box max-w-5xl bg-base-200 w-11/12">
|
|
255
|
+
<div class="flex items-center justify-between mb-3">
|
|
256
|
+
<h3 class="font-bold text-lg">🎬 Session Replay</h3>
|
|
257
|
+
<form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
|
|
258
|
+
</div>
|
|
259
|
+
<div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
|
|
260
|
+
<div id="replay-usage" class="text-xs mb-3"></div>
|
|
261
|
+
<div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
|
|
262
|
+
</div>
|
|
263
|
+
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
264
|
+
</dialog>
|
|
265
|
+
|
|
252
266
|
<script src="app.js"></script>
|
|
253
267
|
<script>
|
|
254
268
|
if ('serviceWorker' in navigator) {
|
|
@@ -845,3 +845,61 @@ button, a, .session-item, .file-tree-row, .template-item, .tool-item, .mcp-serve
|
|
|
845
845
|
@media (display-mode: standalone) {
|
|
846
846
|
.navbar { padding-top: env(safe-area-inset-top, 0px); }
|
|
847
847
|
}
|
|
848
|
+
|
|
849
|
+
/* ── Session Replay (B1) ───────────────────────────── */
|
|
850
|
+
.replay-step {
|
|
851
|
+
border-left: 3px solid hsl(var(--b3));
|
|
852
|
+
padding: 0.5rem 0.6rem;
|
|
853
|
+
background: hsl(var(--b1));
|
|
854
|
+
border-radius: 0 0.35rem 0.35rem 0;
|
|
855
|
+
font-size: 0.85rem;
|
|
856
|
+
}
|
|
857
|
+
.replay-step.role-user { border-left-color: #3b82f6; }
|
|
858
|
+
.replay-step.role-assistant { border-left-color: #10b981; }
|
|
859
|
+
.replay-step.role-tool { border-left-color: #f59e0b; }
|
|
860
|
+
.replay-step.role-tool.error { border-left-color: #ef4444; }
|
|
861
|
+
.replay-step-header {
|
|
862
|
+
display: flex;
|
|
863
|
+
gap: 0.5rem;
|
|
864
|
+
align-items: center;
|
|
865
|
+
font-size: 0.72rem;
|
|
866
|
+
opacity: 0.75;
|
|
867
|
+
margin-bottom: 0.25rem;
|
|
868
|
+
}
|
|
869
|
+
.replay-step-header .role-tag {
|
|
870
|
+
font-weight: 600;
|
|
871
|
+
padding: 0 0.35rem;
|
|
872
|
+
border-radius: 0.25rem;
|
|
873
|
+
background: hsl(var(--b3));
|
|
874
|
+
}
|
|
875
|
+
.replay-step-body {
|
|
876
|
+
white-space: pre-wrap;
|
|
877
|
+
word-break: break-word;
|
|
878
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
879
|
+
font-size: 0.78rem;
|
|
880
|
+
max-height: 18rem;
|
|
881
|
+
overflow-y: auto;
|
|
882
|
+
}
|
|
883
|
+
.replay-step-body.text-body {
|
|
884
|
+
font-family: inherit;
|
|
885
|
+
font-size: 0.85rem;
|
|
886
|
+
}
|
|
887
|
+
.replay-tool-block {
|
|
888
|
+
margin-top: 0.3rem;
|
|
889
|
+
padding: 0.4rem;
|
|
890
|
+
background: hsl(var(--b2));
|
|
891
|
+
border-radius: 0.3rem;
|
|
892
|
+
font-size: 0.78rem;
|
|
893
|
+
}
|
|
894
|
+
.replay-tool-block .tool-name {
|
|
895
|
+
font-weight: 600;
|
|
896
|
+
color: #f59e0b;
|
|
897
|
+
}
|
|
898
|
+
.replay-tool-block pre {
|
|
899
|
+
margin: 0.2rem 0 0;
|
|
900
|
+
white-space: pre-wrap;
|
|
901
|
+
word-break: break-word;
|
|
902
|
+
font-size: 0.72rem;
|
|
903
|
+
max-height: 12rem;
|
|
904
|
+
overflow-y: auto;
|
|
905
|
+
}
|