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