flavor-code 1.3.0 → 1.3.3

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
  }
@@ -10212,7 +10294,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10212
10294
 
10213
10295
  // src/init/project.ts
10214
10296
  import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
10215
- import { copyFileSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
10297
+ import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
10216
10298
  import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute7, join as join12, relative as relative8, sep as sep8 } from "path";
10217
10299
  import { fileURLToPath } from "url";
10218
10300
  var GENERATED_START = "<!-- flavor-code:start -->";
@@ -10665,22 +10747,6 @@ async function createExampleFlavorConfig(cwd) {
10665
10747
  };
10666
10748
  await writeFile8(configPath, JSON.stringify(example, null, 2) + "\n");
10667
10749
  }
10668
- var CODEISLAND_SRC_DIR = join12(dirname9(fileURLToPath(import.meta.url)), "codeisland");
10669
- var CODEISLAND_FILES = ["flavor-plugin.json", "activate.mjs", "bridge.mjs"];
10670
- async function copyCodeIslandPlugin(cwd) {
10671
- const pluginDir = join12(cwd, ".flavor", "plugins", "codeisland");
10672
- try {
10673
- await lstat4(pluginDir);
10674
- return;
10675
- } catch (error) {
10676
- if (!isMissingPathError(error)) throw error;
10677
- }
10678
- await mkdir9(pluginDir, { recursive: true, mode: 448 });
10679
- for (const name of CODEISLAND_FILES) {
10680
- const content = readFileSync2(join12(CODEISLAND_SRC_DIR, name), "utf8");
10681
- await writeFile8(join12(pluginDir, name), content);
10682
- }
10683
- }
10684
10750
  var ASTGRAPH_SRC_DIR = join12(dirname9(fileURLToPath(import.meta.url)), "astgraph");
