jinzd-ai-cli 0.4.214 → 0.4.215

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-YFHF3PVB.js";
4
+ } from "./chunk-2224JGA6.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-DBYMX2ZC.js";
6
+ import "./chunk-7JES2NWR.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-DBYMX2ZC.js";
11
+ } from "./chunk-7JES2NWR.js";
12
12
  import {
13
13
  atomicWriteFileSync
14
14
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-DBYMX2ZC.js";
4
+ } from "./chunk-7JES2NWR.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-TR6MSPSS.js";
8
+ } from "./chunk-3TSHNZKI.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-YMFGGCUP.js";
11
+ } from "./chunk-X6OXS7KU.js";
12
12
  import {
13
13
  getDangerLevel,
14
14
  isFileWriteTool,
@@ -26,7 +26,7 @@ import {
26
26
  SUBAGENT_ALLOWED_TOOLS,
27
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
28
28
  SUBAGENT_MAX_ROUNDS_LIMIT
29
- } from "./chunk-DBYMX2ZC.js";
29
+ } from "./chunk-7JES2NWR.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.214";
4
+ var VERSION = "0.4.215";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-R5CJD2AB.js";
4
+ } from "./chunk-6C3JYNM6.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-DBYMX2ZC.js";
16
+ } from "./chunk-7JES2NWR.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.214";
9
+ var VERSION = "0.4.215";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-DBYMX2ZC.js";
4
+ } from "./chunk-7JES2NWR.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-DBYMX2ZC.js";
5
+ } from "./chunk-7JES2NWR.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -10,11 +10,11 @@ import {
10
10
  import "./chunk-XPBEJB27.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "./chunk-YFHF3PVB.js";
13
+ } from "./chunk-2224JGA6.js";
14
14
  import "./chunk-TZQHYZKT.js";
15
15
  import {
16
16
  VERSION
17
- } from "./chunk-DBYMX2ZC.js";
17
+ } from "./chunk-7JES2NWR.js";
18
18
  import "./chunk-IW3Q7AE5.js";
19
19
 
20
20
  // src/cli/ci.ts
@@ -37,7 +37,7 @@ import {
37
37
  TEST_TIMEOUT,
38
38
  VERSION,
39
39
  buildUserIdentityPrompt
40
- } from "./chunk-DBYMX2ZC.js";
40
+ } from "./chunk-7JES2NWR.js";
41
41
  export {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
43
43
  APP_NAME,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-JQC7IATA.js";
5
+ } from "./chunk-ZOF5NFKW.js";
6
6
  import {
7
7
  ProviderRegistry
8
8
  } from "./chunk-QMXC327F.js";
@@ -11,17 +11,17 @@ import {
11
11
  getTopFailingTools,
12
12
  getTopUsedTools,
13
13
  resetStats
14
- } from "./chunk-YMFGGCUP.js";
14
+ } from "./chunk-X6OXS7KU.js";
15
15
  import "./chunk-XPBEJB27.js";
16
16
  import {
17
17
  ConfigManager
18
- } from "./chunk-YFHF3PVB.js";
18
+ } from "./chunk-2224JGA6.js";
19
19
  import "./chunk-TZQHYZKT.js";
20
20
  import {
21
21
  DEV_STATE_FILE_NAME,
22
22
  MEMORY_FILE_NAME,
23
23
  VERSION
24
- } from "./chunk-DBYMX2ZC.js";
24
+ } from "./chunk-7JES2NWR.js";
25
25
  import "./chunk-IW3Q7AE5.js";
26
26
 
27
27
  // src/diagnostics/doctor-cli.ts
