flavor-code 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
1
  // src/desktop/main.ts
2
2
  import { appendFileSync } from "fs";
3
- import { readFile as readFile36, stat as stat15, writeFile as writeFile18 } from "fs/promises";
4
- import { dirname as dirname24, join as join43 } from "path";
3
+ import { createHash as createHash27, randomUUID as randomUUID27 } from "crypto";
4
+ import { readFile as readFile39, stat as stat18, writeFile as writeFile19 } from "fs/promises";
5
+ import { basename as basename11, dirname as dirname24, join as join46, resolve as resolve40 } from "path";
5
6
  import { fileURLToPath as fileURLToPath4 } from "url";
6
- import { app, BrowserWindow as BrowserWindow2, dialog, ipcMain, Menu, nativeImage, shell } from "electron";
7
+ import { app, BrowserWindow as BrowserWindow2, clipboard, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
7
8
 
8
9
  // src/desktop/contracts.ts
9
10
  import { z as z3 } from "zod";
@@ -450,7 +451,49 @@ var DESKTOP_CHANNELS = {
450
451
  chooseWorkspace: "desktop:choose-workspace",
451
452
  openWorkspace: "desktop:open-workspace",
452
453
  startSession: "desktop:start-session",
454
+ selectSession: "desktop:select-session",
453
455
  deleteSession: "desktop:delete-session",
456
+ updateSession: "desktop:update-session",
457
+ acknowledgeSession: "desktop:acknowledge-session",
458
+ updateProject: "desktop:update-project",
459
+ closeProject: "desktop:close-project",
460
+ revealProject: "desktop:reveal-project",
461
+ copyProjectPath: "desktop:copy-project-path",
462
+ dismissRecovery: "desktop:dismiss-recovery",
463
+ gitStatus: "desktop:git-status",
464
+ gitDiff: "desktop:git-diff",
465
+ gitStage: "desktop:git-stage",
466
+ gitUnstage: "desktop:git-unstage",
467
+ gitDiscard: "desktop:git-discard",
468
+ gitCommit: "desktop:git-commit",
469
+ gitReview: "desktop:git-review",
470
+ gitHistory: "desktop:git-history",
471
+ listWorktrees: "desktop:list-worktrees",
472
+ removeWorktree: "desktop:remove-worktree",
473
+ mergeWorktree: "desktop:merge-worktree",
474
+ historySnapshot: "desktop:history-snapshot",
475
+ createCheckpoint: "desktop:create-checkpoint",
476
+ rewindHistory: "desktop:rewind-history",
477
+ unrevertHistory: "desktop:unrevert-history",
478
+ forkHistory: "desktop:fork-history",
479
+ terminalOpen: "desktop:terminal-open",
480
+ terminalList: "desktop:terminal-list",
481
+ terminalRead: "desktop:terminal-read",
482
+ terminalWrite: "desktop:terminal-write",
483
+ terminalResize: "desktop:terminal-resize",
484
+ terminalClose: "desktop:terminal-close",
485
+ jobRead: "desktop:job-read",
486
+ previewValidate: "desktop:preview-validate",
487
+ previewOpen: "desktop:preview-open",
488
+ inspectWorkbench: "desktop:inspect-workbench",
489
+ astStatus: "desktop:ast-status",
490
+ astSearch: "desktop:ast-search",
491
+ astRelations: "desktop:ast-relations",
492
+ listPals: "desktop:list-pals",
493
+ sendPalMessage: "desktop:send-pal-message",
494
+ startCoWork: "desktop:start-co-work",
495
+ coWorkStatus: "desktop:co-work-status",
496
+ cancelCoWork: "desktop:cancel-co-work",
454
497
  showAppMenu: "desktop:show-app-menu",
455
498
  submit: "desktop:submit",
456
499
  finishTask: "desktop:finish-task",
@@ -473,6 +516,7 @@ var DESKTOP_CHANNELS = {
473
516
  createMemory: "desktop:create-memory",
474
517
  updateMemory: "desktop:update-memory",
475
518
  deleteMemory: "desktop:delete-memory",
519
+ deleteColdMemory: "desktop:delete-cold-memory",
476
520
  switchModel: "desktop:switch-model",
477
521
  addModel: "desktop:add-model",
478
522
  d2cImport: "desktop:d2c-import",
@@ -512,11 +556,49 @@ var DESKTOP_CHANNELS = {
512
556
  // src/desktop/contracts.ts
513
557
  var OpenWorkspaceInputSchema = z3.object({ path: z3.string().trim().min(1).max(32768) }).strict();
514
558
  var StartSessionInputSchema = z3.object({
515
- resumeSession: z3.string().trim().min(1).max(128).optional()
559
+ resumeSession: z3.string().trim().min(1).max(128).optional(),
560
+ environment: z3.enum(["local", "worktree"]).optional()
516
561
  }).strict();
517
562
  var DeleteSessionInputSchema = z3.object({
518
563
  sessionId: z3.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, "Invalid session id")
519
564
  }).strict();
565
+ var UpdateSessionInputSchema = DeleteSessionInputSchema.extend({
566
+ title: z3.string().trim().max(120).optional(),
567
+ pinned: z3.boolean().optional(),
568
+ archived: z3.boolean().optional()
569
+ }).strict();
570
+ var UpdateProjectInputSchema = z3.object({
571
+ workspace: z3.string().trim().min(1).max(32768),
572
+ label: z3.string().trim().max(80).optional(),
573
+ pinned: z3.boolean().optional()
574
+ }).strict();
575
+ var ProjectPathInputSchema = z3.object({ workspace: z3.string().trim().min(1).max(32768) }).strict();
576
+ var CloseProjectInputSchema = ProjectPathInputSchema.extend({ force: z3.boolean().optional() }).strict();
577
+ var GitPathInputSchema = z3.object({ path: z3.string().min(1).max(32768) }).strict();
578
+ var GitDiffInputSchema = GitPathInputSchema.extend({ staged: z3.boolean().optional() }).strict();
579
+ var GitCommitInputSchema = z3.object({ message: z3.string().trim().min(1).max(2e4) }).strict();
580
+ var GitReviewInputSchema = z3.object({
581
+ scope: z3.enum(["working", "staged", "commit", "base", "last-turn"]),
582
+ target: z3.string().trim().min(1).max(256).regex(/^[A-Za-z0-9][A-Za-z0-9._/@{}~^:-]*$/).optional(),
583
+ paths: z3.array(z3.string().min(1).max(32768)).max(200).optional()
584
+ }).strict();
585
+ var SafeIdInput = z3.string().trim().min(1).max(512).regex(/^[A-Za-z0-9._:/#@+~-]+$/);
586
+ var WorktreeRemoveInputSchema = z3.object({ id: z3.string().regex(/^worktree-[a-z0-9._-]{1,80}$/), force: z3.boolean().optional() }).strict();
587
+ var HistoryNodeInputSchema = z3.object({ nodeId: SafeIdInput }).strict();
588
+ var CheckpointInputSchema = z3.object({ label: z3.string().trim().min(1).max(160).optional() }).strict();
589
+ var TerminalOpenInputSchema = z3.object({ cwd: z3.string().max(32768).optional(), shell: z3.string().max(1024).optional(), columns: z3.number().int().min(20).max(500).optional(), rows: z3.number().int().min(5).max(300).optional() }).strict();
590
+ var TerminalIdInputSchema = z3.object({ id: z3.string().regex(/^term-[A-Za-z0-9-]{1,80}$/) }).strict();
591
+ var TerminalReadInputSchema = TerminalIdInputSchema.extend({ cursor: z3.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional() }).strict();
592
+ var TerminalWriteInputSchema = TerminalIdInputSchema.extend({ data: z3.string().max(1e5) }).strict();
593
+ var TerminalResizeInputSchema = TerminalIdInputSchema.extend({ columns: z3.number().int().min(20).max(500), rows: z3.number().int().min(5).max(300) }).strict();
594
+ var JobReadInputSchema = z3.object({ id: z3.string().regex(/^job-[A-Za-z0-9-]{1,80}$/), cursor: z3.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional() }).strict();
595
+ var PreviewUrlInputSchema = z3.object({ url: z3.string().trim().min(1).max(2048) }).strict();
596
+ var PalMessageInputSchema = z3.object({ target: SafeIdInput, message: z3.string().trim().min(1).max(5e4), kind: z3.enum(["chat", "task"]) }).strict();
597
+ var CoWorkStartInputSchema = z3.object({ target: SafeIdInput, goal: z3.string().trim().min(1).max(5e4) }).strict();
598
+ var CoWorkStatusInputSchema = z3.object({ coWorkId: SafeIdInput.optional() }).strict();
599
+ var CoWorkCancelInputSchema = z3.object({ coWorkId: SafeIdInput, reason: z3.string().trim().min(1).max(4e3).optional() }).strict();
600
+ var AstSearchInputSchema = z3.object({ query: z3.string().trim().min(1).max(200) }).strict();
601
+ var AstNodeInputSchema = z3.object({ id: SafeIdInput, hops: z3.number().int().min(1).max(4).optional() }).strict();
520
602
  var AppMenuInputSchema = z3.object({
521
603
  menu: z3.enum(["file", "edit", "view", "help"]),
522
604
  x: z3.number().int().min(0).max(32768),
@@ -1363,7 +1445,7 @@ async function runInteractionManifest(manifest, baseUrl, createDriver) {
1363
1445
  }
1364
1446
  const started = Date.now();
1365
1447
  let driver;
1366
- let failure;
1448
+ let failure2;
1367
1449
  let apiRequestCount = 0;
1368
1450
  try {
1369
1451
  driver = await createDriver({ id: scenario.id, pageUrl });
@@ -1382,9 +1464,9 @@ async function runInteractionManifest(manifest, baseUrl, createDriver) {
1382
1464
  throw new Error("No API request was observed; the page is still behaving as a static implementation");
1383
1465
  }
1384
1466
  } catch (error) {
1385
- failure = error instanceof Error ? error.message : String(error);
1467
+ failure2 = error instanceof Error ? error.message : String(error);
1386
1468
  const diagnostics = driver?.diagnostics?.();
1387
- if (diagnostics !== void 0) failure = `${failure} | ${diagnostics}`;
1469
+ if (diagnostics !== void 0) failure2 = `${failure2} | ${diagnostics}`;
1388
1470
  apiRequestCount = driver?.apiRequestCount() ?? 0;
1389
1471
  } finally {
1390
1472
  await driver?.close().catch(() => void 0);
@@ -1393,14 +1475,14 @@ async function runInteractionManifest(manifest, baseUrl, createDriver) {
1393
1475
  scenarios.push({
1394
1476
  id: scenario.id,
1395
1477
  pageUrl,
1396
- passed: failure === void 0,
1478
+ passed: failure2 === void 0,
1397
1479
  durationMs: Date.now() - started,
1398
1480
  apiRequestCount,
1399
- ...failure === void 0 ? {} : { failure },
1481
+ ...failure2 === void 0 ? {} : { failure: failure2 },
1400
1482
  ...capturedRequests === void 0 ? {} : { requests: capturedRequests }
1401
1483
  });
1402
- if (failure !== void 0 && isAuthenticationPrerequisite(scenario)) {
1403
- blockedByAuthentication = { id: scenario.id, failure };
1484
+ if (failure2 !== void 0 && isAuthenticationPrerequisite(scenario)) {
1485
+ blockedByAuthentication = { id: scenario.id, failure: failure2 };
1404
1486
  }
1405
1487
  }
1406
1488
  }
@@ -1900,7 +1982,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1900
1982
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
1901
1983
  if (ready === "complete" || ready === "interactive") return candidate;
1902
1984
  }
1903
- await new Promise((resolve36) => setTimeout(resolve36, pollIntervalMs));
1985
+ await new Promise((resolve41) => setTimeout(resolve41, pollIntervalMs));
1904
1986
  }
1905
1987
  throw new Error(`D2C embedded preview did not navigate within ${navigationTimeoutMs} ms: ${url}${observedUrl === void 0 ? "" : `; last observed ${observedUrl}`}${observedFrames.length === 0 ? "; no live frames" : `; live frames ${observedFrames.join(", ")}`}`);
1906
1988
  };
@@ -1915,13 +1997,13 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1915
1997
  while (Date.now() <= deadline) {
1916
1998
  last = await frame(baseUrl).executeJavaScript(assertionScript(step), true);
1917
1999
  if (last.passed) return last;
1918
- await new Promise((resolve36) => setTimeout(resolve36, 50));
2000
+ await new Promise((resolve41) => setTimeout(resolve41, 50));
1919
2001
  }
1920
2002
  return last;
1921
2003
  };
1922
2004
  const settleAfterAction = async (previousUrl) => {
1923
2005
  const origin = new URL(previousUrl).origin;
1924
- await new Promise((resolve36) => setTimeout(resolve36, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
2006
+ await new Promise((resolve41) => setTimeout(resolve41, Math.min(180, Math.max(20, visualDelayMs * 0.25))));
1925
2007
  const deadline = Date.now() + Math.min(2e3, navigationTimeoutMs);
1926
2008
  while (Date.now() <= deadline) {
1927
2009
  const candidate = host().mainFrame.framesInSubtree.find((item) => !item.isDestroyed() && sameOrigin(item.url, origin));
@@ -1929,7 +2011,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
1929
2011
  const ready = await candidate.executeJavaScript("document.readyState", false).catch(() => void 0);
1930
2012
  if (ready === "complete" || ready === "interactive") return candidate;
1931
2013
  }
1932
- await new Promise((resolve36) => setTimeout(resolve36, pollIntervalMs));
2014
+ await new Promise((resolve41) => setTimeout(resolve41, pollIntervalMs));
1933
2015
  }
1934
2016
  throw new Error("D2C preview frame was unavailable after an interaction action");
1935
2017
  };
@@ -2016,7 +2098,7 @@ function createEmbeddedD2cAutomation(getHost, options = {}) {
2016
2098
  return result;
2017
2099
  },
2018
2100
  settle: async () => {
2019
- await new Promise((resolve36) => setTimeout(resolve36, 150));
2101
+ await new Promise((resolve41) => setTimeout(resolve41, 150));
2020
2102
  requests = await frame(activeUrl).executeJavaScript(`performance.getEntriesByType("resource").filter((entry) => {
2021
2103
  try { return ["fetch", "xmlhttprequest"].includes(entry.initiatorType) && new URL(entry.name).origin === ${JSON.stringify(mockOrigin)}; } catch { return false; }
2022
2104
  }).length`, false).catch(() => 0);
@@ -2102,7 +2184,7 @@ function responseText(protocol, value) {
2102
2184
  }
2103
2185
  throw new Error("D2C Judge response did not contain text output");
2104
2186
  }
2105
- function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve36) => setTimeout(resolve36, ms))) {
2187
+ function createD2cJudgeClient(fetcher = globalThis.fetch, retryDelay = (ms) => new Promise((resolve41) => setTimeout(resolve41, ms))) {
2106
2188
  const request = async (config, prompt, images, maxTokens) => {
2107
2189
  const openAi = config.protocol === "openai-compatible";
2108
2190
  const imageContent = openAi ? images.map((image) => ({ type: "image_url", image_url: { url: dataUrl(image), detail: "high" } })) : images.map((image) => ({ type: "image", source: { type: "base64", media_type: "image/png", data: image.toString("base64") } }));
@@ -2461,7 +2543,7 @@ import { z as z7 } from "zod";
2461
2543
  import { Worker } from "worker_threads";
2462
2544
  function comparePngsInWorker(left, right, signal) {
2463
2545
  signal?.throwIfAborted();
2464
- return new Promise((resolve36, reject) => {
2546
+ return new Promise((resolve41, reject) => {
2465
2547
  const worker = new Worker(new URL("./pixel-worker.js", import.meta.url));
2466
2548
  let settled = false;
2467
2549
  const finish = (callback) => {
@@ -2479,7 +2561,7 @@ function comparePngsInWorker(left, right, signal) {
2479
2561
  finish(() => reject(new Error(value.error ?? "D2C pixel worker failed")));
2480
2562
  return;
2481
2563
  }
2482
- finish(() => resolve36({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2564
+ finish(() => resolve41({ ...value.result, heatmapPng: Buffer.from(value.result.heatmapPng) }));
2483
2565
  });
2484
2566
  worker.once("error", (error) => finish(() => reject(error)));
2485
2567
  worker.once("exit", (code) => {
@@ -4349,12 +4431,12 @@ function hasCycle(nodes) {
4349
4431
 
4350
4432
  // src/utils/async.ts
4351
4433
  function withTimeout(promise, ms) {
4352
- return new Promise((resolve36, reject) => {
4434
+ return new Promise((resolve41, reject) => {
4353
4435
  let settled = false;
4354
4436
  const timer = setTimeout(() => {
4355
4437
  if (settled) return;
4356
4438
  settled = true;
4357
- resolve36({ timedOut: true, value: void 0 });
4439
+ resolve41({ timedOut: true, value: void 0 });
4358
4440
  }, ms);
4359
4441
  timer.unref?.();
4360
4442
  promise.then(
@@ -4362,7 +4444,7 @@ function withTimeout(promise, ms) {
4362
4444
  if (!settled) {
4363
4445
  settled = true;
4364
4446
  clearTimeout(timer);
4365
- resolve36({ timedOut: false, value });
4447
+ resolve41({ timedOut: false, value });
4366
4448
  }
4367
4449
  },
4368
4450
  (error) => {
@@ -4377,13 +4459,13 @@ function withTimeout(promise, ms) {
4377
4459
  }
4378
4460
  function awaitWithSignal3(promise, signal) {
4379
4461
  signal.throwIfAborted();
4380
- return new Promise((resolve36, reject) => {
4462
+ return new Promise((resolve41, reject) => {
4381
4463
  const onAbort = () => reject(signal.reason);
4382
4464
  signal.addEventListener("abort", onAbort, { once: true });
4383
4465
  promise.then(
4384
4466
  (value) => {
4385
4467
  signal.removeEventListener("abort", onAbort);
4386
- resolve36(value);
4468
+ resolve41(value);
4387
4469
  },
4388
4470
  (error) => {
4389
4471
  signal.removeEventListener("abort", onAbort);
@@ -4504,7 +4586,7 @@ var SubagentScheduler = class {
4504
4586
  }
4505
4587
  }
4506
4588
  async #runTask(task, signal) {
4507
- let failure;
4589
+ let failure2;
4508
4590
  let completion;
4509
4591
  try {
4510
4592
  const start = await this.#options.hooks.emit({
@@ -4521,14 +4603,14 @@ var SubagentScheduler = class {
4521
4603
  completion = { id: task.id, result: parsed.data };
4522
4604
  break;
4523
4605
  }
4524
- failure = parsed.success ? new Error(`Subagent result taskId ${parsed.data.taskId} does not match ${task.id}`) : parsed.error;
4606
+ failure2 = parsed.success ? new Error(`Subagent result taskId ${parsed.data.taskId} does not match ${task.id}`) : parsed.error;
4525
4607
  }
4526
4608
  completion ??= {
4527
4609
  id: task.id,
4528
- result: syntheticResult(task, "failed", `Invalid structured result after repair: ${message(failure)}`)
4610
+ result: syntheticResult(task, "failed", `Invalid structured result after repair: ${message(failure2)}`)
4529
4611
  };
4530
4612
  } catch (error) {
4531
- failure = error;
4613
+ failure2 = error;
4532
4614
  if (!signal.aborted) completion = { id: task.id, result: syntheticResult(task, "failed", message(error)) };
4533
4615
  } finally {
4534
4616
  try {
@@ -4538,18 +4620,18 @@ var SubagentScheduler = class {
4538
4620
  payload: {
4539
4621
  taskId: task.id,
4540
4622
  status: signal.aborted ? "cancelled" : completion?.result.status ?? "failed",
4541
- ...failure === void 0 ? {} : { error: message(failure) }
4623
+ ...failure2 === void 0 ? {} : { error: message(failure2) }
4542
4624
  }
4543
4625
  });
4544
4626
  } catch (stopError) {
4545
4627
  if (!signal.aborted) {
4546
- failure = stopError;
4628
+ failure2 = stopError;
4547
4629
  completion = { id: task.id, result: syntheticResult(task, "failed", message(stopError)) };
4548
4630
  }
4549
4631
  }
4550
4632
  }
4551
4633
  if (signal.aborted) throw signal.reason;
4552
- return completion ?? { id: task.id, result: syntheticResult(task, "failed", message(failure)) };
4634
+ return completion ?? { id: task.id, result: syntheticResult(task, "failed", message(failure2)) };
4553
4635
  }
4554
4636
  };
4555
4637
  function isReady(task, states) {
@@ -6292,10 +6374,10 @@ function sanitize(message2, secrets) {
6292
6374
  }
6293
6375
  function waitForRetry(delayMs, signal) {
6294
6376
  if (signal?.aborted) return Promise.reject(signal.reason);
6295
- return new Promise((resolve36, reject) => {
6377
+ return new Promise((resolve41, reject) => {
6296
6378
  const timer = setTimeout(() => {
6297
6379
  signal?.removeEventListener("abort", onAbort);
6298
- resolve36();
6380
+ resolve41();
6299
6381
  }, delayMs);
6300
6382
  const onAbort = () => {
6301
6383
  clearTimeout(timer);
@@ -6897,10 +6979,10 @@ function serializeToolInput(input) {
6897
6979
  }
6898
6980
  function waitForRetry2(delayMs, signal) {
6899
6981
  if (signal?.aborted) return Promise.reject(signal.reason);
6900
- return new Promise((resolve36, reject) => {
6982
+ return new Promise((resolve41, reject) => {
6901
6983
  const timer = setTimeout(() => {
6902
6984
  signal?.removeEventListener("abort", onAbort);
6903
- resolve36();
6985
+ resolve41();
6904
6986
  }, delayMs);
6905
6987
  const onAbort = () => {
6906
6988
  clearTimeout(timer);
@@ -8385,7 +8467,7 @@ var HookBus = class {
8385
8467
  }
8386
8468
  };
8387
8469
  function runShellHandler(descriptor, event, signal) {
8388
- return new Promise((resolve36, reject) => {
8470
+ return new Promise((resolve41, reject) => {
8389
8471
  const child = spawn(descriptor.command, [...descriptor.args ?? []], {
8390
8472
  env: { ...process.env, ...descriptor.env },
8391
8473
  shell: false,
@@ -8428,7 +8510,7 @@ function runShellHandler(descriptor, event, signal) {
8428
8510
  return;
8429
8511
  }
8430
8512
  try {
8431
- resolve36(HookDecisionSchema.parse(JSON.parse(stdout)));
8513
+ resolve41(HookDecisionSchema.parse(JSON.parse(stdout)));
8432
8514
  } catch (error) {
8433
8515
  reject(error);
8434
8516
  }
@@ -8467,7 +8549,7 @@ function zodRecord(payload) {
8467
8549
  // src/utils/execFileNoThrow.ts
8468
8550
  import { execFile } from "child_process";
8469
8551
  function execFileNoThrow(file, args, options = {}) {
8470
- return new Promise((resolve36) => {
8552
+ return new Promise((resolve41) => {
8471
8553
  const child = execFile(file, args, {
8472
8554
  timeout: options.timeout,
8473
8555
  cwd: options.useCwd === false ? void 0 : process.cwd(),
@@ -8475,7 +8557,7 @@ function execFileNoThrow(file, args, options = {}) {
8475
8557
  encoding: "utf8",
8476
8558
  windowsHide: true
8477
8559
  }, (error, stdout, stderr) => {
8478
- resolve36({
8560
+ resolve41({
8479
8561
  stdout,
8480
8562
  stderr,
8481
8563
  code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
@@ -9777,7 +9859,7 @@ var USAGE = [
9777
9859
  " clear reset signals, done markers, and verified markers"
9778
9860
  ].join("\n");
9779
9861
  function runCommand(command, cwd, timeoutMs) {
9780
- return new Promise((resolve36) => {
9862
+ return new Promise((resolve41) => {
9781
9863
  const isWindows = process.platform === "win32";
9782
9864
  const child = spawn2(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
9783
9865
  cwd,
@@ -9787,7 +9869,7 @@ function runCommand(command, cwd, timeoutMs) {
9787
9869
  let stderr = "";
9788
9870
  const timer = setTimeout(() => {
9789
9871
  child.kill();
9790
- resolve36({ ok: false, stdout, stderr: `${stderr}
9872
+ resolve41({ ok: false, stdout, stderr: `${stderr}
9791
9873
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
9792
9874
  }, timeoutMs);
9793
9875
  child.stdout.on("data", (chunk) => {
@@ -9798,12 +9880,12 @@ function runCommand(command, cwd, timeoutMs) {
9798
9880
  });
9799
9881
  child.on("error", (error) => {
9800
9882
  clearTimeout(timer);
9801
- resolve36({ ok: false, stdout, stderr: `${stderr}
9883
+ resolve41({ ok: false, stdout, stderr: `${stderr}
9802
9884
  ${error.message}`, code: "spawn" });
9803
9885
  });
9804
9886
  child.on("close", (code) => {
9805
9887
  clearTimeout(timer);
9806
- resolve36({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
9888
+ resolve41({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
9807
9889
  });
9808
9890
  });
9809
9891
  }
@@ -13846,8 +13928,8 @@ var OAuthCallbackAuthProvider = class {
13846
13928
  async #startCallbackServer(expectedState, signal) {
13847
13929
  let serverResolve;
13848
13930
  let serverReject;
13849
- const codePromise = new Promise((resolve36, reject) => {
13850
- serverResolve = resolve36;
13931
+ const codePromise = new Promise((resolve41, reject) => {
13932
+ serverResolve = resolve41;
13851
13933
  serverReject = reject;
13852
13934
  });
13853
13935
  const server = createServer2((req, res) => {
@@ -13881,7 +13963,7 @@ var OAuthCallbackAuthProvider = class {
13881
13963
  );
13882
13964
  serverResolve(code);
13883
13965
  });
13884
- return new Promise((resolve36, reject) => {
13966
+ return new Promise((resolve41, reject) => {
13885
13967
  server.once("error", (error) => {
13886
13968
  reject(new Error(`OAuth callback server failed: ${error.message}`));
13887
13969
  });
@@ -13898,7 +13980,7 @@ var OAuthCallbackAuthProvider = class {
13898
13980
  signal.addEventListener("abort", () => {
13899
13981
  serverReject(new Error("OAuth flow aborted"));
13900
13982
  }, { once: true });
13901
- resolve36({ redirectUri, promise: codePromise });
13983
+ resolve41({ redirectUri, promise: codePromise });
13902
13984
  });
13903
13985
  });
13904
13986
  }
@@ -18527,8 +18609,8 @@ var LspConnection = class {
18527
18609
  const id = this.#nextId++;
18528
18610
  const request = { jsonrpc: "2.0", id, method, params };
18529
18611
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
18530
- const promise = new Promise((resolve36, reject) => {
18531
- this.#pending.set(id, { resolve: resolve36, reject });
18612
+ const promise = new Promise((resolve41, reject) => {
18613
+ this.#pending.set(id, { resolve: resolve41, reject });
18532
18614
  timeout.addEventListener("abort", () => {
18533
18615
  if (this.#pending.has(id)) {
18534
18616
  this.#pending.delete(id);
@@ -18932,19 +19014,19 @@ async function changeSummary(workspace) {
18932
19014
  };
18933
19015
  }
18934
19016
  async function stagedDiff(workspace, maxChars = 96e3) {
18935
- const stat16 = await git2(workspace, ["diff", "--staged", "--stat"]);
19017
+ const stat19 = await git2(workspace, ["diff", "--staged", "--stat"]);
18936
19018
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
18937
19019
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
18938
19020
  const text = diff.stdout;
18939
19021
  return {
18940
19022
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18941
19023
  \u2026[diff truncated]` : text,
18942
- stat: stat16.stdout.trim(),
19024
+ stat: stat19.stdout.trim(),
18943
19025
  truncated: text.length > maxChars
18944
19026
  };
18945
19027
  }
18946
19028
  async function uncommittedDiff(workspace, maxChars = 96e3) {
18947
- const stat16 = await git2(workspace, ["diff", "HEAD", "--stat"]);
19029
+ const stat19 = await git2(workspace, ["diff", "HEAD", "--stat"]);
18948
19030
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
18949
19031
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
18950
19032
  const summary2 = await changeSummary(workspace);
@@ -18952,7 +19034,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
18952
19034
  return {
18953
19035
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18954
19036
  \u2026[diff truncated]` : text,
18955
- stat: stat16.stdout.trim(),
19037
+ stat: stat19.stdout.trim(),
18956
19038
  untracked: summary2.untrackedFiles
18957
19039
  };
18958
19040
  }
@@ -19166,7 +19248,7 @@ var QuestionBridge = class {
19166
19248
  ask(questions, signal) {
19167
19249
  if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
19168
19250
  if (signal.aborted) return Promise.reject(signal.reason);
19169
- return new Promise((resolve36, reject) => {
19251
+ return new Promise((resolve41, reject) => {
19170
19252
  const onAbort = () => {
19171
19253
  this.#pending = void 0;
19172
19254
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
@@ -19178,7 +19260,7 @@ var QuestionBridge = class {
19178
19260
  signal.removeEventListener("abort", onAbort);
19179
19261
  this.#pending = void 0;
19180
19262
  this.#onChange?.();
19181
- resolve36(answers);
19263
+ resolve41(answers);
19182
19264
  },
19183
19265
  reject: (error) => {
19184
19266
  signal.removeEventListener("abort", onAbort);
@@ -20795,9 +20877,9 @@ var FlavorSession = class {
20795
20877
  }
20796
20878
  #waitForPlanningIdle(key) {
20797
20879
  if (!this.#activePlanning.has(key)) return Promise.resolve();
20798
- return new Promise((resolve36) => {
20880
+ return new Promise((resolve41) => {
20799
20881
  const check2 = () => {
20800
- if (!this.#activePlanning.has(key)) resolve36();
20882
+ if (!this.#activePlanning.has(key)) resolve41();
20801
20883
  else this.#planningIdleWaiters.add(check2);
20802
20884
  };
20803
20885
  this.#planningIdleWaiters.add(check2);
@@ -22800,8 +22882,8 @@ var JobRegistry = class {
22800
22882
  const id = `job-${randomUUID17().slice(0, 12)}`;
22801
22883
  const now = (/* @__PURE__ */ new Date()).toISOString();
22802
22884
  let settle;
22803
- const done = new Promise((resolve36) => {
22804
- settle = resolve36;
22885
+ const done = new Promise((resolve41) => {
22886
+ settle = resolve41;
22805
22887
  });
22806
22888
  const record3 = {
22807
22889
  id,
@@ -23305,15 +23387,15 @@ async function acquirePalFileLock(options) {
23305
23387
  // src/pals/broker-cli.ts
23306
23388
  var startupAttempts = /* @__PURE__ */ new Map();
23307
23389
  function delay3(milliseconds) {
23308
- return new Promise((resolve36) => setTimeout(resolve36, milliseconds));
23390
+ return new Promise((resolve41) => setTimeout(resolve41, milliseconds));
23309
23391
  }
23310
23392
  function canConnect(address) {
23311
- return new Promise((resolve36) => {
23393
+ return new Promise((resolve41) => {
23312
23394
  const socket = new Socket2();
23313
23395
  const finish = (connected) => {
23314
23396
  socket.removeAllListeners();
23315
23397
  socket.destroy();
23316
- resolve36(connected);
23398
+ resolve41(connected);
23317
23399
  };
23318
23400
  socket.once("connect", () => finish(true));
23319
23401
  socket.once("error", () => finish(false));
@@ -23327,18 +23409,25 @@ function startupLockPath(address) {
23327
23409
  async function spawnProductionBroker(address) {
23328
23410
  const entry2 = process.argv[1];
23329
23411
  if (entry2 === void 0) throw new Error("Cannot auto-start pals broker without a CLI entry point");
23412
+ return spawnPalBrokerProcess({ address, executable: process.execPath, args: [entry2, "--pals-broker", address] });
23413
+ }
23414
+ async function spawnPalBrokerProcess(options) {
23415
+ if (options.executable.trim() === "" || options.args.length === 0) throw new Error("Pals broker process requires an executable and entry arguments");
23416
+ const address = options.address;
23330
23417
  const lockPath = startupLockPath(address);
23331
23418
  const lock = await acquirePalFileLock({ path: lockPath, endpointLive: () => canConnect(address) });
23332
23419
  if (lock === void 0) return;
23333
23420
  try {
23334
- const child = spawn7(process.execPath, [entry2, "--pals-broker", address], {
23421
+ const child = spawn7(options.executable, [...options.args], {
23335
23422
  detached: true,
23336
23423
  stdio: "ignore",
23337
- windowsHide: true
23424
+ windowsHide: true,
23425
+ ...options.environment === void 0 ? {} : { env: options.environment }
23338
23426
  });
23339
23427
  child.unref();
23340
23428
  const deadline = Date.now() + 5e3;
23341
23429
  while (Date.now() < deadline && !await canConnect(address)) await delay3(25);
23430
+ if (!await canConnect(address)) throw new Error(`Pals broker process did not become ready at '${address}'`);
23342
23431
  } finally {
23343
23432
  await lock.release();
23344
23433
  }
@@ -23497,8 +23586,8 @@ var PalClient = class {
23497
23586
  let disconnectTimer;
23498
23587
  await Promise.race([
23499
23588
  this.request({ version: 1, type: "disconnect", requestId: randomUUID22() }).catch(() => void 0),
23500
- new Promise((resolve36) => {
23501
- disconnectTimer = setTimeout(resolve36, Math.min(this.requestTimeoutMs, 250));
23589
+ new Promise((resolve41) => {
23590
+ disconnectTimer = setTimeout(resolve41, Math.min(this.requestTimeoutMs, 250));
23502
23591
  })
23503
23592
  ]);
23504
23593
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -23544,7 +23633,7 @@ var PalClient = class {
23544
23633
  return presence;
23545
23634
  }
23546
23635
  openSocket() {
23547
- return new Promise((resolve36, reject) => {
23636
+ return new Promise((resolve41, reject) => {
23548
23637
  let settled = false;
23549
23638
  const rejectConnection = (error) => {
23550
23639
  if (settled) return;
@@ -23567,7 +23656,7 @@ var PalClient = class {
23567
23656
  settled = true;
23568
23657
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
23569
23658
  socket.off("error", onInitialError);
23570
- resolve36();
23659
+ resolve41();
23571
23660
  };
23572
23661
  const onInitialError = (error) => {
23573
23662
  socket.off("connect", onConnect);
@@ -23598,12 +23687,12 @@ var PalClient = class {
23598
23687
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
23599
23688
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
23600
23689
  }
23601
- return new Promise((resolve36, reject) => {
23690
+ return new Promise((resolve41, reject) => {
23602
23691
  const timer = setTimeout(() => {
23603
23692
  this.pending.delete(request.requestId);
23604
23693
  reject(new Error(`Pal request '${request.type}' timed out`));
23605
23694
  }, this.requestTimeoutMs);
23606
- this.pending.set(request.requestId, { resolve: resolve36, reject, timer });
23695
+ this.pending.set(request.requestId, { resolve: resolve41, reject, timer });
23607
23696
  let frame;
23608
23697
  try {
23609
23698
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -23916,6 +24005,51 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
23916
24005
  "Reassess the current query independently.",
23917
24006
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
23918
24007
  ].join(" ");
24008
+ function recoverablePalClient(client, onRecovered) {
24009
+ let presence;
24010
+ let starting;
24011
+ const ensure = () => {
24012
+ if (presence !== void 0) return Promise.resolve(presence);
24013
+ if (starting !== void 0) return starting;
24014
+ starting = client.start().then((value) => {
24015
+ presence = value;
24016
+ onRecovered();
24017
+ return value;
24018
+ }).finally(() => {
24019
+ starting = void 0;
24020
+ });
24021
+ return starting;
24022
+ };
24023
+ const invoke = async (run) => {
24024
+ await ensure();
24025
+ try {
24026
+ return await run();
24027
+ } catch (error) {
24028
+ const detail = message(error);
24029
+ if (!/not connected|connection closed|ECONNRESET|ECONNREFUSED|EPIPE/i.test(detail)) throw error;
24030
+ presence = void 0;
24031
+ await ensure();
24032
+ return run();
24033
+ }
24034
+ };
24035
+ return {
24036
+ start: ensure,
24037
+ list: () => invoke(() => client.list()),
24038
+ rename: (alias) => invoke(() => client.rename(alias)),
24039
+ sendTask: (target, value) => invoke(() => client.sendTask(target, value)),
24040
+ sendChat: (target, value) => invoke(() => client.sendChat(target, value)),
24041
+ startCoWork: (input) => invoke(() => client.startCoWork(input)),
24042
+ coWorkAction: (action) => invoke(() => client.coWorkAction(action)),
24043
+ coWorkStatus: (coWorkId) => invoke(() => client.coWorkStatus(coWorkId)),
24044
+ integrateCoWork: (input) => invoke(() => client.integrateCoWork(input)),
24045
+ cancelCoWork: (coWorkId, reason) => invoke(() => client.cancelCoWork(coWorkId, reason)),
24046
+ subscribe: (listener) => client.subscribe(listener),
24047
+ close: async () => {
24048
+ presence = void 0;
24049
+ await client.close();
24050
+ }
24051
+ };
24052
+ }
23919
24053
  var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
23920
24054
  function createPromptEnvironment(input = {}) {
23921
24055
  const now = input.now ?? /* @__PURE__ */ new Date();
@@ -24236,6 +24370,7 @@ async function createProductionRuntime(options) {
24236
24370
  tools.push(evolveService.toolDefinition());
24237
24371
  let sleepScheduler;
24238
24372
  let collaborationClient;
24373
+ let collaborationUnavailableDiagnostic;
24239
24374
  let unsubscribeCollaboration;
24240
24375
  let collaborationSession;
24241
24376
  let deliverCollaborationEvent;
@@ -24278,7 +24413,7 @@ async function createProductionRuntime(options) {
24278
24413
  userScope: home,
24279
24414
  runtimeDir: join31(tmpdir3(), `flavor-code-pals-${createHash20("sha256").update(home).digest("hex").slice(0, 16)}`)
24280
24415
  });
24281
- collaborationClient = collaboration.client ?? collaboration.createClient?.({
24416
+ const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
24282
24417
  instanceId: collaboration.instanceId,
24283
24418
  alias,
24284
24419
  projectPath: workspace
@@ -24286,8 +24421,13 @@ async function createProductionRuntime(options) {
24286
24421
  address,
24287
24422
  authHome: home,
24288
24423
  registration: { id: collaboration.instanceId, alias, projectPath: workspace },
24289
- startBroker: () => ensurePalBrokerRunning({ address })
24424
+ startBroker: () => ensurePalBrokerRunning({ address, ...collaboration.startBroker === void 0 ? {} : { startBroker: () => collaboration.startBroker(address) } })
24290
24425
  });
24426
+ collaborationClient = collaboration.optional ? recoverablePalClient(baseCollaborationClient, () => {
24427
+ if (collaborationUnavailableDiagnostic === void 0) return;
24428
+ remove(diagnostics, collaborationUnavailableDiagnostic);
24429
+ collaborationUnavailableDiagnostic = void 0;
24430
+ }) : baseCollaborationClient;
24291
24431
  deliverCollaborationEvent = async (event) => {
24292
24432
  const session2 = collaborationSession;
24293
24433
  const client = collaborationClient;
@@ -24342,11 +24482,19 @@ async function createProductionRuntime(options) {
24342
24482
  }
24343
24483
  };
24344
24484
  unsubscribeCollaboration = collaborationClient.subscribe(enqueueCollaborationEvent);
24345
- tools.push(...createPalsTools(collaborationClient, {
24346
- selfId: collaboration.instanceId,
24347
- shareGuard: new CollaborationShareGuard({ redact: (value) => redactSecrets(value, secrets) })
24348
- }));
24349
- await collaborationClient.start();
24485
+ try {
24486
+ await collaborationClient.start();
24487
+ } catch (error) {
24488
+ if (!collaboration.optional) throw error;
24489
+ collaborationUnavailableDiagnostic = `Pals unavailable: ${message(error)}`;
24490
+ diagnostics.push(collaborationUnavailableDiagnostic);
24491
+ }
24492
+ if (collaborationClient !== void 0) {
24493
+ tools.push(...createPalsTools(collaborationClient, {
24494
+ selfId: collaboration.instanceId,
24495
+ shareGuard: new CollaborationShareGuard({ redact: (value) => redactSecrets(value, secrets) })
24496
+ }));
24497
+ }
24350
24498
  }
24351
24499
  const syncMcpTools = () => {
24352
24500
  for (const tool of mcpTools) remove(tools, tool);
@@ -25103,6 +25251,7 @@ async function createProductionRuntime(options) {
25103
25251
  }),
25104
25252
  rename: (alias) => collaborationClient.rename(alias),
25105
25253
  info: (target) => resolveCollaborationTarget(target),
25254
+ sendChat: (target, message2) => collaborationClient.sendChat(target, message2),
25106
25255
  sendTask: (target, goal) => collaborationClient.sendTask(target, goal),
25107
25256
  startCoWork: async (target, goal) => {
25108
25257
  const recipient = await resolveCollaborationTarget(target);
@@ -25363,6 +25512,7 @@ ${summary2}`;
25363
25512
  return sessionHistory.checkpoint(tagged, harness.main.context.snapshot());
25364
25513
  },
25365
25514
  tree: () => sessionHistory.tree(),
25515
+ historyLeaf: () => sessionHistory.leafId,
25366
25516
  rewind: async (nodeId) => {
25367
25517
  await sessionHistory.rewind(nodeId, harness.main.context.snapshot());
25368
25518
  await persist();
@@ -25551,7 +25701,7 @@ ${renderMemoryDocument(entries)}`;
25551
25701
  approvals,
25552
25702
  memoryReviews,
25553
25703
  restoredTranscript,
25554
- jobs: { list: () => jobs.list(), subscribe: (listener) => jobs.subscribe(listener) },
25704
+ jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
25555
25705
  get sessionId() {
25556
25706
  return sessionId;
25557
25707
  },
@@ -25832,23 +25982,23 @@ async function* persistEach(source, persist) {
25832
25982
  }
25833
25983
  }
25834
25984
  async function* durableTurn(source, journal, turnId, config) {
25835
- let failure;
25985
+ let failure2;
25836
25986
  let completed = false;
25837
25987
  try {
25838
25988
  for await (const event of source) {
25839
- if (event.type === "error") failure = event.error.message;
25989
+ if (event.type === "error") failure2 = event.error.message;
25840
25990
  yield event;
25841
25991
  }
25842
25992
  completed = true;
25843
25993
  } catch (error) {
25844
- failure = message(error);
25994
+ failure2 = message(error);
25845
25995
  throw error;
25846
25996
  } finally {
25847
- if (completed && failure === void 0) {
25997
+ if (completed && failure2 === void 0) {
25848
25998
  journal.completeTurn(turnId);
25849
25999
  journal.savepoint("turn-complete", config);
25850
26000
  } else {
25851
- journal.interruptTurn(turnId, failure ?? "turn consumer interrupted before completion");
26001
+ journal.interruptTurn(turnId, failure2 ?? "turn consumer interrupted before completion");
25852
26002
  }
25853
26003
  }
25854
26004
  }
@@ -28232,11 +28382,22 @@ var ProjectMemoryManager = class {
28232
28382
  this.#store = store;
28233
28383
  this.#enabled = enabled;
28234
28384
  }
28235
- async snapshot() {
28385
+ async snapshot(now = /* @__PURE__ */ new Date()) {
28386
+ const references = this.#enabled ? await this.#store.references() : [];
28236
28387
  return {
28237
28388
  enabled: this.#enabled,
28238
28389
  path: this.#store.path,
28239
- entries: this.#enabled ? await this.#store.list() : []
28390
+ entries: references.map((reference) => {
28391
+ const lastRecalledAt = Object.values(reference.recalls).sort().at(-1);
28392
+ return {
28393
+ id: reference.id,
28394
+ type: reference.type,
28395
+ content: reference.summary,
28396
+ recallTotal: reference.recallTotal,
28397
+ heat: classifyMemoryHeat(reference, now),
28398
+ ...lastRecalledAt === void 0 ? {} : { lastRecalledAt }
28399
+ };
28400
+ })
28240
28401
  };
28241
28402
  }
28242
28403
  async remember(candidate) {
@@ -28253,6 +28414,10 @@ var ProjectMemoryManager = class {
28253
28414
  this.#assertEnabled();
28254
28415
  return this.#store.delete(id);
28255
28416
  }
28417
+ async deleteCold(now = /* @__PURE__ */ new Date()) {
28418
+ this.#assertEnabled();
28419
+ return this.#store.forgetCold(now);
28420
+ }
28256
28421
  #assertEnabled() {
28257
28422
  if (!this.#enabled) throw new Error("Long-term memory is disabled for this project");
28258
28423
  }
@@ -28577,6 +28742,7 @@ async function d2cBackendSourceFingerprint(project) {
28577
28742
  var DesktopRuntimeController = class {
28578
28743
  #home;
28579
28744
  #createRuntime;
28745
+ #createTerminalService;
28580
28746
  #listSessions;
28581
28747
  #deleteStoredSession;
28582
28748
  #loadModels;
@@ -28599,8 +28765,10 @@ var DesktopRuntimeController = class {
28599
28765
  #skillManager;
28600
28766
  #memoryManager;
28601
28767
  #mcpManager;
28768
+ #terminal;
28602
28769
  #models = DEFAULT_DESKTOP_MODELS;
28603
28770
  #busy = false;
28771
+ #tasks;
28604
28772
  #disposeJobSubscription;
28605
28773
  #d2cMocks = /* @__PURE__ */ new Map();
28606
28774
  #d2cPreviews = /* @__PURE__ */ new Map();
@@ -28612,6 +28780,7 @@ var DesktopRuntimeController = class {
28612
28780
  constructor(options) {
28613
28781
  this.#home = resolve35(options.home ?? homedir4());
28614
28782
  this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
28783
+ this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
28615
28784
  this.#listSessions = options.listSessions ?? (async (workspace) => {
28616
28785
  const store = new SessionStore({ workspace });
28617
28786
  const entries = await store.list();
@@ -28678,7 +28847,8 @@ var DesktopRuntimeController = class {
28678
28847
  },
28679
28848
  diagnostics: runtime?.diagnostics ?? [],
28680
28849
  models: this.#models,
28681
- jobs: runtime?.jobs?.list() ?? []
28850
+ jobs: runtime?.jobs?.list() ?? [],
28851
+ ...this.#tasks === void 0 ? {} : { tasks: this.#tasks }
28682
28852
  };
28683
28853
  }
28684
28854
  async openWorkspace(path) {
@@ -28690,6 +28860,8 @@ var DesktopRuntimeController = class {
28690
28860
  await this.#stopAllD2cMocks();
28691
28861
  }
28692
28862
  this.#workspace = workspace;
28863
+ this.#terminal?.dispose();
28864
+ this.#terminal = this.#createTerminalService(workspace);
28693
28865
  this.#skillManager = new SkillManager({ workspace, home: this.#home });
28694
28866
  this.#memoryManager = await this.#loadMemoryManager(workspace, this.#home);
28695
28867
  this.#mcpManager = this.#loadMcpManager(workspace);
@@ -28720,6 +28892,7 @@ var DesktopRuntimeController = class {
28720
28892
  bufferedOutput.push(event);
28721
28893
  return;
28722
28894
  }
28895
+ this.#captureSessionOutput(event);
28723
28896
  this.#emit({ type: "session-output", sessionId: outputSessionId, event });
28724
28897
  },
28725
28898
  onApprovalChange: () => {
@@ -28743,6 +28916,7 @@ var DesktopRuntimeController = class {
28743
28916
  this.#emit({ type: "session-started", payload });
28744
28917
  outputReady = true;
28745
28918
  for (const event of bufferedOutput) {
28919
+ this.#captureSessionOutput(event);
28746
28920
  this.#emit({ type: "session-output", sessionId: runtime.sessionId, event });
28747
28921
  }
28748
28922
  this.#publishSnapshot();
@@ -28913,6 +29087,83 @@ var DesktopRuntimeController = class {
28913
29087
  if (deleted) await this.#runtime?.services.refreshMemory?.();
28914
29088
  return deleted;
28915
29089
  }
29090
+ async deleteColdMemory() {
29091
+ const manager = this.#requireMemoryManager();
29092
+ if (manager.deleteCold === void 0) throw new Error("Cold-memory cleanup is unavailable");
29093
+ const result = await manager.deleteCold();
29094
+ if (result.removed > 0) await this.#runtime?.services.refreshMemory?.();
29095
+ return result.removed;
29096
+ }
29097
+ async historySnapshot() {
29098
+ const services = this.#requireRuntime().services;
29099
+ if (services.tree === void 0) throw new Error("Session history is unavailable");
29100
+ return { leafId: services.historyLeaf?.() ?? null, nodes: services.tree() };
29101
+ }
29102
+ async createCheckpoint(label) {
29103
+ const checkpoint = this.#requireRuntime().services.checkpoint;
29104
+ if (checkpoint === void 0) throw new Error("Session checkpoints are unavailable");
29105
+ return checkpoint(label?.trim() || void 0);
29106
+ }
29107
+ async rewindHistory(nodeId) {
29108
+ const rewind = this.#requireRuntime().services.rewind;
29109
+ if (rewind === void 0) throw new Error("Session rewind is unavailable");
29110
+ await rewind(nodeId);
29111
+ }
29112
+ async unrevertHistory() {
29113
+ const unrevert = this.#requireRuntime().services.unrevert;
29114
+ if (unrevert === void 0) throw new Error("Session unrevert is unavailable");
29115
+ await unrevert();
29116
+ }
29117
+ async forkHistory(nodeId) {
29118
+ const fork = this.#requireRuntime().services.fork;
29119
+ if (fork === void 0) throw new Error("Session fork is unavailable");
29120
+ await fork(nodeId);
29121
+ }
29122
+ openTerminal(input = {}) {
29123
+ return this.#requireTerminal().open({ ...input, owner: this.#requireRuntime().sessionId });
29124
+ }
29125
+ listTerminals() {
29126
+ return this.#requireTerminal().list(this.#requireRuntime().sessionId).filter((terminal) => terminal.state !== "closed");
29127
+ }
29128
+ readTerminal(id, cursor = 0) {
29129
+ return this.#requireTerminal().read(id, this.#requireRuntime().sessionId, cursor);
29130
+ }
29131
+ writeTerminal(id, data2) {
29132
+ this.#requireTerminal().write(id, this.#requireRuntime().sessionId, data2);
29133
+ }
29134
+ resizeTerminal(id, columns, rows) {
29135
+ this.#requireTerminal().resize(id, this.#requireRuntime().sessionId, columns, rows);
29136
+ }
29137
+ closeTerminal(id) {
29138
+ this.#requireTerminal().close(id, this.#requireRuntime().sessionId);
29139
+ }
29140
+ readJob(id, cursor = 0) {
29141
+ const runtime = this.#requireRuntime();
29142
+ if (runtime.jobs === void 0) throw new Error("Background job output is unavailable");
29143
+ const job = runtime.jobs.list().find((item) => item.id === id);
29144
+ if (job === void 0) throw new Error(`Unknown background job: ${id}`);
29145
+ return runtime.jobs.read(id, job.owner, cursor);
29146
+ }
29147
+ async listPals(verbose = true) {
29148
+ return this.#requirePals().list(verbose);
29149
+ }
29150
+ async sendPalMessage(target, message2, kind) {
29151
+ const pals = this.#requirePals();
29152
+ if (kind === "chat") {
29153
+ if (pals.sendChat === void 0) throw new Error("Pal chat is unavailable");
29154
+ return pals.sendChat(target, message2);
29155
+ }
29156
+ return pals.sendTask(target, message2);
29157
+ }
29158
+ async startCoWork(target, goal) {
29159
+ return this.#requirePals().startCoWork(target, goal);
29160
+ }
29161
+ async coWorkStatus(coWorkId) {
29162
+ return this.#requirePals().coWorkStatus(coWorkId);
29163
+ }
29164
+ async cancelCoWork(coWorkId, reason) {
29165
+ return this.#requirePals().cancelCoWork(coWorkId, reason);
29166
+ }
28916
29167
  async switchModel(modelId) {
28917
29168
  if (this.#busy) throw new Error("Stop the active task before switching models");
28918
29169
  if (!this.#models.some((model) => model.id === modelId)) {
@@ -29675,6 +29926,8 @@ VITE_API_BASE_URL=${running.url}
29675
29926
  await this.#disposeRuntime();
29676
29927
  await this.#stopAllD2cPreviews();
29677
29928
  await this.#stopAllD2cMocks();
29929
+ this.#terminal?.dispose();
29930
+ this.#terminal = void 0;
29678
29931
  }
29679
29932
  #publishSnapshot() {
29680
29933
  const snapshot4 = this.snapshot();
@@ -29695,6 +29948,7 @@ VITE_API_BASE_URL=${running.url}
29695
29948
  this.#disposeJobSubscription = void 0;
29696
29949
  this.#runtime = void 0;
29697
29950
  this.#busy = false;
29951
+ this.#tasks = void 0;
29698
29952
  if (runtime === void 0) return;
29699
29953
  await runtime.session.close();
29700
29954
  await runtime.dispose();
@@ -29732,6 +29986,23 @@ VITE_API_BASE_URL=${running.url}
29732
29986
  if (this.#mcpManager === void 0) throw new Error("Open a project before managing MCP services");
29733
29987
  return this.#mcpManager;
29734
29988
  }
29989
+ #captureSessionOutput(event) {
29990
+ if (event.type === "tasks") this.#tasks = event.snapshot;
29991
+ else if (event.type === "tasks-cleared") this.#tasks = void 0;
29992
+ }
29993
+ #requireRuntime() {
29994
+ if (this.#runtime === void 0) throw new Error("Start a session before using this feature");
29995
+ return this.#runtime;
29996
+ }
29997
+ #requireTerminal() {
29998
+ if (this.#terminal === void 0) throw new Error("Open a project before using the terminal");
29999
+ return this.#terminal;
30000
+ }
30001
+ #requirePals() {
30002
+ const pals = this.#requireRuntime().services.pals;
30003
+ if (pals === void 0) throw new Error("Pals are unavailable for this session");
30004
+ return pals;
30005
+ }
29735
30006
  };
29736
30007
 
29737
30008
  // src/desktop/security.ts
@@ -29748,6 +30019,16 @@ function normalizePersistedWorkspace(value) {
29748
30019
  const workspace = value.workspace;
29749
30020
  return typeof workspace === "string" && workspace.trim().length > 0 && workspace.length <= 32768 ? workspace : void 0;
29750
30021
  }
30022
+ function normalizePersistedDesktopProjects(value) {
30023
+ const workspace = normalizePersistedWorkspace(value);
30024
+ if (typeof value !== "object" || value === null) {
30025
+ return { ...workspace === void 0 ? {} : { workspace }, projects: workspace === void 0 ? [] : [workspace] };
30026
+ }
30027
+ const rawProjects = value.projects;
30028
+ const projects = Array.isArray(rawProjects) ? rawProjects.filter((item) => typeof item === "string" && item.trim().length > 0 && item.length <= 32768) : [];
30029
+ const unique = [.../* @__PURE__ */ new Set([...workspace === void 0 ? [] : [workspace], ...projects])].slice(0, 50);
30030
+ return { ...workspace === void 0 ? {} : { workspace }, projects: unique };
30031
+ }
29751
30032
  function isTrustedNavigation(target, current, trustedRenderer) {
29752
30033
  return target === trustedRenderer || current === trustedRenderer && target === current;
29753
30034
  }
@@ -29831,6 +30112,472 @@ ${reason.stack ?? ""}`;
29831
30112
  process.on("uncaughtException", (error) => crash("uncaughtException", error));
29832
30113
  }
29833
30114
 
30115
+ // src/desktop/git-manager.ts
30116
+ import { readFile as readFile36, rm as rm16 } from "fs/promises";
30117
+ import { isAbsolute as isAbsolute18, relative as relative26, resolve as resolve36 } from "path";
30118
+ function failure(action, stderr, stdout) {
30119
+ return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
30120
+ }
30121
+ async function desktopGitStatus(workspace) {
30122
+ if (!await isGitRepository(workspace)) return { repository: false, branch: "", head: "", files: [] };
30123
+ const summary2 = await changeSummary(workspace);
30124
+ return {
30125
+ repository: true,
30126
+ branch: summary2.branch,
30127
+ head: summary2.head,
30128
+ files: summary2.statusLines.map((line) => {
30129
+ const index = line[0] ?? " ";
30130
+ const worktree = line[1] ?? " ";
30131
+ return {
30132
+ path: line.slice(3).replace(/^.* -> /, ""),
30133
+ index,
30134
+ worktree,
30135
+ staged: index !== " " && index !== "?",
30136
+ unstaged: worktree !== " " && worktree !== "?",
30137
+ untracked: index === "?" && worktree === "?"
30138
+ };
30139
+ })
30140
+ };
30141
+ }
30142
+ async function desktopGitDiff(workspace, path, staged = false) {
30143
+ const status = await desktopGitStatus(workspace);
30144
+ const file = status.files.find((item) => item.path === path);
30145
+ if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
30146
+ if (file.untracked) {
30147
+ const target = safeWorkspacePath(workspace, path);
30148
+ const value = await readFile36(target, "utf8");
30149
+ return value.length > 12e4 ? `${value.slice(0, 12e4)}
30150
+ \u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
30151
+ }
30152
+ const result = await git2(workspace, ["diff", ...staged ? ["--staged"] : [], "--no-ext-diff", "--no-color", "--", path]);
30153
+ if (!result.ok) throw failure("\u8BFB\u53D6\u5DEE\u5F02", result.stderr, result.stdout);
30154
+ return result.stdout || "\uFF08\u6B64\u533A\u57DF\u6CA1\u6709\u5DEE\u5F02\uFF09";
30155
+ }
30156
+ async function desktopGitStage(workspace, path) {
30157
+ await assertListed(workspace, path);
30158
+ const result = await git2(workspace, ["add", "--", path]);
30159
+ if (!result.ok) throw failure("\u6682\u5B58", result.stderr, result.stdout);
30160
+ return desktopGitStatus(workspace);
30161
+ }
30162
+ async function desktopGitUnstage(workspace, path) {
30163
+ const file = await assertListed(workspace, path);
30164
+ if (!file.staged) return desktopGitStatus(workspace);
30165
+ const head = await git2(workspace, ["rev-parse", "--verify", "HEAD"]);
30166
+ const result = head.ok ? await git2(workspace, ["restore", "--staged", "--", path]) : await git2(workspace, ["rm", "--cached", "--", path]);
30167
+ if (!result.ok) throw failure("\u53D6\u6D88\u6682\u5B58", result.stderr, result.stdout);
30168
+ return desktopGitStatus(workspace);
30169
+ }
30170
+ async function desktopGitDiscard(workspace, path) {
30171
+ const file = await assertListed(workspace, path);
30172
+ if (file.untracked) await rm16(safeWorkspacePath(workspace, path), { recursive: false, force: false });
30173
+ else {
30174
+ const result = await git2(workspace, ["restore", "--worktree", "--", path]);
30175
+ if (!result.ok) throw failure("\u8FD8\u539F", result.stderr, result.stdout);
30176
+ }
30177
+ return desktopGitStatus(workspace);
30178
+ }
30179
+ async function desktopGitCommit(workspace, message2) {
30180
+ return { result: await commit(workspace, message2), status: await desktopGitStatus(workspace) };
30181
+ }
30182
+ async function desktopGitReviewDiff(workspace, input) {
30183
+ if (!await isGitRepository(workspace)) throw new Error("Review requires a Git repository");
30184
+ const paths = [...new Set(input.paths ?? [])].slice(0, 200);
30185
+ for (const path of paths) safeWorkspacePath(workspace, path);
30186
+ const suffix = paths.length === 0 ? [] : ["--", ...paths];
30187
+ let args;
30188
+ if (input.scope === "working") args = ["diff", "HEAD", "--no-ext-diff", "--no-color", ...suffix];
30189
+ else if (input.scope === "staged") args = ["diff", "--staged", "--no-ext-diff", "--no-color", ...suffix];
30190
+ else if (input.scope === "last-turn") args = ["diff", "HEAD", "--no-ext-diff", "--no-color", ...suffix];
30191
+ else {
30192
+ const target = safeGitTarget(input.target);
30193
+ args = input.scope === "commit" ? ["show", "--format=", "--no-ext-diff", "--no-color", target] : ["diff", `${target}...HEAD`, "--no-ext-diff", "--no-color"];
30194
+ }
30195
+ const result = await git2(workspace, args, 3e4);
30196
+ if (!result.ok) throw failure("\u8BFB\u53D6\u5BA1\u67E5\u5DEE\u5F02", result.stderr, result.stdout);
30197
+ return result.stdout.length > 24e4 ? `${result.stdout.slice(0, 24e4)}
30198
+ \u2026[diff truncated]` : result.stdout || "\uFF08\u6B64\u8303\u56F4\u6CA1\u6709\u5DEE\u5F02\uFF09";
30199
+ }
30200
+ function desktopGitHistory(workspace) {
30201
+ return fileHistory(workspace, void 0, 50);
30202
+ }
30203
+ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
30204
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(sessionId)) throw new Error("Invalid session id");
30205
+ const requested = new Set(paths.map((path) => {
30206
+ safeWorkspacePath(workspace, path);
30207
+ return path.replaceAll("\\", "/");
30208
+ }));
30209
+ try {
30210
+ const tree = JSON.parse(await readFile36(resolve36(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
30211
+ const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
30212
+ const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
30213
+ if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
30214
+ const load = async (id) => {
30215
+ if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
30216
+ return JSON.parse(await readFile36(resolve36(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
30217
+ };
30218
+ const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
30219
+ const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
30220
+ const newFiles = new Map(after.files.map((file) => [file.path, file.digest]));
30221
+ const changed = [.../* @__PURE__ */ new Set([...oldFiles.keys(), ...newFiles.keys()])].filter((path) => oldFiles.get(path) !== newFiles.get(path) && (requested.size === 0 || requested.has(path))).slice(0, 200);
30222
+ const object = async (digest) => {
30223
+ if (digest === void 0) return "";
30224
+ if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
30225
+ const value = await readFile36(resolve36(workspace, ".flavor", "checkpoints", "objects", digest));
30226
+ return value.includes(0) ? "[binary content]" : value.toString("utf8");
30227
+ };
30228
+ const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
30229
+ const result = chunks.join("\n");
30230
+ return result.length > 24e4 ? `${result.slice(0, 24e4)}
30231
+ \u2026[diff truncated]` : result || "\uFF08\u4E0A\u4E00\u4E2A assistant turn \u6CA1\u6709\u6587\u4EF6\u53D8\u5316\uFF09";
30232
+ } catch (error) {
30233
+ if (error.code === "ENOENT") return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
30234
+ throw error;
30235
+ }
30236
+ }
30237
+ function wholeFileDiff(path, before, after) {
30238
+ const oldLines = before.replace(/\n$/, "").split("\n");
30239
+ const newLines = after.replace(/\n$/, "").split("\n");
30240
+ return [`diff --flavor a/${path} b/${path}`, `--- ${before === "" ? "/dev/null" : `a/${path}`}`, `+++ ${after === "" ? "/dev/null" : `b/${path}`}`, `@@ -1,${before === "" ? 0 : oldLines.length} +1,${after === "" ? 0 : newLines.length} @@`, ...before === "" ? [] : oldLines.map((line) => `-${line}`), ...after === "" ? [] : newLines.map((line) => `+${line}`)].join("\n");
30241
+ }
30242
+ async function assertListed(workspace, path) {
30243
+ const file = (await desktopGitStatus(workspace)).files.find((item) => item.path === path);
30244
+ if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
30245
+ safeWorkspacePath(workspace, path);
30246
+ return file;
30247
+ }
30248
+ function safeWorkspacePath(workspace, path) {
30249
+ if (isAbsolute18(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
30250
+ const root = resolve36(workspace);
30251
+ const target = resolve36(root, path);
30252
+ const child = relative26(root, target);
30253
+ if (child === "" || child.startsWith("..") || isAbsolute18(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
30254
+ return target;
30255
+ }
30256
+ function safeGitTarget(value) {
30257
+ const target = value?.trim();
30258
+ if (target === void 0 || target.length === 0 || target.length > 256 || target.startsWith("-") || !/^[A-Za-z0-9][A-Za-z0-9._/@{}~^:-]*$/.test(target)) throw new Error("Invalid Git review target");
30259
+ return target;
30260
+ }
30261
+
30262
+ // src/desktop/worktree-manager.ts
30263
+ import { randomUUID as randomUUID26 } from "crypto";
30264
+ import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat15, writeFile as writeFile18 } from "fs/promises";
30265
+ import { basename as basename10, isAbsolute as isAbsolute19, join as join43, relative as relative27, resolve as resolve37, sep as sep27 } from "path";
30266
+ var DesktopWorktreeManager = class {
30267
+ #repository;
30268
+ #storage;
30269
+ constructor(options) {
30270
+ this.#repository = resolve37(options.repository);
30271
+ this.#storage = resolve37(options.storage);
30272
+ }
30273
+ async create(label) {
30274
+ if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
30275
+ const safe = label.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36) || "task";
30276
+ const suffix = randomUUID26().slice(0, 8);
30277
+ const id = `worktree-${safe}-${suffix}`;
30278
+ const branch = `flavor/desktop-${safe}-${suffix}`;
30279
+ const path = this.#safeStoragePath(id);
30280
+ await mkdir28(this.#storage, { recursive: true, mode: 448 });
30281
+ const result = await git2(this.#repository, ["worktree", "add", "-b", branch, path, "HEAD"], 6e4);
30282
+ if (!result.ok) throw new Error(`git worktree add failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30283
+ const stored = {
30284
+ version: 1,
30285
+ id,
30286
+ mode: "worktree",
30287
+ repository: this.#repository,
30288
+ path,
30289
+ branch,
30290
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
30291
+ };
30292
+ try {
30293
+ await writeFile18(this.#metadataPath(id), `${JSON.stringify(stored)}
30294
+ `, { encoding: "utf8", mode: 384 });
30295
+ } catch (error) {
30296
+ await git2(this.#repository, ["worktree", "remove", "--force", path], 6e4).catch(() => void 0);
30297
+ throw error;
30298
+ }
30299
+ return { ...stored, dirty: false, merged: false };
30300
+ }
30301
+ async list() {
30302
+ let names;
30303
+ try {
30304
+ names = await readdir13(this.#storage);
30305
+ } catch (error) {
30306
+ if (error.code === "ENOENT") return [];
30307
+ throw error;
30308
+ }
30309
+ const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
30310
+ try {
30311
+ const stored = validateStored(JSON.parse(await readFile37(join43(this.#storage, name), "utf8")), this.#repository, this.#storage);
30312
+ if ((await stat15(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
30313
+ const status = await git2(stored.path, ["status", "--porcelain"]);
30314
+ const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
30315
+ const { version: _version, ...item } = stored;
30316
+ return { ...item, dirty: status.ok ? status.stdout.trim().length > 0 : true, merged: merged.ok };
30317
+ } catch {
30318
+ return void 0;
30319
+ }
30320
+ }));
30321
+ return values.filter((item) => item !== void 0).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
30322
+ }
30323
+ async remove(id, force = false) {
30324
+ const item = await this.#load(id);
30325
+ const status = await git2(item.path, ["status", "--porcelain"]);
30326
+ const dirty = !status.ok || status.stdout.trim().length > 0;
30327
+ if (dirty && !force) throw new Error("Worktree is dirty; review or explicitly force removal");
30328
+ const result = await git2(this.#repository, ["worktree", "remove", ...force ? ["--force"] : [], item.path], 6e4);
30329
+ if (!result.ok) throw new Error(`git worktree remove failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30330
+ await rm17(this.#metadataPath(id), { force: true });
30331
+ }
30332
+ async merge(id) {
30333
+ const item = await this.#load(id);
30334
+ const [taskStatus, repositoryStatus] = await Promise.all([
30335
+ git2(item.path, ["status", "--porcelain"]),
30336
+ git2(this.#repository, ["status", "--porcelain"])
30337
+ ]);
30338
+ if (!taskStatus.ok || taskStatus.stdout.trim().length > 0) throw new Error("Commit or discard task worktree changes before handoff");
30339
+ if (!repositoryStatus.ok || repositoryStatus.stdout.trim().length > 0) throw new Error("The destination checkout must be clean before handoff");
30340
+ const result = await git2(this.#repository, ["merge", "--no-ff", "--no-edit", item.branch], 6e4);
30341
+ if (!result.ok) throw new Error(`Worktree handoff merge failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30342
+ }
30343
+ async #load(id) {
30344
+ const path = this.#metadataPath(id);
30345
+ return validateStored(JSON.parse(await readFile37(path, "utf8")), this.#repository, this.#storage);
30346
+ }
30347
+ #metadataPath(id) {
30348
+ if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
30349
+ return this.#safeStoragePath(`${id}.json`);
30350
+ }
30351
+ #safeStoragePath(name) {
30352
+ const target = resolve37(this.#storage, name);
30353
+ const delta = relative27(this.#storage, target);
30354
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta)) throw new Error("Worktree path escapes storage");
30355
+ return target;
30356
+ }
30357
+ };
30358
+ function validateStored(value, repository, storage) {
30359
+ if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
30360
+ const item = value;
30361
+ if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve37(item.repository ?? "") !== repository) {
30362
+ throw new Error("Invalid worktree metadata");
30363
+ }
30364
+ const path = resolve37(item.path);
30365
+ const delta = relative27(storage, path);
30366
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta) || basename10(path) !== item.id) {
30367
+ throw new Error("Worktree metadata escapes storage");
30368
+ }
30369
+ return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
30370
+ }
30371
+
30372
+ // src/desktop/workbench-service.ts
30373
+ import { readFile as readFile38, readdir as readdir14, stat as stat16 } from "fs/promises";
30374
+ import { homedir as homedir5 } from "os";
30375
+ import { join as join44, resolve as resolve38 } from "path";
30376
+ var MAX_TEXT = 128e3;
30377
+ var MAX_ITEMS = 200;
30378
+ var DesktopWorkbenchService = class {
30379
+ #workspace;
30380
+ #home;
30381
+ constructor(workspace, home = homedir5()) {
30382
+ this.#workspace = resolve38(workspace);
30383
+ this.#home = resolve38(home);
30384
+ }
30385
+ async inspect(sessionId) {
30386
+ const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
30387
+ this.#goals(),
30388
+ this.#instructions(),
30389
+ this.#permissionFiles(),
30390
+ this.#audit(),
30391
+ this.#context(sessionId)
30392
+ ]);
30393
+ return { goals, instructions, permissionFiles, audit, ...context === void 0 ? {} : { context } };
30394
+ }
30395
+ static normalizePreviewUrl(input) {
30396
+ let url;
30397
+ try {
30398
+ url = new URL(input.trim());
30399
+ } catch {
30400
+ throw new Error("Preview URL must be a valid loopback HTTP(S) URL");
30401
+ }
30402
+ if (!["http:", "https:"].includes(url.protocol) || !["127.0.0.1", "localhost", "::1", "[::1]"].includes(url.hostname.toLowerCase()) || url.username !== "" || url.password !== "") {
30403
+ throw new Error("Preview URL must use a loopback HTTP(S) origin");
30404
+ }
30405
+ return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
30406
+ }
30407
+ async #goals() {
30408
+ const root = join44(this.#workspace, ".flavor", "goals");
30409
+ const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
30410
+ const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
30411
+ try {
30412
+ return GoalStateSchema.parse(JSON.parse(await boundedRead2(join44(root, name))));
30413
+ } catch {
30414
+ return void 0;
30415
+ }
30416
+ }));
30417
+ return values.filter((item) => item !== void 0).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
30418
+ }
30419
+ async #instructions() {
30420
+ const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
30421
+ const items = await Promise.all(names.map(async (name) => {
30422
+ const path = join44(this.#workspace, name);
30423
+ try {
30424
+ return { name, path, content: redactErrorText(await boundedRead2(path)) };
30425
+ } catch (error) {
30426
+ if (error.code === "ENOENT") return void 0;
30427
+ throw error;
30428
+ }
30429
+ }));
30430
+ return items.filter((item) => item !== void 0);
30431
+ }
30432
+ async #permissionFiles() {
30433
+ const candidates = [
30434
+ ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve38(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
30435
+ { tier: "user", path: join44(this.#home, ".flavor-code", "permissions.json"), content: "" },
30436
+ { tier: "project", path: join44(this.#workspace, ".flavor", "permissions.json"), content: "" },
30437
+ { tier: "local", path: join44(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
30438
+ ];
30439
+ const values = await Promise.all(candidates.map(async (item) => {
30440
+ try {
30441
+ return { ...item, content: redactErrorText(await boundedRead2(item.path)) };
30442
+ } catch (error) {
30443
+ if (error.code === "ENOENT") return void 0;
30444
+ throw error;
30445
+ }
30446
+ }));
30447
+ return values.filter((item) => item !== void 0);
30448
+ }
30449
+ async #audit() {
30450
+ let raw;
30451
+ try {
30452
+ raw = await boundedRead2(join44(this.#workspace, ".flavor", "audit.jsonl"));
30453
+ } catch (error) {
30454
+ if (error.code === "ENOENT") return [];
30455
+ throw error;
30456
+ }
30457
+ return raw.split(/\r?\n/).filter(Boolean).slice(-MAX_ITEMS).flatMap((line) => {
30458
+ try {
30459
+ const value = JSON.parse(line);
30460
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? [redact2(value)] : [];
30461
+ } catch {
30462
+ return [];
30463
+ }
30464
+ }).reverse();
30465
+ }
30466
+ async #context(sessionId) {
30467
+ const usage = await readJsonLines2(join44(this.#workspace, ".flavor", "usage.jsonl"));
30468
+ if (sessionId === void 0) return { visibility: [], usage };
30469
+ try {
30470
+ const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
30471
+ return { ...document.conversation.epoch === void 0 ? {} : { epoch: redact2(document.conversation.epoch) }, visibility: (document.conversation.visibilityLog ?? []).slice(-MAX_ITEMS).map((item) => redact2(item)), usage };
30472
+ } catch {
30473
+ return { visibility: [], usage };
30474
+ }
30475
+ }
30476
+ };
30477
+ async function boundedRead2(path) {
30478
+ const info = await stat16(path);
30479
+ if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
30480
+ if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
30481
+ return readFile38(path, "utf8");
30482
+ }
30483
+ async function readJsonLines2(path) {
30484
+ try {
30485
+ return (await boundedRead2(path)).split(/\r?\n/).filter(Boolean).slice(-MAX_ITEMS).flatMap((line) => {
30486
+ try {
30487
+ const value = JSON.parse(line);
30488
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? [redact2(value)] : [];
30489
+ } catch {
30490
+ return [];
30491
+ }
30492
+ }).reverse();
30493
+ } catch (error) {
30494
+ if (error.code === "ENOENT") return [];
30495
+ throw error;
30496
+ }
30497
+ }
30498
+ function redact2(value, key = "") {
30499
+ if (/token|secret|password|authorization|api[-_]?key/i.test(key)) return "[REDACTED]";
30500
+ if (Array.isArray(value)) return value.slice(0, MAX_ITEMS).map((item) => redact2(item));
30501
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).slice(0, MAX_ITEMS).map(([name, item]) => [name, redact2(item, name)]));
30502
+ return typeof value === "string" && value.length > MAX_TEXT ? `${value.slice(0, MAX_TEXT)}\u2026` : value;
30503
+ }
30504
+
30505
+ // src/desktop/astgraph-service.ts
30506
+ import { stat as stat17 } from "fs/promises";
30507
+ import { createRequire as nodeCreateRequire2 } from "module";
30508
+ import { join as join45, resolve as resolve39 } from "path";
30509
+ var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
30510
+ var DesktopAstGraphService = class {
30511
+ #path;
30512
+ constructor(workspace) {
30513
+ this.#path = join45(resolve39(workspace), ".flavor", "astgraph", "index.db");
30514
+ }
30515
+ async status() {
30516
+ if ((await stat17(this.#path).catch(() => void 0))?.isFile() !== true) {
30517
+ return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
30518
+ }
30519
+ return this.#read((db) => {
30520
+ const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
30521
+ const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
30522
+ let indexedAt;
30523
+ try {
30524
+ const at = Number(JSON.parse(metadata?.value ?? "{}").at);
30525
+ if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
30526
+ } catch {
30527
+ }
30528
+ return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
30529
+ });
30530
+ }
30531
+ async search(query, limit = 40) {
30532
+ const value = query.trim().slice(0, 200);
30533
+ if (value.length === 0 || !(await this.status()).available) return [];
30534
+ return this.#read((db) => db.prepare(`SELECT ${FIELDS} FROM nodes WHERE lower(name) LIKE ? OR lower(qualified_name) LIKE ? ORDER BY CASE WHEN lower(name) = ? THEN 0 ELSE 1 END, name LIMIT ?`).all(`%${value.toLowerCase()}%`, `%${value.toLowerCase()}%`, value.toLowerCase(), Math.min(Math.max(limit, 1), 100)).map(mapNode));
30535
+ }
30536
+ async relations(id, hops = 2) {
30537
+ if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
30538
+ if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
30539
+ return this.#read((db) => {
30540
+ const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
30541
+ const linked = (direction) => {
30542
+ const source = direction === "callers" ? "e.source" : "e.target";
30543
+ const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
30544
+ return db.prepare(`SELECT DISTINCT n.${FIELDS.split(", ").join(", n.")} FROM edges e JOIN nodes n ON n.id = ${source} WHERE ${predicate} AND e.kind IN ('calls','imports') LIMIT 100`).all(id).map(mapNode);
30545
+ };
30546
+ const seen = /* @__PURE__ */ new Map();
30547
+ let frontier = [id];
30548
+ for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
30549
+ const next = [];
30550
+ for (const current of frontier) {
30551
+ const rows = db.prepare("SELECT source AS other FROM edges WHERE target = ? UNION SELECT target AS other FROM edges WHERE source = ? LIMIT 200").all(current, current);
30552
+ for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
30553
+ seen.set(row.other, hop);
30554
+ next.push(row.other);
30555
+ }
30556
+ }
30557
+ frontier = next;
30558
+ }
30559
+ const impact = [...seen].flatMap(([nodeId, hop]) => {
30560
+ const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
30561
+ return row === void 0 ? [] : [{ ...mapNode(row), hop }];
30562
+ });
30563
+ return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
30564
+ });
30565
+ }
30566
+ #read(run) {
30567
+ const sqlite = nodeCreateRequire2(import.meta.url)("node:sqlite");
30568
+ const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
30569
+ try {
30570
+ return run(db);
30571
+ } finally {
30572
+ db.close();
30573
+ }
30574
+ }
30575
+ };
30576
+ function mapNode(value) {
30577
+ const row = value;
30578
+ return { id: String(row.id), kind: String(row.kind), name: String(row.name), qualifiedName: String(row.qualified_name), filePath: String(row.file_path), language: String(row.language), startLine: Number(row.start_line), endLine: Number(row.end_line), ...row.signature == null ? {} : { signature: String(row.signature) } };
30579
+ }
30580
+
29834
30581
  // src/desktop/main.ts
29835
30582
  installCrashGuard();
29836
30583
  var moduleDirectory = dirname24(fileURLToPath4(import.meta.url));
@@ -29840,7 +30587,7 @@ var appMenu;
29840
30587
  var quitting = false;
29841
30588
  function logStartup(step, detail) {
29842
30589
  try {
29843
- const logPath = join43(dirname24(process.execPath), "flavor-code-startup.log");
30590
+ const logPath = join46(dirname24(process.execPath), "flavor-code-startup.log");
29844
30591
  const ts = (/* @__PURE__ */ new Date()).toISOString();
29845
30592
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
29846
30593
  `);
@@ -29865,7 +30612,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
29865
30612
  };
29866
30613
  });
29867
30614
  var d2cJudgeClient = createD2cJudgeClient();
29868
- var judgeStore = () => createD2cJudgeConfigStore(join43(app.getPath("userData"), "d2c-judge.json"));
30615
+ var judgeStore = () => createD2cJudgeConfigStore(join46(app.getPath("userData"), "d2c-judge.json"));
29869
30616
  function judgeImage(png) {
29870
30617
  const image = nativeImage.createFromBuffer(png);
29871
30618
  const size = image.getSize();
@@ -29874,70 +30621,342 @@ function judgeImage(png) {
29874
30621
  const ratio = 2048 / longest;
29875
30622
  return image.resize({ width: Math.max(1, Math.round(size.width * ratio)), height: Math.max(1, Math.round(size.height * ratio)), quality: "best" }).toPNG();
29876
30623
  }
29877
- var controller = new DesktopRuntimeController({
29878
- emit: emitDesktopEvent,
29879
- runD2cInteractionTests: (manifest, baseUrl, mockUrl) => d2cEmbedded.run(manifest, baseUrl, mockUrl),
29880
- observeD2cPages: (manifest, baseUrl) => d2cEmbedded.observe(manifest, baseUrl),
29881
- captureD2cPreview: (url) => d2cEmbedded.capture(url),
29882
- d2cJudge: {
29883
- config: () => judgeStore().view(),
29884
- saveConfig: (input) => judgeStore().save(input),
29885
- evaluate: async ({ report, interaction, designPng, implementationPng }) => {
29886
- const config = await judgeStore().load();
29887
- if (config === void 0) throw new Error("Configure a multimodal D2C judge model before running the quality gate");
29888
- const assessment = await d2cJudgeClient.evaluate(config, {
29889
- prompt: buildD2cJudgePrompt({ report, interaction }),
29890
- designPng: judgeImage(designPng),
29891
- implementationPng: judgeImage(implementationPng)
29892
- });
29893
- return finalizeD2cQualityJudgment({ assessment, report, interaction, model: config.model, passThreshold: config.passThreshold });
30624
+ var workbench = {
30625
+ projectMeta: {},
30626
+ sessionMeta: {},
30627
+ activities: [],
30628
+ recoveryItems: [],
30629
+ runningSessions: [],
30630
+ taskCheckouts: [],
30631
+ cleanShutdown: true
30632
+ };
30633
+ var managedProjects = /* @__PURE__ */ new Map();
30634
+ var projectOrder = [];
30635
+ var activeWorkspace;
30636
+ function sessionMetaKey(workspace, sessionId) {
30637
+ return `${workspace}\0${sessionId}`;
30638
+ }
30639
+ function worktreeManager(workspace) {
30640
+ const key = createHash27("sha256").update(resolve40(workspace)).digest("hex").slice(0, 20);
30641
+ return new DesktopWorktreeManager({ repository: workspace, storage: join46(app.getPath("userData"), "task-worktrees", key) });
30642
+ }
30643
+ function activeWorkingDirectory() {
30644
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
30645
+ return managedProjects.get(activeWorkspace)?.selectedTask?.workingDirectory ?? activeWorkspace;
30646
+ }
30647
+ function projectSessions(workspace, project) {
30648
+ const merged = new Map(project.snapshot.sessions.map((session) => [session.sessionId, session]));
30649
+ const taskBySession = new Map([...project.tasks].flatMap((task) => task.sessionId === void 0 ? [] : [[task.sessionId, task]]));
30650
+ for (const task of project.tasks) for (const session of task.snapshot.sessions) {
30651
+ const previous = merged.get(session.sessionId);
30652
+ if (previous === void 0 || session.updatedAt >= previous.updatedAt) merged.set(session.sessionId, session);
30653
+ }
30654
+ return [...merged.values()].map((session) => {
30655
+ const meta = workbench.sessionMeta[sessionMetaKey(workspace, session.sessionId)] ?? {};
30656
+ const activity = workbench.activities.find((item) => item.workspace === workspace && item.sessionId === session.sessionId);
30657
+ const running = [...project.tasks].some((task2) => task2.sessionId === session.sessionId && task2.snapshot.activeSession?.busy);
30658
+ const task = taskBySession.get(session.sessionId);
30659
+ return { ...session, ...meta, ...task === void 0 ? {} : { environment: task.environment, workingDirectory: task.workingDirectory, ...task.worktreeId === void 0 ? {} : { worktreeId: task.worktreeId } }, ...running ? { activity: "running" } : activity === void 0 ? {} : { activity: activity.kind, unread: activity.unread } };
30660
+ }).sort((left, right) => Number(Boolean(right.pinned)) - Number(Boolean(left.pinned)) || right.updatedAt.localeCompare(left.updatedAt));
30661
+ }
30662
+ function projectSummaries() {
30663
+ return [...projectOrder].sort((left, right) => Number(Boolean(workbench.projectMeta[right]?.pinned)) - Number(Boolean(workbench.projectMeta[left]?.pinned))).flatMap((workspace) => {
30664
+ const project = managedProjects.get(workspace);
30665
+ if (project === void 0) return [];
30666
+ const snapshot4 = project.selectedTask?.snapshot ?? project.snapshot;
30667
+ const active = snapshot4.activeSession;
30668
+ const runningSessions = [...project.tasks].flatMap((task) => task.sessionId !== void 0 && task.snapshot.activeSession?.busy ? [task.sessionId] : []);
30669
+ return [{
30670
+ workspace,
30671
+ ...workbench.projectMeta[workspace],
30672
+ sessions: projectSessions(workspace, project),
30673
+ ...active === void 0 ? {} : { activeSession: { sessionId: active.sessionId, busy: active.busy } },
30674
+ running: runningSessions.length > 0 || snapshot4.jobs.some((job) => job.state === "running"),
30675
+ runningSessions,
30676
+ unreadCount: workbench.activities.filter((item) => item.workspace === workspace && item.unread).length
30677
+ }];
30678
+ });
30679
+ }
30680
+ function decorateSnapshot(snapshot4) {
30681
+ const workspace = snapshot4.workspace !== void 0 && managedProjects.has(snapshot4.workspace) ? snapshot4.workspace : activeWorkspace ?? snapshot4.workspace;
30682
+ const selected = workspace === void 0 ? void 0 : managedProjects.get(workspace)?.selectedTask;
30683
+ return {
30684
+ ...snapshot4,
30685
+ ...workspace === void 0 ? {} : { workspace },
30686
+ ...snapshot4.activeSession === void 0 || selected === void 0 ? {} : { activeSession: { ...snapshot4.activeSession, environment: selected.environment, workingDirectory: selected.workingDirectory, ...selected.worktreeId === void 0 ? {} : { worktreeId: selected.worktreeId } } },
30687
+ sessions: workspace === void 0 ? snapshot4.sessions : projectSessions(workspace, managedProjects.get(workspace) ?? { controller, snapshot: snapshot4, tasks: /* @__PURE__ */ new Set() }),
30688
+ projects: projectSummaries(),
30689
+ activities: workbench.activities,
30690
+ recoveryItems: workbench.recoveryItems
30691
+ };
30692
+ }
30693
+ function decorateSessionStarted(payload) {
30694
+ return { ...payload, snapshot: decorateSnapshot(payload.snapshot) };
30695
+ }
30696
+ function emitManagedDesktopEvent(workspace, owner2, event) {
30697
+ if (workspace === void 0) {
30698
+ emitDesktopEvent(event.type === "snapshot" ? { ...event, snapshot: decorateSnapshot(event.snapshot) } : event);
30699
+ return;
30700
+ }
30701
+ const project = managedProjects.get(workspace);
30702
+ if (project !== void 0) {
30703
+ if (event.type === "snapshot") {
30704
+ event = { ...event, snapshot: { ...event.snapshot, workspace } };
30705
+ const previous = owner2?.snapshot;
30706
+ if (owner2 !== void 0) {
30707
+ owner2.snapshot = event.snapshot;
30708
+ if (owner2.payload !== void 0) owner2.payload = { ...owner2.payload, snapshot: event.snapshot };
30709
+ } else project.snapshot = event.snapshot;
30710
+ trackTaskTransition(workspace, owner2, previous, event.snapshot);
30711
+ } else if (event.type === "session-started") {
30712
+ event = { ...event, payload: { ...event.payload, snapshot: { ...event.payload.snapshot, workspace } } };
30713
+ if (owner2 !== void 0) {
30714
+ owner2.sessionId = event.payload.sessionId;
30715
+ owner2.payload = event.payload;
30716
+ owner2.snapshot = event.payload.snapshot;
30717
+ } else project.snapshot = event.payload.snapshot;
30718
+ } else if (event.type === "session-output" && owner2 !== void 0) {
30719
+ if (event.event.type === "done") owner2.terminal = "completed";
30720
+ else if (event.event.type === "error") owner2.terminal = "failed";
30721
+ else if (event.event.type === "exit") owner2.terminal = "interrupted";
30722
+ } else if (event.type === "runtime-error") {
30723
+ if (owner2 !== void 0) owner2.terminal = "failed";
30724
+ addActivity(workspace, event.sessionId ?? owner2?.sessionId, "failed", "\u4EFB\u52A1\u6267\u884C\u5931\u8D25", event.message);
30725
+ }
30726
+ }
30727
+ const ownerSelected = owner2 === void 0 ? project?.selectedTask === void 0 : project?.selectedTask === owner2;
30728
+ if (workspace === activeWorkspace && ownerSelected) {
30729
+ if (event.type === "snapshot") emitDesktopEvent({ ...event, workspace, snapshot: decorateSnapshot(event.snapshot) });
30730
+ else if (event.type === "session-started") emitDesktopEvent({ ...event, workspace, payload: decorateSessionStarted(event.payload) });
30731
+ else emitDesktopEvent({ ...event, workspace });
30732
+ return;
30733
+ }
30734
+ if (event.type === "session-output") emitDesktopEvent({ ...event, workspace });
30735
+ if ((event.type === "snapshot" || event.type === "session-started") && activeWorkspace !== void 0) {
30736
+ emitDesktopEvent({ type: "snapshot", workspace: activeWorkspace, snapshot: decorateSnapshot(controller.snapshot()) });
30737
+ }
30738
+ }
30739
+ function createDesktopController(workspace, owner2) {
30740
+ const collaborationId = randomUUID27();
30741
+ return new DesktopRuntimeController({
30742
+ emit: (event) => emitManagedDesktopEvent(workspace, owner2, event),
30743
+ runD2cInteractionTests: (manifest, baseUrl, mockUrl) => d2cEmbedded.run(manifest, baseUrl, mockUrl),
30744
+ observeD2cPages: (manifest, baseUrl) => d2cEmbedded.observe(manifest, baseUrl),
30745
+ captureD2cPreview: (url) => d2cEmbedded.capture(url),
30746
+ d2cJudge: {
30747
+ config: () => judgeStore().view(),
30748
+ saveConfig: (input) => judgeStore().save(input),
30749
+ evaluate: async ({ report, interaction, designPng, implementationPng }) => {
30750
+ const config = await judgeStore().load();
30751
+ if (config === void 0) throw new Error("Configure a multimodal D2C judge model before running the quality gate");
30752
+ const assessment = await d2cJudgeClient.evaluate(config, {
30753
+ prompt: buildD2cJudgePrompt({ report, interaction }),
30754
+ designPng: judgeImage(designPng),
30755
+ implementationPng: judgeImage(implementationPng)
30756
+ });
30757
+ return finalizeD2cQualityJudgment({ assessment, report, interaction, model: config.model, passThreshold: config.passThreshold });
30758
+ },
30759
+ planInteractions: async (input) => {
30760
+ const config = await judgeStore().load();
30761
+ if (config === void 0) throw new Error("Configure a multimodal D2C judge model before autonomous interaction review");
30762
+ return d2cJudgeClient.planInteractions(config, {
30763
+ prompt: buildD2cAutonomousPlanPrompt(input),
30764
+ screenshots: input.observations.map((page) => judgeImage(page.screenshot)),
30765
+ observedPages: input.observations.map((page) => page.url),
30766
+ observedSelectors: input.observations.flatMap((page) => page.elements.map((element) => element.selector))
30767
+ });
30768
+ }
29894
30769
  },
29895
- planInteractions: async (input) => {
29896
- const config = await judgeStore().load();
29897
- if (config === void 0) throw new Error("Configure a multimodal D2C judge model before autonomous interaction review");
29898
- return d2cJudgeClient.planInteractions(config, {
29899
- prompt: buildD2cAutonomousPlanPrompt(input),
29900
- screenshots: input.observations.map((page) => judgeImage(page.screenshot)),
29901
- observedPages: input.observations.map((page) => page.url),
29902
- observedSelectors: input.observations.flatMap((page) => page.elements.map((element) => element.selector))
29903
- });
29904
- }
29905
- },
29906
- createRuntime: async (runtimeOptions) => createProductionRuntime({
29907
- ...runtimeOptions,
29908
- ...runtimeOptions.workspace === void 0 ? {} : {
29909
- extraTools: createD2cTools(runtimeOptions.workspace, {
29910
- capture: d2cCapture,
29911
- onProgress: (progress) => emitDesktopEvent({ type: "d2c-progress", payload: progress }),
29912
- onReport: (report) => emitDesktopEvent({ type: "d2c-report", payload: report })
29913
- })
29914
- }
29915
- })
29916
- });
30770
+ createRuntime: async (runtimeOptions) => createProductionRuntime({
30771
+ ...runtimeOptions,
30772
+ collaboration: {
30773
+ instanceId: collaborationId,
30774
+ alias: `desktop-${basename11(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
30775
+ optional: true,
30776
+ startBroker: (address) => {
30777
+ const entry2 = app.isPackaged ? join46(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join46(moduleDirectory, "pals-broker-entry.js");
30778
+ return spawnPalBrokerProcess({
30779
+ address,
30780
+ executable: process.execPath,
30781
+ args: [entry2, address],
30782
+ environment: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }
30783
+ });
30784
+ }
30785
+ },
30786
+ ...runtimeOptions.workspace === void 0 ? {} : {
30787
+ extraTools: createD2cTools(runtimeOptions.workspace, {
30788
+ capture: d2cCapture,
30789
+ onProgress: (progress) => emitManagedDesktopEvent(workspace, owner2, { type: "d2c-progress", payload: progress }),
30790
+ onReport: (report) => emitManagedDesktopEvent(workspace, owner2, { type: "d2c-report", payload: report })
30791
+ })
30792
+ }
30793
+ })
30794
+ });
30795
+ }
30796
+ var detachedController = createDesktopController();
30797
+ var controller = detachedController;
29917
30798
  function statePath() {
29918
- return join43(app.getPath("userData"), "desktop-state.json");
30799
+ return join46(app.getPath("userData"), "desktop-state.json");
29919
30800
  }
29920
- async function loadPersistedWorkspace() {
30801
+ async function loadPersistedProjects() {
29921
30802
  try {
29922
- const raw = await readFile36(statePath(), "utf8");
29923
- if (raw.length > 4e4) return void 0;
29924
- return normalizePersistedWorkspace(JSON.parse(raw));
30803
+ const raw = await readFile39(statePath(), "utf8");
30804
+ if (raw.length > 1e5) return { projects: [] };
30805
+ const parsed = JSON.parse(raw);
30806
+ Object.assign(workbench, {
30807
+ projectMeta: validRecord(parsed.projectMeta),
30808
+ sessionMeta: validRecord(parsed.sessionMeta),
30809
+ activities: Array.isArray(parsed.activities) ? parsed.activities.slice(0, 500) : [],
30810
+ recoveryItems: Array.isArray(parsed.recoveryItems) ? parsed.recoveryItems.slice(0, 100) : [],
30811
+ runningSessions: Array.isArray(parsed.runningSessions) ? parsed.runningSessions.slice(0, 100) : [],
30812
+ taskCheckouts: Array.isArray(parsed.taskCheckouts) ? parsed.taskCheckouts.filter((item) => typeof item === "object" && item !== null && item.environment === "worktree" && typeof item.workspace === "string" && typeof item.sessionId === "string" && typeof item.workingDirectory === "string" && typeof item.worktreeId === "string").slice(0, 100) : [],
30813
+ cleanShutdown: parsed.cleanShutdown !== false
30814
+ });
30815
+ if (!workbench.cleanShutdown) {
30816
+ const interruptedAt = (/* @__PURE__ */ new Date()).toISOString();
30817
+ workbench.recoveryItems = workbench.runningSessions.map((item) => ({ ...item, interruptedAt, reason: "\u5E94\u7528\u4E0A\u6B21\u9000\u51FA\u65F6\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C" }));
30818
+ workbench.activities = [...workbench.runningSessions.map((item, index) => ({
30819
+ id: `recovery-${Date.now()}-${index}`,
30820
+ ...item,
30821
+ kind: "interrupted",
30822
+ title: "\u4EFB\u52A1\u56E0\u5E94\u7528\u9000\u51FA\u800C\u4E2D\u65AD",
30823
+ detail: "\u53EF\u4EE5\u6062\u590D\u4F1A\u8BDD\u5E76\u7EE7\u7EED\u6267\u884C",
30824
+ createdAt: interruptedAt,
30825
+ unread: true
30826
+ })), ...workbench.activities].slice(0, 500);
30827
+ }
30828
+ return normalizePersistedDesktopProjects(parsed);
29925
30829
  } catch {
29926
- return void 0;
30830
+ return { projects: [] };
29927
30831
  }
29928
30832
  }
29929
- async function savePersistedWorkspace(workspace) {
29930
- await writeFile18(statePath(), `${JSON.stringify({ workspace })}
30833
+ async function savePersistedProjects() {
30834
+ await writeFile19(statePath(), `${JSON.stringify({ workspace: activeWorkspace, projects: projectOrder, ...workbench })}
29931
30835
  `, { encoding: "utf8", mode: 384 });
29932
30836
  }
30837
+ function validRecord(value) {
30838
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
30839
+ }
29933
30840
  async function assertDirectory(path) {
29934
- if (!(await stat15(path)).isDirectory()) throw new Error("The selected project is not a directory");
30841
+ if (!(await stat18(path)).isDirectory()) throw new Error("The selected project is not a directory");
29935
30842
  }
29936
30843
  async function openWorkspace(path) {
29937
- await assertDirectory(path);
29938
- const snapshot4 = await controller.openWorkspace(path);
29939
- await savePersistedWorkspace(path);
29940
- return snapshot4;
30844
+ const workspace = resolve40(path);
30845
+ await assertDirectory(workspace);
30846
+ let project = managedProjects.get(workspace);
30847
+ if (project === void 0) {
30848
+ const nextController = createDesktopController(workspace);
30849
+ project = { controller: nextController, snapshot: nextController.snapshot(), tasks: /* @__PURE__ */ new Set() };
30850
+ managedProjects.set(workspace, project);
30851
+ projectOrder.unshift(workspace);
30852
+ activeWorkspace = workspace;
30853
+ controller = nextController;
30854
+ project.snapshot = await nextController.openWorkspace(workspace);
30855
+ for (const saved of workbench.taskCheckouts.filter((item) => resolve40(item.workspace) === workspace)) {
30856
+ try {
30857
+ await assertDirectory(saved.workingDirectory);
30858
+ const task = { environment: "worktree", workingDirectory: resolve40(saved.workingDirectory), worktreeId: saved.worktreeId };
30859
+ task.controller = createDesktopController(workspace, task);
30860
+ task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
30861
+ task.sessionId = saved.sessionId;
30862
+ project.tasks.add(task);
30863
+ } catch {
30864
+ }
30865
+ }
30866
+ } else {
30867
+ activeWorkspace = workspace;
30868
+ controller = project.selectedTask?.controller ?? project.controller;
30869
+ project.snapshot = await project.controller.refreshSessions();
30870
+ }
30871
+ await savePersistedProjects();
30872
+ return decorateSnapshot(project.selectedTask?.snapshot ?? project.snapshot);
30873
+ }
30874
+ function trackTaskTransition(workspace, task, previous, next) {
30875
+ const sessionId = task?.sessionId ?? next.activeSession?.sessionId;
30876
+ if (sessionId === void 0) return;
30877
+ const wasBusy = previous?.activeSession?.busy ?? false;
30878
+ const busy = next.activeSession?.busy ?? false;
30879
+ const key = sessionMetaKey(workspace, sessionId);
30880
+ if (!wasBusy && busy) {
30881
+ if (task !== void 0) delete task.terminal;
30882
+ workbench.activities = workbench.activities.filter((item) => !(item.workspace === workspace && item.sessionId === sessionId));
30883
+ workbench.runningSessions = [...workbench.runningSessions.filter((item) => sessionMetaKey(item.workspace, item.sessionId) !== key), { workspace, sessionId }];
30884
+ } else if (wasBusy && !busy) {
30885
+ workbench.runningSessions = workbench.runningSessions.filter((item) => sessionMetaKey(item.workspace, item.sessionId) !== key);
30886
+ const outcome = task?.terminal ?? "completed";
30887
+ addActivity(workspace, sessionId, outcome, outcome === "failed" ? "\u4EFB\u52A1\u6267\u884C\u5931\u8D25" : outcome === "interrupted" ? "\u4EFB\u52A1\u5DF2\u4E2D\u65AD" : "\u4EFB\u52A1\u5DF2\u8FD0\u884C\u5B8C\u6210");
30888
+ }
30889
+ const needsAttention = next.approval !== void 0 || (next.questions?.length ?? 0) > 0;
30890
+ const hadAttention = previous?.approval !== void 0 || (previous?.questions?.length ?? 0) > 0;
30891
+ if (needsAttention && !hadAttention) addActivity(workspace, sessionId, "attention", "\u4EFB\u52A1\u7B49\u5F85\u4F60\u7684\u786E\u8BA4");
30892
+ void savePersistedProjects();
30893
+ }
30894
+ function addActivity(workspace, sessionId, kind, title, detail) {
30895
+ const item = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, workspace, ...sessionId === void 0 ? {} : { sessionId }, kind, title, ...detail === void 0 ? {} : { detail }, createdAt: (/* @__PURE__ */ new Date()).toISOString(), unread: true };
30896
+ workbench.activities = [item, ...workbench.activities.filter((old) => !(old.workspace === workspace && old.sessionId === sessionId && old.kind === kind))].slice(0, 500);
30897
+ if (Notification.isSupported()) {
30898
+ const notification = new Notification({ title, body: detail ?? `${workbench.projectMeta[workspace]?.label ?? workspace.split(/[\\/]/).pop() ?? workspace}` });
30899
+ notification.on("click", () => {
30900
+ void activateManagedSession(workspace, sessionId);
30901
+ });
30902
+ notification.show();
30903
+ }
30904
+ void savePersistedProjects();
30905
+ }
30906
+ async function startManagedSession(resumeSession, environment = "local") {
30907
+ if (activeWorkspace === void 0) throw new Error("Open a project before starting a session");
30908
+ const project = managedProjects.get(activeWorkspace);
30909
+ if (resumeSession !== void 0) {
30910
+ const existing = [...project.tasks].find((task2) => task2.sessionId === resumeSession);
30911
+ if (existing?.payload !== void 0) {
30912
+ project.selectedTask = existing;
30913
+ controller = existing.controller;
30914
+ return decorateSessionStarted(existing.payload);
30915
+ }
30916
+ }
30917
+ if ([...project.tasks].filter((task2) => task2.snapshot.activeSession?.busy).length >= 4) throw new Error("\u6BCF\u4E2A\u9879\u76EE\u6700\u591A\u540C\u65F6\u8FD0\u884C 4 \u4E2A\u4EFB\u52A1");
30918
+ const isolated = environment === "worktree" ? await worktreeManager(activeWorkspace).create(`task-${Date.now()}`) : void 0;
30919
+ const task = {
30920
+ environment,
30921
+ workingDirectory: isolated?.path ?? activeWorkspace,
30922
+ ...isolated === void 0 ? {} : { worktreeId: isolated.id }
30923
+ };
30924
+ task.controller = createDesktopController(activeWorkspace, task);
30925
+ task.snapshot = task.controller.snapshot();
30926
+ project.tasks.add(task);
30927
+ project.selectedTask = task;
30928
+ controller = task.controller;
30929
+ await task.controller.openWorkspace(task.workingDirectory);
30930
+ const payload = await task.controller.startSession(resumeSession);
30931
+ task.sessionId = payload.sessionId;
30932
+ task.payload = payload;
30933
+ task.snapshot = payload.snapshot;
30934
+ if (task.environment === "worktree" && task.worktreeId !== void 0) {
30935
+ workbench.taskCheckouts = [...workbench.taskCheckouts.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === payload.sessionId)), { workspace: activeWorkspace, sessionId: payload.sessionId, environment: "worktree", workingDirectory: task.workingDirectory, worktreeId: task.worktreeId }];
30936
+ }
30937
+ project.snapshot = { ...project.snapshot, sessions: payload.snapshot.sessions };
30938
+ await savePersistedProjects();
30939
+ return decorateSessionStarted(payload);
30940
+ }
30941
+ async function activateManagedSession(workspace, sessionId) {
30942
+ await openWorkspace(workspace);
30943
+ if (sessionId === void 0) return void 0;
30944
+ const project = managedProjects.get(workspace);
30945
+ const task = [...project.tasks].find((item) => item.sessionId === sessionId);
30946
+ let payload;
30947
+ if (task !== void 0) {
30948
+ project.selectedTask = task;
30949
+ controller = task.controller;
30950
+ payload = task.payload ?? await task.controller.startSession(sessionId);
30951
+ task.payload = payload;
30952
+ task.snapshot = payload.snapshot;
30953
+ } else payload = await startManagedSession(sessionId);
30954
+ workbench.activities = workbench.activities.map((item) => item.workspace === workspace && item.sessionId === sessionId ? { ...item, unread: false } : item);
30955
+ mainWindow?.show();
30956
+ mainWindow?.focus();
30957
+ emitDesktopEvent({ type: "session-started", workspace, payload: decorateSessionStarted(payload) });
30958
+ await savePersistedProjects();
30959
+ return decorateSessionStarted(payload);
29941
30960
  }
29942
30961
  async function chooseAndOpenWorkspace() {
29943
30962
  const result = await dialog.showOpenDialog(mainWindow, {
@@ -29950,7 +30969,7 @@ async function chooseAndOpenWorkspace() {
29950
30969
  var cachedAppIconDataUrl;
29951
30970
  function appIconDataUrl() {
29952
30971
  if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
29953
- const icon = nativeImage.createFromPath(join43(app.getAppPath(), "assets", "icon.png"));
30972
+ const icon = nativeImage.createFromPath(join46(app.getAppPath(), "assets", "icon.png"));
29954
30973
  if (icon.isEmpty()) return void 0;
29955
30974
  cachedAppIconDataUrl = icon.toDataURL();
29956
30975
  return cachedAppIconDataUrl;
@@ -29958,13 +30977,23 @@ function appIconDataUrl() {
29958
30977
  function installIpcHandlers() {
29959
30978
  ipcMain.handle(DESKTOP_CHANNELS.appIcon, () => appIconDataUrl());
29960
30979
  ipcMain.handle(DESKTOP_CHANNELS.bootstrap, async () => {
29961
- const workspace = await loadPersistedWorkspace();
29962
- if (workspace === void 0) return controller.snapshot();
29963
- try {
29964
- return await openWorkspace(workspace);
29965
- } catch {
29966
- return controller.snapshot();
30980
+ const persisted = await loadPersistedProjects();
30981
+ workbench.cleanShutdown = false;
30982
+ for (const path of [...persisted.projects].reverse()) {
30983
+ try {
30984
+ await openWorkspace(path);
30985
+ } catch {
30986
+ }
30987
+ }
30988
+ if (persisted.workspace !== void 0 && managedProjects.has(resolve40(persisted.workspace))) {
30989
+ try {
30990
+ return await openWorkspace(persisted.workspace);
30991
+ } catch {
30992
+ }
29967
30993
  }
30994
+ const fallback = projectOrder[0];
30995
+ if (fallback !== void 0) return openWorkspace(fallback);
30996
+ return decorateSnapshot(controller.snapshot());
29968
30997
  });
29969
30998
  ipcMain.handle(DESKTOP_CHANNELS.chooseWorkspace, chooseAndOpenWorkspace);
29970
30999
  ipcMain.handle(DESKTOP_CHANNELS.openWorkspace, async (_event, value) => {
@@ -29972,12 +31001,170 @@ function installIpcHandlers() {
29972
31001
  return openWorkspace(path);
29973
31002
  });
29974
31003
  ipcMain.handle(DESKTOP_CHANNELS.startSession, async (_event, value) => {
29975
- const { resumeSession } = StartSessionInputSchema.parse(value);
29976
- return controller.startSession(resumeSession);
31004
+ const { resumeSession, environment } = StartSessionInputSchema.parse(value);
31005
+ return startManagedSession(resumeSession, environment);
31006
+ });
31007
+ ipcMain.handle(DESKTOP_CHANNELS.selectSession, async (_event, value) => {
31008
+ const { sessionId } = DeleteSessionInputSchema.parse(value);
31009
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31010
+ return activateManagedSession(activeWorkspace, sessionId);
29977
31011
  });
29978
31012
  ipcMain.handle(DESKTOP_CHANNELS.deleteSession, async (_event, value) => {
29979
31013
  const { sessionId } = DeleteSessionInputSchema.parse(value);
29980
- return controller.deleteSession(sessionId);
31014
+ if (activeWorkspace === void 0) return decorateSnapshot(await controller.deleteSession(sessionId));
31015
+ const project = managedProjects.get(activeWorkspace);
31016
+ const task = [...project.tasks].find((item) => item.sessionId === sessionId);
31017
+ if (task !== void 0) {
31018
+ if (task.snapshot.activeSession?.busy) throw new Error("\u8BF7\u5148\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1");
31019
+ await task.controller.dispose();
31020
+ project.tasks.delete(task);
31021
+ if (project.selectedTask === task) {
31022
+ delete project.selectedTask;
31023
+ controller = project.controller;
31024
+ }
31025
+ }
31026
+ delete workbench.sessionMeta[sessionMetaKey(activeWorkspace, sessionId)];
31027
+ workbench.taskCheckouts = workbench.taskCheckouts.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31028
+ workbench.activities = workbench.activities.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31029
+ project.snapshot = await project.controller.deleteSession(sessionId);
31030
+ await savePersistedProjects();
31031
+ return decorateSnapshot(project.selectedTask?.snapshot ?? project.snapshot);
31032
+ });
31033
+ ipcMain.handle(DESKTOP_CHANNELS.updateSession, async (_event, value) => {
31034
+ const input = UpdateSessionInputSchema.parse(value);
31035
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31036
+ const key = sessionMetaKey(activeWorkspace, input.sessionId);
31037
+ workbench.sessionMeta[key] = { ...workbench.sessionMeta[key], ...input.title === void 0 ? {} : { title: input.title || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned }, ...input.archived === void 0 ? {} : { archived: input.archived } };
31038
+ await savePersistedProjects();
31039
+ return decorateSnapshot(controller.snapshot());
31040
+ });
31041
+ ipcMain.handle(DESKTOP_CHANNELS.acknowledgeSession, async (_event, value) => {
31042
+ const sessionId = Object.keys(value).length === 0 ? void 0 : DeleteSessionInputSchema.parse(value).sessionId;
31043
+ workbench.activities = workbench.activities.map((item) => item.workspace === activeWorkspace && (sessionId === void 0 || item.sessionId === sessionId) ? { ...item, unread: false } : item);
31044
+ await savePersistedProjects();
31045
+ return decorateSnapshot(controller.snapshot());
31046
+ });
31047
+ ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
31048
+ const input = UpdateProjectInputSchema.parse(value);
31049
+ const workspace = resolve40(input.workspace);
31050
+ if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
31051
+ workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
31052
+ await savePersistedProjects();
31053
+ return decorateSnapshot(controller.snapshot());
31054
+ });
31055
+ ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
31056
+ const input = CloseProjectInputSchema.parse(value);
31057
+ const workspace = resolve40(input.workspace);
31058
+ const project = managedProjects.get(workspace);
31059
+ if (project === void 0) return decorateSnapshot(controller.snapshot());
31060
+ if ([...project.tasks].some((task) => task.snapshot.activeSession?.busy) && !input.force) throw new Error("\u9879\u76EE\u4E2D\u4ECD\u6709\u4EFB\u52A1\u8FD0\u884C\uFF1B\u786E\u8BA4\u540E\u518D\u5173\u95ED\u9879\u76EE");
31061
+ await Promise.all([project.controller, ...[...project.tasks].map((task) => task.controller)].map((item) => item.dispose()));
31062
+ managedProjects.delete(workspace);
31063
+ projectOrder.splice(projectOrder.indexOf(workspace), 1);
31064
+ if (activeWorkspace === workspace) {
31065
+ activeWorkspace = projectOrder[0];
31066
+ const next = activeWorkspace === void 0 ? void 0 : managedProjects.get(activeWorkspace);
31067
+ controller = next?.selectedTask?.controller ?? next?.controller ?? detachedController;
31068
+ }
31069
+ await savePersistedProjects();
31070
+ return decorateSnapshot(controller.snapshot());
31071
+ });
31072
+ ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
31073
+ const workspace = resolve40(ProjectPathInputSchema.parse(value).workspace);
31074
+ await assertDirectory(workspace);
31075
+ await shell.openPath(workspace);
31076
+ });
31077
+ ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
31078
+ clipboard.writeText(resolve40(ProjectPathInputSchema.parse(value).workspace));
31079
+ });
31080
+ ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
31081
+ const { sessionId } = DeleteSessionInputSchema.parse(value);
31082
+ workbench.recoveryItems = workbench.recoveryItems.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31083
+ await savePersistedProjects();
31084
+ return decorateSnapshot(controller.snapshot());
31085
+ });
31086
+ ipcMain.handle(DESKTOP_CHANNELS.gitStatus, async () => activeWorkspace === void 0 ? { repository: false, branch: "", head: "", files: [] } : desktopGitStatus(activeWorkingDirectory()));
31087
+ ipcMain.handle(DESKTOP_CHANNELS.gitDiff, async (_event, value) => {
31088
+ const input = GitDiffInputSchema.parse(value);
31089
+ return desktopGitDiff(activeWorkingDirectory(), input.path, input.staged);
31090
+ });
31091
+ ipcMain.handle(DESKTOP_CHANNELS.gitStage, async (_event, value) => desktopGitStage(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31092
+ ipcMain.handle(DESKTOP_CHANNELS.gitUnstage, async (_event, value) => desktopGitUnstage(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31093
+ ipcMain.handle(DESKTOP_CHANNELS.gitDiscard, async (_event, value) => desktopGitDiscard(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31094
+ ipcMain.handle(DESKTOP_CHANNELS.gitCommit, async (_event, value) => desktopGitCommit(activeWorkingDirectory(), GitCommitInputSchema.parse(value).message));
31095
+ ipcMain.handle(DESKTOP_CHANNELS.gitReview, async (_event, value) => {
31096
+ const input = GitReviewInputSchema.parse(value);
31097
+ const workspace = activeWorkingDirectory();
31098
+ return input.scope === "last-turn" && controller.snapshot().activeSession !== void 0 ? desktopLastTurnDiff(workspace, controller.snapshot().activeSession.sessionId, input.paths ?? []) : desktopGitReviewDiff(workspace, { scope: input.scope, ...input.target === void 0 ? {} : { target: input.target }, ...input.paths === void 0 ? {} : { paths: input.paths } });
31099
+ });
31100
+ ipcMain.handle(DESKTOP_CHANNELS.gitHistory, async () => desktopGitHistory(activeWorkingDirectory()));
31101
+ ipcMain.handle(DESKTOP_CHANNELS.listWorktrees, async () => activeWorkspace === void 0 ? [] : worktreeManager(activeWorkspace).list());
31102
+ ipcMain.handle(DESKTOP_CHANNELS.removeWorktree, async (_event, value) => {
31103
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31104
+ const input = WorktreeRemoveInputSchema.parse(value);
31105
+ const task = [...managedProjects.get(activeWorkspace).tasks].find((item) => item.worktreeId === input.id);
31106
+ if (task?.snapshot.activeSession?.busy) throw new Error("\u4E0D\u80FD\u5220\u9664\u6B63\u5728\u8FD0\u884C\u4EFB\u52A1\u7684\u5DE5\u4F5C\u6811");
31107
+ if (task !== void 0) {
31108
+ await task.controller.dispose();
31109
+ managedProjects.get(activeWorkspace).tasks.delete(task);
31110
+ }
31111
+ await worktreeManager(activeWorkspace).remove(input.id, input.force);
31112
+ workbench.taskCheckouts = workbench.taskCheckouts.filter((item) => !(item.workspace === activeWorkspace && item.worktreeId === input.id));
31113
+ await savePersistedProjects();
31114
+ });
31115
+ ipcMain.handle(DESKTOP_CHANNELS.mergeWorktree, async (_event, value) => {
31116
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31117
+ await worktreeManager(activeWorkspace).merge(WorktreeRemoveInputSchema.parse(value).id);
31118
+ });
31119
+ ipcMain.handle(DESKTOP_CHANNELS.historySnapshot, async () => controller.historySnapshot());
31120
+ ipcMain.handle(DESKTOP_CHANNELS.createCheckpoint, async (_event, value) => controller.createCheckpoint(CheckpointInputSchema.parse(value).label));
31121
+ ipcMain.handle(DESKTOP_CHANNELS.rewindHistory, async (_event, value) => controller.rewindHistory(HistoryNodeInputSchema.parse(value).nodeId));
31122
+ ipcMain.handle(DESKTOP_CHANNELS.unrevertHistory, async () => controller.unrevertHistory());
31123
+ ipcMain.handle(DESKTOP_CHANNELS.forkHistory, async (_event, value) => controller.forkHistory(HistoryNodeInputSchema.parse(value).nodeId));
31124
+ ipcMain.handle(DESKTOP_CHANNELS.terminalOpen, async (_event, value) => {
31125
+ const input = TerminalOpenInputSchema.parse(value);
31126
+ return controller.openTerminal({ ...input.cwd === void 0 ? {} : { cwd: input.cwd }, ...input.shell === void 0 ? {} : { shell: input.shell }, ...input.columns === void 0 ? {} : { columns: input.columns }, ...input.rows === void 0 ? {} : { rows: input.rows } });
31127
+ });
31128
+ ipcMain.handle(DESKTOP_CHANNELS.terminalList, async () => controller.listTerminals());
31129
+ ipcMain.handle(DESKTOP_CHANNELS.terminalRead, async (_event, value) => {
31130
+ const input = TerminalReadInputSchema.parse(value);
31131
+ return controller.readTerminal(input.id, input.cursor);
31132
+ });
31133
+ ipcMain.handle(DESKTOP_CHANNELS.terminalWrite, async (_event, value) => {
31134
+ const input = TerminalWriteInputSchema.parse(value);
31135
+ controller.writeTerminal(input.id, input.data);
31136
+ });
31137
+ ipcMain.handle(DESKTOP_CHANNELS.terminalResize, async (_event, value) => {
31138
+ const input = TerminalResizeInputSchema.parse(value);
31139
+ controller.resizeTerminal(input.id, input.columns, input.rows);
31140
+ });
31141
+ ipcMain.handle(DESKTOP_CHANNELS.terminalClose, async (_event, value) => controller.closeTerminal(TerminalIdInputSchema.parse(value).id));
31142
+ ipcMain.handle(DESKTOP_CHANNELS.jobRead, async (_event, value) => {
31143
+ const input = JobReadInputSchema.parse(value);
31144
+ return controller.readJob(input.id, input.cursor);
31145
+ });
31146
+ ipcMain.handle(DESKTOP_CHANNELS.previewValidate, async (_event, value) => DesktopWorkbenchService.normalizePreviewUrl(PreviewUrlInputSchema.parse(value).url));
31147
+ ipcMain.handle(DESKTOP_CHANNELS.previewOpen, async (_event, value) => shell.openExternal(DesktopWorkbenchService.normalizePreviewUrl(PreviewUrlInputSchema.parse(value).url)));
31148
+ ipcMain.handle(DESKTOP_CHANNELS.inspectWorkbench, async () => new DesktopWorkbenchService(activeWorkingDirectory()).inspect(controller.snapshot().activeSession?.sessionId));
31149
+ ipcMain.handle(DESKTOP_CHANNELS.astStatus, async () => new DesktopAstGraphService(activeWorkingDirectory()).status());
31150
+ ipcMain.handle(DESKTOP_CHANNELS.astSearch, async (_event, value) => new DesktopAstGraphService(activeWorkingDirectory()).search(AstSearchInputSchema.parse(value).query));
31151
+ ipcMain.handle(DESKTOP_CHANNELS.astRelations, async (_event, value) => {
31152
+ const input = AstNodeInputSchema.parse(value);
31153
+ return new DesktopAstGraphService(activeWorkingDirectory()).relations(input.id, input.hops);
31154
+ });
31155
+ ipcMain.handle(DESKTOP_CHANNELS.listPals, async () => controller.listPals());
31156
+ ipcMain.handle(DESKTOP_CHANNELS.sendPalMessage, async (_event, value) => {
31157
+ const input = PalMessageInputSchema.parse(value);
31158
+ return controller.sendPalMessage(input.target, input.message, input.kind);
31159
+ });
31160
+ ipcMain.handle(DESKTOP_CHANNELS.startCoWork, async (_event, value) => {
31161
+ const input = CoWorkStartInputSchema.parse(value);
31162
+ return controller.startCoWork(input.target, input.goal);
31163
+ });
31164
+ ipcMain.handle(DESKTOP_CHANNELS.coWorkStatus, async (_event, value) => controller.coWorkStatus(CoWorkStatusInputSchema.parse(value).coWorkId));
31165
+ ipcMain.handle(DESKTOP_CHANNELS.cancelCoWork, async (_event, value) => {
31166
+ const input = CoWorkCancelInputSchema.parse(value);
31167
+ return controller.cancelCoWork(input.coWorkId, input.reason);
29981
31168
  });
29982
31169
  ipcMain.handle(DESKTOP_CHANNELS.showAppMenu, async (_event, value) => {
29983
31170
  const { menu, x, y } = AppMenuInputSchema.parse(value);
@@ -30046,11 +31233,13 @@ function installIpcHandlers() {
30046
31233
  ipcMain.handle(DESKTOP_CHANNELS.deleteMemory, async (_event, value) => {
30047
31234
  return controller.deleteMemory(DeleteMemoryInputSchema.parse(value).id);
30048
31235
  });
31236
+ ipcMain.handle(DESKTOP_CHANNELS.deleteColdMemory, async () => controller.deleteColdMemory());
30049
31237
  ipcMain.handle(DESKTOP_CHANNELS.switchModel, async (_event, value) => {
30050
- return controller.switchModel(SwitchDesktopModelInputSchema.parse(value).modelId);
31238
+ return decorateSnapshot(await controller.switchModel(SwitchDesktopModelInputSchema.parse(value).modelId));
30051
31239
  });
30052
31240
  ipcMain.handle(DESKTOP_CHANNELS.addModel, async (_event, value) => {
30053
- return controller.addModel(AddDesktopModelInputSchema.parse(value));
31241
+ const result = await controller.addModel(AddDesktopModelInputSchema.parse(value));
31242
+ return { ...result, snapshot: decorateSnapshot(result.snapshot) };
30054
31243
  });
30055
31244
  ipcMain.handle(DESKTOP_CHANNELS.d2cImport, async (_event, value) => {
30056
31245
  const { task } = D2cImportInputSchema.parse(value);
@@ -30198,7 +31387,7 @@ function applicationMenu() {
30198
31387
  ]);
30199
31388
  }
30200
31389
  async function createWindow() {
30201
- const rendererPath = join43(app.getAppPath(), "dist", "desktop-renderer", "index.html");
31390
+ const rendererPath = join46(app.getAppPath(), "dist", "desktop-renderer", "index.html");
30202
31391
  mainWindow = new BrowserWindow2({
30203
31392
  title: "Flavor Code",
30204
31393
  width: 1280,
@@ -30209,14 +31398,14 @@ async function createWindow() {
30209
31398
  show: false,
30210
31399
  ...desktopWindowChrome(),
30211
31400
  webPreferences: {
30212
- preload: join43(moduleDirectory, "preload.cjs"),
31401
+ preload: join46(moduleDirectory, "preload.cjs"),
30213
31402
  contextIsolation: true,
30214
31403
  nodeIntegration: false,
30215
31404
  sandbox: false,
30216
31405
  webSecurity: true
30217
31406
  }
30218
31407
  });
30219
- const iconPath = join43(app.getAppPath(), "assets", "icon.png");
31408
+ const iconPath = join46(app.getAppPath(), "assets", "icon.png");
30220
31409
  const icon = nativeImage.createFromPath(iconPath);
30221
31410
  if (!icon.isEmpty()) {
30222
31411
  mainWindow.setIcon(icon);
@@ -30231,7 +31420,9 @@ async function createWindow() {
30231
31420
  if (quitting) return;
30232
31421
  event.preventDefault();
30233
31422
  quitting = true;
30234
- void controller.dispose().finally(() => {
31423
+ workbench.cleanShutdown = true;
31424
+ const controllers = [detachedController, ...[...managedProjects.values()].flatMap((project) => [project.controller, ...[...project.tasks].map((task) => task.controller)])];
31425
+ void savePersistedProjects().catch(() => void 0).then(() => Promise.all(controllers.map((item) => item.dispose()))).finally(() => {
30235
31426
  mainWindow?.destroy();
30236
31427
  app.quit();
30237
31428
  });
@@ -30264,8 +31455,16 @@ app.whenReady().then(async () => {
30264
31455
  dialog.showErrorBox("Flavor Code \u65E0\u6CD5\u542F\u52A8", error instanceof Error ? error.message : String(error));
30265
31456
  app.quit();
30266
31457
  });
30267
- app.on("before-quit", () => {
31458
+ app.on("before-quit", (event) => {
31459
+ if (quitting) return;
31460
+ event.preventDefault();
30268
31461
  quitting = true;
31462
+ workbench.cleanShutdown = true;
31463
+ const controllers = [detachedController, ...[...managedProjects.values()].flatMap((project) => [project.controller, ...[...project.tasks].map((task) => task.controller)])];
31464
+ void savePersistedProjects().catch(() => void 0).then(() => Promise.all(controllers.map((item) => item.dispose()))).finally(() => {
31465
+ mainWindow?.destroy();
31466
+ app.quit();
31467
+ });
30269
31468
  });
30270
31469
  app.on("window-all-closed", () => {
30271
31470
  if (process.platform !== "darwin") app.quit();