10685
10751
  async function copyAstgraphPlugin(cwd) {
10686
10752
  const pluginDir = join12(cwd, ".flavor", "plugins", "astgraph");
@@ -10703,7 +10769,6 @@ function copyDirectory(from, to) {
10703
10769
  }
10704
10770
  async function initializeFlavor(cwd) {
10705
10771
  await ensureFlavorDirectories(cwd);
10706
- await copyCodeIslandPlugin(cwd);
10707
10772
  await copyAstgraphPlugin(cwd);
10708
10773
  await createExampleFlavorConfig(cwd);
10709
10774
  const facts = await inspectProject(cwd);
@@ -13604,7 +13669,7 @@ import {
13604
13669
  } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
13605
13670
 
13606
13671
  // src/utils/version.ts
13607
- import { readFileSync as readFileSync3 } from "fs";
13672
+ import { readFileSync as readFileSync2 } from "fs";
13608
13673
  import { dirname as dirname13, join as join20 } from "path";
13609
13674
  import { fileURLToPath as fileURLToPath2 } from "url";
13610
13675
  var cached;
@@ -13613,7 +13678,7 @@ function packageVersion() {
13613
13678
  let directory = dirname13(fileURLToPath2(import.meta.url));
13614
13679
  for (; ; ) {
13615
13680
  try {
13616
- const manifest = JSON.parse(readFileSync3(join20(directory, "package.json"), "utf8"));
13681
+ const manifest = JSON.parse(readFileSync2(join20(directory, "package.json"), "utf8"));
13617
13682
  if (typeof manifest.version === "string" && manifest.version.length > 0) {
13618
13683
  cached = manifest.version;
13619
13684
  return cached;
@@ -13846,8 +13911,8 @@ var OAuthCallbackAuthProvider = class {
13846
13911
  async #startCallbackServer(expectedState, signal) {
13847
13912
  let serverResolve;
13848
13913
  let serverReject;
13849
- const codePromise = new Promise((resolve36, reject) => {
13850
- serverResolve = resolve36;
13914
+ const codePromise = new Promise((resolve41, reject) => {
13915
+ serverResolve = resolve41;
13851
13916
  serverReject = reject;
13852
13917
  });
13853
13918
  const server = createServer2((req, res) => {
@@ -13881,7 +13946,7 @@ var OAuthCallbackAuthProvider = class {
13881
13946
  );
13882
13947
  serverResolve(code);
13883
13948
  });
13884
- return new Promise((resolve36, reject) => {
13949
+ return new Promise((resolve41, reject) => {
13885
13950
  server.once("error", (error) => {
13886
13951
  reject(new Error(`OAuth callback server failed: ${error.message}`));
13887
13952
  });
@@ -13898,7 +13963,7 @@ var OAuthCallbackAuthProvider = class {
13898
13963
  signal.addEventListener("abort", () => {
13899
13964
  serverReject(new Error("OAuth flow aborted"));
13900
13965
  }, { once: true });
13901
- resolve36({ redirectUri, promise: codePromise });
13966
+ resolve41({ redirectUri, promise: codePromise });
13902
13967
  });
13903
13968
  });
13904
13969
  }
@@ -18527,8 +18592,8 @@ var LspConnection = class {
18527
18592
  const id = this.#nextId++;
18528
18593
  const request = { jsonrpc: "2.0", id, method, params };
18529
18594
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
18530
- const promise = new Promise((resolve36, reject) => {
18531
- this.#pending.set(id, { resolve: resolve36, reject });
18595
+ const promise = new Promise((resolve41, reject) => {
18596
+ this.#pending.set(id, { resolve: resolve41, reject });
18532
18597
  timeout.addEventListener("abort", () => {
18533
18598
  if (this.#pending.has(id)) {
18534
18599
  this.#pending.delete(id);
@@ -18932,19 +18997,19 @@ async function changeSummary(workspace) {
18932
18997
  };
18933
18998
  }
18934
18999
  async function stagedDiff(workspace, maxChars = 96e3) {
18935
- const stat16 = await git2(workspace, ["diff", "--staged", "--stat"]);
19000
+ const stat19 = await git2(workspace, ["diff", "--staged", "--stat"]);
18936
19001
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
18937
19002
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
18938
19003
  const text = diff.stdout;
18939
19004
  return {
18940
19005
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18941
19006
  \u2026[diff truncated]` : text,
18942
- stat: stat16.stdout.trim(),
19007
+ stat: stat19.stdout.trim(),
18943
19008
  truncated: text.length > maxChars
18944
19009
  };
18945
19010
  }
18946
19011
  async function uncommittedDiff(workspace, maxChars = 96e3) {
18947
- const stat16 = await git2(workspace, ["diff", "HEAD", "--stat"]);
19012
+ const stat19 = await git2(workspace, ["diff", "HEAD", "--stat"]);
18948
19013
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
18949
19014
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
18950
19015
  const summary2 = await changeSummary(workspace);
@@ -18952,7 +19017,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
18952
19017
  return {
18953
19018
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
18954
19019
  \u2026[diff truncated]` : text,
18955
- stat: stat16.stdout.trim(),
19020
+ stat: stat19.stdout.trim(),
18956
19021
  untracked: summary2.untrackedFiles
18957
19022
  };
18958
19023
  }
@@ -19166,7 +19231,7 @@ var QuestionBridge = class {
19166
19231
  ask(questions, signal) {
19167
19232
  if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
19168
19233
  if (signal.aborted) return Promise.reject(signal.reason);
19169
- return new Promise((resolve36, reject) => {
19234
+ return new Promise((resolve41, reject) => {
19170
19235
  const onAbort = () => {
19171
19236
  this.#pending = void 0;
19172
19237
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
@@ -19178,7 +19243,7 @@ var QuestionBridge = class {
19178
19243
  signal.removeEventListener("abort", onAbort);
19179
19244
  this.#pending = void 0;
19180
19245
  this.#onChange?.();
19181
- resolve36(answers);
19246
+ resolve41(answers);
19182
19247
  },
19183
19248
  reject: (error) => {
19184
19249
  signal.removeEventListener("abort", onAbort);
@@ -20795,9 +20860,9 @@ var FlavorSession = class {
20795
20860
  }
20796
20861
  #waitForPlanningIdle(key) {
20797
20862
  if (!this.#activePlanning.has(key)) return Promise.resolve();
20798
- return new Promise((resolve36) => {
20863
+ return new Promise((resolve41) => {
20799
20864
  const check2 = () => {
20800
- if (!this.#activePlanning.has(key)) resolve36();
20865
+ if (!this.#activePlanning.has(key)) resolve41();
20801
20866
  else this.#planningIdleWaiters.add(check2);
20802
20867
  };
20803
20868
  this.#planningIdleWaiters.add(check2);
@@ -22800,8 +22865,8 @@ var JobRegistry = class {
22800
22865
  const id = `job-${randomUUID17().slice(0, 12)}`;
22801
22866
  const now = (/* @__PURE__ */ new Date()).toISOString();
22802
22867
  let settle;
22803
- const done = new Promise((resolve36) => {
22804
- settle = resolve36;
22868
+ const done = new Promise((resolve41) => {
22869
+ settle = resolve41;
22805
22870
  });
22806
22871
  const record3 = {
22807
22872
  id,
@@ -23305,15 +23370,15 @@ async function acquirePalFileLock(options) {
23305
23370
  // src/pals/broker-cli.ts
23306
23371
  var startupAttempts = /* @__PURE__ */ new Map();
23307
23372
  function delay3(milliseconds) {
23308
- return new Promise((resolve36) => setTimeout(resolve36, milliseconds));
23373
+ return new Promise((resolve41) => setTimeout(resolve41, milliseconds));
23309
23374
  }
23310
23375
  function canConnect(address) {
23311
- return new Promise((resolve36) => {
23376
+ return new Promise((resolve41) => {
23312
23377
  const socket = new Socket2();
23313
23378
  const finish = (connected) => {
23314
23379
  socket.removeAllListeners();
23315
23380
  socket.destroy();
23316
- resolve36(connected);
23381
+ resolve41(connected);
23317
23382
  };
23318
23383
  socket.once("connect", () => finish(true));
23319
23384
  socket.once("error", () => finish(false));
@@ -23327,18 +23392,25 @@ function startupLockPath(address) {
23327
23392
  async function spawnProductionBroker(address) {
23328
23393
  const entry2 = process.argv[1];
23329
23394
  if (entry2 === void 0) throw new Error("Cannot auto-start pals broker without a CLI entry point");
23395
+ return spawnPalBrokerProcess({ address, executable: process.execPath, args: [entry2, "--pals-broker", address] });
23396
+ }
23397
+ async function spawnPalBrokerProcess(options) {
23398
+ if (options.executable.trim() === "" || options.args.length === 0) throw new Error("Pals broker process requires an executable and entry arguments");
23399
+ const address = options.address;
23330
23400
  const lockPath = startupLockPath(address);
23331
23401
  const lock = await acquirePalFileLock({ path: lockPath, endpointLive: () => canConnect(address) });
23332
23402
  if (lock === void 0) return;
23333
23403
  try {
23334
- const child = spawn7(process.execPath, [entry2, "--pals-broker", address], {
23404
+ const child = spawn7(options.executable, [...options.args], {
23335
23405
  detached: true,
23336
23406
  stdio: "ignore",
23337
- windowsHide: true
23407
+ windowsHide: true,
23408
+ ...options.environment === void 0 ? {} : { env: options.environment }
23338
23409
  });
23339
23410
  child.unref();
23340
23411
  const deadline = Date.now() + 5e3;
23341
23412
  while (Date.now() < deadline && !await canConnect(address)) await delay3(25);
23413
+ if (!await canConnect(address)) throw new Error(`Pals broker process did not become ready at '${address}'`);
23342
23414
  } finally {
23343
23415
  await lock.release();
23344
23416
  }
@@ -23497,8 +23569,8 @@ var PalClient = class {
23497
23569
  let disconnectTimer;
23498
23570
  await Promise.race([
23499
23571
  this.request({ version: 1, type: "disconnect", requestId: randomUUID22() }).catch(() => void 0),
23500
- new Promise((resolve36) => {
23501
- disconnectTimer = setTimeout(resolve36, Math.min(this.requestTimeoutMs, 250));
23572
+ new Promise((resolve41) => {
23573
+ disconnectTimer = setTimeout(resolve41, Math.min(this.requestTimeoutMs, 250));
23502
23574
  })
23503
23575
  ]);
23504
23576
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -23544,7 +23616,7 @@ var PalClient = class {
23544
23616
  return presence;
23545
23617
  }
23546
23618
  openSocket() {
23547
- return new Promise((resolve36, reject) => {
23619
+ return new Promise((resolve41, reject) => {
23548
23620
  let settled = false;
23549
23621
  const rejectConnection = (error) => {
23550
23622
  if (settled) return;
@@ -23567,7 +23639,7 @@ var PalClient = class {
23567
23639
  settled = true;
23568
23640
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
23569
23641
  socket.off("error", onInitialError);
23570
- resolve36();
23642
+ resolve41();
23571
23643
  };
23572
23644
  const onInitialError = (error) => {
23573
23645
  socket.off("connect", onConnect);
@@ -23598,12 +23670,12 @@ var PalClient = class {
23598
23670
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
23599
23671
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
23600
23672
  }
23601
- return new Promise((resolve36, reject) => {
23673
+ return new Promise((resolve41, reject) => {
23602
23674
  const timer = setTimeout(() => {
23603
23675
  this.pending.delete(request.requestId);
23604
23676
  reject(new Error(`Pal request '${request.type}' timed out`));
23605
23677
  }, this.requestTimeoutMs);
23606
- this.pending.set(request.requestId, { resolve: resolve36, reject, timer });
23678
+ this.pending.set(request.requestId, { resolve: resolve41, reject, timer });
23607
23679
  let frame;
23608
23680
  try {
23609
23681
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -23916,6 +23988,51 @@ var INTERRUPTED_TASK_PLAN_CONTEXT = [
23916
23988
  "Reassess the current query independently.",
23917
23989
  "If the current work needs a plan, create a fresh one with TaskPlan before using TaskUpdate."
23918
23990
  ].join(" ");
23991
+ function recoverablePalClient(client, onRecovered) {
23992
+ let presence;
23993
+ let starting;
23994
+ const ensure = () => {
23995
+ if (presence !== void 0) return Promise.resolve(presence);
23996
+ if (starting !== void 0) return starting;
23997
+ starting = client.start().then((value) => {
23998
+ presence = value;
23999
+ onRecovered();
24000
+ return value;
24001
+ }).finally(() => {
24002
+ starting = void 0;
24003
+ });
24004
+ return starting;
24005
+ };
24006
+ const invoke = async (run) => {
24007
+ await ensure();
24008
+ try {
24009
+ return await run();
24010
+ } catch (error) {
24011
+ const detail = message(error);
24012
+ if (!/not connected|connection closed|ECONNRESET|ECONNREFUSED|EPIPE/i.test(detail)) throw error;
24013
+ presence = void 0;
24014
+ await ensure();
24015
+ return run();
24016
+ }
24017
+ };
24018
+ return {
24019
+ start: ensure,
24020
+ list: () => invoke(() => client.list()),
24021
+ rename: (alias) => invoke(() => client.rename(alias)),
24022
+ sendTask: (target, value) => invoke(() => client.sendTask(target, value)),
24023
+ sendChat: (target, value) => invoke(() => client.sendChat(target, value)),
24024
+ startCoWork: (input) => invoke(() => client.startCoWork(input)),
24025
+ coWorkAction: (action) => invoke(() => client.coWorkAction(action)),
24026
+ coWorkStatus: (coWorkId) => invoke(() => client.coWorkStatus(coWorkId)),
24027
+ integrateCoWork: (input) => invoke(() => client.integrateCoWork(input)),
24028
+ cancelCoWork: (coWorkId, reason) => invoke(() => client.cancelCoWork(coWorkId, reason)),
24029
+ subscribe: (listener) => client.subscribe(listener),
24030
+ close: async () => {
24031
+ presence = void 0;
24032
+ await client.close();
24033
+ }
24034
+ };
24035
+ }
23919
24036
  var DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 3e3;
23920
24037
  function createPromptEnvironment(input = {}) {
23921
24038
  const now = input.now ?? /* @__PURE__ */ new Date();
@@ -24175,7 +24292,10 @@ async function createProductionRuntime(options) {
24175
24292
  globalPluginDirs: [join31(home, ".flavor-code", "plugins")],
24176
24293
  projectPluginDirs: [join31(workspace, ".flavor", "plugins")],
24177
24294
  config,
24178
- sandbox: options.pluginSandbox ?? true,
24295
+ // Existing project plugins (including astgraph and superharness) use Node.js
24296
+ // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
24297
+ // must stay opt-in until equivalent mediated capabilities are available.
24298
+ sandbox: options.pluginSandbox ?? false,
24179
24299
  registrations: {
24180
24300
  command(name, handler, description) {
24181
24301
  if (typeof handler !== "function") throw new Error(`Plugin command "${name}" must be a function.`);
@@ -24236,6 +24356,7 @@ async function createProductionRuntime(options) {
24236
24356
  tools.push(evolveService.toolDefinition());
24237
24357
  let sleepScheduler;
24238
24358
  let collaborationClient;
24359
+ let collaborationUnavailableDiagnostic;
24239
24360
  let unsubscribeCollaboration;
24240
24361
  let collaborationSession;
24241
24362
  let deliverCollaborationEvent;
@@ -24278,7 +24399,7 @@ async function createProductionRuntime(options) {
24278
24399
  userScope: home,
24279
24400
  runtimeDir: join31(tmpdir3(), `flavor-code-pals-${createHash20("sha256").update(home).digest("hex").slice(0, 16)}`)
24280
24401
  });
24281
- collaborationClient = collaboration.client ?? collaboration.createClient?.({
24402
+ const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
24282
24403
  instanceId: collaboration.instanceId,
24283
24404
  alias,
24284
24405
  projectPath: workspace
@@ -24286,8 +24407,13 @@ async function createProductionRuntime(options) {
24286
24407
  address,
24287
24408
  authHome: home,
24288
24409
  registration: { id: collaboration.instanceId, alias, projectPath: workspace },
24289
- startBroker: () => ensurePalBrokerRunning({ address })
24410
+ startBroker: () => ensurePalBrokerRunning({ address, ...collaboration.startBroker === void 0 ? {} : { startBroker: () => collaboration.startBroker(address) } })
24290
24411
  });
24412
+ collaborationClient = collaboration.optional ? recoverablePalClient(baseCollaborationClient, () => {
24413
+ if (collaborationUnavailableDiagnostic === void 0) return;
24414
+ remove(diagnostics, collaborationUnavailableDiagnostic);
24415
+ collaborationUnavailableDiagnostic = void 0;
24416
+ }) : baseCollaborationClient;
24291
24417
  deliverCollaborationEvent = async (event) => {
24292
24418
  const session2 = collaborationSession;
24293
24419
  const client = collaborationClient;
@@ -24342,11 +24468,19 @@ async function createProductionRuntime(options) {
24342
24468
  }
24343
24469
  };
24344
24470
  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();
24471
+ try {
24472
+ await collaborationClient.start();
24473
+ } catch (error) {
24474
+ if (!collaboration.optional) throw error;
24475
+ collaborationUnavailableDiagnostic = `Pals unavailable: ${message(error)}`;
24476
+ diagnostics.push(collaborationUnavailableDiagnostic);
24477
+ }
24478
+ if (collaborationClient !== void 0) {
24479
+ tools.push(...createPalsTools(collaborationClient, {
24480
+ selfId: collaboration.instanceId,
24481
+ shareGuard: new CollaborationShareGuard({ redact: (value) => redactSecrets(value, secrets) })
24482
+ }));
24483
+ }
24350
24484
  }
24351
24485
  const syncMcpTools = () => {
24352
24486
  for (const tool of mcpTools) remove(tools, tool);
@@ -25103,6 +25237,7 @@ async function createProductionRuntime(options) {
25103
25237
  }),
25104
25238
  rename: (alias) => collaborationClient.rename(alias),
25105
25239
  info: (target) => resolveCollaborationTarget(target),
25240
+ sendChat: (target, message2) => collaborationClient.sendChat(target, message2),
25106
25241
  sendTask: (target, goal) => collaborationClient.sendTask(target, goal),
25107
25242
  startCoWork: async (target, goal) => {
25108
25243
  const recipient = await resolveCollaborationTarget(target);
@@ -25363,6 +25498,7 @@ ${summary2}`;
25363
25498
  return sessionHistory.checkpoint(tagged, harness.main.context.snapshot());
25364
25499
  },
25365
25500
  tree: () => sessionHistory.tree(),
25501
+ historyLeaf: () => sessionHistory.leafId,
25366
25502
  rewind: async (nodeId) => {
25367
25503
  await sessionHistory.rewind(nodeId, harness.main.context.snapshot());
25368
25504
  await persist();
@@ -25551,7 +25687,7 @@ ${renderMemoryDocument(entries)}`;
25551
25687
  approvals,
25552
25688
  memoryReviews,
25553
25689
  restoredTranscript,
25554
- jobs: { list: () => jobs.list(), subscribe: (listener) => jobs.subscribe(listener) },
25690
+ jobs: { list: () => jobs.list(), read: (id, owner2, cursor) => jobs.read(id, owner2, cursor), subscribe: (listener) => jobs.subscribe(listener) },
25555
25691
  get sessionId() {
25556
25692
  return sessionId;
25557
25693
  },
@@ -25832,23 +25968,23 @@ async function* persistEach(source, persist) {
25832
25968
  }
25833
25969
  }
25834
25970
  async function* durableTurn(source, journal, turnId, config) {
25835
- let failure;
25971
+ let failure2;
25836
25972
  let completed = false;
25837
25973
  try {
25838
25974
  for await (const event of source) {
25839
- if (event.type === "error") failure = event.error.message;
25975
+ if (event.type === "error") failure2 = event.error.message;
25840
25976
  yield event;
25841
25977
  }
25842
25978
  completed = true;
25843
25979
  } catch (error) {
25844
- failure = message(error);
25980
+ failure2 = message(error);
25845
25981
  throw error;
25846
25982
  } finally {
25847
- if (completed && failure === void 0) {
25983
+ if (completed && failure2 === void 0) {
25848
25984
  journal.completeTurn(turnId);
25849
25985
  journal.savepoint("turn-complete", config);
25850
25986
  } else {
25851
- journal.interruptTurn(turnId, failure ?? "turn consumer interrupted before completion");
25987
+ journal.interruptTurn(turnId, failure2 ?? "turn consumer interrupted before completion");
25852
25988
  }
25853
25989
  }
25854
25990
  }
@@ -28232,11 +28368,22 @@ var ProjectMemoryManager = class {
28232
28368
  this.#store = store;
28233
28369
  this.#enabled = enabled;
28234
28370
  }
28235
- async snapshot() {
28371
+ async snapshot(now = /* @__PURE__ */ new Date()) {
28372
+ const references = this.#enabled ? await this.#store.references() : [];
28236
28373
  return {
28237
28374
  enabled: this.#enabled,
28238
28375
  path: this.#store.path,
28239
- entries: this.#enabled ? await this.#store.list() : []
28376
+ entries: references.map((reference) => {
28377
+ const lastRecalledAt = Object.values(reference.recalls).sort().at(-1);
28378
+ return {
28379
+ id: reference.id,
28380
+ type: reference.type,
28381
+ content: reference.summary,
28382
+ recallTotal: reference.recallTotal,
28383
+ heat: classifyMemoryHeat(reference, now),
28384
+ ...lastRecalledAt === void 0 ? {} : { lastRecalledAt }
28385
+ };
28386
+ })
28240
28387
  };
28241
28388
  }
28242
28389
  async remember(candidate) {
@@ -28253,6 +28400,10 @@ var ProjectMemoryManager = class {
28253
28400
  this.#assertEnabled();
28254
28401
  return this.#store.delete(id);
28255
28402
  }
28403
+ async deleteCold(now = /* @__PURE__ */ new Date()) {
28404
+ this.#assertEnabled();
28405
+ return this.#store.forgetCold(now);
28406
+ }
28256
28407
  #assertEnabled() {
28257
28408
  if (!this.#enabled) throw new Error("Long-term memory is disabled for this project");
28258
28409
  }
@@ -28577,6 +28728,7 @@ async function d2cBackendSourceFingerprint(project) {
28577
28728
  var DesktopRuntimeController = class {
28578
28729
  #home;
28579
28730
  #createRuntime;
28731
+ #createTerminalService;
28580
28732
  #listSessions;
28581
28733
  #deleteStoredSession;
28582
28734
  #loadModels;
@@ -28599,8 +28751,10 @@ var DesktopRuntimeController = class {
28599
28751
  #skillManager;
28600
28752
  #memoryManager;
28601
28753
  #mcpManager;
28754
+ #terminal;
28602
28755
  #models = DEFAULT_DESKTOP_MODELS;
28603
28756
  #busy = false;
28757
+ #tasks;
28604
28758
  #disposeJobSubscription;
28605
28759
  #d2cMocks = /* @__PURE__ */ new Map();
28606
28760
  #d2cPreviews = /* @__PURE__ */ new Map();
@@ -28612,6 +28766,7 @@ var DesktopRuntimeController = class {
28612
28766
  constructor(options) {
28613
28767
  this.#home = resolve35(options.home ?? homedir4());
28614
28768
  this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
28769
+ this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
28615
28770
  this.#listSessions = options.listSessions ?? (async (workspace) => {
28616
28771
  const store = new SessionStore({ workspace });
28617
28772
  const entries = await store.list();
@@ -28678,7 +28833,8 @@ var DesktopRuntimeController = class {
28678
28833
  },
28679
28834
  diagnostics: runtime?.diagnostics ?? [],
28680
28835
  models: this.#models,
28681
- jobs: runtime?.jobs?.list() ?? []
28836
+ jobs: runtime?.jobs?.list() ?? [],
28837
+ ...this.#tasks === void 0 ? {} : { tasks: this.#tasks }
28682
28838
  };
28683
28839
  }
28684
28840
  async openWorkspace(path) {
@@ -28690,6 +28846,8 @@ var DesktopRuntimeController = class {
28690
28846
  await this.#stopAllD2cMocks();
28691
28847
  }
28692
28848
  this.#workspace = workspace;
28849
+ this.#terminal?.dispose();
28850
+ this.#terminal = this.#createTerminalService(workspace);
28693
28851
  this.#skillManager = new SkillManager({ workspace, home: this.#home });
28694
28852
  this.#memoryManager = await this.#loadMemoryManager(workspace, this.#home);
28695
28853
  this.#mcpManager = this.#loadMcpManager(workspace);
@@ -28720,6 +28878,7 @@ var DesktopRuntimeController = class {
28720
28878
  bufferedOutput.push(event);
28721
28879
  return;
28722
28880
  }
28881
+ this.#captureSessionOutput(event);
28723
28882
  this.#emit({ type: "session-output", sessionId: outputSessionId, event });
28724
28883
  },
28725
28884
  onApprovalChange: () => {
@@ -28743,6 +28902,7 @@ var DesktopRuntimeController = class {
28743
28902
  this.#emit({ type: "session-started", payload });
28744
28903
  outputReady = true;
28745
28904
  for (const event of bufferedOutput) {
28905
+ this.#captureSessionOutput(event);
28746
28906
  this.#emit({ type: "session-output", sessionId: runtime.sessionId, event });
28747
28907
  }
28748
28908
  this.#publishSnapshot();
@@ -28913,6 +29073,83 @@ var DesktopRuntimeController = class {
28913
29073
  if (deleted) await this.#runtime?.services.refreshMemory?.();
28914
29074
  return deleted;
28915
29075
  }
29076
+ async deleteColdMemory() {
29077
+ const manager = this.#requireMemoryManager();
29078
+ if (manager.deleteCold === void 0) throw new Error("Cold-memory cleanup is unavailable");
29079
+ const result = await manager.deleteCold();
29080
+ if (result.removed > 0) await this.#runtime?.services.refreshMemory?.();
29081
+ return result.removed;
29082
+ }
29083
+ async historySnapshot() {
29084
+ const services = this.#requireRuntime().services;
29085
+ if (services.tree === void 0) throw new Error("Session history is unavailable");
29086
+ return { leafId: services.historyLeaf?.() ?? null, nodes: services.tree() };
29087
+ }
29088
+ async createCheckpoint(label) {
29089
+ const checkpoint = this.#requireRuntime().services.checkpoint;
29090
+ if (checkpoint === void 0) throw new Error("Session checkpoints are unavailable");
29091
+ return checkpoint(label?.trim() || void 0);
29092
+ }
29093
+ async rewindHistory(nodeId) {
29094
+ const rewind = this.#requireRuntime().services.rewind;
29095
+ if (rewind === void 0) throw new Error("Session rewind is unavailable");
29096
+ await rewind(nodeId);
29097
+ }
29098
+ async unrevertHistory() {
29099
+ const unrevert = this.#requireRuntime().services.unrevert;
29100
+ if (unrevert === void 0) throw new Error("Session unrevert is unavailable");
29101
+ await unrevert();
29102
+ }
29103
+ async forkHistory(nodeId) {
29104
+ const fork = this.#requireRuntime().services.fork;
29105
+ if (fork === void 0) throw new Error("Session fork is unavailable");
29106
+ await fork(nodeId);
29107
+ }
29108
+ openTerminal(input = {}) {
29109
+ return this.#requireTerminal().open({ ...input, owner: this.#requireRuntime().sessionId });
29110
+ }
29111
+ listTerminals() {
29112
+ return this.#requireTerminal().list(this.#requireRuntime().sessionId).filter((terminal) => terminal.state !== "closed");
29113
+ }
29114
+ readTerminal(id, cursor = 0) {
29115
+ return this.#requireTerminal().read(id, this.#requireRuntime().sessionId, cursor);
29116
+ }
29117
+ writeTerminal(id, data2) {
29118
+ this.#requireTerminal().write(id, this.#requireRuntime().sessionId, data2);
29119
+ }
29120
+ resizeTerminal(id, columns, rows) {
29121
+ this.#requireTerminal().resize(id, this.#requireRuntime().sessionId, columns, rows);
29122
+ }
29123
+ closeTerminal(id) {
29124
+ this.#requireTerminal().close(id, this.#requireRuntime().sessionId);
29125
+ }
29126
+ readJob(id, cursor = 0) {
29127
+ const runtime = this.#requireRuntime();
29128
+ if (runtime.jobs === void 0) throw new Error("Background job output is unavailable");
29129
+ const job = runtime.jobs.list().find((item) => item.id === id);
29130
+ if (job === void 0) throw new Error(`Unknown background job: ${id}`);
29131
+ return runtime.jobs.read(id, job.owner, cursor);
29132
+ }
29133
+ async listPals(verbose = true) {
29134
+ return this.#requirePals().list(verbose);
29135
+ }
29136
+ async sendPalMessage(target, message2, kind) {
29137
+ const pals = this.#requirePals();
29138
+ if (kind === "chat") {
29139
+ if (pals.sendChat === void 0) throw new Error("Pal chat is unavailable");
29140
+ return pals.sendChat(target, message2);
29141
+ }
29142
+ return pals.sendTask(target, message2);
29143
+ }
29144
+ async startCoWork(target, goal) {
29145
+ return this.#requirePals().startCoWork(target, goal);
29146
+ }
29147
+ async coWorkStatus(coWorkId) {
29148
+ return this.#requirePals().coWorkStatus(coWorkId);
29149
+ }
29150
+ async cancelCoWork(coWorkId, reason) {
29151
+ return this.#requirePals().cancelCoWork(coWorkId, reason);
29152
+ }
28916
29153
  async switchModel(modelId) {
28917
29154
  if (this.#busy) throw new Error("Stop the active task before switching models");
28918
29155
  if (!this.#models.some((model) => model.id === modelId)) {
@@ -29675,6 +29912,8 @@ VITE_API_BASE_URL=${running.url}
29675
29912
  await this.#disposeRuntime();
29676
29913
  await this.#stopAllD2cPreviews();
29677
29914
  await this.#stopAllD2cMocks();
29915
+ this.#terminal?.dispose();
29916
+ this.#terminal = void 0;
29678
29917
  }
29679
29918
  #publishSnapshot() {
29680
29919
  const snapshot4 = this.snapshot();
@@ -29695,6 +29934,7 @@ VITE_API_BASE_URL=${running.url}
29695
29934
  this.#disposeJobSubscription = void 0;
29696
29935
  this.#runtime = void 0;
29697
29936
  this.#busy = false;
29937
+ this.#tasks = void 0;
29698
29938
  if (runtime === void 0) return;
29699
29939
  await runtime.session.close();
29700
29940
  await runtime.dispose();
@@ -29732,6 +29972,23 @@ VITE_API_BASE_URL=${running.url}
29732
29972
  if (this.#mcpManager === void 0) throw new Error("Open a project before managing MCP services");
29733
29973
  return this.#mcpManager;
29734
29974
  }
29975
+ #captureSessionOutput(event) {
29976
+ if (event.type === "tasks") this.#tasks = event.snapshot;
29977
+ else if (event.type === "tasks-cleared") this.#tasks = void 0;
29978
+ }
29979
+ #requireRuntime() {
29980
+ if (this.#runtime === void 0) throw new Error("Start a session before using this feature");
29981
+ return this.#runtime;
29982
+ }
29983
+ #requireTerminal() {
29984
+ if (this.#terminal === void 0) throw new Error("Open a project before using the terminal");
29985
+ return this.#terminal;
29986
+ }
29987
+ #requirePals() {
29988
+ const pals = this.#requireRuntime().services.pals;
29989
+ if (pals === void 0) throw new Error("Pals are unavailable for this session");
29990
+ return pals;
29991
+ }
29735
29992
  };
29736
29993
 
29737
29994
  // src/desktop/security.ts
@@ -29748,6 +30005,16 @@ function normalizePersistedWorkspace(value) {
29748
30005
  const workspace = value.workspace;
29749
30006
  return typeof workspace === "string" && workspace.trim().length > 0 && workspace.length <= 32768 ? workspace : void 0;
29750
30007
  }
30008
+ function normalizePersistedDesktopProjects(value) {
30009
+ const workspace = normalizePersistedWorkspace(value);
30010
+ if (typeof value !== "object" || value === null) {
30011
+ return { ...workspace === void 0 ? {} : { workspace }, projects: workspace === void 0 ? [] : [workspace] };
30012
+ }
30013
+ const rawProjects = value.projects;
30014
+ const projects = Array.isArray(rawProjects) ? rawProjects.filter((item) => typeof item === "string" && item.trim().length > 0 && item.length <= 32768) : [];
30015
+ const unique = [.../* @__PURE__ */ new Set([...workspace === void 0 ? [] : [workspace], ...projects])].slice(0, 50);
30016
+ return { ...workspace === void 0 ? {} : { workspace }, projects: unique };
30017
+ }
29751
30018
  function isTrustedNavigation(target, current, trustedRenderer) {
29752
30019
  return target === trustedRenderer || current === trustedRenderer && target === current;
29753
30020
  }
@@ -29831,6 +30098,472 @@ ${reason.stack ?? ""}`;
29831
30098
  process.on("uncaughtException", (error) => crash("uncaughtException", error));
29832
30099
  }
29833
30100
 
30101
+ // src/desktop/git-manager.ts
30102
+ import { readFile as readFile36, rm as rm16 } from "fs/promises";
30103
+ import { isAbsolute as isAbsolute18, relative as relative26, resolve as resolve36 } from "path";
30104
+ function failure(action, stderr, stdout) {
30105
+ return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
30106
+ }
30107
+ async function desktopGitStatus(workspace) {
30108
+ if (!await isGitRepository(workspace)) return { repository: false, branch: "", head: "", files: [] };
30109
+ const summary2 = await changeSummary(workspace);
30110
+ return {
30111
+ repository: true,
30112
+ branch: summary2.branch,
30113
+ head: summary2.head,
30114
+ files: summary2.statusLines.map((line) => {
30115
+ const index = line[0] ?? " ";
30116
+ const worktree = line[1] ?? " ";
30117
+ return {
30118
+ path: line.slice(3).replace(/^.* -> /, ""),
30119
+ index,
30120
+ worktree,
30121
+ staged: index !== " " && index !== "?",
30122
+ unstaged: worktree !== " " && worktree !== "?",
30123
+ untracked: index === "?" && worktree === "?"
30124
+ };
30125
+ })
30126
+ };
30127
+ }
30128
+ async function desktopGitDiff(workspace, path, staged = false) {
30129
+ const status = await desktopGitStatus(workspace);
30130
+ const file = status.files.find((item) => item.path === path);
30131
+ if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
30132
+ if (file.untracked) {
30133
+ const target = safeWorkspacePath(workspace, path);
30134
+ const value = await readFile36(target, "utf8");
30135
+ return value.length > 12e4 ? `${value.slice(0, 12e4)}
30136
+ \u2026[\u5185\u5BB9\u5DF2\u622A\u65AD]` : value;
30137
+ }
30138
+ const result = await git2(workspace, ["diff", ...staged ? ["--staged"] : [], "--no-ext-diff", "--no-color", "--", path]);
30139
+ if (!result.ok) throw failure("\u8BFB\u53D6\u5DEE\u5F02", result.stderr, result.stdout);
30140
+ return result.stdout || "\uFF08\u6B64\u533A\u57DF\u6CA1\u6709\u5DEE\u5F02\uFF09";
30141
+ }
30142
+ async function desktopGitStage(workspace, path) {
30143
+ await assertListed(workspace, path);
30144
+ const result = await git2(workspace, ["add", "--", path]);
30145
+ if (!result.ok) throw failure("\u6682\u5B58", result.stderr, result.stdout);
30146
+ return desktopGitStatus(workspace);
30147
+ }
30148
+ async function desktopGitUnstage(workspace, path) {
30149
+ const file = await assertListed(workspace, path);
30150
+ if (!file.staged) return desktopGitStatus(workspace);
30151
+ const head = await git2(workspace, ["rev-parse", "--verify", "HEAD"]);
30152
+ const result = head.ok ? await git2(workspace, ["restore", "--staged", "--", path]) : await git2(workspace, ["rm", "--cached", "--", path]);
30153
+ if (!result.ok) throw failure("\u53D6\u6D88\u6682\u5B58", result.stderr, result.stdout);
30154
+ return desktopGitStatus(workspace);
30155
+ }
30156
+ async function desktopGitDiscard(workspace, path) {
30157
+ const file = await assertListed(workspace, path);
30158
+ if (file.untracked) await rm16(safeWorkspacePath(workspace, path), { recursive: false, force: false });
30159
+ else {
30160
+ const result = await git2(workspace, ["restore", "--worktree", "--", path]);
30161
+ if (!result.ok) throw failure("\u8FD8\u539F", result.stderr, result.stdout);
30162
+ }
30163
+ return desktopGitStatus(workspace);
30164
+ }
30165
+ async function desktopGitCommit(workspace, message2) {
30166
+ return { result: await commit(workspace, message2), status: await desktopGitStatus(workspace) };
30167
+ }
30168
+ async function desktopGitReviewDiff(workspace, input) {
30169
+ if (!await isGitRepository(workspace)) throw new Error("Review requires a Git repository");
30170
+ const paths = [...new Set(input.paths ?? [])].slice(0, 200);
30171
+ for (const path of paths) safeWorkspacePath(workspace, path);
30172
+ const suffix = paths.length === 0 ? [] : ["--", ...paths];
30173
+ let args;
30174
+ if (input.scope === "working") args = ["diff", "HEAD", "--no-ext-diff", "--no-color", ...suffix];
30175
+ else if (input.scope === "staged") args = ["diff", "--staged", "--no-ext-diff", "--no-color", ...suffix];
30176
+ else if (input.scope === "last-turn") args = ["diff", "HEAD", "--no-ext-diff", "--no-color", ...suffix];
30177
+ else {
30178
+ const target = safeGitTarget(input.target);
30179
+ args = input.scope === "commit" ? ["show", "--format=", "--no-ext-diff", "--no-color", target] : ["diff", `${target}...HEAD`, "--no-ext-diff", "--no-color"];
30180
+ }
30181
+ const result = await git2(workspace, args, 3e4);
30182
+ if (!result.ok) throw failure("\u8BFB\u53D6\u5BA1\u67E5\u5DEE\u5F02", result.stderr, result.stdout);
30183
+ return result.stdout.length > 24e4 ? `${result.stdout.slice(0, 24e4)}
30184
+ \u2026[diff truncated]` : result.stdout || "\uFF08\u6B64\u8303\u56F4\u6CA1\u6709\u5DEE\u5F02\uFF09";
30185
+ }
30186
+ function desktopGitHistory(workspace) {
30187
+ return fileHistory(workspace, void 0, 50);
30188
+ }
30189
+ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
30190
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(sessionId)) throw new Error("Invalid session id");
30191
+ const requested = new Set(paths.map((path) => {
30192
+ safeWorkspacePath(workspace, path);
30193
+ return path.replaceAll("\\", "/");
30194
+ }));
30195
+ try {
30196
+ const tree = JSON.parse(await readFile36(resolve36(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
30197
+ const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
30198
+ const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
30199
+ if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
30200
+ const load = async (id) => {
30201
+ if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
30202
+ return JSON.parse(await readFile36(resolve36(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
30203
+ };
30204
+ const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
30205
+ const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
30206
+ const newFiles = new Map(after.files.map((file) => [file.path, file.digest]));
30207
+ 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);
30208
+ const object = async (digest) => {
30209
+ if (digest === void 0) return "";
30210
+ if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
30211
+ const value = await readFile36(resolve36(workspace, ".flavor", "checkpoints", "objects", digest));
30212
+ return value.includes(0) ? "[binary content]" : value.toString("utf8");
30213
+ };
30214
+ const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
30215
+ const result = chunks.join("\n");
30216
+ return result.length > 24e4 ? `${result.slice(0, 24e4)}
30217
+ \u2026[diff truncated]` : result || "\uFF08\u4E0A\u4E00\u4E2A assistant turn \u6CA1\u6709\u6587\u4EF6\u53D8\u5316\uFF09";
30218
+ } catch (error) {
30219
+ if (error.code === "ENOENT") return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
30220
+ throw error;
30221
+ }
30222
+ }
30223
+ function wholeFileDiff(path, before, after) {
30224
+ const oldLines = before.replace(/\n$/, "").split("\n");
30225
+ const newLines = after.replace(/\n$/, "").split("\n");
30226
+ 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");
30227
+ }
30228
+ async function assertListed(workspace, path) {
30229
+ const file = (await desktopGitStatus(workspace)).files.find((item) => item.path === path);
30230
+ if (file === void 0) throw new Error("\u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5728\u53D8\u66F4\u5217\u8868\u4E2D");
30231
+ safeWorkspacePath(workspace, path);
30232
+ return file;
30233
+ }
30234
+ function safeWorkspacePath(workspace, path) {
30235
+ if (isAbsolute18(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
30236
+ const root = resolve36(workspace);
30237
+ const target = resolve36(root, path);
30238
+ const child = relative26(root, target);
30239
+ if (child === "" || child.startsWith("..") || isAbsolute18(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
30240
+ return target;
30241
+ }
30242
+ function safeGitTarget(value) {
30243
+ const target = value?.trim();
30244
+ 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");
30245
+ return target;
30246
+ }
30247
+
30248
+ // src/desktop/worktree-manager.ts
30249
+ import { randomUUID as randomUUID26 } from "crypto";
30250
+ import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat15, writeFile as writeFile18 } from "fs/promises";
30251
+ import { basename as basename10, isAbsolute as isAbsolute19, join as join43, relative as relative27, resolve as resolve37, sep as sep27 } from "path";
30252
+ var DesktopWorktreeManager = class {
30253
+ #repository;
30254
+ #storage;
30255
+ constructor(options) {
30256
+ this.#repository = resolve37(options.repository);
30257
+ this.#storage = resolve37(options.storage);
30258
+ }
30259
+ async create(label) {
30260
+ if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
30261
+ const safe = label.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36) || "task";
30262
+ const suffix = randomUUID26().slice(0, 8);
30263
+ const id = `worktree-${safe}-${suffix}`;
30264
+ const branch = `flavor/desktop-${safe}-${suffix}`;
30265
+ const path = this.#safeStoragePath(id);
30266
+ await mkdir28(this.#storage, { recursive: true, mode: 448 });
30267
+ const result = await git2(this.#repository, ["worktree", "add", "-b", branch, path, "HEAD"], 6e4);
30268
+ if (!result.ok) throw new Error(`git worktree add failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30269
+ const stored = {
30270
+ version: 1,
30271
+ id,
30272
+ mode: "worktree",
30273
+ repository: this.#repository,
30274
+ path,
30275
+ branch,
30276
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
30277
+ };
30278
+ try {
30279
+ await writeFile18(this.#metadataPath(id), `${JSON.stringify(stored)}
30280
+ `, { encoding: "utf8", mode: 384 });
30281
+ } catch (error) {
30282
+ await git2(this.#repository, ["worktree", "remove", "--force", path], 6e4).catch(() => void 0);
30283
+ throw error;
30284
+ }
30285
+ return { ...stored, dirty: false, merged: false };
30286
+ }
30287
+ async list() {
30288
+ let names;
30289
+ try {
30290
+ names = await readdir13(this.#storage);
30291
+ } catch (error) {
30292
+ if (error.code === "ENOENT") return [];
30293
+ throw error;
30294
+ }
30295
+ const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
30296
+ try {
30297
+ const stored = validateStored(JSON.parse(await readFile37(join43(this.#storage, name), "utf8")), this.#repository, this.#storage);
30298
+ if ((await stat15(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
30299
+ const status = await git2(stored.path, ["status", "--porcelain"]);
30300
+ const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
30301
+ const { version: _version, ...item } = stored;
30302
+ return { ...item, dirty: status.ok ? status.stdout.trim().length > 0 : true, merged: merged.ok };
30303
+ } catch {
30304
+ return void 0;
30305
+ }
30306
+ }));
30307
+ return values.filter((item) => item !== void 0).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
30308
+ }
30309
+ async remove(id, force = false) {
30310
+ const item = await this.#load(id);
30311
+ const status = await git2(item.path, ["status", "--porcelain"]);
30312
+ const dirty = !status.ok || status.stdout.trim().length > 0;
30313
+ if (dirty && !force) throw new Error("Worktree is dirty; review or explicitly force removal");
30314
+ const result = await git2(this.#repository, ["worktree", "remove", ...force ? ["--force"] : [], item.path], 6e4);
30315
+ if (!result.ok) throw new Error(`git worktree remove failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30316
+ await rm17(this.#metadataPath(id), { force: true });
30317
+ }
30318
+ async merge(id) {
30319
+ const item = await this.#load(id);
30320
+ const [taskStatus, repositoryStatus] = await Promise.all([
30321
+ git2(item.path, ["status", "--porcelain"]),
30322
+ git2(this.#repository, ["status", "--porcelain"])
30323
+ ]);
30324
+ if (!taskStatus.ok || taskStatus.stdout.trim().length > 0) throw new Error("Commit or discard task worktree changes before handoff");
30325
+ if (!repositoryStatus.ok || repositoryStatus.stdout.trim().length > 0) throw new Error("The destination checkout must be clean before handoff");
30326
+ const result = await git2(this.#repository, ["merge", "--no-ff", "--no-edit", item.branch], 6e4);
30327
+ if (!result.ok) throw new Error(`Worktree handoff merge failed: ${(result.stderr || result.stdout).trim().split("\n")[0]}`);
30328
+ }
30329
+ async #load(id) {
30330
+ const path = this.#metadataPath(id);
30331
+ return validateStored(JSON.parse(await readFile37(path, "utf8")), this.#repository, this.#storage);
30332
+ }
30333
+ #metadataPath(id) {
30334
+ if (!/^worktree-[a-z0-9._-]{1,80}$/.test(id)) throw new Error("Invalid worktree id");
30335
+ return this.#safeStoragePath(`${id}.json`);
30336
+ }
30337
+ #safeStoragePath(name) {
30338
+ const target = resolve37(this.#storage, name);
30339
+ const delta = relative27(this.#storage, target);
30340
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta)) throw new Error("Worktree path escapes storage");
30341
+ return target;
30342
+ }
30343
+ };
30344
+ function validateStored(value, repository, storage) {
30345
+ if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
30346
+ const item = value;
30347
+ 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) {
30348
+ throw new Error("Invalid worktree metadata");
30349
+ }
30350
+ const path = resolve37(item.path);
30351
+ const delta = relative27(storage, path);
30352
+ if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta) || basename10(path) !== item.id) {
30353
+ throw new Error("Worktree metadata escapes storage");
30354
+ }
30355
+ return { version: 1, id: item.id, mode: "worktree", repository, path, branch: item.branch, createdAt: item.createdAt };
30356
+ }
30357
+
30358
+ // src/desktop/workbench-service.ts
30359
+ import { readFile as readFile38, readdir as readdir14, stat as stat16 } from "fs/promises";
30360
+ import { homedir as homedir5 } from "os";
30361
+ import { join as join44, resolve as resolve38 } from "path";
30362
+ var MAX_TEXT = 128e3;
30363
+ var MAX_ITEMS = 200;
30364
+ var DesktopWorkbenchService = class {
30365
+ #workspace;
30366
+ #home;
30367
+ constructor(workspace, home = homedir5()) {
30368
+ this.#workspace = resolve38(workspace);
30369
+ this.#home = resolve38(home);
30370
+ }
30371
+ async inspect(sessionId) {
30372
+ const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
30373
+ this.#goals(),
30374
+ this.#instructions(),
30375
+ this.#permissionFiles(),
30376
+ this.#audit(),
30377
+ this.#context(sessionId)
30378
+ ]);
30379
+ return { goals, instructions, permissionFiles, audit, ...context === void 0 ? {} : { context } };
30380
+ }
30381
+ static normalizePreviewUrl(input) {
30382
+ let url;
30383
+ try {
30384
+ url = new URL(input.trim());
30385
+ } catch {
30386
+ throw new Error("Preview URL must be a valid loopback HTTP(S) URL");
30387
+ }
30388
+ if (!["http:", "https:"].includes(url.protocol) || !["127.0.0.1", "localhost", "::1", "[::1]"].includes(url.hostname.toLowerCase()) || url.username !== "" || url.password !== "") {
30389
+ throw new Error("Preview URL must use a loopback HTTP(S) origin");
30390
+ }
30391
+ return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
30392
+ }
30393
+ async #goals() {
30394
+ const root = join44(this.#workspace, ".flavor", "goals");
30395
+ const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
30396
+ const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
30397
+ try {
30398
+ return GoalStateSchema.parse(JSON.parse(await boundedRead2(join44(root, name))));
30399
+ } catch {
30400
+ return void 0;
30401
+ }
30402
+ }));
30403
+ return values.filter((item) => item !== void 0).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
30404
+ }
30405
+ async #instructions() {
30406
+ const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
30407
+ const items = await Promise.all(names.map(async (name) => {
30408
+ const path = join44(this.#workspace, name);
30409
+ try {
30410
+ return { name, path, content: redactErrorText(await boundedRead2(path)) };
30411
+ } catch (error) {
30412
+ if (error.code === "ENOENT") return void 0;
30413
+ throw error;
30414
+ }
30415
+ }));
30416
+ return items.filter((item) => item !== void 0);
30417
+ }
30418
+ async #permissionFiles() {
30419
+ const candidates = [
30420
+ ...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve38(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
30421
+ { tier: "user", path: join44(this.#home, ".flavor-code", "permissions.json"), content: "" },
30422
+ { tier: "project", path: join44(this.#workspace, ".flavor", "permissions.json"), content: "" },
30423
+ { tier: "local", path: join44(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
30424
+ ];
30425
+ const values = await Promise.all(candidates.map(async (item) => {
30426
+ try {
30427
+ return { ...item, content: redactErrorText(await boundedRead2(item.path)) };
30428
+ } catch (error) {
30429
+ if (error.code === "ENOENT") return void 0;
30430
+ throw error;
30431
+ }
30432
+ }));
30433
+ return values.filter((item) => item !== void 0);
30434
+ }
30435
+ async #audit() {
30436
+ let raw;
30437
+ try {
30438
+ raw = await boundedRead2(join44(this.#workspace, ".flavor", "audit.jsonl"));
30439
+ } catch (error) {
30440
+ if (error.code === "ENOENT") return [];
30441
+ throw error;
30442
+ }
30443
+ return raw.split(/\r?\n/).filter(Boolean).slice(-MAX_ITEMS).flatMap((line) => {
30444
+ try {
30445
+ const value = JSON.parse(line);
30446
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? [redact2(value)] : [];
30447
+ } catch {
30448
+ return [];
30449
+ }
30450
+ }).reverse();
30451
+ }
30452
+ async #context(sessionId) {
30453
+ const usage = await readJsonLines2(join44(this.#workspace, ".flavor", "usage.jsonl"));
30454
+ if (sessionId === void 0) return { visibility: [], usage };
30455
+ try {
30456
+ const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
30457
+ return { ...document.conversation.epoch === void 0 ? {} : { epoch: redact2(document.conversation.epoch) }, visibility: (document.conversation.visibilityLog ?? []).slice(-MAX_ITEMS).map((item) => redact2(item)), usage };
30458
+ } catch {
30459
+ return { visibility: [], usage };
30460
+ }
30461
+ }
30462
+ };
30463
+ async function boundedRead2(path) {
30464
+ const info = await stat16(path);
30465
+ if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
30466
+ if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
30467
+ return readFile38(path, "utf8");
30468
+ }
30469
+ async function readJsonLines2(path) {
30470
+ try {
30471
+ return (await boundedRead2(path)).split(/\r?\n/).filter(Boolean).slice(-MAX_ITEMS).flatMap((line) => {
30472
+ try {
30473
+ const value = JSON.parse(line);
30474
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? [redact2(value)] : [];
30475
+ } catch {
30476
+ return [];
30477
+ }
30478
+ }).reverse();
30479
+ } catch (error) {
30480
+ if (error.code === "ENOENT") return [];
30481
+ throw error;
30482
+ }
30483
+ }
30484
+ function redact2(value, key = "") {
30485
+ if (/token|secret|password|authorization|api[-_]?key/i.test(key)) return "[REDACTED]";
30486
+ if (Array.isArray(value)) return value.slice(0, MAX_ITEMS).map((item) => redact2(item));
30487
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).slice(0, MAX_ITEMS).map(([name, item]) => [name, redact2(item, name)]));
30488
+ return typeof value === "string" && value.length > MAX_TEXT ? `${value.slice(0, MAX_TEXT)}\u2026` : value;
30489
+ }
30490
+
30491
+ // src/desktop/astgraph-service.ts
30492
+ import { stat as stat17 } from "fs/promises";
30493
+ import { createRequire as nodeCreateRequire2 } from "module";
30494
+ import { join as join45, resolve as resolve39 } from "path";
30495
+ var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
30496
+ var DesktopAstGraphService = class {
30497
+ #path;
30498
+ constructor(workspace) {
30499
+ this.#path = join45(resolve39(workspace), ".flavor", "astgraph", "index.db");
30500
+ }
30501
+ async status() {
30502
+ if ((await stat17(this.#path).catch(() => void 0))?.isFile() !== true) {
30503
+ return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
30504
+ }
30505
+ return this.#read((db) => {
30506
+ const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
30507
+ const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
30508
+ let indexedAt;
30509
+ try {
30510
+ const at = Number(JSON.parse(metadata?.value ?? "{}").at);
30511
+ if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
30512
+ } catch {
30513
+ }
30514
+ return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
30515
+ });
30516
+ }
30517
+ async search(query, limit = 40) {
30518
+ const value = query.trim().slice(0, 200);
30519
+ if (value.length === 0 || !(await this.status()).available) return [];
30520
+ 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));
30521
+ }
30522
+ async relations(id, hops = 2) {
30523
+ if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
30524
+ if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
30525
+ return this.#read((db) => {
30526
+ const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
30527
+ const linked = (direction) => {
30528
+ const source = direction === "callers" ? "e.source" : "e.target";
30529
+ const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
30530
+ 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);
30531
+ };
30532
+ const seen = /* @__PURE__ */ new Map();
30533
+ let frontier = [id];
30534
+ for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
30535
+ const next = [];
30536
+ for (const current of frontier) {
30537
+ 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);
30538
+ for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
30539
+ seen.set(row.other, hop);
30540
+ next.push(row.other);
30541
+ }
30542
+ }
30543
+ frontier = next;
30544
+ }
30545
+ const impact = [...seen].flatMap(([nodeId, hop]) => {
30546
+ const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
30547
+ return row === void 0 ? [] : [{ ...mapNode(row), hop }];
30548
+ });
30549
+ return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
30550
+ });
30551
+ }
30552
+ #read(run) {
30553
+ const sqlite = nodeCreateRequire2(import.meta.url)("node:sqlite");
30554
+ const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
30555
+ try {
30556
+ return run(db);
30557
+ } finally {
30558
+ db.close();
30559
+ }
30560
+ }
30561
+ };
30562
+ function mapNode(value) {
30563
+ const row = value;
30564
+ 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) } };
30565
+ }
30566
+
29834
30567
  // src/desktop/main.ts
29835
30568
  installCrashGuard();
29836
30569
  var moduleDirectory = dirname24(fileURLToPath4(import.meta.url));
@@ -29840,7 +30573,7 @@ var appMenu;
29840
30573
  var quitting = false;
29841
30574
  function logStartup(step, detail) {
29842
30575
  try {
29843
- const logPath = join43(dirname24(process.execPath), "flavor-code-startup.log");
30576
+ const logPath = join46(dirname24(process.execPath), "flavor-code-startup.log");
29844
30577
  const ts = (/* @__PURE__ */ new Date()).toISOString();
29845
30578
  appendFileSync(logPath, `[${ts}] ${step}${detail ? ` | ${detail}` : ""}
29846
30579
  `);
@@ -29865,7 +30598,7 @@ var d2cEmbedded = createEmbeddedD2cAutomation(() => {
29865
30598
  };
29866
30599
  });
29867
30600
  var d2cJudgeClient = createD2cJudgeClient();
29868
- var judgeStore = () => createD2cJudgeConfigStore(join43(app.getPath("userData"), "d2c-judge.json"));
30601
+ var judgeStore = () => createD2cJudgeConfigStore(join46(app.getPath("userData"), "d2c-judge.json"));
29869
30602
  function judgeImage(png) {
29870
30603
  const image = nativeImage.createFromBuffer(png);
29871
30604
  const size = image.getSize();
@@ -29874,70 +30607,342 @@ function judgeImage(png) {
29874
30607
  const ratio = 2048 / longest;
29875
30608
  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
30609
  }
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 });
30610
+ var workbench = {
30611
+ projectMeta: {},
30612
+ sessionMeta: {},
30613
+ activities: [],
30614
+ recoveryItems: [],
30615
+ runningSessions: [],
30616
+ taskCheckouts: [],
30617
+ cleanShutdown: true
30618
+ };
30619
+ var managedProjects = /* @__PURE__ */ new Map();
30620
+ var projectOrder = [];
30621
+ var activeWorkspace;
30622
+ function sessionMetaKey(workspace, sessionId) {
30623
+ return `${workspace}\0${sessionId}`;
30624
+ }
30625
+ function worktreeManager(workspace) {
30626
+ const key = createHash27("sha256").update(resolve40(workspace)).digest("hex").slice(0, 20);
30627
+ return new DesktopWorktreeManager({ repository: workspace, storage: join46(app.getPath("userData"), "task-worktrees", key) });
30628
+ }
30629
+ function activeWorkingDirectory() {
30630
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
30631
+ return managedProjects.get(activeWorkspace)?.selectedTask?.workingDirectory ?? activeWorkspace;
30632
+ }
30633
+ function projectSessions(workspace, project) {
30634
+ const merged = new Map(project.snapshot.sessions.map((session) => [session.sessionId, session]));
30635
+ const taskBySession = new Map([...project.tasks].flatMap((task) => task.sessionId === void 0 ? [] : [[task.sessionId, task]]));
30636
+ for (const task of project.tasks) for (const session of task.snapshot.sessions) {
30637
+ const previous = merged.get(session.sessionId);
30638
+ if (previous === void 0 || session.updatedAt >= previous.updatedAt) merged.set(session.sessionId, session);
30639
+ }
30640
+ return [...merged.values()].map((session) => {
30641
+ const meta = workbench.sessionMeta[sessionMetaKey(workspace, session.sessionId)] ?? {};
30642
+ const activity = workbench.activities.find((item) => item.workspace === workspace && item.sessionId === session.sessionId);
30643
+ const running = [...project.tasks].some((task2) => task2.sessionId === session.sessionId && task2.snapshot.activeSession?.busy);
30644
+ const task = taskBySession.get(session.sessionId);
30645
+ 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 } };
30646
+ }).sort((left, right) => Number(Boolean(right.pinned)) - Number(Boolean(left.pinned)) || right.updatedAt.localeCompare(left.updatedAt));
30647
+ }
30648
+ function projectSummaries() {
30649
+ return [...projectOrder].sort((left, right) => Number(Boolean(workbench.projectMeta[right]?.pinned)) - Number(Boolean(workbench.projectMeta[left]?.pinned))).flatMap((workspace) => {
30650
+ const project = managedProjects.get(workspace);
30651
+ if (project === void 0) return [];
30652
+ const snapshot4 = project.selectedTask?.snapshot ?? project.snapshot;
30653
+ const active = snapshot4.activeSession;
30654
+ const runningSessions = [...project.tasks].flatMap((task) => task.sessionId !== void 0 && task.snapshot.activeSession?.busy ? [task.sessionId] : []);
30655
+ return [{
30656
+ workspace,
30657
+ ...workbench.projectMeta[workspace],
30658
+ sessions: projectSessions(workspace, project),
30659
+ ...active === void 0 ? {} : { activeSession: { sessionId: active.sessionId, busy: active.busy } },
30660
+ running: runningSessions.length > 0 || snapshot4.jobs.some((job) => job.state === "running"),
30661
+ runningSessions,
30662
+ unreadCount: workbench.activities.filter((item) => item.workspace === workspace && item.unread).length
30663
+ }];
30664
+ });
30665
+ }
30666
+ function decorateSnapshot(snapshot4) {
30667
+ const workspace = snapshot4.workspace !== void 0 && managedProjects.has(snapshot4.workspace) ? snapshot4.workspace : activeWorkspace ?? snapshot4.workspace;
30668
+ const selected = workspace === void 0 ? void 0 : managedProjects.get(workspace)?.selectedTask;
30669
+ return {
30670
+ ...snapshot4,
30671
+ ...workspace === void 0 ? {} : { workspace },
30672
+ ...snapshot4.activeSession === void 0 || selected === void 0 ? {} : { activeSession: { ...snapshot4.activeSession, environment: selected.environment, workingDirectory: selected.workingDirectory, ...selected.worktreeId === void 0 ? {} : { worktreeId: selected.worktreeId } } },
30673
+ sessions: workspace === void 0 ? snapshot4.sessions : projectSessions(workspace, managedProjects.get(workspace) ?? { controller, snapshot: snapshot4, tasks: /* @__PURE__ */ new Set() }),
30674
+ projects: projectSummaries(),
30675
+ activities: workbench.activities,
30676
+ recoveryItems: workbench.recoveryItems
30677
+ };
30678
+ }
30679
+ function decorateSessionStarted(payload) {
30680
+ return { ...payload, snapshot: decorateSnapshot(payload.snapshot) };
30681
+ }
30682
+ function emitManagedDesktopEvent(workspace, owner2, event) {
30683
+ if (workspace === void 0) {
30684
+ emitDesktopEvent(event.type === "snapshot" ? { ...event, snapshot: decorateSnapshot(event.snapshot) } : event);
30685
+ return;
30686
+ }
30687
+ const project = managedProjects.get(workspace);
30688
+ if (project !== void 0) {
30689
+ if (event.type === "snapshot") {
30690
+ event = { ...event, snapshot: { ...event.snapshot, workspace } };
30691
+ const previous = owner2?.snapshot;
30692
+ if (owner2 !== void 0) {
30693
+ owner2.snapshot = event.snapshot;
30694
+ if (owner2.payload !== void 0) owner2.payload = { ...owner2.payload, snapshot: event.snapshot };
30695
+ } else project.snapshot = event.snapshot;
30696
+ trackTaskTransition(workspace, owner2, previous, event.snapshot);
30697
+ } else if (event.type === "session-started") {
30698
+ event = { ...event, payload: { ...event.payload, snapshot: { ...event.payload.snapshot, workspace } } };
30699
+ if (owner2 !== void 0) {
30700
+ owner2.sessionId = event.payload.sessionId;
30701
+ owner2.payload = event.payload;
30702
+ owner2.snapshot = event.payload.snapshot;
30703
+ } else project.snapshot = event.payload.snapshot;
30704
+ } else if (event.type === "session-output" && owner2 !== void 0) {
30705
+ if (event.event.type === "done") owner2.terminal = "completed";
30706
+ else if (event.event.type === "error") owner2.terminal = "failed";
30707
+ else if (event.event.type === "exit") owner2.terminal = "interrupted";
30708
+ } else if (event.type === "runtime-error") {
30709
+ if (owner2 !== void 0) owner2.terminal = "failed";
30710
+ addActivity(workspace, event.sessionId ?? owner2?.sessionId, "failed", "\u4EFB\u52A1\u6267\u884C\u5931\u8D25", event.message);
30711
+ }
30712
+ }
30713
+ const ownerSelected = owner2 === void 0 ? project?.selectedTask === void 0 : project?.selectedTask === owner2;
30714
+ if (workspace === activeWorkspace && ownerSelected) {
30715
+ if (event.type === "snapshot") emitDesktopEvent({ ...event, workspace, snapshot: decorateSnapshot(event.snapshot) });
30716
+ else if (event.type === "session-started") emitDesktopEvent({ ...event, workspace, payload: decorateSessionStarted(event.payload) });
30717
+ else emitDesktopEvent({ ...event, workspace });
30718
+ return;
30719
+ }
30720
+ if (event.type === "session-output") emitDesktopEvent({ ...event, workspace });
30721
+ if ((event.type === "snapshot" || event.type === "session-started") && activeWorkspace !== void 0) {
30722
+ emitDesktopEvent({ type: "snapshot", workspace: activeWorkspace, snapshot: decorateSnapshot(controller.snapshot()) });
30723
+ }
30724
+ }
30725
+ function createDesktopController(workspace, owner2) {
30726
+ const collaborationId = randomUUID27();
30727
+ return new DesktopRuntimeController({
30728
+ emit: (event) => emitManagedDesktopEvent(workspace, owner2, event),
30729
+ runD2cInteractionTests: (manifest, baseUrl, mockUrl) => d2cEmbedded.run(manifest, baseUrl, mockUrl),
30730
+ observeD2cPages: (manifest, baseUrl) => d2cEmbedded.observe(manifest, baseUrl),
30731
+ captureD2cPreview: (url) => d2cEmbedded.capture(url),
30732
+ d2cJudge: {
30733
+ config: () => judgeStore().view(),
30734
+ saveConfig: (input) => judgeStore().save(input),
30735
+ evaluate: async ({ report, interaction, designPng, implementationPng }) => {
30736
+ const config = await judgeStore().load();
30737
+ if (config === void 0) throw new Error("Configure a multimodal D2C judge model before running the quality gate");
30738
+ const assessment = await d2cJudgeClient.evaluate(config, {
30739
+ prompt: buildD2cJudgePrompt({ report, interaction }),
30740
+ designPng: judgeImage(designPng),
30741
+ implementationPng: judgeImage(implementationPng)
30742
+ });
30743
+ return finalizeD2cQualityJudgment({ assessment, report, interaction, model: config.model, passThreshold: config.passThreshold });
30744
+ },
30745
+ planInteractions: async (input) => {
30746
+ const config = await judgeStore().load();
30747
+ if (config === void 0) throw new Error("Configure a multimodal D2C judge model before autonomous interaction review");
30748
+ return d2cJudgeClient.planInteractions(config, {
30749
+ prompt: buildD2cAutonomousPlanPrompt(input),
30750
+ screenshots: input.observations.map((page) => judgeImage(page.screenshot)),
30751
+ observedPages: input.observations.map((page) => page.url),
30752
+ observedSelectors: input.observations.flatMap((page) => page.elements.map((element) => element.selector))
30753
+ });
30754
+ }
29894
30755
  },
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
- });
30756
+ createRuntime: async (runtimeOptions) => createProductionRuntime({
30757
+ ...runtimeOptions,
30758
+ collaboration: {
30759
+ instanceId: collaborationId,
30760
+ alias: `desktop-${basename11(workspace ?? runtimeOptions.workspace ?? "flavor")}`.slice(0, 64),
30761
+ optional: true,
30762
+ startBroker: (address) => {
30763
+ const entry2 = app.isPackaged ? join46(process.resourcesPath, "app.asar.unpacked", "dist", "desktop", "pals-broker-entry.js") : join46(moduleDirectory, "pals-broker-entry.js");
30764
+ return spawnPalBrokerProcess({
30765
+ address,
30766
+ executable: process.execPath,
30767
+ args: [entry2, address],
30768
+ environment: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }
30769
+ });
30770
+ }
30771
+ },
30772
+ ...runtimeOptions.workspace === void 0 ? {} : {
30773
+ extraTools: createD2cTools(runtimeOptions.workspace, {
30774
+ capture: d2cCapture,
30775
+ onProgress: (progress) => emitManagedDesktopEvent(workspace, owner2, { type: "d2c-progress", payload: progress }),
30776
+ onReport: (report) => emitManagedDesktopEvent(workspace, owner2, { type: "d2c-report", payload: report })
30777
+ })
30778
+ }
30779
+ })
30780
+ });
30781
+ }
30782
+ var detachedController = createDesktopController();
30783
+ var controller = detachedController;
29917
30784
  function statePath() {
29918
- return join43(app.getPath("userData"), "desktop-state.json");
30785
+ return join46(app.getPath("userData"), "desktop-state.json");
29919
30786
  }
29920
- async function loadPersistedWorkspace() {
30787
+ async function loadPersistedProjects() {
29921
30788
  try {
29922
- const raw = await readFile36(statePath(), "utf8");
29923
- if (raw.length > 4e4) return void 0;
29924
- return normalizePersistedWorkspace(JSON.parse(raw));
30789
+ const raw = await readFile39(statePath(), "utf8");
30790
+ if (raw.length > 1e5) return { projects: [] };
30791
+ const parsed = JSON.parse(raw);
30792
+ Object.assign(workbench, {
30793
+ projectMeta: validRecord(parsed.projectMeta),
30794
+ sessionMeta: validRecord(parsed.sessionMeta),
30795
+ activities: Array.isArray(parsed.activities) ? parsed.activities.slice(0, 500) : [],
30796
+ recoveryItems: Array.isArray(parsed.recoveryItems) ? parsed.recoveryItems.slice(0, 100) : [],
30797
+ runningSessions: Array.isArray(parsed.runningSessions) ? parsed.runningSessions.slice(0, 100) : [],
30798
+ 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) : [],
30799
+ cleanShutdown: parsed.cleanShutdown !== false
30800
+ });
30801
+ if (!workbench.cleanShutdown) {
30802
+ const interruptedAt = (/* @__PURE__ */ new Date()).toISOString();
30803
+ workbench.recoveryItems = workbench.runningSessions.map((item) => ({ ...item, interruptedAt, reason: "\u5E94\u7528\u4E0A\u6B21\u9000\u51FA\u65F6\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C" }));
30804
+ workbench.activities = [...workbench.runningSessions.map((item, index) => ({
30805
+ id: `recovery-${Date.now()}-${index}`,
30806
+ ...item,
30807
+ kind: "interrupted",
30808
+ title: "\u4EFB\u52A1\u56E0\u5E94\u7528\u9000\u51FA\u800C\u4E2D\u65AD",
30809
+ detail: "\u53EF\u4EE5\u6062\u590D\u4F1A\u8BDD\u5E76\u7EE7\u7EED\u6267\u884C",
30810
+ createdAt: interruptedAt,
30811
+ unread: true
30812
+ })), ...workbench.activities].slice(0, 500);
30813
+ }
30814
+ return normalizePersistedDesktopProjects(parsed);
29925
30815
  } catch {
29926
- return void 0;
30816
+ return { projects: [] };
29927
30817
  }
29928
30818
  }
29929
- async function savePersistedWorkspace(workspace) {
29930
- await writeFile18(statePath(), `${JSON.stringify({ workspace })}
30819
+ async function savePersistedProjects() {
30820
+ await writeFile19(statePath(), `${JSON.stringify({ workspace: activeWorkspace, projects: projectOrder, ...workbench })}
29931
30821
  `, { encoding: "utf8", mode: 384 });
29932
30822
  }
30823
+ function validRecord(value) {
30824
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
30825
+ }
29933
30826
  async function assertDirectory(path) {
29934
- if (!(await stat15(path)).isDirectory()) throw new Error("The selected project is not a directory");
30827
+ if (!(await stat18(path)).isDirectory()) throw new Error("The selected project is not a directory");
29935
30828
  }
29936
30829
  async function openWorkspace(path) {
29937
- await assertDirectory(path);
29938
- const snapshot4 = await controller.openWorkspace(path);
29939
- await savePersistedWorkspace(path);
29940
- return snapshot4;
30830
+ const workspace = resolve40(path);
30831
+ await assertDirectory(workspace);
30832
+ let project = managedProjects.get(workspace);
30833
+ if (project === void 0) {
30834
+ const nextController = createDesktopController(workspace);
30835
+ project = { controller: nextController, snapshot: nextController.snapshot(), tasks: /* @__PURE__ */ new Set() };
30836
+ managedProjects.set(workspace, project);
30837
+ projectOrder.unshift(workspace);
30838
+ activeWorkspace = workspace;
30839
+ controller = nextController;
30840
+ project.snapshot = await nextController.openWorkspace(workspace);
30841
+ for (const saved of workbench.taskCheckouts.filter((item) => resolve40(item.workspace) === workspace)) {
30842
+ try {
30843
+ await assertDirectory(saved.workingDirectory);
30844
+ const task = { environment: "worktree", workingDirectory: resolve40(saved.workingDirectory), worktreeId: saved.worktreeId };
30845
+ task.controller = createDesktopController(workspace, task);
30846
+ task.snapshot = await task.controller.openWorkspace(task.workingDirectory);
30847
+ task.sessionId = saved.sessionId;
30848
+ project.tasks.add(task);
30849
+ } catch {
30850
+ }
30851
+ }
30852
+ } else {
30853
+ activeWorkspace = workspace;
30854
+ controller = project.selectedTask?.controller ?? project.controller;
30855
+ project.snapshot = await project.controller.refreshSessions();
30856
+ }
30857
+ await savePersistedProjects();
30858
+ return decorateSnapshot(project.selectedTask?.snapshot ?? project.snapshot);
30859
+ }
30860
+ function trackTaskTransition(workspace, task, previous, next) {
30861
+ const sessionId = task?.sessionId ?? next.activeSession?.sessionId;
30862
+ if (sessionId === void 0) return;
30863
+ const wasBusy = previous?.activeSession?.busy ?? false;
30864
+ const busy = next.activeSession?.busy ?? false;
30865
+ const key = sessionMetaKey(workspace, sessionId);
30866
+ if (!wasBusy && busy) {
30867
+ if (task !== void 0) delete task.terminal;
30868
+ workbench.activities = workbench.activities.filter((item) => !(item.workspace === workspace && item.sessionId === sessionId));
30869
+ workbench.runningSessions = [...workbench.runningSessions.filter((item) => sessionMetaKey(item.workspace, item.sessionId) !== key), { workspace, sessionId }];
30870
+ } else if (wasBusy && !busy) {
30871
+ workbench.runningSessions = workbench.runningSessions.filter((item) => sessionMetaKey(item.workspace, item.sessionId) !== key);
30872
+ const outcome = task?.terminal ?? "completed";
30873
+ 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");
30874
+ }
30875
+ const needsAttention = next.approval !== void 0 || (next.questions?.length ?? 0) > 0;
30876
+ const hadAttention = previous?.approval !== void 0 || (previous?.questions?.length ?? 0) > 0;
30877
+ if (needsAttention && !hadAttention) addActivity(workspace, sessionId, "attention", "\u4EFB\u52A1\u7B49\u5F85\u4F60\u7684\u786E\u8BA4");
30878
+ void savePersistedProjects();
30879
+ }
30880
+ function addActivity(workspace, sessionId, kind, title, detail) {
30881
+ 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 };
30882
+ workbench.activities = [item, ...workbench.activities.filter((old) => !(old.workspace === workspace && old.sessionId === sessionId && old.kind === kind))].slice(0, 500);
30883
+ if (Notification.isSupported()) {
30884
+ const notification = new Notification({ title, body: detail ?? `${workbench.projectMeta[workspace]?.label ?? workspace.split(/[\\/]/).pop() ?? workspace}` });
30885
+ notification.on("click", () => {
30886
+ void activateManagedSession(workspace, sessionId);
30887
+ });
30888
+ notification.show();
30889
+ }
30890
+ void savePersistedProjects();
30891
+ }
30892
+ async function startManagedSession(resumeSession, environment = "local") {
30893
+ if (activeWorkspace === void 0) throw new Error("Open a project before starting a session");
30894
+ const project = managedProjects.get(activeWorkspace);
30895
+ if (resumeSession !== void 0) {
30896
+ const existing = [...project.tasks].find((task2) => task2.sessionId === resumeSession);
30897
+ if (existing?.payload !== void 0) {
30898
+ project.selectedTask = existing;
30899
+ controller = existing.controller;
30900
+ return decorateSessionStarted(existing.payload);
30901
+ }
30902
+ }
30903
+ 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");
30904
+ const isolated = environment === "worktree" ? await worktreeManager(activeWorkspace).create(`task-${Date.now()}`) : void 0;
30905
+ const task = {
30906
+ environment,
30907
+ workingDirectory: isolated?.path ?? activeWorkspace,
30908
+ ...isolated === void 0 ? {} : { worktreeId: isolated.id }
30909
+ };
30910
+ task.controller = createDesktopController(activeWorkspace, task);
30911
+ task.snapshot = task.controller.snapshot();
30912
+ project.tasks.add(task);
30913
+ project.selectedTask = task;
30914
+ controller = task.controller;
30915
+ await task.controller.openWorkspace(task.workingDirectory);
30916
+ const payload = await task.controller.startSession(resumeSession);
30917
+ task.sessionId = payload.sessionId;
30918
+ task.payload = payload;
30919
+ task.snapshot = payload.snapshot;
30920
+ if (task.environment === "worktree" && task.worktreeId !== void 0) {
30921
+ 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 }];
30922
+ }
30923
+ project.snapshot = { ...project.snapshot, sessions: payload.snapshot.sessions };
30924
+ await savePersistedProjects();
30925
+ return decorateSessionStarted(payload);
30926
+ }
30927
+ async function activateManagedSession(workspace, sessionId) {
30928
+ await openWorkspace(workspace);
30929
+ if (sessionId === void 0) return void 0;
30930
+ const project = managedProjects.get(workspace);
30931
+ const task = [...project.tasks].find((item) => item.sessionId === sessionId);
30932
+ let payload;
30933
+ if (task !== void 0) {
30934
+ project.selectedTask = task;
30935
+ controller = task.controller;
30936
+ payload = task.payload ?? await task.controller.startSession(sessionId);
30937
+ task.payload = payload;
30938
+ task.snapshot = payload.snapshot;
30939
+ } else payload = await startManagedSession(sessionId);
30940
+ workbench.activities = workbench.activities.map((item) => item.workspace === workspace && item.sessionId === sessionId ? { ...item, unread: false } : item);
30941
+ mainWindow?.show();
30942
+ mainWindow?.focus();
30943
+ emitDesktopEvent({ type: "session-started", workspace, payload: decorateSessionStarted(payload) });
30944
+ await savePersistedProjects();
30945
+ return decorateSessionStarted(payload);
29941
30946
  }
29942
30947
  async function chooseAndOpenWorkspace() {
29943
30948
  const result = await dialog.showOpenDialog(mainWindow, {
@@ -29950,7 +30955,7 @@ async function chooseAndOpenWorkspace() {
29950
30955
  var cachedAppIconDataUrl;
29951
30956
  function appIconDataUrl() {
29952
30957
  if (cachedAppIconDataUrl !== void 0) return cachedAppIconDataUrl;
29953
- const icon = nativeImage.createFromPath(join43(app.getAppPath(), "assets", "icon.png"));
30958
+ const icon = nativeImage.createFromPath(join46(app.getAppPath(), "assets", "icon.png"));
29954
30959
  if (icon.isEmpty()) return void 0;
29955
30960
  cachedAppIconDataUrl = icon.toDataURL();
29956
30961
  return cachedAppIconDataUrl;
@@ -29958,13 +30963,23 @@ function appIconDataUrl() {
29958
30963
  function installIpcHandlers() {
29959
30964
  ipcMain.handle(DESKTOP_CHANNELS.appIcon, () => appIconDataUrl());
29960
30965
  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();
30966
+ const persisted = await loadPersistedProjects();
30967
+ workbench.cleanShutdown = false;
30968
+ for (const path of [...persisted.projects].reverse()) {
30969
+ try {
30970
+ await openWorkspace(path);
30971
+ } catch {
30972
+ }
30973
+ }
30974
+ if (persisted.workspace !== void 0 && managedProjects.has(resolve40(persisted.workspace))) {
30975
+ try {
30976
+ return await openWorkspace(persisted.workspace);
30977
+ } catch {
30978
+ }
29967
30979
  }
30980
+ const fallback = projectOrder[0];
30981
+ if (fallback !== void 0) return openWorkspace(fallback);
30982
+ return decorateSnapshot(controller.snapshot());
29968
30983
  });
29969
30984
  ipcMain.handle(DESKTOP_CHANNELS.chooseWorkspace, chooseAndOpenWorkspace);
29970
30985
  ipcMain.handle(DESKTOP_CHANNELS.openWorkspace, async (_event, value) => {
@@ -29972,12 +30987,170 @@ function installIpcHandlers() {
29972
30987
  return openWorkspace(path);
29973
30988
  });
29974
30989
  ipcMain.handle(DESKTOP_CHANNELS.startSession, async (_event, value) => {
29975
- const { resumeSession } = StartSessionInputSchema.parse(value);
29976
- return controller.startSession(resumeSession);
30990
+ const { resumeSession, environment } = StartSessionInputSchema.parse(value);
30991
+ return startManagedSession(resumeSession, environment);
30992
+ });
30993
+ ipcMain.handle(DESKTOP_CHANNELS.selectSession, async (_event, value) => {
30994
+ const { sessionId } = DeleteSessionInputSchema.parse(value);
30995
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
30996
+ return activateManagedSession(activeWorkspace, sessionId);
29977
30997
  });
29978
30998
  ipcMain.handle(DESKTOP_CHANNELS.deleteSession, async (_event, value) => {
29979
30999
  const { sessionId } = DeleteSessionInputSchema.parse(value);
29980
- return controller.deleteSession(sessionId);
31000
+ if (activeWorkspace === void 0) return decorateSnapshot(await controller.deleteSession(sessionId));
31001
+ const project = managedProjects.get(activeWorkspace);
31002
+ const task = [...project.tasks].find((item) => item.sessionId === sessionId);
31003
+ if (task !== void 0) {
31004
+ if (task.snapshot.activeSession?.busy) throw new Error("\u8BF7\u5148\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1");
31005
+ await task.controller.dispose();
31006
+ project.tasks.delete(task);
31007
+ if (project.selectedTask === task) {
31008
+ delete project.selectedTask;
31009
+ controller = project.controller;
31010
+ }
31011
+ }
31012
+ delete workbench.sessionMeta[sessionMetaKey(activeWorkspace, sessionId)];
31013
+ workbench.taskCheckouts = workbench.taskCheckouts.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31014
+ workbench.activities = workbench.activities.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31015
+ project.snapshot = await project.controller.deleteSession(sessionId);
31016
+ await savePersistedProjects();
31017
+ return decorateSnapshot(project.selectedTask?.snapshot ?? project.snapshot);
31018
+ });
31019
+ ipcMain.handle(DESKTOP_CHANNELS.updateSession, async (_event, value) => {
31020
+ const input = UpdateSessionInputSchema.parse(value);
31021
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31022
+ const key = sessionMetaKey(activeWorkspace, input.sessionId);
31023
+ 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 } };
31024
+ await savePersistedProjects();
31025
+ return decorateSnapshot(controller.snapshot());
31026
+ });
31027
+ ipcMain.handle(DESKTOP_CHANNELS.acknowledgeSession, async (_event, value) => {
31028
+ const sessionId = Object.keys(value).length === 0 ? void 0 : DeleteSessionInputSchema.parse(value).sessionId;
31029
+ workbench.activities = workbench.activities.map((item) => item.workspace === activeWorkspace && (sessionId === void 0 || item.sessionId === sessionId) ? { ...item, unread: false } : item);
31030
+ await savePersistedProjects();
31031
+ return decorateSnapshot(controller.snapshot());
31032
+ });
31033
+ ipcMain.handle(DESKTOP_CHANNELS.updateProject, async (_event, value) => {
31034
+ const input = UpdateProjectInputSchema.parse(value);
31035
+ const workspace = resolve40(input.workspace);
31036
+ if (!managedProjects.has(workspace)) throw new Error("\u9879\u76EE\u672A\u6253\u5F00");
31037
+ workbench.projectMeta[workspace] = { ...workbench.projectMeta[workspace], ...input.label === void 0 ? {} : { label: input.label || void 0 }, ...input.pinned === void 0 ? {} : { pinned: input.pinned } };
31038
+ await savePersistedProjects();
31039
+ return decorateSnapshot(controller.snapshot());
31040
+ });
31041
+ ipcMain.handle(DESKTOP_CHANNELS.closeProject, async (_event, value) => {
31042
+ const input = CloseProjectInputSchema.parse(value);
31043
+ const workspace = resolve40(input.workspace);
31044
+ const project = managedProjects.get(workspace);
31045
+ if (project === void 0) return decorateSnapshot(controller.snapshot());
31046
+ 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");
31047
+ await Promise.all([project.controller, ...[...project.tasks].map((task) => task.controller)].map((item) => item.dispose()));
31048
+ managedProjects.delete(workspace);
31049
+ projectOrder.splice(projectOrder.indexOf(workspace), 1);
31050
+ if (activeWorkspace === workspace) {
31051
+ activeWorkspace = projectOrder[0];
31052
+ const next = activeWorkspace === void 0 ? void 0 : managedProjects.get(activeWorkspace);
31053
+ controller = next?.selectedTask?.controller ?? next?.controller ?? detachedController;
31054
+ }
31055
+ await savePersistedProjects();
31056
+ return decorateSnapshot(controller.snapshot());
31057
+ });
31058
+ ipcMain.handle(DESKTOP_CHANNELS.revealProject, async (_event, value) => {
31059
+ const workspace = resolve40(ProjectPathInputSchema.parse(value).workspace);
31060
+ await assertDirectory(workspace);
31061
+ await shell.openPath(workspace);
31062
+ });
31063
+ ipcMain.handle(DESKTOP_CHANNELS.copyProjectPath, async (_event, value) => {
31064
+ clipboard.writeText(resolve40(ProjectPathInputSchema.parse(value).workspace));
31065
+ });
31066
+ ipcMain.handle(DESKTOP_CHANNELS.dismissRecovery, async (_event, value) => {
31067
+ const { sessionId } = DeleteSessionInputSchema.parse(value);
31068
+ workbench.recoveryItems = workbench.recoveryItems.filter((item) => !(item.workspace === activeWorkspace && item.sessionId === sessionId));
31069
+ await savePersistedProjects();
31070
+ return decorateSnapshot(controller.snapshot());
31071
+ });
31072
+ ipcMain.handle(DESKTOP_CHANNELS.gitStatus, async () => activeWorkspace === void 0 ? { repository: false, branch: "", head: "", files: [] } : desktopGitStatus(activeWorkingDirectory()));
31073
+ ipcMain.handle(DESKTOP_CHANNELS.gitDiff, async (_event, value) => {
31074
+ const input = GitDiffInputSchema.parse(value);
31075
+ return desktopGitDiff(activeWorkingDirectory(), input.path, input.staged);
31076
+ });
31077
+ ipcMain.handle(DESKTOP_CHANNELS.gitStage, async (_event, value) => desktopGitStage(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31078
+ ipcMain.handle(DESKTOP_CHANNELS.gitUnstage, async (_event, value) => desktopGitUnstage(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31079
+ ipcMain.handle(DESKTOP_CHANNELS.gitDiscard, async (_event, value) => desktopGitDiscard(activeWorkingDirectory(), GitPathInputSchema.parse(value).path));
31080
+ ipcMain.handle(DESKTOP_CHANNELS.gitCommit, async (_event, value) => desktopGitCommit(activeWorkingDirectory(), GitCommitInputSchema.parse(value).message));
31081
+ ipcMain.handle(DESKTOP_CHANNELS.gitReview, async (_event, value) => {
31082
+ const input = GitReviewInputSchema.parse(value);
31083
+ const workspace = activeWorkingDirectory();
31084
+ 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 } });
31085
+ });
31086
+ ipcMain.handle(DESKTOP_CHANNELS.gitHistory, async () => desktopGitHistory(activeWorkingDirectory()));
31087
+ ipcMain.handle(DESKTOP_CHANNELS.listWorktrees, async () => activeWorkspace === void 0 ? [] : worktreeManager(activeWorkspace).list());
31088
+ ipcMain.handle(DESKTOP_CHANNELS.removeWorktree, async (_event, value) => {
31089
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31090
+ const input = WorktreeRemoveInputSchema.parse(value);
31091
+ const task = [...managedProjects.get(activeWorkspace).tasks].find((item) => item.worktreeId === input.id);
31092
+ if (task?.snapshot.activeSession?.busy) throw new Error("\u4E0D\u80FD\u5220\u9664\u6B63\u5728\u8FD0\u884C\u4EFB\u52A1\u7684\u5DE5\u4F5C\u6811");
31093
+ if (task !== void 0) {
31094
+ await task.controller.dispose();
31095
+ managedProjects.get(activeWorkspace).tasks.delete(task);
31096
+ }
31097
+ await worktreeManager(activeWorkspace).remove(input.id, input.force);
31098
+ workbench.taskCheckouts = workbench.taskCheckouts.filter((item) => !(item.workspace === activeWorkspace && item.worktreeId === input.id));
31099
+ await savePersistedProjects();
31100
+ });
31101
+ ipcMain.handle(DESKTOP_CHANNELS.mergeWorktree, async (_event, value) => {
31102
+ if (activeWorkspace === void 0) throw new Error("\u8BF7\u5148\u6253\u5F00\u9879\u76EE");
31103
+ await worktreeManager(activeWorkspace).merge(WorktreeRemoveInputSchema.parse(value).id);
31104
+ });
31105
+ ipcMain.handle(DESKTOP_CHANNELS.historySnapshot, async () => controller.historySnapshot());
31106
+ ipcMain.handle(DESKTOP_CHANNELS.createCheckpoint, async (_event, value) => controller.createCheckpoint(CheckpointInputSchema.parse(value).label));
31107
+ ipcMain.handle(DESKTOP_CHANNELS.rewindHistory, async (_event, value) => controller.rewindHistory(HistoryNodeInputSchema.parse(value).nodeId));
31108
+ ipcMain.handle(DESKTOP_CHANNELS.unrevertHistory, async () => controller.unrevertHistory());
31109
+ ipcMain.handle(DESKTOP_CHANNELS.forkHistory, async (_event, value) => controller.forkHistory(HistoryNodeInputSchema.parse(value).nodeId));
31110
+ ipcMain.handle(DESKTOP_CHANNELS.terminalOpen, async (_event, value) => {
31111
+ const input = TerminalOpenInputSchema.parse(value);
31112
+ 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 } });
31113
+ });
31114
+ ipcMain.handle(DESKTOP_CHANNELS.terminalList, async () => controller.listTerminals());
31115
+ ipcMain.handle(DESKTOP_CHANNELS.terminalRead, async (_event, value) => {
31116
+ const input = TerminalReadInputSchema.parse(value);
31117
+ return controller.readTerminal(input.id, input.cursor);
31118
+ });
31119
+ ipcMain.handle(DESKTOP_CHANNELS.terminalWrite, async (_event, value) => {
31120
+ const input = TerminalWriteInputSchema.parse(value);
31121
+ controller.writeTerminal(input.id, input.data);
31122
+ });
31123
+ ipcMain.handle(DESKTOP_CHANNELS.terminalResize, async (_event, value) => {
31124
+ const input = TerminalResizeInputSchema.parse(value);
31125
+ controller.resizeTerminal(input.id, input.columns, input.rows);
31126
+ });
31127
+ ipcMain.handle(DESKTOP_CHANNELS.terminalClose, async (_event, value) => controller.closeTerminal(TerminalIdInputSchema.parse(value).id));
31128
+ ipcMain.handle(DESKTOP_CHANNELS.jobRead, async (_event, value) => {
31129
+ const input = JobReadInputSchema.parse(value);
31130
+ return controller.readJob(input.id, input.cursor);
31131
+ });
31132
+ ipcMain.handle(DESKTOP_CHANNELS.previewValidate, async (_event, value) => DesktopWorkbenchService.normalizePreviewUrl(PreviewUrlInputSchema.parse(value).url));
31133
+ ipcMain.handle(DESKTOP_CHANNELS.previewOpen, async (_event, value) => shell.openExternal(DesktopWorkbenchService.normalizePreviewUrl(PreviewUrlInputSchema.parse(value).url)));
31134
+ ipcMain.handle(DESKTOP_CHANNELS.inspectWorkbench, async () => new DesktopWorkbenchService(activeWorkingDirectory()).inspect(controller.snapshot().activeSession?.sessionId));
31135
+ ipcMain.handle(DESKTOP_CHANNELS.astStatus, async () => new DesktopAstGraphService(activeWorkingDirectory()).status());
31136
+ ipcMain.handle(DESKTOP_CHANNELS.astSearch, async (_event, value) => new DesktopAstGraphService(activeWorkingDirectory()).search(AstSearchInputSchema.parse(value).query));
31137
+ ipcMain.handle(DESKTOP_CHANNELS.astRelations, async (_event, value) => {
31138
+ const input = AstNodeInputSchema.parse(value);
31139
+ return new DesktopAstGraphService(activeWorkingDirectory()).relations(input.id, input.hops);
31140
+ });
31141
+ ipcMain.handle(DESKTOP_CHANNELS.listPals, async () => controller.listPals());
31142
+ ipcMain.handle(DESKTOP_CHANNELS.sendPalMessage, async (_event, value) => {
31143
+ const input = PalMessageInputSchema.parse(value);
31144
+ return controller.sendPalMessage(input.target, input.message, input.kind);
31145
+ });
31146
+ ipcMain.handle(DESKTOP_CHANNELS.startCoWork, async (_event, value) => {
31147
+ const input = CoWorkStartInputSchema.parse(value);
31148
+ return controller.startCoWork(input.target, input.goal);
31149
+ });
31150
+ ipcMain.handle(DESKTOP_CHANNELS.coWorkStatus, async (_event, value) => controller.coWorkStatus(CoWorkStatusInputSchema.parse(value).coWorkId));
31151
+ ipcMain.handle(DESKTOP_CHANNELS.cancelCoWork, async (_event, value) => {
31152
+ const input = CoWorkCancelInputSchema.parse(value);
31153
+ return controller.cancelCoWork(input.coWorkId, input.reason);
29981
31154
  });
29982
31155
  ipcMain.handle(DESKTOP_CHANNELS.showAppMenu, async (_event, value) => {
29983
31156
  const { menu, x, y } = AppMenuInputSchema.parse(value);
@@ -30046,11 +31219,13 @@ function installIpcHandlers() {
30046
31219
  ipcMain.handle(DESKTOP_CHANNELS.deleteMemory, async (_event, value) => {
30047
31220
  return controller.deleteMemory(DeleteMemoryInputSchema.parse(value).id);
30048
31221
  });
31222
+ ipcMain.handle(DESKTOP_CHANNELS.deleteColdMemory, async () => controller.deleteColdMemory());
30049
31223
  ipcMain.handle(DESKTOP_CHANNELS.switchModel, async (_event, value) => {
30050
- return controller.switchModel(SwitchDesktopModelInputSchema.parse(value).modelId);
31224
+ return decorateSnapshot(await controller.switchModel(SwitchDesktopModelInputSchema.parse(value).modelId));
30051
31225
  });
30052
31226
  ipcMain.handle(DESKTOP_CHANNELS.addModel, async (_event, value) => {
30053
- return controller.addModel(AddDesktopModelInputSchema.parse(value));
31227
+ const result = await controller.addModel(AddDesktopModelInputSchema.parse(value));
31228
+ return { ...result, snapshot: decorateSnapshot(result.snapshot) };
30054
31229
  });
30055
31230
  ipcMain.handle(DESKTOP_CHANNELS.d2cImport, async (_event, value) => {
30056
31231
  const { task } = D2cImportInputSchema.parse(value);
@@ -30198,7 +31373,7 @@ function applicationMenu() {
30198
31373
  ]);
30199
31374
  }
30200
31375
  async function createWindow() {
30201
- const rendererPath = join43(app.getAppPath(), "dist", "desktop-renderer", "index.html");
31376
+ const rendererPath = join46(app.getAppPath(), "dist", "desktop-renderer", "index.html");
30202
31377
  mainWindow = new BrowserWindow2({
30203
31378
  title: "Flavor Code",
30204
31379
  width: 1280,
@@ -30209,14 +31384,14 @@ async function createWindow() {
30209
31384
  show: false,
30210
31385
  ...desktopWindowChrome(),
30211
31386
  webPreferences: {
30212
- preload: join43(moduleDirectory, "preload.cjs"),
31387
+ preload: join46(moduleDirectory, "preload.cjs"),
30213
31388
  contextIsolation: true,
30214
31389
  nodeIntegration: false,
30215
31390
  sandbox: false,
30216
31391
  webSecurity: true
30217
31392
  }
30218
31393
  });
30219
- const iconPath = join43(app.getAppPath(), "assets", "icon.png");
31394
+ const iconPath = join46(app.getAppPath(), "assets", "icon.png");
30220
31395
  const icon = nativeImage.createFromPath(iconPath);
30221
31396
  if (!icon.isEmpty()) {
30222
31397
  mainWindow.setIcon(icon);
@@ -30231,7 +31406,9 @@ async function createWindow() {
30231
31406
  if (quitting) return;
30232
31407
  event.preventDefault();
30233
31408
  quitting = true;
30234
- void controller.dispose().finally(() => {
31409
+ workbench.cleanShutdown = true;
31410
+ const controllers = [detachedController, ...[...managedProjects.values()].flatMap((project) => [project.controller, ...[...project.tasks].map((task) => task.controller)])];
31411
+ void savePersistedProjects().catch(() => void 0).then(() => Promise.all(controllers.map((item) => item.dispose()))).finally(() => {
30235
31412
  mainWindow?.destroy();
30236
31413
  app.quit();
30237
31414
  });
@@ -30264,8 +31441,16 @@ app.whenReady().then(async () => {
30264
31441
  dialog.showErrorBox("Flavor Code \u65E0\u6CD5\u542F\u52A8", error instanceof Error ? error.message : String(error));
30265
31442
  app.quit();
30266
31443
  });
30267
- app.on("before-quit", () => {
31444
+ app.on("before-quit", (event) => {
31445
+ if (quitting) return;
31446
+ event.preventDefault();
30268
31447
  quitting = true;
31448
+ workbench.cleanShutdown = true;
31449
+ const controllers = [detachedController, ...[...managedProjects.values()].flatMap((project) => [project.controller, ...[...project.tasks].map((task) => task.controller)])];
31450
+ void savePersistedProjects().catch(() => void 0).then(() => Promise.all(controllers.map((item) => item.dispose()))).finally(() => {
31451
+ mainWindow?.destroy();
31452
+ app.quit();
31453
+ });
30269
31454
  });
30270
31455
  app.on("window-all-closed", () => {
30271
31456
  if (process.platform !== "darwin") app.quit();