@vibedeckx/linux-x64 0.3.26 → 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 +404 -32
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -186425,6 +186425,10 @@ var createRemoteServerRepos = (kdb, _h) => ({
|
|
|
186425
186425
|
]).where("project_remotes.project_id", "=", projectId).orderBy("project_remotes.sort_order", "asc").orderBy("project_remotes.id", "asc").execute();
|
|
186426
186426
|
return rows.map(mapProjectRemoteWithServer);
|
|
186427
186427
|
},
|
|
186428
|
+
listProjectIdsByServer: async (remoteServerId) => {
|
|
186429
|
+
const rows = await kdb.selectFrom("project_remotes").select("project_id").distinct().where("remote_server_id", "=", remoteServerId).execute();
|
|
186430
|
+
return rows.map((r) => r.project_id);
|
|
186431
|
+
},
|
|
186428
186432
|
getByProjectAndServer: async (projectId, remoteServerId) => {
|
|
186429
186433
|
const row = await kdb.selectFrom("project_remotes").innerJoin("remote_servers", "remote_servers.id", "project_remotes.remote_server_id").select([
|
|
186430
186434
|
"project_remotes.id",
|
|
@@ -207169,7 +207173,7 @@ function sessionMilestoneForTurnEnd(opts) {
|
|
|
207169
207173
|
workflow_run_id: null,
|
|
207170
207174
|
created_at: opts.createdAt
|
|
207171
207175
|
};
|
|
207172
|
-
if (opts.outcome === "completed") {
|
|
207176
|
+
if (opts.outcome === "completed" || opts.outcome === "completed_with_pending_tasks") {
|
|
207173
207177
|
return {
|
|
207174
207178
|
...base,
|
|
207175
207179
|
id: sessionResultReadyId(opts.sessionId, opts.entryIndex),
|
|
@@ -207274,8 +207278,10 @@ var ClaudeCodeProvider = class {
|
|
|
207274
207278
|
if (systemMsg.subtype === BACKGROUND_TASKS_CHANGED_SUBTYPE) {
|
|
207275
207279
|
const tasks = msg.tasks;
|
|
207276
207280
|
if (!Array.isArray(tasks)) return [];
|
|
207277
|
-
const
|
|
207278
|
-
|
|
207281
|
+
const parsed = tasks.flatMap(
|
|
207282
|
+
(t) => typeof t.task_id === "string" ? [{ taskId: t.task_id, taskType: t.task_type, description: t.description }] : []
|
|
207283
|
+
);
|
|
207284
|
+
return [{ type: "task_list_changed", tasks: parsed }];
|
|
207279
207285
|
}
|
|
207280
207286
|
if (systemMsg.subtype === INIT_SUBTYPE) {
|
|
207281
207287
|
const nativeId = msg.session_id;
|
|
@@ -207306,6 +207312,25 @@ var ClaudeCodeProvider = class {
|
|
|
207306
207312
|
formatUserInput(content, _sessionId) {
|
|
207307
207313
|
return serializeUserInput(content);
|
|
207308
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
|
+
}
|
|
207309
207334
|
// Lifecycle hooks are no-ops for Claude (stateless per-session)
|
|
207310
207335
|
onSessionCreated(_sessionId, _permissionMode) {
|
|
207311
207336
|
}
|
|
@@ -229421,7 +229446,7 @@ function isResidentProcessInScope(candidate, scope) {
|
|
|
229421
229446
|
return candidate.projectId === scope.projectId && candidate.branch === scope.branch;
|
|
229422
229447
|
}
|
|
229423
229448
|
function isIdleResidentProcess(candidate) {
|
|
229424
|
-
return candidate.processAlive && !candidate.dormant && candidate.status !== "running" && candidate.
|
|
229449
|
+
return candidate.processAlive && !candidate.dormant && candidate.status !== "running" && !candidate.backgroundTasksProtect;
|
|
229425
229450
|
}
|
|
229426
229451
|
function pickIdleResidentEvictionCandidate(candidates, scope) {
|
|
229427
229452
|
return candidates.filter((candidate) => !scope || isResidentProcessInScope(candidate, scope)).filter(isIdleResidentProcess).sort((a, b2) => a.lastActiveAt - b2.lastActiveAt)[0] ?? null;
|
|
@@ -229443,9 +229468,14 @@ var ResidentProcessLimitError = class extends Error {
|
|
|
229443
229468
|
|
|
229444
229469
|
// src/turn-completion.ts
|
|
229445
229470
|
var COMPLETION_GRACE_MS = 1500;
|
|
229471
|
+
var PARK_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
229446
229472
|
var TurnCompletionLedger = class {
|
|
229473
|
+
constructor(parkTimeoutMs = PARK_TIMEOUT_MS) {
|
|
229474
|
+
this.parkTimeoutMs = parkTimeoutMs;
|
|
229475
|
+
}
|
|
229476
|
+
parkTimeoutMs;
|
|
229447
229477
|
/** Live background tasks by harness task_id (same id may restart). */
|
|
229448
|
-
tasks = /* @__PURE__ */ new
|
|
229478
|
+
tasks = /* @__PURE__ */ new Map();
|
|
229449
229479
|
/** Held completion candidate — the latest success result, if any. */
|
|
229450
229480
|
pending = null;
|
|
229451
229481
|
/** Bumped whenever the candidate changes; stale grace timers no-op. */
|
|
@@ -229456,27 +229486,99 @@ var TurnCompletionLedger = class {
|
|
|
229456
229486
|
* grace delay (the common case).
|
|
229457
229487
|
*/
|
|
229458
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;
|
|
229459
229506
|
get pendingTaskCount() {
|
|
229460
229507
|
return this.tasks.size;
|
|
229461
229508
|
}
|
|
229462
229509
|
get hasPendingCompletion() {
|
|
229463
229510
|
return this.pending !== null;
|
|
229464
229511
|
}
|
|
229465
|
-
|
|
229466
|
-
|
|
229512
|
+
/** Live tasks in first-seen order — the payload the UI renders. */
|
|
229513
|
+
get backgroundTasks() {
|
|
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));
|
|
229558
|
+
}
|
|
229559
|
+
taskStarted(task, now3) {
|
|
229560
|
+
this.upsert(task, this.tasks.get(task.taskId), now3);
|
|
229467
229561
|
this.sawBackgroundActivity = true;
|
|
229468
|
-
return this.rearmIfHeld();
|
|
229562
|
+
return this.rearmIfHeld(now3);
|
|
229469
229563
|
}
|
|
229470
|
-
taskFinished(taskId) {
|
|
229564
|
+
taskFinished(taskId, now3) {
|
|
229471
229565
|
this.tasks.delete(taskId);
|
|
229566
|
+
this.sanctioned.delete(taskId);
|
|
229472
229567
|
this.sawBackgroundActivity = true;
|
|
229473
|
-
return this.rearmIfHeld();
|
|
229568
|
+
return this.rearmIfHeld(now3);
|
|
229474
229569
|
}
|
|
229475
229570
|
/** Authoritative snapshot from `system/background_tasks_changed`. */
|
|
229476
|
-
taskListChanged(
|
|
229477
|
-
this.tasks
|
|
229478
|
-
|
|
229479
|
-
|
|
229571
|
+
taskListChanged(tasks, now3) {
|
|
229572
|
+
const previous = this.tasks;
|
|
229573
|
+
this.tasks = /* @__PURE__ */ new Map();
|
|
229574
|
+
for (const task of tasks) {
|
|
229575
|
+
this.upsert(task, previous.get(task.taskId), now3);
|
|
229576
|
+
}
|
|
229577
|
+
for (const id of this.sanctioned) {
|
|
229578
|
+
if (!this.tasks.has(id)) this.sanctioned.delete(id);
|
|
229579
|
+
}
|
|
229580
|
+
if (tasks.length > 0) this.sawBackgroundActivity = true;
|
|
229581
|
+
return this.rearmIfHeld(now3);
|
|
229480
229582
|
}
|
|
229481
229583
|
/**
|
|
229482
229584
|
* The process emitted turn activity: if a completion was held, it was an
|
|
@@ -229488,8 +229590,10 @@ var TurnCompletionLedger = class {
|
|
|
229488
229590
|
* the race against the grace window.
|
|
229489
229591
|
*/
|
|
229490
229592
|
noteTurnActivity() {
|
|
229593
|
+
this.parkDeadlineExpired = false;
|
|
229491
229594
|
if (this.pending === null) return { kind: "none" };
|
|
229492
229595
|
this.pending = null;
|
|
229596
|
+
this.parkedSince = null;
|
|
229493
229597
|
this.generation++;
|
|
229494
229598
|
return { kind: "cancel" };
|
|
229495
229599
|
}
|
|
@@ -229505,9 +229609,11 @@ var TurnCompletionLedger = class {
|
|
|
229505
229609
|
this.sawBackgroundActivity = false;
|
|
229506
229610
|
return this.noteTurnActivity();
|
|
229507
229611
|
}
|
|
229508
|
-
successResult(payload) {
|
|
229612
|
+
successResult(payload, now3) {
|
|
229509
229613
|
this.generation++;
|
|
229510
229614
|
if (this.tasks.size > 0) {
|
|
229615
|
+
this.parkedSince = now3;
|
|
229616
|
+
this.parkDeadlineExpired = false;
|
|
229511
229617
|
this.pending = payload;
|
|
229512
229618
|
return { kind: "cancel" };
|
|
229513
229619
|
}
|
|
@@ -229521,6 +229627,7 @@ var TurnCompletionLedger = class {
|
|
|
229521
229627
|
errorResult() {
|
|
229522
229628
|
if (this.pending === null) return { kind: "none" };
|
|
229523
229629
|
this.pending = null;
|
|
229630
|
+
this.parkedSince = null;
|
|
229524
229631
|
this.generation++;
|
|
229525
229632
|
return { kind: "cancel" };
|
|
229526
229633
|
}
|
|
@@ -229546,7 +229653,10 @@ var TurnCompletionLedger = class {
|
|
|
229546
229653
|
/** Full reset (fresh spawn / stop / hibernate / agent switch). */
|
|
229547
229654
|
reset() {
|
|
229548
229655
|
this.tasks.clear();
|
|
229656
|
+
this.sanctioned.clear();
|
|
229549
229657
|
this.pending = null;
|
|
229658
|
+
this.parkedSince = null;
|
|
229659
|
+
this.parkDeadlineExpired = false;
|
|
229550
229660
|
this.generation++;
|
|
229551
229661
|
this.sawBackgroundActivity = false;
|
|
229552
229662
|
}
|
|
@@ -229555,15 +229665,34 @@ var TurnCompletionLedger = class {
|
|
|
229555
229665
|
* have no resume behind it), so they delay the commit rather than cancel
|
|
229556
229666
|
* it — and while tasks are still live the candidate stays parked with no
|
|
229557
229667
|
* timer at all (only an empty set can complete a turn). */
|
|
229558
|
-
rearmIfHeld() {
|
|
229668
|
+
rearmIfHeld(now3) {
|
|
229559
229669
|
if (this.pending === null) return { kind: "none" };
|
|
229560
229670
|
this.generation++;
|
|
229561
|
-
if (this.tasks.size > 0)
|
|
229671
|
+
if (this.tasks.size > 0) {
|
|
229672
|
+
this.parkedSince ??= now3;
|
|
229673
|
+
return { kind: "cancel" };
|
|
229674
|
+
}
|
|
229562
229675
|
return { kind: "schedule", generation: this.generation };
|
|
229563
229676
|
}
|
|
229677
|
+
/**
|
|
229678
|
+
* Merge a descriptor into the live set, keeping the earliest `startedAt` and
|
|
229679
|
+
* any label already known: `task_started` carries a description that the
|
|
229680
|
+
* snapshot for the same task may omit, and the two arrive in either order.
|
|
229681
|
+
* `known` comes from the caller: a snapshot resync rebuilds the map, so the
|
|
229682
|
+
* prior entry is no longer reachable through `this.tasks`.
|
|
229683
|
+
*/
|
|
229684
|
+
upsert(task, known, now3) {
|
|
229685
|
+
this.tasks.set(task.taskId, {
|
|
229686
|
+
taskId: task.taskId,
|
|
229687
|
+
taskType: task.taskType ?? known?.taskType,
|
|
229688
|
+
description: task.description ?? known?.description,
|
|
229689
|
+
startedAt: known?.startedAt ?? now3
|
|
229690
|
+
});
|
|
229691
|
+
}
|
|
229564
229692
|
commitHeld() {
|
|
229565
229693
|
const payload = this.pending;
|
|
229566
229694
|
this.pending = null;
|
|
229695
|
+
this.parkedSince = null;
|
|
229567
229696
|
this.generation++;
|
|
229568
229697
|
return { kind: "commit", payload };
|
|
229569
229698
|
}
|
|
@@ -229644,10 +229773,13 @@ var AgentSessionManager = class {
|
|
|
229644
229773
|
retentionDeleting = /* @__PURE__ */ new Set();
|
|
229645
229774
|
/** Grace window before committing a held completion (injectable for tests). */
|
|
229646
229775
|
completionGraceMs;
|
|
229776
|
+
/** Bound on a parked completion (injectable for tests). */
|
|
229777
|
+
parkTimeoutMs;
|
|
229647
229778
|
workflowSuppressionCheck = null;
|
|
229648
229779
|
constructor(storage2, opts) {
|
|
229649
229780
|
this.storage = storage2;
|
|
229650
229781
|
this.completionGraceMs = opts?.completionGraceMs ?? COMPLETION_GRACE_MS;
|
|
229782
|
+
this.parkTimeoutMs = opts?.parkTimeoutMs ?? PARK_TIMEOUT_MS;
|
|
229651
229783
|
}
|
|
229652
229784
|
async resolveSessionWorktreePath(session, legacyProjectPath) {
|
|
229653
229785
|
if (!session.workspaceCheckoutId) {
|
|
@@ -229922,7 +230054,7 @@ var AgentSessionManager = class {
|
|
|
229922
230054
|
processAlive: this.isProcessAlive(session),
|
|
229923
230055
|
status: session.status,
|
|
229924
230056
|
dormant: session.dormant,
|
|
229925
|
-
|
|
230057
|
+
backgroundTasksProtect: session.completion.backgroundTasksProtectSession,
|
|
229926
230058
|
lastActiveAt: session.lastActiveAt,
|
|
229927
230059
|
projectId: session.projectId,
|
|
229928
230060
|
branch: session.branch
|
|
@@ -230070,8 +230202,9 @@ var AgentSessionManager = class {
|
|
|
230070
230202
|
crossRemoteMcp: opts.crossRemoteMcp,
|
|
230071
230203
|
agentType,
|
|
230072
230204
|
model,
|
|
230073
|
-
completion: new TurnCompletionLedger(),
|
|
230205
|
+
completion: new TurnCompletionLedger(this.parkTimeoutMs),
|
|
230074
230206
|
graceTimer: null,
|
|
230207
|
+
parkTimer: null,
|
|
230075
230208
|
eventChain: Promise.resolve(),
|
|
230076
230209
|
bgSpawnHintsThisTurn: 0,
|
|
230077
230210
|
taskStartedThisTurn: 0,
|
|
@@ -230226,6 +230359,7 @@ var AgentSessionManager = class {
|
|
|
230226
230359
|
if (action.kind === "commit") {
|
|
230227
230360
|
await this.commitCompletion(session, action.payload);
|
|
230228
230361
|
}
|
|
230362
|
+
this.broadcastBackgroundTasks(session);
|
|
230229
230363
|
if (code !== 0 && !spawnFailed && !session.producedOutput) {
|
|
230230
230364
|
try {
|
|
230231
230365
|
await this.pushEntry(session.id, {
|
|
@@ -230327,25 +230461,119 @@ var AgentSessionManager = class {
|
|
|
230327
230461
|
} else if (action.kind === "schedule") {
|
|
230328
230462
|
this.armGraceTimer(session, action.generation);
|
|
230329
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;
|
|
230330
230530
|
}
|
|
230331
230531
|
/** Discard all turn-completion state (fresh spawn / stop / hibernate / agent switch). */
|
|
230332
230532
|
resetCompletion(session) {
|
|
230333
230533
|
this.clearGraceTimer(session);
|
|
230334
230534
|
session.completion.reset();
|
|
230535
|
+
this.syncParkTimer(session);
|
|
230536
|
+
this.broadcastBackgroundTasks(session);
|
|
230335
230537
|
}
|
|
230336
230538
|
/**
|
|
230337
230539
|
* The single place completion side effects run. Fired by processAgentEvent
|
|
230338
230540
|
* for turns with no background activity (zero delay), by the grace timer
|
|
230339
230541
|
* for held candidates, and by the close handler on a clean process exit.
|
|
230340
230542
|
*/
|
|
230341
|
-
|
|
230543
|
+
/**
|
|
230544
|
+
* Push the live background-task set to every subscriber. Called on each
|
|
230545
|
+
* lifecycle event rather than diffed: the set is tiny and the harness
|
|
230546
|
+
* already only speaks on change, so a plain snapshot keeps the client
|
|
230547
|
+
* stateless (no patch application, no ordering assumptions).
|
|
230548
|
+
*/
|
|
230549
|
+
broadcastBackgroundTasks(session) {
|
|
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 {
|
|
230560
|
+
backgroundTasks: {
|
|
230561
|
+
tasks: session.completion.backgroundTasks,
|
|
230562
|
+
turnParked: session.completion.hasPendingCompletion,
|
|
230563
|
+
parkDeadlineAt: session.completion.parkDeadlineAt,
|
|
230564
|
+
canStopTasks: !!getProvider(session.agentType).formatStopBackgroundTask
|
|
230565
|
+
}
|
|
230566
|
+
};
|
|
230567
|
+
}
|
|
230568
|
+
async commitCompletion(session, payload, outcome = "completed") {
|
|
230342
230569
|
const sessionId = session.id;
|
|
230343
230570
|
console.log(`[AgentSession] taskCompleted: sessionId=${sessionId}, eventBus=${!!this.eventBus}, projectId=${session.projectId}, branch=${session.branch}`);
|
|
230344
230571
|
const completedAt = Date.now();
|
|
230345
230572
|
if (!session.skipDb) {
|
|
230346
230573
|
await this.storage.agentSessions.markCompleted(sessionId, completedAt);
|
|
230347
230574
|
}
|
|
230348
|
-
const turnEndEntryIndex = await this.endActiveTurn(session,
|
|
230575
|
+
const turnEndEntryIndex = await this.endActiveTurn(session, outcome);
|
|
230576
|
+
this.broadcastBackgroundTasks(session);
|
|
230349
230577
|
const summaryText = extractLastAssistantText(session.store.entries);
|
|
230350
230578
|
this.broadcastRaw(sessionId, {
|
|
230351
230579
|
taskCompleted: {
|
|
@@ -230526,19 +230754,26 @@ var AgentSessionManager = class {
|
|
|
230526
230754
|
// no auto-resume behind it, and cancelling on it would drop the
|
|
230527
230755
|
// completion entirely.
|
|
230528
230756
|
case "task_started":
|
|
230529
|
-
this.applyCompletionTimerAction(session, session.completion.taskStarted(
|
|
230757
|
+
this.applyCompletionTimerAction(session, session.completion.taskStarted({
|
|
230758
|
+
taskId: event.taskId,
|
|
230759
|
+
taskType: event.taskType,
|
|
230760
|
+
description: event.description
|
|
230761
|
+
}, timestamp));
|
|
230530
230762
|
session.taskStartedThisTurn++;
|
|
230763
|
+
this.broadcastBackgroundTasks(session);
|
|
230531
230764
|
console.log(`[AgentSession] Background task started: ${event.taskId} (${event.taskType ?? "?"}) \u2014 ${session.completion.pendingTaskCount} pending in ${sessionId}`);
|
|
230532
230765
|
break;
|
|
230533
230766
|
case "task_finished":
|
|
230534
|
-
this.applyCompletionTimerAction(session, session.completion.taskFinished(event.taskId));
|
|
230767
|
+
this.applyCompletionTimerAction(session, session.completion.taskFinished(event.taskId, timestamp));
|
|
230768
|
+
this.broadcastBackgroundTasks(session);
|
|
230535
230769
|
console.log(`[AgentSession] Background task finished: ${event.taskId} (${event.status ?? "?"}) \u2014 ${session.completion.pendingTaskCount} pending in ${sessionId}`);
|
|
230536
230770
|
break;
|
|
230537
230771
|
// Authoritative running-task snapshot from the CLI — resyncs the ledger
|
|
230538
230772
|
// so add/delete drift in the started/finished pairs can't accumulate.
|
|
230539
230773
|
case "task_list_changed":
|
|
230540
|
-
this.applyCompletionTimerAction(session, session.completion.taskListChanged(event.
|
|
230541
|
-
|
|
230774
|
+
this.applyCompletionTimerAction(session, session.completion.taskListChanged(event.tasks, timestamp));
|
|
230775
|
+
this.broadcastBackgroundTasks(session);
|
|
230776
|
+
console.log(`[AgentSession] Background task snapshot: [${event.tasks.map((t) => t.taskId).join(", ")}] in ${sessionId}`);
|
|
230542
230777
|
break;
|
|
230543
230778
|
// Handled above (cancels a grace-held completion); no store entry.
|
|
230544
230779
|
case "turn_started":
|
|
@@ -230599,7 +230834,7 @@ var AgentSessionManager = class {
|
|
|
230599
230834
|
cost_usd: event.cost_usd,
|
|
230600
230835
|
input_tokens: event.input_tokens,
|
|
230601
230836
|
output_tokens: event.output_tokens
|
|
230602
|
-
});
|
|
230837
|
+
}, timestamp);
|
|
230603
230838
|
if (action.kind === "commit") {
|
|
230604
230839
|
await this.commitCompletion(session, action.payload);
|
|
230605
230840
|
} else {
|
|
@@ -230609,6 +230844,7 @@ var AgentSessionManager = class {
|
|
|
230609
230844
|
console.log(`[AgentSession] result after background-task activity \u2014 holding completion for ${this.completionGraceMs}ms grace (session=${sessionId})`);
|
|
230610
230845
|
}
|
|
230611
230846
|
this.applyCompletionTimerAction(session, action);
|
|
230847
|
+
this.broadcastBackgroundTasks(session);
|
|
230612
230848
|
}
|
|
230613
230849
|
}
|
|
230614
230850
|
break;
|
|
@@ -230941,6 +231177,8 @@ var AgentSessionManager = class {
|
|
|
230941
231177
|
ws.send(JSON.stringify({ Ready: true, historyEpoch: session.historyEpoch }));
|
|
230942
231178
|
const statusPatch = ConversationPatch.updateStatus(session.status);
|
|
230943
231179
|
ws.send(JSON.stringify({ JsonPatch: statusPatch }));
|
|
231180
|
+
const tasksMsg = this.backgroundTasksMessage(session);
|
|
231181
|
+
ws.send(JSON.stringify(tasksMsg));
|
|
230944
231182
|
return () => {
|
|
230945
231183
|
session.subscribers.delete(ws);
|
|
230946
231184
|
};
|
|
@@ -231228,6 +231466,7 @@ var AgentSessionManager = class {
|
|
|
231228
231466
|
session.process = null;
|
|
231229
231467
|
this.killProcess(proc);
|
|
231230
231468
|
this.emitProcessAlive(session, false);
|
|
231469
|
+
this.resetCompletion(session);
|
|
231231
231470
|
if (!session.skipDb) {
|
|
231232
231471
|
await this.storage.agentSessions.deleteEntries(sessionId);
|
|
231233
231472
|
session.historyEpoch = await this.storage.agentSessions.incrementHistoryEpoch(sessionId);
|
|
@@ -231738,8 +231977,9 @@ var AgentSessionManager = class {
|
|
|
231738
231977
|
permissionMode,
|
|
231739
231978
|
agentType: dbSession.agent_type || "claude-code",
|
|
231740
231979
|
model: dbSession.model ?? null,
|
|
231741
|
-
completion: new TurnCompletionLedger(),
|
|
231980
|
+
completion: new TurnCompletionLedger(this.parkTimeoutMs),
|
|
231742
231981
|
graceTimer: null,
|
|
231982
|
+
parkTimer: null,
|
|
231743
231983
|
eventChain: Promise.resolve(),
|
|
231744
231984
|
bgSpawnHintsThisTurn: 0,
|
|
231745
231985
|
taskStartedThisTurn: 0,
|
|
@@ -231930,8 +232170,9 @@ var AgentSessionManager = class {
|
|
|
231930
232170
|
permissionMode,
|
|
231931
232171
|
agentType,
|
|
231932
232172
|
model,
|
|
231933
|
-
completion: new TurnCompletionLedger(),
|
|
232173
|
+
completion: new TurnCompletionLedger(this.parkTimeoutMs),
|
|
231934
232174
|
graceTimer: null,
|
|
232175
|
+
parkTimer: null,
|
|
231935
232176
|
eventChain: Promise.resolve(),
|
|
231936
232177
|
bgSpawnHintsThisTurn: 0,
|
|
231937
232178
|
taskStartedThisTurn: 0,
|
|
@@ -233597,6 +233838,9 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
|
|
|
233597
233838
|
);
|
|
233598
233839
|
}
|
|
233599
233840
|
}
|
|
233841
|
+
} else if ("backgroundTasks" in parsed) {
|
|
233842
|
+
cache2.setBackgroundTasks(sessionId, raw);
|
|
233843
|
+
cache2.broadcast(sessionId, raw);
|
|
233600
233844
|
} else if ("error" in parsed) {
|
|
233601
233845
|
cache2.setSessionStatus(sessionId, "error");
|
|
233602
233846
|
cache2.appendMessage(sessionId, raw, false);
|
|
@@ -239414,6 +239658,8 @@ function selfReportSection(report) {
|
|
|
239414
239658
|
].join("\n");
|
|
239415
239659
|
}
|
|
239416
239660
|
var VERDICT_INSTRUCTIONS = [
|
|
239661
|
+
"\nThe bar for blocking: a real defect that is worth fixing \u2014 wrong behavior, a case a user or caller will actually hit, a security or data-loss risk, or a missing test for logic that matters. Report those plainly; do not soften a real problem because the fix is inconvenient.",
|
|
239662
|
+
"Not blocking: over-engineering \u2014 speculative hardening, defenses against inputs this code cannot receive, abstractions or configurability for cases nobody has asked for, or a rewrite in your preferred style. When the fix would add more complexity than the problem it prevents is worth, it is a non-blocking note at most.",
|
|
239417
239663
|
"\nEnd your final message with:",
|
|
239418
239664
|
"1. Verdict \u2014 exactly one of: ship / needs-changes / cannot-verify. Use cannot-verify when you could not gather enough evidence to judge, rather than guessing.",
|
|
239419
239665
|
"2. Blocking findings \u2014 what must change before shipping, each specific and actionable (say explicitly when there are none).",
|
|
@@ -240101,7 +240347,8 @@ var RemotePatchCache = class {
|
|
|
240101
240347
|
latestEntryIndex: null,
|
|
240102
240348
|
lastTurnEndEntryIndex: null,
|
|
240103
240349
|
coverage: null,
|
|
240104
|
-
sessionStatus: null
|
|
240350
|
+
sessionStatus: null,
|
|
240351
|
+
backgroundTasks: null
|
|
240105
240352
|
};
|
|
240106
240353
|
this.cache.set(sessionId, entry);
|
|
240107
240354
|
}
|
|
@@ -240149,6 +240396,7 @@ var RemotePatchCache = class {
|
|
|
240149
240396
|
if (metadata.lastTurnEnd !== null) lastTurnEndEntryIndex = Math.max(lastTurnEndEntryIndex ?? -1, metadata.lastTurnEnd);
|
|
240150
240397
|
}
|
|
240151
240398
|
const sessionStatus = existing?.sessionStatus ?? null;
|
|
240399
|
+
const backgroundTasks = existing?.backgroundTasks ?? null;
|
|
240152
240400
|
const coverage = existing?.coverage ?? null;
|
|
240153
240401
|
this.cache.set(sessionId, {
|
|
240154
240402
|
messages,
|
|
@@ -240164,7 +240412,8 @@ var RemotePatchCache = class {
|
|
|
240164
240412
|
latestEntryIndex,
|
|
240165
240413
|
lastTurnEndEntryIndex,
|
|
240166
240414
|
coverage,
|
|
240167
|
-
sessionStatus
|
|
240415
|
+
sessionStatus,
|
|
240416
|
+
backgroundTasks
|
|
240168
240417
|
});
|
|
240169
240418
|
}
|
|
240170
240419
|
/**
|
|
@@ -240229,6 +240478,7 @@ var RemotePatchCache = class {
|
|
|
240229
240478
|
entry.historyEpoch = epoch;
|
|
240230
240479
|
entry.latestEntryIndex = null;
|
|
240231
240480
|
entry.lastTurnEndEntryIndex = null;
|
|
240481
|
+
entry.backgroundTasks = null;
|
|
240232
240482
|
entry.coverage = { epoch, start: 0 };
|
|
240233
240483
|
}
|
|
240234
240484
|
setLastTurnEndEntryIndex(sessionId, index) {
|
|
@@ -240237,6 +240487,10 @@ var RemotePatchCache = class {
|
|
|
240237
240487
|
setSessionStatus(sessionId, status) {
|
|
240238
240488
|
this.getOrCreate(sessionId).sessionStatus = status;
|
|
240239
240489
|
}
|
|
240490
|
+
/** Last-value store for the live background-task snapshot (see CacheEntry). */
|
|
240491
|
+
setBackgroundTasks(sessionId, raw) {
|
|
240492
|
+
this.getOrCreate(sessionId).backgroundTasks = raw;
|
|
240493
|
+
}
|
|
240240
240494
|
/** Store a persistent remote WebSocket connection. */
|
|
240241
240495
|
setRemoteWs(sessionId, ws) {
|
|
240242
240496
|
const entry = this.getOrCreate(sessionId);
|
|
@@ -243265,6 +243519,13 @@ var sharedServices = async (fastify2, opts) => {
|
|
|
243265
243519
|
reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
|
|
243266
243520
|
void (async () => {
|
|
243267
243521
|
await opts.storage.remoteServers.updateStatus(remoteServerId, status);
|
|
243522
|
+
try {
|
|
243523
|
+
for (const projectId of await opts.storage.projectRemotes.listProjectIdsByServer(remoteServerId)) {
|
|
243524
|
+
eventBus.emit({ type: "remote-server:status", projectId, remoteServerId, status });
|
|
243525
|
+
}
|
|
243526
|
+
} catch (err) {
|
|
243527
|
+
console.error(`[SharedServices] remote-server:status fan-out failed for ${remoteServerId}:`, err);
|
|
243528
|
+
}
|
|
243268
243529
|
if (status === "online") {
|
|
243269
243530
|
const machineId = reverseConnectManager.getMachineId(remoteServerId);
|
|
243270
243531
|
await restoreRemoteExecutorsForServer(remoteServerId, machineId);
|
|
@@ -248671,6 +248932,68 @@ var routes11 = async (fastify2) => {
|
|
|
248671
248932
|
});
|
|
248672
248933
|
}
|
|
248673
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
|
+
);
|
|
248674
248997
|
fastify2.post(
|
|
248675
248998
|
"/api/agent-sessions/:sessionId/branch",
|
|
248676
248999
|
async (req, reply) => {
|
|
@@ -251688,6 +252011,11 @@ var routes23 = async (fastify2) => {
|
|
|
251688
252011
|
try {
|
|
251689
252012
|
const message = JSON.parse(data.toString());
|
|
251690
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
|
+
}
|
|
251691
252019
|
handle.handleInput(message);
|
|
251692
252020
|
}
|
|
251693
252021
|
} catch (error48) {
|
|
@@ -251763,6 +252091,9 @@ var routes23 = async (fastify2) => {
|
|
|
251763
252091
|
} else if (msg.type === "input") {
|
|
251764
252092
|
handleInputMap.get(msg.processId)?.({ type: "input", data: msg.data });
|
|
251765
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
|
+
);
|
|
251766
252097
|
handleInputMap.get(msg.processId)?.({ type: "resize", cols: msg.cols, rows: msg.rows });
|
|
251767
252098
|
}
|
|
251768
252099
|
} catch (error48) {
|
|
@@ -251877,6 +252208,12 @@ var routes23 = async (fastify2) => {
|
|
|
251877
252208
|
socket.send(JSON.stringify({ Ready: true, historyEpoch: cacheEntry.historyEpoch ?? void 0 }));
|
|
251878
252209
|
} catch {
|
|
251879
252210
|
}
|
|
252211
|
+
if (cacheEntry.backgroundTasks !== null) {
|
|
252212
|
+
try {
|
|
252213
|
+
socket.send(cacheEntry.backgroundTasks);
|
|
252214
|
+
} catch {
|
|
252215
|
+
}
|
|
252216
|
+
}
|
|
251880
252217
|
if (cacheEntry.finished) {
|
|
251881
252218
|
try {
|
|
251882
252219
|
socket.send(JSON.stringify({ finished: true }));
|
|
@@ -252264,6 +252601,11 @@ var routes25 = async (fastify2) => {
|
|
|
252264
252601
|
"Access-Control-Allow-Origin": "*"
|
|
252265
252602
|
});
|
|
252266
252603
|
reply.raw.write(":ok\n\n");
|
|
252604
|
+
reply.raw.write(
|
|
252605
|
+
`data: ${JSON.stringify({ type: "hello", ...fastify2.uiBuildId ? { uiBuildId: fastify2.uiBuildId } : {} })}
|
|
252606
|
+
|
|
252607
|
+
`
|
|
252608
|
+
);
|
|
252267
252609
|
const unsubscribe = fastify2.eventBus.subscribe((event) => {
|
|
252268
252610
|
void (async () => {
|
|
252269
252611
|
if (userId !== null && !await fastify2.storage.projects.getById(event.projectId, userId)) {
|
|
@@ -258633,6 +258975,8 @@ var REMOTE_ID_PROP = {
|
|
|
258633
258975
|
};
|
|
258634
258976
|
var CROSS_REMOTE_MCP_INSTRUCTIONS = [
|
|
258635
258977
|
"Use these tools when the task requires inspecting or operating another remote machine, or using an MCP server reachable from that remote.",
|
|
258978
|
+
"Treat a machine or host name in the user's request (for example, 'look at the ubuntu machine') as an explicit target signal, not as a request to inspect the current local workspace. Call `list_accessible_remotes` and match the user's wording against the returned remote names and ids before reading files or running local commands.",
|
|
258979
|
+
"If exactly one accessible remote matches the named machine, perform the requested work on that remote. If multiple remotes match, or the wording could reasonably refer to either the local machine or a remote, ask the user which target they mean before operating. If no remote matches, say so instead of silently falling back to local.",
|
|
258636
258980
|
"Cross-remote can discover accessible machines, inspect files, directories, paths, and processes, run commands on exec-tier remotes, and persistently use MCP servers reachable from those remotes. Available operations depend on the remote's access tier, online state, and worker capabilities.",
|
|
258637
258981
|
"Call `list_accessible_remotes` first to discover the remote id, access tier, online state, and whether its MCP broker is supported.",
|
|
258638
258982
|
"For a remote MCP server, call `remote_mcp_open` once, use the returned tool schemas and handle for repeated `remote_mcp_call` calls, then call `remote_mcp_close` when the work is complete. Do not reopen the MCP server for every tool call.",
|
|
@@ -258643,7 +258987,7 @@ var CROSS_REMOTE_MCP_INSTRUCTIONS = [
|
|
|
258643
258987
|
var TOOLS = [
|
|
258644
258988
|
{
|
|
258645
258989
|
name: "list_accessible_remotes",
|
|
258646
|
-
description: "List
|
|
258990
|
+
description: "List remote machines this agent may access, including their names, ids, access tiers, and online status. Call this before acting whenever the user names or otherwise identifies a machine/host, so the request is routed to the intended target instead of the current local machine.",
|
|
258647
258991
|
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
258648
258992
|
},
|
|
258649
258993
|
{
|
|
@@ -259996,6 +260340,12 @@ function registerTraceContext(server) {
|
|
|
259996
260340
|
|
|
259997
260341
|
// src/server.ts
|
|
259998
260342
|
var API_KEY2 = process.env.VIBEDECKX_API_KEY || void 0;
|
|
260343
|
+
function staticCacheControl(filePath) {
|
|
260344
|
+
const p2 = filePath.replace(/\\/g, "/");
|
|
260345
|
+
if (p2.includes("/_next/static/")) return "public, max-age=31536000, immutable";
|
|
260346
|
+
if (p2.endsWith(".html") || p2.endsWith(".txt")) return "no-cache";
|
|
260347
|
+
return "public, max-age=86400";
|
|
260348
|
+
}
|
|
259999
260349
|
function requireAuth(req, reply) {
|
|
260000
260350
|
const server = req.server;
|
|
260001
260351
|
if (!server.authEnabled) return void 0;
|
|
@@ -260045,6 +260395,14 @@ var createServer = async (opts) => {
|
|
|
260045
260395
|
"./ui"
|
|
260046
260396
|
);
|
|
260047
260397
|
const UI_ROOT = opts.uiRoot !== void 0 ? opts.uiRoot : fs5.existsSync(bakedUiRoot) ? bakedUiRoot : null;
|
|
260398
|
+
let uiBuildId;
|
|
260399
|
+
if (UI_ROOT) {
|
|
260400
|
+
try {
|
|
260401
|
+
const parsed = JSON.parse(fs5.readFileSync(path19.join(UI_ROOT, "build-id.json"), "utf8"));
|
|
260402
|
+
if (typeof parsed.buildId === "string" && parsed.buildId) uiBuildId = parsed.buildId;
|
|
260403
|
+
} catch {
|
|
260404
|
+
}
|
|
260405
|
+
}
|
|
260048
260406
|
const server = (0, import_fastify.default)({
|
|
260049
260407
|
maxParamLength: 500,
|
|
260050
260408
|
bodyLimit: 16 * 1024 * 1024,
|
|
@@ -260071,6 +260429,7 @@ var createServer = async (opts) => {
|
|
|
260071
260429
|
console.log(`[WS-RAW] HTTP upgrade event: ${redactUrlForLog(req.url)}`);
|
|
260072
260430
|
});
|
|
260073
260431
|
server.decorate("authEnabled", authEnabled);
|
|
260432
|
+
server.decorate("uiBuildId", uiBuildId);
|
|
260074
260433
|
server.decorate("noLocalProjects", noLocalProjects);
|
|
260075
260434
|
registerTraceContext(server);
|
|
260076
260435
|
server.addHook("onRequest", (req, reply, done) => {
|
|
@@ -260185,7 +260544,14 @@ var createServer = async (opts) => {
|
|
|
260185
260544
|
if (UI_ROOT) {
|
|
260186
260545
|
server.register(import_static.fastifyStatic, {
|
|
260187
260546
|
root: UI_ROOT,
|
|
260188
|
-
wildcard: false
|
|
260547
|
+
wildcard: false,
|
|
260548
|
+
// Cache policy is ours, not send's default (`public, max-age=0`): with no
|
|
260549
|
+
// origin Cache-Control, Cloudflare was stamping a 4h TTL on every asset
|
|
260550
|
+
// and every reload re-downloaded ~1.2MB of content-hashed chunks.
|
|
260551
|
+
cacheControl: false,
|
|
260552
|
+
setHeaders: (res, filePath) => {
|
|
260553
|
+
res.setHeader("Cache-Control", staticCacheControl(filePath));
|
|
260554
|
+
}
|
|
260189
260555
|
});
|
|
260190
260556
|
}
|
|
260191
260557
|
server.addHook("onError", (req, _reply, error48, done) => {
|
|
@@ -260415,6 +260781,12 @@ var WORKER_CAPABILITIES = {
|
|
|
260415
260781
|
"http:POST /api/agent-sessions/:param/restart": { since: "0.2.0", summary: "\u91CD\u542F\u4F1A\u8BDD\u8FDB\u7A0B" },
|
|
260416
260782
|
"http:POST /api/agent-sessions/:param/agent-type": { since: "0.2.0", summary: "\u5207\u6362 agent \u7C7B\u578B" },
|
|
260417
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" },
|
|
260418
260790
|
"http:POST /api/path/agent-sessions/:param/branch": { since: "0.2.0", summary: "\u4ECE\u5386\u53F2\u5206\u53C9\u4F1A\u8BDD" },
|
|
260419
260791
|
"http:POST /api/agent-sessions/:param/switch-mode": { since: "0.2.0", summary: "\u6743\u9650\u6A21\u5F0F\u5207\u6362" },
|
|
260420
260792
|
"http:POST /api/agent-sessions/:param/accept-plan": { since: "0.2.0", summary: "\u63A5\u53D7\u8BA1\u5212(\u9000\u51FA plan \u6A21\u5F0F)" },
|