@vibedeckx/linux-x64 0.3.27 → 0.3.28

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.
Files changed (2) hide show
  1. package/dist/bin.js +282 -27
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -207173,7 +207173,7 @@ function sessionMilestoneForTurnEnd(opts) {
207173
207173
  workflow_run_id: null,
207174
207174
  created_at: opts.createdAt
207175
207175
  };
207176
- if (opts.outcome === "completed") {
207176
+ if (opts.outcome === "completed" || opts.outcome === "completed_with_pending_tasks") {
207177
207177
  return {
207178
207178
  ...base,
207179
207179
  id: sessionResultReadyId(opts.sessionId, opts.entryIndex),
@@ -207312,6 +207312,25 @@ var ClaudeCodeProvider = class {
207312
207312
  formatUserInput(content, _sessionId) {
207313
207313
  return serializeUserInput(content);
207314
207314
  }
207315
+ /**
207316
+ * `stop_task` control_request — the documented Agent SDK primitive for
207317
+ * killing one background task, verified live against claude 2.1.238: the CLI
207318
+ * answers `control_response { subtype: "success" }`, empties its task list
207319
+ * and the process really dies.
207320
+ *
207321
+ * `request_id` is namespaced by task id so a response could be correlated;
207322
+ * nothing reads control_response today, and it does not need to — the
207323
+ * authoritative `background_tasks_changed` snapshot that follows is what
207324
+ * updates the ledger. An older CLI that does not know the subtype answers an
207325
+ * error and simply changes nothing.
207326
+ */
207327
+ formatStopBackgroundTask(taskId, _sessionId) {
207328
+ return JSON.stringify({
207329
+ type: "control_request",
207330
+ request_id: `stop_task-${taskId}`,
207331
+ request: { subtype: "stop_task", task_id: taskId }
207332
+ }) + "\n";
207333
+ }
207315
207334
  // Lifecycle hooks are no-ops for Claude (stateless per-session)
207316
207335
  onSessionCreated(_sessionId, _permissionMode) {
207317
207336
  }
@@ -229427,7 +229446,7 @@ function isResidentProcessInScope(candidate, scope) {
229427
229446
  return candidate.projectId === scope.projectId && candidate.branch === scope.branch;
229428
229447
  }
229429
229448
  function isIdleResidentProcess(candidate) {
229430
- return candidate.processAlive && !candidate.dormant && candidate.status !== "running" && candidate.backgroundTaskCount === 0;
229449
+ return candidate.processAlive && !candidate.dormant && candidate.status !== "running" && !candidate.backgroundTasksProtect;
229431
229450
  }
229432
229451
  function pickIdleResidentEvictionCandidate(candidates, scope) {
229433
229452
  return candidates.filter((candidate) => !scope || isResidentProcessInScope(candidate, scope)).filter(isIdleResidentProcess).sort((a, b2) => a.lastActiveAt - b2.lastActiveAt)[0] ?? null;
@@ -229449,7 +229468,12 @@ var ResidentProcessLimitError = class extends Error {
229449
229468
 
229450
229469
  // src/turn-completion.ts
229451
229470
  var COMPLETION_GRACE_MS = 1500;
229471
+ var PARK_TIMEOUT_MS = 20 * 60 * 1e3;
229452
229472
  var TurnCompletionLedger = class {
229473
+ constructor(parkTimeoutMs = PARK_TIMEOUT_MS) {
229474
+ this.parkTimeoutMs = parkTimeoutMs;
229475
+ }
229476
+ parkTimeoutMs;
229453
229477
  /** Live background tasks by harness task_id (same id may restart). */
229454
229478
  tasks = /* @__PURE__ */ new Map();
229455
229479
  /** Held completion candidate — the latest success result, if any. */
@@ -229462,6 +229486,23 @@ var TurnCompletionLedger = class {
229462
229486
  * grace delay (the common case).
229463
229487
  */
229464
229488
  sawBackgroundActivity = false;
229489
+ /**
229490
+ * When the held candidate was parked behind live background tasks — the
229491
+ * moment the agent stopped working and only tasks kept the turn open. The
229492
+ * park deadline counts from here, not from when a task started: the number
229493
+ * that matters to the user is "how long since the agent answered".
229494
+ */
229495
+ parkedSince = null;
229496
+ /** Task ids the user vouched for; they stop counting toward the deadline. */
229497
+ sanctioned = /* @__PURE__ */ new Set();
229498
+ /**
229499
+ * Whether a park deadline already expired and closed the turn. Live tasks
229500
+ * normally shield the session from being reclaimed — hibernating would kill
229501
+ * a real build and the auto-resume that reads it — but that shield must not
229502
+ * outlast the deadline, or one stuck task pins a resident process slot
229503
+ * forever and new sessions on the branch get turned away.
229504
+ */
229505
+ parkDeadlineExpired = false;
229465
229506
  get pendingTaskCount() {
229466
229507
  return this.tasks.size;
229467
229508
  }
@@ -229470,17 +229511,61 @@ var TurnCompletionLedger = class {
229470
229511
  }
229471
229512
  /** Live tasks in first-seen order — the payload the UI renders. */
229472
229513
  get backgroundTasks() {
229473
- return [...this.tasks.values()];
229514
+ return [...this.tasks.values()].map(
229515
+ (task) => this.sanctioned.has(task.taskId) ? { ...task, sanctioned: true } : task
229516
+ );
229517
+ }
229518
+ /**
229519
+ * When the parked turn will be committed anyway, or null if nothing is
229520
+ * parked or every live task has been vouched for. Timer-free by design: the
229521
+ * caller re-reads this after each mutation and syncs its own timer, so the
229522
+ * ledger stays a pure state machine.
229523
+ */
229524
+ get parkDeadlineAt() {
229525
+ if (this.pending === null || this.parkedSince === null) return null;
229526
+ const allVouchedFor = [...this.tasks.keys()].every((id) => this.sanctioned.has(id));
229527
+ return allVouchedFor ? null : this.parkedSince + this.parkTimeoutMs;
229528
+ }
229529
+ /**
229530
+ * The user vouched for a task: stop counting it toward the deadline. This
229531
+ * restores the original behavior for that task — wait for it, let the
229532
+ * auto-resume close the turn — but now as an explicit choice.
229533
+ */
229534
+ sanction(taskId) {
229535
+ if (this.tasks.has(taskId)) this.sanctioned.add(taskId);
229536
+ }
229537
+ /**
229538
+ * The deadline expired: commit the parked candidate. Deliberately uses the
229539
+ * ORIGINAL payload — its duration/cost/tokens describe the turn the agent
229540
+ * actually ran, not the time spent waiting on a stuck task.
229541
+ */
229542
+ parkDeadlineElapsed() {
229543
+ if (this.pending === null) return { kind: "none" };
229544
+ this.parkDeadlineExpired = true;
229545
+ return this.commitHeld();
229546
+ }
229547
+ /**
229548
+ * Whether live background tasks should still shield this session from
229549
+ * resident-process reclamation. True while they are plausibly doing real
229550
+ * work; false once the deadline judged them anomalous — unless the user
229551
+ * vouched for every one of them, which restores the shield along with the
229552
+ * waiting behavior it protects.
229553
+ */
229554
+ get backgroundTasksProtectSession() {
229555
+ if (this.tasks.size === 0) return false;
229556
+ if (!this.parkDeadlineExpired) return true;
229557
+ return [...this.tasks.keys()].every((id) => this.sanctioned.has(id));
229474
229558
  }
229475
229559
  taskStarted(task, now3) {
229476
229560
  this.upsert(task, this.tasks.get(task.taskId), now3);
229477
229561
  this.sawBackgroundActivity = true;
229478
- return this.rearmIfHeld();
229562
+ return this.rearmIfHeld(now3);
229479
229563
  }
229480
- taskFinished(taskId) {
229564
+ taskFinished(taskId, now3) {
229481
229565
  this.tasks.delete(taskId);
229566
+ this.sanctioned.delete(taskId);
229482
229567
  this.sawBackgroundActivity = true;
229483
- return this.rearmIfHeld();
229568
+ return this.rearmIfHeld(now3);
229484
229569
  }
229485
229570
  /** Authoritative snapshot from `system/background_tasks_changed`. */
229486
229571
  taskListChanged(tasks, now3) {
@@ -229489,8 +229574,11 @@ var TurnCompletionLedger = class {
229489
229574
  for (const task of tasks) {
229490
229575
  this.upsert(task, previous.get(task.taskId), now3);
229491
229576
  }
229577
+ for (const id of this.sanctioned) {
229578
+ if (!this.tasks.has(id)) this.sanctioned.delete(id);
229579
+ }
229492
229580
  if (tasks.length > 0) this.sawBackgroundActivity = true;
229493
- return this.rearmIfHeld();
229581
+ return this.rearmIfHeld(now3);
229494
229582
  }
229495
229583
  /**
229496
229584
  * The process emitted turn activity: if a completion was held, it was an
@@ -229502,8 +229590,10 @@ var TurnCompletionLedger = class {
229502
229590
  * the race against the grace window.
229503
229591
  */
229504
229592
  noteTurnActivity() {
229593
+ this.parkDeadlineExpired = false;
229505
229594
  if (this.pending === null) return { kind: "none" };
229506
229595
  this.pending = null;
229596
+ this.parkedSince = null;
229507
229597
  this.generation++;
229508
229598
  return { kind: "cancel" };
229509
229599
  }
@@ -229519,9 +229609,11 @@ var TurnCompletionLedger = class {
229519
229609
  this.sawBackgroundActivity = false;
229520
229610
  return this.noteTurnActivity();
229521
229611
  }
229522
- successResult(payload) {
229612
+ successResult(payload, now3) {
229523
229613
  this.generation++;
229524
229614
  if (this.tasks.size > 0) {
229615
+ this.parkedSince = now3;
229616
+ this.parkDeadlineExpired = false;
229525
229617
  this.pending = payload;
229526
229618
  return { kind: "cancel" };
229527
229619
  }
@@ -229535,6 +229627,7 @@ var TurnCompletionLedger = class {
229535
229627
  errorResult() {
229536
229628
  if (this.pending === null) return { kind: "none" };
229537
229629
  this.pending = null;
229630
+ this.parkedSince = null;
229538
229631
  this.generation++;
229539
229632
  return { kind: "cancel" };
229540
229633
  }
@@ -229560,7 +229653,10 @@ var TurnCompletionLedger = class {
229560
229653
  /** Full reset (fresh spawn / stop / hibernate / agent switch). */
229561
229654
  reset() {
229562
229655
  this.tasks.clear();
229656
+ this.sanctioned.clear();
229563
229657
  this.pending = null;
229658
+ this.parkedSince = null;
229659
+ this.parkDeadlineExpired = false;
229564
229660
  this.generation++;
229565
229661
  this.sawBackgroundActivity = false;
229566
229662
  }
@@ -229569,10 +229665,13 @@ var TurnCompletionLedger = class {
229569
229665
  * have no resume behind it), so they delay the commit rather than cancel
229570
229666
  * it — and while tasks are still live the candidate stays parked with no
229571
229667
  * timer at all (only an empty set can complete a turn). */
229572
- rearmIfHeld() {
229668
+ rearmIfHeld(now3) {
229573
229669
  if (this.pending === null) return { kind: "none" };
229574
229670
  this.generation++;
229575
- if (this.tasks.size > 0) return { kind: "cancel" };
229671
+ if (this.tasks.size > 0) {
229672
+ this.parkedSince ??= now3;
229673
+ return { kind: "cancel" };
229674
+ }
229576
229675
  return { kind: "schedule", generation: this.generation };
229577
229676
  }
229578
229677
  /**
@@ -229593,6 +229692,7 @@ var TurnCompletionLedger = class {
229593
229692
  commitHeld() {
229594
229693
  const payload = this.pending;
229595
229694
  this.pending = null;
229695
+ this.parkedSince = null;
229596
229696
  this.generation++;
229597
229697
  return { kind: "commit", payload };
229598
229698
  }
@@ -229673,10 +229773,13 @@ var AgentSessionManager = class {
229673
229773
  retentionDeleting = /* @__PURE__ */ new Set();
229674
229774
  /** Grace window before committing a held completion (injectable for tests). */
229675
229775
  completionGraceMs;
229776
+ /** Bound on a parked completion (injectable for tests). */
229777
+ parkTimeoutMs;
229676
229778
  workflowSuppressionCheck = null;
229677
229779
  constructor(storage2, opts) {
229678
229780
  this.storage = storage2;
229679
229781
  this.completionGraceMs = opts?.completionGraceMs ?? COMPLETION_GRACE_MS;
229782
+ this.parkTimeoutMs = opts?.parkTimeoutMs ?? PARK_TIMEOUT_MS;
229680
229783
  }
229681
229784
  async resolveSessionWorktreePath(session, legacyProjectPath) {
229682
229785
  if (!session.workspaceCheckoutId) {
@@ -229951,7 +230054,7 @@ var AgentSessionManager = class {
229951
230054
  processAlive: this.isProcessAlive(session),
229952
230055
  status: session.status,
229953
230056
  dormant: session.dormant,
229954
- backgroundTaskCount: session.completion.pendingTaskCount,
230057
+ backgroundTasksProtect: session.completion.backgroundTasksProtectSession,
229955
230058
  lastActiveAt: session.lastActiveAt,
229956
230059
  projectId: session.projectId,
229957
230060
  branch: session.branch
@@ -230099,8 +230202,9 @@ var AgentSessionManager = class {
230099
230202
  crossRemoteMcp: opts.crossRemoteMcp,
230100
230203
  agentType,
230101
230204
  model,
230102
- completion: new TurnCompletionLedger(),
230205
+ completion: new TurnCompletionLedger(this.parkTimeoutMs),
230103
230206
  graceTimer: null,
230207
+ parkTimer: null,
230104
230208
  eventChain: Promise.resolve(),
230105
230209
  bgSpawnHintsThisTurn: 0,
230106
230210
  taskStartedThisTurn: 0,
@@ -230357,11 +230461,78 @@ var AgentSessionManager = class {
230357
230461
  } else if (action.kind === "schedule") {
230358
230462
  this.armGraceTimer(session, action.generation);
230359
230463
  }
230464
+ this.syncParkTimer(session);
230465
+ }
230466
+ /**
230467
+ * Keep the park timer in step with the ledger's deadline.
230468
+ *
230469
+ * Driven by ledger STATE rather than by an action kind, because the deadline
230470
+ * survives across many actions (every task event returns `cancel` while a
230471
+ * completion stays parked) and can also be lifted without any action at all
230472
+ * when the user vouches for the last unvouched task. Re-reading the state
230473
+ * after each mutation is the only way the two can't drift.
230474
+ */
230475
+ syncParkTimer(session) {
230476
+ const deadlineAt = session.completion.parkDeadlineAt;
230477
+ if (deadlineAt === null) {
230478
+ if (session.parkTimer) {
230479
+ clearTimeout(session.parkTimer);
230480
+ session.parkTimer = null;
230481
+ }
230482
+ return;
230483
+ }
230484
+ if (session.parkTimer) return;
230485
+ const timer = setTimeout(() => {
230486
+ session.parkTimer = null;
230487
+ this.enqueueSessionWork(session, async () => {
230488
+ const action = session.completion.parkDeadlineElapsed();
230489
+ if (action.kind !== "commit") return;
230490
+ console.log(
230491
+ `[AgentSession] parked completion exceeded ${this.parkTimeoutMs}ms with ${session.completion.pendingTaskCount} background task(s) still running \u2014 committing the turn anyway (session=${session.id})`
230492
+ );
230493
+ await this.commitCompletion(session, action.payload, "completed_with_pending_tasks");
230494
+ this.broadcastBackgroundTasks(session);
230495
+ }, "completion-park-deadline");
230496
+ }, Math.max(0, deadlineAt - Date.now()));
230497
+ timer.unref?.();
230498
+ session.parkTimer = timer;
230499
+ }
230500
+ /**
230501
+ * The user vouched for a background task: it stops counting toward the park
230502
+ * deadline, restoring the original wait-for-auto-resume behavior for that
230503
+ * task alone — now as an explicit choice rather than a silent assumption.
230504
+ */
230505
+ /**
230506
+ * Ask the agent to stop one background task.
230507
+ *
230508
+ * Returns "unsupported" for agents with no such primitive (Codex), so the
230509
+ * caller can say "stop the session instead" rather than showing a dead
230510
+ * button. On success nothing is updated here: the CLI's own
230511
+ * `background_tasks_changed` snapshot drains the ledger, which then commits
230512
+ * the parked turn through the normal path.
230513
+ */
230514
+ stopBackgroundTask(sessionId, taskId) {
230515
+ const session = this.sessions.get(sessionId);
230516
+ if (!session?.process?.stdin) return "not_found";
230517
+ const frame = getProvider(session.agentType).formatStopBackgroundTask?.(taskId, sessionId);
230518
+ if (!frame) return "unsupported";
230519
+ session.process.stdin.write(frame);
230520
+ console.log(`[AgentSession] stop_task sent for ${taskId} (session=${sessionId})`);
230521
+ return "ok";
230522
+ }
230523
+ sanctionBackgroundTask(sessionId, taskId) {
230524
+ const session = this.sessions.get(sessionId);
230525
+ if (!session) return false;
230526
+ session.completion.sanction(taskId);
230527
+ this.syncParkTimer(session);
230528
+ this.broadcastBackgroundTasks(session);
230529
+ return true;
230360
230530
  }
230361
230531
  /** Discard all turn-completion state (fresh spawn / stop / hibernate / agent switch). */
230362
230532
  resetCompletion(session) {
230363
230533
  this.clearGraceTimer(session);
230364
230534
  session.completion.reset();
230535
+ this.syncParkTimer(session);
230365
230536
  this.broadcastBackgroundTasks(session);
230366
230537
  }
230367
230538
  /**
@@ -230376,21 +230547,32 @@ var AgentSessionManager = class {
230376
230547
  * stateless (no patch application, no ordering assumptions).
230377
230548
  */
230378
230549
  broadcastBackgroundTasks(session) {
230379
- this.broadcastRaw(session.id, {
230550
+ this.broadcastRaw(session.id, this.backgroundTasksMessage(session));
230551
+ }
230552
+ /**
230553
+ * Reported rather than inferred client-side: whether a single task can be
230554
+ * stopped is a property of the agent (Claude Code has `stop_task`, Codex has
230555
+ * nothing equivalent), and the server is the only side that knows. A client
230556
+ * guessing from the agent type would drift the day Codex gains one.
230557
+ */
230558
+ backgroundTasksMessage(session) {
230559
+ return {
230380
230560
  backgroundTasks: {
230381
230561
  tasks: session.completion.backgroundTasks,
230382
- turnParked: session.completion.hasPendingCompletion
230562
+ turnParked: session.completion.hasPendingCompletion,
230563
+ parkDeadlineAt: session.completion.parkDeadlineAt,
230564
+ canStopTasks: !!getProvider(session.agentType).formatStopBackgroundTask
230383
230565
  }
230384
- });
230566
+ };
230385
230567
  }
230386
- async commitCompletion(session, payload) {
230568
+ async commitCompletion(session, payload, outcome = "completed") {
230387
230569
  const sessionId = session.id;
230388
230570
  console.log(`[AgentSession] taskCompleted: sessionId=${sessionId}, eventBus=${!!this.eventBus}, projectId=${session.projectId}, branch=${session.branch}`);
230389
230571
  const completedAt = Date.now();
230390
230572
  if (!session.skipDb) {
230391
230573
  await this.storage.agentSessions.markCompleted(sessionId, completedAt);
230392
230574
  }
230393
- const turnEndEntryIndex = await this.endActiveTurn(session, "completed");
230575
+ const turnEndEntryIndex = await this.endActiveTurn(session, outcome);
230394
230576
  this.broadcastBackgroundTasks(session);
230395
230577
  const summaryText = extractLastAssistantText(session.store.entries);
230396
230578
  this.broadcastRaw(sessionId, {
@@ -230582,7 +230764,7 @@ var AgentSessionManager = class {
230582
230764
  console.log(`[AgentSession] Background task started: ${event.taskId} (${event.taskType ?? "?"}) \u2014 ${session.completion.pendingTaskCount} pending in ${sessionId}`);
230583
230765
  break;
230584
230766
  case "task_finished":
230585
- this.applyCompletionTimerAction(session, session.completion.taskFinished(event.taskId));
230767
+ this.applyCompletionTimerAction(session, session.completion.taskFinished(event.taskId, timestamp));
230586
230768
  this.broadcastBackgroundTasks(session);
230587
230769
  console.log(`[AgentSession] Background task finished: ${event.taskId} (${event.status ?? "?"}) \u2014 ${session.completion.pendingTaskCount} pending in ${sessionId}`);
230588
230770
  break;
@@ -230652,7 +230834,7 @@ var AgentSessionManager = class {
230652
230834
  cost_usd: event.cost_usd,
230653
230835
  input_tokens: event.input_tokens,
230654
230836
  output_tokens: event.output_tokens
230655
- });
230837
+ }, timestamp);
230656
230838
  if (action.kind === "commit") {
230657
230839
  await this.commitCompletion(session, action.payload);
230658
230840
  } else {
@@ -230995,12 +231177,7 @@ var AgentSessionManager = class {
230995
231177
  ws.send(JSON.stringify({ Ready: true, historyEpoch: session.historyEpoch }));
230996
231178
  const statusPatch = ConversationPatch.updateStatus(session.status);
230997
231179
  ws.send(JSON.stringify({ JsonPatch: statusPatch }));
230998
- const tasksMsg = {
230999
- backgroundTasks: {
231000
- tasks: session.completion.backgroundTasks,
231001
- turnParked: session.completion.hasPendingCompletion
231002
- }
231003
- };
231180
+ const tasksMsg = this.backgroundTasksMessage(session);
231004
231181
  ws.send(JSON.stringify(tasksMsg));
231005
231182
  return () => {
231006
231183
  session.subscribers.delete(ws);
@@ -231800,8 +231977,9 @@ var AgentSessionManager = class {
231800
231977
  permissionMode,
231801
231978
  agentType: dbSession.agent_type || "claude-code",
231802
231979
  model: dbSession.model ?? null,
231803
- completion: new TurnCompletionLedger(),
231980
+ completion: new TurnCompletionLedger(this.parkTimeoutMs),
231804
231981
  graceTimer: null,
231982
+ parkTimer: null,
231805
231983
  eventChain: Promise.resolve(),
231806
231984
  bgSpawnHintsThisTurn: 0,
231807
231985
  taskStartedThisTurn: 0,
@@ -231992,8 +232170,9 @@ var AgentSessionManager = class {
231992
232170
  permissionMode,
231993
232171
  agentType,
231994
232172
  model,
231995
- completion: new TurnCompletionLedger(),
232173
+ completion: new TurnCompletionLedger(this.parkTimeoutMs),
231996
232174
  graceTimer: null,
232175
+ parkTimer: null,
231997
232176
  eventChain: Promise.resolve(),
231998
232177
  bgSpawnHintsThisTurn: 0,
231999
232178
  taskStartedThisTurn: 0,
@@ -248753,6 +248932,68 @@ var routes11 = async (fastify2) => {
248753
248932
  });
248754
248933
  }
248755
248934
  );
248935
+ fastify2.post(
248936
+ "/api/agent-sessions/:sessionId/background-tasks/:taskId/keep",
248937
+ async (req, reply) => {
248938
+ const userId = requireUserFacingUserId(req, reply);
248939
+ if (userId === null) return;
248940
+ const { sessionId, taskId } = req.params;
248941
+ if (sessionId.startsWith("remote-")) {
248942
+ const remoteInfo = await getAuthorizedRemoteSessionInfo(sessionId, userId);
248943
+ if (!remoteInfo) {
248944
+ return reply.code(404).send({ error: "Remote session not found" });
248945
+ }
248946
+ const result = await proxyAuto(
248947
+ remoteInfo.remoteServerId,
248948
+ "POST",
248949
+ `/api/agent-sessions/${remoteInfo.remoteSessionId}/background-tasks/${encodeURIComponent(taskId)}/keep`,
248950
+ {}
248951
+ );
248952
+ return reply.code(proxyStatus(result)).send(result.data);
248953
+ }
248954
+ const row = await fastify2.storage.agentSessions.getById(sessionId);
248955
+ if (!row || !await fastify2.storage.projects.getById(row.project_id, userId)) {
248956
+ return reply.code(404).send({ error: "Session not found" });
248957
+ }
248958
+ if (!fastify2.agentSessionManager.sanctionBackgroundTask(sessionId, taskId)) {
248959
+ return reply.code(404).send({ error: "Session not running" });
248960
+ }
248961
+ return reply.code(200).send({ success: true });
248962
+ }
248963
+ );
248964
+ fastify2.post(
248965
+ "/api/agent-sessions/:sessionId/background-tasks/:taskId/stop",
248966
+ async (req, reply) => {
248967
+ const userId = requireUserFacingUserId(req, reply);
248968
+ if (userId === null) return;
248969
+ const { sessionId, taskId } = req.params;
248970
+ if (sessionId.startsWith("remote-")) {
248971
+ const remoteInfo = await getAuthorizedRemoteSessionInfo(sessionId, userId);
248972
+ if (!remoteInfo) {
248973
+ return reply.code(404).send({ error: "Remote session not found" });
248974
+ }
248975
+ const result = await proxyAuto(
248976
+ remoteInfo.remoteServerId,
248977
+ "POST",
248978
+ `/api/agent-sessions/${remoteInfo.remoteSessionId}/background-tasks/${encodeURIComponent(taskId)}/stop`,
248979
+ {}
248980
+ );
248981
+ return reply.code(proxyStatus(result)).send(result.data);
248982
+ }
248983
+ const row = await fastify2.storage.agentSessions.getById(sessionId);
248984
+ if (!row || !await fastify2.storage.projects.getById(row.project_id, userId)) {
248985
+ return reply.code(404).send({ error: "Session not found" });
248986
+ }
248987
+ const outcome = fastify2.agentSessionManager.stopBackgroundTask(sessionId, taskId);
248988
+ if (outcome === "not_found") {
248989
+ return reply.code(404).send({ error: "Session not running" });
248990
+ }
248991
+ if (outcome === "unsupported") {
248992
+ return reply.code(501).send({ error: "This agent cannot stop a single background task \u2014 stop the session instead" });
248993
+ }
248994
+ return reply.code(200).send({ success: true });
248995
+ }
248996
+ );
248756
248997
  fastify2.post(
248757
248998
  "/api/agent-sessions/:sessionId/branch",
248758
248999
  async (req, reply) => {
@@ -251770,6 +252011,11 @@ var routes23 = async (fastify2) => {
251770
252011
  try {
251771
252012
  const message = JSON.parse(data.toString());
251772
252013
  if (message.type === "input" || message.type === "resize") {
252014
+ if (message.type === "resize") {
252015
+ console.log(
252016
+ `[WebSocket] resize ${processId} \u2192 ${message.cols}x${message.rows} ip=${req.ip} ua=${req.headers["user-agent"] ?? "?"}`
252017
+ );
252018
+ }
251773
252019
  handle.handleInput(message);
251774
252020
  }
251775
252021
  } catch (error48) {
@@ -251845,6 +252091,9 @@ var routes23 = async (fastify2) => {
251845
252091
  } else if (msg.type === "input") {
251846
252092
  handleInputMap.get(msg.processId)?.({ type: "input", data: msg.data });
251847
252093
  } else if (msg.type === "resize") {
252094
+ console.log(
252095
+ `[ExecutorMux] resize ${msg.processId} \u2192 ${msg.cols}x${msg.rows} ip=${req.ip} ua=${req.headers["user-agent"] ?? "?"}`
252096
+ );
251848
252097
  handleInputMap.get(msg.processId)?.({ type: "resize", cols: msg.cols, rows: msg.rows });
251849
252098
  }
251850
252099
  } catch (error48) {
@@ -260532,6 +260781,12 @@ var WORKER_CAPABILITIES = {
260532
260781
  "http:POST /api/agent-sessions/:param/restart": { since: "0.2.0", summary: "\u91CD\u542F\u4F1A\u8BDD\u8FDB\u7A0B" },
260533
260782
  "http:POST /api/agent-sessions/:param/agent-type": { since: "0.2.0", summary: "\u5207\u6362 agent \u7C7B\u578B" },
260534
260783
  "http:POST /api/agent-sessions/:param/model": { since: "0.2.0", summary: "\u5207\u6362\u4F1A\u8BDD\u6A21\u578B" },
260784
+ // Additive: an older worker 404s it, and the UI degrades by hiding the
260785
+ // "keep running" button — that worker has no park deadline to defuse.
260786
+ "http:POST /api/agent-sessions/:param/background-tasks/:param/keep": { since: "0.3.28", summary: "\u4E3A\u540E\u53F0\u4EFB\u52A1\u80CC\u4E66,\u514D\u4E8E\u8D85\u65F6\u6536\u5C3E" },
260787
+ // Additive alongside /keep, and degrades the same way: an older worker 404s
260788
+ // and the UI hides the button.
260789
+ "http:POST /api/agent-sessions/:param/background-tasks/:param/stop": { since: "0.3.28", summary: "\u505C\u6B62\u5355\u4E2A\u540E\u53F0\u4EFB\u52A1" },
260535
260790
  "http:POST /api/path/agent-sessions/:param/branch": { since: "0.2.0", summary: "\u4ECE\u5386\u53F2\u5206\u53C9\u4F1A\u8BDD" },
260536
260791
  "http:POST /api/agent-sessions/:param/switch-mode": { since: "0.2.0", summary: "\u6743\u9650\u6A21\u5F0F\u5207\u6362" },
260537
260792
  "http:POST /api/agent-sessions/:param/accept-plan": { since: "0.2.0", summary: "\u63A5\u53D7\u8BA1\u5212(\u9000\u51FA plan \u6A21\u5F0F)" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.27",
3
+ "version": "0.3.28",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"