larkin 0.4.28 → 0.5.0

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +5 -9
  3. package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
  4. package/dist/app/agent-cli.mjs +14 -2
  5. package/dist/app/agent-config.mjs +26 -84
  6. package/dist/app/binary-entry.mjs +0 -20
  7. package/dist/app/cli.mjs +8 -14
  8. package/dist/app/internal-command.cjs +0 -2
  9. package/dist/app/internal-command.mjs +0 -2
  10. package/dist/app/local-control.mjs +16 -3
  11. package/dist/app/runtime-model-directory.mjs +2 -16
  12. package/dist/app/runtime-process.mjs +42 -6
  13. package/dist/app/setup.mjs +52 -57
  14. package/dist/dashboard/dashboard-config-controller.mjs +83 -24
  15. package/dist/dashboard/dashboard-view-model.mjs +4 -7
  16. package/dist/dashboard/web/assets/dashboard.js +1 -1
  17. package/dist/feishu/host-shell.cjs +68 -17
  18. package/dist/feishu/host-shell.mjs +69 -18
  19. package/dist/platform/config.cjs +201 -138
  20. package/dist/platform/config.mjs +199 -138
  21. package/dist/runtime/pi-artifact-provenance.mjs +3 -3
  22. package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
  23. package/dist/runtime/pi-compaction-recovery.cjs +9 -11
  24. package/dist/runtime/pi-compaction-recovery.mjs +8 -10
  25. package/dist/runtime/pi-extension-api.mjs +1 -0
  26. package/dist/runtime/pi-model-catalog.cjs +7 -8
  27. package/dist/runtime/pi-model-catalog.mjs +7 -8
  28. package/dist/runtime/pi-subagent-injection.mjs +17 -4
  29. package/dist/runtime/runtime-adapters.mjs +41 -38
  30. package/dist/runtime/runtime-host.cjs +197 -20
  31. package/dist/runtime/runtime-host.mjs +198 -21
  32. package/dist/runtime/runtime-model-catalog.cjs +1 -1
  33. package/dist/runtime/runtime-model-catalog.mjs +1 -1
  34. package/dist/runtime/runtime-readiness.cjs +112 -49
  35. package/dist/runtime/runtime-readiness.mjs +103 -49
  36. package/dist/runtime/user-runtime.cjs +7 -24
  37. package/dist/runtime/user-runtime.mjs +7 -23
  38. package/dist/setup/bot-register.mjs +15 -123
  39. package/dist/setup/setup-agent-choice.mjs +48 -74
  40. package/dist/setup/setup-bind.mjs +51 -151
  41. package/dist/setup/setup-binding.mjs +6 -9
  42. package/package.json +3 -2
  43. package/dist/app/pi-auth-cli.mjs +0 -67
  44. package/dist/platform/process-boundary-trace.cjs +0 -108
  45. package/dist/runtime/builtin-pi-assets.cjs +0 -117
  46. package/dist/runtime/builtin-pi-assets.mjs +0 -105
  47. package/dist/runtime/pi-artifact-provenance.cjs +0 -147
  48. package/dist/runtime/pi-distribution-label.cjs +0 -6
  49. package/dist/runtime/pi-distribution-label.mjs +0 -3
  50. package/dist/runtime/pi-inline-extensions.mjs +0 -78
  51. package/dist/runtime/pi-official-auth.mjs +0 -491
  52. package/dist/runtime/pi-profile-migration.cjs +0 -764
  53. package/dist/runtime/pi-profile-migration.mjs +0 -719
  54. package/dist/runtime/pi-provider-config.cjs +0 -331
  55. package/dist/runtime/pi-provider-config.mjs +0 -314
@@ -7,13 +7,11 @@ import path from "node:path";
7
7
  import readline from "node:readline/promises";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import * as larkinConfig from "../platform/config.mjs";