@@ -37,7 +37,7 @@ import {
37
37
  VERSION,
38
38
  buildUserIdentityPrompt,
39
39
  runTestsTool
40
- } from "./chunk-JOJA35RK.js";
40
+ } from "./chunk-RS4WBI73.js";
41
41
  import {
42
42
  hasSemanticIndex,
43
43
  semanticSearch
@@ -16070,7 +16070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16070
16070
  case "test": {
16071
16071
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16072
16072
  try {
16073
- const { executeTests } = await import("./run-tests-R6FL6CQ3.js");
16073
+ const { executeTests } = await import("./run-tests-J2JQ57R3.js");
16074
16074
  const argStr = args.join(" ").trim();
16075
16075
  let testArgs = {};
16076
16076
  if (argStr) {
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-6AZD5WSR.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-QFOCO3N7.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  scanProject,
16
16
  sessionHasMeaningfulContent,
17
17
  setupProxy
18
- } from "./chunk-5FG7PSVM.js";
18
+ } from "./chunk-KL7UBVSQ.js";
19
19
  import {
20
20
  buildReviewPrompt,
21
21
  buildSecurityReviewPrompt
@@ -47,10 +47,10 @@ import {
47
47
  trustHook,
48
48
  undoStack,
49
49
  untrustHook
50
- } from "./chunk-R5CJD2AB.js";
50
+ } from "./chunk-6C3JYNM6.js";
51
51
  import "./chunk-T2NL5ZIA.js";
52
52
  import "./chunk-BXP6YZ2P.js";
53
- import "./chunk-TR6MSPSS.js";
53
+ import "./chunk-3TSHNZKI.js";
54
54
  import {
55
55
  SessionManager,
56
56
  getContentText
@@ -67,7 +67,7 @@ import {
67
67
  getConfigDirUsage,
68
68
  listRecentCrashes,
69
69
  writeCrashLog
70
- } from "./chunk-JQC7IATA.js";
70
+ } from "./chunk-ZOF5NFKW.js";
71
71
  import {
72
72
  ProviderRegistry
73
73
  } from "./chunk-QMXC327F.js";
@@ -76,7 +76,7 @@ import {
76
76
  getTopFailingTools,
77
77
  getTopUsedTools,
78
78
  installFlushOnExit
79
- } from "./chunk-YMFGGCUP.js";
79
+ } from "./chunk-X6OXS7KU.js";
80
80
  import {
81
81
  CONTENT_ONLY_STREAM_REMINDER,
82
82
  TEE_FINAL_USER_NUDGE,
@@ -98,7 +98,7 @@ import {
98
98
  } from "./chunk-XPBEJB27.js";
99
99
  import {
100
100
  ConfigManager
101
- } from "./chunk-YFHF3PVB.js";
101
+ } from "./chunk-2224JGA6.js";
102
102
  import {
103
103
  AuthError,
104
104
  ProviderError,
@@ -124,7 +124,7 @@ import {
124
124
  SKILLS_DIR_NAME,
125
125
  VERSION,
126
126
  buildUserIdentityPrompt
127
- } from "./chunk-DBYMX2ZC.js";
127
+ } from "./chunk-7JES2NWR.js";
128
128
  import {
129
129
  formatGitContextForPrompt,
130
130
  getGitContext,
@@ -1661,7 +1661,7 @@ No tools match "${filter}".
1661
1661
  const { join: join5 } = await import("path");
1662
1662
  const { existsSync: existsSync5 } = await import("fs");
1663
1663
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1664
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-BKR7T3QH.js");
1664
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-IN2HXJK7.js");
1665
1665
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1666
1666
  const cwd = process.cwd();
1667
1667
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2720,7 +2720,7 @@ ${hint}` : "")
2720
2720
  usage: "/test [command|filter]",
2721
2721
  async execute(args, ctx) {
2722
2722
  try {
2723
- const { executeTests } = await import("./run-tests-F44JA2YW.js");
2723
+ const { executeTests } = await import("./run-tests-QJD43AML.js");
2724
2724
  const argStr = args.join(" ").trim();
2725
2725
  let testArgs = {};
2726
2726
  if (argStr) {
@@ -7207,7 +7207,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7207
7207
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7208
7208
  process.exit(1);
7209
7209
  }
7210
- const { startWebServer } = await import("./server-FXWCSCDZ.js");
7210
+ const { startWebServer } = await import("./server-VGPZOISQ.js");
7211
7211
  await startWebServer({ port, host: options.host });
7212
7212
  });
7213
7213
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7374,16 +7374,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7374
7374
  console.log(footer + "\n");
7375
7375
  });
7376
7376
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7377
- const { runUsageCli } = await import("./usage-W7J2QA3G.js");
7377
+ const { runUsageCli } = await import("./usage-IEB476NE.js");
7378
7378
  await runUsageCli(options);
7379
7379
  });
7380
7380
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7381
- const { runDoctorCli } = await import("./doctor-cli-TGVSWKDB.js");
7381
+ const { runDoctorCli } = await import("./doctor-cli-7XL4TCVT.js");
7382
7382
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7383
7383
  });
7384
7384
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7385
7385
  try {
7386
- const batch = await import("./batch-NPUUDETK.js");
7386
+ const batch = await import("./batch-74H5SA7P.js");
7387
7387
  switch (action) {
7388
7388
  case "submit":
7389
7389
  if (!arg) {
@@ -7426,7 +7426,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7426
7426
  }
7427
7427
  });
7428
7428
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7429
- const { startMcpServer } = await import("./server-GQXC46R5.js");
7429
+ const { startMcpServer } = await import("./server-WDLV3W4F.js");
7430
7430
  await startMcpServer({
7431
7431
  allowDestructive: !!options.allowDestructive,
7432
7432
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7435,7 +7435,7 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7435
7435
  });
7436
7436
  });
7437
7437
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7438
- const { runCi } = await import("./ci-3TU7WCY4.js");
7438
+ const { runCi } = await import("./ci-3ALK2XJN.js");
7439
7439
  const result = await runCi({
7440
7440
  pr: options.pr,
7441
7441
  base: options.base,
@@ -7581,7 +7581,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7581
7581
  }),
7582
7582
  config.get("customProviders")
7583
7583
  );
7584
- const { startHub } = await import("./hub-GURYEW6F.js");
7584
+ const { startHub } = await import("./hub-VAOG5EY6.js");
7585
7585
  await startHub(
7586
7586
  {
7587
7587
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-JOJA35RK.js";
4
+ } from "./chunk-RS4WBI73.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-TR6MSPSS.js";
6
- import "./chunk-DBYMX2ZC.js";
5
+ } from "./chunk-3TSHNZKI.js";
6
+ import "./chunk-7JES2NWR.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -24,7 +24,7 @@ import {
24
24
  scanDirTree,
25
25
  scanProject,
26
26
  setupProxy
27
- } from "./chunk-5FG7PSVM.js";
27
+ } from "./chunk-KL7UBVSQ.js";
28
28
  import {
29
29
  buildReviewPrompt,
30
30
  buildSecurityReviewPrompt
@@ -61,10 +61,10 @@ import {
61
61
  spawnAgentContext,
62
62
  truncateOutput,
63
63
  undoStack
64
- } from "./chunk-R5CJD2AB.js";
64
+ } from "./chunk-6C3JYNM6.js";
65
65
  import "./chunk-T2NL5ZIA.js";
66
66
  import "./chunk-BXP6YZ2P.js";
67
- import "./chunk-TR6MSPSS.js";
67
+ import "./chunk-3TSHNZKI.js";
68
68
  import {
69
69
  SessionManager,
70
70
  getContentText
@@ -78,7 +78,7 @@ import {
78
78
  } from "./chunk-QMXC327F.js";
79
79
  import {
80
80
  runTool
81
- } from "./chunk-YMFGGCUP.js";
81
+ } from "./chunk-X6OXS7KU.js";
82
82
  import {
83
83
  CONTENT_ONLY_STREAM_REMINDER,
84
84
  TEE_FINAL_USER_NUDGE,
@@ -97,7 +97,7 @@ import {
97
97
  } from "./chunk-XPBEJB27.js";
98
98
  import {
99
99
  ConfigManager
100
- } from "./chunk-YFHF3PVB.js";
100
+ } from "./chunk-2224JGA6.js";
101
101
  import "./chunk-TZQHYZKT.js";
102
102
  import {
103
103
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -116,7 +116,7 @@ import {
116
116
  SKILLS_DIR_NAME,
117
117
  VERSION,
118
118
  buildUserIdentityPrompt
119
- } from "./chunk-DBYMX2ZC.js";
119
+ } from "./chunk-7JES2NWR.js";
120
120
  import {
121
121
  formatGitContextForPrompt,
122
122
  getGitContext,
@@ -2755,7 +2755,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2755
2755
  case "test": {
2756
2756
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2757
2757
  try {
2758
- const { executeTests } = await import("./run-tests-F44JA2YW.js");
2758
+ const { executeTests } = await import("./run-tests-QJD43AML.js");
2759
2759
  const argStr = args.join(" ").trim();
2760
2760
  let testArgs = {};
2761
2761
  if (argStr) {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-R5CJD2AB.js";
4
+ } from "./chunk-6C3JYNM6.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-TR6MSPSS.js";
7
+ import "./chunk-3TSHNZKI.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-YMFGGCUP.js";
10
+ } from "./chunk-X6OXS7KU.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-DBYMX2ZC.js";
18
+ } from "./chunk-7JES2NWR.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -3,13 +3,13 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-R5CJD2AB.js";
6
+ } from "./chunk-6C3JYNM6.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-TR6MSPSS.js";
9
+ import "./chunk-3TSHNZKI.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-YMFGGCUP.js";
12
+ } from "./chunk-X6OXS7KU.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
@@ -17,7 +17,7 @@ import {
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-DBYMX2ZC.js";
20
+ } from "./chunk-7JES2NWR.js";
21
21
  import "./chunk-4BKXL7SM.js";
22
22
  import "./chunk-TB4W4Y4T.js";
23
23
  import "./chunk-KHYD3WXE.js";
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-YFHF3PVB.js";
11
+ } from "./chunk-2224JGA6.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-DBYMX2ZC.js";
13
+ import "./chunk-7JES2NWR.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
@@ -24,6 +24,7 @@ function handleDeclarativeAction(action, el) {
24
24
  case 'batch-deny-response': window.respondBatchDeny(el.dataset.requestId, el); break;
25
25
  case 'ask-user-response': window.respondAskUser(el.dataset.requestId); break;
26
26
  case 'auto-pause-response': window.respondAutoPause(el.dataset.requestId, el.dataset.pauseAction); break;
27
+ case 'copy-confirm-diff': window.copyConfirmDiff(el); break;
27
28
  case 'use-template': useTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
28
29
  case 'edit-template': editTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
29
30
  case 'delete-template': deleteTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
@@ -27,6 +27,11 @@ let inputHistory = []; // Previous user inputs for ↑/↓ navigation
27
27
  let historyIndex = -1; // -1 = not browsing history
28
28
  let savedInputDraft = ''; // Saved current input when entering history mode
29
29
  let toolTimers = new Map(); // callId → { startTime, intervalId }
30
+ let latestStatus = null;
31
+ let pendingApprovals = new Map(); // requestId -> approval metadata
32
+ let lastToolResults = [];
33
+ let replayCurrentMessages = [];
34
+ let replayCurrentFilter = 'all';
30
35
 
31
36
  // ── Multi-Tab state (P2-1) ────────────────────────────────────────
32
37
  // Each "tab" represents an open session within the single page.
@@ -60,6 +65,8 @@ let cachedSessions = [];
60
65
  let cachedToolsData = null;
61
66
  const sessionTabsListEl = document.getElementById('session-tabs-list');
62
67
  const btnAddTab = document.getElementById('btn-add-tab');
68
+ const dashboardPanel = document.getElementById('dashboard-panel');
69
+ const btnDashboardRefresh = document.getElementById('btn-dashboard-refresh');
63
70
 
64
71
  // ── Configure marked.js ────────────────────────────────────────────
65
72
 
@@ -283,6 +290,9 @@ function handleToolCallStart(msg) {
283
290
  }
284
291
 
285
292
  function handleToolCallResult(msg) {
293
+ lastToolResults.push({ toolName: msg.toolName, content: msg.content || '', isError: !!msg.isError, timestamp: Date.now() });
294
+ if (lastToolResults.length > 30) lastToolResults.shift();
295
+ renderDashboard();
286
296
  // Stop live timer
287
297
  const timer = toolTimers.get(msg.callId);
288
298
  if (timer) {
@@ -325,6 +335,8 @@ function handleToolCallResult(msg) {
325
335
  }
326
336
 
327
337
  function handleConfirmRequest(msg) {
338
+ pendingApprovals.set(msg.requestId, { kind: 'tool', requestId: msg.requestId, toolName: msg.toolName, args: msg.args, dangerLevel: msg.dangerLevel, diff: msg.diff, timestamp: Date.now() });
339
+ renderDashboard();
328
340
  const isDestructive = msg.dangerLevel === 'destructive';
329
341
  const el = document.createElement('div');
330
342
  el.className = `confirm-card ${isDestructive ? 'tool-border-destructive' : 'tool-border-write'} my-1`;
@@ -334,7 +346,7 @@ function handleConfirmRequest(msg) {
334
346
  <span class="badge ${isDestructive ? 'badge-error' : 'badge-warning'} badge-sm">${isDestructive ? '⚠ DESTRUCTIVE' : '✎ Write'}</span>
335
347
  <span class="text-sm font-semibold">${escapeHtml(msg.toolName)}</span>
336
348
  </div>
337
- ${msg.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
349
+ ${msg.diff ? `<div class="flex justify-end mb-1"><button class="btn btn-xs btn-ghost" data-action="copy-confirm-diff" data-diff="${escapeHtml(msg.diff)}">Copy patch</button></div><div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
338
350
  <div class="flex gap-2 mt-2">
339
351
  <button class="btn btn-success btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="true">✓ Approve</button>
340
352
  <button class="btn btn-error btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="false">✗ Deny</button>
@@ -345,15 +357,21 @@ function handleConfirmRequest(msg) {
345
357
  }
346
358
 
347
359
  function handleBatchConfirmRequest(msg) {
360
+ pendingApprovals.set(msg.requestId, { kind: 'batch', requestId: msg.requestId, files: msg.files, dangerLevel: 'write', timestamp: Date.now() });
361
+ renderDashboard();
348
362
  const el = document.createElement('div');
349
363
  el.className = 'confirm-card tool-border-write my-1 batch-confirm';
350
364
  el.setAttribute('data-request-id', msg.requestId);
351
365
 
352
- const fileListHtml = msg.files.map(f => `
353
- <div class="file-item">
354
- <input type="checkbox" checked class="checkbox checkbox-sm checkbox-primary" data-index="${f.index}">
355
- <span class="opacity-80">${escapeHtml(f.filePath)} <span class="badge badge-ghost badge-xs">${escapeHtml(f.toolName)}</span></span>
356
- </div>
366
+ const fileListHtml = msg.files.map((f, idx) => `
367
+ <details class="diff-file-details" ${idx === 0 ? 'open' : ''}>
368
+ <summary class="flex items-center gap-2">
369
+ <input type="checkbox" checked class="checkbox checkbox-sm checkbox-primary" data-index="${f.index}" onclick="event.stopPropagation()">
370
+ <span class="truncate flex-1">${escapeHtml(f.filePath)} <span class="badge badge-ghost badge-xs">${escapeHtml(f.toolName)}</span></span>
371
+ ${f.diff ? `<button class="btn btn-xs btn-ghost" data-action="copy-confirm-diff" data-diff="${escapeHtml(f.diff)}" onclick="event.stopPropagation()">Copy</button>` : ''}
372
+ </summary>
373
+ ${f.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(f.diff)}</div>` : '<div class="text-xs opacity-50 p-2">No diff preview.</div>'}
374
+ </details>
357
375
  `).join('');
358
376
 
359
377
  el.innerHTML = `
@@ -372,6 +390,8 @@ function handleBatchConfirmRequest(msg) {
372
390
  }
373
391
 
374
392
  function handleAskUserRequest(msg) {
393
+ pendingApprovals.set(msg.requestId, { kind: 'ask_user', requestId: msg.requestId, toolName: 'ask_user', dangerLevel: 'safe', question: msg.question, timestamp: Date.now() });
394
+ renderDashboard();
375
395
  const el = document.createElement('div');
376
396
  el.className = 'confirm-card tool-border-safe my-1';
377
397
  el.innerHTML = `
@@ -548,9 +568,9 @@ function handleStatus(msg) {
548
568
  // Active tab: full UI reflection
549
569
  btnThink.classList.toggle('btn-active-toggle', msg.thinkingMode);
550
570
  btnPlan.classList.toggle('btn-active-toggle', msg.planMode);
551
- const runningAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'running').length : 0;
552
- const failedAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'error').length : 0;
553
- const agentStatus = (runningAgents ? ' · agents:' + runningAgents : '') + (failedAgents ? ' · agent-errors:' + failedAgents : '');
571
+ const runningAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'running').length : 0;
572
+ const failedAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'error').length : 0;
573
+ const agentStatus = (runningAgents ? ' · agents:' + runningAgents : '') + (failedAgents ? ' · agent-errors:' + failedAgents : '');
554
574
  statusSession.textContent = '📋 ' + (msg.sessionId?.slice(0, 8) || '—') + ' (' + msg.messageCount + ' msgs) · ' + (msg.permissionProfile || 'legacy') + (msg.networkPolicyEnabled ? ' · net:on' : '') + (msg.autoMode ? ' · auto:on' : '') + (msg.pendingHookTrustCount ? ' · hooks:' + msg.pendingHookTrustCount : '') + agentStatus;
