@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.
- package/dist/bin.js +282 -27
- 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.
|
|
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)
|
|
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
|
-
|
|
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,
|
|
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)" },
|