10
- import { PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
11
- import { fromUserRuntime, isUserRuntime } from "../runtime/user-runtime.mjs";
12
- const PI_PROVIDER_IDS = PI_PROVIDER_PRESETS.map((p) => p.id).join(" | ") + " | custom";
10
+ import { isUserRuntime } from "../runtime/user-runtime.mjs";
13
11
  import { acquireProcessLock, readProcessState } from "../platform/process-state.mjs";
14
12
  import { requestAgentUpsert } from "./local-control.mjs";
15
13
  import { openOwnedDashboardWhenReady } from "./setup-dashboard.mjs";
16
- import { probeNativeRuntimeReadiness } from "../runtime/runtime-readiness.mjs";
14
+ import { probeNativeRuntimeReadiness, resolveRuntimeExecutable, runtimeInstallNextAction } from "../runtime/runtime-readiness.mjs";
17
15
  import { internalCommandSpec, processCommandToken } from "./internal-command.mjs";
18
16
  import { ensureOfficialLarkCliForSetup, formatOfficialLarkCliConsent, } from "./official-lark-cli.mjs";
19
17
  const CFG_DIR = process.env.LARKIN_CONFIG_DIR || path.join(os.homedir(), ".larkin");
@@ -33,60 +31,54 @@ if (has("--start") && has("--no-start"))
33
31
  if (has("--comment-subscription")) {
34
32
  die("--comment-subscription 已移除:评论订阅默认 application(平台验证的应用级订阅),不需要该 flag");
35
33
  }
34
+ const REMOVED_SETUP_FLAGS = ["--provider", "--api-key", "--base-url", "--pi-distribution", "--api-key-stdin", "--model-from-preset"];
35
+ if (REMOVED_SETUP_FLAGS.some((name) => has(name) || argv.some((arg) => arg.startsWith(`${name}=`)))) {
36
+ die("setup 不再接受 --provider/--api-key/--base-url;请安装并登录外部 pi、codex 或 claude");
37
+ }
36
38
  // 默认应用级评论订阅:平台验证的应用订阅让该应用可见文档的全部评论进入 Agent Inbox。
37
39
  const commentSubscription = "application";
38
- const OPT = { runtime: flag("--runtime") || null, commentSubscription, start: true, help: has("--help") || has("-h"), nonInteractive: !Boolean(process.stdin.isTTY) };
39
- // runtime 枚举归一:builtin-pi(默认)/ pi / codex / claude → 内部 (runtime, distribution)
40
- const RUNTIME_ENUM = ["builtin-pi", "pi", "codex", "claude"];
40
+ const OPT = { runtime: flag("--runtime") || null, commentSubscription, start: !has("--no-start"), help: has("--help") || has("-h"), nonInteractive: !Boolean(process.stdin.isTTY) };
41
+ const RUNTIME_ENUM = ["pi", "codex", "claude"];
42
+ function runtimeCommand(runtime, env = process.env) {
43
+ if (runtime === "pi")
44
+ return env.LARKIN_PI_COMMAND || "pi";
45
+ if (runtime === "codex")
46
+ return env.LARKIN_CODEX_COMMAND || "codex";
47
+ return env.LARKIN_CLAUDE_COMMAND || "claude";
48
+ }
41
49
  function normalizeRuntime(value) {
42
- const raw = value || "builtin-pi";
43
- if (raw === "external-pi") {
50
+ if (!value)
51
+ return null;
52
+ if (value === "external-pi") {
44
53
  console.error("external-pi 已改名为 pi");
45
- return fromUserRuntime("pi");
54
+ return "pi";
46
55
  }
47
- if (!isUserRuntime(raw) || !RUNTIME_ENUM.includes(raw)) {
48
- die(`未知 runtime \`${value}\`;可选:${RUNTIME_ENUM.join(" | ")}`);
49
- }
50
- return fromUserRuntime(raw);
51
- }
52
- const normalizedRuntime = normalizeRuntime(OPT.runtime);
53
- // 参数化路径:显式 --runtime、无 TTY(Agent 驱动)、或任何 provider 参数。
54
- // 交互终端且未指定任何参数时,不传 --runtime 给 bot-register,由其交互选择流程接管。
55
- const parameterized = Boolean(OPT.runtime) || OPT.nonInteractive
56
- || ["--provider", "--api-key", "--base-url"].some((name) => has(name));
57
- // builtin-pi 前置校验:授权前就 fast-fail(避免用户白授权后才发现缺 key);--help 永远跳过
58
- if (!OPT.help && parameterized && normalizedRuntime.distribution === "builtin") {
59
- const p = flag("--provider");
60
- const b = flag("--base-url");
61
- const k = flag("--api-key");
62
- if (!p && !b)
63
- die(`builtin-pi 需要 --provider <id>(${PI_PROVIDER_IDS})或 --base-url;或 --runtime pi 用已有 pi`);
64
- if (!k)
65
- die("builtin-pi 需要 --api-key;或 --runtime pi 用已有登录");
56
+ if (isUserRuntime(value))
57
+ return value;
58
+ return die(`未知 runtime \`${value}\`;可选:${RUNTIME_ENUM.join(" | ")}`);
66
59
  }
67
- if (!OPT.help && normalizedRuntime.distribution === "external" && (flag("--provider") || flag("--api-key") || flag("--base-url"))) {
68
- die("pi 使用已有 pi 环境,不接受 --provider/--api-key/--base-url");
60
+ function assertRuntimeInstalled(runtime) {
61
+ if (resolveRuntimeExecutable(runtimeCommand(runtime), process.env))
62
+ return;
63
+ die(`${runtime} is not installed;${runtimeInstallNextAction(runtime)}`);
69
64
  }
70
65
  if (OPT.help) {
71
66
  say(`larkin setup — Create or connect a Feishu (Lark) bot, then configure and attach its Agent
72
67
 
73
68
  Usage:
74
- larkin setup Select a bot in the browser and run setup(默认:内置 Pi + 配置完成即热挂载)
75
- larkin setup --provider <id> --api-key <key> 指定内置 Pi provider(无需交互)
69
+ larkin setup Select a bot in the browser and choose an installed runtime
70
+ larkin setup --runtime pi Non-interactive setup for an externally installed runtime
76
71
 
77
72
  Options:
78
- --runtime <runtime> Agent runtime 枚举:builtin-pi(默认,内置 Pi)| pi | codex | claude
79
- --provider <id> 内置 Pi provider:deepseek | kimi | minimax | zhipu | openai |
80
- anthropic | gemini | groq | cerebras | xai | fireworks |
81
- together | mistral | openrouter | kimi-coding | qwen-cn | custom
82
- --api-key <key> API key(内置 Pi 必填)
83
- --base-url <url> custom 端点(provider=custom 时必填;也可覆盖 preset 默认端点)
84
- --model <id> 模型 ID;默认取 provider 预设。不同 runtime 的模型可用
85
- \`larkin model\` 查看 / 切换
86
- --from-cli-profile <name> 可选:复用已有官方 lark-cli profile(需 LARKIN_SETUP_APP_SECRET)
87
- --tenant feishu|lark 授权二维码之前选择品牌。默认 feishu(扫码 /page/launcher)。
88
- Lark 用 --tenant lark:扫码 /page/cli,完成后凭证回传,
89
- 不要打开 /page/launcher。交互式未指定时会先询问。
73
+ --runtime <runtime> Required without a TTY. Agent runtime: pi | codex | claude
74
+ --model <id> Optional model ID; otherwise the runtime default is used.
75
+ Use \`larkin model\` after setup to inspect or switch.
76
+ --from-cli-profile <name> Optional: reuse an existing official lark-cli profile
77
+ (requires LARKIN_SETUP_APP_SECRET)
78
+ --tenant feishu|lark Choose the brand before the authorization QR. Default feishu
79
+ (scan /page/launcher). Lark uses --tenant lark: scan /page/cli,
80
+ then credentials return; do not open /page/launcher.
81
+ Interactive runs ask when omitted.
90
82
 
91
83
  setup handles browser authorization, permission grants, credential storage, Agent configuration,
92
84
  and target-only hot attach. Each Agent is identified by its bot App ID: selecting the same bot reuses
@@ -95,11 +87,18 @@ its existing Agent, memory, and state; creating a new bot creates a new Agent.
95
87
  If larkin is running, only the selected Agent is added or updated. Otherwise setup starts the same
96
88
  daemon + dashboard supervisor used by larkin start.
97
89
 
98
- Interactive terminals without flags keep the guided flow (browser bot selection + runtime choice).
99
- Non-interactive (Agent-driven) runs default to builtin-pi and require --provider/--api-key (or
100
- --runtime pi to reuse an existing pi login).`);
90
+ Interactive terminals list pi, Codex, and Claude Code with installed / not installed status and
91
+ refuse a runtime that is not installed. Non-interactive (Agent-driven) runs require --runtime
92
+ and exit non-zero with the readiness message when that executable is missing.`);
101
93
  process.exit(0);
102
94
  }
95
+ if (OPT.nonInteractive && !OPT.runtime) {
96
+ die("非交互 setup 必须指定 --runtime pi|codex|claude");
97
+ }
98
+ const selectedRuntime = normalizeRuntime(OPT.runtime);
99
+ if (selectedRuntime)
100
+ assertRuntimeInstalled(selectedRuntime);
101
+ const parameterized = Boolean(OPT.runtime);
103
102
  function runForeground(mode, args) {
104
103
  return new Promise((resolve, reject) => {
105
104
  const childSpec = internalCommandSpec(mode, args);
@@ -151,11 +150,8 @@ export async function main() {
151
150
  fs.mkdirSync(CFG_DIR, { recursive: true, mode: 0o700 });
152
151
  const resultFile = path.join(CFG_DIR, `.setup-result-${process.pid}.json`);
153
152
  const registerArgs = ["--auto", "--result-file", resultFile];
154
- if (parameterized) {
155
- registerArgs.push("--runtime", normalizedRuntime.runtime);
156
- if (normalizedRuntime.distribution)
157
- registerArgs.push("--pi-distribution", normalizedRuntime.distribution);
158
- }
153
+ if (parameterized && selectedRuntime)
154
+ registerArgs.push("--runtime", selectedRuntime);
159
155
  registerArgs.push("--comment-subscription", OPT.commentSubscription);
160
156
  if (has("--tenant")) {
161
157
  const tenant = flag("--tenant");
@@ -163,7 +159,7 @@ export async function main() {
163
159
  die("--tenant 只支持 feishu 或 lark");
164
160
  registerArgs.push("--tenant", tenant === "lark" ? "lark" : "feishu");
165
161
  }
166
- for (const name of ["--provider", "--api-key", "--base-url", "--model", "--from-cli-profile"]) {
162
+ for (const name of ["--model", "--from-cli-profile"]) {
167
163
  const value = flag(name);
168
164
  if (value)
169
165
  registerArgs.push(name, value);
@@ -193,12 +189,11 @@ export async function main() {
193
189
  die(`Runtime ${configuredAgent.runtime} 不受支持`);
194
190
  // 非交互(Agent 驱动)时仍跳过 probeNativeRuntimeReadiness:该探测保持 interactive-only。
195
191
  // external-pi 绑定已在 setup-bind 内做非交互 Pi RPC catalog discovery(RPC over pipes 不需要 TTY);
196
- // daemon attach 探测仍是权威校验。其余真实阻塞(缺 runtime 可执行文件 / 缺 key / 缺 lark-cli)一律 fast-fail。
192
+ // daemon attach 探测仍是权威校验。其余真实阻塞(缺 runtime 可执行文件 / 缺登录 / 缺 lark-cli)一律 fast-fail。
197
193
  if (!OPT.nonInteractive) {
198
194
  const runtimeReadiness = await probeNativeRuntimeReadiness({ runtime: configuredAgent.runtime,
199
195
  agentId: selectedAgentId, cwd: configuredAgent.workspaceDir,
200
- env: { ...process.env, LARKIN_CONFIG_DIR: CFG_DIR,
201
- ...(configuredAgent.piDistribution ? { LARKIN_PI_DISTRIBUTION: configuredAgent.piDistribution } : {}) } });
196
+ env: { ...process.env, LARKIN_CONFIG_DIR: CFG_DIR } });
202
197
  if (runtimeReadiness.state !== "ready") {
203
198
  die(`Runtime ${configuredAgent.runtime} ${runtimeReadiness.state}:${runtimeReadiness.reason || "prerequisite unavailable"};${runtimeReadiness.nextAction || "修复后重试"}`);
204
199
  }
@@ -7,8 +7,8 @@ import { discoverClaudeModelCatalog } from "../runtime/claude-model-catalog.mjs"
7
7
  import { discoverCodexModelCatalog } from "../runtime/codex-model-catalog.mjs";
8
8
  import { discoverPiModelCatalog } from "../runtime/pi-model-catalog.mjs";
9
9
  import { managedOfficialLarkCli } from "../app/agent-lark-cli-workspace.mjs";
10
- import { ownedPiCatalogAgentDir, piCatalogCommandSpec } from "../runtime/pi-provider-config.mjs";
11
- import { RUNTIME_OPTIONS, piCatalogDistributionForUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
10
+ import { RUNTIME_OPTIONS, isUserRuntime } from "../runtime/user-runtime.mjs";
11
+ import { probeNativeRuntimeReadiness, RuntimePrerequisiteError } from "../runtime/runtime-readiness.mjs";
12
12
  function pruneCache(cache, expired, maxEntries) {
13
13
  for (const [key, value] of cache)
14
14
  if (expired(value))
@@ -170,9 +170,38 @@ export function createPiModelDirectoryResolver(options = {}) {
170
170
  const cache = new Map();
171
171
  const failures = new Map();
172
172
  const inFlight = new Map();
173
+ const generations = new Map();
174
+ // 单调序号:targeted 后再 global 时,in-flight 的 started 不能与新 generation 相等。
175
+ let seq = 0;
176
+ let baseline = 0;
177
+ const generationOf = (agentId) => Math.max(baseline, generations.get(agentId) ?? 0);
178
+ const dropPrefixed = (store, agentId) => {
179
+ if (!agentId) {
180
+ store.clear();
181
+ return;
182
+ }
183
+ const prefix = `${agentId}\u0000`;
184
+ for (const key of store.keys())
185
+ if (key.startsWith(prefix))
186
+ store.delete(key);
187
+ };
173
188
  return {
189
+ invalidate(agentId) {
190
+ dropPrefixed(cache, agentId);
191
+ dropPrefixed(failures, agentId);
192
+ dropPrefixed(inFlight, agentId);
193
+ seq += 1;
194
+ if (!agentId) {
195
+ baseline = seq;
196
+ generations.clear();
197
+ }
198
+ else {
199
+ generations.set(agentId, seq);
200
+ }
201
+ },
174
202
  async resolve(input) {
175
- const key = [input.agentId, input.cwd, input.agentDir, input.command ?? "", ...(input.commandArgs ?? [])].join("\u0000");
203
+ const key = [input.agentId, input.cwd, input.agentDir ?? "", input.command ?? "", ...(input.commandArgs ?? [])].join("\u0000");
204
+ const started = generationOf(input.agentId);
176
205
  pruneCache(cache, (entry) => now() >= entry.expiresAt, 64);
177
206
  pruneCache(failures, (entry) => now() >= entry.expiresAt, 64);
178
207
  const cached = cache.get(key);
@@ -186,7 +215,7 @@ export function createPiModelDirectoryResolver(options = {}) {
186
215
  try {
187
216
  const catalog = await discover({
188
217
  cwd: input.cwd,
189
- agentDir: input.agentDir,
218
+ ...(input.agentDir ? { agentDir: input.agentDir } : {}),
190
219
  ...(input.command ? { command: input.command } : {}),
191
220
  ...(input.commandArgs ? { commandArgs: input.commandArgs } : {}),
192
221
  });
@@ -196,17 +225,23 @@ export function createPiModelDirectoryResolver(options = {}) {
196
225
  id, label, ...(contextWindow ? { contextWindow } : {}), supportedReasoningEfforts, ...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
197
226
  })),
198
227
  ];
199
- failures.delete(key);
200
- cache.set(key, { expiresAt: now() + ttlMs, value: models });
228
+ if (generationOf(input.agentId) === started) {
229
+ failures.delete(key);
230
+ cache.set(key, { expiresAt: now() + ttlMs, value: models });
231
+ }
201
232
  return models;
202
233
  }
203
234
  catch (error) {
204
- failures.set(key, { expiresAt: now() + negativeTtlMs });
235
+ if (generationOf(input.agentId) === started)
236
+ failures.set(key, { expiresAt: now() + negativeTtlMs });
205
237
  throw error;
206
238
  }
207
239
  })();
208
240
  inFlight.set(key, pending);
209
- void pending.finally(() => inFlight.delete(key)).catch(() => undefined);
241
+ void pending.finally(() => {
242
+ if (inFlight.get(key) === pending)
243
+ inFlight.delete(key);
244
+ }).catch(() => undefined);
210
245
  }
211
246
  return await pending;
212
247
  },
@@ -383,19 +418,18 @@ function dashboardMutation(value) {
383
418
  return { kind: operation, agentId, model: String(value.model || "") };
384
419
  return { kind: "set-agent-effort", agentId, effort: value.effort === null || value.effort === "default" ? null : String(value.effort || "") };
385
420
  }
386
- export function createDashboardConfigController({ csrfCapability, env = process.env, requestUpsert = requestAgentUpsert, chatDirectoryResolver = createChatDirectoryResolver(), claudeModelDirectoryResolver = createClaudeModelDirectoryResolver(), codexModelDirectoryResolver = createCodexModelDirectoryResolver(), piModelDirectoryResolver = createPiModelDirectoryResolver(), }) {
387
- const resolvePiModelDirectory = async (agentId, userRuntime) => {
388
- const { config, configDir } = loadConfig(env);
421
+ export function createDashboardConfigController({ csrfCapability, env = process.env, requestUpsert = requestAgentUpsert, chatDirectoryResolver = createChatDirectoryResolver(), claudeModelDirectoryResolver = createClaudeModelDirectoryResolver(), codexModelDirectoryResolver = createCodexModelDirectoryResolver(), piModelDirectoryResolver = createPiModelDirectoryResolver(), probeRuntimeReadiness = probeNativeRuntimeReadiness, }) {
422
+ const resolvePiModelDirectory = async (agentId) => {
423
+ const { config } = loadConfig(env);
389
424
  const agent = config.agents[agentId];
390
425
  if (!agent)
391
426
  throw new Error("unknown agent");
392
- const catalogCommand = piCatalogCommandSpec(piCatalogDistributionForUserRuntime(userRuntime), env);
427
+ // 外部 `pi --mode rpc` 使用用户自己的 Pi home,不再注入 Larkin 持有的 agentDir。
393
428
  return await piModelDirectoryResolver.resolve({
394
429
  agentId,
395
430
  cwd: agent.workspaceDir,
396
- agentDir: ownedPiCatalogAgentDir(configDir, agentId),
397
- command: catalogCommand.command,
398
- commandArgs: catalogCommand.commandArgs,
431
+ command: env.LARKIN_PI_COMMAND || process.env.LARKIN_PI_COMMAND || "pi",
432
+ commandArgs: [],
399
433
  });
400
434
  };
401
435
  const resolveModelDirectory = async (runtime, agentId) => {
@@ -403,8 +437,8 @@ export function createDashboardConfigController({ csrfCapability, env = process.
403
437
  const agent = config.agents[agentId];
404
438
  if (!agent)
405
439
  throw new Error("unknown agent");
406
- if (runtime === "pi" || runtime === "builtin-pi")
407
- return await resolvePiModelDirectory(agentId, runtime);
440
+ if (runtime === "pi")
441
+ return await resolvePiModelDirectory(agentId);
408
442
  if (runtime === "codex")
409
443
  return await codexModelDirectoryResolver.resolve({ agentId, cwd: agent.workspaceDir, env });
410
444
  if (runtime === "claude")
@@ -414,6 +448,24 @@ export function createDashboardConfigController({ csrfCapability, env = process.
414
448
  throw new Error("unknown runtime");
415
449
  return authored;
416
450
  };
451
+ const assertRuntimeSwitchReady = async (mutation) => {
452
+ if (mutation.kind !== "set-agent-runtime")
453
+ return;
454
+ if (!isUserRuntime(mutation.runtime))
455
+ throw new Error(`未知 runtime:${mutation.runtime}`);
456
+ const { config } = loadConfig(env);
457
+ const agent = config.agents[mutation.agentId];
458
+ if (!agent)
459
+ throw new Error("unknown agent");
460
+ const readiness = await probeRuntimeReadiness({
461
+ runtime: mutation.runtime,
462
+ cwd: agent.workspaceDir,
463
+ env,
464
+ agentId: mutation.agentId,
465
+ });
466
+ if (readiness.state === "missing")
467
+ throw new RuntimePrerequisiteError(readiness);
468
+ };
417
469
  const assertDirectoryMutation = async (mutation) => {
418
470
  if (mutation.kind !== "set-agent-runtime" && mutation.kind !== "set-agent-model" && mutation.kind !== "set-agent-effort")
419
471
  return;
@@ -421,9 +473,7 @@ export function createDashboardConfigController({ csrfCapability, env = process.
421
473
  const agent = config.agents[mutation.agentId];
422
474
  if (!agent)
423
475
  throw new Error("unknown agent");
424
- const runtime = mutation.kind === "set-agent-runtime"
425
- ? mutation.runtime
426
- : toUserRuntime(agent.runtime, agent.piDistribution);
476
+ const runtime = mutation.kind === "set-agent-runtime" ? mutation.runtime : agent.runtime;
427
477
  const model = mutation.kind === "set-agent-runtime" ? mutation.model || "default"
428
478
  : mutation.kind === "set-agent-model" ? mutation.model : agent.model;
429
479
  if (mutation.kind === "set-agent-effort" && mutation.effort === null)
@@ -454,11 +504,11 @@ export function createDashboardConfigController({ csrfCapability, env = process.
454
504
  }
455
505
  return true;
456
506
  }
457
- if ((requestUrl.pathname === "/api/models/pi" || requestUrl.pathname === "/api/models/builtin-pi") && req.method === "GET") {
507
+ if (requestUrl.pathname === "/api/models/pi" && req.method === "GET") {
458
508
  try {
459
509
  assertPrivateReadRequest(req, csrfCapability);
460
510
  const agentId = requestUrl.searchParams.get("agent") || "";
461
- const models = await resolvePiModelDirectory(agentId, requestUrl.pathname === "/api/models/builtin-pi" ? "builtin-pi" : "pi");
511
+ const models = await resolvePiModelDirectory(agentId);
462
512
  json(res, 200, { models });
463
513
  }
464
514
  catch (error) {
@@ -502,12 +552,21 @@ export function createDashboardConfigController({ csrfCapability, env = process.
502
552
  try {
503
553
  assertWriteRequest(req, csrfCapability);
504
554
  const mutation = dashboardMutation(await boundedJson(req));
555
+ await assertRuntimeSwitchReady(mutation);
505
556
  await assertDirectoryMutation(mutation);
506
557
  const result = mutateConfig(env, mutation, { kind: "user" });
507
558
  json(res, 200, { ok: true, revision: result.revision, persisted: true, applyState: result.applyState, changedScope: result.changedScope });
508
559
  }
509
- catch {
510
- json(res, 400, { error: "configuration update rejected" });
560
+ catch (error) {
561
+ if (error instanceof RuntimePrerequisiteError) {
562
+ json(res, 400, { error: error.message, readiness: error.readiness });
563
+ }
564
+ else if (error instanceof Error && error.message.startsWith("未知 runtime")) {
565
+ json(res, 400, { error: error.message });
566
+ }
567
+ else {
568
+ json(res, 400, { error: "configuration update rejected" });
569
+ }
511
570
  }
512
571
  return true;
513
572
  }
@@ -12,7 +12,6 @@ import { isAllowedDashboardAvatarUrl } from "./dashboard-avatar.mjs";
12
12
  import { collectWorkspaceEntry as collectTypedWorkspaceEntry } from "./dashboard-workspace.mjs";
13
13
  import { buildFingerprint, packageVersion } from "../platform/build-info.mjs";
14
14
  import * as larkinConfig from "../platform/config.mjs";
15
- import { ownedPiCatalogAgentDir, piCatalogCommandSpec } from "../runtime/pi-provider-config.mjs";
16
15
  import { toUserRuntime } from "../runtime/user-runtime.mjs";
17
16
  const HERE = path.dirname(fileURLToPath(import.meta.url));
18
17
  const ROOT = path.resolve(HERE, "../..");
@@ -444,13 +443,11 @@ async function collectAgentStatus(a, configDir, daemonStartedAt, piModelResolver
444
443
  let piCatalog = [];
445
444
  if (a.runtime === "pi" && piModelResolver) {
446
445
  try {
447
- const catalogCommand = piCatalogCommandSpec(a.piDistribution, process.env);
448
446
  piCatalog = await piModelResolver.resolve({
449
447
  agentId: a.agentId,
450
448
  cwd: a.workspaceDir,
451
- agentDir: ownedPiCatalogAgentDir(configDir, a.agentId),
452
- command: catalogCommand.command,
453
- commandArgs: catalogCommand.commandArgs,
449
+ command: process.env.LARKIN_PI_COMMAND || "pi",
450
+ commandArgs: [],
454
451
  });
455
452
  }
456
453
  catch { /* unknown or unavailable catalog keeps the explicit turns fallback */ }
@@ -503,7 +500,7 @@ async function collectAgentStatus(a, configDir, daemonStartedAt, piModelResolver
503
500
  agentId: a.agentId,
504
501
  name: a.name,
505
502
  displayName: (botIdentity && botIdentity.name) || a.name,
506
- runtime: toUserRuntime(a.runtime, a.piDistribution),
503
+ runtime: toUserRuntime(a.runtime),
507
504
  model: status.session?.runtime === a.runtime && status.session?.model ? String(status.session.model) : a.model,
508
505
  effort: status.session?.runtime === a.runtime && status.session?.reasoningEffort ? String(status.session.reasoningEffort) : a.effort || null,
509
506
  runtimeReadiness,
@@ -527,7 +524,7 @@ async function collectAgentStatus(a, configDir, daemonStartedAt, piModelResolver
527
524
  sessions: agentState.sessions || {},
528
525
  session: sessionId ? {
529
526
  id: sessionId,
530
- runtime: toUserRuntime(a.runtime, a.piDistribution),
527
+ runtime: toUserRuntime(a.runtime),
531
528
  startedAt: usage.startedAt || (status.session?.id === sessionId ? status.session.startedAt : null) || null,
532
529
  ageSec: ageSec(usage.startedAt || (status.session?.id === sessionId ? status.session.startedAt : null)),
533
530
  lastTurnAt: status.session?.lastTurnAt || null,
@@ -46,4 +46,4 @@ Error generating stack: `+e.message+`
46
46
  `},or=function(){var e=parseInt(document.body.getAttribute(`data-scroll-locked`)||`0`,10);return isFinite(e)?e:0},sr=function(){C.useEffect(function(){return document.body.setAttribute(ir,(or()+1).toString()),function(){var e=or()-1;e<=0?document.body.removeAttribute(ir):document.body.setAttribute(ir,e.toString())}},[])},cr=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,i=r===void 0?`margin`:r;sr();var a=C.useMemo(function(){return nr(i)},[i]);return C.createElement(rr,{styles:ar(a,!t,i,n?``:`!important`)})},lr=!1;if(typeof window<`u`)try{var ur=Object.defineProperty({},"passive",{get:function(){return lr=!0,!0}});window.addEventListener(`test`,ur,ur),window.removeEventListener(`test`,ur,ur)}catch{lr=!1}var dr=lr?{passive:!1}:!1,fr=function(e){return e.tagName===`TEXTAREA`},pr=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return n[t]!==`hidden`&&!(n.overflowY===n.overflowX&&!fr(e)&&n[t]===`visible`)},mr=function(e){return pr(e,`overflowY`)},hr=function(e){return pr(e,`overflowX`)},gr=function(e,t){var n=t.ownerDocument,r=t;do{if(typeof ShadowRoot<`u`&&r instanceof ShadowRoot&&(r=r.host),yr(e,r)){var i=br(e,r);if(i[1]>i[2])return!0}r=r.parentNode}while(r&&r!==n.body);return!1},_r=function(e){return[e.scrollTop,e.scrollHeight,e.clientHeight]},vr=function(e){return[e.scrollLeft,e.scrollWidth,e.clientWidth]},yr=function(e,t){return e===`v`?mr(t):hr(t)},br=function(e,t){return e===`v`?_r(t):vr(t)},xr=function(e,t){return e===`h`&&t===`rtl`?-1:1},Sr=function(e,t,n,r,i){var a=xr(e,window.getComputedStyle(t).direction),o=a*r,s=n.target,c=t.contains(s),l=!1,u=o>0,d=0,f=0;do{if(!s)break;var p=br(e,s),m=p[0],h=p[1]-p[2]-a*m;(m||h)&&yr(e,s)&&(d+=h,f+=m);var g=s.parentNode;s=g&&g.nodeType===Node.DOCUMENT_FRAGMENT_NODE?g.host:g}while(!c&&s!==document.body||c&&(t.contains(s)||t===s));return(u&&(i&&Math.abs(d)<1||!i&&o>d)||!u&&(i&&Math.abs(f)<1||!i&&-o>f))&&(l=!0),l},Cr=function(e){return`changedTouches`in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},wr=function(e){return[e.deltaX,e.deltaY]},Tr=function(e){return e&&`current`in e?e.current:e},Er=function(e,t){return e[0]===t[0]&&e[1]===t[1]},Dr=function(e){return`
47
47
  .block-interactivity-${e} {pointer-events: none;}
48
48
  .allow-interactivity-${e} {pointer-events: all;}
49
- `},Or=0,kr=[];function Ar(e){var t=C.useRef([]),n=C.useRef([0,0]),r=C.useRef(),i=C.useState(Or++)[0],a=C.useState(Qn)[0],o=C.useRef(e);C.useEffect(function(){o.current=e},[e]),C.useEffect(function(){if(e.inert){document.body.classList.add(`block-interactivity-${i}`);var t=Dn([e.lockRef.current],(e.shards||[]).map(Tr),!0).filter(Boolean);return t.forEach(function(e){return e.classList.add(`allow-interactivity-${i}`)}),function(){document.body.classList.remove(`block-interactivity-${i}`),t.forEach(function(e){return e.classList.remove(`allow-interactivity-${i}`)})}}},[e.inert,e.lockRef.current,e.shards]);var s=C.useCallback(function(e,t){if(`touches`in e&&e.touches.length===2||e.type===`wheel`&&e.ctrlKey)return!o.current.allowPinchZoom;var i=Cr(e),a=n.current,s=`deltaX`in e?e.deltaX:a[0]-i[0],c=`deltaY`in e?e.deltaY:a[1]-i[1],l,u=e.target,d=Math.abs(s)>Math.abs(c)?`h`:`v`;if(`touches`in e&&d===`h`&&u.type===`range`)return!1;var f=window.getSelection(),p=f&&f.anchorNode;if(p&&(p===u||p.contains(u)))return!1;var m=gr(d,u);if(!m)return!0;if(m?l=d:(l=d===`v`?`h`:`v`,m=gr(d,u)),!m)return!1;if(!r.current&&`changedTouches`in e&&(s||c)&&(r.current=l),!l)return!0;var h=r.current||l;return Sr(h,t,e,h===`h`?s:c,!0)},[]),c=C.useCallback(function(e){var n=e;if(!(!kr.length||kr[kr.length-1]!==a)){var r=`deltaY`in n?wr(n):Cr(n),i=t.current.filter(function(e){return e.name===n.type&&(e.target===n.target||n.target===e.shadowParent)&&Er(e.delta,r)})[0];if(i&&i.should){n.cancelable&&n.preventDefault();return}if(!i){var c=(o.current.shards||[]).map(Tr).filter(Boolean).filter(function(e){return e.contains(n.target)});(c.length>0?s(n,c[0]):!o.current.noIsolation)&&n.cancelable&&n.preventDefault()}}},[]),l=C.useCallback(function(e,n,r,i){var a={name:e,delta:n,target:r,should:i,shadowParent:jr(r)};t.current.push(a),setTimeout(function(){t.current=t.current.filter(function(e){return e!==a})},1)},[]),u=C.useCallback(function(e){n.current=Cr(e),r.current=void 0},[]),d=C.useCallback(function(t){l(t.type,wr(t),t.target,s(t,e.lockRef.current))},[]),f=C.useCallback(function(t){l(t.type,Cr(t),t.target,s(t,e.lockRef.current))},[]);C.useEffect(function(){return kr.push(a),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:f}),document.addEventListener(`wheel`,c,dr),document.addEventListener(`touchmove`,c,dr),document.addEventListener(`touchstart`,u,dr),function(){kr=kr.filter(function(e){return e!==a}),document.removeEventListener(`wheel`,c,dr),document.removeEventListener(`touchmove`,c,dr),document.removeEventListener(`touchstart`,u,dr)}},[]);var p=e.removeScrollBar,m=e.inert;return C.createElement(C.Fragment,null,m?C.createElement(a,{styles:Dr(i)}):null,p?C.createElement(cr,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function jr(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}var Mr=Vn(Hn,Ar),Nr=C.forwardRef(function(e,t){return C.createElement(Wn,Tn({},e,{ref:t,sideCar:Mr}))});Nr.classNames=Wn.classNames;var Pr=function(e){return typeof document>`u`?null:(Array.isArray(e)?e[0]:e).ownerDocument.body},Fr=new WeakMap,Ir=new WeakMap,Lr={},Rr=0,zr=function(e){return e&&(e.host||zr(e.parentNode))},Br=function(e,t){return t.map(function(t){if(e.contains(t))return t;var n=zr(t);return n&&e.contains(n)?n:(console.error(`aria-hidden`,t,`in not contained inside`,e,`. Doing nothing`),null)}).filter(function(e){return!!e})},Vr=function(e,t,n,r){var i=Br(t,Array.isArray(e)?e:[e]);Lr[n]||(Lr[n]=new WeakMap);var a=Lr[n],o=[],s=new Set,c=new Set(i),l=function(e){!e||s.has(e)||(s.add(e),l(e.parentNode))};i.forEach(l);var u=function(e){!e||c.has(e)||Array.prototype.forEach.call(e.children,function(e){if(s.has(e))u(e);else try{var t=e.getAttribute(r),i=t!==null&&t!==`false`,c=(Fr.get(e)||0)+1,l=(a.get(e)||0)+1;Fr.set(e,c),a.set(e,l),o.push(e),c===1&&i&&Ir.set(e,!0),l===1&&e.setAttribute(n,`true`),i||e.setAttribute(r,`true`)}catch(t){console.error(`aria-hidden: cannot operate on `,e,t)}})};return u(t),s.clear(),Rr++,function(){o.forEach(function(e){var t=Fr.get(e)-1,i=a.get(e)-1;Fr.set(e,t),a.set(e,i),t||(Ir.has(e)||e.removeAttribute(r),Ir.delete(e)),i||e.removeAttribute(n)}),Rr--,Rr||(Fr=new WeakMap,Fr=new WeakMap,Ir=new WeakMap,Lr={})}},Hr=function(e,t,n){n===void 0&&(n=`data-aria-hidden`);var r=Array.from(Array.isArray(e)?e:[e]),i=t||Pr(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll(`[aria-live], script`))),Vr(r,i,n,`aria-hidden`)):function(){return null}},Ur=Object.defineProperty,Wr=(e,t)=>Ur(e,`name`,{value:t,configurable:!0}),Gr=`Dialog`,[Kr,qr]=Be(Gr),[Jr,Yr]=Kr(Gr),Xr=Wr(e=>{let{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!0}=e,s=C.useRef(null),c=C.useRef(null),[l,u]=nt({prop:r,defaultProp:i??!1,onChange:a,caller:Gr}),[d,f]=C.useState(0),[p,m]=C.useState(0);return(0,A.jsx)(Jr,{scope:t,triggerRef:s,contentRef:c,contentId:qe(),titleId:qe(),descriptionId:qe(),titlePresent:d>0,descriptionPresent:p>0,setTitleCount:f,setDescriptionCount:m,open:l,onOpenChange:u,onOpenToggle:C.useCallback(()=>u(e=>!e),[u]),modal:o,children:n})},`Dialog`),Zr=`DialogPortal`,[Qr,$r]=Kr(Zr,{forceMount:void 0}),ei=Wr(e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=Yr(Zr,t);return(0,A.jsx)(Qr,{scope:t,forceMount:n,children:C.Children.map(r,e=>(0,A.jsx)(fn,{present:n||a.open,children:(0,A.jsx)(cn,{asChild:!0,container:i,children:e})}))})},`DialogPortal`),ti=`DialogOverlay`,ni=C.forwardRef(Wr(function(e,t){let n=$r(ti,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=Yr(ti,e.__scopeDialog);return a.modal?(0,A.jsx)(fn,{present:r||a.open,children:(0,A.jsx)(ii,{...i,ref:t})}):null},`DialogOverlay`)),ri=ut(`DialogOverlay.RemoveScroll`),ii=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(ti,n),a=Fe(t,Lt());return(0,A.jsx)(Nr,{as:ri,allowPinchZoom:!0,shards:[i.contentRef],children:(0,A.jsx)(Tt.div,{"data-state":gi(i.open),...r,ref:a,style:{pointerEvents:`auto`,...r.style}})})},`DialogOverlayImpl`)),ai=`DialogContent`,oi=C.forwardRef(Wr(function(e,t){let n=$r(ai,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=Yr(ai,e.__scopeDialog);return(0,A.jsx)(fn,{present:r||a.open,children:a.modal?(0,A.jsx)(si,{...i,ref:t}):(0,A.jsx)(ci,{...i,ref:t})})},`DialogContent`)),si=C.forwardRef(Wr(function(e,t){let n=Yr(ai,e.__scopeDialog),r=C.useRef(null),i=Fe(t,n.contentRef,r);return C.useEffect(()=>{let e=r.current;if(e)return Hr(e)},[]),(0,A.jsx)(li,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,onCloseAutoFocus:Ee(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:Ee(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:Ee(e.onFocusOutside,e=>e.preventDefault())})},`DialogContentModal`)),ci=C.forwardRef(Wr(function(e,t){let n=Yr(ai,e.__scopeDialog),r=C.useRef(!1),i=C.useRef(!1);return(0,A.jsx)(li,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})},`DialogContentNonModal`)),li=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,...o}=e,s=Yr(ai,n);return Cn(),(0,A.jsx)(A.Fragment,{children:(0,A.jsx)(Jt,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,A.jsx)(It,{role:`dialog`,id:s.contentId,"aria-describedby":s.descriptionPresent?s.descriptionId:void 0,"aria-labelledby":s.titlePresent?s.titleId:void 0,"data-state":gi(s.open),...o,ref:t,deferPointerDownOutside:!0,onDismiss:()=>s.onOpenChange(!1)})})})},`DialogContentImpl`)),ui=`DialogTitle`,di=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(ui,n),{setTitleCount:a}=i;return He(()=>(a(e=>e+1),()=>a(e=>e-1)),[a]),(0,A.jsx)(Tt.h2,{id:i.titleId,...r,ref:t})},`DialogTitle`)),fi=`DialogDescription`,pi=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(fi,n),{setDescriptionCount:a}=i;return He(()=>(a(e=>e+1),()=>a(e=>e-1)),[a]),(0,A.jsx)(Tt.p,{id:i.descriptionId,...r,ref:t})},`DialogDescription`)),mi=`DialogClose`,hi=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(mi,n);return(0,A.jsx)(Tt.button,{type:`button`,...r,ref:t,onClick:Ee(e.onClick,()=>i.onOpenChange(!1))})},`DialogClose`));function gi(e){return e?`open`:`closed`}Wr(gi,`getState`);function _i(...e){return e.filter(Boolean).join(` `)}function vi({className:e,...t}){return(0,A.jsx)(`button`,{className:_i(`ui-button`,e),...t})}function yi({className:e,...t}){return(0,A.jsx)(`span`,{className:_i(`ui-badge`,e),...t})}function bi({open:e,onOpenChange:t,title:n,description:r,children:i}){return(0,A.jsx)(Xr,{open:e,onOpenChange:t,children:(0,A.jsxs)(ei,{children:[(0,A.jsx)(ni,{className:`sheet-overlay`}),(0,A.jsxs)(oi,{className:`sheet-content`,children:[(0,A.jsxs)(`div`,{className:`sheet-head`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(di,{children:n}),r?(0,A.jsx)(pi,{children:r}):null]}),(0,A.jsx)(hi,{asChild:!0,children:(0,A.jsx)(vi,{"aria-label":`关闭面板`,className:`icon-button`,children:(0,A.jsx)(me,{size:18})})})]}),i]})]})})}function xi({title:e,detail:t}){return(0,A.jsxs)(`div`,{className:`empty-state`,children:[(0,A.jsx)(`strong`,{children:e}),t?(0,A.jsx)(`p`,{children:t}):null]})}var Si=[`codex`,`claude`,`pi`,`builtin-pi`],Ci={overview:`概览`,conversation:`对话`,configuration:`配置`,reminders:`提醒`,workspace:`工作区`,logs:`日志`},wi=window.__LARKIN_DASHBOARD__||{packageVersion:`dev`,dashboardVersion:`dev`,buildFingerprint:``,csrfCapability:``},Ti=class extends Error{body;constructor(e,t){super(e),this.body=t}};async function Ei(e,t={}){let n=await fetch(e,{cache:`no-store`,...t});if(!n.ok){let e=await n.json().catch(()=>({}));throw new Ti(e.error||`HTTP ${n.status}`,e)}return n.json()}function Di(e,t){if(!(e instanceof Ti)||!e.body.readiness)return e instanceof Error?e.message:t;let n=e.body.readiness;return[n.reason||e.message,n.nextAction?`下一步:${n.nextAction}`:null].filter(Boolean).join(`;`)}async function Oi(e){return Ei(`/api/config`,{method:`PATCH`,headers:{"Content-Type":`application/json`,"X-Larkin-CSRF":wi.csrfCapability},body:JSON.stringify(e)})}function ki(e,t={}){return Ei(e,{...t,headers:{...t.headers,"X-Larkin-CSRF":wi.csrfCapability}})}function Ai(e){return e==null||!Number.isFinite(e)?`暂无`:e<60?`${e}s 前`:e<3600?`${Math.floor(e/60)}m 前`:e<86400?`${Math.floor(e/3600)}h 前`:`${Math.floor(e/86400)}d 前`}function ji(e){let t=new Date(String(e||``));return Number.isFinite(t.getTime())?t.toLocaleString(`zh-CN`,{hour12:!1}):`未知时间`}function Mi(e){return Number.isFinite(Number(e))?Number(e).toLocaleString(`en-US`):`—`}function Ni(e){return e.issue?`异常`:e.lastActivity?.state?String(e.lastActivity.state):e.running?`空闲`:`离线`}function Pi(){let[e,t]=(0,C.useState)(null),[n,r]=(0,C.useState)(null),[i,a]=(0,C.useState)(!0),[o,s]=(0,C.useState)(!1),c=(0,C.useRef)(Se()),l=(0,C.useRef)(null),u=(0,C.useRef)(null),d=(0,C.useCallback)(async()=>{u.current?.abort();let e=new AbortController;u.current=e;let n=c.current.issue();try{let i=await Ei(`/api/status`,{signal:e.signal});if(!c.current.accepts(n))return;if(wi.buildFingerprint&&i.buildFingerprint&&i.buildFingerprint!==wi.buildFingerprint){window.location.reload();return}t(i),r(null),s(!1)}catch(t){if(e.signal.aborted||!c.current.accepts(n))return;r(t instanceof Error?t.message:String(t)),s(!0)}finally{c.current.accepts(n)&&a(!1)}},[]);return(0,C.useEffect)(()=>{let e=!0,t=async()=>{await d(),e&&(l.current=window.setTimeout(t,3e3))};return t(),()=>{e=!1,l.current!==null&&window.clearTimeout(l.current),u.current?.abort()}},[d]),{data:e,error:n,loading:i,stale:o,retry:d}}function Fi({agent:e}){let t=(e.displayName||e.name||e.agentId).slice(0,2).toUpperCase(),[n,r]=(0,C.useState)(!1);(0,C.useEffect)(()=>r(!1),[e.agentId,e.bot?.hasAvatar]);let i=e.bot?.hasAvatar&&!n;return(0,A.jsx)(`span`,{className:`agent-avatar`,"aria-hidden":`true`,children:i?(0,A.jsx)(`img`,{src:`/api/avatar/${encodeURIComponent(e.agentId)}`,alt:``,onError:()=>r(!0)}):t})}function Ii({agents:e,selectedId:t,onSelect:n,compact:r=!1}){let[i,a]=(0,C.useState)(``),o=(0,C.useMemo)(()=>be(e,i),[e,i]),s=(0,C.useRef)([]),c=(e,t)=>{if(!o.length)return;let n=t===0?e:(e+t+o.length)%o.length;s.current[n]?.focus()};return(0,A.jsxs)(`div`,{className:_i(`sidebar-inner`,r&&`compact-sidebar`),children:[(0,A.jsxs)(`label`,{className:`agent-search`,children:[(0,A.jsx)(de,{size:15,"aria-hidden":`true`}),(0,A.jsx)(`span`,{className:`sr-only`,children:`搜索 Agent`}),(0,A.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`搜索 Agent…`})]}),(0,A.jsxs)(`div`,{className:`agent-list`,role:`listbox`,"aria-label":`Agent 列表`,children:[o.map((e,r)=>(0,A.jsxs)(`button`,{ref:e=>{s.current[r]=e},type:`button`,role:`option`,"aria-selected":e.agentId===t,className:_i(`agent-list-item`,e.agentId===t&&`selected`),onClick:()=>n(e.agentId),onKeyDown:e=>{e.key===`ArrowDown`&&(e.preventDefault(),c(r,1)),e.key===`ArrowUp`&&(e.preventDefault(),c(r,-1)),e.key===`Home`&&(e.preventDefault(),c(0,0)),e.key===`End`&&(e.preventDefault(),c(o.length-1,0))},children:[(0,A.jsx)(Fi,{agent:e}),(0,A.jsxs)(`span`,{className:`agent-list-copy`,children:[(0,A.jsx)(`strong`,{children:e.displayName||e.name}),(0,A.jsxs)(`small`,{children:[e.runtime,` · `,e.model,e.runtimeReadiness?` · ${e.runtimeReadiness.state}`:``]}),(0,A.jsxs)(`span`,{className:`status-line`,children:[(0,A.jsx)(ie,{size:10}),` `,e.connection.state,` · `,Ni(e)]})]}),e.issue?(0,A.jsx)(k,{size:15,className:`danger`,"aria-label":`异常`}):(0,A.jsx)(re,{size:15,"aria-hidden":`true`})]},e.agentId)),o.length?null:(0,A.jsx)(xi,{title:`没有匹配的 Agent`,detail:`可按名称或 App ID 搜索`})]})]})}function Li({open:e,onOpenChange:t,onSaved:n}){let[r,i]=(0,C.useState)(`require`),[a,o]=(0,C.useState)(`require`),[s,c]=(0,C.useState)(!1),[l,u]=(0,C.useState)(null),d=a!==r;return(0,C.useEffect)(()=>{if(!e)return;let t=new AbortController;return c(!0),ki(`/api/config`,{signal:t.signal}).then(e=>{i(e.mentionPolicy),o(e.mentionPolicy),u(null)}).catch(e=>{t.signal.aborted||u(e.message)}).finally(()=>{t.signal.aborted||c(!1)}),()=>t.abort()},[e]),(0,A.jsx)(bi,{open:e,onOpenChange:e=>{!e&&d&&!window.confirm(`全局设置尚未保存,确定放弃草稿吗?`)||t(e)},title:`全局设置`,description:`只管理所有 Agent 共同继承的默认值;高级操作仍使用 CLI。`,children:(0,A.jsxs)(`div`,{className:`settings-form`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`真人群消息默认策略`}),(0,A.jsxs)(`select`,{value:a,onChange:e=>o(e.target.value),disabled:s,children:[(0,A.jsx)(`option`,{value:`require`,children:`require · 群内需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`free · 群内无需 @`})]})]}),(0,A.jsx)(`p`,{className:`field-help`,children:`未单独设置的 Agent 和群会使用这个值。机器人消息仍必须精确 @ 当前 Agent。`}),(0,A.jsxs)(`div`,{className:`form-actions`,children:[(0,A.jsx)(vi,{onClick:()=>o(r),disabled:!d||s,children:`放弃草稿`}),(0,A.jsx)(vi,{className:`primary`,onClick:async()=>{c(!0);try{let e=await Oi({operation:`set-global-mention`,value:a});i(a),u(`已保存 · ${e.applyState} · ${e.revision.slice(0,20)}…`),n()}catch(e){u(e instanceof Error?e.message:String(e))}finally{c(!1)}},disabled:!d||s,children:s?`保存中…`:`保存全局设置`})]}),l?(0,A.jsx)(`p`,{role:`status`,className:`feedback`,children:l}):null]})})}function Ri({config:e,onSave:t,disabled:n}){let[r,i]=(0,C.useState)(``),[a,o]=(0,C.useState)(``),[s,c]=(0,C.useState)(`require`),l=e.knownChats.filter(e=>e.kind===`group`&&e.override!==`inherit`).filter(e=>{let t=r.trim().toLocaleLowerCase();return!t||e.chatId.toLocaleLowerCase().includes(t)||String(e.displayName||`未解析群名`).toLocaleLowerCase().includes(t)});return(0,A.jsxs)(`section`,{className:`group-policy-section`,"aria-labelledby":`group-policy-title`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{id:`group-policy-title`,children:`群策略`}),(0,A.jsx)(`p`,{children:`这里只显示为当前 Agent 单独设置过消息策略的群。`})]}),(0,A.jsxs)(`label`,{className:`table-search`,children:[(0,A.jsx)(de,{size:14}),(0,A.jsx)(`span`,{className:`sr-only`,children:`搜索群`}),(0,A.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`搜索群名或 chat_id`})]})]}),(0,A.jsxs)(`div`,{className:`policy-table-wrap`,children:[(0,A.jsxs)(`table`,{className:`policy-table`,children:[(0,A.jsx)(`thead`,{children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{children:`群`}),(0,A.jsx)(`th`,{children:`消息策略`}),(0,A.jsx)(`th`,{children:`操作`})]})}),(0,A.jsx)(`tbody`,{children:l.map(e=>(0,A.jsxs)(`tr`,{children:[(0,A.jsxs)(`td`,{children:[(0,A.jsx)(`strong`,{children:e.displayName||e.chatId}),e.displayName?(0,A.jsx)(`code`,{children:e.chatId}):(0,A.jsx)(`small`,{children:`群名暂时不可用`})]}),(0,A.jsx)(`td`,{children:(0,A.jsxs)(`select`,{"aria-label":`${e.displayName||e.chatId} 消息策略`,value:e.override,disabled:n,onChange:n=>void t(e.chatId,n.target.value),children:[(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})}),(0,A.jsx)(`td`,{children:(0,A.jsx)(vi,{disabled:n,onClick:()=>void t(e.chatId,`inherit`),children:`移除特别配置`})})]},e.chatId))})]}),l.length?null:(0,A.jsx)(xi,{title:r?`没有匹配的群`:`还没有单独配置的群`,detail:`新增后,Larkin 会自动解析并缓存群名。`})]}),(0,A.jsxs)(`div`,{className:`chat-add`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`完整 chat_id`}),(0,A.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`oc_QAConfigChat1`})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`消息策略`}),(0,A.jsxs)(`select`,{value:s,onChange:e=>c(e.target.value),children:[(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})]}),(0,A.jsx)(vi,{disabled:n||!/^oc_[A-Za-z0-9_-]+$/.test(a),onClick:()=>void t(a,s).then(()=>o(``)),children:`保存群策略`})]})]})}function zi({agentId:e,onDirtyChange:t,refreshKey:n}){let[r,i]=(0,C.useState)(null),[a,o]=(0,C.useState)({}),[s,c]=(0,C.useState)({}),[l,u]=(0,C.useState)(!0),[d,f]=(0,C.useState)(null),[p,m]=(0,C.useState)(null),h=(0,C.useRef)(Se()),g=(0,C.useRef)(Se()),_=!xe(a,s);(0,C.useEffect)(()=>t(_),[_,t]),(0,C.useEffect)(()=>{let e=e=>{_&&e.preventDefault()};return window.addEventListener(`beforeunload`,e),()=>window.removeEventListener(`beforeunload`,e)},[_]);let v=(0,C.useCallback)(async(t=!1)=>{let n=h.current.issue();u(!0);try{let r=await ki(`/api/config?agent=${encodeURIComponent(e)}`);if(!h.current.accepts(n))return;let a=r.agents[0];if(!a)throw Error(`配置投影中没有当前 Agent`);let s={runtime:a.runtimeOption||a.runtime,model:a.model,effort:a.effort||`default`,mention:a.mention.override};i(r),o(s),(!t||!_)&&c(s)}catch(e){h.current.accepts(n)&&f(e instanceof Error?e.message:String(e))}finally{h.current.accepts(n)&&u(!1)}},[e,_]);(0,C.useEffect)(()=>{v(!1)},[e]),(0,C.useEffect)(()=>{n>0&&v(!0)},[n]);let y=r?.agents[0],b=(e,t)=>c(n=>({...n,[e]:t})),x=String(s.runtime||y?.runtime||``),S=String(s.model||y?.model||`default`);(0,C.useEffect)(()=>{if(m({runtime:x,status:`loading`,models:[]}),!new Set([`pi`,`builtin-pi`,`codex`,`claude`]).has(x)){m({runtime:x,status:`error`,models:[]});return}let t=g.current.issue(),n=new AbortController;return ki(`/api/models/${x}?agent=${encodeURIComponent(e)}`,{signal:n.signal}).then(e=>{g.current.accepts(t)&&m({runtime:x,status:`ready`,models:e.models})}).catch(e=>{!n.signal.aborted&&g.current.accepts(t)&&(m({runtime:x,status:`error`,models:[]}),f(`${x} 模型列表暂时不可用:${e instanceof Error?e.message:String(e)}`))}),()=>n.abort()},[e,x]);let w=p?.runtime===x&&p.status===`ready`,ee=p?.runtime===x?p.status:`loading`,te=w?p.models:[],T=`${S} · 当前配置(模型目录${ee===`error`?`不可用`:`加载中`})`,E=w?te.some(e=>e.id===S)?te:[{id:S,label:`${S} · 当前配置`},...te]:[{id:S,label:T}],ne=w&&S!=="default"&&te.find(e=>e.id===S)?.supportedReasoningEfforts||[],re=s.runtime!==a.runtime||s.model!==a.model||s.effort!==a.effort,ie=()=>Ei(`/api/config/apply`,{method:`POST`,headers:{"Content-Type":`application/json`,"X-Larkin-CSRF":wi.csrfCapability},body:JSON.stringify({agentId:e})}),ae=async()=>{if(y){u(!0);try{let t=[];s.runtime===a.runtime?s.model!==a.model&&t.push({operation:`set-agent-model`,agentId:e,model:s.model}):t.push({operation:`set-agent-runtime`,agentId:e,runtime:s.runtime,model:s.model}),s.effort!==a.effort&&t.push({operation:`set-agent-effort`,agentId:e,effort:s.effort}),s.mention!==a.mention&&t.push({operation:`set-agent-mention`,agentId:e,value:s.mention});let n=null;for(let e of t)n=await Oi(e);if(re)try{await ie(),f(`配置已保存并应用`)}catch(e){f(`配置已保存但未应用:${Di(e,`稍后重试`)}`)}else f(n?`配置已保存`:`没有需要保存的修改`);await v(!1)}catch(e){f(e instanceof Error?e.message:String(e))}finally{u(!1)}}},oe=async()=>{u(!0);try{await ie(),f(`已应用保存的运行配置`),await v(!0)}catch(e){f(`配置保持已保存但未应用:${Di(e,`稍后重试`)}`)}finally{u(!1)}},se=async(t,n)=>{u(!0);try{let r=await Oi({operation:`set-chat-mention`,agentId:e,chatId:t,value:n});f(`群策略已保存 · ${r.applyState}`),await v(!0)}catch(e){f(e instanceof Error?e.message:String(e))}finally{u(!1)}};if(!y&&l)return(0,A.jsx)(xi,{title:`加载 Agent 配置…`});if(!y)return(0,A.jsx)(xi,{title:`配置不可用`,detail:d||`请重试`});let ce=y.apply.applyState===`pending`?`待应用`:y.apply.applyState===`applied`?`已应用`:`状态未知`;return(0,A.jsxs)(`div`,{className:`configuration-page`,children:[(0,A.jsxs)(`section`,{className:`config-section`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`Agent 配置`}),(0,A.jsxs)(`p`,{children:[`只作用于 `,e,`;运行配置会在安全时机自动应用,Agent 正忙时会保留待处理。`]})]}),(0,A.jsx)(yi,{className:y.apply.applyState===`pending`?`warning`:`success`,children:ce})]}),(0,A.jsxs)(`p`,{className:`cli-guidance`,children:[`这里只提供常用微调。更多配置可以直接告诉当前 Agent,让它运行 `,(0,A.jsx)(`code`,{children:`larkin config --help`}),` 后完成。`]}),(0,A.jsxs)(`div`,{className:`config-grid`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Runtime`}),(0,A.jsx)(`select`,{value:x,disabled:l,onChange:e=>{b(`runtime`,e.target.value),b(`model`,`default`),b(`effort`,`default`)},children:(r?.runtimeOptions||Si).map(e=>(0,A.jsx)(`option`,{value:e,children:e},e))})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Model`}),(0,A.jsx)(`select`,{value:S,disabled:l||!w,onChange:e=>{b(`model`,e.target.value),e.target.value==="default"&&b(`effort`,`default`)},children:E.map(e=>(0,A.jsx)(`option`,{value:e.id,children:e.label||e.id},e.id))})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Effort`}),(0,A.jsxs)(`select`,{value:String(s.effort||`default`),disabled:l||!w||S==="default"||!ne.length,onChange:e=>b(`effort`,e.target.value),children:[(0,A.jsx)(`option`,{value:`default`,children:`default · 不指定`}),ne.map(e=>(0,A.jsx)(`option`,{value:e,children:e},e))]})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`群消息策略`}),(0,A.jsxs)(`select`,{value:String(s.mention||`inherit`),disabled:l,onChange:e=>b(`mention`,e.target.value),children:[(0,A.jsx)(`option`,{value:`inherit`,children:`跟随全局设置`}),(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})]})]}),_||y.apply.applyState===`pending`?(0,A.jsxs)(`div`,{className:`form-actions`,children:[_?(0,A.jsx)(vi,{disabled:l,onClick:()=>c(a),children:`放弃草稿`}):null,_?(0,A.jsx)(vi,{className:`primary`,disabled:l,onClick:ae,children:l?`处理中…`:re?`保存并应用`:`保存 Agent 配置`}):(0,A.jsxs)(vi,{className:`primary`,disabled:l,onClick:oe,children:[(0,A.jsx)(ue,{size:14}),` 应用已保存配置`]})]}):null,d?(0,A.jsx)(`p`,{role:`status`,className:`feedback`,children:d}):null]}),(0,A.jsx)(Ri,{config:y,onSave:se,disabled:l})]})}function Bi({agent:e}){let t=e.session?.usage||{},n=e.session?.compaction||{};return(0,A.jsxs)(`div`,{className:`overview-page`,children:[(0,A.jsxs)(`div`,{className:`metrics-band`,"aria-label":`Agent 运行指标`,children:[(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ie,{})}),(0,A.jsx)(`span`,{children:`连接`}),(0,A.jsx)(`strong`,{children:e.connection.state}),(0,A.jsx)(`small`,{children:e.connection.reason})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(E,{})}),(0,A.jsx)(`span`,{children:`执行状态`}),(0,A.jsx)(`strong`,{children:Ni(e)}),(0,A.jsx)(`small`,{children:e.lastActivity?Ai(e.lastActivity.ageSec):`尚无活动`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(le,{})}),(0,A.jsx)(`span`,{children:`最近消息`}),(0,A.jsx)(`strong`,{children:e.lastDeliver?.from||`暂无`}),(0,A.jsx)(`small`,{children:e.lastDeliver?`${e.lastDeliver.target||``} · ${Ai(e.lastDeliver.ageSec)}`:`尚无消息记录`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ce,{})}),(0,A.jsx)(`span`,{children:`上下文`}),(0,A.jsx)(`strong`,{children:t.contextPercent==null?`${e.session?.turns||0} turns`:`${Math.round(Number(t.contextPercent))}%`}),(0,A.jsx)(`small`,{children:e.session?`session ${e.session.id.slice(0,12)}…`:`尚未建立 session`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ce,{})}),(0,A.jsx)(`span`,{children:`累计 Token`}),(0,A.jsx)(`strong`,{children:Mi(t.cumulativeTokens)}),(0,A.jsx)(`small`,{children:t.available?`完整 session 累计`:t.reason||`暂无用量`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(E,{})}),(0,A.jsx)(`span`,{children:`最近一轮用量`}),(0,A.jsx)(`strong`,{children:Mi(t.latestTokens)}),(0,A.jsx)(`small`,{children:`输入、缓存与输出合计`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ue,{})}),(0,A.jsx)(`span`,{children:`Compaction`}),(0,A.jsx)(`strong`,{children:Mi(n.count)}),(0,A.jsx)(`small`,{children:n.active?`正在压缩上下文`:n.lastFinishedAt?`最近 ${ji(n.lastFinishedAt)}`:`尚无压缩记录`})]})]}),(0,A.jsxs)(`div`,{className:`overview-sections`,children:[(0,A.jsxs)(`section`,{className:`content-section wide`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`最近动态`}),(0,A.jsx)(`p`,{children:`当前 Agent 的消息、工具和状态转换。`})]}),(0,A.jsx)(yi,{children:e.feed.length})]}),(0,A.jsx)(M,{items:e.feed.slice(0,10)})]}),(0,A.jsxs)(`section`,{className:`content-section`,children:[(0,A.jsx)(`h3`,{children:`运行摘要`}),(0,A.jsxs)(`dl`,{className:`facts`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Runtime`}),(0,A.jsx)(`dd`,{children:e.runtime})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Readiness`}),(0,A.jsx)(`dd`,{children:e.runtimeReadiness?`${e.runtimeReadiness.state}${e.runtimeReadiness.version?` · ${e.runtimeReadiness.version}`:``}${e.runtimeReadiness.executable?` · ${e.runtimeReadiness.executable}`:``}${e.runtimeReadiness.reason?` · ${e.runtimeReadiness.reason}`:``}${e.runtimeReadiness.nextAction?` · ${e.runtimeReadiness.nextAction}`:``}`:`unknown`})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Model`}),(0,A.jsx)(`dd`,{children:e.model})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Effort`}),(0,A.jsx)(`dd`,{children:e.effort||`default`})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`入站`}),(0,A.jsx)(`dd`,{children:e.inbound.state})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`提醒`}),(0,A.jsx)(`dd`,{children:e.activeReminders})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`指示灯`}),(0,A.jsx)(`dd`,{children:e.eyeIndicator.stuck?`疑似卡住`:`${e.eyeIndicator.pendingCount} pending`})]})]})]})]})]})}function M({items:e}){if(!e.length)return(0,A.jsx)(xi,{title:`还没有活动记录`});let t=e=>e.kind===`error`||e.state===`error`?`state-error`:e.kind===`deliver`?`state-deliver`:[`working`,`thinking`,`tool`].includes(String(e.state))?`state-active`:[`idle`,`online`].includes(String(e.state))?`state-idle`:`state-unknown`;return(0,A.jsx)(`ol`,{className:`timeline`,children:e.map((e,n)=>(0,A.jsxs)(`li`,{children:[(0,A.jsx)(`span`,{className:_i(`timeline-dot`,t(e))}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`strong`,{children:e.kind===`deliver`?`收到消息`:e.kind===`error`?`错误`:e.state||`活动`}),(0,A.jsx)(`p`,{children:e.text||e.detail||[e.from,e.target].filter(Boolean).join(` → `)||e.tool||`状态已更新`}),(0,A.jsx)(`time`,{children:ji(e.at)})]})]},`${e.at}-${n}`))})}function N({agent:e}){return e.conversation.length?(0,A.jsx)(`div`,{className:`conversation-list`,children:e.conversation.map((t,n)=>{let r=t.direction===`out`?`已发送`:t.wake?`入站 · 已唤醒`:`旁听 · 未唤醒`;return(0,A.jsxs)(`article`,{className:_i(`conversation-item`,t.direction===`out`?`outbound`:`inbound`,t.direction===`in`&&!t.wake&&`listening`),children:[(0,A.jsxs)(`header`,{children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(yi,{className:t.direction===`in`&&!t.wake?`warning`:t.direction===`out`?`accent`:`success`,children:r}),(0,A.jsx)(`strong`,{children:t.from||(t.direction===`out`?e.displayName:`未知发送者`)})]}),(0,A.jsx)(`time`,{children:ji(t.at)})]}),(0,A.jsx)(`p`,{children:t.text||`[非文本消息]`}),t.target?(0,A.jsx)(`small`,{children:t.target}):null]},`${t.at}-${n}`)})}):(0,A.jsx)(xi,{title:`还没有对话摘录`,detail:`Host 更新本地 conversation projection 后会显示在这里。`})}function Vi({agent:e}){if(!e.remindersList.length)return(0,A.jsx)(xi,{title:`还没有提醒`});let t={scheduled:`待执行`,pending:`待执行`,fired:`已完成`,canceled:`已取消`,cancelled:`已取消`,failed:`失败`};return(0,A.jsx)(`div`,{className:`reminder-list`,children:e.remindersList.map((e,n)=>{let r=e.status||`scheduled`,i=[`fired`,`canceled`,`cancelled`].includes(r);return(0,A.jsxs)(`article`,{className:_i(`status-${r===`cancelled`?`canceled`:r}`,i&&`done`),children:[(0,A.jsx)(ne,{size:16}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`strong`,{children:e.title||`未命名提醒`}),(0,A.jsxs)(`p`,{children:[ji(e.fireAt),e.repeat?` · 重复`:``]})]}),(0,A.jsx)(yi,{className:[`scheduled`,`pending`].includes(r)?`warning`:r===`fired`?`success`:r===`failed`?`danger-badge`:``,children:t[r]||r})]},`${e.fireAt}-${n}`)})})}function Hi({agentId:e}){let[t,n]=(0,C.useState)(null),[r,i]=(0,C.useState)(null),[a,o]=(0,C.useState)(!0),[s,c]=(0,C.useState)(null),l=(0,C.useRef)(Se()),u=(0,C.useCallback)(async t=>{let r=l.current.issue();o(!0);try{let a=await Ei(`/api/workspace?agent=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`);if(!l.current.accepts(r))return;a.kind===`directory`?(n(a),i(null)):i(a),c(null)}catch(e){l.current.accepts(r)&&c(e instanceof Error?e.message:String(e))}finally{l.current.accepts(r)&&o(!1)}},[e]);(0,C.useEffect)(()=>{u(``)},[u]);let d=t?.path.split(`/`).filter(Boolean)||[];return(0,A.jsxs)(`div`,{className:`workspace-browser`,children:[(0,A.jsxs)(`div`,{className:`workspace-side`,children:[(0,A.jsxs)(`nav`,{className:`breadcrumbs`,"aria-label":`工作区路径`,children:[(0,A.jsx)(`button`,{onClick:()=>void u(``),children:`workspace`}),d.map((e,t)=>(0,A.jsxs)(`span`,{children:[(0,A.jsx)(re,{size:12}),(0,A.jsx)(`button`,{onClick:()=>void u(d.slice(0,t+1).join(`/`)),children:e})]},`${e}-${t}`))]}),(0,A.jsxs)(`div`,{className:`workspace-list`,role:`region`,"aria-label":`工作区目录`,children:[t?.entries.map(e=>(0,A.jsxs)(`button`,{onClick:()=>void u(e.path),children:[e.kind===`directory`?(0,A.jsx)(se,{size:15}):(0,A.jsx)(oe,{size:15}),(0,A.jsx)(`span`,{children:e.name}),(0,A.jsx)(`small`,{children:e.kind===`file`?`${e.size} B`:`目录`})]},e.path)),!a&&t&&!t.entries.length?(0,A.jsx)(xi,{title:`空目录`}):null]})]}),(0,A.jsx)(`div`,{className:`workspace-preview`,role:`region`,"aria-label":`文件预览`,children:a?(0,A.jsx)(xi,{title:`读取工作区…`}):s?(0,A.jsx)(xi,{title:`工作区读取失败`,detail:s}):r?(0,A.jsxs)(A.Fragment,{children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`strong`,{children:r.name}),(0,A.jsx)(`small`,{children:r.truncated?`仅预览前 1 MB`:`${r.size} B`})]}),r.binary?(0,A.jsx)(xi,{title:`二进制文件不可预览`}):(0,A.jsx)(`pre`,{children:r.content})]}):(0,A.jsx)(xi,{title:`只读工作区`,detail:`选择文件进行预览`})})]})}function Ui({agent:e}){return(0,A.jsxs)(`section`,{className:`logs-section`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`结构化日志`}),(0,A.jsx)(`p`,{children:`最近 30 条本地状态投影;不暴露原始日志文件或路径。`})]}),(0,A.jsx)(yi,{children:e.feed.length})]}),(0,A.jsx)(M,{items:e.feed}),e.recentErrors.length?(0,A.jsxs)(`section`,{className:`error-stack`,children:[(0,A.jsx)(`h4`,{children:`最近错误`}),e.recentErrors.map((e,t)=>(0,A.jsxs)(`div`,{children:[(0,A.jsx)(ae,{size:14}),(0,A.jsx)(`span`,{children:e.text||e.message||`未知错误`}),(0,A.jsx)(`time`,{children:ji(e.at)})]},`${e.at}-${t}`))]}):null]})}function Wi({agent:e}){let t=e.session?.usage||{},n=e.session?.compaction||{},r=t.contextPercent==null?`上下文 ${e.session?.turns||0} turns`:`上下文 ${Math.round(Number(t.contextPercent))}% · ${Mi(t.latestTokens)} / ${Mi(t.contextWindow)}`;return(0,A.jsxs)(`header`,{className:`agent-header`,children:[(0,A.jsxs)(`div`,{className:`agent-title`,children:[(0,A.jsx)(Fi,{agent:e}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`p`,{children:e.agentId}),(0,A.jsx)(`h1`,{children:e.displayName||e.name})]})]}),(0,A.jsxs)(`div`,{className:`agent-header-summary`,children:[(0,A.jsx)(yi,{className:e.connection.state===`connected`?`success`:e.issue?`danger-badge`:``,children:e.connection.state}),(0,A.jsx)(`span`,{children:e.runtime}),(0,A.jsx)(`span`,{children:e.model}),(0,A.jsx)(`span`,{children:e.effort||`default effort`}),(0,A.jsx)(`span`,{children:r}),(0,A.jsxs)(`span`,{children:[`累计 `,Mi(t.cumulativeTokens),` tokens`]}),(0,A.jsxs)(`span`,{children:[`最近一轮 `,Mi(t.latestTokens),` tokens`]}),(0,A.jsxs)(`span`,{children:[`Compact `,Mi(n.count),` 次 · `,n.active?`active`:`idle`]}),(0,A.jsxs)(`span`,{children:[`最近消息 `,e.lastDeliver?Ai(e.lastDeliver.ageSec):`暂无`]}),(0,A.jsx)(`span`,{children:e.lastActivity?.detail||Ni(e)})]})]})}function Gi(){let e=Pi(),t=(0,C.useMemo)(()=>_e(window.location.search),[]),[n,r]=(0,C.useState)(t.agentId),[i,a]=(0,C.useState)(t.tab),[o,s]=(0,C.useState)(!1),[c,l]=(0,C.useState)(!1),[u,d]=(0,C.useState)(0),[f,p]=(0,C.useState)(!1),[m,h]=(0,C.useState)(null),g=(0,C.useRef)(new Map),_=e.data?.agents||[],v=e.data?.packageVersion||wi.packageVersion,y=_.find(e=>e.agentId===n)||null,b=(0,C.useCallback)((e,t)=>{let n=`${e}:${t}`,r=window.scrollY;g.current.set(n,r);try{sessionStorage.setItem(`larkin-dashboard-scroll:${n}`,String(r))}catch{}},[]),x=(0,C.useCallback)(e=>{if(g.current.has(e))return g.current.get(e)||0;try{let t=Number(sessionStorage.getItem(`larkin-dashboard-scroll:${e}`));return Number.isFinite(t)&&t>=0?t:0}catch{return 0}},[]),S=(0,C.useCallback)((e,t,o=!1)=>{n&&b(n,i);let s=o?`replaceState`:`pushState`;window.history[s](null,``,ve(e,t)),r(e),a(t)},[b,n,i]),w=()=>!f||window.confirm(`Agent 配置尚未保存,确定放弃草稿吗?`),ee=e=>{w()&&(p(!1),S(e,i),l(!1))},te=e=>{e!==`configuration`&&!w()||(e!==`configuration`&&p(!1),S(n,e))};return(0,C.useEffect)(()=>{if(!e.data)return;let t=ye(n,e.data.agents);t!==n&&(h(n?`原 Agent 已不存在,已切换到第一个可用 Agent。`:null),S(t,i,!0))},[e.data,n,i,S]),(0,C.useEffect)(()=>{let e=()=>{let e=_e(window.location.search);if(f&&!window.confirm(`Agent 配置尚未保存,确定离开吗?`)){window.history.pushState(null,``,ve(n,i));return}n&&b(n,i),p(!1),r(e.agentId),a(e.tab)};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[f,b,n,i]),(0,C.useLayoutEffect)(()=>{if(!n)return;let e=`${n}:${i}`,t=x(e);window.scrollY!==t&&window.scrollTo({top:t,behavior:`auto`})},[x,n,i]),(0,A.jsxs)(`div`,{className:_i(`app-shell`,i===`workspace`&&`workspace-app-active`),children:[(0,A.jsxs)(`aside`,{className:`desktop-sidebar`,children:[(0,A.jsxs)(`div`,{className:`brand`,children:[(0,A.jsx)(`img`,{src:`/assets/larkin-mark.svg`,alt:``}),(0,A.jsxs)(`strong`,{children:[`Larkin `,(0,A.jsxs)(`span`,{className:`brand-version`,children:[`v`,v]})]})]}),(0,A.jsx)(Ii,{agents:_,selectedId:n,onSelect:ee}),(0,A.jsx)(`footer`,{children:e.data?.daemon.running?`${_.length} Agents · daemon online`:`daemon offline`})]}),(0,A.jsxs)(`main`,{className:_i(`workspace-shell`,i===`workspace`&&`workspace-route-active`),children:[(0,A.jsxs)(`div`,{className:`app-topbar`,children:[(0,A.jsx)(vi,{className:`mobile-menu`,"aria-label":`打开 Agent 导航`,onClick:()=>l(!0),children:(0,A.jsx)(O,{size:18})}),(0,A.jsxs)(`div`,{children:[(0,A.jsxs)(`strong`,{children:[`Larkin `,(0,A.jsxs)(`span`,{className:`brand-version`,children:[`v`,v]})]}),(0,A.jsx)(`span`,{children:e.data?.daemon.running?`运行中`:`未运行`}),e.stale?(0,A.jsx)(yi,{className:`warning`,children:`状态陈旧`}):null]}),(0,A.jsxs)(vi,{onClick:()=>s(!0),children:[(0,A.jsx)(fe,{size:15}),` 全局设置`]})]}),e.error?(0,A.jsxs)(`div`,{className:`stale-banner`,role:`alert`,children:[(0,A.jsx)(k,{size:15}),`状态刷新失败,保留最后一次数据:`,e.error,(0,A.jsx)(vi,{onClick:()=>void e.retry(),children:`重试`})]}):null,m?(0,A.jsxs)(`div`,{className:`notice`,role:`status`,children:[m,(0,A.jsx)(vi,{"aria-label":`关闭提示`,onClick:()=>h(null),children:`×`})]}):null,e.loading&&!e.data?(0,A.jsxs)(`div`,{className:`page-loading`,children:[(0,A.jsx)(ue,{className:`spin`}),`加载 Agent 工作台…`]}):y?(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Wi,{agent:y}),(0,A.jsx)(`nav`,{className:`agent-tabs`,"aria-label":`${y.displayName} 内容页签`,role:`tablist`,children:ge.map(e=>(0,A.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":i===e,className:i===e?`active`:``,onClick:()=>te(e),children:[e===`overview`?(0,A.jsx)(ce,{}):e===`conversation`?(0,A.jsx)(le,{}):e===`configuration`?(0,A.jsx)(pe,{}):e===`reminders`?(0,A.jsx)(ne,{}):e===`workspace`?(0,A.jsx)(se,{}):(0,A.jsx)(D,{}),Ci[e]]},e))}),(0,A.jsxs)(`div`,{className:_i(`agent-content`,i===`workspace`&&`workspace-active`),children:[(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`overview`,children:(0,A.jsx)(Bi,{agent:y})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`conversation`,children:(0,A.jsx)(N,{agent:y})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`configuration`,children:(0,A.jsx)(zi,{agentId:y.agentId,onDirtyChange:p,refreshKey:u})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`reminders`,children:(0,A.jsx)(Vi,{agent:y})}),(0,A.jsx)(`section`,{className:`workspace-tab-panel`,role:`tabpanel`,hidden:i!==`workspace`,children:(0,A.jsx)(Hi,{agentId:y.agentId})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`logs`,children:(0,A.jsx)(Ui,{agent:y})})]},y.agentId)]}):(0,A.jsx)(xi,{title:`还没有配置任何 Agent`,detail:`先运行 larkin setup。`})]}),(0,A.jsx)(Li,{open:o,onOpenChange:s,onSaved:()=>d(e=>e+1)}),(0,A.jsx)(bi,{open:c,onOpenChange:l,title:`选择 Agent`,description:`搜索、查看状态并切换当前工作区。`,children:(0,A.jsx)(Ii,{compact:!0,agents:_,selectedId:n,onSelect:ee})})]})}var Ki=document.getElementById(`root`);if(!Ki)throw Error(`dashboard root is missing`);(0,he.createRoot)(Ki).render((0,A.jsx)(C.StrictMode,{children:(0,A.jsx)(Gi,{})}));
49
+ `},Or=0,kr=[];function Ar(e){var t=C.useRef([]),n=C.useRef([0,0]),r=C.useRef(),i=C.useState(Or++)[0],a=C.useState(Qn)[0],o=C.useRef(e);C.useEffect(function(){o.current=e},[e]),C.useEffect(function(){if(e.inert){document.body.classList.add(`block-interactivity-${i}`);var t=Dn([e.lockRef.current],(e.shards||[]).map(Tr),!0).filter(Boolean);return t.forEach(function(e){return e.classList.add(`allow-interactivity-${i}`)}),function(){document.body.classList.remove(`block-interactivity-${i}`),t.forEach(function(e){return e.classList.remove(`allow-interactivity-${i}`)})}}},[e.inert,e.lockRef.current,e.shards]);var s=C.useCallback(function(e,t){if(`touches`in e&&e.touches.length===2||e.type===`wheel`&&e.ctrlKey)return!o.current.allowPinchZoom;var i=Cr(e),a=n.current,s=`deltaX`in e?e.deltaX:a[0]-i[0],c=`deltaY`in e?e.deltaY:a[1]-i[1],l,u=e.target,d=Math.abs(s)>Math.abs(c)?`h`:`v`;if(`touches`in e&&d===`h`&&u.type===`range`)return!1;var f=window.getSelection(),p=f&&f.anchorNode;if(p&&(p===u||p.contains(u)))return!1;var m=gr(d,u);if(!m)return!0;if(m?l=d:(l=d===`v`?`h`:`v`,m=gr(d,u)),!m)return!1;if(!r.current&&`changedTouches`in e&&(s||c)&&(r.current=l),!l)return!0;var h=r.current||l;return Sr(h,t,e,h===`h`?s:c,!0)},[]),c=C.useCallback(function(e){var n=e;if(!(!kr.length||kr[kr.length-1]!==a)){var r=`deltaY`in n?wr(n):Cr(n),i=t.current.filter(function(e){return e.name===n.type&&(e.target===n.target||n.target===e.shadowParent)&&Er(e.delta,r)})[0];if(i&&i.should){n.cancelable&&n.preventDefault();return}if(!i){var c=(o.current.shards||[]).map(Tr).filter(Boolean).filter(function(e){return e.contains(n.target)});(c.length>0?s(n,c[0]):!o.current.noIsolation)&&n.cancelable&&n.preventDefault()}}},[]),l=C.useCallback(function(e,n,r,i){var a={name:e,delta:n,target:r,should:i,shadowParent:jr(r)};t.current.push(a),setTimeout(function(){t.current=t.current.filter(function(e){return e!==a})},1)},[]),u=C.useCallback(function(e){n.current=Cr(e),r.current=void 0},[]),d=C.useCallback(function(t){l(t.type,wr(t),t.target,s(t,e.lockRef.current))},[]),f=C.useCallback(function(t){l(t.type,Cr(t),t.target,s(t,e.lockRef.current))},[]);C.useEffect(function(){return kr.push(a),e.setCallbacks({onScrollCapture:d,onWheelCapture:d,onTouchMoveCapture:f}),document.addEventListener(`wheel`,c,dr),document.addEventListener(`touchmove`,c,dr),document.addEventListener(`touchstart`,u,dr),function(){kr=kr.filter(function(e){return e!==a}),document.removeEventListener(`wheel`,c,dr),document.removeEventListener(`touchmove`,c,dr),document.removeEventListener(`touchstart`,u,dr)}},[]);var p=e.removeScrollBar,m=e.inert;return C.createElement(C.Fragment,null,m?C.createElement(a,{styles:Dr(i)}):null,p?C.createElement(cr,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function jr(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}var Mr=Vn(Hn,Ar),Nr=C.forwardRef(function(e,t){return C.createElement(Wn,Tn({},e,{ref:t,sideCar:Mr}))});Nr.classNames=Wn.classNames;var Pr=function(e){return typeof document>`u`?null:(Array.isArray(e)?e[0]:e).ownerDocument.body},Fr=new WeakMap,Ir=new WeakMap,Lr={},Rr=0,zr=function(e){return e&&(e.host||zr(e.parentNode))},Br=function(e,t){return t.map(function(t){if(e.contains(t))return t;var n=zr(t);return n&&e.contains(n)?n:(console.error(`aria-hidden`,t,`in not contained inside`,e,`. Doing nothing`),null)}).filter(function(e){return!!e})},Vr=function(e,t,n,r){var i=Br(t,Array.isArray(e)?e:[e]);Lr[n]||(Lr[n]=new WeakMap);var a=Lr[n],o=[],s=new Set,c=new Set(i),l=function(e){!e||s.has(e)||(s.add(e),l(e.parentNode))};i.forEach(l);var u=function(e){!e||c.has(e)||Array.prototype.forEach.call(e.children,function(e){if(s.has(e))u(e);else try{var t=e.getAttribute(r),i=t!==null&&t!==`false`,c=(Fr.get(e)||0)+1,l=(a.get(e)||0)+1;Fr.set(e,c),a.set(e,l),o.push(e),c===1&&i&&Ir.set(e,!0),l===1&&e.setAttribute(n,`true`),i||e.setAttribute(r,`true`)}catch(t){console.error(`aria-hidden: cannot operate on `,e,t)}})};return u(t),s.clear(),Rr++,function(){o.forEach(function(e){var t=Fr.get(e)-1,i=a.get(e)-1;Fr.set(e,t),a.set(e,i),t||(Ir.has(e)||e.removeAttribute(r),Ir.delete(e)),i||e.removeAttribute(n)}),Rr--,Rr||(Fr=new WeakMap,Fr=new WeakMap,Ir=new WeakMap,Lr={})}},Hr=function(e,t,n){n===void 0&&(n=`data-aria-hidden`);var r=Array.from(Array.isArray(e)?e:[e]),i=t||Pr(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll(`[aria-live], script`))),Vr(r,i,n,`aria-hidden`)):function(){return null}},Ur=Object.defineProperty,Wr=(e,t)=>Ur(e,`name`,{value:t,configurable:!0}),Gr=`Dialog`,[Kr,qr]=Be(Gr),[Jr,Yr]=Kr(Gr),Xr=Wr(e=>{let{__scopeDialog:t,children:n,open:r,defaultOpen:i,onOpenChange:a,modal:o=!0}=e,s=C.useRef(null),c=C.useRef(null),[l,u]=nt({prop:r,defaultProp:i??!1,onChange:a,caller:Gr}),[d,f]=C.useState(0),[p,m]=C.useState(0);return(0,A.jsx)(Jr,{scope:t,triggerRef:s,contentRef:c,contentId:qe(),titleId:qe(),descriptionId:qe(),titlePresent:d>0,descriptionPresent:p>0,setTitleCount:f,setDescriptionCount:m,open:l,onOpenChange:u,onOpenToggle:C.useCallback(()=>u(e=>!e),[u]),modal:o,children:n})},`Dialog`),Zr=`DialogPortal`,[Qr,$r]=Kr(Zr,{forceMount:void 0}),ei=Wr(e=>{let{__scopeDialog:t,forceMount:n,children:r,container:i}=e,a=Yr(Zr,t);return(0,A.jsx)(Qr,{scope:t,forceMount:n,children:C.Children.map(r,e=>(0,A.jsx)(fn,{present:n||a.open,children:(0,A.jsx)(cn,{asChild:!0,container:i,children:e})}))})},`DialogPortal`),ti=`DialogOverlay`,ni=C.forwardRef(Wr(function(e,t){let n=$r(ti,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=Yr(ti,e.__scopeDialog);return a.modal?(0,A.jsx)(fn,{present:r||a.open,children:(0,A.jsx)(ii,{...i,ref:t})}):null},`DialogOverlay`)),ri=ut(`DialogOverlay.RemoveScroll`),ii=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(ti,n),a=Fe(t,Lt());return(0,A.jsx)(Nr,{as:ri,allowPinchZoom:!0,shards:[i.contentRef],children:(0,A.jsx)(Tt.div,{"data-state":gi(i.open),...r,ref:a,style:{pointerEvents:`auto`,...r.style}})})},`DialogOverlayImpl`)),ai=`DialogContent`,oi=C.forwardRef(Wr(function(e,t){let n=$r(ai,e.__scopeDialog),{forceMount:r=n.forceMount,...i}=e,a=Yr(ai,e.__scopeDialog);return(0,A.jsx)(fn,{present:r||a.open,children:a.modal?(0,A.jsx)(si,{...i,ref:t}):(0,A.jsx)(ci,{...i,ref:t})})},`DialogContent`)),si=C.forwardRef(Wr(function(e,t){let n=Yr(ai,e.__scopeDialog),r=C.useRef(null),i=Fe(t,n.contentRef,r);return C.useEffect(()=>{let e=r.current;if(e)return Hr(e)},[]),(0,A.jsx)(li,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:n.open,onCloseAutoFocus:Ee(e.onCloseAutoFocus,e=>{e.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:Ee(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()}),onFocusOutside:Ee(e.onFocusOutside,e=>e.preventDefault())})},`DialogContentModal`)),ci=C.forwardRef(Wr(function(e,t){let n=Yr(ai,e.__scopeDialog),r=C.useRef(!1),i=C.useRef(!1);return(0,A.jsx)(li,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})},`DialogContentNonModal`)),li=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,...o}=e,s=Yr(ai,n);return Cn(),(0,A.jsx)(A.Fragment,{children:(0,A.jsx)(Jt,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,A.jsx)(It,{role:`dialog`,id:s.contentId,"aria-describedby":s.descriptionPresent?s.descriptionId:void 0,"aria-labelledby":s.titlePresent?s.titleId:void 0,"data-state":gi(s.open),...o,ref:t,deferPointerDownOutside:!0,onDismiss:()=>s.onOpenChange(!1)})})})},`DialogContentImpl`)),ui=`DialogTitle`,di=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(ui,n),{setTitleCount:a}=i;return He(()=>(a(e=>e+1),()=>a(e=>e-1)),[a]),(0,A.jsx)(Tt.h2,{id:i.titleId,...r,ref:t})},`DialogTitle`)),fi=`DialogDescription`,pi=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(fi,n),{setDescriptionCount:a}=i;return He(()=>(a(e=>e+1),()=>a(e=>e-1)),[a]),(0,A.jsx)(Tt.p,{id:i.descriptionId,...r,ref:t})},`DialogDescription`)),mi=`DialogClose`,hi=C.forwardRef(Wr(function(e,t){let{__scopeDialog:n,...r}=e,i=Yr(mi,n);return(0,A.jsx)(Tt.button,{type:`button`,...r,ref:t,onClick:Ee(e.onClick,()=>i.onOpenChange(!1))})},`DialogClose`));function gi(e){return e?`open`:`closed`}Wr(gi,`getState`);function _i(...e){return e.filter(Boolean).join(` `)}function vi({className:e,...t}){return(0,A.jsx)(`button`,{className:_i(`ui-button`,e),...t})}function yi({className:e,...t}){return(0,A.jsx)(`span`,{className:_i(`ui-badge`,e),...t})}function bi({open:e,onOpenChange:t,title:n,description:r,children:i}){return(0,A.jsx)(Xr,{open:e,onOpenChange:t,children:(0,A.jsxs)(ei,{children:[(0,A.jsx)(ni,{className:`sheet-overlay`}),(0,A.jsxs)(oi,{className:`sheet-content`,children:[(0,A.jsxs)(`div`,{className:`sheet-head`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(di,{children:n}),r?(0,A.jsx)(pi,{children:r}):null]}),(0,A.jsx)(hi,{asChild:!0,children:(0,A.jsx)(vi,{"aria-label":`关闭面板`,className:`icon-button`,children:(0,A.jsx)(me,{size:18})})})]}),i]})]})})}function xi({title:e,detail:t}){return(0,A.jsxs)(`div`,{className:`empty-state`,children:[(0,A.jsx)(`strong`,{children:e}),t?(0,A.jsx)(`p`,{children:t}):null]})}var Si=[`codex`,`claude`,`pi`],Ci={overview:`概览`,conversation:`对话`,configuration:`配置`,reminders:`提醒`,workspace:`工作区`,logs:`日志`},wi=window.__LARKIN_DASHBOARD__||{packageVersion:`dev`,dashboardVersion:`dev`,buildFingerprint:``,csrfCapability:``},Ti=class extends Error{body;constructor(e,t){super(e),this.body=t}};async function Ei(e,t={}){let n=await fetch(e,{cache:`no-store`,...t});if(!n.ok){let e=await n.json().catch(()=>({}));throw new Ti(e.error||`HTTP ${n.status}`,e)}return n.json()}function Di(e,t){if(!(e instanceof Ti)||!e.body.readiness)return e instanceof Error?e.message:t;let n=e.body.readiness;return[n.reason||e.message,n.nextAction?`下一步:${n.nextAction}`:null].filter(Boolean).join(`;`)}async function Oi(e){return Ei(`/api/config`,{method:`PATCH`,headers:{"Content-Type":`application/json`,"X-Larkin-CSRF":wi.csrfCapability},body:JSON.stringify(e)})}function ki(e,t={}){return Ei(e,{...t,headers:{...t.headers,"X-Larkin-CSRF":wi.csrfCapability}})}function Ai(e){return e==null||!Number.isFinite(e)?`暂无`:e<60?`${e}s 前`:e<3600?`${Math.floor(e/60)}m 前`:e<86400?`${Math.floor(e/3600)}h 前`:`${Math.floor(e/86400)}d 前`}function ji(e){let t=new Date(String(e||``));return Number.isFinite(t.getTime())?t.toLocaleString(`zh-CN`,{hour12:!1}):`未知时间`}function Mi(e){return Number.isFinite(Number(e))?Number(e).toLocaleString(`en-US`):`—`}function Ni(e){return e.issue?`异常`:e.lastActivity?.state?String(e.lastActivity.state):e.running?`空闲`:`离线`}function Pi(){let[e,t]=(0,C.useState)(null),[n,r]=(0,C.useState)(null),[i,a]=(0,C.useState)(!0),[o,s]=(0,C.useState)(!1),c=(0,C.useRef)(Se()),l=(0,C.useRef)(null),u=(0,C.useRef)(null),d=(0,C.useCallback)(async()=>{u.current?.abort();let e=new AbortController;u.current=e;let n=c.current.issue();try{let i=await Ei(`/api/status`,{signal:e.signal});if(!c.current.accepts(n))return;if(wi.buildFingerprint&&i.buildFingerprint&&i.buildFingerprint!==wi.buildFingerprint){window.location.reload();return}t(i),r(null),s(!1)}catch(t){if(e.signal.aborted||!c.current.accepts(n))return;r(t instanceof Error?t.message:String(t)),s(!0)}finally{c.current.accepts(n)&&a(!1)}},[]);return(0,C.useEffect)(()=>{let e=!0,t=async()=>{await d(),e&&(l.current=window.setTimeout(t,3e3))};return t(),()=>{e=!1,l.current!==null&&window.clearTimeout(l.current),u.current?.abort()}},[d]),{data:e,error:n,loading:i,stale:o,retry:d}}function Fi({agent:e}){let t=(e.displayName||e.name||e.agentId).slice(0,2).toUpperCase(),[n,r]=(0,C.useState)(!1);(0,C.useEffect)(()=>r(!1),[e.agentId,e.bot?.hasAvatar]);let i=e.bot?.hasAvatar&&!n;return(0,A.jsx)(`span`,{className:`agent-avatar`,"aria-hidden":`true`,children:i?(0,A.jsx)(`img`,{src:`/api/avatar/${encodeURIComponent(e.agentId)}`,alt:``,onError:()=>r(!0)}):t})}function Ii({agents:e,selectedId:t,onSelect:n,compact:r=!1}){let[i,a]=(0,C.useState)(``),o=(0,C.useMemo)(()=>be(e,i),[e,i]),s=(0,C.useRef)([]),c=(e,t)=>{if(!o.length)return;let n=t===0?e:(e+t+o.length)%o.length;s.current[n]?.focus()};return(0,A.jsxs)(`div`,{className:_i(`sidebar-inner`,r&&`compact-sidebar`),children:[(0,A.jsxs)(`label`,{className:`agent-search`,children:[(0,A.jsx)(de,{size:15,"aria-hidden":`true`}),(0,A.jsx)(`span`,{className:`sr-only`,children:`搜索 Agent`}),(0,A.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`搜索 Agent…`})]}),(0,A.jsxs)(`div`,{className:`agent-list`,role:`listbox`,"aria-label":`Agent 列表`,children:[o.map((e,r)=>(0,A.jsxs)(`button`,{ref:e=>{s.current[r]=e},type:`button`,role:`option`,"aria-selected":e.agentId===t,className:_i(`agent-list-item`,e.agentId===t&&`selected`),onClick:()=>n(e.agentId),onKeyDown:e=>{e.key===`ArrowDown`&&(e.preventDefault(),c(r,1)),e.key===`ArrowUp`&&(e.preventDefault(),c(r,-1)),e.key===`Home`&&(e.preventDefault(),c(0,0)),e.key===`End`&&(e.preventDefault(),c(o.length-1,0))},children:[(0,A.jsx)(Fi,{agent:e}),(0,A.jsxs)(`span`,{className:`agent-list-copy`,children:[(0,A.jsx)(`strong`,{children:e.displayName||e.name}),(0,A.jsxs)(`small`,{children:[e.runtime,` · `,e.model,e.runtimeReadiness?` · ${e.runtimeReadiness.state}`:``]}),(0,A.jsxs)(`span`,{className:`status-line`,children:[(0,A.jsx)(ie,{size:10}),` `,e.connection.state,` · `,Ni(e)]})]}),e.issue?(0,A.jsx)(k,{size:15,className:`danger`,"aria-label":`异常`}):(0,A.jsx)(re,{size:15,"aria-hidden":`true`})]},e.agentId)),o.length?null:(0,A.jsx)(xi,{title:`没有匹配的 Agent`,detail:`可按名称或 App ID 搜索`})]})]})}function Li({open:e,onOpenChange:t,onSaved:n}){let[r,i]=(0,C.useState)(`require`),[a,o]=(0,C.useState)(`require`),[s,c]=(0,C.useState)(!1),[l,u]=(0,C.useState)(null),d=a!==r;return(0,C.useEffect)(()=>{if(!e)return;let t=new AbortController;return c(!0),ki(`/api/config`,{signal:t.signal}).then(e=>{i(e.mentionPolicy),o(e.mentionPolicy),u(null)}).catch(e=>{t.signal.aborted||u(e.message)}).finally(()=>{t.signal.aborted||c(!1)}),()=>t.abort()},[e]),(0,A.jsx)(bi,{open:e,onOpenChange:e=>{!e&&d&&!window.confirm(`全局设置尚未保存,确定放弃草稿吗?`)||t(e)},title:`全局设置`,description:`只管理所有 Agent 共同继承的默认值;高级操作仍使用 CLI。`,children:(0,A.jsxs)(`div`,{className:`settings-form`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`真人群消息默认策略`}),(0,A.jsxs)(`select`,{value:a,onChange:e=>o(e.target.value),disabled:s,children:[(0,A.jsx)(`option`,{value:`require`,children:`require · 群内需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`free · 群内无需 @`})]})]}),(0,A.jsx)(`p`,{className:`field-help`,children:`未单独设置的 Agent 和群会使用这个值。机器人消息仍必须精确 @ 当前 Agent。`}),(0,A.jsxs)(`div`,{className:`form-actions`,children:[(0,A.jsx)(vi,{onClick:()=>o(r),disabled:!d||s,children:`放弃草稿`}),(0,A.jsx)(vi,{className:`primary`,onClick:async()=>{c(!0);try{let e=await Oi({operation:`set-global-mention`,value:a});i(a),u(`已保存 · ${e.applyState} · ${e.revision.slice(0,20)}…`),n()}catch(e){u(e instanceof Error?e.message:String(e))}finally{c(!1)}},disabled:!d||s,children:s?`保存中…`:`保存全局设置`})]}),l?(0,A.jsx)(`p`,{role:`status`,className:`feedback`,children:l}):null]})})}function Ri({config:e,onSave:t,disabled:n}){let[r,i]=(0,C.useState)(``),[a,o]=(0,C.useState)(``),[s,c]=(0,C.useState)(`require`),l=e.knownChats.filter(e=>e.kind===`group`&&e.override!==`inherit`).filter(e=>{let t=r.trim().toLocaleLowerCase();return!t||e.chatId.toLocaleLowerCase().includes(t)||String(e.displayName||`未解析群名`).toLocaleLowerCase().includes(t)});return(0,A.jsxs)(`section`,{className:`group-policy-section`,"aria-labelledby":`group-policy-title`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{id:`group-policy-title`,children:`群策略`}),(0,A.jsx)(`p`,{children:`这里只显示为当前 Agent 单独设置过消息策略的群。`})]}),(0,A.jsxs)(`label`,{className:`table-search`,children:[(0,A.jsx)(de,{size:14}),(0,A.jsx)(`span`,{className:`sr-only`,children:`搜索群`}),(0,A.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`搜索群名或 chat_id`})]})]}),(0,A.jsxs)(`div`,{className:`policy-table-wrap`,children:[(0,A.jsxs)(`table`,{className:`policy-table`,children:[(0,A.jsx)(`thead`,{children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{children:`群`}),(0,A.jsx)(`th`,{children:`消息策略`}),(0,A.jsx)(`th`,{children:`操作`})]})}),(0,A.jsx)(`tbody`,{children:l.map(e=>(0,A.jsxs)(`tr`,{children:[(0,A.jsxs)(`td`,{children:[(0,A.jsx)(`strong`,{children:e.displayName||e.chatId}),e.displayName?(0,A.jsx)(`code`,{children:e.chatId}):(0,A.jsx)(`small`,{children:`群名暂时不可用`})]}),(0,A.jsx)(`td`,{children:(0,A.jsxs)(`select`,{"aria-label":`${e.displayName||e.chatId} 消息策略`,value:e.override,disabled:n,onChange:n=>void t(e.chatId,n.target.value),children:[(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})}),(0,A.jsx)(`td`,{children:(0,A.jsx)(vi,{disabled:n,onClick:()=>void t(e.chatId,`inherit`),children:`移除特别配置`})})]},e.chatId))})]}),l.length?null:(0,A.jsx)(xi,{title:r?`没有匹配的群`:`还没有单独配置的群`,detail:`新增后,Larkin 会自动解析并缓存群名。`})]}),(0,A.jsxs)(`div`,{className:`chat-add`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`完整 chat_id`}),(0,A.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`oc_QAConfigChat1`})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`消息策略`}),(0,A.jsxs)(`select`,{value:s,onChange:e=>c(e.target.value),children:[(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})]}),(0,A.jsx)(vi,{disabled:n||!/^oc_[A-Za-z0-9_-]+$/.test(a),onClick:()=>void t(a,s).then(()=>o(``)),children:`保存群策略`})]})]})}function zi(e){return[`Readiness ${e.state}`,e.reason,e.nextAction].filter(Boolean).join(` · `)}function Bi({agentId:e,readiness:t,onDirtyChange:n,refreshKey:r}){let[i,a]=(0,C.useState)(null),[o,s]=(0,C.useState)({}),[c,l]=(0,C.useState)({}),[u,d]=(0,C.useState)(!0),[f,p]=(0,C.useState)(null),[m,h]=(0,C.useState)(null),g=(0,C.useRef)(Se()),_=(0,C.useRef)(Se()),v=!xe(o,c);(0,C.useEffect)(()=>n(v),[v,n]),(0,C.useEffect)(()=>{let e=e=>{v&&e.preventDefault()};return window.addEventListener(`beforeunload`,e),()=>window.removeEventListener(`beforeunload`,e)},[v]);let y=(0,C.useCallback)(async(t=!1)=>{let n=g.current.issue();d(!0);try{let r=await ki(`/api/config?agent=${encodeURIComponent(e)}`);if(!g.current.accepts(n))return;let i=r.agents[0];if(!i)throw Error(`配置投影中没有当前 Agent`);let o={runtime:i.runtimeOption||i.runtime,model:i.model,effort:i.effort||`default`,mention:i.mention.override};a(r),s(o),(!t||!v)&&l(o)}catch(e){g.current.accepts(n)&&p(e instanceof Error?e.message:String(e))}finally{g.current.accepts(n)&&d(!1)}},[e,v]);(0,C.useEffect)(()=>{y(!1)},[e]),(0,C.useEffect)(()=>{r>0&&y(!0)},[r]);let b=i?.agents[0],x=(e,t)=>l(n=>({...n,[e]:t})),S=String(c.runtime||b?.runtime||``),w=String(c.model||b?.model||`default`);(0,C.useEffect)(()=>{if(h({runtime:S,status:`loading`,models:[]}),!Si.includes(S)){h({runtime:S,status:`error`,models:[]});return}let t=_.current.issue(),n=new AbortController;return ki(`/api/models/${S}?agent=${encodeURIComponent(e)}`,{signal:n.signal}).then(e=>{_.current.accepts(t)&&h({runtime:S,status:`ready`,models:e.models})}).catch(e=>{!n.signal.aborted&&_.current.accepts(t)&&(h({runtime:S,status:`error`,models:[]}),p(`${S} 模型列表暂时不可用:${e instanceof Error?e.message:String(e)}`))}),()=>n.abort()},[e,S]);let ee=m?.runtime===S&&m.status===`ready`,te=m?.runtime===S?m.status:`loading`,T=ee?m.models:[],E=`${w} · 当前配置(模型目录${te===`error`?`不可用`:`加载中`})`,ne=ee?T.some(e=>e.id===w)?T:[{id:w,label:`${w} · 当前配置`},...T]:[{id:w,label:E}],re=ee&&w!=="default"&&T.find(e=>e.id===w)?.supportedReasoningEfforts||[],ie=c.runtime!==o.runtime||c.model!==o.model||c.effort!==o.effort,ae=()=>Ei(`/api/config/apply`,{method:`POST`,headers:{"Content-Type":`application/json`,"X-Larkin-CSRF":wi.csrfCapability},body:JSON.stringify({agentId:e})}),oe=async()=>{if(b){d(!0);try{let t=[];c.runtime===o.runtime?c.model!==o.model&&t.push({operation:`set-agent-model`,agentId:e,model:c.model}):t.push({operation:`set-agent-runtime`,agentId:e,runtime:c.runtime,model:c.model}),c.effort!==o.effort&&t.push({operation:`set-agent-effort`,agentId:e,effort:c.effort}),c.mention!==o.mention&&t.push({operation:`set-agent-mention`,agentId:e,value:c.mention});let n=null;for(let e of t)n=await Oi(e);if(ie)try{await ae(),p(`配置已保存并应用`)}catch(e){p(`配置已保存但未应用:${Di(e,`稍后重试`)}`)}else p(n?`配置已保存`:`没有需要保存的修改`);await y(!1)}catch(e){p(Di(e,e instanceof Error?e.message:String(e)))}finally{d(!1)}}},se=async()=>{d(!0);try{await ae(),p(`已应用保存的运行配置`),await y(!0)}catch(e){p(`配置保持已保存但未应用:${Di(e,`稍后重试`)}`)}finally{d(!1)}},ce=async(t,n)=>{d(!0);try{let r=await Oi({operation:`set-chat-mention`,agentId:e,chatId:t,value:n});p(`群策略已保存 · ${r.applyState}`),await y(!0)}catch(e){p(e instanceof Error?e.message:String(e))}finally{d(!1)}};if(!b&&u)return(0,A.jsx)(xi,{title:`加载 Agent 配置…`});if(!b)return(0,A.jsx)(xi,{title:`配置不可用`,detail:f||`请重试`});let D=b.apply.applyState===`pending`?`待应用`:b.apply.applyState===`applied`?`已应用`:`状态未知`;return(0,A.jsxs)(`div`,{className:`configuration-page`,children:[(0,A.jsxs)(`section`,{className:`config-section`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`Agent 配置`}),(0,A.jsxs)(`p`,{children:[`只作用于 `,e,`;运行配置会在安全时机自动应用,Agent 正忙时会保留待处理。`]})]}),(0,A.jsx)(yi,{className:b.apply.applyState===`pending`?`warning`:`success`,children:D})]}),(0,A.jsxs)(`p`,{className:`cli-guidance`,children:[`这里只提供常用微调。更多配置可以直接告诉当前 Agent,让它运行 `,(0,A.jsx)(`code`,{children:`larkin config --help`}),` 后完成。`]}),t?(0,A.jsx)(`p`,{className:`field-help`,children:zi(t)}):null,(0,A.jsxs)(`div`,{className:`config-grid`,children:[(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Runtime`}),(0,A.jsx)(`select`,{value:S,disabled:u,onChange:e=>{x(`runtime`,e.target.value),x(`model`,`default`),x(`effort`,`default`)},children:(i?.runtimeOptions||Si).map(e=>(0,A.jsx)(`option`,{value:e,children:e},e))})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Model`}),(0,A.jsx)(`select`,{value:w,disabled:u||!ee,onChange:e=>{x(`model`,e.target.value),e.target.value==="default"&&x(`effort`,`default`)},children:ne.map(e=>(0,A.jsx)(`option`,{value:e.id,children:e.label||e.id},e.id))})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`Effort`}),(0,A.jsxs)(`select`,{value:String(c.effort||`default`),disabled:u||!ee||w==="default"||!re.length,onChange:e=>x(`effort`,e.target.value),children:[(0,A.jsx)(`option`,{value:`default`,children:`default · 不指定`}),re.map(e=>(0,A.jsx)(`option`,{value:e,children:e},e))]})]}),(0,A.jsxs)(`label`,{children:[(0,A.jsx)(`span`,{children:`群消息策略`}),(0,A.jsxs)(`select`,{value:String(c.mention||`inherit`),disabled:u,onChange:e=>x(`mention`,e.target.value),children:[(0,A.jsx)(`option`,{value:`inherit`,children:`跟随全局设置`}),(0,A.jsx)(`option`,{value:`require`,children:`需要 @`}),(0,A.jsx)(`option`,{value:`free`,children:`无需 @`})]})]})]}),v||b.apply.applyState===`pending`?(0,A.jsxs)(`div`,{className:`form-actions`,children:[v?(0,A.jsx)(vi,{disabled:u,onClick:()=>l(o),children:`放弃草稿`}):null,v?(0,A.jsx)(vi,{className:`primary`,disabled:u,onClick:oe,children:u?`处理中…`:ie?`保存并应用`:`保存 Agent 配置`}):(0,A.jsxs)(vi,{className:`primary`,disabled:u,onClick:se,children:[(0,A.jsx)(ue,{size:14}),` 应用已保存配置`]})]}):null,f?(0,A.jsx)(`p`,{role:`status`,className:`feedback`,children:f}):null]}),(0,A.jsx)(Ri,{config:b,onSave:ce,disabled:u})]})}function M({agent:e}){let t=e.session?.usage||{},n=e.session?.compaction||{};return(0,A.jsxs)(`div`,{className:`overview-page`,children:[(0,A.jsxs)(`div`,{className:`metrics-band`,"aria-label":`Agent 运行指标`,children:[(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ie,{})}),(0,A.jsx)(`span`,{children:`连接`}),(0,A.jsx)(`strong`,{children:e.connection.state}),(0,A.jsx)(`small`,{children:e.connection.reason})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(E,{})}),(0,A.jsx)(`span`,{children:`执行状态`}),(0,A.jsx)(`strong`,{children:Ni(e)}),(0,A.jsx)(`small`,{children:e.lastActivity?Ai(e.lastActivity.ageSec):`尚无活动`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(le,{})}),(0,A.jsx)(`span`,{children:`最近消息`}),(0,A.jsx)(`strong`,{children:e.lastDeliver?.from||`暂无`}),(0,A.jsx)(`small`,{children:e.lastDeliver?`${e.lastDeliver.target||``} · ${Ai(e.lastDeliver.ageSec)}`:`尚无消息记录`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ce,{})}),(0,A.jsx)(`span`,{children:`上下文`}),(0,A.jsx)(`strong`,{children:t.contextPercent==null?`${e.session?.turns||0} turns`:`${Math.round(Number(t.contextPercent))}%`}),(0,A.jsx)(`small`,{children:e.session?`session ${e.session.id.slice(0,12)}…`:`尚未建立 session`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ce,{})}),(0,A.jsx)(`span`,{children:`累计 Token`}),(0,A.jsx)(`strong`,{children:Mi(t.cumulativeTokens)}),(0,A.jsx)(`small`,{children:t.available?`完整 session 累计`:t.reason||`暂无用量`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(E,{})}),(0,A.jsx)(`span`,{children:`最近一轮用量`}),(0,A.jsx)(`strong`,{children:Mi(t.latestTokens)}),(0,A.jsx)(`small`,{children:`输入、缓存与输出合计`})]}),(0,A.jsxs)(`section`,{className:`metric-stat`,children:[(0,A.jsx)(`div`,{className:`metric-icon`,children:(0,A.jsx)(ue,{})}),(0,A.jsx)(`span`,{children:`Compaction`}),(0,A.jsx)(`strong`,{children:Mi(n.count)}),(0,A.jsx)(`small`,{children:n.active?`正在压缩上下文`:n.lastFinishedAt?`最近 ${ji(n.lastFinishedAt)}`:`尚无压缩记录`})]})]}),(0,A.jsxs)(`div`,{className:`overview-sections`,children:[(0,A.jsxs)(`section`,{className:`content-section wide`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`最近动态`}),(0,A.jsx)(`p`,{children:`当前 Agent 的消息、工具和状态转换。`})]}),(0,A.jsx)(yi,{children:e.feed.length})]}),(0,A.jsx)(N,{items:e.feed.slice(0,10)})]}),(0,A.jsxs)(`section`,{className:`content-section`,children:[(0,A.jsx)(`h3`,{children:`运行摘要`}),(0,A.jsxs)(`dl`,{className:`facts`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Runtime`}),(0,A.jsx)(`dd`,{children:e.runtime})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Readiness`}),(0,A.jsx)(`dd`,{children:e.runtimeReadiness?`${e.runtimeReadiness.state}${e.runtimeReadiness.version?` · ${e.runtimeReadiness.version}`:``}${e.runtimeReadiness.executable?` · ${e.runtimeReadiness.executable}`:``}${e.runtimeReadiness.reason?` · ${e.runtimeReadiness.reason}`:``}${e.runtimeReadiness.nextAction?` · ${e.runtimeReadiness.nextAction}`:``}`:`unknown`})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Model`}),(0,A.jsx)(`dd`,{children:e.model})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`Effort`}),(0,A.jsx)(`dd`,{children:e.effort||`default`})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`入站`}),(0,A.jsx)(`dd`,{children:e.inbound.state})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`提醒`}),(0,A.jsx)(`dd`,{children:e.activeReminders})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`dt`,{children:`指示灯`}),(0,A.jsx)(`dd`,{children:e.eyeIndicator.stuck?`疑似卡住`:`${e.eyeIndicator.pendingCount} pending`})]})]})]})]})]})}function N({items:e}){if(!e.length)return(0,A.jsx)(xi,{title:`还没有活动记录`});let t=e=>e.kind===`error`||e.state===`error`?`state-error`:e.kind===`deliver`?`state-deliver`:[`working`,`thinking`,`tool`].includes(String(e.state))?`state-active`:[`idle`,`online`].includes(String(e.state))?`state-idle`:`state-unknown`;return(0,A.jsx)(`ol`,{className:`timeline`,children:e.map((e,n)=>(0,A.jsxs)(`li`,{children:[(0,A.jsx)(`span`,{className:_i(`timeline-dot`,t(e))}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`strong`,{children:e.kind===`deliver`?`收到消息`:e.kind===`error`?`错误`:e.state||`活动`}),(0,A.jsx)(`p`,{children:e.text||e.detail||[e.from,e.target].filter(Boolean).join(` → `)||e.tool||`状态已更新`}),(0,A.jsx)(`time`,{children:ji(e.at)})]})]},`${e.at}-${n}`))})}function Vi({agent:e}){return e.conversation.length?(0,A.jsx)(`div`,{className:`conversation-list`,children:e.conversation.map((t,n)=>{let r=t.direction===`out`?`已发送`:t.wake?`入站 · 已唤醒`:`旁听 · 未唤醒`;return(0,A.jsxs)(`article`,{className:_i(`conversation-item`,t.direction===`out`?`outbound`:`inbound`,t.direction===`in`&&!t.wake&&`listening`),children:[(0,A.jsxs)(`header`,{children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(yi,{className:t.direction===`in`&&!t.wake?`warning`:t.direction===`out`?`accent`:`success`,children:r}),(0,A.jsx)(`strong`,{children:t.from||(t.direction===`out`?e.displayName:`未知发送者`)})]}),(0,A.jsx)(`time`,{children:ji(t.at)})]}),(0,A.jsx)(`p`,{children:t.text||`[非文本消息]`}),t.target?(0,A.jsx)(`small`,{children:t.target}):null]},`${t.at}-${n}`)})}):(0,A.jsx)(xi,{title:`还没有对话摘录`,detail:`Host 更新本地 conversation projection 后会显示在这里。`})}function Hi({agent:e}){if(!e.remindersList.length)return(0,A.jsx)(xi,{title:`还没有提醒`});let t={scheduled:`待执行`,pending:`待执行`,fired:`已完成`,canceled:`已取消`,cancelled:`已取消`,failed:`失败`};return(0,A.jsx)(`div`,{className:`reminder-list`,children:e.remindersList.map((e,n)=>{let r=e.status||`scheduled`,i=[`fired`,`canceled`,`cancelled`].includes(r);return(0,A.jsxs)(`article`,{className:_i(`status-${r===`cancelled`?`canceled`:r}`,i&&`done`),children:[(0,A.jsx)(ne,{size:16}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`strong`,{children:e.title||`未命名提醒`}),(0,A.jsxs)(`p`,{children:[ji(e.fireAt),e.repeat?` · 重复`:``]})]}),(0,A.jsx)(yi,{className:[`scheduled`,`pending`].includes(r)?`warning`:r===`fired`?`success`:r===`failed`?`danger-badge`:``,children:t[r]||r})]},`${e.fireAt}-${n}`)})})}function Ui({agentId:e}){let[t,n]=(0,C.useState)(null),[r,i]=(0,C.useState)(null),[a,o]=(0,C.useState)(!0),[s,c]=(0,C.useState)(null),l=(0,C.useRef)(Se()),u=(0,C.useCallback)(async t=>{let r=l.current.issue();o(!0);try{let a=await Ei(`/api/workspace?agent=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`);if(!l.current.accepts(r))return;a.kind===`directory`?(n(a),i(null)):i(a),c(null)}catch(e){l.current.accepts(r)&&c(e instanceof Error?e.message:String(e))}finally{l.current.accepts(r)&&o(!1)}},[e]);(0,C.useEffect)(()=>{u(``)},[u]);let d=t?.path.split(`/`).filter(Boolean)||[];return(0,A.jsxs)(`div`,{className:`workspace-browser`,children:[(0,A.jsxs)(`div`,{className:`workspace-side`,children:[(0,A.jsxs)(`nav`,{className:`breadcrumbs`,"aria-label":`工作区路径`,children:[(0,A.jsx)(`button`,{onClick:()=>void u(``),children:`workspace`}),d.map((e,t)=>(0,A.jsxs)(`span`,{children:[(0,A.jsx)(re,{size:12}),(0,A.jsx)(`button`,{onClick:()=>void u(d.slice(0,t+1).join(`/`)),children:e})]},`${e}-${t}`))]}),(0,A.jsxs)(`div`,{className:`workspace-list`,role:`region`,"aria-label":`工作区目录`,children:[t?.entries.map(e=>(0,A.jsxs)(`button`,{onClick:()=>void u(e.path),children:[e.kind===`directory`?(0,A.jsx)(se,{size:15}):(0,A.jsx)(oe,{size:15}),(0,A.jsx)(`span`,{children:e.name}),(0,A.jsx)(`small`,{children:e.kind===`file`?`${e.size} B`:`目录`})]},e.path)),!a&&t&&!t.entries.length?(0,A.jsx)(xi,{title:`空目录`}):null]})]}),(0,A.jsx)(`div`,{className:`workspace-preview`,role:`region`,"aria-label":`文件预览`,children:a?(0,A.jsx)(xi,{title:`读取工作区…`}):s?(0,A.jsx)(xi,{title:`工作区读取失败`,detail:s}):r?(0,A.jsxs)(A.Fragment,{children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`strong`,{children:r.name}),(0,A.jsx)(`small`,{children:r.truncated?`仅预览前 1 MB`:`${r.size} B`})]}),r.binary?(0,A.jsx)(xi,{title:`二进制文件不可预览`}):(0,A.jsx)(`pre`,{children:r.content})]}):(0,A.jsx)(xi,{title:`只读工作区`,detail:`选择文件进行预览`})})]})}function Wi({agent:e}){return(0,A.jsxs)(`section`,{className:`logs-section`,children:[(0,A.jsxs)(`div`,{className:`section-heading`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h3`,{children:`结构化日志`}),(0,A.jsx)(`p`,{children:`最近 30 条本地状态投影;不暴露原始日志文件或路径。`})]}),(0,A.jsx)(yi,{children:e.feed.length})]}),(0,A.jsx)(N,{items:e.feed}),e.recentErrors.length?(0,A.jsxs)(`section`,{className:`error-stack`,children:[(0,A.jsx)(`h4`,{children:`最近错误`}),e.recentErrors.map((e,t)=>(0,A.jsxs)(`div`,{children:[(0,A.jsx)(ae,{size:14}),(0,A.jsx)(`span`,{children:e.text||e.message||`未知错误`}),(0,A.jsx)(`time`,{children:ji(e.at)})]},`${e.at}-${t}`))]}):null]})}function Gi({agent:e}){let t=e.session?.usage||{},n=e.session?.compaction||{},r=t.contextPercent==null?`上下文 ${e.session?.turns||0} turns`:`上下文 ${Math.round(Number(t.contextPercent))}% · ${Mi(t.latestTokens)} / ${Mi(t.contextWindow)}`;return(0,A.jsxs)(`header`,{className:`agent-header`,children:[(0,A.jsxs)(`div`,{className:`agent-title`,children:[(0,A.jsx)(Fi,{agent:e}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`p`,{children:e.agentId}),(0,A.jsx)(`h1`,{children:e.displayName||e.name})]})]}),(0,A.jsxs)(`div`,{className:`agent-header-summary`,children:[(0,A.jsx)(yi,{className:e.connection.state===`connected`?`success`:e.issue?`danger-badge`:``,children:e.connection.state}),(0,A.jsx)(`span`,{children:e.runtime}),(0,A.jsx)(`span`,{children:e.model}),(0,A.jsx)(`span`,{children:e.effort||`default effort`}),(0,A.jsx)(`span`,{children:r}),(0,A.jsxs)(`span`,{children:[`累计 `,Mi(t.cumulativeTokens),` tokens`]}),(0,A.jsxs)(`span`,{children:[`最近一轮 `,Mi(t.latestTokens),` tokens`]}),(0,A.jsxs)(`span`,{children:[`Compact `,Mi(n.count),` 次 · `,n.active?`active`:`idle`]}),(0,A.jsxs)(`span`,{children:[`最近消息 `,e.lastDeliver?Ai(e.lastDeliver.ageSec):`暂无`]}),(0,A.jsx)(`span`,{children:e.lastActivity?.detail||Ni(e)})]})]})}function Ki(){let e=Pi(),t=(0,C.useMemo)(()=>_e(window.location.search),[]),[n,r]=(0,C.useState)(t.agentId),[i,a]=(0,C.useState)(t.tab),[o,s]=(0,C.useState)(!1),[c,l]=(0,C.useState)(!1),[u,d]=(0,C.useState)(0),[f,p]=(0,C.useState)(!1),[m,h]=(0,C.useState)(null),g=(0,C.useRef)(new Map),_=e.data?.agents||[],v=e.data?.packageVersion||wi.packageVersion,y=_.find(e=>e.agentId===n)||null,b=(0,C.useCallback)((e,t)=>{let n=`${e}:${t}`,r=window.scrollY;g.current.set(n,r);try{sessionStorage.setItem(`larkin-dashboard-scroll:${n}`,String(r))}catch{}},[]),x=(0,C.useCallback)(e=>{if(g.current.has(e))return g.current.get(e)||0;try{let t=Number(sessionStorage.getItem(`larkin-dashboard-scroll:${e}`));return Number.isFinite(t)&&t>=0?t:0}catch{return 0}},[]),S=(0,C.useCallback)((e,t,o=!1)=>{n&&b(n,i);let s=o?`replaceState`:`pushState`;window.history[s](null,``,ve(e,t)),r(e),a(t)},[b,n,i]),w=()=>!f||window.confirm(`Agent 配置尚未保存,确定放弃草稿吗?`),ee=e=>{w()&&(p(!1),S(e,i),l(!1))},te=e=>{e!==`configuration`&&!w()||(e!==`configuration`&&p(!1),S(n,e))};return(0,C.useEffect)(()=>{if(!e.data)return;let t=ye(n,e.data.agents);t!==n&&(h(n?`原 Agent 已不存在,已切换到第一个可用 Agent。`:null),S(t,i,!0))},[e.data,n,i,S]),(0,C.useEffect)(()=>{let e=()=>{let e=_e(window.location.search);if(f&&!window.confirm(`Agent 配置尚未保存,确定离开吗?`)){window.history.pushState(null,``,ve(n,i));return}n&&b(n,i),p(!1),r(e.agentId),a(e.tab)};return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[f,b,n,i]),(0,C.useLayoutEffect)(()=>{if(!n)return;let e=`${n}:${i}`,t=x(e);window.scrollY!==t&&window.scrollTo({top:t,behavior:`auto`})},[x,n,i]),(0,A.jsxs)(`div`,{className:_i(`app-shell`,i===`workspace`&&`workspace-app-active`),children:[(0,A.jsxs)(`aside`,{className:`desktop-sidebar`,children:[(0,A.jsxs)(`div`,{className:`brand`,children:[(0,A.jsx)(`img`,{src:`/assets/larkin-mark.svg`,alt:``}),(0,A.jsxs)(`strong`,{children:[`Larkin `,(0,A.jsxs)(`span`,{className:`brand-version`,children:[`v`,v]})]})]}),(0,A.jsx)(Ii,{agents:_,selectedId:n,onSelect:ee}),(0,A.jsx)(`footer`,{children:e.data?.daemon.running?`${_.length} Agents · daemon online`:`daemon offline`})]}),(0,A.jsxs)(`main`,{className:_i(`workspace-shell`,i===`workspace`&&`workspace-route-active`),children:[(0,A.jsxs)(`div`,{className:`app-topbar`,children:[(0,A.jsx)(vi,{className:`mobile-menu`,"aria-label":`打开 Agent 导航`,onClick:()=>l(!0),children:(0,A.jsx)(O,{size:18})}),(0,A.jsxs)(`div`,{children:[(0,A.jsxs)(`strong`,{children:[`Larkin `,(0,A.jsxs)(`span`,{className:`brand-version`,children:[`v`,v]})]}),(0,A.jsx)(`span`,{children:e.data?.daemon.running?`运行中`:`未运行`}),e.stale?(0,A.jsx)(yi,{className:`warning`,children:`状态陈旧`}):null]}),(0,A.jsxs)(vi,{onClick:()=>s(!0),children:[(0,A.jsx)(fe,{size:15}),` 全局设置`]})]}),e.error?(0,A.jsxs)(`div`,{className:`stale-banner`,role:`alert`,children:[(0,A.jsx)(k,{size:15}),`状态刷新失败,保留最后一次数据:`,e.error,(0,A.jsx)(vi,{onClick:()=>void e.retry(),children:`重试`})]}):null,m?(0,A.jsxs)(`div`,{className:`notice`,role:`status`,children:[m,(0,A.jsx)(vi,{"aria-label":`关闭提示`,onClick:()=>h(null),children:`×`})]}):null,e.loading&&!e.data?(0,A.jsxs)(`div`,{className:`page-loading`,children:[(0,A.jsx)(ue,{className:`spin`}),`加载 Agent 工作台…`]}):y?(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Gi,{agent:y}),(0,A.jsx)(`nav`,{className:`agent-tabs`,"aria-label":`${y.displayName} 内容页签`,role:`tablist`,children:ge.map(e=>(0,A.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":i===e,className:i===e?`active`:``,onClick:()=>te(e),children:[e===`overview`?(0,A.jsx)(ce,{}):e===`conversation`?(0,A.jsx)(le,{}):e===`configuration`?(0,A.jsx)(pe,{}):e===`reminders`?(0,A.jsx)(ne,{}):e===`workspace`?(0,A.jsx)(se,{}):(0,A.jsx)(D,{}),Ci[e]]},e))}),(0,A.jsxs)(`div`,{className:_i(`agent-content`,i===`workspace`&&`workspace-active`),children:[(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`overview`,children:(0,A.jsx)(M,{agent:y})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`conversation`,children:(0,A.jsx)(Vi,{agent:y})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`configuration`,children:(0,A.jsx)(Bi,{agentId:y.agentId,readiness:y.runtimeReadiness??null,onDirtyChange:p,refreshKey:u})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`reminders`,children:(0,A.jsx)(Hi,{agent:y})}),(0,A.jsx)(`section`,{className:`workspace-tab-panel`,role:`tabpanel`,hidden:i!==`workspace`,children:(0,A.jsx)(Ui,{agentId:y.agentId})}),(0,A.jsx)(`section`,{role:`tabpanel`,hidden:i!==`logs`,children:(0,A.jsx)(Wi,{agent:y})})]},y.agentId)]}):(0,A.jsx)(xi,{title:`还没有配置任何 Agent`,detail:`先运行 larkin setup。`})]}),(0,A.jsx)(Li,{open:o,onOpenChange:s,onSaved:()=>d(e=>e+1)}),(0,A.jsx)(bi,{open:c,onOpenChange:l,title:`选择 Agent`,description:`搜索、查看状态并切换当前工作区。`,children:(0,A.jsx)(Ii,{compact:!0,agents:_,selectedId:n,onSelect:ee})})]})}var qi=document.getElementById(`root`);if(!qi)throw Error(`dashboard root is missing`);(0,he.createRoot)(qi).render((0,A.jsx)(C.StrictMode,{children:(0,A.jsx)(Ki,{})}));