555
575
  if (msg.tokenUsage) {
556
576
  const u = msg.tokenUsage;
@@ -573,16 +593,98 @@ function handleStatus(msg) {
573
593
  targetTab.isProcessing = processing;
574
594
  const title = msg.sessionTitle || msg.sessionId?.slice(0, 8) || 'New Session';
575
595
  document.title = `ai-cli — ${title}`;
596
+ latestStatus = msg;
597
+ renderDashboard();
576
598
  }
577
599
 
578
600
  renderTabBar();
579
601
  saveTabState();
580
602
  }
581
603
 
604
+
605
+ function renderDashboard() {
606
+ if (!dashboardPanel) return;
607
+ const s = latestStatus || {};
608
+ const agents = Array.isArray(s.agents) ? s.agents : [];
609
+ const runningAgents = agents.filter(a => a.status === 'running');
610
+ const erroredAgents = agents.filter(a => a.status === 'error');
611
+ const pending = [...pendingApprovals.values()];
612
+ const tokens = s.tokenUsage || {};
613
+ const totalTokens = (tokens.inputTokens || 0) + (tokens.outputTokens || 0) + (tokens.cacheCreationTokens || 0) + (tokens.cacheReadTokens || 0);
614
+ const cost = typeof s.costUsd === 'number'
615
+ ? (s.costUsd === 0 ? '$0' : s.costUsd < 0.01 ? `$${s.costUsd.toFixed(4)}` : s.costUsd < 1 ? `$${s.costUsd.toFixed(3)}` : `$${s.costUsd.toFixed(2)}`)
616
+ : 'unknown';
617
+ const recentTests = lastToolResults.filter(r => r.toolName === 'run_tests').slice(-3).reverse();
618
+ const recentToolErrors = lastToolResults.filter(r => r.isError).slice(-3).reverse();
619
+
620
+ dashboardPanel.innerHTML = `
621
+ <section class="dashboard-section">
622
+ <div class="dashboard-section-title">Current Session</div>
623
+ ${dashRow('Session', s.sessionTitle || (s.sessionId ? s.sessionId.slice(0, 8) : '-'))}
624
+ ${dashRow('Provider', `${s.provider || '-'} / ${s.model || '-'}`)}
625
+ ${dashRow('Messages', String(s.messageCount ?? 0))}
626
+ ${dashRow('Mode', `${s.planMode ? 'plan' : 'execute'}${s.thinkingMode ? ' · thinking' : ''}${s.autoMode ? ' · auto' : ''}`)}
627
+ </section>
628
+ <section class="dashboard-section">
629
+ <div class="dashboard-section-title">Approvals <span class="confirm-queue-count">${pending.length}</span></div>
630
+ ${pending.length ? pending.map(renderDashboardApproval).join('') : '<div class="dashboard-empty">No pending approvals.</div>'}
631
+ </section>
632
+ <section class="dashboard-section">
633
+ <div class="dashboard-section-title">Sub-Agents</div>
634
+ ${dashRow('Running', String(runningAgents.length))}
635
+ ${dashRow('Errors', String(erroredAgents.length))}
636
+ ${agents.length ? agents.slice(0, 5).map(renderDashboardAgent).join('') : '<div class="dashboard-empty">No agent runs yet.</div>'}
637
+ <div class="dashboard-actions"><button class="btn btn-xs btn-ghost" data-dashboard-command="agent summary">summary</button><button class="btn btn-xs btn-ghost" data-dashboard-command="agent list">list</button></div>
638
+ </section>
639
+ <section class="dashboard-section">
640
+ <div class="dashboard-section-title">Permissions & Cost</div>
641
+ ${dashRow('Profile', s.permissionProfile || 'legacy')}
642
+ ${dashRow('Network', s.networkPolicyEnabled ? 'enabled' : 'off')}
643
+ ${dashRow('Hooks', String(s.pendingHookTrustCount || 0))}
644
+ ${dashRow('Tokens', totalTokens.toLocaleString())}
645
+ ${dashRow('Cost', cost)}
646
+ </section>
647
+ <section class="dashboard-section">
648
+ <div class="dashboard-section-title">Recent Tests / Errors</div>
649
+ ${recentTests.length ? recentTests.map(renderDashboardToolResult).join('') : '<div class="dashboard-empty">No recent run_tests result.</div>'}
650
+ ${recentToolErrors.length ? '<div class="dashboard-section-title mt-2">Tool Errors</div>' + recentToolErrors.map(renderDashboardToolResult).join('') : ''}
651
+ </section>
652
+ `;
653
+
654
+ dashboardPanel.querySelectorAll('[data-dashboard-command]').forEach(btn => {
655
+ btn.addEventListener('click', () => {
656
+ const parts = btn.dataset.dashboardCommand.split(/\s+/);
657
+ send({ type: 'command', name: parts[0], args: parts.slice(1) });
658
+ });
659
+ });
660
+ }
661
+
662
+ function dashRow(label, value) {
663
+ return `<div class="dashboard-row"><span class="label">${escapeHtml(label)}</span><span class="value" title="${escapeHtml(value)}">${escapeHtml(value)}</span></div>`;
664
+ }
665
+
666
+ function renderDashboardApproval(item) {
667
+ const label = item.kind === 'batch' ? `${item.files?.length || 0} file batch` : item.toolName;
668
+ return `<div class="dashboard-item running"><div class="font-semibold truncate">${escapeHtml(label || item.kind)}</div><div class="text-xs opacity-60 truncate">${escapeHtml(item.dangerLevel || item.kind)} · ${escapeHtml(item.requestId)}</div></div>`;
669
+ }
670
+
671
+ function renderDashboardAgent(agent) {
672
+ const cls = agent.status || 'ok';
673
+ const task = agent.task || '';
674
+ return `<div class="dashboard-item ${escapeHtml(cls)}"><div class="font-semibold truncate">${escapeHtml(agent.agentName || 'agent')} · ${escapeHtml(cls)}</div><div class="text-xs opacity-60 truncate" title="${escapeHtml(task)}">${escapeHtml(task || '(no task)')}</div>${agent.summary ? `<div class="text-xs opacity-80 truncate">${escapeHtml(agent.summary)}</div>` : ''}</div>`;
675
+ }
676
+
677
+ function renderDashboardToolResult(result) {
678
+ const cls = result.isError ? 'error' : 'ok';
679
+ const content = result.content || '';
680
+ return `<div class="dashboard-item ${cls}"><div class="font-semibold truncate">${escapeHtml(result.toolName || 'tool')} · ${result.isError ? 'error' : 'ok'}</div><div class="text-xs opacity-60 truncate" title="${escapeHtml(content)}">${escapeHtml(content.slice(0, 120))}</div></div>`;
681
+ }
582
682
  // ── Response helpers ───────────────────────────────────────────────
