whipped 0.7.0 → 0.8.1

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 CHANGED
@@ -13562,7 +13562,10 @@ async function runLogs(options) {
13562
13562
  process.exit(1);
13563
13563
  }
13564
13564
  if (options.follow) {
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]];
13565
+ const [cmd, args] = process.platform === "win32" ? [
13566
+ "powershell",
13567
+ ["-NoProfile", "-Command", `Get-Content -Path '${path2}' -Tail ${options.lines} -Wait`]
13568
+ ] : ["tail", ["-f", "-n", String(options.lines), path2]];
13566
13569
  const child = spawn2(cmd, args, {
13567
13570
  stdio: "inherit"
13568
13571
  });
@@ -13624,7 +13627,7 @@ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:
13624
13627
  import { createServer } from "node:http";
13625
13628
  import { join as join22 } from "node:path";
13626
13629
  import { fileURLToPath as fileURLToPath5 } from "node:url";
13627
- import * as nodePty2 from "node-pty";
13630
+ import * as nodePty3 from "node-pty";
13628
13631
 
13629
13632
  // node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
13630
13633
  var import_stream = __toESM(require_stream(), 1);
@@ -20478,11 +20481,12 @@ init_workspace_state();
20478
20481
  init_workspace_state();
20479
20482
 
20480
20483
  // src/daemon/scheduler.ts
20481
- import { spawn as spawn6 } from "node:child_process";
20484
+ var import_tree_kill2 = __toESM(require_tree_kill(), 1);
20482
20485
  import { existsSync as existsSync10 } from "node:fs";
20483
20486
  import { cp, link, mkdir as mkdir3, stat as stat2, symlink, unlink as unlink2 } from "node:fs/promises";
20484
20487
  import { dirname as dirname6, join as join17, resolve as resolve2 } from "node:path";
20485
20488
  import { fileURLToPath as fileURLToPath4 } from "node:url";
20489
+ import * as nodePty2 from "node-pty";
20486
20490
  init_api_contract();
20487
20491
  init_logger();
20488
20492
 
@@ -21789,6 +21793,12 @@ var TaskScheduler = class {
21789
21793
  planPhaseManuallyStopped = /* @__PURE__ */ new Set();
21790
21794
  // Individual review stream IDs stopped by a manual stopTask() call.
21791
21795
  manuallyStoppedStreams = /* @__PURE__ */ new Set();
21796
+ // Worktree-setup install commands currently running, keyed by taskId — so stopTask()
21797
+ // can kill the install PTY before the dev agent has even started.
21798
+ runningInstalls = /* @__PURE__ */ new Map();
21799
+ // Tasks whose install was manually stopped — signals the install runner to reset the
21800
+ // task to its initial state instead of proceeding to the agent.
21801
+ manuallyStoppedInstalls = /* @__PURE__ */ new Set();
21792
21802
  // Shared worktree IDs currently in use by a dev agent — prevents sibling cards from
21793
21803
  // running concurrently in the same worktree directory.
21794
21804
  runningSharedWorktrees = /* @__PURE__ */ new Set();
@@ -22139,28 +22149,81 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
22139
22149
  }
22140
22150
  }
