whipped 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +236 -109
- package/dist/mcp-server.js +17 -2
- package/dist/sounds/blocked.wav +0 -0
- package/dist/sounds/done.wav +0 -0
- package/dist/sounds/pr-comment.wav +0 -0
- package/dist/sounds/ready-for-review.wav +0 -0
- package/dist/sounds/reopened.wav +0 -0
- package/dist/sounds/run-error.wav +0 -0
- package/dist/web-ui/assets/{index-BBh0-Z42.js → index-BDiIsuhZ.js} +186 -44
- package/dist/web-ui/assets/{index-Do7b5IJu.css → index-CRXPsGTP.css} +16 -0
- package/dist/web-ui/index.html +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3529,7 +3529,7 @@ function isResumableSessionState(state) {
|
|
|
3529
3529
|
function normalizeTag(raw2) {
|
|
3530
3530
|
return raw2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3531
3531
|
}
|
|
3532
|
-
var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
|
|
3532
|
+
var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, notificationSoundsConfigSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeCopyEntrySchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
|
|
3533
3533
|
var init_api_contract = __esm({
|
|
3534
3534
|
"src/core/api-contract.ts"() {
|
|
3535
3535
|
"use strict";
|
|
@@ -3847,6 +3847,15 @@ Do NOT include:
|
|
|
3847
3847
|
columns: z.array(runtimeBoardColumnSchema),
|
|
3848
3848
|
cards: z.record(z.string(), runtimeBoardCardSchema)
|
|
3849
3849
|
});
|
|
3850
|
+
notificationSoundsConfigSchema = z.object({
|
|
3851
|
+
enabled: z.boolean().default(false),
|
|
3852
|
+
readyForReview: z.boolean().default(true),
|
|
3853
|
+
prComment: z.boolean().default(true),
|
|
3854
|
+
done: z.boolean().default(true),
|
|
3855
|
+
reopened: z.boolean().default(true),
|
|
3856
|
+
blocked: z.boolean().default(true),
|
|
3857
|
+
runError: z.boolean().default(true)
|
|
3858
|
+
});
|
|
3850
3859
|
runtimeGlobalConfigSchema = z.object({
|
|
3851
3860
|
defaultAgent: runtimeAgentIdSchema.default("claude"),
|
|
3852
3861
|
maxParallelTasks: z.number().int().positive().default(4),
|
|
@@ -3855,6 +3864,7 @@ Do NOT include:
|
|
|
3855
3864
|
pollingIntervalSeconds: z.number().int().positive().default(30),
|
|
3856
3865
|
prPollingIntervalSeconds: z.number().int().positive().default(60),
|
|
3857
3866
|
terminalApp: z.string().optional(),
|
|
3867
|
+
notificationSounds: notificationSoundsConfigSchema.prefault({}),
|
|
3858
3868
|
slackEnabled: z.boolean().default(true),
|
|
3859
3869
|
slackBotToken: z.string().optional(),
|
|
3860
3870
|
slackSigningSecret: z.string().optional(),
|
|
@@ -3880,8 +3890,13 @@ Do NOT include:
|
|
|
3880
3890
|
runtimeGithubConfigSchema = z.object({
|
|
3881
3891
|
token: z.string()
|
|
3882
3892
|
});
|
|
3893
|
+
runtimeWorktreeCopyEntrySchema = z.object({
|
|
3894
|
+
path: z.string().min(1),
|
|
3895
|
+
symlink: z.boolean().default(false)
|
|
3896
|
+
});
|
|
3883
3897
|
runtimeWorktreeSetupSchema = z.object({
|
|
3884
|
-
|
|
3898
|
+
// Legacy configs stored bare strings; accept and normalize them to copy entries.
|
|
3899
|
+
filesToCopy: z.array(z.union([z.string().transform((path2) => ({ path: path2, symlink: false })), runtimeWorktreeCopyEntrySchema])).default([]),
|
|
3885
3900
|
installCommand: z.string().default("")
|
|
3886
3901
|
});
|
|
3887
3902
|
runtimeProjectSecretSchema = z.object({
|
|
@@ -8973,7 +8988,7 @@ var require_tree_kill = __commonJS({
|
|
|
8973
8988
|
"node_modules/.pnpm/tree-kill@1.2.2/node_modules/tree-kill/index.js"(exports, module) {
|
|
8974
8989
|
"use strict";
|
|
8975
8990
|
var childProcess4 = __require("child_process");
|
|
8976
|
-
var
|
|
8991
|
+
var spawn9 = childProcess4.spawn;
|
|
8977
8992
|
var exec = childProcess4.exec;
|
|
8978
8993
|
module.exports = function(pid, signal, callback) {
|
|
8979
8994
|
if (typeof signal === "function" && callback === void 0) {
|
|
@@ -8998,7 +9013,7 @@ var require_tree_kill = __commonJS({
|
|
|
8998
9013
|
break;
|
|
8999
9014
|
case "darwin":
|
|
9000
9015
|
buildProcessTree(pid, tree, pidsToProcess, function(parentPid) {
|
|
9001
|
-
return
|
|
9016
|
+
return spawn9("pgrep", ["-P", parentPid]);
|
|
9002
9017
|
}, function() {
|
|
9003
9018
|
killAll(tree, signal, callback);
|
|
9004
9019
|
});
|
|
@@ -9010,7 +9025,7 @@ var require_tree_kill = __commonJS({
|
|
|
9010
9025
|
// break;
|
|
9011
9026
|
default:
|
|
9012
9027
|
buildProcessTree(pid, tree, pidsToProcess, function(parentPid) {
|
|
9013
|
-
return
|
|
9028
|
+
return spawn9("ps", ["-o", "pid", "--no-headers", "--ppid", parentPid]);
|
|
9014
9029
|
}, function() {
|
|
9015
9030
|
killAll(tree, signal, callback);
|
|
9016
9031
|
});
|
|
@@ -13547,11 +13562,12 @@ async function runLogs(options) {
|
|
|
13547
13562
|
process.exit(1);
|
|
13548
13563
|
}
|
|
13549
13564
|
if (options.follow) {
|
|
13550
|
-
const
|
|
13565
|
+
const [cmd, args] = process.platform === "win32" ? ["powershell", ["-NoProfile", "-Command", `Get-Content -Path '${path2}' -Tail ${options.lines} -Wait`]] : ["tail", ["-f", "-n", String(options.lines), path2]];
|
|
13566
|
+
const child = spawn2(cmd, args, {
|
|
13551
13567
|
stdio: "inherit"
|
|
13552
13568
|
});
|
|
13553
13569
|
child.on("error", (err) => {
|
|
13554
|
-
console.error(`Failed to
|
|
13570
|
+
console.error(`Failed to follow log file: ${err.message}`);
|
|
13555
13571
|
process.exit(1);
|
|
13556
13572
|
});
|
|
13557
13573
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
@@ -13606,8 +13622,8 @@ init_logger();
|
|
|
13606
13622
|
// src/server/runtime-server.ts
|
|
13607
13623
|
import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
|
|
13608
13624
|
import { createServer } from "node:http";
|
|
13609
|
-
import { join as
|
|
13610
|
-
import { fileURLToPath as
|
|
13625
|
+
import { join as join22 } from "node:path";
|
|
13626
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
13611
13627
|
import * as nodePty2 from "node-pty";
|
|
13612
13628
|
|
|
13613
13629
|
// node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
|
|
@@ -14387,8 +14403,55 @@ var slackNotifier = new SlackNotifier();
|
|
|
14387
14403
|
// src/server/runtime-server.ts
|
|
14388
14404
|
init_runtime_config();
|
|
14389
14405
|
init_logger();
|
|
14406
|
+
|
|
14407
|
+
// src/core/shell.ts
|
|
14408
|
+
function getShellInvocation(command) {
|
|
14409
|
+
if (process.platform === "win32") {
|
|
14410
|
+
return [process.env.ComSpec ?? "cmd.exe", ["/c", command]];
|
|
14411
|
+
}
|
|
14412
|
+
return [process.env.SHELL ?? "/bin/sh", ["-c", command]];
|
|
14413
|
+
}
|
|
14414
|
+
|
|
14415
|
+
// src/server/runtime-server.ts
|
|
14390
14416
|
init_task_id();
|
|
14391
14417
|
|
|
14418
|
+
// src/notifications/sound-player.ts
|
|
14419
|
+
init_runtime_config();
|
|
14420
|
+
init_logger();
|
|
14421
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
14422
|
+
import { dirname as dirname4, join as join11 } from "node:path";
|
|
14423
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
14424
|
+
var SOUNDS_DIR = join11(dirname4(fileURLToPath3(import.meta.url)), "sounds");
|
|
14425
|
+
var SOUND_FILES = {
|
|
14426
|
+
readyForReview: "ready-for-review.wav",
|
|
14427
|
+
prComment: "pr-comment.wav",
|
|
14428
|
+
done: "done.wav",
|
|
14429
|
+
reopened: "reopened.wav",
|
|
14430
|
+
blocked: "blocked.wav",
|
|
14431
|
+
runError: "run-error.wav"
|
|
14432
|
+
};
|
|
14433
|
+
async function playNotificationSound(event) {
|
|
14434
|
+
try {
|
|
14435
|
+
const { notificationSounds } = await loadGlobalConfig();
|
|
14436
|
+
if (!notificationSounds.enabled || !notificationSounds[event]) return;
|
|
14437
|
+
playOnHost(join11(SOUNDS_DIR, SOUND_FILES[event]));
|
|
14438
|
+
} catch (err) {
|
|
14439
|
+
logger.debug({ err }, `[sound] could not play notification sound for ${event}`);
|
|
14440
|
+
}
|
|
14441
|
+
}
|
|
14442
|
+
function playOnHost(file) {
|
|
14443
|
+
const [command, args] = process.platform === "darwin" ? ["afplay", [file]] : process.platform === "win32" ? (
|
|
14444
|
+
// PowerShell's SoundPlayer plays the WAV chimes synchronously in the
|
|
14445
|
+
// detached child (it exits once done); no extra binary needed.
|
|
14446
|
+
["powershell", ["-NoProfile", "-Command", `(New-Object Media.SoundPlayer '${file}').PlaySync()`]]
|
|
14447
|
+
) : process.platform === "linux" ? ["paplay", [file]] : [null, null];
|
|
14448
|
+
if (!command) return;
|
|
14449
|
+
const child = spawn4(command, [...args], { stdio: "ignore", detached: true });
|
|
14450
|
+
child.on("error", () => {
|
|
14451
|
+
});
|
|
14452
|
+
child.unref();
|
|
14453
|
+
}
|
|
14454
|
+
|
|
14392
14455
|
// src/daemon/poller.ts
|
|
14393
14456
|
init_runtime_config();
|
|
14394
14457
|
init_logger();
|
|
@@ -14400,7 +14463,7 @@ import { existsSync as existsSync8 } from "node:fs";
|
|
|
14400
14463
|
import { execFile as execFile7, spawnSync } from "node:child_process";
|
|
14401
14464
|
import { existsSync as existsSync5, mkdirSync as mkdirSync7, rmSync as rmSync2 } from "node:fs";
|
|
14402
14465
|
import { homedir as homedir2 } from "node:os";
|
|
14403
|
-
import { dirname as
|
|
14466
|
+
import { dirname as dirname5, join as join12, resolve, sep as sep2 } from "node:path";
|
|
14404
14467
|
import { promisify as promisify7 } from "node:util";
|
|
14405
14468
|
|
|
14406
14469
|
// node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
|
|
@@ -17953,7 +18016,7 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes
|
|
|
17953
18016
|
// src/git/merge-operations.ts
|
|
17954
18017
|
init_logger();
|
|
17955
18018
|
var execFileAsync5 = promisify7(execFile7);
|
|
17956
|
-
var WORKTREES_DIR =
|
|
18019
|
+
var WORKTREES_DIR = join12(homedir2(), ".whipped", "worktrees");
|
|
17957
18020
|
function git(args, cwd) {
|
|
17958
18021
|
const r = spawnSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
17959
18022
|
return { stdout: r.stdout?.trim() ?? "", stderr: r.stderr?.trim() ?? "", ok: r.status === 0 };
|
|
@@ -18002,9 +18065,9 @@ async function commitIfDirty(worktreePath, message) {
|
|
|
18002
18065
|
return true;
|
|
18003
18066
|
}
|
|
18004
18067
|
function attemptMerge(repoPath, effectiveWorktreeId, taskBranch) {
|
|
18005
|
-
const worktreePath =
|
|
18068
|
+
const worktreePath = join12(WORKTREES_DIR, effectiveWorktreeId);
|
|
18006
18069
|
if (existsSync5(worktreePath)) {
|
|
18007
|
-
rmSync2(worktreePath, { recursive: true, force: true });
|
|
18070
|
+
rmSync2(worktreePath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
18008
18071
|
git(["worktree", "prune"], repoPath);
|
|
18009
18072
|
}
|
|
18010
18073
|
const statusResult = git(["status", "--porcelain"], repoPath);
|
|
@@ -18040,10 +18103,10 @@ async function pushBranch(worktreePath, branch) {
|
|
|
18040
18103
|
throw new Error(`Failed to push: ${detail}`);
|
|
18041
18104
|
});
|
|
18042
18105
|
}
|
|
18043
|
-
var YOLO_DIR =
|
|
18106
|
+
var YOLO_DIR = join12(WORKTREES_DIR, ".yolo");
|
|
18044
18107
|
function createYoloWorktree(repoPath, workspaceId, cardId, baseRef) {
|
|
18045
|
-
const tmpPath =
|
|
18046
|
-
mkdirSync7(
|
|
18108
|
+
const tmpPath = join12(YOLO_DIR, workspaceId, `${cardId}-${Date.now()}`);
|
|
18109
|
+
mkdirSync7(dirname5(tmpPath), { recursive: true });
|
|
18047
18110
|
git(["worktree", "prune"], repoPath);
|
|
18048
18111
|
const res = git(["worktree", "add", "--detach", tmpPath, baseRef], repoPath);
|
|
18049
18112
|
if (!res.ok) throw new Error(`Failed to create YOLO worktree at ${tmpPath}: ${res.stderr}`);
|
|
@@ -18268,10 +18331,10 @@ init_logger();
|
|
|
18268
18331
|
import { execFile as execFile8, spawnSync as spawnSync2 } from "node:child_process";
|
|
18269
18332
|
import { existsSync as existsSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
18270
18333
|
import { rm as rm2 } from "node:fs/promises";
|
|
18271
|
-
import { join as
|
|
18334
|
+
import { join as join13 } from "node:path";
|
|
18272
18335
|
import { promisify as promisify8 } from "node:util";
|
|
18273
18336
|
var execFileAsync6 = promisify8(execFile8);
|
|
18274
|
-
var WORKTREES_DIR2 =
|
|
18337
|
+
var WORKTREES_DIR2 = join13(WHIPPED_HOME_DIR, "worktrees");
|
|
18275
18338
|
function git2(args, cwd) {
|
|
18276
18339
|
const result = spawnSync2("git", args, {
|
|
18277
18340
|
cwd,
|
|
@@ -18289,7 +18352,7 @@ function getCardBranch(card) {
|
|
|
18289
18352
|
function createWorktree(taskId, repoPath, baseRef, branchName) {
|
|
18290
18353
|
mkdirSync8(WORKTREES_DIR2, { recursive: true });
|
|
18291
18354
|
let branch = branchName ?? `task/${taskId}`;
|
|
18292
|
-
const worktreePath =
|
|
18355
|
+
const worktreePath = join13(WORKTREES_DIR2, taskId);
|
|
18293
18356
|
logger.info(`[worktree:create] taskId=${taskId} branch=${branch} baseRef=${baseRef} worktreePath=${worktreePath}`);
|
|
18294
18357
|
if (existsSync6(worktreePath)) {
|
|
18295
18358
|
const actualBranch = git2(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
@@ -18367,12 +18430,12 @@ function createWorktree(taskId, repoPath, baseRef, branchName) {
|
|
|
18367
18430
|
return { taskId, path: worktreePath, branch, isNew: true, conflictedFiles: [] };
|
|
18368
18431
|
}
|
|
18369
18432
|
async function removeWorktreeAsync(taskId, repoPath, branchName) {
|
|
18370
|
-
const worktreePath =
|
|
18433
|
+
const worktreePath = join13(WORKTREES_DIR2, taskId);
|
|
18371
18434
|
const branch = branchName ?? `task/${taskId}`;
|
|
18372
18435
|
const t0 = Date.now();
|
|
18373
18436
|
logger.info(`[cleanup:${taskId}] starting worktree removal`);
|
|
18374
18437
|
try {
|
|
18375
|
-
await rm2(worktreePath, { recursive: true, force: true });
|
|
18438
|
+
await rm2(worktreePath, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 });
|
|
18376
18439
|
logger.info(`[cleanup:${taskId}] rm worktree dir done (${Date.now() - t0}ms)`);
|
|
18377
18440
|
} catch (err) {
|
|
18378
18441
|
logger.error({ err }, `[cleanup:${taskId}] rm worktree dir failed:`);
|
|
@@ -18389,7 +18452,7 @@ async function removeWorktreeAsync(taskId, repoPath, branchName) {
|
|
|
18389
18452
|
logger.info(`[cleanup:${taskId}] done in ${Date.now() - t0}ms`);
|
|
18390
18453
|
}
|
|
18391
18454
|
function getWorktreePath(taskId) {
|
|
18392
|
-
return
|
|
18455
|
+
return join13(WORKTREES_DIR2, taskId);
|
|
18393
18456
|
}
|
|
18394
18457
|
function resolveWorktreeOwnerId(cardId, cards) {
|
|
18395
18458
|
const story = Object.values(cards).find((c) => c.type === "story" && (c.subtaskIds ?? []).includes(cardId));
|
|
@@ -18849,6 +18912,7 @@ var BoardPoller = class {
|
|
|
18849
18912
|
`[poller] ${readyEntries.length} new comment(s) from GitHub PR for "${card.description?.split("\n")[0]?.slice(0, 60) ?? card.id}"`
|
|
18850
18913
|
);
|
|
18851
18914
|
await appendActivityLog(workspaceId, taskId, `${readyEntries.length} new comment(s) imported from GitHub PR`);
|
|
18915
|
+
void playNotificationSound("prComment");
|
|
18852
18916
|
}
|
|
18853
18917
|
updated = true;
|
|
18854
18918
|
}
|
|
@@ -18859,6 +18923,7 @@ var BoardPoller = class {
|
|
|
18859
18923
|
await moveCard(workspaceId, taskId, "done");
|
|
18860
18924
|
await clearCardSession(workspaceId, taskId);
|
|
18861
18925
|
await appendActivityLog(workspaceId, taskId, "PR merged on GitHub \u2192 Done");
|
|
18926
|
+
void playNotificationSound("done");
|
|
18862
18927
|
const boardAfterDone = await loadBoard(workspaceId);
|
|
18863
18928
|
const ownerId = resolveWorktreeOwnerId(taskId, boardAfterDone.cards);
|
|
18864
18929
|
const groupCards = Object.values(boardAfterDone.cards).filter(
|
|
@@ -18876,6 +18941,7 @@ var BoardPoller = class {
|
|
|
18876
18941
|
await moveCard(workspaceId, taskId, "blocked");
|
|
18877
18942
|
await clearCardSession(workspaceId, taskId);
|
|
18878
18943
|
await appendActivityLog(workspaceId, taskId, "PR closed without merging \u2192 Blocked");
|
|
18944
|
+
void playNotificationSound("blocked");
|
|
18879
18945
|
updated = true;
|
|
18880
18946
|
} else if (info2.mergeable === "CONFLICTING") {
|
|
18881
18947
|
if (!card.terminalSessions?.some((ts) => !ts.endedAt)) {
|
|
@@ -18892,6 +18958,7 @@ var BoardPoller = class {
|
|
|
18892
18958
|
await moveCard(workspaceId, taskId, "reopened");
|
|
18893
18959
|
await appendActivityLog(workspaceId, taskId, `${reason} \u2192 Reopened`);
|
|
18894
18960
|
await clearCardSession(workspaceId, taskId);
|
|
18961
|
+
void playNotificationSound("reopened");
|
|
18895
18962
|
updated = true;
|
|
18896
18963
|
}
|
|
18897
18964
|
if (updated) stateHub.broadcastWorkspaceUpdate(workspaceId);
|
|
@@ -19002,10 +19069,10 @@ function buildCodexEffortOverride(effort) {
|
|
|
19002
19069
|
|
|
19003
19070
|
// src/agents/playwright-mcp.ts
|
|
19004
19071
|
init_runtime_config();
|
|
19005
|
-
import { join as
|
|
19072
|
+
import { join as join14 } from "node:path";
|
|
19006
19073
|
var PLAYWRIGHT_MCP_SERVER_NAME = "playwright";
|
|
19007
19074
|
function buildBrowserMcpServer(cardId) {
|
|
19008
|
-
const outputDir =
|
|
19075
|
+
const outputDir = join14(ATTACHMENTS_DIR, cardId);
|
|
19009
19076
|
return {
|
|
19010
19077
|
command: "npx",
|
|
19011
19078
|
args: ["-y", "@playwright/mcp", "--headless", "--isolated", "--output-dir", outputDir],
|
|
@@ -19102,12 +19169,34 @@ function getAvailableAgents() {
|
|
|
19102
19169
|
}
|
|
19103
19170
|
});
|
|
19104
19171
|
}
|
|
19172
|
+
var resolvedCommandCache = /* @__PURE__ */ new Map();
|
|
19173
|
+
function resolveCommandPath(command) {
|
|
19174
|
+
if (process.platform !== "win32") return command;
|
|
19175
|
+
const cached2 = resolvedCommandCache.get(command);
|
|
19176
|
+
if (cached2) return cached2;
|
|
19177
|
+
try {
|
|
19178
|
+
const result = spawnSync4("where.exe", [command], {
|
|
19179
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
19180
|
+
timeout: 5e3,
|
|
19181
|
+
encoding: "utf-8"
|
|
19182
|
+
});
|
|
19183
|
+
if (result.status === 0 && result.stdout) {
|
|
19184
|
+
const first = result.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean)[0];
|
|
19185
|
+
if (first) {
|
|
19186
|
+
resolvedCommandCache.set(command, first);
|
|
19187
|
+
return first;
|
|
19188
|
+
}
|
|
19189
|
+
}
|
|
19190
|
+
} catch {
|
|
19191
|
+
}
|
|
19192
|
+
return command;
|
|
19193
|
+
}
|
|
19105
19194
|
function getAgentCommand(agentId) {
|
|
19106
19195
|
const agent = AGENT_DEFINITIONS.find((a) => a.id === agentId);
|
|
19107
19196
|
if (!agent) {
|
|
19108
19197
|
throw new Error(`Unknown agent: ${agentId}`);
|
|
19109
19198
|
}
|
|
19110
|
-
return agent.command;
|
|
19199
|
+
return resolveCommandPath(agent.command);
|
|
19111
19200
|
}
|
|
19112
19201
|
var READONLY_DISALLOWED_TOOLS = ["Edit", "Write", "NotebookEdit", "Bash"];
|
|
19113
19202
|
function buildAgentArgs(agentId, prompt, ctx = {}) {
|
|
@@ -20389,22 +20478,22 @@ init_workspace_state();
|
|
|
20389
20478
|
init_workspace_state();
|
|
20390
20479
|
|
|
20391
20480
|
// src/daemon/scheduler.ts
|
|
20392
|
-
import { spawn as
|
|
20393
|
-
import {
|
|
20394
|
-
import { unlink as unlink2 } from "node:fs/promises";
|
|
20395
|
-
import { dirname as
|
|
20396
|
-
import { fileURLToPath as
|
|
20481
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
20482
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
20483
|
+
import { cp, link, mkdir as mkdir3, stat as stat2, symlink, unlink as unlink2 } from "node:fs/promises";
|
|
20484
|
+
import { dirname as dirname6, join as join17, resolve as resolve2 } from "node:path";
|
|
20485
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
20397
20486
|
init_api_contract();
|
|
20398
20487
|
init_logger();
|
|
20399
20488
|
|
|
20400
20489
|
// src/core/prompt-resolver.ts
|
|
20401
20490
|
init_logger();
|
|
20402
20491
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
20403
|
-
import { isAbsolute, join as
|
|
20492
|
+
import { isAbsolute, join as join15 } from "node:path";
|
|
20404
20493
|
function resolvePromptText(prompt, repoPath) {
|
|
20405
20494
|
if (!prompt) return "";
|
|
20406
20495
|
if (prompt.source === "inline") return prompt.text;
|
|
20407
|
-
const path2 = isAbsolute(prompt.path) ? prompt.path :
|
|
20496
|
+
const path2 = isAbsolute(prompt.path) ? prompt.path : join15(repoPath, prompt.path);
|
|
20408
20497
|
try {
|
|
20409
20498
|
return readFileSync5(path2, "utf-8");
|
|
20410
20499
|
} catch (err) {
|
|
@@ -20421,7 +20510,7 @@ init_workspace_state();
|
|
|
20421
20510
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
20422
20511
|
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
20423
20512
|
import { readdir, readFile as readFile2, stat, unlink } from "node:fs/promises";
|
|
20424
|
-
import { join as
|
|
20513
|
+
import { join as join16 } from "node:path";
|
|
20425
20514
|
init_runtime_config();
|
|
20426
20515
|
init_api_contract();
|
|
20427
20516
|
init_logger();
|
|
@@ -20579,7 +20668,7 @@ async function runReviewSlot(slot, card, streamId, options, customPrompt) {
|
|
|
20579
20668
|
useIncremental,
|
|
20580
20669
|
diffRef: priorReviewedSha ?? card.baseRef
|
|
20581
20670
|
};
|
|
20582
|
-
const
|
|
20671
|
+
const stat3 = getGitStat(worktreePath, card.baseRef);
|
|
20583
20672
|
const fullDiff = getGitFullDiff(worktreePath, useIncremental ? priorReviewedSha : card.baseRef);
|
|
20584
20673
|
const context = formatPriorComments(card);
|
|
20585
20674
|
let subtaskCards = [];
|
|
@@ -20589,7 +20678,7 @@ async function runReviewSlot(slot, card, streamId, options, customPrompt) {
|
|
|
20589
20678
|
const rawSystemPrompt = buildReviewSlotSystemPrompt(
|
|
20590
20679
|
slot,
|
|
20591
20680
|
card,
|
|
20592
|
-
|
|
20681
|
+
stat3,
|
|
20593
20682
|
fullDiff,
|
|
20594
20683
|
customPrompt,
|
|
20595
20684
|
context.text,
|
|
@@ -20706,7 +20795,7 @@ function getSlotTriggerWord(type) {
|
|
|
20706
20795
|
}
|
|
20707
20796
|
var SCREENSHOT_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp"]);
|
|
20708
20797
|
async function attachBrowserArtifacts(workspaceId, card, result, since) {
|
|
20709
|
-
const dir =
|
|
20798
|
+
const dir = join16(ATTACHMENTS_DIR, card.id);
|
|
20710
20799
|
let entries;
|
|
20711
20800
|
try {
|
|
20712
20801
|
entries = await readdir(dir);
|
|
@@ -20719,7 +20808,7 @@ async function attachBrowserArtifacts(workspaceId, card, result, since) {
|
|
|
20719
20808
|
for (const name of entries) {
|
|
20720
20809
|
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
20721
20810
|
if (!SCREENSHOT_EXTENSIONS.has(ext)) continue;
|
|
20722
|
-
const filePath =
|
|
20811
|
+
const filePath = join16(dir, name);
|
|
20723
20812
|
try {
|
|
20724
20813
|
const info2 = await stat(filePath);
|
|
20725
20814
|
if (!info2.isFile() || info2.mtimeMs < since) continue;
|
|
@@ -20862,6 +20951,7 @@ async function handleReviewSuccess(card, options) {
|
|
|
20862
20951
|
await moveCard(workspaceId, card.id, "ready_for_review");
|
|
20863
20952
|
await appendActivityLog(workspaceId, card.id, "All reviews passed \u2192 moved to Ready for Review");
|
|
20864
20953
|
stateHub.broadcastWorkspaceUpdate(workspaceId);
|
|
20954
|
+
void playNotificationSound("readyForReview");
|
|
20865
20955
|
if (deliveryMode === "off") return;
|
|
20866
20956
|
if (card.type === "subtask") return;
|
|
20867
20957
|
if (deliveryMode === "yolo") {
|
|
@@ -21022,7 +21112,7 @@ ${diffParts.join("\n")}
|
|
|
21022
21112
|
if (newUntracked.length > 0) {
|
|
21023
21113
|
const newFileContents = [];
|
|
21024
21114
|
for (const file of newUntracked) {
|
|
21025
|
-
const content = readFileSafe(
|
|
21115
|
+
const content = readFileSafe(join16(worktreePath, file));
|
|
21026
21116
|
const ext = file.split(".").pop() ?? "";
|
|
21027
21117
|
newFileContents.push(content ? `### ${file}
|
|
21028
21118
|
\`\`\`${ext}
|
|
@@ -21170,16 +21260,16 @@ ${fullDiff}`;
|
|
|
21170
21260
|
return `Large changeset (${fullDiff.length.toLocaleString()} chars). Use \`git diff ${baseRef}...HEAD\` and read individual files to explore.`;
|
|
21171
21261
|
}
|
|
21172
21262
|
var FOLLOWUP_REVIEW_FOCUS = `**This is a follow-up review.** An earlier version of this work already passed review in a prior round \u2014 only the \`## New Feedback\` / \`## Current Iteration\` items triggered this run. Re-review ONLY: (1) whether those items were addressed in the changes below, and (2) whether the new changes introduced a regression or broke a caller (grep callers of anything touched to confirm). Do NOT re-litigate previously-approved code or raise issues unrelated to this round's feedback.`;
|
|
21173
|
-
function renderReviewDiff(
|
|
21263
|
+
function renderReviewDiff(stat3, fullDiff, baseRef, scope) {
|
|
21174
21264
|
if (!scope.useIncremental) {
|
|
21175
21265
|
return `## Changed files
|
|
21176
|
-
${
|
|
21266
|
+
${stat3}
|
|
21177
21267
|
|
|
21178
21268
|
## Diff
|
|
21179
21269
|
${formatDiffBlock(fullDiff, baseRef)}`;
|
|
21180
21270
|
}
|
|
21181
21271
|
return `## Changed files (full changeset vs \`${baseRef}\`, for context)
|
|
21182
|
-
${
|
|
21272
|
+
${stat3}
|
|
21183
21273
|
|
|
21184
21274
|
## Changes since your last review (review THIS)
|
|
21185
21275
|
_The diff below is only what changed since you last reviewed this card. The full changeset is summarised above; run \`git diff ${baseRef}...HEAD\` only if you need it to chase a regression._
|
|
@@ -21219,11 +21309,11 @@ Revise these values rather than rewriting from scratch, unless they no longer re
|
|
|
21219
21309
|
let statSection = "";
|
|
21220
21310
|
if (worktreePath) {
|
|
21221
21311
|
if (fullDiff) {
|
|
21222
|
-
const
|
|
21312
|
+
const stat3 = getGitStat(worktreePath, statBase);
|
|
21223
21313
|
statSection = `
|
|
21224
21314
|
|
|
21225
21315
|
## Current worktree state (vs ${statBase})
|
|
21226
|
-
${
|
|
21316
|
+
${stat3}
|
|
21227
21317
|
|
|
21228
21318
|
## Diff (vs ${statBase})
|
|
21229
21319
|
${formatDiffBlock(fullDiff, statBase, "Git diff")}`;
|
|
@@ -21234,11 +21324,11 @@ ${formatDiffBlock(fullDiff, statBase, "Git diff")}`;
|
|
|
21234
21324
|
|
|
21235
21325
|
This is the initial dev run on this card. The worktree is clean and branched from \`${statBase}\` \u2014 there is no prior diff to inspect. Skip \`git diff\` and start implementing.`;
|
|
21236
21326
|
} else {
|
|
21237
|
-
const
|
|
21327
|
+
const stat3 = getGitStat(worktreePath, statBase);
|
|
21238
21328
|
statSection = `
|
|
21239
21329
|
|
|
21240
21330
|
## Current worktree state (vs ${statBase})
|
|
21241
|
-
${
|
|
21331
|
+
${stat3}`;
|
|
21242
21332
|
}
|
|
21243
21333
|
}
|
|
21244
21334
|
const descAttachNote = (card.descriptionAttachments?.length ?? 0) > 0 ? `
|
|
@@ -21347,12 +21437,12 @@ ${systemPrompt.trim()}`);
|
|
|
21347
21437
|
${effectiveGitInstructions}`);
|
|
21348
21438
|
return { text: parts.join("\n\n"), files: context.files };
|
|
21349
21439
|
}
|
|
21350
|
-
function buildReviewSlotSystemPrompt(slot, card,
|
|
21440
|
+
function buildReviewSlotSystemPrompt(slot, card, stat3, fullDiff, customPrompt, priorContext, scope, secrets = [], systemPrompt, autoCommit = true, subtaskCards = [], browserEnabled = false) {
|
|
21351
21441
|
if (slot.type === "orch") {
|
|
21352
21442
|
return buildOrchSystemPrompt(
|
|
21353
21443
|
slot,
|
|
21354
21444
|
card,
|
|
21355
|
-
|
|
21445
|
+
stat3,
|
|
21356
21446
|
fullDiff,
|
|
21357
21447
|
customPrompt,
|
|
21358
21448
|
priorContext,
|
|
@@ -21366,7 +21456,7 @@ function buildReviewSlotSystemPrompt(slot, card, stat2, fullDiff, customPrompt,
|
|
|
21366
21456
|
return buildMergedReviewSystemPrompt(
|
|
21367
21457
|
slot,
|
|
21368
21458
|
card,
|
|
21369
|
-
|
|
21459
|
+
stat3,
|
|
21370
21460
|
fullDiff,
|
|
21371
21461
|
customPrompt,
|
|
21372
21462
|
priorContext,
|
|
@@ -21376,7 +21466,7 @@ function buildReviewSlotSystemPrompt(slot, card, stat2, fullDiff, customPrompt,
|
|
|
21376
21466
|
browserEnabled
|
|
21377
21467
|
);
|
|
21378
21468
|
}
|
|
21379
|
-
function buildMergedReviewSystemPrompt(slot, card,
|
|
21469
|
+
function buildMergedReviewSystemPrompt(slot, card, stat3, fullDiff, customPrompt, priorContext, scope, secrets, systemPrompt, browserEnabled = false) {
|
|
21380
21470
|
const custom = customPrompt.trim() ? `
|
|
21381
21471
|
|
|
21382
21472
|
## Project-specific instructions
|
|
@@ -21412,7 +21502,7 @@ When you set status "fail" (reopening for rework), you can right-size the **tier
|
|
|
21412
21502
|
## Task to review
|
|
21413
21503
|
${card.description ?? ""}${descAttachSection}${priorContext}
|
|
21414
21504
|
|
|
21415
|
-
${renderReviewDiff(
|
|
21505
|
+
${renderReviewDiff(stat3, fullDiff, card.baseRef, scope)}
|
|
21416
21506
|
|
|
21417
21507
|
${ITERATION_SCOPING_NOTE}
|
|
21418
21508
|
|
|
@@ -21440,7 +21530,7 @@ ${secretsSection}` : ""}${projectContext}${runningAppSection}${levelAdjustSectio
|
|
|
21440
21530
|
|
|
21441
21531
|
This MCP call is required \u2014 without it the pipeline has no record of your verdict.`;
|
|
21442
21532
|
}
|
|
21443
|
-
function buildOrchSystemPrompt(slot, card,
|
|
21533
|
+
function buildOrchSystemPrompt(slot, card, stat3, fullDiff, customPrompt, priorContext, scope, secrets, systemPrompt, autoCommit = true, subtaskCards = []) {
|
|
21444
21534
|
const commentType = slotCommentType(slot);
|
|
21445
21535
|
const custom = customPrompt.trim() ? `
|
|
21446
21536
|
|
|
@@ -21486,7 +21576,7 @@ ${card.description}
|
|
|
21486
21576
|
|
|
21487
21577
|
${subtasksSection}${priorContext}
|
|
21488
21578
|
|
|
21489
|
-
${renderReviewDiff(
|
|
21579
|
+
${renderReviewDiff(stat3, fullDiff, card.baseRef, scope)}
|
|
21490
21580
|
|
|
21491
21581
|
${ITERATION_SCOPING_NOTE}
|
|
21492
21582
|
|
|
@@ -22018,19 +22108,33 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
22018
22108
|
const { filesToCopy, installCommand } = projectConfig.worktreeSetup;
|
|
22019
22109
|
if (filesToCopy.length > 0) {
|
|
22020
22110
|
const copied = [];
|
|
22021
|
-
|
|
22022
|
-
|
|
22111
|
+
const linked = [];
|
|
22112
|
+
for (const entry of filesToCopy) {
|
|
22113
|
+
const src = join17(repoPath, entry.path);
|
|
22023
22114
|
if (!existsSync10(src)) continue;
|
|
22024
|
-
const dst =
|
|
22025
|
-
|
|
22115
|
+
const dst = join17(worktree.path, entry.path);
|
|
22116
|
+
await mkdir3(dirname6(dst), { recursive: true });
|
|
22026
22117
|
try {
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22118
|
+
if (entry.symlink) {
|
|
22119
|
+
await shareIntoWorktree(src, dst);
|
|
22120
|
+
linked.push(entry.path);
|
|
22121
|
+
} else {
|
|
22122
|
+
await cp(src, dst, { recursive: true, dereference: true });
|
|
22123
|
+
copied.push(entry.path);
|
|
22124
|
+
}
|
|
22125
|
+
} catch (err) {
|
|
22126
|
+
logger.warn(
|
|
22127
|
+
{ err },
|
|
22128
|
+
`[scheduler] worktree setup: failed to ${entry.symlink ? "link" : "copy"} ${entry.path}`
|
|
22129
|
+
);
|
|
22030
22130
|
}
|
|
22031
22131
|
}
|
|
22032
|
-
|
|
22033
|
-
|
|
22132
|
+
const summary = [
|
|
22133
|
+
copied.length ? `Copied: ${copied.join(", ")}` : "",
|
|
22134
|
+
linked.length ? `Linked: ${linked.join(", ")}` : ""
|
|
22135
|
+
].filter(Boolean).join(" \xB7 ");
|
|
22136
|
+
if (summary) {
|
|
22137
|
+
await appendActivityLog(workspaceId, taskId, `Worktree setup \u2014 ${summary}`);
|
|
22034
22138
|
stateHub.broadcastWorkspaceUpdate(workspaceId);
|
|
22035
22139
|
}
|
|
22036
22140
|
}
|
|
@@ -22038,7 +22142,8 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
22038
22142
|
await appendActivityLog(workspaceId, taskId, `Running: ${installCommand.trim()}`);
|
|
22039
22143
|
stateHub.broadcastWorkspaceUpdate(workspaceId);
|
|
22040
22144
|
await new Promise((resolve5) => {
|
|
22041
|
-
const
|
|
22145
|
+
const [shell, shellArgs] = getShellInvocation(installCommand.trim());
|
|
22146
|
+
const proc = spawn6(shell, shellArgs, {
|
|
22042
22147
|
cwd: worktree.path,
|
|
22043
22148
|
stdio: "ignore",
|
|
22044
22149
|
env: { ...process.env, REPO_PATH: repoPath }
|
|
@@ -22242,6 +22347,7 @@ ${devSystemPromptResult.text}`;
|
|
|
22242
22347
|
if (!hasReviewSlots) {
|
|
22243
22348
|
await moveCard(workspaceId, taskId, "ready_for_review");
|
|
22244
22349
|
await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 moved to Ready for Review");
|
|
22350
|
+
void playNotificationSound("readyForReview");
|
|
22245
22351
|
} else {
|
|
22246
22352
|
await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 AI review starting");
|
|
22247
22353
|
}
|
|
@@ -22268,6 +22374,7 @@ ${devSystemPromptResult.text}`;
|
|
|
22268
22374
|
);
|
|
22269
22375
|
}
|
|
22270
22376
|
await moveCard(workspaceId, taskId, destination);
|
|
22377
|
+
void playNotificationSound(destination === "blocked" ? "blocked" : "reopened");
|
|
22271
22378
|
}
|
|
22272
22379
|
stateHub.broadcastWorkspaceUpdate(workspaceId);
|
|
22273
22380
|
this.options.onTaskCompleted(taskId);
|
|
@@ -22469,6 +22576,7 @@ ${devSystemPromptResult.text}`;
|
|
|
22469
22576
|
if (!hookHasReview) {
|
|
22470
22577
|
await moveCard(workspaceId, taskId, "ready_for_review");
|
|
22471
22578
|
await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 moved to Ready for Review");
|
|
22579
|
+
void playNotificationSound("readyForReview");
|
|
22472
22580
|
} else {
|
|
22473
22581
|
await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 AI review starting");
|
|
22474
22582
|
}
|
|
@@ -22567,9 +22675,25 @@ ${devSystemPromptResult.text}`;
|
|
|
22567
22675
|
this.isShuttingDown = true;
|
|
22568
22676
|
}
|
|
22569
22677
|
};
|
|
22678
|
+
async function shareIntoWorktree(src, dst) {
|
|
22679
|
+
const isDir = (await stat2(src)).isDirectory();
|
|
22680
|
+
if (process.platform !== "win32") {
|
|
22681
|
+
await symlink(src, dst, isDir ? "dir" : "file");
|
|
22682
|
+
return;
|
|
22683
|
+
}
|
|
22684
|
+
if (isDir) {
|
|
22685
|
+
await symlink(src, dst, "junction");
|
|
22686
|
+
return;
|
|
22687
|
+
}
|
|
22688
|
+
try {
|
|
22689
|
+
await link(src, dst);
|
|
22690
|
+
} catch {
|
|
22691
|
+
await cp(src, dst, { recursive: true, dereference: true });
|
|
22692
|
+
}
|
|
22693
|
+
}
|
|
22570
22694
|
function getMcpServerPath() {
|
|
22571
|
-
const thisFile =
|
|
22572
|
-
const thisDir =
|
|
22695
|
+
const thisFile = fileURLToPath4(import.meta.url);
|
|
22696
|
+
const thisDir = dirname6(thisFile);
|
|
22573
22697
|
const isDev = thisFile.endsWith(".ts");
|
|
22574
22698
|
if (isDev) {
|
|
22575
22699
|
const projectRoot = resolve2(thisDir, "../..");
|
|
@@ -26406,13 +26530,13 @@ init_runtime_config();
|
|
|
26406
26530
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
26407
26531
|
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "node:fs";
|
|
26408
26532
|
import { homedir as homedir4 } from "node:os";
|
|
26409
|
-
import { dirname as
|
|
26533
|
+
import { dirname as dirname7, join as join19, resolve as resolve3 } from "node:path";
|
|
26410
26534
|
|
|
26411
26535
|
// src/core/terminal-apps.ts
|
|
26412
26536
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
26413
26537
|
import { existsSync as existsSync11 } from "node:fs";
|
|
26414
26538
|
import { homedir as homedir3 } from "node:os";
|
|
26415
|
-
import { join as
|
|
26539
|
+
import { join as join18 } from "node:path";
|
|
26416
26540
|
var MACOS_TERMINALS = [
|
|
26417
26541
|
{ bundle: "Terminal", label: "Terminal" },
|
|
26418
26542
|
{ bundle: "iTerm", label: "iTerm" },
|
|
@@ -26444,12 +26568,13 @@ function appExists(bundle) {
|
|
|
26444
26568
|
`/Applications/${bundle}.app`,
|
|
26445
26569
|
`/System/Applications/${bundle}.app`,
|
|
26446
26570
|
`/System/Applications/Utilities/${bundle}.app`,
|
|
26447
|
-
|
|
26571
|
+
join18(homedir3(), "Applications", `${bundle}.app`)
|
|
26448
26572
|
];
|
|
26449
26573
|
return paths.some((p2) => existsSync11(p2));
|
|
26450
26574
|
}
|
|
26451
26575
|
function binaryExists(bin) {
|
|
26452
|
-
const
|
|
26576
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
26577
|
+
const r = spawnSync7(finder, [bin], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" });
|
|
26453
26578
|
return r.status === 0 && r.stdout.trim().length > 0;
|
|
26454
26579
|
}
|
|
26455
26580
|
function listTerminalApps() {
|
|
@@ -26520,15 +26645,15 @@ var openTerminal = async (path2) => {
|
|
|
26520
26645
|
};
|
|
26521
26646
|
var listDir = async (path2, includeFiles, showHidden) => {
|
|
26522
26647
|
let target = resolve3(path2 || homedir4());
|
|
26523
|
-
while (target !==
|
|
26524
|
-
target =
|
|
26648
|
+
while (target !== dirname7(target) && !(existsSync12(target) && statSync(target).isDirectory())) {
|
|
26649
|
+
target = dirname7(target);
|
|
26525
26650
|
}
|
|
26526
|
-
const parent =
|
|
26651
|
+
const parent = dirname7(target);
|
|
26527
26652
|
const visible = (name) => showHidden || !name.startsWith(".");
|
|
26528
26653
|
try {
|
|
26529
26654
|
const entries = readdirSync2(target, { withFileTypes: true });
|
|
26530
|
-
const dirs = entries.filter((e) => e.isDirectory() && visible(e.name)).map((e) => ({ name: e.name, path:
|
|
26531
|
-
const files = includeFiles ? entries.filter((e) => e.isFile() && visible(e.name)).map((e) => ({ name: e.name, path:
|
|
26655
|
+
const dirs = entries.filter((e) => e.isDirectory() && visible(e.name)).map((e) => ({ name: e.name, path: join19(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name));
|
|
26656
|
+
const files = includeFiles ? entries.filter((e) => e.isFile() && visible(e.name)).map((e) => ({ name: e.name, path: join19(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name)) : [];
|
|
26532
26657
|
return { current: target, parent: parent !== target ? parent : null, dirs, files };
|
|
26533
26658
|
} catch {
|
|
26534
26659
|
return { current: target, parent: parent !== target ? parent : null, dirs: [], files: [] };
|
|
@@ -26890,8 +27015,8 @@ var checkProjectPath = async (repoPath) => {
|
|
|
26890
27015
|
return { valid: false, isGitRepo: false, error: null, name: null, branch: null, remote: null, branches: [] };
|
|
26891
27016
|
const { statSync: statSync2 } = await import("node:fs");
|
|
26892
27017
|
try {
|
|
26893
|
-
const
|
|
26894
|
-
if (!
|
|
27018
|
+
const stat3 = statSync2(repoPath);
|
|
27019
|
+
if (!stat3.isDirectory())
|
|
26895
27020
|
return {
|
|
26896
27021
|
valid: false,
|
|
26897
27022
|
isGitRepo: false,
|
|
@@ -26947,8 +27072,8 @@ var checkProjectPath = async (repoPath) => {
|
|
|
26947
27072
|
var addProject = async (repoPath, initialConfig) => {
|
|
26948
27073
|
const { statSync: statSync2 } = await import("node:fs");
|
|
26949
27074
|
try {
|
|
26950
|
-
const
|
|
26951
|
-
if (!
|
|
27075
|
+
const stat3 = statSync2(repoPath);
|
|
27076
|
+
if (!stat3.isDirectory()) throw new Error("Not a directory");
|
|
26952
27077
|
} catch {
|
|
26953
27078
|
throw BadRequestError(`Path does not exist: ${repoPath}`);
|
|
26954
27079
|
}
|
|
@@ -26984,7 +27109,7 @@ var removeProject = async (workspaceId) => {
|
|
|
26984
27109
|
const cards = boardCards;
|
|
26985
27110
|
enqueueCleanup2(async () => {
|
|
26986
27111
|
const { rm: rm3 } = await import("node:fs/promises");
|
|
26987
|
-
const { join:
|
|
27112
|
+
const { join: join23 } = await import("node:path");
|
|
26988
27113
|
for (const [cardId, card] of Object.entries(cards)) {
|
|
26989
27114
|
if (resolveWorktreeOwnerId(cardId, cards) === cardId) {
|
|
26990
27115
|
await removeWorktreeAsync(cardId, repoPath, card.branchName).catch((err) => {
|
|
@@ -26992,11 +27117,11 @@ var removeProject = async (workspaceId) => {
|
|
|
26992
27117
|
});
|
|
26993
27118
|
}
|
|
26994
27119
|
}
|
|
26995
|
-
await rm3(
|
|
27120
|
+
await rm3(join23(WORKSPACES_DIR, workspaceId), { recursive: true, force: true }).catch((err) => {
|
|
26996
27121
|
logger.warn(`[cleanup:project:${workspaceId}] workspace dir failed: ${String(err)}`);
|
|
26997
27122
|
});
|
|
26998
27123
|
for (const cardId of Object.keys(cards)) {
|
|
26999
|
-
await rm3(
|
|
27124
|
+
await rm3(join23(ATTACHMENTS_DIR, cardId), { recursive: true, force: true }).catch(() => {
|
|
27000
27125
|
});
|
|
27001
27126
|
}
|
|
27002
27127
|
logger.info(`[cleanup:project:${workspaceId}] done`);
|
|
@@ -27285,15 +27410,15 @@ import { z as z14 } from "zod";
|
|
|
27285
27410
|
init_runtime_config();
|
|
27286
27411
|
|
|
27287
27412
|
// src/tunnel/cloudflare-setup.ts
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
import { execFile as execFile9, spawn as spawn6 } from "node:child_process";
|
|
27291
|
-
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile3, access, unlink as unlink4 } from "node:fs/promises";
|
|
27413
|
+
import { execFile as execFile9, spawn as spawn7 } from "node:child_process";
|
|
27414
|
+
import { mkdir as mkdir4, writeFile as writeFile3, readFile as readFile3, access, unlink as unlink4 } from "node:fs/promises";
|
|
27292
27415
|
import { homedir as homedir5 } from "node:os";
|
|
27293
|
-
import { join as
|
|
27416
|
+
import { join as join20 } from "node:path";
|
|
27294
27417
|
import { promisify as promisify9 } from "node:util";
|
|
27418
|
+
init_runtime_config();
|
|
27419
|
+
init_logger();
|
|
27295
27420
|
var execFileAsync7 = promisify9(execFile9);
|
|
27296
|
-
var CLOUDFLARED_DIR =
|
|
27421
|
+
var CLOUDFLARED_DIR = join20(homedir5(), ".cloudflared");
|
|
27297
27422
|
async function checkCloudflaredInstalled() {
|
|
27298
27423
|
try {
|
|
27299
27424
|
const { stdout } = await execFileAsync7("cloudflared", ["--version"]);
|
|
@@ -27305,7 +27430,7 @@ async function checkCloudflaredInstalled() {
|
|
|
27305
27430
|
}
|
|
27306
27431
|
async function checkCloudflaredAuth() {
|
|
27307
27432
|
try {
|
|
27308
|
-
await access(
|
|
27433
|
+
await access(join20(CLOUDFLARED_DIR, "cert.pem"));
|
|
27309
27434
|
return true;
|
|
27310
27435
|
} catch {
|
|
27311
27436
|
return false;
|
|
@@ -27316,12 +27441,12 @@ async function openCloudflaredLogin(force = false) {
|
|
|
27316
27441
|
if (alreadyLoggedIn && !force) return { alreadyLoggedIn: true };
|
|
27317
27442
|
if (force) {
|
|
27318
27443
|
try {
|
|
27319
|
-
await unlink4(
|
|
27444
|
+
await unlink4(join20(CLOUDFLARED_DIR, "cert.pem"));
|
|
27320
27445
|
} catch {
|
|
27321
27446
|
}
|
|
27322
27447
|
}
|
|
27323
27448
|
return new Promise((resolve5) => {
|
|
27324
|
-
const proc =
|
|
27449
|
+
const proc = spawn7("cloudflared", ["tunnel", "login"], {
|
|
27325
27450
|
stdio: ["ignore", "pipe", "pipe"]
|
|
27326
27451
|
});
|
|
27327
27452
|
let buffer = "";
|
|
@@ -27334,7 +27459,8 @@ async function openCloudflaredLogin(force = false) {
|
|
|
27334
27459
|
resolved = true;
|
|
27335
27460
|
const loginUrl = match2[1].trim();
|
|
27336
27461
|
logger.info(`[cloudflared-login] Auth URL: ${loginUrl}`);
|
|
27337
|
-
|
|
27462
|
+
open_default(loginUrl).catch(() => {
|
|
27463
|
+
});
|
|
27338
27464
|
proc.unref();
|
|
27339
27465
|
resolve5({ alreadyLoggedIn: false, loginUrl });
|
|
27340
27466
|
}
|
|
@@ -27384,7 +27510,7 @@ async function createTunnel(tunnelName) {
|
|
|
27384
27510
|
}
|
|
27385
27511
|
}
|
|
27386
27512
|
async function writeTunnelConfig(tunnelId, tunnelName, domain) {
|
|
27387
|
-
const credentialsFile =
|
|
27513
|
+
const credentialsFile = join20(CLOUDFLARED_DIR, `${tunnelId}.json`);
|
|
27388
27514
|
const config = [
|
|
27389
27515
|
`tunnel: ${tunnelId}`,
|
|
27390
27516
|
`credentials-file: ${credentialsFile}`,
|
|
@@ -27394,13 +27520,13 @@ async function writeTunnelConfig(tunnelId, tunnelName, domain) {
|
|
|
27394
27520
|
` service: http://127.0.0.1:${DEFAULT_PORT}`,
|
|
27395
27521
|
` - service: http_status:404`
|
|
27396
27522
|
].join("\n");
|
|
27397
|
-
await
|
|
27398
|
-
await writeFile3(
|
|
27523
|
+
await mkdir4(CLOUDFLARED_DIR, { recursive: true });
|
|
27524
|
+
await writeFile3(join20(CLOUDFLARED_DIR, "config.yml"), config, "utf-8");
|
|
27399
27525
|
logger.info(`[tunnel-setup] Wrote ~/.cloudflared/config.yml for tunnel ${tunnelName}`);
|
|
27400
27526
|
}
|
|
27401
27527
|
async function readTunnelConfig() {
|
|
27402
27528
|
try {
|
|
27403
|
-
const raw2 = await readFile3(
|
|
27529
|
+
const raw2 = await readFile3(join20(CLOUDFLARED_DIR, "config.yml"), "utf-8");
|
|
27404
27530
|
const tunnelMatch = raw2.match(/^tunnel:\s*(.+)$/m);
|
|
27405
27531
|
const hostnameMatch = raw2.match(/hostname:\s*(.+)$/m);
|
|
27406
27532
|
return {
|
|
@@ -27449,9 +27575,9 @@ var resetTunnel = async () => {
|
|
|
27449
27575
|
await updateGlobalConfig({ tunnelId: void 0, tunnelDomain: void 0, autoStartTunnel: false });
|
|
27450
27576
|
const { unlink: unlink5 } = await import("node:fs/promises");
|
|
27451
27577
|
const { homedir: homedir6 } = await import("node:os");
|
|
27452
|
-
const { join:
|
|
27578
|
+
const { join: join23 } = await import("node:path");
|
|
27453
27579
|
try {
|
|
27454
|
-
await unlink5(
|
|
27580
|
+
await unlink5(join23(homedir6(), ".cloudflared", "config.yml"));
|
|
27455
27581
|
} catch {
|
|
27456
27582
|
}
|
|
27457
27583
|
};
|
|
@@ -27483,8 +27609,8 @@ import { z as z15 } from "zod";
|
|
|
27483
27609
|
// src/api/services/workflows-service.ts
|
|
27484
27610
|
init_workspace_state();
|
|
27485
27611
|
import { existsSync as existsSync13 } from "node:fs";
|
|
27486
|
-
import { mkdir as
|
|
27487
|
-
import { dirname as
|
|
27612
|
+
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
27613
|
+
import { dirname as dirname8, isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
|
|
27488
27614
|
var resolvePromptPath = async (workspaceId, requestedPath) => {
|
|
27489
27615
|
const workspaces = await listWorkspaces();
|
|
27490
27616
|
const ws = workspaces.find((w2) => w2.workspaceId === workspaceId);
|
|
@@ -27521,7 +27647,7 @@ var deleteWorkflow = async (workspaceId, workflowId) => {
|
|
|
27521
27647
|
};
|
|
27522
27648
|
var writePromptFile = async (workspaceId, path2, content) => {
|
|
27523
27649
|
const targetPath = await resolvePromptPath(workspaceId, path2);
|
|
27524
|
-
await
|
|
27650
|
+
await mkdir5(dirname8(targetPath), { recursive: true });
|
|
27525
27651
|
await writeFile4(targetPath, content, "utf-8");
|
|
27526
27652
|
return { path: path2 };
|
|
27527
27653
|
};
|
|
@@ -27745,8 +27871,8 @@ var RuntimeStateHub = class {
|
|
|
27745
27871
|
// src/core/update-check.ts
|
|
27746
27872
|
init_paths();
|
|
27747
27873
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27748
|
-
import { join as
|
|
27749
|
-
var CACHE_FILE =
|
|
27874
|
+
import { join as join21 } from "node:path";
|
|
27875
|
+
var CACHE_FILE = join21(WHIPPED_HOME_DIR, "update-check.json");
|
|
27750
27876
|
var REGISTRY_URL = "https://registry.npmjs.org/whipped/latest";
|
|
27751
27877
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
27752
27878
|
function readCache() {
|
|
@@ -27797,7 +27923,7 @@ function scheduleUpdateChecks(currentVersion, onUpdate) {
|
|
|
27797
27923
|
}
|
|
27798
27924
|
|
|
27799
27925
|
// src/server/runtime-server.ts
|
|
27800
|
-
var __dirname2 =
|
|
27926
|
+
var __dirname2 = fileURLToPath5(new URL(".", import.meta.url));
|
|
27801
27927
|
async function cleanupStaleTasks(workspaceId, hub) {
|
|
27802
27928
|
const board = await loadBoard(workspaceId);
|
|
27803
27929
|
const now = Date.now();
|
|
@@ -27851,8 +27977,8 @@ async function createRuntimeServer(options) {
|
|
|
27851
27977
|
const runTerminalListeners = /* @__PURE__ */ new Map();
|
|
27852
27978
|
function startRun(workspaceId, cardId, command, cwd) {
|
|
27853
27979
|
stopRun(workspaceId);
|
|
27854
|
-
const shell =
|
|
27855
|
-
const pty = nodePty2.spawn(shell,
|
|
27980
|
+
const [shell, shellArgs] = getShellInvocation(command);
|
|
27981
|
+
const pty = nodePty2.spawn(shell, shellArgs, {
|
|
27856
27982
|
name: "xterm-256color",
|
|
27857
27983
|
cols: 120,
|
|
27858
27984
|
rows: 40,
|
|
@@ -27894,6 +28020,7 @@ async function createRuntimeServer(options) {
|
|
|
27894
28020
|
session.status = "error";
|
|
27895
28021
|
session.errorMessage = `Process exited with code ${exitCode}`;
|
|
27896
28022
|
stateHub.broadcastRunSessionChange(workspaceId, cardId, "error", session.errorMessage);
|
|
28023
|
+
void playNotificationSound("runError");
|
|
27897
28024
|
}
|
|
27898
28025
|
});
|
|
27899
28026
|
}
|
|
@@ -28032,8 +28159,8 @@ async function createRuntimeServer(options) {
|
|
|
28032
28159
|
};
|
|
28033
28160
|
}
|
|
28034
28161
|
const apiApp = createApiApp(createContext());
|
|
28035
|
-
const webUiDistPath =
|
|
28036
|
-
const webUiIndexPath =
|
|
28162
|
+
const webUiDistPath = join22(__dirname2, "web-ui");
|
|
28163
|
+
const webUiIndexPath = join22(webUiDistPath, "index.html");
|
|
28037
28164
|
const hasWebUi = existsSync14(webUiIndexPath);
|
|
28038
28165
|
const httpServer = createServer(async (req, res) => {
|
|
28039
28166
|
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
@@ -28267,7 +28394,7 @@ async function createRuntimeServer(options) {
|
|
|
28267
28394
|
res.end("Bad request");
|
|
28268
28395
|
return;
|
|
28269
28396
|
}
|
|
28270
|
-
const filePath =
|
|
28397
|
+
const filePath = join22(ATTACHMENTS_DIR, cardId, filename);
|
|
28271
28398
|
const { readFile: readFile5 } = await import("node:fs/promises");
|
|
28272
28399
|
try {
|
|
28273
28400
|
const data = await readFile5(filePath);
|
|
@@ -28342,7 +28469,7 @@ async function createRuntimeServer(options) {
|
|
|
28342
28469
|
return;
|
|
28343
28470
|
}
|
|
28344
28471
|
if (hasWebUi) {
|
|
28345
|
-
const filePath = url.pathname === "/" || !url.pathname.includes(".") ? webUiIndexPath :
|
|
28472
|
+
const filePath = url.pathname === "/" || !url.pathname.includes(".") ? webUiIndexPath : join22(webUiDistPath, url.pathname);
|
|
28346
28473
|
if (existsSync14(filePath)) {
|
|
28347
28474
|
const content = readFileSync8(filePath);
|
|
28348
28475
|
res.writeHead(200, { "Content-Type": getContentType(filePath) });
|
|
@@ -28561,7 +28688,7 @@ process.on("uncaughtException", (err) => {
|
|
|
28561
28688
|
if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
|
|
28562
28689
|
throw err;
|
|
28563
28690
|
});
|
|
28564
|
-
var VERSION9 = true ? "0.
|
|
28691
|
+
var VERSION9 = true ? "0.7.0" : "0.0.0-dev";
|
|
28565
28692
|
async function isPortAvailable(port, host) {
|
|
28566
28693
|
return new Promise((resolve5) => {
|
|
28567
28694
|
const probe = createServer2();
|