583
683
 
584
684
  window.respondConfirm = function(requestId, approved) {
585
685
  send({ type: 'confirm_response', requestId, approved });
686
+ pendingApprovals.delete(requestId);
687
+ renderDashboard();
586
688
  const el = document.querySelector(`[data-request-id="${requestId}"]`);
587
689
  if (el) {
588
690
  el.className = `confirm-card ${approved ? 'tool-border-safe' : 'tool-border-destructive'} my-1 opacity-60`;
@@ -594,7 +696,7 @@ window.respondBatchConfirm = function(requestId, btn) {
594
696
  const dialog = btn.closest('.batch-confirm');
595
697
  const checks = dialog.querySelectorAll('input[type="checkbox"]');
596
698
  const selected = [];
597
- checks.forEach(c => { if (c.checked) selected.push(parseInt(c.dataset.index)); });
699
+ checks.forEach(c => { if (c.checked) selected.push(parseInt(c.dataset.index, 10)); });
598
700
 
599
701
  if (selected.length === checks.length) {
600
702
  send({ type: 'batch_confirm_response', requestId, decision: 'all' });
@@ -603,12 +705,16 @@ window.respondBatchConfirm = function(requestId, btn) {
603
705
  } else {
604
706
  send({ type: 'batch_confirm_response', requestId, decision: selected });
605
707
  }
708
+ pendingApprovals.delete(requestId);
709
+ renderDashboard();
606
710
  dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
607
711
  dialog.innerHTML = `<span class="text-sm text-success">✓ Approved ${selected.length}/${checks.length} files</span>`;
608
712
  };
609
713
 
610
714
  window.respondBatchDeny = function(requestId, btn) {
611
715
  send({ type: 'batch_confirm_response', requestId, decision: 'none' });
716
+ pendingApprovals.delete(requestId);
717
+ renderDashboard();
612
718
  const dialog = btn.closest('.batch-confirm');
613
719
  dialog.className = 'confirm-card tool-border-destructive my-1 opacity-60';
614
720
  dialog.innerHTML = `<span class="text-sm text-error">✗ All rejected</span>`;
@@ -618,6 +724,8 @@ window.respondAskUser = function(requestId) {
618
724
  const input = document.getElementById(`ask-input-${requestId}`);
619
725
  const answer = input ? input.value : '';
620
726
  send({ type: 'ask_user_response', requestId, answer: answer || null });
727
+ pendingApprovals.delete(requestId);
728
+ renderDashboard();
621
729
  const dialog = input?.closest('.confirm-card');
622
730
  if (dialog) {
623
731
  dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
@@ -649,6 +757,16 @@ window.respondAutoPause = function(requestId, action) {
649
757
  }
650
758
  };
651
759
 
760
+
761
+ window.copyConfirmDiff = function(el) {
762
+ const diff = el?.dataset?.diff || '';
763
+ if (!diff) return;
764
+ navigator.clipboard.writeText(diff).then(() => {
765
+ const old = el.textContent;
766
+ el.textContent = 'Copied';
767
+ setTimeout(() => { el.textContent = old || 'Copy'; }, 1200);
768
+ }).catch(() => addInfoMessage('Could not copy patch.'));
769
+ };
652
770
  // DaisyUI light themes → highlight.js light stylesheet; others → dark
653
771
  const LIGHT_DAISYUI_THEMES = new Set(['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter', 'nord']);
654
772
  // v0.4.153: vendored locally (was cdn.jsdelivr.net) — see index.html.
@@ -1124,7 +1242,11 @@ function renderReplay(session, metaEl, usageEl, timelineEl) {
1124
1242
  `<span class="badge badge-sm badge-ghost">cache-read ${tu.cacheReadTokens || 0}</span>`;
1125
1243
 
1126
1244
  const messages = Array.isArray(session.messages) ? session.messages : [];
1245
+ replayCurrentMessages = messages;
1246
+ replayCurrentFilter = 'all';
1127
1247
  timelineEl.innerHTML = messages.map((m, i) => renderReplayStep(m, i)).join('');
1248
+ wireReplayFilters();
1249
+ applyReplayFilter();
1128
1250
 
1129
1251
  // Wire 🌿 fork-from-here buttons.
1130
1252
  timelineEl.querySelectorAll('.replay-fork-btn').forEach((btn) => {
@@ -1159,6 +1281,9 @@ function renderReplayStep(m, idx) {
1159
1281
  const role = m.role || 'user';
1160
1282
  const ts = m.timestamp ? new Date(m.timestamp).toLocaleTimeString() : '';
1161
1283
  const isError = !!m.isError;
1284
+ const hasToolCalls = Array.isArray(m.toolCalls) && m.toolCalls.length > 0;
1285
+ const isAgentRelated = (hasToolCalls && m.toolCalls.some(tc => (tc.name || tc.function?.name || '').includes('agent'))) || String(m.toolName || '').includes('agent');
1286
+ const isCostRelated = !!(m.usage || m.tokenUsage) || /token|cost|usage/i.test(String(m.content || ''));
1162
1287
  const cls = 'replay-step role-' + role + (isError ? ' error' : '');
1163
1288
  const roleTag = role + (m.toolName ? ' · ' + m.toolName : '') + (isError ? ' · ERROR' : '');
1164
1289
 
@@ -1197,7 +1322,7 @@ function renderReplayStep(m, idx) {
1197
1322
  body += `<details class="replay-tool-block"><summary class="opacity-70">💭 reasoning</summary><pre>${escapeHtml(m.reasoningContent)}</pre></details>`;
1198
1323
  }
1199
1324
 
1200
- return `<div class="${cls}">
1325
+ return `<div class="${cls}" data-has-tools="${hasToolCalls || role === 'tool' ? '1' : '0'}" data-is-agent="${isAgentRelated ? '1' : '0'}" data-is-error="${isError ? '1' : '0'}" data-is-cost="${isCostRelated ? '1' : '0'}">
1201
1326
  <div class="replay-step-header">
1202
1327
  <span class="opacity-50">#${idx + 1}</span>
1203
1328
  <span class="role-tag">${escapeHtml(roleTag)}</span>
@@ -1209,6 +1334,40 @@ function renderReplayStep(m, idx) {
1209
1334
  </div>`;
1210
1335
  }
1211
1336
 
1337
+
1338
+ function wireReplayFilters() {
1339
+ document.querySelectorAll('[data-replay-filter]').forEach(btn => {
1340
+ const active = btn.dataset.replayFilter === replayCurrentFilter;
1341
+ btn.classList.toggle('btn-primary', active);
1342
+ btn.classList.toggle('btn-outline', active);
1343
+ btn.classList.toggle('btn-ghost', !active);
1344
+ if (btn.dataset.replayFilterWired === '1') return;
1345
+ btn.dataset.replayFilterWired = '1';
1346
+ btn.addEventListener('click', () => {
1347
+ replayCurrentFilter = btn.dataset.replayFilter || 'all';
1348
+ document.querySelectorAll('[data-replay-filter]').forEach(b => {
1349
+ const active = b.dataset.replayFilter === replayCurrentFilter;
1350
+ b.classList.toggle('btn-primary', active);
1351
+ b.classList.toggle('btn-outline', active);
1352
+ b.classList.toggle('btn-ghost', !active);
1353
+ });
1354
+ applyReplayFilter();
1355
+ });
1356
+ });
1357
+ }
1358
+
1359
+
1360
+ function applyReplayFilter() {
1361
+ const steps = document.querySelectorAll('#replay-timeline .replay-step');
1362
+ steps.forEach(step => {
1363
+ let show = true;
1364
+ if (replayCurrentFilter === 'tools') show = step.dataset.hasTools === '1';
1365
+ else if (replayCurrentFilter === 'agents') show = step.dataset.isAgent === '1';
1366
+ else if (replayCurrentFilter === 'errors') show = step.dataset.isError === '1';
1367
+ else if (replayCurrentFilter === 'cost') show = step.dataset.isCost === '1';
1368
+ step.classList.toggle('hidden-by-filter', !show);
1369
+ });
1370
+ }
1212
1371
  // ── B2: Branch sidebar panel ─────────────────────────────
1213
1372
  let _cachedBranches = [];
1214
1373
  let _cachedActiveBranchId = 'main';
@@ -1574,11 +1733,16 @@ function switchSidebarTab(tabName) {
1574
1733
  fileTreeLoaded = true;
1575
1734
  initFileTree();
1576
1735
  }
1736
+ if (tabName === 'dashboard') renderDashboard();
1577
1737
  }
1578
1738
 
1579
1739
  document.querySelectorAll('.sidebar-tab').forEach(btn => {
1580
1740
  btn.addEventListener('click', () => switchSidebarTab(btn.dataset.tab));
1581
1741
  });
1742
+ btnDashboardRefresh?.addEventListener('click', () => {
1743
+ send({ type: 'command', name: 'status', args: [] });
1744
+ renderDashboard();
1745
+ });
1582
1746
 
1583
1747
  // ── Tools list rendering ──────────────────────────────────────────────
1584
1748
 
@@ -2853,7 +3017,7 @@ function renderMemoryHits(msg) {
2853
3017
  const header = `
2854
3018
  <div class="flex items-center gap-1 text-xs pb-1 mb-1 border-b border-base-content/10">
2855
3019
  <span class="opacity-60 flex-1">${msg.hits.length} hit(s) for "${escapeHtml(msg.query)}"</span>
2856
- <button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="Inject top 3 hits into chat input as a quoted context block">➕ Inject top 3</button>
3020
+ <button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="Manually approve injecting top 3 hits into chat input">Approve top 3</button>
2857
3021
  </div>
2858
3022
  `;
2859
3023
 
@@ -2865,13 +3029,18 @@ function renderMemoryHits(msg) {
2865
3029
  return `
2866
3030
  <div class="memory-hit card bg-base-100 border border-base-content/10 p-2 hover:border-primary/40"
2867
3031
  data-hit-id="${escapeHtml(h.id)}">
3032
+ <div class="memory-meta-badges">
3033
+ <span class="badge badge-xs badge-ghost">scope: chat</span>
3034
+ <span class="badge badge-xs badge-ghost">source: ${escapeHtml(h.sessionId.slice(0, 8))}</span>
3035
+ <span class="badge badge-xs badge-ghost">manual inject</span>
3036
+ </div>
2868
3037
  <div class="flex items-center gap-1 text-xs opacity-70 mb-1">
2869
3038
  <span class="font-semibold truncate flex-1">${escapeHtml(title)}</span>
2870
3039
  <span class="badge badge-xs">${score}</span>
2871
3040
  </div>
2872
3041
  <div class="text-xs opacity-50 mb-1 flex items-center gap-2">
2873
3042
  <span class="flex-1">${ts} · session ${escapeHtml(h.sessionId.slice(0, 8))}</span>
2874
- <button class="btn btn-xs btn-ghost memory-hit-inject" title="Inject this hit into chat input">➕</button>
3043
+ <button class="btn btn-xs btn-ghost memory-hit-inject" title="Manually approve injecting this hit into chat input">Approve inject</button>
2875
3044
  <button class="btn btn-xs btn-ghost memory-hit-load" title="Load this session in current tab">↗</button>
2876
3045
  </div>
2877
3046
  <div class="text-xs whitespace-pre-wrap leading-snug opacity-90" style="max-height: 8em; overflow: hidden;">${escapeHtml(preview)}</div>
@@ -108,15 +108,26 @@
108
108
  <aside id="sidebar" class="sidebar bg-base-200 border-r border-base-content/10 flex flex-col flex-shrink-0 overflow-hidden">
109
109
  <!-- Sidebar tabs -->
110
110
  <div class="flex border-b border-base-content/10 flex-shrink-0">
111
- <button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
111
+ <button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="dashboard" title="Agent control dashboard">⌘ Dash</button>
112
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
112
113
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
113
114
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
114
115
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
115
116
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
116
117
  <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="hub" title="Multi-agent brainstorm room (P4)">🏛 Hub</button>
117
118
  </div>
119
+ <!-- Dashboard tab (Phase 6) -->
120
+ <div id="tab-dashboard" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
121
+ <div class="p-2 border-b border-base-content/10 flex items-center gap-1">
122
+ <span class="text-xs font-semibold flex-1">Agent Console</span>
123
+ <button id="btn-dashboard-refresh" class="btn btn-xs btn-ghost" title="Refresh dashboard">↻</button>
124
+ </div>
125
+ <div id="dashboard-panel" class="dashboard-panel flex-1 overflow-y-auto p-2 text-sm">
126
+ <div class="text-xs opacity-40 text-center py-4">Waiting for status…</div>
127
+ </div>
128
+ </div>
118
129
  <!-- Sessions tab -->
119
- <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
130
+ <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
120
131
  <div class="p-2 border-b border-base-content/10 flex items-center justify-between gap-1">
121
132
  <input id="session-search" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search...">
122
133
  <button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" data-action="toggle-batch-select">☑</button>
@@ -332,7 +343,14 @@
332
343
  <form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
333
344
  </div>
334
345
  <div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
335
- <div id="replay-usage" class="text-xs mb-3"></div>
346
+ <div id="replay-usage" class="text-xs mb-2"></div>
347
+ <div id="replay-filters" class="replay-filters flex flex-wrap gap-1 mb-3">
348
+ <button class="btn btn-xs btn-primary btn-outline" data-replay-filter="all">All</button>
349
+ <button class="btn btn-xs btn-ghost" data-replay-filter="tools">Tools</button>
350
+ <button class="btn btn-xs btn-ghost" data-replay-filter="agents">Agents</button>
351
+ <button class="btn btn-xs btn-ghost" data-replay-filter="errors">Errors</button>
352
+ <button class="btn btn-xs btn-ghost" data-replay-filter="cost">Cost</button>
353
+ </div>
336
354
  <div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
337
355
  </div>
338
356
  <form method="dialog" class="modal-backdrop"><button>close</button></form>
@@ -989,3 +989,65 @@ button, a, .session-item, .file-tree-row, .template-item, .tool-item, .mcp-serve
989
989
  font-size: 0.72rem;
990
990
  white-space: pre;
991
991
  }
992
+
993
+ /* ── Phase 6 Dashboard ─────────────────────────────── */
994
+ .dashboard-panel {
995
+ display: flex;
996
+ flex-direction: column;
997
+ gap: 0.5rem;
998
+ }
999
+ .dashboard-section {
1000
+ border: 1px solid oklch(var(--bc) / 0.1);
1001
+ border-radius: 0.5rem;
1002
+ background: oklch(var(--b1));
1003
+ padding: 0.55rem;
1004
+ }
1005
+ .dashboard-section-title {
1006
+ display: flex;
1007
+ align-items: center;
1008
+ gap: 0.35rem;
1009
+ font-size: 0.72rem;
1010
+ font-weight: 700;
1011
+ text-transform: uppercase;
1012
+ letter-spacing: 0.04em;
1013
+ opacity: 0.65;
1014
+ margin-bottom: 0.4rem;
1015
+ }
1016
+ .dashboard-row {
1017
+ display: flex;
1018
+ align-items: center;
1019
+ gap: 0.4rem;
1020
+ min-width: 0;
1021
+ padding: 0.18rem 0;
1022
+ font-size: 0.78rem;
1023
+ }
1024
+ .dashboard-row .label { opacity: 0.55; min-width: 4.2rem; }
1025
+ .dashboard-row .value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1026
+ .dashboard-item {
1027
+ border-left: 2px solid oklch(var(--bc) / 0.15);
1028
+ padding: 0.35rem 0.45rem;
1029
+ margin-top: 0.3rem;
1030
+ background: oklch(var(--b2));
1031
+ border-radius: 0.35rem;
1032
+ }
1033
+ .dashboard-item.running { border-left-color: oklch(var(--wa)); }
1034
+ .dashboard-item.ok { border-left-color: oklch(var(--su)); }
1035
+ .dashboard-item.error { border-left-color: oklch(var(--er)); }
1036
+ .dashboard-item.stopped { border-left-color: oklch(var(--bc) / 0.35); }
1037
+ .dashboard-actions { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.4rem; }
1038
+ .dashboard-empty { font-size: 0.75rem; opacity: 0.45; padding: 0.35rem 0; }
1039
+ .confirm-queue-count { font-weight: 700; color: oklch(var(--wa)); }
1040
+ .diff-file-details {
1041
+ border: 1px solid oklch(var(--bc) / 0.1);
1042
+ border-radius: 0.4rem;
1043
+ margin: 0.35rem 0;
1044
+ overflow: hidden;
1045
+ }
1046
+ .diff-file-details summary {
1047
+ padding: 0.35rem 0.45rem;
1048
+ cursor: pointer;
1049
+ background: oklch(var(--b3));
1050
+ }
1051
+ .diff-file-details .confirm-diff { margin: 0; border-radius: 0; max-height: 180px; }
1052
+ .memory-meta-badges { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.35rem; }
1053
+ .replay-step.hidden-by-filter { display: none; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.214",
3
+ "version": "0.4.215",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",