22141
22151
  if (installCommand.trim()) {
22142
- await appendActivityLog(workspaceId, taskId, `Running: ${installCommand.trim()}`);
22152
+ const installCmd = installCommand.trim();
22153
+ await appendActivityLog(workspaceId, taskId, `Running: ${installCmd}`);
22154
+ const installStartedAt = Date.now();
22155
+ const installStreamId = `${taskId}-install-${installStartedAt}`;
22156
+ await appendTerminalSession(workspaceId, taskId, {
22157
+ streamId: installStreamId,
22158
+ type: "install",
22159
+ startedAt: installStartedAt,
22160
+ state: "running"
22161
+ });
22143
22162
  stateHub.broadcastWorkspaceUpdate(workspaceId);
22144
- await new Promise((resolve5) => {
22145
- const [shell, shellArgs] = getShellInvocation(installCommand.trim());
22146
- const proc = spawn6(shell, shellArgs, {
22163
+ let installBuffer = "";
22164
+ const emitInstall = (data) => {
22165
+ installBuffer += data;
22166
+ stateHub.broadcastTerminalOutput(workspaceId, installStreamId, data);
22167
+ };
22168
+ emitInstall(`\x1B[1;36m$ ${installCmd}\x1B[0m\r
22169
+ `);
22170
+ const exitCode = await new Promise((resolveExit) => {
22171
+ const [shell, shellArgs] = getShellInvocation(installCmd);
22172
+ const proc = nodePty2.spawn(shell, shellArgs, {
22173
+ name: "xterm-256color",
22174
+ cols: 220,
22175
+ rows: 50,
22147
22176
  cwd: worktree.path,
22148
- stdio: "ignore",
22149
- env: { ...process.env, REPO_PATH: repoPath }
22150
- });
22151
- proc.on("close", (code) => {
22152
- if (code !== 0) {
22153
- logger.error(`[scheduler] Install command failed (code ${code}) for task ${taskId}`);
22154
- void appendActivityLog(
22155
- workspaceId,
22156
- taskId,
22157
- `Install command failed (code ${code}) \u2014 proceeding anyway`
22158
- );
22159
- }
22160
- resolve5();
22177
+ env: { ...process.env, REPO_PATH: repoPath, TERM: "xterm-256color" }
22161
22178
  });
22179
+ this.runningInstalls.set(taskId, proc);
22180
+ proc.onData(emitInstall);
22181
+ proc.onExit(({ exitCode: exitCode2 }) => resolveExit(exitCode2 ?? 0));
22162
22182
  });
22163
- await appendActivityLog(workspaceId, taskId, "Install complete");
22183
+ this.runningInstalls.delete(taskId);
22184
+ this.setRecentBuffer(installStreamId, installBuffer);
22185
+ if (this.isShuttingDown) {
22186
+ await saveTerminalBuffer(workspaceId, installStreamId, installBuffer);
22187
+ return;
22188
+ }
22189
+ if (this.manuallyStoppedInstalls.delete(taskId)) {
22190
+ emitInstall("\r\n\x1B[1;33mInstall stopped \u2014 task reset\x1B[0m\r\n");
22191
+ await saveTerminalBuffer(workspaceId, installStreamId, installBuffer);
22192
+ await endTerminalSession(workspaceId, taskId, installStreamId, Date.now(), "stopped");
22193
+ if (hasSharedWorktree) this.runningSharedWorktrees.delete(effectiveWorktreeId);
22194
+ await removeWorktreeAsync(effectiveWorktreeId, repoPath, worktree.branch);
22195
+ await clearCardSession(workspaceId, taskId);
22196
+ await updateCard(workspaceId, taskId, { readyForDev: false });
22197
+ const stoppedBoard = await loadBoard(workspaceId);
22198
+ if (stoppedBoard.cards[taskId]?.columnId === "in_progress") {
22199
+ await moveCard(workspaceId, taskId, "todo");
22200
+ await appendActivityLog(workspaceId, taskId, "Moved back to Todo");
22201
+ }
22202
+ stateHub.broadcastWorkspaceUpdate(workspaceId);
22203
+ return;
22204
+ }
22205
+ if (exitCode !== 0) {
22206
+ logger.error(`[scheduler] Install command failed (code ${exitCode}) for task ${taskId}`);
22207
+ emitInstall(`\r
22208
+ \x1B[1;31mInstall command failed (code ${exitCode}) \u2014 proceeding anyway\x1B[0m\r
22209
+ `);
22210
+ await appendActivityLog(
22211
+ workspaceId,
22212
+ taskId,
22213
+ `Install command failed (code ${exitCode}) \u2014 proceeding anyway`
22214
+ );
22215
+ } else {
22216
+ emitInstall("\r\n\x1B[1;32mInstall complete\x1B[0m\r\n");
22217
+ await appendActivityLog(workspaceId, taskId, "Install complete");
22218
+ }
22219
+ await saveTerminalBuffer(workspaceId, installStreamId, installBuffer);
22220
+ await endTerminalSession(
22221
+ workspaceId,
22222
+ taskId,
22223
+ installStreamId,
22224
+ Date.now(),
22225
+ exitCode === 0 ? "completed" : "failed"
22226
+ );
22164
22227
  stateHub.broadcastWorkspaceUpdate(workspaceId);
22165
22228
  }
22166
22229
  }
@@ -22412,6 +22475,15 @@ ${devSystemPromptResult.text}`;
22412
22475
  }
22413
22476
  }
22414
22477
  stopTask(taskId) {
22478
+ const installProc = this.runningInstalls.get(taskId);
22479
+ if (installProc) {
22480
+ logger.info(`[scheduler] stopTask: install running \u2014 stopping task ${taskId}`);
22481
+ this.manuallyStoppedInstalls.add(taskId);
22482
+ this.runningInstalls.delete(taskId);
22483
+ killInstallProcess(installProc);
22484
+ void appendActivityLog(this.options.workspaceId, taskId, "Install stopped manually");
22485
+ return;
22486
+ }
22415
22487
  const task = this.running.get(taskId);
22416
22488
  if (task) {
22417
22489
  logger.info(`[scheduler] stopTask: dev agent running \u2014 stopping task ${taskId}`);
@@ -22667,6 +22739,10 @@ ${devSystemPromptResult.text}`;
22667
22739
  for (const [taskId] of this.running) {
22668
22740
  this.stopTask(taskId);
22669
22741
  }
22742
+ for (const proc of this.runningInstalls.values()) {
22743
+ killInstallProcess(proc);
22744
+ }
22745
+ this.runningInstalls.clear();
22670
22746
  this.stopAssistantAgent();
22671
22747
  }
22672
22748
  // Call before stopAll() during graceful shutdown so onExit handlers bail out
@@ -22675,6 +22751,12 @@ ${devSystemPromptResult.text}`;
22675
22751
  this.isShuttingDown = true;
22676
22752
  }
22677
22753
  };
22754
+ function killInstallProcess(proc) {
22755
+ try {
22756
+ (0, import_tree_kill2.default)(proc.pid, "SIGKILL");
22757
+ } catch {
22758
+ }
22759
+ }
22678
22760
  async function shareIntoWorktree(src, dst) {
22679
22761
  const isDir = (await stat2(src)).isDirectory();
22680
22762
  if (process.platform !== "win32") {
@@ -27978,7 +28060,7 @@ async function createRuntimeServer(options) {
27978
28060
  function startRun(workspaceId, cardId, command, cwd) {
27979
28061
  stopRun(workspaceId);
27980
28062
  const [shell, shellArgs] = getShellInvocation(command);
27981
- const pty = nodePty2.spawn(shell, shellArgs, {
28063
+ const pty = nodePty3.spawn(shell, shellArgs, {
27982
28064
  name: "xterm-256color",
27983
28065
  cols: 120,
27984
28066
  rows: 40,
@@ -28688,7 +28770,7 @@ process.on("uncaughtException", (err) => {
28688
28770
  if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
28689
28771
  throw err;
28690
28772
  });
28691
- var VERSION9 = true ? "0.7.0" : "0.0.0-dev";
28773
+ var VERSION9 = true ? "0.8.1" : "0.0.0-dev";
28692
28774
  async function isPortAvailable(port, host) {
28693
28775
  return new Promise((resolve5) => {
28694
28776
  const probe = createServer2();
@@ -50181,6 +50181,9 @@ const AGENT_DISPLAY = {
50181
50181
  dotColor: "bg-[#fb8147]"
50182
50182
  }
50183
50183
  };
50184
+ const SESSION_TYPE_LABELS = {
50185
+ install: "Install"
50186
+ };
50184
50187
  function formatElapsed(sec) {
50185
50188
  return `${Math.floor(sec / 60)}m ${(sec % 60).toString().padStart(2, "0")}s`;
50186
50189
  }
@@ -50511,7 +50514,7 @@ function WorkflowPipeline({
50511
50514
  /* Collapsed: icon-only timeline centered */
50512
50515
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center pb-4 gap-0", children: sessions.length > 0 ? sessions.map((session, idx) => {
50513
50516
  var _a3;
50514
- const slotName = ((_a3 = workflowSlots == null ? void 0 : workflowSlots.find((s16) => s16.id === session.type)) == null ? void 0 : _a3.name) ?? session.type;
50517
+ const slotName = ((_a3 = workflowSlots == null ? void 0 : workflowSlots.find((s16) => s16.id === session.type)) == null ? void 0 : _a3.name) ?? SESSION_TYPE_LABELS[session.type] ?? session.type;
50515
50518
  const status = sessionStatus(session);
50516
50519
  const isFocused = activeStreamId === session.streamId;
50517
50520
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
@@ -50546,7 +50549,7 @@ function WorkflowPipeline({
50546
50549
  /* Expanded: full rows */
50547
50550
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col px-[18px] pb-4 max-h-72 overflow-y-auto", children: sessions.length > 0 ? sessions.map((session, idx) => {
50548
50551
  var _a3;
50549
- const slotName = ((_a3 = workflowSlots == null ? void 0 : workflowSlots.find((s16) => s16.id === session.type)) == null ? void 0 : _a3.name) ?? session.type;
50552
+ const slotName = ((_a3 = workflowSlots == null ? void 0 : workflowSlots.find((s16) => s16.id === session.type)) == null ? void 0 : _a3.name) ?? SESSION_TYPE_LABELS[session.type] ?? session.type;
50550
50553
  const status = sessionStatus(session);
50551
50554
  const duration2 = slotDuration(session.startedAt, session.endedAt);
50552
50555
  const isFocused = activeStreamId === session.streamId;
@@ -76894,8 +76897,8 @@ function KanbanBoard({
76894
76897
  const { trigger: stopAll } = useWrite((api) => api("cards/stop-all").POST());
76895
76898
  const { trigger: resumeAll } = useWrite((api) => api("cards/resume-all").POST());
76896
76899
  const currentBranch = (branchesData == null ? void 0 : branchesData.defaultBranch) ?? "";
76897
- const openCard = (id) => navigate(`/${encodeURIComponent(workspaceId)}/board/${encodeURIComponent(id)}`, { replace: true });
76898
- const closeCard = () => navigate(`/${encodeURIComponent(workspaceId)}/board`, { replace: true });
76900
+ const openCard = (id) => navigate(`/${encodeURIComponent(workspaceId)}/board/${encodeURIComponent(id)}`);
76901
+ const closeCard = () => navigate(`/${encodeURIComponent(workspaceId)}/board`);
76899
76902
  const handleCardDelete = (card) => {
76900
76903
  var _a4;
76901
76904
  ConfirmDialog_default.show({
@@ -77279,7 +77282,7 @@ function BoardPage({ onOpenAgent }) {
77279
77282
  if (projects.length === 0) return;
77280
77283
  if (projects.some((p) => p.workspaceId === workspaceId)) return;
77281
77284
  const id = (layout ? firstSortedProjectId(layout, projects) : null) ?? projects[0].workspaceId;
77282
- navigate(`/${encodeURIComponent(id)}/board`, { replace: true });
77285
+ navigate(`/${encodeURIComponent(id)}/board`);
77283
77286
  }, [projectList, layout, workspaceId, navigate]);
77284
77287
  const switchProject = (wsId) => {
77285
77288
  navigate(`/${encodeURIComponent(wsId)}/board`);
@@ -77290,7 +77293,7 @@ function BoardPage({ onOpenAgent }) {
77290
77293
  if (wsId !== workspaceId) return;
77291
77294
  const remaining = projects.filter((p) => p.workspaceId !== wsId);
77292
77295
  const nextId = (layout ? firstSortedProjectId(layout, remaining) : null) ?? ((_a3 = remaining[0]) == null ? void 0 : _a3.workspaceId);
77293
- navigate(nextId ? `/${encodeURIComponent(nextId)}/board` : "/", { replace: true });
77296
+ navigate(nextId ? `/${encodeURIComponent(nextId)}/board` : "/");
77294
77297
  };
77295
77298
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-full overflow-hidden", children: [
77296
77299
  /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { className: "w-[220px] shrink-0 flex flex-col bg-[#141418] border-r border-[#2a2a35]", children: [
@@ -78079,7 +78082,7 @@ function RecurringAgentsPage() {
78079
78082
  }, [list2.trigger]);
78080
78083
  reactExports.useEffect(() => {
78081
78084
  if (agentId || agents.length === 0) return;
78082
- navigate(`/${encodeURIComponent(wsId)}/recurring-agents/${encodeURIComponent(agents[0].id)}`, { replace: true });
78085
+ navigate(`/${encodeURIComponent(wsId)}/recurring-agents/${encodeURIComponent(agents[0].id)}`);
78083
78086
  }, [agentId, agents, wsId, navigate]);
78084
78087
  const select = (id) => navigate(`/${encodeURIComponent(wsId)}/recurring-agents/${encodeURIComponent(id)}`);
78085
78088
  const handleToggle = async (enabled) => {
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>whipped</title>
8
- <script type="module" crossorigin src="/assets/index-BDiIsuhZ.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BMFVAmy4.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CRXPsGTP.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whipped",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Autonomous AI agent kanban board for Claude, Codex, Opencode, and Cursor.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/nxnom/whipped#readme",