@centralu/darwin-arm64 0.1.0-beta.43 → 0.1.0-beta.44
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/Centralu.app/Contents/Info.plist +2 -2
- package/Centralu.app/Contents/MacOS/centralu +0 -0
- package/Centralu.app/Contents/MacOS/centralu-keeper +0 -0
- package/Centralu.app/Contents/Resources/content/centralu-keeper +0 -0
- package/Centralu.app/Contents/Resources/content/content-manifest.json +6 -6
- package/Centralu.app/Contents/Resources/content/content-manifest.json.sig +1 -1
- package/Centralu.app/Contents/Resources/content/host/bundle-info.json +2 -2
- package/Centralu.app/Contents/Resources/content/host/centralu-package.json +5 -5
- package/Centralu.app/Contents/Resources/content/host/main.mjs +1025 -281
- package/Centralu.app/Contents/Resources/resources/host/bundle-info.json +2 -2
- package/Centralu.app/Contents/Resources/resources/host/centralu-package.json +5 -5
- package/Centralu.app/Contents/Resources/resources/host/main.mjs +1025 -281
- package/Centralu.app/Contents/_CodeSignature/CodeResources +20 -20
- package/package.json +1 -1
|
@@ -43054,7 +43054,7 @@ import { dirname as dirname26, join as join58 } from "node:path";
|
|
|
43054
43054
|
import { mkdirSync as mkdirSync20, writeSync as writeSync3 } from "node:fs";
|
|
43055
43055
|
|
|
43056
43056
|
// packages/protocol/src/version.generated.ts
|
|
43057
|
-
var APP_VERSION = "0.1.0-beta.
|
|
43057
|
+
var APP_VERSION = "0.1.0-beta.44";
|
|
43058
43058
|
|
|
43059
43059
|
// packages/protocol/src/brand.ts
|
|
43060
43060
|
var APP_NAME = "Centralu";
|
|
@@ -59081,7 +59081,31 @@ var UsageSnapshot = external_exports.object({
|
|
|
59081
59081
|
plan: external_exports.string().nullable().default(null),
|
|
59082
59082
|
windows: external_exports.array(UsageWindow).default([]),
|
|
59083
59083
|
/** Daily tokens. A tool that cannot report this leaves it empty — the UI collapses that row on its own */
|
|
59084
|
-
daily: external_exports.array(external_exports.object({ date: external_exports.string(), tokens: external_exports.number() })).default([])
|
|
59084
|
+
daily: external_exports.array(external_exports.object({ date: external_exports.string(), tokens: external_exports.number() })).default([]),
|
|
59085
|
+
/**
|
|
59086
|
+
* What the tool counted locally, as a total and not a limit (#1623): a tool with no limits to read (`agents.usage`
|
|
59087
|
+
* answers it `notReported`) may still say how much was used on this computer. Absent when it says nothing, and from a
|
|
59088
|
+
* host that predates it.
|
|
59089
|
+
*/
|
|
59090
|
+
totals: external_exports.object({
|
|
59091
|
+
/** What the totals cover, in the words the person reads under their title ("all time") */
|
|
59092
|
+
scope: external_exports.string(),
|
|
59093
|
+
/**
|
|
59094
|
+
* When the host read them, in ms since the epoch. A large database is read slowly, so the host keeps the last totals and
|
|
59095
|
+
* the screen says how old they are. Absent from a host that predates it
|
|
59096
|
+
*/
|
|
59097
|
+
asOf: external_exports.number().optional(),
|
|
59098
|
+
rows: external_exports.array(
|
|
59099
|
+
external_exports.object({
|
|
59100
|
+
id: external_exports.string(),
|
|
59101
|
+
label: external_exports.string(),
|
|
59102
|
+
/** Null: the tool printed it in a form the host could not read. The screen shows it as unread, never as a number */
|
|
59103
|
+
value: external_exports.number().nullable(),
|
|
59104
|
+
/** How the screen writes the value: a count, a number of tokens, or dollars */
|
|
59105
|
+
unit: external_exports.enum(["count", "tokens", "usd"])
|
|
59106
|
+
})
|
|
59107
|
+
)
|
|
59108
|
+
}).optional()
|
|
59085
59109
|
});
|
|
59086
59110
|
|
|
59087
59111
|
// packages/protocol/src/entities/errors.ts
|
|
@@ -59865,6 +59889,8 @@ var NormalizedEvent = external_exports.discriminatedUnion("type", [
|
|
|
59865
59889
|
data: external_exports.string(),
|
|
59866
59890
|
/** If the image exists on disk, its source path (Codex imageView) */
|
|
59867
59891
|
path: external_exports.string().optional(),
|
|
59892
|
+
/** The file the agent viewed, as its path: `path` of a stored row is the host's own copy of it (#1464) */
|
|
59893
|
+
name: external_exports.string().optional(),
|
|
59868
59894
|
note: external_exports.string().optional()
|
|
59869
59895
|
}),
|
|
59870
59896
|
/**
|
|
@@ -61685,8 +61711,14 @@ var hostParams = {
|
|
|
61685
61711
|
* command can start), so a turn cannot slip in between the check and the stop. Otherwise `{ ok: false, busy, calls }`
|
|
61686
61712
|
* and nothing changes. A host older than `ifIdle` ignores it and stops; the hand-over sends it only to a host that says
|
|
61687
61713
|
* its logon, which came with it
|
|
61714
|
+
*
|
|
61715
|
+
* `forUpdate` (#1618, additive): a linked hub stops this host to update it, roll it back or put a version back, and starts
|
|
61716
|
+
* one again in this folder. As it lets its sessions go, this host writes those in a turn to a list of its own in the
|
|
61717
|
+
* folder, each with the last row of its conversation (docs/agent-host.md §4.8), so the host that starts next continues
|
|
61718
|
+
* each of those turns once, while its conversation still ends there. A host
|
|
61719
|
+
* older than it ignores it and stops, and its turns end as a stop's always did
|
|
61688
61720
|
*/
|
|
61689
|
-
"host.stop": external_exports.object({ ifIdle: external_exports.boolean().optional() }),
|
|
61721
|
+
"host.stop": external_exports.object({ ifIdle: external_exports.boolean().optional(), forUpdate: external_exports.boolean().optional() }),
|
|
61690
61722
|
/**
|
|
61691
61723
|
* What a `serve` in the person's Windows sign-in says about taking over from this host (#1224): this host keeps it,
|
|
61692
61724
|
* says it in its hellos and sends `host_handover`, so a hub's row can say it. `handover: null` clears it. Additive
|
|
@@ -62771,6 +62803,12 @@ var sessionsParams = {
|
|
|
62771
62803
|
* reading of limits nobody will look at. Absent: read them.
|
|
62772
62804
|
*/
|
|
62773
62805
|
limits: external_exports.boolean().optional(),
|
|
62806
|
+
/**
|
|
62807
|
+
* `true`: when a read of the tool's own command is in flight (an answer marked `provisional`), wait for it instead of
|
|
62808
|
+
* answering at once (#1623). The screen asks once more with this when the first answer was provisional, so it
|
|
62809
|
+
* receives the result when the command finishes and does not poll. Absent: answer at once.
|
|
62810
|
+
*/
|
|
62811
|
+
settle: external_exports.boolean().optional(),
|
|
62774
62812
|
...machineParam
|
|
62775
62813
|
}),
|
|
62776
62814
|
/**
|
|
@@ -62980,6 +63018,20 @@ var sessionsResults = {
|
|
|
62980
63018
|
supported: external_exports.boolean(),
|
|
62981
63019
|
reason: external_exports.string().optional(),
|
|
62982
63020
|
usage: UsageSnapshot.nullable(),
|
|
63021
|
+
/**
|
|
63022
|
+
* With `supported: false`: the tool has no way to report limits at all (its adapter has none), as against a read
|
|
63023
|
+
* that failed, which also answers `supported: false` (not logged in, a timeout). The screen draws only this one as
|
|
63024
|
+
* a plain dimmed ring (#1616); a host that predates it sends neither, and its failed and never answers look alike,
|
|
63025
|
+
* so both stay the unknown they were. Absent otherwise.
|
|
63026
|
+
*/
|
|
63027
|
+
notReported: external_exports.boolean().optional(),
|
|
63028
|
+
/**
|
|
63029
|
+
* The answer stands in for one the host is still reading (#1623): a tool with no limits whose own command was
|
|
63030
|
+
* slower than the host waits (`usage` holds nothing yet), or whose last answer is old and is being read again (`usage`
|
|
63031
|
+
* is that answer, with its `totals.asOf`). Asking again with `settle` returns what the command read. Absent
|
|
63032
|
+
* otherwise, and from a host that predates it.
|
|
63033
|
+
*/
|
|
63034
|
+
provisional: external_exports.boolean().optional(),
|
|
62983
63035
|
/**
|
|
62984
63036
|
* Set on the hub's answer for a linked machine when `usage` is this computer's own because both are logged in to
|
|
62985
63037
|
* the same account (#726); the meter says so. Absent otherwise, and from a host that predates it.
|
|
@@ -63508,7 +63560,15 @@ var HelloServer = external_exports.object({
|
|
|
63508
63560
|
*/
|
|
63509
63561
|
logon: HostLogon.optional().catch(void 0),
|
|
63510
63562
|
/** A sign-in's `serve` waiting to take over from this host, or one that gave up (#1224, `HostHandover`). Additive */
|
|
63511
|
-
handover: HostHandover.optional().catch(void 0)
|
|
63563
|
+
handover: HostHandover.optional().catch(void 0),
|
|
63564
|
+
/**
|
|
63565
|
+
* The turns this host continues because an update of this machine cut them (#1618, `host.stop { forUpdate }`), with when
|
|
63566
|
+
* its start took each up, on this host's clock: what tells a linked hub's caller of such a session that its turn goes on
|
|
63567
|
+
* rather than that the link dropped. Named from the start on, before any hello is answered, whether or not the Continue
|
|
63568
|
+
* is in yet; one that turns out lost is no longer named. An empty list says the host continues the turns an update cuts;
|
|
63569
|
+
* absent, it predates that (or the field could not be read). Additive
|
|
63570
|
+
*/
|
|
63571
|
+
updateContinued: external_exports.array(external_exports.object({ sessionId: external_exports.string(), at: external_exports.number() })).optional().catch(void 0)
|
|
63512
63572
|
});
|
|
63513
63573
|
var RpcRequest = external_exports.object({
|
|
63514
63574
|
kind: external_exports.literal("rpc"),
|
|
@@ -63968,6 +64028,12 @@ function activityCounts(s) {
|
|
|
63968
64028
|
}
|
|
63969
64029
|
return c;
|
|
63970
64030
|
}
|
|
64031
|
+
function turnCut(s) {
|
|
64032
|
+
return inTurn(s.state);
|
|
64033
|
+
}
|
|
64034
|
+
function cutTurns(s) {
|
|
64035
|
+
return s.sessions.flatMap((x) => x.id !== void 0 && turnCut(x) ? [x.id] : []);
|
|
64036
|
+
}
|
|
63971
64037
|
function sessionIdle(s, process4) {
|
|
63972
64038
|
if (s.pendingApproval) return { idle: false, reason: "approval" };
|
|
63973
64039
|
if (s.pendingQuestions.length > 0) return { idle: false, reason: "question" };
|
|
@@ -66441,7 +66507,8 @@ var HostServer = class _HostServer {
|
|
|
66441
66507
|
...this.opts.build ? { build: this.opts.build } : {},
|
|
66442
66508
|
...elevatedField(this.opts.elevated?.()),
|
|
66443
66509
|
...this.opts.logon?.() ? { logon: this.opts.logon() } : {},
|
|
66444
|
-
...this.opts.handover?.() ? { handover: this.opts.handover() } : {}
|
|
66510
|
+
...this.opts.handover?.() ? { handover: this.opts.handover() } : {},
|
|
66511
|
+
...this.opts.updateContinued ? { updateContinued: this.opts.updateContinued() } : {}
|
|
66445
66512
|
});
|
|
66446
66513
|
}
|
|
66447
66514
|
/**
|
|
@@ -105573,6 +105640,12 @@ var RestartPolicy = class {
|
|
|
105573
105640
|
* app-server), even though the screen said "starting next turn".
|
|
105574
105641
|
*/
|
|
105575
105642
|
restartAfterTurn = /* @__PURE__ */ new Set();
|
|
105643
|
+
/**
|
|
105644
|
+
* Sessions whose turn a linked hub's update cut, being woken to continue it (#1618, `continueCutTurns`): their new process
|
|
105645
|
+
* keeps the turn's state as it comes up, as a re-attached one does, rather than settling idle, so a hub reading the machine
|
|
105646
|
+
* between the wake and the note's turn does not read the cut turn as ended
|
|
105647
|
+
*/
|
|
105648
|
+
continuingTurns = /* @__PURE__ */ new Set();
|
|
105576
105649
|
/** Input arriving after a note completes waits for its deferred process replacement, including disposal. */
|
|
105577
105650
|
deferredRestarts = /* @__PURE__ */ new Map();
|
|
105578
105651
|
/** A new host served a different Centralu tool prefix; replace the old agent at its safe boundary. */
|
|
@@ -107404,6 +107477,7 @@ function interruptionWords(i) {
|
|
|
107404
107477
|
return `reconnecting to its API for ${Math.max(1, Math.round((Date.now() - i.at) / 6e4))} min`;
|
|
107405
107478
|
}
|
|
107406
107479
|
}
|
|
107480
|
+
var UPDATE_CONTINUE_LINE = "Centralu was updated during this turn, which stopped it; Centralu continued it on the new version.";
|
|
107407
107481
|
|
|
107408
107482
|
// packages/agent-host/src/sessions/error-kind.ts
|
|
107409
107483
|
var USAGE_LIMIT = /usage limit|rate.?limit|\b429\b|quota|limit reached|hit your limit/i;
|
|
@@ -110484,6 +110558,9 @@ Now:
|
|
|
110484
110558
|
${machineLine(machine)}` };
|
|
110485
110559
|
}
|
|
110486
110560
|
|
|
110561
|
+
// packages/agent-host/src/usage-clock.ts
|
|
110562
|
+
var isFromTheFuture = (at2, now) => at2 > now;
|
|
110563
|
+
|
|
110487
110564
|
// packages/agent-host/src/dev-services/store/usage-history.ts
|
|
110488
110565
|
var HOUR_MS = 60 * 6e4;
|
|
110489
110566
|
var BUCKET_COLUMNS = `hour, session_id AS sessionId, project_id AS projectId, tool, model, machine, requests, input,
|
|
@@ -110518,6 +110595,27 @@ var UsageRecords = class {
|
|
|
110518
110595
|
})();
|
|
110519
110596
|
return written;
|
|
110520
110597
|
}
|
|
110598
|
+
/**
|
|
110599
|
+
* Keeps the totals a tool that reports no limits last read (`usage_local_totals`, v64), replacing the ones before: a restart
|
|
110600
|
+
* then shows them at once with `totals.asOf`, while a fresh read goes on. An older read than the one kept is not stored.
|
|
110601
|
+
*/
|
|
110602
|
+
saveLocalTotals(tool, totals, now = Date.now()) {
|
|
110603
|
+
if (totals.asOf === void 0) return;
|
|
110604
|
+
const kept = this.db.prepare(`SELECT at FROM usage_local_totals WHERE tool = ?`).get(tool);
|
|
110605
|
+
if (kept && kept.at > totals.asOf && !isFromTheFuture(kept.at, now)) return;
|
|
110606
|
+
this.db.prepare(`INSERT INTO usage_local_totals (tool, at, totals) VALUES (?, ?, ?) ON CONFLICT(tool) DO UPDATE SET at = excluded.at, totals = excluded.totals`).run(tool, totals.asOf, JSON.stringify(totals));
|
|
110607
|
+
}
|
|
110608
|
+
/** The totals `saveLocalTotals` kept for a tool, or null: none, or not in the form this build reads (never a guess) */
|
|
110609
|
+
localTotals(tool) {
|
|
110610
|
+
const row = this.db.prepare(`SELECT at, totals FROM usage_local_totals WHERE tool = ?`).get(tool);
|
|
110611
|
+
if (!row) return null;
|
|
110612
|
+
try {
|
|
110613
|
+
const parsed = UsageSnapshot.shape.totals.safeParse(JSON.parse(row.totals));
|
|
110614
|
+
return parsed.success && parsed.data && parsed.data.asOf === row.at ? parsed.data : null;
|
|
110615
|
+
} catch {
|
|
110616
|
+
return null;
|
|
110617
|
+
}
|
|
110618
|
+
}
|
|
110521
110619
|
/**
|
|
110522
110620
|
* The readings with `from <= at < to`, oldest first, and for each series the last one before `from`: a reading is
|
|
110523
110621
|
* stored only when it changes, so the value in force when the range began is that older row. One tool's when `tool` is given.
|
|
@@ -113082,6 +113180,10 @@ var RemoteAgents = class {
|
|
|
113082
113180
|
* turned Auto reconnect off, or disconnected it, while it was down
|
|
113083
113181
|
*/
|
|
113084
113182
|
toldDown = /* @__PURE__ */ new Map();
|
|
113183
|
+
/** The machines whose link this hub's own update or rollback holds, until it is back (#1618, `linkChanged`) */
|
|
113184
|
+
updateHolds = /* @__PURE__ */ new Set();
|
|
113185
|
+
/** The turns an update of their machine cut whose callers were told it goes on (#1618), by session and when it was taken up: once each */
|
|
113186
|
+
toldCut = /* @__PURE__ */ new Set();
|
|
113085
113187
|
/** The cards of a caller's turn its caller was told of, once each (`<session>\0<turn>\0<request>`, #676) */
|
|
113086
113188
|
toldCards = /* @__PURE__ */ new Set();
|
|
113087
113189
|
/** Whose each child's turn is, as settled in this host (`Whose`); one not here is read from its record (`whoseOf`) */
|
|
@@ -113913,7 +114015,10 @@ var RemoteAgents = class {
|
|
|
113913
114015
|
*/
|
|
113914
114016
|
linkChanged(machine) {
|
|
113915
114017
|
const m = machine;
|
|
113916
|
-
if (!m || typeof m.id !== "string"
|
|
114018
|
+
if (!m || typeof m.id !== "string") return;
|
|
114019
|
+
if (updating(m.operation)) this.updateHolds.add(m.id);
|
|
114020
|
+
else if (m.status !== "connecting" && m.status !== "starting") this.updateHolds.delete(m.id);
|
|
114021
|
+
if (m.status === "connected") return;
|
|
113917
114022
|
const alone = this.host.linkedReader?.machineWaitsForPerson(m.id) === true;
|
|
113918
114023
|
const mayRecover = m.autoReconnect === false && !alone && m.status !== "versions_differ" && m.status !== "updating";
|
|
113919
114024
|
const name = typeof m.name === "string" && m.name ? m.name : m.id;
|
|
@@ -113932,6 +114037,7 @@ var RemoteAgents = class {
|
|
|
113932
114037
|
const told = this.toldDown.get(rec.sessionId);
|
|
113933
114038
|
if (!report || report.orchestratorId !== rec.callerId || report.paused || told !== void 0 && (told || !alone)) continue;
|
|
113934
114039
|
if (mayRecover) continue;
|
|
114040
|
+
if (this.updateHolds.has(m.id) && this.host.linkedReader?.continuesCutTurns?.(m.id) === true) continue;
|
|
113935
114041
|
this.toldDown.set(rec.sessionId, alone);
|
|
113936
114042
|
const why = !alone ? "It cannot be followed until the link is back: " : m.status === "disconnected" ? "The person disconnected the machine, so nothing here reconnects it: ask the person to connect it. It cannot be followed until then: " : "The machine's Auto reconnect is off, so nothing here reconnects it: ask the person to connect it. It cannot be followed until then: ";
|
|
113937
114043
|
const rest = why + "its report comes then, once its turn has ended, if that turn is still yours. agent_session status reads its state once the machine can be reached.";
|
|
@@ -113970,10 +114076,35 @@ var RemoteAgents = class {
|
|
|
113970
114076
|
if (!report.busy && !missed && !held3) continue;
|
|
113971
114077
|
const now = await reader.read(rec.sessionId, 1);
|
|
113972
114078
|
if (!now.ok || this.host.children.linkedReports.get(rec.sessionId) !== report || !this.gateNow(rec).owned) continue;
|
|
113973
|
-
if (inTurn(now.state ?? "idle"))
|
|
113974
|
-
|
|
114079
|
+
if (inTurn(now.state ?? "idle")) {
|
|
114080
|
+
report.busy = true;
|
|
114081
|
+
this.tellContinued(rec);
|
|
114082
|
+
} else this.host.settleLinkedTurn(rec.sessionId);
|
|
113975
114083
|
}
|
|
113976
114084
|
}
|
|
114085
|
+
/**
|
|
114086
|
+
* A child whose turn an update of its machine cut, found in a turn again once the link is back: the host that update
|
|
114087
|
+
* started continued it (#1618). Its caller is told so once, and that the report it is owed is that turn's end, so it
|
|
114088
|
+
* neither waits on a link that came back nor continues the child by hand a second time.
|
|
114089
|
+
*
|
|
114090
|
+
* Whatever the order of the Continue and the reconnect (#1626 review): the machine's host names each turn it continues
|
|
114091
|
+
* from its start on, before it answers any hello, not once the Continue is in (`SessionManager.updateContinued`), so a
|
|
114092
|
+
* link back while the Continue is still on its way is told it too
|
|
114093
|
+
*/
|
|
114094
|
+
tellContinued(rec) {
|
|
114095
|
+
const at2 = this.host.linkedReader?.continuedAfterUpdate?.(rec.sessionId);
|
|
114096
|
+
if (at2 == null || this.toldCut.has(`${rec.sessionId}@${at2}`)) return;
|
|
114097
|
+
this.toldCut.add(`${rec.sessionId}@${at2}`);
|
|
114098
|
+
const rest = "and Centralu continued it there on the new version. Its report comes when that turn ends; nothing is needed from you now.";
|
|
114099
|
+
void this.host.tellSession(
|
|
114100
|
+
rec.callerId,
|
|
114101
|
+
`[Centralu] An update of Centralu on its machine cut the turn "${rec.name}" [${rec.sessionId}] was running there, ${rest}`,
|
|
114102
|
+
{ sessionId: rec.sessionId, name: rec.name },
|
|
114103
|
+
true,
|
|
114104
|
+
// What the model reads (#1186): all of it but the name, which is not Centralu's words (#120)
|
|
114105
|
+
`[Centralu] An update of Centralu on its machine cut the turn the session [${rec.sessionId}] was running there, ${rest}`
|
|
114106
|
+
);
|
|
114107
|
+
}
|
|
113977
114108
|
/**
|
|
113978
114109
|
* Whether a message the child took was sent by this hub over this link (#1038 review): the machine records a hub's
|
|
113979
114110
|
* instruction from `HUB_SENDER`, which reaches the hub qualified with that machine's id. Another machine's prefix is no
|
|
@@ -114080,6 +114211,9 @@ var RemoteAgents = class {
|
|
|
114080
114211
|
return { error: `${machine.name} has no checkout of ${key}, so nothing was started there.${listed2 ? ` Its projects: ${listed2}.` : " It has no projects."} The person can clone it there first.` };
|
|
114081
114212
|
}
|
|
114082
114213
|
};
|
|
114214
|
+
function updating(op) {
|
|
114215
|
+
return op?.kind === "update" || op?.kind === "rollback";
|
|
114216
|
+
}
|
|
114083
114217
|
|
|
114084
114218
|
// packages/agent-host/src/sessions/delegation-flow.ts
|
|
114085
114219
|
var DelegationFlow = class {
|
|
@@ -114162,8 +114296,9 @@ var DelegationFlow = class {
|
|
|
114162
114296
|
if (!joinedTurn) this.host.children.ownTurn(sessionId, callerId, false);
|
|
114163
114297
|
void d2.outcome.then((r) => {
|
|
114164
114298
|
if (this.host.agentRuns.get(sessionId) === wait) this.host.agentRuns.delete(sessionId);
|
|
114165
|
-
if (
|
|
114166
|
-
if (!
|
|
114299
|
+
if (this.host.closing) return;
|
|
114300
|
+
if (!joinedTurn) this.host.children.releaseTurn(sessionId, d2.callerId);
|
|
114301
|
+
if (!agent && r.ok && !joinedTurn && this.host.meta.has(d2.callerId) && !this.host.children.pausedReports.has(sessionId)) {
|
|
114167
114302
|
const left = this.host.ownTasksRunning(sessionId);
|
|
114168
114303
|
if (left.length) {
|
|
114169
114304
|
this.host.children.markReport(sessionId, d2.callerId);
|
|
@@ -114269,6 +114404,7 @@ var NOT_READ = "(not read: the session's last turn was the person's; read_sessio
|
|
|
114269
114404
|
function outcomeWord(work, stop, ending) {
|
|
114270
114405
|
if (work && "tookOver" in work) return "took_over";
|
|
114271
114406
|
if (work && "swapped" in work) return "swapped";
|
|
114407
|
+
if (work && "resumed" in work) return "resumed";
|
|
114272
114408
|
if (work && ("machineRemoved" in work || "linkLost" in work)) return "stopped";
|
|
114273
114409
|
if (work && ("paused" in work || "waitingOn" in work)) return "paused";
|
|
114274
114410
|
if (work && "settled" in work) return "settled";
|
|
@@ -114319,6 +114455,9 @@ function machineRemovedReportLine(machine, paused, name = STORED) {
|
|
|
114319
114455
|
function swappedReportLine() {
|
|
114320
114456
|
return "[Centralu] The assigned work's session ended a turn while Centralu was restarting, so that end was not followed. It is most likely the end of the work you gave it; check its last response before relying on it.\n";
|
|
114321
114457
|
}
|
|
114458
|
+
function resumedReportLine(owed) {
|
|
114459
|
+
return "[Centralu] The assigned work's turn was cut short by an update of Centralu on its machine, and Centralu continued it there on the new version. " + (owed ? "Its report follows when that turn ends; this is not it, and nothing is needed from you now.\n" : "Its end is not reported to you; read the session to follow it.\n");
|
|
114460
|
+
}
|
|
114322
114461
|
function tookOverReportLine(paused, name = STORED) {
|
|
114323
114462
|
return "[Centralu] The person took this session over; its later turns are theirs, and none of them will be reported to you. " + (paused.length ? `The work you assigned had paused on its own background work (${name.tasks(paused)}), and no final report follows.
|
|
114324
114463
|
` : "The work you assigned was still running, and no report of it follows.\n");
|
|
@@ -114515,6 +114654,19 @@ var ChildReports = class {
|
|
|
114515
114654
|
const { name, project: project2 } = this.reportedAs(m);
|
|
114516
114655
|
await this.deliverReport(asker, sessionId, name, project2, previewOf(this.host.store.messages, sessionId, 600), { type: stop.reason === "network" ? "error" : "state_change" }, void 0, this.answerSeq(sessionId));
|
|
114517
114656
|
}
|
|
114657
|
+
/**
|
|
114658
|
+
* A turn of this session that a linked hub's update cut, and this host continued (#1618): its requester hears it once,
|
|
114659
|
+
* so it does not read the session's stop as the end of the work, nor continue it a second time by hand. The report it is
|
|
114660
|
+
* owed, if one is, stays owed: the continued turn's end is it, so it is neither lost nor sent twice
|
|
114661
|
+
*/
|
|
114662
|
+
async tellCutByUpdate(sessionId, requester) {
|
|
114663
|
+
const m = this.host.meta.get(sessionId);
|
|
114664
|
+
if (!m || !this.host.meta.has(requester) || this.host.handingOver) return;
|
|
114665
|
+
const { name, project: project2 } = this.reportedAs(m);
|
|
114666
|
+
const owed = this.host.children.awaitingReport.get(sessionId) === requester;
|
|
114667
|
+
console.error(`[sessions] ${sessionId.slice(0, 8)}: an update cut its turn and it was continued; telling ${requester.slice(0, 8)}`);
|
|
114668
|
+
await this.deliverReport(requester, sessionId, name, project2, previewOf(this.host.store.messages, sessionId, 600), { type: "state_change" }, { resumed: true, owed }, this.answerSeq(sessionId));
|
|
114669
|
+
}
|
|
114518
114670
|
/**
|
|
114519
114671
|
* A report a host swap held back (#646, #1186): its session ended a turn while the old host let go, and the mark the
|
|
114520
114672
|
* store kept is dropped at start (`restoreReports`), so a later turn is never reported as the work. The caller is
|
|
@@ -114567,7 +114719,7 @@ ${read} shows what the person and the session did since.`;
|
|
|
114567
114719
|
async deliverReport(orchestratorId, sessionId, name, project2, preview, ending, work, answerSeq, part) {
|
|
114568
114720
|
const stop = this.host.meta.get(sessionId)?.interruption;
|
|
114569
114721
|
const cutShort = stop?.reason === "network" || stop?.reason === "interrupted" || stop?.reason === "quit";
|
|
114570
|
-
const outcomeOf2 = (name2) => work && "tookOver" in work ? tookOverReportLine(work.paused, name2) : work && "swapped" in work ? swappedReportLine() : work && "machineRemoved" in work ? machineRemovedReportLine(work.machineRemoved, work.paused, name2) : work && "linkLost" in work ? linkLostReportLine(work.linkLost, work.paused, name2) : work && "paused" in work ? pausedReportLine(work.paused, work.stoppable ? this.agentCall(orchestratorId, { action: "stop" }) : void 0, name2) : work && "settled" in work ? settledReportLine(work.settled, name2) : work && "waitingOn" in work ? waitingReportLine(work.waitingOn) : stop?.reason === "network" ? `[Centralu] The assigned work stopped before finishing: a network error ended its turn.
|
|
114722
|
+
const outcomeOf2 = (name2) => work && "tookOver" in work ? tookOverReportLine(work.paused, name2) : work && "swapped" in work ? swappedReportLine() : work && "resumed" in work ? resumedReportLine(work.owed) : work && "machineRemoved" in work ? machineRemovedReportLine(work.machineRemoved, work.paused, name2) : work && "linkLost" in work ? linkLostReportLine(work.linkLost, work.paused, name2) : work && "paused" in work ? pausedReportLine(work.paused, work.stoppable ? this.agentCall(orchestratorId, { action: "stop" }) : void 0, name2) : work && "settled" in work ? settledReportLine(work.settled, name2) : work && "waitingOn" in work ? waitingReportLine(work.waitingOn) : stop?.reason === "network" ? `[Centralu] The assigned work stopped before finishing: a network error ended its turn.
|
|
114571
114723
|
` : stop?.reason === "interrupted" ? `[Centralu] The assigned work stopped before finishing: its tool interrupted the turn.
|
|
114572
114724
|
` : stop?.reason === "quit" ? `[Centralu] The assigned work stopped before finishing: Centralu quit or restarted during its turn.
|
|
114573
114725
|
` : ending.type === "error" ? `[Centralu] The assigned work failed.
|
|
@@ -114601,6 +114753,7 @@ ${preview || "(no content)"}
|
|
|
114601
114753
|
If you need more, you can read that session's recent conversation with read_session.` + cleanUp;
|
|
114602
114754
|
const holding = !!work && "paused" in work && !("tookOver" in work) && !("machineRemoved" in work) && !("linkLost" in work);
|
|
114603
114755
|
const handedOn = !!work && "waitingOn" in work;
|
|
114756
|
+
const goesOn = !!work && "resumed" in work;
|
|
114604
114757
|
const reported = this.toMailbox(orchestratorId, sessionId, {
|
|
114605
114758
|
outcome: outcomeWord(work, stop, ending),
|
|
114606
114759
|
body: forModel(true),
|
|
@@ -114608,7 +114761,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
114608
114761
|
...kind ? { error: kindWord(kind) } : {},
|
|
114609
114762
|
...holding ? { paused: work.paused.length } : {},
|
|
114610
114763
|
...part?.wrote || part?.added ? { personNote: true } : {},
|
|
114611
|
-
then: holding || handedOn ? "running" : "review"
|
|
114764
|
+
then: holding || handedOn || goesOn ? "running" : "review"
|
|
114612
114765
|
});
|
|
114613
114766
|
if (reported === "mailed" || reported === "closed") return;
|
|
114614
114767
|
await this.tellSession(
|
|
@@ -119744,7 +119897,7 @@ function unfitError(reason) {
|
|
|
119744
119897
|
}
|
|
119745
119898
|
|
|
119746
119899
|
// packages/agent-host/src/sessions/session-start.ts
|
|
119747
|
-
import { randomUUID as
|
|
119900
|
+
import { randomUUID as randomUUID20 } from "node:crypto";
|
|
119748
119901
|
|
|
119749
119902
|
// packages/agent-host/src/sessions/opened-settings.ts
|
|
119750
119903
|
function adoptOpenedSettings(handle, record2, launched, imported) {
|
|
@@ -119774,10 +119927,112 @@ function remember(map2, key, value, cap = FOLDER_CACHE_KEYS) {
|
|
|
119774
119927
|
}
|
|
119775
119928
|
}
|
|
119776
119929
|
|
|
119930
|
+
// packages/agent-host/src/adapters/input-receipts.ts
|
|
119931
|
+
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
119932
|
+
var UUID_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
119933
|
+
function isUuid(s) {
|
|
119934
|
+
return UUID_FORMAT.test(s);
|
|
119935
|
+
}
|
|
119936
|
+
var CENTRALU_INPUT_MARK = "ce47a1ce47a1";
|
|
119937
|
+
function centraluInputId() {
|
|
119938
|
+
const id3 = randomUUID19();
|
|
119939
|
+
return `${id3.slice(0, 8)}-${id3.slice(9, 13)}-${id3.slice(14, 18)}-${id3.slice(19, 23)}-${CENTRALU_INPUT_MARK}`;
|
|
119940
|
+
}
|
|
119941
|
+
function isCentraluInputId(id3) {
|
|
119942
|
+
return typeof id3 === "string" && id3.length === 36 && id3.toLowerCase().endsWith(`-${CENTRALU_INPUT_MARK}`);
|
|
119943
|
+
}
|
|
119944
|
+
var UPDATE_CONTINUE_TEXT = "[Centralu] Centralu on this machine was updated while your turn was running, which stopped the turn before it finished. Continue where you stopped. Check what already happened before repeating a step.";
|
|
119945
|
+
function isUpdateContinueNote(text2) {
|
|
119946
|
+
return text2.trim() === UPDATE_CONTINUE_TEXT;
|
|
119947
|
+
}
|
|
119948
|
+
var InputReceipts = class {
|
|
119949
|
+
pending = /* @__PURE__ */ new Map();
|
|
119950
|
+
ended = /* @__PURE__ */ new Set();
|
|
119951
|
+
/** `named`: the id the caller gave the input, which the tool then names back; none, one is made (with Centralu's mark) */
|
|
119952
|
+
create(observer, named) {
|
|
119953
|
+
const id3 = named ?? centraluInputId();
|
|
119954
|
+
let resolve21;
|
|
119955
|
+
let reject;
|
|
119956
|
+
const done2 = new Promise((yes, no2) => {
|
|
119957
|
+
resolve21 = yes;
|
|
119958
|
+
reject = no2;
|
|
119959
|
+
});
|
|
119960
|
+
done2.catch(() => {
|
|
119961
|
+
});
|
|
119962
|
+
const receipt = { id: id3, done: done2, cancelled: false, completed: false };
|
|
119963
|
+
this.pending.set(id3, { receipt, resolve: resolve21, reject, observer });
|
|
119964
|
+
return receipt;
|
|
119965
|
+
}
|
|
119966
|
+
get size() {
|
|
119967
|
+
return this.pending.size;
|
|
119968
|
+
}
|
|
119969
|
+
get ids() {
|
|
119970
|
+
return [...this.pending.keys()];
|
|
119971
|
+
}
|
|
119972
|
+
/** The tool took the input (once): the observer is told before the turn is recorded */
|
|
119973
|
+
accept(id3) {
|
|
119974
|
+
const p = this.pending.get(id3);
|
|
119975
|
+
if (!p || p.accepted) return;
|
|
119976
|
+
p.accepted = true;
|
|
119977
|
+
p.observer?.accepted?.();
|
|
119978
|
+
}
|
|
119979
|
+
/** Codex's turn/start response can arrive after that turn's completion notification. */
|
|
119980
|
+
bind(receipt, turn) {
|
|
119981
|
+
const p = this.pending.get(receipt.id);
|
|
119982
|
+
if (!p) return;
|
|
119983
|
+
this.accept(receipt.id);
|
|
119984
|
+
p.turn = turn;
|
|
119985
|
+
if (this.ended.has(turn)) this.finish(receipt.id);
|
|
119986
|
+
}
|
|
119987
|
+
endTurn(turn) {
|
|
119988
|
+
if (this.pending.size) this.ended.add(turn);
|
|
119989
|
+
for (const [id3, p] of this.pending) if (p.turn === turn) this.finish(id3);
|
|
119990
|
+
}
|
|
119991
|
+
/** Retries can reorder inputs; only UUIDs actually reported as consumed have completed. */
|
|
119992
|
+
complete(ids) {
|
|
119993
|
+
for (const id3 of ids) this.finish(id3);
|
|
119994
|
+
}
|
|
119995
|
+
/** These inputs did not finish (a stopped turn, a process gone): each one's observer is told, the others go on waiting */
|
|
119996
|
+
fail(ids, error51) {
|
|
119997
|
+
for (const id3 of ids) {
|
|
119998
|
+
const p = this.pending.get(id3);
|
|
119999
|
+
if (!p) continue;
|
|
120000
|
+
this.pending.delete(id3);
|
|
120001
|
+
if (!this.pending.size) this.ended.clear();
|
|
120002
|
+
p.receipt.cancelled = true;
|
|
120003
|
+
p.observer?.fail(error51);
|
|
120004
|
+
p.reject(error51);
|
|
120005
|
+
}
|
|
120006
|
+
}
|
|
120007
|
+
failAll(error51) {
|
|
120008
|
+
const turns = [...new Set([...this.pending.values()].flatMap((p) => p.turn ? [p.turn] : []))];
|
|
120009
|
+
const pending = [...this.pending.values()];
|
|
120010
|
+
this.pending.clear();
|
|
120011
|
+
this.ended.clear();
|
|
120012
|
+
for (const p of pending) {
|
|
120013
|
+
p.receipt.cancelled = true;
|
|
120014
|
+
p.observer?.fail(error51);
|
|
120015
|
+
p.reject(error51);
|
|
120016
|
+
}
|
|
120017
|
+
return turns;
|
|
120018
|
+
}
|
|
120019
|
+
finish(id3) {
|
|
120020
|
+
const p = this.pending.get(id3);
|
|
120021
|
+
if (!p) return;
|
|
120022
|
+
this.pending.delete(id3);
|
|
120023
|
+
if (!this.pending.size) this.ended.clear();
|
|
120024
|
+
if (!p.accepted) p.observer?.accepted?.();
|
|
120025
|
+
p.receipt.completed = true;
|
|
120026
|
+
p.observer?.complete();
|
|
120027
|
+
p.resolve();
|
|
120028
|
+
}
|
|
120029
|
+
};
|
|
120030
|
+
|
|
119777
120031
|
// packages/agent-host/src/sessions/external-conversations.ts
|
|
119778
120032
|
var HISTORY_LIMIT = 200;
|
|
119779
120033
|
var SYNC_LIMIT = 600;
|
|
119780
120034
|
var externalSyncedKey = (sessionId) => `external_synced:${sessionId}`;
|
|
120035
|
+
var ownNote = (h) => h.role === "user" && isUpdateContinueNote(h.text);
|
|
119781
120036
|
function historyRow(sessionId, seq, h, fallbackTs) {
|
|
119782
120037
|
const ts = h.ts ?? fallbackTs;
|
|
119783
120038
|
if (h.role === "system") {
|
|
@@ -119914,39 +120169,32 @@ var ExternalConversations = class {
|
|
|
119914
120169
|
return parts.length > 0 ? parts.join("") : void 0;
|
|
119915
120170
|
};
|
|
119916
120171
|
const lastText = lastMessageText()?.trim();
|
|
120172
|
+
const said = lastText ? history.findLastIndex((h) => h.role !== "system" && h.text.trim() === lastText) : -1;
|
|
120173
|
+
const wrote = history.findLastIndex((h) => h.role === "user" && h.sent === true);
|
|
119917
120174
|
let start2 = -1;
|
|
119918
|
-
if (
|
|
119919
|
-
|
|
119920
|
-
const h = history[i];
|
|
119921
|
-
if (h.role !== "system" && h.text.trim() === lastText) {
|
|
119922
|
-
start2 = i + 1;
|
|
119923
|
-
break;
|
|
119924
|
-
}
|
|
119925
|
-
}
|
|
119926
|
-
} else if (ours.length === 0) {
|
|
119927
|
-
start2 = 0;
|
|
119928
|
-
}
|
|
120175
|
+
if (ours.length === 0) start2 = 0;
|
|
120176
|
+
else if (said >= 0 || !lastText && wrote >= 0) start2 = Math.max(said, wrote) + 1;
|
|
119929
120177
|
if (start2 < 0 || start2 >= history.length) {
|
|
119930
120178
|
mark();
|
|
119931
120179
|
return 0;
|
|
119932
120180
|
}
|
|
119933
120181
|
const known = /* @__PURE__ */ new Map();
|
|
119934
|
-
|
|
120182
|
+
let trailing = true;
|
|
120183
|
+
for (let i = ours.length - 1; i >= 0; i--) {
|
|
120184
|
+
if (ours[i].kind === "text") trailing = false;
|
|
119935
120185
|
const key2 = eventKey(ours[i]);
|
|
119936
|
-
if (key2) known.set(key2, (known.get(key2) ?? 0) + 1);
|
|
120186
|
+
if (key2 && (key2 !== "compaction" || trailing)) known.set(key2, (known.get(key2) ?? 0) + 1);
|
|
119937
120187
|
}
|
|
119938
120188
|
let end = start2;
|
|
119939
120189
|
while (history[end]?.role === "system") end++;
|
|
119940
|
-
const fresh =
|
|
119941
|
-
|
|
119942
|
-
|
|
119943
|
-
|
|
119944
|
-
|
|
119945
|
-
|
|
119946
|
-
|
|
119947
|
-
|
|
119948
|
-
...history.slice(end)
|
|
119949
|
-
];
|
|
120190
|
+
const fresh = history.slice(start2).filter((h, i) => {
|
|
120191
|
+
if (ownNote(h)) return false;
|
|
120192
|
+
const key2 = eventKey(historyRow(info.id, 0, h, 0));
|
|
120193
|
+
const left = key2 && (key2 !== "compaction" || start2 + i < end) ? known.get(key2) ?? 0 : 0;
|
|
120194
|
+
if (!key2 || left === 0) return true;
|
|
120195
|
+
known.set(key2, left - 1);
|
|
120196
|
+
return false;
|
|
120197
|
+
});
|
|
119950
120198
|
if (fresh.length === 0) {
|
|
119951
120199
|
mark();
|
|
119952
120200
|
return 0;
|
|
@@ -119983,6 +120231,7 @@ var ExternalConversations = class {
|
|
|
119983
120231
|
});
|
|
119984
120232
|
return;
|
|
119985
120233
|
}
|
|
120234
|
+
history = history.filter((h) => !ownNote(h));
|
|
119986
120235
|
if (history.length === 0) return;
|
|
119987
120236
|
const base2 = this.host.store.messages.nextSeq(info.id);
|
|
119988
120237
|
const rows3 = history.map((h, i) => historyRow(info.id, base2 + i, h, info.createdAt));
|
|
@@ -120099,7 +120348,7 @@ var SessionStarts = class {
|
|
|
120099
120348
|
throw rpcError(`This conversation is already open in the "${holder.name}" session \u2014 continue there`);
|
|
120100
120349
|
}
|
|
120101
120350
|
}
|
|
120102
|
-
const id3 =
|
|
120351
|
+
const id3 = randomUUID20();
|
|
120103
120352
|
const permissionPreset = presetFromHub(params, this.host.uiPreferences());
|
|
120104
120353
|
let worktree = null;
|
|
120105
120354
|
let setupProblems = [];
|
|
@@ -120541,6 +120790,9 @@ var SessionStarts = class {
|
|
|
120541
120790
|
this.host.emit({ type: "state_change", sessionId, state: m.state, reason: "reattached" });
|
|
120542
120791
|
if (kept === null) this.host.restartPolicy.backgroundUnknown.set(sessionId, Date.now());
|
|
120543
120792
|
else if (kept.length > 0) this.host.onEvent({ type: "background_tasks", sessionId, live: kept });
|
|
120793
|
+
} else if (this.host.restartPolicy.continuingTurns.has(sessionId)) {
|
|
120794
|
+
this.host.store.sessions.upsertSession(m);
|
|
120795
|
+
this.host.emit({ type: "state_change", sessionId, state: m.state, reason: "continued" });
|
|
120544
120796
|
} else {
|
|
120545
120797
|
this.host.settleState(m, { type: "state_change", sessionId, state: "idle", reason: "resumed" });
|
|
120546
120798
|
}
|
|
@@ -120591,7 +120843,6 @@ function startFit(adapter, opts, sink) {
|
|
|
120591
120843
|
}
|
|
120592
120844
|
|
|
120593
120845
|
// packages/agent-host/src/sessions/session-events.ts
|
|
120594
|
-
import { randomUUID as randomUUID20 } from "node:crypto";
|
|
120595
120846
|
import { relative as relative5, resolve as resolve13, sep as sep5 } from "node:path";
|
|
120596
120847
|
|
|
120597
120848
|
// packages/agent-host/src/dev-services/git-attrib.ts
|
|
@@ -121603,7 +121854,7 @@ ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
|
|
|
121603
121854
|
${adapterText}` : adapterText;
|
|
121604
121855
|
const joins = inTurn(m.state) && this.host.joinsRunningTurn(sessionId);
|
|
121605
121856
|
const hubInput = from?.sessionId === HUB_SENDER;
|
|
121606
|
-
const inputId = joins || hubInput ?
|
|
121857
|
+
const inputId = joins || hubInput ? centraluInputId() : void 0;
|
|
121607
121858
|
this.host.children.noteMessage(sessionId, !from && !fromApp2 && !fromScript ? "person" : "work", inTurn(m.state), this.host.joinsRunningTurn(sessionId), intent === "task", inputId, relayed ? "report" : fromApp2 ? "app" : "caller");
|
|
121608
121859
|
if (inputId && from && hubInput) this.host.children.noteInputOrigin(sessionId, inputId, { seq, from });
|
|
121609
121860
|
this.host.cardRests.turnAsked(sessionId);
|
|
@@ -121644,7 +121895,8 @@ ${adapterText}` : adapterText;
|
|
|
121644
121895
|
this.host.cardRests.turnAsked(sessionId);
|
|
121645
121896
|
await takeInput(handle, input.text, late);
|
|
121646
121897
|
}
|
|
121647
|
-
|
|
121898
|
+
if (input.skipLine?.()) return;
|
|
121899
|
+
this.onEvent({ type: "notice", sessionId, level: "info", from: "Centralu", label: input.label ?? "mail", text: input.line });
|
|
121648
121900
|
}
|
|
121649
121901
|
/**
|
|
121650
121902
|
* Centralu's own compact command (#1451): the tool's `/compact` as a turn of its own, in no one's name and with no user row,
|
|
@@ -122272,97 +122524,8 @@ function spawnLocalAgent(spec, os2) {
|
|
|
122272
122524
|
return LocalAgentProcess.spawn(spec, os2);
|
|
122273
122525
|
}
|
|
122274
122526
|
|
|
122275
|
-
// packages/agent-host/src/adapters/input-receipts.ts
|
|
122276
|
-
import { randomUUID as randomUUID21 } from "node:crypto";
|
|
122277
|
-
var UUID_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
122278
|
-
function isUuid(s) {
|
|
122279
|
-
return UUID_FORMAT.test(s);
|
|
122280
|
-
}
|
|
122281
|
-
var InputReceipts = class {
|
|
122282
|
-
pending = /* @__PURE__ */ new Map();
|
|
122283
|
-
ended = /* @__PURE__ */ new Set();
|
|
122284
|
-
/** `named`: the id the caller gave the input, which the tool then names back; none, one is made */
|
|
122285
|
-
create(observer, named) {
|
|
122286
|
-
const id3 = named ?? randomUUID21();
|
|
122287
|
-
let resolve21;
|
|
122288
|
-
let reject;
|
|
122289
|
-
const done2 = new Promise((yes, no2) => {
|
|
122290
|
-
resolve21 = yes;
|
|
122291
|
-
reject = no2;
|
|
122292
|
-
});
|
|
122293
|
-
done2.catch(() => {
|
|
122294
|
-
});
|
|
122295
|
-
const receipt = { id: id3, done: done2, cancelled: false, completed: false };
|
|
122296
|
-
this.pending.set(id3, { receipt, resolve: resolve21, reject, observer });
|
|
122297
|
-
return receipt;
|
|
122298
|
-
}
|
|
122299
|
-
get size() {
|
|
122300
|
-
return this.pending.size;
|
|
122301
|
-
}
|
|
122302
|
-
get ids() {
|
|
122303
|
-
return [...this.pending.keys()];
|
|
122304
|
-
}
|
|
122305
|
-
/** The tool took the input (once): the observer is told before the turn is recorded */
|
|
122306
|
-
accept(id3) {
|
|
122307
|
-
const p = this.pending.get(id3);
|
|
122308
|
-
if (!p || p.accepted) return;
|
|
122309
|
-
p.accepted = true;
|
|
122310
|
-
p.observer?.accepted?.();
|
|
122311
|
-
}
|
|
122312
|
-
/** Codex's turn/start response can arrive after that turn's completion notification. */
|
|
122313
|
-
bind(receipt, turn) {
|
|
122314
|
-
const p = this.pending.get(receipt.id);
|
|
122315
|
-
if (!p) return;
|
|
122316
|
-
this.accept(receipt.id);
|
|
122317
|
-
p.turn = turn;
|
|
122318
|
-
if (this.ended.has(turn)) this.finish(receipt.id);
|
|
122319
|
-
}
|
|
122320
|
-
endTurn(turn) {
|
|
122321
|
-
if (this.pending.size) this.ended.add(turn);
|
|
122322
|
-
for (const [id3, p] of this.pending) if (p.turn === turn) this.finish(id3);
|
|
122323
|
-
}
|
|
122324
|
-
/** Retries can reorder inputs; only UUIDs actually reported as consumed have completed. */
|
|
122325
|
-
complete(ids) {
|
|
122326
|
-
for (const id3 of ids) this.finish(id3);
|
|
122327
|
-
}
|
|
122328
|
-
/** These inputs did not finish (a stopped turn, a process gone): each one's observer is told, the others go on waiting */
|
|
122329
|
-
fail(ids, error51) {
|
|
122330
|
-
for (const id3 of ids) {
|
|
122331
|
-
const p = this.pending.get(id3);
|
|
122332
|
-
if (!p) continue;
|
|
122333
|
-
this.pending.delete(id3);
|
|
122334
|
-
if (!this.pending.size) this.ended.clear();
|
|
122335
|
-
p.receipt.cancelled = true;
|
|
122336
|
-
p.observer?.fail(error51);
|
|
122337
|
-
p.reject(error51);
|
|
122338
|
-
}
|
|
122339
|
-
}
|
|
122340
|
-
failAll(error51) {
|
|
122341
|
-
const turns = [...new Set([...this.pending.values()].flatMap((p) => p.turn ? [p.turn] : []))];
|
|
122342
|
-
const pending = [...this.pending.values()];
|
|
122343
|
-
this.pending.clear();
|
|
122344
|
-
this.ended.clear();
|
|
122345
|
-
for (const p of pending) {
|
|
122346
|
-
p.receipt.cancelled = true;
|
|
122347
|
-
p.observer?.fail(error51);
|
|
122348
|
-
p.reject(error51);
|
|
122349
|
-
}
|
|
122350
|
-
return turns;
|
|
122351
|
-
}
|
|
122352
|
-
finish(id3) {
|
|
122353
|
-
const p = this.pending.get(id3);
|
|
122354
|
-
if (!p) return;
|
|
122355
|
-
this.pending.delete(id3);
|
|
122356
|
-
if (!this.pending.size) this.ended.clear();
|
|
122357
|
-
if (!p.accepted) p.observer?.accepted?.();
|
|
122358
|
-
p.receipt.completed = true;
|
|
122359
|
-
p.observer?.complete();
|
|
122360
|
-
p.resolve();
|
|
122361
|
-
}
|
|
122362
|
-
};
|
|
122363
|
-
|
|
122364
122527
|
// packages/agent-host/src/adapters/session-kit.ts
|
|
122365
|
-
import { randomUUID as
|
|
122528
|
+
import { randomUUID as randomUUID21 } from "node:crypto";
|
|
122366
122529
|
var notices = 0;
|
|
122367
122530
|
function adapterNotice(sessionId, text2) {
|
|
122368
122531
|
return { type: "message_delta", sessionId, role: "assistant", messageId: `centralu-notice-${++notices}`, text: text2 };
|
|
@@ -122410,7 +122573,7 @@ function stopNotConfirmed(sessionId, text2) {
|
|
|
122410
122573
|
}
|
|
122411
122574
|
var CardIds = class {
|
|
122412
122575
|
counter = 0;
|
|
122413
|
-
tag =
|
|
122576
|
+
tag = randomUUID21().slice(0, 8);
|
|
122414
122577
|
next(prefix) {
|
|
122415
122578
|
return `${prefix}-${this.tag}-${++this.counter}`;
|
|
122416
122579
|
}
|
|
@@ -124305,10 +124468,14 @@ var AcpAdapter = class {
|
|
|
124305
124468
|
this.profile = profile;
|
|
124306
124469
|
this.spawn = spawn6;
|
|
124307
124470
|
this.stopConfirmMs = stopConfirmMs;
|
|
124471
|
+
const localUsage = profile.localUsage?.bind(profile);
|
|
124472
|
+
if (localUsage) this.localUsage = localUsage;
|
|
124308
124473
|
}
|
|
124309
124474
|
profile;
|
|
124310
124475
|
spawn;
|
|
124311
124476
|
stopConfirmMs;
|
|
124477
|
+
/** What the tool's own command says about its usage (the profile's `localUsage`), when it has one */
|
|
124478
|
+
localUsage;
|
|
124312
124479
|
get descriptor() {
|
|
124313
124480
|
return this.profile.descriptor;
|
|
124314
124481
|
}
|
|
@@ -125395,7 +125562,7 @@ var AntigravityAdapter = class extends AcpAdapter {
|
|
|
125395
125562
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
125396
125563
|
import { mkdtemp as mkdtemp3, readdir as readdir10, rm as rm8, stat as stat8 } from "node:fs/promises";
|
|
125397
125564
|
import { join as join43 } from "node:path";
|
|
125398
|
-
import { randomUUID as
|
|
125565
|
+
import { randomUUID as randomUUID23 } from "node:crypto";
|
|
125399
125566
|
import { execFile as execFile6 } from "node:child_process";
|
|
125400
125567
|
import { promisify as promisify5 } from "node:util";
|
|
125401
125568
|
|
|
@@ -126058,20 +126225,58 @@ function contentBlocks(m) {
|
|
|
126058
126225
|
|
|
126059
126226
|
// packages/agent-host/src/adapters/images.ts
|
|
126060
126227
|
import { readFile as readFile6, stat as stat6 } from "node:fs/promises";
|
|
126061
|
-
var
|
|
126062
|
-
|
|
126063
|
-
|
|
126064
|
-
|
|
126065
|
-
|
|
126066
|
-
|
|
126067
|
-
|
|
126068
|
-
|
|
126228
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp"]);
|
|
126229
|
+
var startsWith = (buf, bytes2, at2 = 0) => bytes2.every((b2, i) => buf[at2 + i] === b2);
|
|
126230
|
+
function gifReachesTrailer(buf) {
|
|
126231
|
+
const n = buf.length;
|
|
126232
|
+
const at2 = (i8) => buf[i8] ?? 0;
|
|
126233
|
+
const table = (flags) => flags & 128 ? 3 * 2 ** ((flags & 7) + 1) : 0;
|
|
126234
|
+
const skipSubBlocks = (from) => {
|
|
126235
|
+
let i8 = from;
|
|
126236
|
+
while (i8 < n && at2(i8) !== 0) i8 += at2(i8) + 1;
|
|
126237
|
+
return i8 + 1;
|
|
126238
|
+
};
|
|
126239
|
+
let i = 13 + table(at2(10));
|
|
126240
|
+
while (i < n) {
|
|
126241
|
+
const b2 = at2(i);
|
|
126242
|
+
if (b2 === 59) return true;
|
|
126243
|
+
if (b2 === 33) i = skipSubBlocks(i + 2);
|
|
126244
|
+
else if (b2 === 44) i = skipSubBlocks(i + 11 + table(at2(i + 9)));
|
|
126245
|
+
else return true;
|
|
126246
|
+
}
|
|
126247
|
+
return false;
|
|
126248
|
+
}
|
|
126249
|
+
function sniffImage(buf) {
|
|
126250
|
+
const n = buf.length;
|
|
126251
|
+
if (startsWith(buf, [137, 80, 78, 71, 13, 10, 26, 10])) {
|
|
126252
|
+
return { mime: "image/png", complete: buf.lastIndexOf("IEND", n, "latin1") >= 8 };
|
|
126253
|
+
}
|
|
126254
|
+
if (startsWith(buf, [255, 216, 255])) return { mime: "image/jpeg", complete: buf.lastIndexOf(Buffer.from([255, 217])) > 2 };
|
|
126255
|
+
const head = buf.subarray(0, 6).toString("latin1");
|
|
126256
|
+
if (head === "GIF87a" || head === "GIF89a") return { mime: "image/gif", complete: gifReachesTrailer(buf) };
|
|
126257
|
+
if (head.startsWith("RIFF") && buf.subarray(8, 12).toString("latin1") === "WEBP") {
|
|
126258
|
+
return { mime: "image/webp", complete: buf.readUInt32LE(4) + 8 <= n };
|
|
126259
|
+
}
|
|
126260
|
+
if (startsWith(buf, [66, 77]) && n >= 26 && buf.readUInt32LE(6) === 0 && [12, 40, 52, 56, 64, 108, 124].includes(buf.readUInt32LE(14))) return { mime: "image/bmp", complete: buf.readUInt32LE(2) <= n };
|
|
126261
|
+
return null;
|
|
126262
|
+
}
|
|
126263
|
+
var SIGNATURES = [[137, 80, 78, 71, 13, 10, 26, 10], [255, 216, 255], [...Buffer.from("GIF89a")], [...Buffer.from("RIFF")], [66, 77]];
|
|
126264
|
+
function judgeImage(buf) {
|
|
126265
|
+
const seen = sniffImage(buf);
|
|
126266
|
+
if (seen?.complete) return { mime: seen.mime };
|
|
126267
|
+
if (seen || buf.length < 8 && SIGNATURES.some((sig) => buf.every((b2, i) => i >= sig.length || b2 === sig[i]))) {
|
|
126268
|
+
return { note: `the file is cut short (${buf.length} bytes): it may still be being written` };
|
|
126269
|
+
}
|
|
126270
|
+
return null;
|
|
126271
|
+
}
|
|
126069
126272
|
var IMAGE_MAX_BYTES = 8 * 1048576;
|
|
126070
|
-
var tooLarge = (bytes2) => `
|
|
126273
|
+
var tooLarge = (bytes2) => `too large (${Math.round(bytes2 / 1048576)}MB)`;
|
|
126071
126274
|
function inlineImageEvent(sessionId, mime, data) {
|
|
126072
126275
|
const bytes2 = Math.floor(data.length * 3 / 4);
|
|
126073
126276
|
if (bytes2 > IMAGE_MAX_BYTES) return { type: "message_image", sessionId, mime, data: "", note: tooLarge(bytes2) };
|
|
126074
|
-
|
|
126277
|
+
const judged = judgeImage(Buffer.from(data, "base64"));
|
|
126278
|
+
if (judged && "note" in judged) return undrawnImageEvent(sessionId, judged.note);
|
|
126279
|
+
return { type: "message_image", sessionId, mime: judged?.mime ?? (mime || "image/png"), data };
|
|
126075
126280
|
}
|
|
126076
126281
|
function undrawnImageEvent(sessionId, note) {
|
|
126077
126282
|
return { type: "message_image", sessionId, mime: "", data: "", note };
|
|
@@ -126084,22 +126289,31 @@ function mcpImageEvents(sessionId, content) {
|
|
|
126084
126289
|
if (part.type !== "image") continue;
|
|
126085
126290
|
const data = typeof part.data === "string" ? part.data : "";
|
|
126086
126291
|
const mime = typeof part.mimeType === "string" ? part.mimeType : "";
|
|
126087
|
-
out.push(data ? inlineImageEvent(sessionId, mime, data) : undrawnImageEvent(sessionId, "
|
|
126292
|
+
out.push(data ? inlineImageEvent(sessionId, mime, data) : undrawnImageEvent(sessionId, "the tool sent it with no data"));
|
|
126088
126293
|
}
|
|
126089
126294
|
return out;
|
|
126090
126295
|
}
|
|
126296
|
+
function readFailure2(err) {
|
|
126297
|
+
const code2 = asObj(err).code;
|
|
126298
|
+
if (code2 === "ENOENT") return "the file is gone";
|
|
126299
|
+
if (code2 === "EACCES" || code2 === "EPERM") return "no permission to read it";
|
|
126300
|
+
if (code2 === "EISDIR") return "it is a folder";
|
|
126301
|
+
return typeof code2 === "string" ? `the system refused the read (${code2})` : "the read failed";
|
|
126302
|
+
}
|
|
126091
126303
|
async function imageEventFromDisk(sessionId, path2, maxBytes = IMAGE_MAX_BYTES) {
|
|
126092
126304
|
const fail4 = (note) => ({ ...undrawnImageEvent(sessionId, note), path: path2 });
|
|
126093
|
-
const
|
|
126094
|
-
const
|
|
126095
|
-
if (!mime) return fail4(`Cannot display this format (.${ext2})`);
|
|
126305
|
+
const file2 = path2.split(/[\\/]/).pop() ?? path2;
|
|
126306
|
+
const ext2 = file2.includes(".") ? file2.slice(file2.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
126096
126307
|
try {
|
|
126097
126308
|
const s = await stat6(path2);
|
|
126098
126309
|
if (s.size > maxBytes) return fail4(tooLarge(s.size));
|
|
126099
126310
|
const buf = await readFile6(path2);
|
|
126100
|
-
|
|
126311
|
+
const seen = judgeImage(buf);
|
|
126312
|
+
if (!seen) return fail4(!ext2 ? "the file is not a picture" : IMAGE_EXTENSIONS.has(ext2) ? `the file is not a valid .${ext2} image` : `the .${ext2} format cannot be shown`);
|
|
126313
|
+
if ("note" in seen) return fail4(seen.note);
|
|
126314
|
+
return { type: "message_image", sessionId, mime: seen.mime, data: buf.toString("base64"), path: path2 };
|
|
126101
126315
|
} catch (err) {
|
|
126102
|
-
return fail4(
|
|
126316
|
+
return fail4(readFailure2(err));
|
|
126103
126317
|
}
|
|
126104
126318
|
}
|
|
126105
126319
|
|
|
@@ -126327,7 +126541,7 @@ async function readClaudeHistory(externalId, cwd, limit) {
|
|
|
126327
126541
|
try {
|
|
126328
126542
|
const message = readableMessage(r?.message);
|
|
126329
126543
|
if (r?.type === "assistant") out.push(...assistantLines(message, agents));
|
|
126330
|
-
else if (r?.type === "user") out.push(...userLines(message, agents));
|
|
126544
|
+
else if (r?.type === "user") out.push(...userLines(message, agents, isCentraluInputId(r.uuid)));
|
|
126331
126545
|
} catch (err) {
|
|
126332
126546
|
console.error(`[claude] a transcript row was skipped on read-back: ${err.message}`);
|
|
126333
126547
|
}
|
|
@@ -126355,11 +126569,14 @@ function agentResult(e, agents) {
|
|
|
126355
126569
|
if (!agents.has(e.callId) && !FOR_THE_MODEL.test(e.output ?? e.summary)) return e;
|
|
126356
126570
|
return toolResultEvent(HISTORY_SESSION, e.callId, e.ok, "", AGENT_REPORT_NOT_RECORDED);
|
|
126357
126571
|
}
|
|
126358
|
-
|
|
126572
|
+
var INTERRUPT_LINE = /^\[Request interrupted by user( for tool use)?\]$/;
|
|
126573
|
+
function userLines(message, agents, sent) {
|
|
126359
126574
|
const raw = userText(message);
|
|
126360
126575
|
if (COMPACT_SUMMARY.test(raw)) return [{ role: "system", event: { type: "compaction", sessionId: HISTORY_SESSION, failed: false } }];
|
|
126361
|
-
const text2 = stripInjectedBlocks(raw);
|
|
126362
|
-
const
|
|
126576
|
+
const text2 = INTERRUPT_LINE.test(raw) ? "" : stripInjectedBlocks(raw);
|
|
126577
|
+
const note = isUpdateContinueNote(text2);
|
|
126578
|
+
if (note && !sent) console.error("[claude] Centralu's Continue note is in the transcript without the input id Centralu gave it: this CLI does not keep it. The catch-up recognises the note by its text");
|
|
126579
|
+
const out = text2 ? [{ role: "user", text: text2, ...sent || note ? { sent: true } : {} }] : [];
|
|
126363
126580
|
const events2 = userEvents({ message }, HISTORY_SESSION);
|
|
126364
126581
|
out.push(...historyEvents(events2.map((e) => e.type === "tool_result" ? agentResult(e, agents) : e)));
|
|
126365
126582
|
return out;
|
|
@@ -126467,7 +126684,6 @@ async function readClaudeModels(q2) {
|
|
|
126467
126684
|
|
|
126468
126685
|
// packages/agent-host/src/adapters/claude/session.ts
|
|
126469
126686
|
init_sdk();
|
|
126470
|
-
import { randomUUID as randomUUID24 } from "node:crypto";
|
|
126471
126687
|
|
|
126472
126688
|
// packages/agent-host/src/adapters/subagent-steps.ts
|
|
126473
126689
|
function subagentSteps(sessionId, parentCallId, events2) {
|
|
@@ -128901,8 +129117,9 @@ var ClaudeSession = class {
|
|
|
128901
129117
|
this.closed = true;
|
|
128902
129118
|
this.notify?.();
|
|
128903
129119
|
}
|
|
129120
|
+
/** Every input goes out under an id with Centralu's mark, so its line in the transcript reads back as Centralu's (`centraluInputId`) */
|
|
128904
129121
|
send(text2, opts) {
|
|
128905
|
-
this.sendInput(text2, opts?.id !== void 0 && isUuid(opts.id) ? opts.id :
|
|
129122
|
+
this.sendInput(text2, opts?.id !== void 0 && isUuid(opts.id) ? opts.id : centraluInputId(), void 0, opts?.origin, opts?.quiet);
|
|
128906
129123
|
}
|
|
128907
129124
|
/** A note read with the next message or at the running turn's next tool round, starting nothing (#1492): quiet input, stamped as Centralu's */
|
|
128908
129125
|
nudge(text2) {
|
|
@@ -128920,7 +129137,7 @@ var ClaudeSession = class {
|
|
|
128920
129137
|
turnInputs() {
|
|
128921
129138
|
return this.resultInputs;
|
|
128922
129139
|
}
|
|
128923
|
-
sendInput(text2, uuid3 =
|
|
129140
|
+
sendInput(text2, uuid3 = centraluInputId(), note, origin, quiet) {
|
|
128924
129141
|
const goal = isGoalCommand(text2);
|
|
128925
129142
|
if (!this.sendGate && !(goal && this.goalCommand === void 0)) {
|
|
128926
129143
|
this.deliver(text2, uuid3, !!note, origin, quiet);
|
|
@@ -129397,7 +129614,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
129397
129614
|
await sweepProbeDirs();
|
|
129398
129615
|
}
|
|
129399
129616
|
const dir = await mkdtemp3(join43(tmpdir3(), PROBE_DIR_PREFIX));
|
|
129400
|
-
const sessionId = `usage-probe-${
|
|
129617
|
+
const sessionId = `usage-probe-${randomUUID23()}`;
|
|
129401
129618
|
_ClaudeAdapter.probeIds.add(sessionId);
|
|
129402
129619
|
let probe = null;
|
|
129403
129620
|
try {
|
|
@@ -130479,7 +130696,7 @@ function itemToMessages(item, ts) {
|
|
|
130479
130696
|
}
|
|
130480
130697
|
if (it2.type === "userMessage") {
|
|
130481
130698
|
const text2 = stripInjectedBlocks(userInputText(it2.content));
|
|
130482
|
-
return text2 ? [{ role: "user", text: text2, ts }] : [];
|
|
130699
|
+
return text2 ? [{ role: "user", text: text2, ts, ...isUpdateContinueNote(text2) ? { sent: true } : {} }] : [];
|
|
130483
130700
|
}
|
|
130484
130701
|
const live2 = (method) => normalizeNotification(HISTORY_SESSION, { method, params: { item: it2 } });
|
|
130485
130702
|
return historyEvents([...live2("item/started"), ...live2("item/completed")], ts);
|
|
@@ -130870,6 +131087,29 @@ var CODEX_KNOWN_NOTIFICATIONS = /* @__PURE__ */ new Set([
|
|
|
130870
131087
|
"thread/realtime/closed"
|
|
130871
131088
|
]);
|
|
130872
131089
|
|
|
131090
|
+
// packages/agent-host/src/adapters/codex/reasoning-parts.ts
|
|
131091
|
+
var ReasoningParts = class {
|
|
131092
|
+
item = null;
|
|
131093
|
+
/** Something ended the open row (a tool call, the end of the turn, an image): the next chunk starts a row, not a part */
|
|
131094
|
+
reset() {
|
|
131095
|
+
this.item = null;
|
|
131096
|
+
}
|
|
131097
|
+
/** The events one notification maps to, with the blank line in front of a new item's first chunk */
|
|
131098
|
+
join(n, events2) {
|
|
131099
|
+
if (n.method !== "item/reasoning/summaryTextDelta") return events2;
|
|
131100
|
+
const p = asObj(n.params);
|
|
131101
|
+
const item = str(p.itemId);
|
|
131102
|
+
if (!item) return events2;
|
|
131103
|
+
if (!events2.some((e) => e.type === "reasoning_delta" && e.text)) return events2;
|
|
131104
|
+
const apart = this.item !== null && this.item !== item;
|
|
131105
|
+
this.item = item;
|
|
131106
|
+
if (!apart || typeof p.summaryIndex === "number" && p.summaryIndex > 0) return events2;
|
|
131107
|
+
return events2.map((e) => e.type === "reasoning_delta" && e.text ? { ...e, text: `
|
|
131108
|
+
|
|
131109
|
+
${e.text}` } : e);
|
|
131110
|
+
}
|
|
131111
|
+
};
|
|
131112
|
+
|
|
130873
131113
|
// packages/agent-host/src/adapters/codex/thread-settings.ts
|
|
130874
131114
|
function threadSettingsOf(res) {
|
|
130875
131115
|
if (!res) return null;
|
|
@@ -131819,7 +132059,10 @@ var CodexSession = class {
|
|
|
131819
132059
|
this.sessionId = opts.sessionId;
|
|
131820
132060
|
this.livePreset = opts.permissionPreset;
|
|
131821
132061
|
const sink = emit;
|
|
131822
|
-
this.emit = (e) =>
|
|
132062
|
+
this.emit = (e) => {
|
|
132063
|
+
if (endsStreamRow(e) || streamRowKind(e) === "text") this.reasoningParts.reset();
|
|
132064
|
+
sink(keepAttachedAppMarks(e, () => (opts.apps?.current() ?? []).map((a) => a.server)));
|
|
132065
|
+
};
|
|
131823
132066
|
this.unmapped = new UnmappedTypes("codex", opts.sessionId, CODEX_KNOWN_NOTIFICATIONS);
|
|
131824
132067
|
this.retry = new RetryActivity(opts.sessionId);
|
|
131825
132068
|
this.slashCommands = new CodexSlashCommands({
|
|
@@ -131978,6 +132221,8 @@ var CodexSession = class {
|
|
|
131978
132221
|
ready;
|
|
131979
132222
|
/** The preset the session is set to now (#1525), which can differ from the one it launched under only between `normal` and `edits` */
|
|
131980
132223
|
livePreset;
|
|
132224
|
+
/** Which reasoning item the open reasoning row last grew from, so a second item's summary does not run into the first's (#1464) */
|
|
132225
|
+
reasoningParts = new ReasoningParts();
|
|
131981
132226
|
setPermissionPreset(preset) {
|
|
131982
132227
|
this.livePreset = preset;
|
|
131983
132228
|
}
|
|
@@ -132123,7 +132368,7 @@ var CodexSession = class {
|
|
|
132123
132368
|
}
|
|
132124
132369
|
/** A notification of this thread's own, as the events it maps to (`normalizeNotification`), each the way it goes out */
|
|
132125
132370
|
emitNotification(n) {
|
|
132126
|
-
for (const said of normalizeNotification(this.sessionId, n)) {
|
|
132371
|
+
for (const said of this.reasoningParts.join(n, normalizeNotification(this.sessionId, n))) {
|
|
132127
132372
|
const sort = said.type === "compaction" ? { ...said, trigger: this.compactTurn ? "manual" : "auto" } : said;
|
|
132128
132373
|
const compactAt = codexCompactAt(this.opts);
|
|
132129
132374
|
const e = sort.type === "context_update" && compactAt !== void 0 ? { ...sort, compactAt } : sort;
|
|
@@ -133147,6 +133392,111 @@ var CURSOR_SURFACE = {
|
|
|
133147
133392
|
baseline: SURFACE_BASELINE3
|
|
133148
133393
|
};
|
|
133149
133394
|
|
|
133395
|
+
// packages/agent-host/src/adapters/usage-read.ts
|
|
133396
|
+
function softRead(o) {
|
|
133397
|
+
const now = o.now ?? Date.now;
|
|
133398
|
+
const freshFor = (e) => typeof o.freshMs === "function" ? o.freshMs(e.v, e.tookMs) : o.freshMs;
|
|
133399
|
+
let value = null;
|
|
133400
|
+
let failedAt = null;
|
|
133401
|
+
let inflight = null;
|
|
133402
|
+
const standIn = () => value && (o.staleOk?.(value.v) ?? true) ? value.v : o.fallback();
|
|
133403
|
+
const start2 = () => {
|
|
133404
|
+
const began = now();
|
|
133405
|
+
return inflight ??= o.read().then(
|
|
133406
|
+
(v) => {
|
|
133407
|
+
value = { at: now(), v, tookMs: now() - began };
|
|
133408
|
+
failedAt = null;
|
|
133409
|
+
return v;
|
|
133410
|
+
},
|
|
133411
|
+
(e) => {
|
|
133412
|
+
failedAt = now();
|
|
133413
|
+
o.onFail?.(e instanceof Error ? e : new Error(String(e)));
|
|
133414
|
+
return standIn();
|
|
133415
|
+
}
|
|
133416
|
+
).finally(() => {
|
|
133417
|
+
inflight = null;
|
|
133418
|
+
});
|
|
133419
|
+
};
|
|
133420
|
+
return async (ask = {}) => {
|
|
133421
|
+
const t = now();
|
|
133422
|
+
if (!value && ask.seed) value = { at: ask.seed.at, v: ask.seed.value, tookMs: 0 };
|
|
133423
|
+
if (value && !isFromTheFuture(value.at, t) && t - value.at < freshFor(value)) return { value: value.v, provisional: false };
|
|
133424
|
+
if (failedAt !== null && t - failedAt < o.failMs) return { value: standIn(), provisional: false };
|
|
133425
|
+
const run4 = start2();
|
|
133426
|
+
const held3 = value && (o.staleOk?.(value.v) ?? true) ? { v: value.v } : null;
|
|
133427
|
+
if (held3 && !ask.settle) return { value: held3.v, provisional: true };
|
|
133428
|
+
let timer;
|
|
133429
|
+
const soft = new Promise((resolve21) => {
|
|
133430
|
+
timer = setTimeout(() => resolve21({ value: held3 ? held3.v : o.fallback(), provisional: true }), ask.settle ? o.settleMs ?? 2e4 : o.softMs);
|
|
133431
|
+
timer.unref?.();
|
|
133432
|
+
});
|
|
133433
|
+
try {
|
|
133434
|
+
return await Promise.race([run4.then((v) => ({ value: v, provisional: false })), soft]);
|
|
133435
|
+
} finally {
|
|
133436
|
+
clearTimeout(timer);
|
|
133437
|
+
}
|
|
133438
|
+
};
|
|
133439
|
+
}
|
|
133440
|
+
function localUsageReader(o) {
|
|
133441
|
+
const read = softRead(o);
|
|
133442
|
+
return async (ask) => {
|
|
133443
|
+
const at2 = ask?.seed?.totals?.asOf;
|
|
133444
|
+
const { value, provisional } = await read({ settle: ask?.settle, ...ask?.seed && at2 !== void 0 ? { seed: { value: ask.seed, at: at2 } } : {} });
|
|
133445
|
+
return provisional ? { ...value, provisional: true } : value;
|
|
133446
|
+
};
|
|
133447
|
+
}
|
|
133448
|
+
|
|
133449
|
+
// packages/agent-host/src/adapters/cursor-agent/usage.ts
|
|
133450
|
+
var ABOUT = external_exports.object({ subscriptionTier: external_exports.unknown().optional() });
|
|
133451
|
+
function parseAbout(stdout) {
|
|
133452
|
+
const about = ABOUT.parse(jsonObjectOf(stdout));
|
|
133453
|
+
if (about.subscriptionTier === null) return { plan: null, loggedOut: true };
|
|
133454
|
+
const tier = typeof about.subscriptionTier === "string" ? about.subscriptionTier.trim() : "";
|
|
133455
|
+
return { plan: tier || null };
|
|
133456
|
+
}
|
|
133457
|
+
function jsonObjectOf(stdout) {
|
|
133458
|
+
for (let from = stdout.indexOf("{"); from >= 0; from = stdout.indexOf("{", from + 1)) {
|
|
133459
|
+
let depth = 0;
|
|
133460
|
+
let inString = false;
|
|
133461
|
+
for (let i = from; i < stdout.length; i++) {
|
|
133462
|
+
const c = stdout[i];
|
|
133463
|
+
if (inString) {
|
|
133464
|
+
if (c === "\\") i++;
|
|
133465
|
+
else if (c === '"') inString = false;
|
|
133466
|
+
} else if (c === '"') inString = true;
|
|
133467
|
+
else if (c === "{") depth++;
|
|
133468
|
+
else if (c === "}" && --depth === 0) {
|
|
133469
|
+
try {
|
|
133470
|
+
const value = JSON.parse(stdout.slice(from, i + 1));
|
|
133471
|
+
if (typeof value === "object" && value !== null) return value;
|
|
133472
|
+
} catch {
|
|
133473
|
+
}
|
|
133474
|
+
break;
|
|
133475
|
+
}
|
|
133476
|
+
}
|
|
133477
|
+
}
|
|
133478
|
+
throw new Error("`agent about` printed no JSON object");
|
|
133479
|
+
}
|
|
133480
|
+
var NOTHING = { plan: null };
|
|
133481
|
+
var FRESH_MS = 5 * 6e4;
|
|
133482
|
+
var LOGGED_OUT_FRESH_MS = 1e4;
|
|
133483
|
+
var FAIL_MS = 6e4;
|
|
133484
|
+
var SOFT_MS = 3e3;
|
|
133485
|
+
var ABOUT_RUN_MS = 15e3;
|
|
133486
|
+
function cursorUsageReader(run4, now = Date.now) {
|
|
133487
|
+
return localUsageReader({
|
|
133488
|
+
now,
|
|
133489
|
+
freshMs: (v) => v.loggedOut ? LOGGED_OUT_FRESH_MS : FRESH_MS,
|
|
133490
|
+
// A "not logged in" that is ten seconds old is read again before it is told: a login a moment ago is seen at once
|
|
133491
|
+
staleOk: (v) => !v.loggedOut,
|
|
133492
|
+
failMs: FAIL_MS,
|
|
133493
|
+
softMs: SOFT_MS,
|
|
133494
|
+
fallback: () => NOTHING,
|
|
133495
|
+
onFail: (e) => console.error(`[usage] cursor: could not read the plan: ${e.message}`),
|
|
133496
|
+
read: async () => parseAbout(await run4(ABOUT_RUN_MS))
|
|
133497
|
+
});
|
|
133498
|
+
}
|
|
133499
|
+
|
|
133150
133500
|
// packages/agent-host/src/adapters/cursor-agent/index.ts
|
|
133151
133501
|
var exec7 = promisify8(execFile9);
|
|
133152
133502
|
var CAPABILITIES2 = {
|
|
@@ -133185,6 +133535,12 @@ function centraluToolOfCall(call2) {
|
|
|
133185
133535
|
var STATUS_FOR_MS = 6e4;
|
|
133186
133536
|
function cursorProfile() {
|
|
133187
133537
|
let status = null;
|
|
133538
|
+
const localUsage = cursorUsageReader(async (timeout) => {
|
|
133539
|
+
const install = findCursor();
|
|
133540
|
+
if (!install) throw new Error("Cursor CLI not found");
|
|
133541
|
+
const r = await exec7(install.command, [...install.args, "about", "--format", "json"], { timeout, windowsHide: true, env: { ...process.env, ...install.env } });
|
|
133542
|
+
return r.stdout;
|
|
133543
|
+
});
|
|
133188
133544
|
return {
|
|
133189
133545
|
descriptor: DESCRIPTOR2,
|
|
133190
133546
|
capabilities: CAPABILITIES2,
|
|
@@ -133197,6 +133553,7 @@ function cursorProfile() {
|
|
|
133197
133553
|
return findCursor()?.version ?? null;
|
|
133198
133554
|
},
|
|
133199
133555
|
centraluToolOf: centraluToolOfCall,
|
|
133556
|
+
localUsage,
|
|
133200
133557
|
async detect() {
|
|
133201
133558
|
if (status && Date.now() - status.at < STATUS_FOR_MS) return status.result;
|
|
133202
133559
|
const install = findCursor();
|
|
@@ -133560,6 +133917,75 @@ var OPENCODE_SURFACE = {
|
|
|
133560
133917
|
baseline: SURFACE_BASELINE4
|
|
133561
133918
|
};
|
|
133562
133919
|
|
|
133920
|
+
// packages/agent-host/src/adapters/opencode/usage.ts
|
|
133921
|
+
var ROWS4 = [
|
|
133922
|
+
{ match: "Sessions", id: "sessions", label: "Sessions", unit: "count" },
|
|
133923
|
+
{ match: "Messages", id: "messages", label: "Messages", unit: "count" },
|
|
133924
|
+
{ match: "Input", id: "input", label: "Input", unit: "tokens" },
|
|
133925
|
+
{ match: "Output", id: "output", label: "Output", unit: "tokens" },
|
|
133926
|
+
{ match: "Cache Read", id: "cache-read", label: "Cache read", unit: "tokens" },
|
|
133927
|
+
{ match: "Cache Write", id: "cache-write", label: "Cache write", unit: "tokens" },
|
|
133928
|
+
{ match: "Total Cost", id: "cost", label: "Cost", unit: "usd" }
|
|
133929
|
+
];
|
|
133930
|
+
function parseAmount(text2) {
|
|
133931
|
+
const m = /^\$?\s*([\d,]*\.?\d+)\s*([KMB])?$/i.exec(text2.trim());
|
|
133932
|
+
const digits = m?.[1];
|
|
133933
|
+
if (!digits) return null;
|
|
133934
|
+
const n = Number(digits.replace(/,/g, ""));
|
|
133935
|
+
if (!Number.isFinite(n)) return null;
|
|
133936
|
+
const k2 = SUFFIX.get((m[2] ?? "").toUpperCase()) ?? 1;
|
|
133937
|
+
return Math.round(n * k2 * 100) / 100;
|
|
133938
|
+
}
|
|
133939
|
+
var GROUPED = /^\d{1,3}([.,\u00A0\u202F '’])\d{3}(?:\1\d{3})*$/;
|
|
133940
|
+
var LAKH = /^\d{1,2}(?:,\d{2})+,\d{3}$/;
|
|
133941
|
+
function parseCount(text2) {
|
|
133942
|
+
const t = text2.trim();
|
|
133943
|
+
if (!(/^\d+$/.test(t) || GROUPED.test(t) || LAKH.test(t))) return null;
|
|
133944
|
+
const n = Number(t.replace(/\D/g, ""));
|
|
133945
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
133946
|
+
}
|
|
133947
|
+
var SUFFIX = /* @__PURE__ */ new Map([
|
|
133948
|
+
["K", 1e3],
|
|
133949
|
+
["M", 1e6],
|
|
133950
|
+
["B", 1e9]
|
|
133951
|
+
]);
|
|
133952
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
133953
|
+
function parseStats(stdout) {
|
|
133954
|
+
const found = /* @__PURE__ */ new Map();
|
|
133955
|
+
for (const raw of stdout.replace(ANSI, "").split(/\r?\n/)) {
|
|
133956
|
+
const line = raw.replace(/^[│|]\s*/, "").replace(/\s*[│|]$/, "").trim();
|
|
133957
|
+
const cells = /^(\S.*?)\s{2,}(\S.*)$/.exec(line);
|
|
133958
|
+
const row = ROWS4.find((r) => r.match === cells?.[1]);
|
|
133959
|
+
if (!row || !cells?.[2] || found.has(row.id)) continue;
|
|
133960
|
+
found.set(row.id, row.unit === "count" ? parseCount(cells[2]) : parseAmount(cells[2]));
|
|
133961
|
+
}
|
|
133962
|
+
const rows3 = ROWS4.flatMap((r) => {
|
|
133963
|
+
const value = found.get(r.id);
|
|
133964
|
+
return value === void 0 ? [] : [{ id: r.id, label: r.label, value, unit: r.unit }];
|
|
133965
|
+
});
|
|
133966
|
+
return { plan: null, ...rows3.length > 0 ? { totals: { scope: "all time", rows: rows3 } } : {} };
|
|
133967
|
+
}
|
|
133968
|
+
var NOTHING2 = { plan: null };
|
|
133969
|
+
var MIN_FRESH_MS = 6e4;
|
|
133970
|
+
var FRESH_PER_RUN = 10;
|
|
133971
|
+
var FAIL_MS2 = 6e4;
|
|
133972
|
+
var SOFT_MS2 = 3e3;
|
|
133973
|
+
var STATS_RUN_MS = 5 * 6e4;
|
|
133974
|
+
function openCodeUsageReader(run4, now = Date.now) {
|
|
133975
|
+
return localUsageReader({
|
|
133976
|
+
freshMs: (_v2, tookMs) => Math.max(MIN_FRESH_MS, tookMs * FRESH_PER_RUN),
|
|
133977
|
+
failMs: FAIL_MS2,
|
|
133978
|
+
softMs: SOFT_MS2,
|
|
133979
|
+
now,
|
|
133980
|
+
fallback: () => NOTHING2,
|
|
133981
|
+
onFail: (e) => console.error(`[usage] opencode: could not read the totals: ${e.message}`),
|
|
133982
|
+
read: async () => {
|
|
133983
|
+
const read = parseStats(await run4(STATS_RUN_MS));
|
|
133984
|
+
return read.totals ? { ...read, totals: { ...read.totals, asOf: now() } } : read;
|
|
133985
|
+
}
|
|
133986
|
+
});
|
|
133987
|
+
}
|
|
133988
|
+
|
|
133563
133989
|
// packages/agent-host/src/adapters/opencode/index.ts
|
|
133564
133990
|
var exec9 = promisify10(execFile11);
|
|
133565
133991
|
var CAPABILITIES3 = {
|
|
@@ -133592,6 +134018,12 @@ var DESCRIPTOR3 = {
|
|
|
133592
134018
|
var VERSION_FOR_MS = 6e4;
|
|
133593
134019
|
function openCodeProfile() {
|
|
133594
134020
|
let known = null;
|
|
134021
|
+
const localUsage = openCodeUsageReader(async (timeout) => {
|
|
134022
|
+
const l = findOpenCode();
|
|
134023
|
+
if (!l) throw new Error("OpenCode not found");
|
|
134024
|
+
const r = await exec9(l.command, [...l.args, "stats"], { timeout, windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
134025
|
+
return r.stdout;
|
|
134026
|
+
});
|
|
133595
134027
|
const version2 = async () => {
|
|
133596
134028
|
if (known && Date.now() - known.at < VERSION_FOR_MS) return known.version;
|
|
133597
134029
|
const l = findOpenCode();
|
|
@@ -133604,6 +134036,7 @@ function openCodeProfile() {
|
|
|
133604
134036
|
capabilities: CAPABILITIES3,
|
|
133605
134037
|
surface: OPENCODE_SURFACE,
|
|
133606
134038
|
infersCompaction: true,
|
|
134039
|
+
localUsage,
|
|
133607
134040
|
async launch(opts) {
|
|
133608
134041
|
const l = findOpenCode();
|
|
133609
134042
|
return l ? { command: l.command, args: [...l.args, "acp"], env: presetEnv(opts.permissionPreset, process.env, readOpenCodeConfigs(opts.cwd, process.env)) } : null;
|
|
@@ -135577,8 +136010,8 @@ var AppRunRecords = class {
|
|
|
135577
136010
|
/**
|
|
135578
136011
|
* Closes out runs that never saw an end (once, at startup). When the host dies, the app
|
|
135579
136012
|
* process's input also closes and it ends — a row left at `running` would look like it is
|
|
135580
|
-
* running forever. The same reasoning as fixing session state at startup (`manager.ts
|
|
135581
|
-
*
|
|
136013
|
+
* running forever. The same reasoning as fixing session state at startup (`manager.ts`, by
|
|
136014
|
+
* `turnCut`): a "live" state is only true while a process backs it.
|
|
135582
136015
|
*/
|
|
135583
136016
|
settleUnfinishedAppRuns(error51) {
|
|
135584
136017
|
return this.db.prepare(`UPDATE app_runs SET status = 'error', error = ? WHERE status = 'running'`).run(error51).changes;
|
|
@@ -138710,6 +139143,28 @@ var StepsV62 = class extends MigrationRange {
|
|
|
138710
139143
|
this.db.exec(`ALTER TABLE usage_buckets ADD COLUMN rebuild_tokens INTEGER NOT NULL DEFAULT 0`);
|
|
138711
139144
|
}
|
|
138712
139145
|
}
|
|
139146
|
+
},
|
|
139147
|
+
{
|
|
139148
|
+
to: 64,
|
|
139149
|
+
breaksOlderReaders: false,
|
|
139150
|
+
/**
|
|
139151
|
+
* The last good totals of a tool that reports no limits (#1623), `usage_local_totals`: one row per tool with the JSON of
|
|
139152
|
+
* its `UsageSnapshot.totals` and `at`, when they were read (ms). A tool whose command reads a large database takes a
|
|
139153
|
+
* minute to count, so after a restart the usage panel shows these at once, with the time they were read, while a fresh
|
|
139154
|
+
* read goes on. A row is replaced by each newer read. Counts only, no content; local to this computer.
|
|
139155
|
+
*
|
|
139156
|
+
* **Expand only (#292's rule).** A new table that an older host neither reads nor writes. It goes away when the totals
|
|
139157
|
+
* are not needed any more, in a contract step one release after the code stops using it.
|
|
139158
|
+
*/
|
|
139159
|
+
run: () => {
|
|
139160
|
+
this.db.exec(`
|
|
139161
|
+
CREATE TABLE IF NOT EXISTS usage_local_totals (
|
|
139162
|
+
tool TEXT PRIMARY KEY,
|
|
139163
|
+
at INTEGER NOT NULL,
|
|
139164
|
+
totals TEXT NOT NULL
|
|
139165
|
+
);
|
|
139166
|
+
`);
|
|
139167
|
+
}
|
|
138713
139168
|
}
|
|
138714
139169
|
];
|
|
138715
139170
|
}
|
|
@@ -139744,6 +140199,116 @@ var Store = class extends MigrationRunner {
|
|
|
139744
140199
|
};
|
|
139745
140200
|
var WAL_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
139746
140201
|
|
|
140202
|
+
// packages/agent-host/src/lifecycle/update-resume.ts
|
|
140203
|
+
var UPDATE_RESUME_PREFIX = "update-resume.";
|
|
140204
|
+
var MAX_TURNS = 1e3;
|
|
140205
|
+
var ID3 = /^[0-9A-Za-z_.:-]{1,200}$/;
|
|
140206
|
+
var READ_TRIES = 5;
|
|
140207
|
+
var READ_RETRY_MS = 200;
|
|
140208
|
+
function sleepSync(ms3) {
|
|
140209
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms3);
|
|
140210
|
+
}
|
|
140211
|
+
function readHeld(folder, name, wait) {
|
|
140212
|
+
for (let attempt = 1; ; attempt++) {
|
|
140213
|
+
try {
|
|
140214
|
+
return folder.read(name);
|
|
140215
|
+
} catch (err) {
|
|
140216
|
+
const busy = err instanceof Error && "code" in err && err.code === "EBUSY";
|
|
140217
|
+
if (!busy || attempt >= READ_TRIES) throw err;
|
|
140218
|
+
console.error(`[agent-host] ${name} is held by another process (${errorMessage(err)}); reading it again (${attempt}/${READ_TRIES})`);
|
|
140219
|
+
wait(READ_RETRY_MS);
|
|
140220
|
+
}
|
|
140221
|
+
}
|
|
140222
|
+
}
|
|
140223
|
+
function parseUpdateResume(text2) {
|
|
140224
|
+
let o;
|
|
140225
|
+
try {
|
|
140226
|
+
o = JSON.parse(text2);
|
|
140227
|
+
} catch {
|
|
140228
|
+
return "torn";
|
|
140229
|
+
}
|
|
140230
|
+
if (typeof o !== "object" || o === null || !("v" in o) || o.v !== 1 || !("turns" in o) || !Array.isArray(o.turns)) return "other";
|
|
140231
|
+
const turns = [];
|
|
140232
|
+
const listed2 = o.turns;
|
|
140233
|
+
for (const t of listed2) {
|
|
140234
|
+
if (typeof t !== "object" || t === null || !("id" in t) || !("seq" in t)) continue;
|
|
140235
|
+
if (typeof t.id === "string" && ID3.test(t.id) && typeof t.seq === "number" && Number.isSafeInteger(t.seq) && t.seq >= 0) turns.push({ id: t.id, seq: t.seq });
|
|
140236
|
+
}
|
|
140237
|
+
return turns.slice(0, MAX_TURNS);
|
|
140238
|
+
}
|
|
140239
|
+
function recordUpdateCut(folder, cut2) {
|
|
140240
|
+
const turns = cut2.filter((t) => ID3.test(t.id)).slice(0, MAX_TURNS);
|
|
140241
|
+
if (!turns.length) return;
|
|
140242
|
+
try {
|
|
140243
|
+
folder.add(`${JSON.stringify({ v: 1, turns })}
|
|
140244
|
+
`);
|
|
140245
|
+
} catch (err) {
|
|
140246
|
+
console.error(`[agent-host] the turns this update ends could not be listed for the next start (${errorMessage(err)}); they end as a stop's do`);
|
|
140247
|
+
}
|
|
140248
|
+
}
|
|
140249
|
+
var UpdateResumeLists = class {
|
|
140250
|
+
/**
|
|
140251
|
+
* `wait` sleeps between the tries at a list a scanner holds (`READ_TRIES`); the host's own start is one thread with nothing
|
|
140252
|
+
* yet to serve, so it blocks. Tests pass their own
|
|
140253
|
+
*/
|
|
140254
|
+
constructor(folder, wait = sleepSync) {
|
|
140255
|
+
this.folder = folder;
|
|
140256
|
+
let names;
|
|
140257
|
+
try {
|
|
140258
|
+
names = folder.names();
|
|
140259
|
+
} catch (err) {
|
|
140260
|
+
console.error(`[agent-host] the turns an update ended could not be read (${errorMessage(err)}); none is continued`);
|
|
140261
|
+
return;
|
|
140262
|
+
}
|
|
140263
|
+
for (const name of names) {
|
|
140264
|
+
let text2;
|
|
140265
|
+
try {
|
|
140266
|
+
text2 = readHeld(folder, name, wait);
|
|
140267
|
+
} catch (err) {
|
|
140268
|
+
console.error(`[agent-host] ${name} could not be read (${errorMessage(err)}); the list is kept for a later start, and the turns it names end as a quit's do`);
|
|
140269
|
+
continue;
|
|
140270
|
+
}
|
|
140271
|
+
if (text2 === null) continue;
|
|
140272
|
+
const turns = parseUpdateResume(text2);
|
|
140273
|
+
if (turns === "other") continue;
|
|
140274
|
+
if (turns === "torn") this.removeList(name);
|
|
140275
|
+
else this.lists.set(name, turns);
|
|
140276
|
+
}
|
|
140277
|
+
}
|
|
140278
|
+
folder;
|
|
140279
|
+
/** Each list's name and the turns it names, as read at the start */
|
|
140280
|
+
lists = /* @__PURE__ */ new Map();
|
|
140281
|
+
dealt = /* @__PURE__ */ new Set();
|
|
140282
|
+
/** The sessions the lists name */
|
|
140283
|
+
ids() {
|
|
140284
|
+
return new Set([...this.lists.values()].flat().map((t) => t.id));
|
|
140285
|
+
}
|
|
140286
|
+
/** Whether a stop listed this session's turn as the one it cut: its conversation still ends where it ended then */
|
|
140287
|
+
cut(id3, lastSeq) {
|
|
140288
|
+
return [...this.lists.values()].some((turns) => turns.some((t) => t.id === id3 && t.seq === lastSeq));
|
|
140289
|
+
}
|
|
140290
|
+
/**
|
|
140291
|
+
* One listed session dealt with: continued, reported as lost, or not found cut. Each list all of whose sessions are
|
|
140292
|
+
* dealt with goes. One that cannot be removed is said in the log and stays harmless: the turn it names is over, so
|
|
140293
|
+
* its mark no longer matches, and a later start removes it
|
|
140294
|
+
*/
|
|
140295
|
+
done(id3) {
|
|
140296
|
+
this.dealt.add(id3);
|
|
140297
|
+
for (const [name, turns] of this.lists) {
|
|
140298
|
+
if (!turns.some((t) => t.id === id3) || !turns.every((t) => this.dealt.has(t.id))) continue;
|
|
140299
|
+
this.lists.delete(name);
|
|
140300
|
+
this.removeList(name);
|
|
140301
|
+
}
|
|
140302
|
+
}
|
|
140303
|
+
removeList(name) {
|
|
140304
|
+
try {
|
|
140305
|
+
this.folder.remove(name);
|
|
140306
|
+
} catch (err) {
|
|
140307
|
+
console.error(`[agent-host] ${name} could not be removed (${errorMessage(err)}); the turns it names are over, and a later start removes it`);
|
|
140308
|
+
}
|
|
140309
|
+
}
|
|
140310
|
+
};
|
|
140311
|
+
|
|
139747
140312
|
// packages/agent-host/src/sessions/card-rest.ts
|
|
139748
140313
|
var CardRests = class {
|
|
139749
140314
|
constructor(record2 = null, now = () => Date.now()) {
|
|
@@ -140614,7 +141179,7 @@ function pausedWords(tasks, sessionId, there = "") {
|
|
|
140614
141179
|
}
|
|
140615
141180
|
|
|
140616
141181
|
// packages/agent-host/src/sessions/handoff-flow.ts
|
|
140617
|
-
import { randomUUID as
|
|
141182
|
+
import { randomUUID as randomUUID24 } from "node:crypto";
|
|
140618
141183
|
var TIMINGS = { callMs: 24e4, noteMs: 10 * 6e4 };
|
|
140619
141184
|
var HandoffFlow = class {
|
|
140620
141185
|
constructor(host, timings = TIMINGS) {
|
|
@@ -140693,7 +141258,7 @@ var HandoffFlow = class {
|
|
|
140693
141258
|
const again = this.refusal(callerId, now, mode);
|
|
140694
141259
|
if (again) return this.refuse(callerId, now.id, again);
|
|
140695
141260
|
if (always) this.always.add(callerId);
|
|
140696
|
-
const run4 = { id: `ho-${
|
|
141261
|
+
const run4 = { id: `ho-${randomUUID24().slice(0, 8)}`, callerId, targetId: now.id, done: Promise.resolve({ ok: false, error: "" }) };
|
|
140697
141262
|
this.running.set(now.id, run4);
|
|
140698
141263
|
this.runs.set(run4.id, run4);
|
|
140699
141264
|
if (viaAuto && !this.host.autoBoundaryOpen(callerId, `${SESSION_HANDOFF} of ${now.id} (${mode}, to ${tool})`)) {
|
|
@@ -140829,7 +141394,7 @@ var HandoffFlow = class {
|
|
|
140829
141394
|
|
|
140830
141395
|
// packages/agent-host/src/sessions/host-cards.ts
|
|
140831
141396
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
140832
|
-
import { randomUUID as
|
|
141397
|
+
import { randomUUID as randomUUID25 } from "node:crypto";
|
|
140833
141398
|
var HostCards = class {
|
|
140834
141399
|
constructor(host) {
|
|
140835
141400
|
this.host = host;
|
|
@@ -140893,7 +141458,7 @@ var HostCards = class {
|
|
|
140893
141458
|
if (!card.alwaysAsk && this.host.autoBoundaryOpen(sessionId, card.log ?? `${detail.kind}${note}`)) return Promise.resolve("allow");
|
|
140894
141459
|
return new Promise((resolve21) => {
|
|
140895
141460
|
if (signal?.aborted) return resolve21(null);
|
|
140896
|
-
const requestId = card.requestId ?? `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" || isCentraluApply(detail) ? "up" : "mk"}-${
|
|
141461
|
+
const requestId = card.requestId ?? `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" || isCentraluApply(detail) ? "up" : "mk"}-${randomUUID25()}`;
|
|
140897
141462
|
const ask = { requestId, sessionId, detail, shown: card.shown ?? false, resolve: resolve21 };
|
|
140898
141463
|
this.hostAsks.set(requestId, ask);
|
|
140899
141464
|
signal?.addEventListener("abort", () => this.withdrawHostAsk(requestId), { once: true });
|
|
@@ -140954,7 +141519,7 @@ var HostCards = class {
|
|
|
140954
141519
|
if (signal.aborted) return resolve21(null);
|
|
140955
141520
|
const app2 = { appId: q2.app.appId, projectId: q2.app.projectId, name: q2.appName };
|
|
140956
141521
|
if (q2.origin.kind === "session" && this.host.meta.has(q2.origin.sessionId)) {
|
|
140957
|
-
const requestId = `cap-${
|
|
141522
|
+
const requestId = `cap-${randomUUID25()}`;
|
|
140958
141523
|
const ask = {
|
|
140959
141524
|
requestId,
|
|
140960
141525
|
sessionId: q2.origin.sessionId,
|
|
@@ -140968,7 +141533,7 @@ var HostCards = class {
|
|
|
140968
141533
|
return;
|
|
140969
141534
|
}
|
|
140970
141535
|
const origin = q2.origin.kind === "view" ? q2.origin.app : q2.app;
|
|
140971
|
-
const id3 = `q-${
|
|
141536
|
+
const id3 = `q-${randomUUID25()}`;
|
|
140972
141537
|
this.appQuestions.set(id3, {
|
|
140973
141538
|
question: { id: id3, app: app2, capability: q2.capability, text: q2.text, origin: { appId: origin.appId, projectId: origin.projectId }, askedAt: q2.askedAt, expiresAt: q2.expiresAt },
|
|
140974
141539
|
resolve: resolve21
|
|
@@ -141038,7 +141603,7 @@ import { posix as posix4, sep as sep10, win32 as win328 } from "node:path";
|
|
|
141038
141603
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
141039
141604
|
var OPEN_FLAGS = constants8.O_RDONLY | constants8.O_NONBLOCK | (constants8.O_NOFOLLOW ?? 0);
|
|
141040
141605
|
var MESSAGE_IMAGE_KINDS = "PNG, JPEG, GIF or WebP";
|
|
141041
|
-
function
|
|
141606
|
+
function sniffImage2(b2) {
|
|
141042
141607
|
const at2 = (i, ...bytes2) => bytes2.every((x, k2) => b2[i + k2] === x);
|
|
141043
141608
|
if (b2.length >= 8 && at2(0, 137, 80, 78, 71, 13, 10, 26, 10)) return "image/png";
|
|
141044
141609
|
if (b2.length >= 3 && at2(0, 255, 216, 255)) return "image/jpeg";
|
|
@@ -141126,7 +141691,7 @@ async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
|
141126
141691
|
}
|
|
141127
141692
|
if (total > max) return refuse6("too_large", `The image is over ${mb3(max)}; a reply shows images up to ${mb3(max)}`, file2);
|
|
141128
141693
|
const bytes2 = buf.subarray(0, total);
|
|
141129
|
-
const mime =
|
|
141694
|
+
const mime = sniffImage2(bytes2);
|
|
141130
141695
|
if (!mime) return refuse6("not_an_image", `${file2} is not a ${MESSAGE_IMAGE_KINDS} image`, file2);
|
|
141131
141696
|
return { ok: true, mime, data: bytes2.toString("base64"), file: file2 };
|
|
141132
141697
|
} finally {
|
|
@@ -141249,9 +141814,10 @@ var SessionRecord = class {
|
|
|
141249
141814
|
return run4.seq;
|
|
141250
141815
|
}
|
|
141251
141816
|
if (run4) this.closeStream(m.id);
|
|
141252
|
-
|
|
141817
|
+
const first = streamKind === "reasoning" ? text2.replace(/^\s+/, "") : text2;
|
|
141818
|
+
if (!first) return null;
|
|
141253
141819
|
const seq2 = this.host.store.messages.nextSeq(m.id);
|
|
141254
|
-
const fresh = { seq: seq2, kind: streamKind, payload: { ...e }, text:
|
|
141820
|
+
const fresh = { seq: seq2, kind: streamKind, payload: { ...e }, text: first, written: 0, lastWrite: 0 };
|
|
141255
141821
|
this.streams.set(m.id, fresh);
|
|
141256
141822
|
this.flushStream(m.id, fresh);
|
|
141257
141823
|
m.lastSeq = seq2;
|
|
@@ -141372,13 +141938,14 @@ var SessionRecord = class {
|
|
|
141372
141938
|
persistImage(e, m) {
|
|
141373
141939
|
const ts = Date.now();
|
|
141374
141940
|
const seq = this.host.store.messages.nextSeq(m.id);
|
|
141941
|
+
const name = e.path;
|
|
141375
141942
|
const row = (path2, note) => ({
|
|
141376
141943
|
sessionId: m.id,
|
|
141377
141944
|
seq,
|
|
141378
141945
|
role: "system",
|
|
141379
141946
|
kind: "image",
|
|
141380
141947
|
ts,
|
|
141381
|
-
payload: { type: "message_image", sessionId: m.id, mime: e.mime, data: "", path: path2, note }
|
|
141948
|
+
payload: { type: "message_image", sessionId: m.id, mime: e.mime, data: "", path: path2, note, ...name ? { name } : {} }
|
|
141382
141949
|
});
|
|
141383
141950
|
this.host.store.messages.appendMessages([row(e.path, e.note)]);
|
|
141384
141951
|
m.lastSeq = seq;
|
|
@@ -141393,8 +141960,8 @@ var SessionRecord = class {
|
|
|
141393
141960
|
stored = (await saveAttachment(m.id, "agent-image", e.mime, e.data)).path;
|
|
141394
141961
|
void sweepAttachments().catch(() => {
|
|
141395
141962
|
});
|
|
141396
|
-
} catch
|
|
141397
|
-
note =
|
|
141963
|
+
} catch {
|
|
141964
|
+
note = "a copy could not be kept";
|
|
141398
141965
|
}
|
|
141399
141966
|
try {
|
|
141400
141967
|
this.host.store.messages.upsertMessageNoIndex(row(stored ?? e.path, note));
|
|
@@ -141443,7 +142010,7 @@ var SessionRecord = class {
|
|
|
141443
142010
|
const data = (await readFile9(p.path)).toString("base64");
|
|
141444
142011
|
return { ...r, payload: { ...p, data } };
|
|
141445
142012
|
} catch {
|
|
141446
|
-
return { ...r, payload: { ...p, note: "
|
|
142013
|
+
return { ...r, payload: { ...p, note: "the saved copy was cleared (total-size cap)" } };
|
|
141447
142014
|
}
|
|
141448
142015
|
}
|
|
141449
142016
|
if (r.kind === "text" && r.role === "user") {
|
|
@@ -141470,6 +142037,9 @@ var SessionRecord = class {
|
|
|
141470
142037
|
// packages/agent-host/src/sessions/tool-queries.ts
|
|
141471
142038
|
var MODEL_LIST_READ_MS = 6e4;
|
|
141472
142039
|
var MODEL_LIST_RETRY_MS = 6e4;
|
|
142040
|
+
function noLimitsReason(label) {
|
|
142041
|
+
return `${label} does not report usage limits`;
|
|
142042
|
+
}
|
|
141473
142043
|
var ToolQueries = class {
|
|
141474
142044
|
constructor(host) {
|
|
141475
142045
|
this.host = host;
|
|
@@ -141524,7 +142094,22 @@ var ToolQueries = class {
|
|
|
141524
142094
|
async usageFor(tool, opts = {}) {
|
|
141525
142095
|
const cache2 = this.host.cacheLedger.stats(tool, (id3) => this.host.meta.get(id3)?.name);
|
|
141526
142096
|
const adapter = this.host.adapters.get(tool);
|
|
141527
|
-
|
|
142097
|
+
const label = adapter?.descriptor.label ?? tool;
|
|
142098
|
+
if (!adapter) return { supported: false, reason: `${tool} does not support usage queries`, usage: null, cache: cache2 };
|
|
142099
|
+
if (!adapter.listUsage) {
|
|
142100
|
+
const none = { supported: false, reason: noLimitsReason(label), notReported: true, usage: null, cache: cache2 };
|
|
142101
|
+
if (!adapter.localUsage || opts.limits === false) return none;
|
|
142102
|
+
try {
|
|
142103
|
+
const local = await adapter.localUsage({ settle: opts.settle, seed: this.localTotalsSeed(tool) });
|
|
142104
|
+
this.keepLocalTotals(tool, local);
|
|
142105
|
+
if (local.loggedOut) return { supported: false, reason: `${label} is not logged in on this computer`, usage: null, cache: cache2 };
|
|
142106
|
+
const usage = { plan: local.plan, windows: [], daily: [], ...local.totals ? { totals: local.totals } : {} };
|
|
142107
|
+
return { ...none, usage, ...local.provisional ? { provisional: true } : {} };
|
|
142108
|
+
} catch (err) {
|
|
142109
|
+
console.error(`[usage] ${tool}: the tool's own usage could not be read: ${errorMessage(err)}`);
|
|
142110
|
+
return none;
|
|
142111
|
+
}
|
|
142112
|
+
}
|
|
141528
142113
|
if (opts.limits === false) return { supported: true, usage: null, cache: cache2 };
|
|
141529
142114
|
const hit = this.usageCache.get(tool);
|
|
141530
142115
|
if (hit && Date.now() - hit.at < 6e4) return { supported: true, usage: hit.snapshot, cache: cache2 };
|
|
@@ -141537,6 +142122,38 @@ var ToolQueries = class {
|
|
|
141537
142122
|
return { supported: false, reason: err.message, usage: hit?.snapshot ?? null, cache: cache2 };
|
|
141538
142123
|
}
|
|
141539
142124
|
}
|
|
142125
|
+
/** Per tool: the read time of the totals last kept, so a stale answer returned again is not written again */
|
|
142126
|
+
keptTotalsAt = /* @__PURE__ */ new Map();
|
|
142127
|
+
/** Tools whose kept totals were already offered to their reader: it keeps its own answer after that */
|
|
142128
|
+
seededTotals = /* @__PURE__ */ new Set();
|
|
142129
|
+
/**
|
|
142130
|
+
* The totals the last process kept for a tool (v64), offered once to its reader, which stands on them as on a stale answer
|
|
142131
|
+
* until its own read ends. After a restart a panel then shows the totals at once, with the time they were read.
|
|
142132
|
+
*/
|
|
142133
|
+
localTotalsSeed(tool) {
|
|
142134
|
+
if (this.seededTotals.has(tool)) return void 0;
|
|
142135
|
+
this.seededTotals.add(tool);
|
|
142136
|
+
try {
|
|
142137
|
+
const totals = this.host.store.usage.localTotals(tool);
|
|
142138
|
+
if (!totals) return void 0;
|
|
142139
|
+
if (totals.asOf !== void 0) this.keptTotalsAt.set(tool, totals.asOf);
|
|
142140
|
+
return { plan: null, totals };
|
|
142141
|
+
} catch (err) {
|
|
142142
|
+
console.error(`[usage] ${tool}: the kept totals could not be read: ${errorMessage(err)}`);
|
|
142143
|
+
return void 0;
|
|
142144
|
+
}
|
|
142145
|
+
}
|
|
142146
|
+
/** Keeps totals a read produced (not a stale answer returned again, nor a provisional one), for the next process */
|
|
142147
|
+
keepLocalTotals(tool, local) {
|
|
142148
|
+
const totals = local.totals;
|
|
142149
|
+
if (!totals || totals.asOf === void 0 || local.provisional || this.keptTotalsAt.get(tool) === totals.asOf) return;
|
|
142150
|
+
this.keptTotalsAt.set(tool, totals.asOf);
|
|
142151
|
+
try {
|
|
142152
|
+
this.host.store.usage.saveLocalTotals(tool, totals);
|
|
142153
|
+
} catch (err) {
|
|
142154
|
+
console.error(`[usage] ${tool}: the totals could not be kept: ${errorMessage(err)}`);
|
|
142155
|
+
}
|
|
142156
|
+
}
|
|
141540
142157
|
/**
|
|
141541
142158
|
* The models that can be picked, and each model's reasoning effort levels.
|
|
141542
142159
|
*
|
|
@@ -142954,11 +143571,15 @@ var SessionManager = class {
|
|
|
142954
143571
|
this.emit = (full) => {
|
|
142955
143572
|
rawEmit(withoutToolRecord(full));
|
|
142956
143573
|
};
|
|
142957
|
-
const LIVE_ONLY = ["working", "waiting_approval"];
|
|
142958
143574
|
const stoppedByQuit = [];
|
|
143575
|
+
const cutByUpdate = [];
|
|
143576
|
+
const lists = keeperOpts.updateResume ? new UpdateResumeLists(keeperOpts.updateResume) : null;
|
|
143577
|
+
this.updateLists = lists;
|
|
142959
143578
|
for (const s of store2.sessions.listSessions()) {
|
|
142960
|
-
const
|
|
142961
|
-
const
|
|
143579
|
+
const cut2 = turnCut(s) && !keeperOpts.keptSessions?.has(s.id);
|
|
143580
|
+
const goesOn = cut2 && !!lists?.cut(s.id, s.lastSeq);
|
|
143581
|
+
const stale = cut2 && !goesOn;
|
|
143582
|
+
const fixed = stale ? { ...s, state: "idle", waitingSince: null } : goesOn ? { ...s, state: "working", waitingSince: null } : s;
|
|
142962
143583
|
this.meta.set(s.id, withHubMark(store2.settings, fixed));
|
|
142963
143584
|
if (stale) {
|
|
142964
143585
|
console.error(`[agent-host] stale state reset: ${s.id.slice(0, 8)} ${s.state} -> idle`);
|
|
@@ -142967,27 +143588,39 @@ var SessionManager = class {
|
|
|
142967
143588
|
this.children.processGone(s.id);
|
|
142968
143589
|
stoppedByQuit.push(fixed);
|
|
142969
143590
|
}
|
|
143591
|
+
if (goesOn) {
|
|
143592
|
+
console.error(`[agent-host] ${s.id.slice(0, 8)}: an update of this machine ended its turn; continuing it`);
|
|
143593
|
+
store2.sessions.upsertSession(fixed);
|
|
143594
|
+
cutByUpdate.push(fixed);
|
|
143595
|
+
this.restartPolicy.continuingTurns.add(s.id);
|
|
143596
|
+
this.continuedAfterUpdate.push({ sessionId: s.id, at: Date.now() });
|
|
143597
|
+
}
|
|
142970
143598
|
}
|
|
143599
|
+
for (const id3 of lists?.ids() ?? []) if (!cutByUpdate.some((s) => s.id === id3)) this.updateResumeDone(id3);
|
|
143600
|
+
const goingOn = /* @__PURE__ */ new Set([...keeperOpts.keptSessions ?? [], ...cutByUpdate.map((s) => s.id)]);
|
|
142971
143601
|
this.quitPersonWork = stoppedByQuit.map((s) => s.id);
|
|
142972
143602
|
const marks = new Map(store2.children.listReportMarks().map((r) => [r.sessionId, r.callerSessionId]));
|
|
142973
143603
|
const turnGivers = new Map(store2.children.listDelegatedTurns().map((r) => [r.sessionId, r.callerSessionId]));
|
|
143604
|
+
const requesterOf = (s) => [marks.get(s.id), turnGivers.get(s.id), s.askedBy].find((id3) => !!id3 && id3 !== s.id && this.meta.has(id3));
|
|
142974
143605
|
for (const s of stoppedByQuit) {
|
|
142975
|
-
const requester =
|
|
143606
|
+
const requester = requesterOf(s);
|
|
142976
143607
|
if (requester) this.quitNotices.push({ sessionId: s.id, requester });
|
|
142977
143608
|
}
|
|
143609
|
+
this.updateCuts = cutByUpdate.map((s) => ({ sessionId: s.id, requester: requesterOf(s) ?? null }));
|
|
142978
143610
|
this.cardRests.restore((id3) => !!keeperOpts.keptSessions?.has(id3) && this.meta.get(id3)?.state === "waiting_approval");
|
|
142979
|
-
for (const m of this.children.restoreReports(this.meta,
|
|
143611
|
+
for (const m of this.children.restoreReports(this.meta, goingOn)) this.quitNotices.push({ sessionId: m.sessionId, requester: m.callerSessionId, swapped: true });
|
|
142980
143612
|
this.delegationFlow.starts.restoreAgentStarts();
|
|
142981
143613
|
this.delegationFlow.remote.restore();
|
|
142982
|
-
this.children.restoreTurns(this.meta,
|
|
142983
|
-
this.keptAtStart =
|
|
143614
|
+
this.children.restoreTurns(this.meta, goingOn);
|
|
143615
|
+
this.keptAtStart = goingOn;
|
|
142984
143616
|
this.children.restoreQueued(this.meta, (r) => r.via === "ask_project" && !this.queue.pairConsented(r.callerSessionId, this.meta.get(r.sessionId)));
|
|
142985
143617
|
if (this.children.queued.size) {
|
|
142986
143618
|
setTimeout(() => {
|
|
142987
|
-
for (const id3 of this.children.queued.keys()) if (!
|
|
143619
|
+
for (const id3 of this.children.queued.keys()) if (!goingOn.has(id3)) this.queue.scheduleDrain(id3);
|
|
142988
143620
|
}, 0);
|
|
142989
143621
|
}
|
|
142990
|
-
if (this.quitNotices.length || this.quitPersonWork.length) setTimeout(() => this.tellQuitNotices((id3) => !
|
|
143622
|
+
if (this.quitNotices.length || this.quitPersonWork.length) setTimeout(() => this.tellQuitNotices((id3) => !goingOn.has(id3)), 0);
|
|
143623
|
+
if (this.updateCuts.length) setTimeout(() => void this.continueCutTurns(), 0);
|
|
142991
143624
|
void this.handoffNotes.sweepOrphanHandoffNotes().catch(() => {
|
|
142992
143625
|
});
|
|
142993
143626
|
this.recoveryRepair = repairWorktreeRecoveryRemovals((id3) => store2.trash.isTrashed(id3)).catch((e) => console.error(`[archive] recovery folders left by an interrupted purge were not repaired: ${e.message}`));
|
|
@@ -143163,6 +143796,18 @@ var SessionManager = class {
|
|
|
143163
143796
|
quitNotices = [];
|
|
143164
143797
|
/** Sessions this start found mid-turn with no process: the person is told of the ones whose work is theirs (§8.1), once the mailbox is wired */
|
|
143165
143798
|
quitPersonWork = [];
|
|
143799
|
+
/** Turns a hub's update cut, to continue once the start is done, and who to tell (#1618, `continueCutTurns`) */
|
|
143800
|
+
updateCuts = [];
|
|
143801
|
+
/**
|
|
143802
|
+
* The turns this host continues because an update cut them, and when its start took them up (#1618): said in every hello,
|
|
143803
|
+
* for a hub's callers. Taken at the start, before this host answers any hello, so a hub that connects while a Continue is
|
|
143804
|
+
* still on its way is told of it too (#1626 review); one that turns out lost leaves it (`turnLostInSwap`)
|
|
143805
|
+
*/
|
|
143806
|
+
continuedAfterUpdate = [];
|
|
143807
|
+
/** The lists of the turns an update cut that the stops before this start left (#1618), each removed once dealt with */
|
|
143808
|
+
updateLists = null;
|
|
143809
|
+
/** A linked hub stops this host for an update (`host.stop { forUpdate }`): the turns its stop ends are listed (#1618) */
|
|
143810
|
+
stoppingForUpdate = false;
|
|
143166
143811
|
/** `disposeAll` has begun (#673): a card it withdraws is not the person's answer, and its request stays stored */
|
|
143167
143812
|
disposing = false;
|
|
143168
143813
|
/**
|
|
@@ -143684,6 +144329,73 @@ var SessionManager = class {
|
|
|
143684
144329
|
);
|
|
143685
144330
|
this.tellQuitNotices(() => true);
|
|
143686
144331
|
}
|
|
144332
|
+
/**
|
|
144333
|
+
* Continues each turn a linked hub's update cut (#1618, owner 2026-10-26): the update no longer waits for the turns on a
|
|
144334
|
+
* machine, so the host it starts takes them up again, once. The session was left `working` by the start; it is woken
|
|
144335
|
+
* and given Centralu's note through Centralu's own channel (`deliverMail`: no message of the person's, so the turn stays
|
|
144336
|
+
* whose it was and its report, if one is owed, is still that turn's end). Its requester here is told that the turn was
|
|
144337
|
+
* cut and goes on, unless it is one of the turns continued, whose own note covers its children, as a quit's rule has it
|
|
144338
|
+
* (`tellAskerOfInterruption`). A session that cannot be continued (its tool cannot resume it, it is gone there) ends
|
|
144339
|
+
* as a kept process that could not be re-attached does (`turnLostInSwap`): idle, marked stopped, its report or its
|
|
144340
|
+
* requester told. Each is taken once: the list is emptied first, and its id leaves the file once its Continue was sent
|
|
144341
|
+
* or its loss reported (`updateResumeDone`), not before, so a host stopped in between leaves it for the next.
|
|
144342
|
+
*/
|
|
144343
|
+
async continueCutTurns() {
|
|
144344
|
+
const cuts = this.updateCuts;
|
|
144345
|
+
this.updateCuts = [];
|
|
144346
|
+
const continued = new Set(cuts.map((c) => c.sessionId));
|
|
144347
|
+
await Promise.all(
|
|
144348
|
+
cuts.map(async ({ sessionId, requester }) => {
|
|
144349
|
+
const m = this.meta.get(sessionId);
|
|
144350
|
+
if (this.disposing || this.handingOver) return;
|
|
144351
|
+
if (!m || !inTurn(m.state)) {
|
|
144352
|
+
this.restartPolicy.continuingTurns.delete(sessionId);
|
|
144353
|
+
this.notContinued(sessionId);
|
|
144354
|
+
this.updateResumeDone(sessionId);
|
|
144355
|
+
return;
|
|
144356
|
+
}
|
|
144357
|
+
try {
|
|
144358
|
+
await this.events.deliverMail(
|
|
144359
|
+
sessionId,
|
|
144360
|
+
{ text: UPDATE_CONTINUE_TEXT, quiet: false, line: UPDATE_CONTINUE_LINE, label: "update", skipLine: () => this.disposing && this.stoppingForUpdate },
|
|
144361
|
+
{ leaving: () => this.handingOver || this.disposing, atRest: () => true }
|
|
144362
|
+
);
|
|
144363
|
+
} catch (e) {
|
|
144364
|
+
if (this.disposing || this.handingOver || this.meta.get(sessionId) !== m) return;
|
|
144365
|
+
this.restartPolicy.continuingTurns.delete(sessionId);
|
|
144366
|
+
console.error(`[agent-host] ${sessionId.slice(0, 8)}: the turn an update ended could not be continued (${errorMessage(e)})`);
|
|
144367
|
+
this.turnLostInSwap(m);
|
|
144368
|
+
this.notContinued(sessionId);
|
|
144369
|
+
this.updateResumeDone(sessionId);
|
|
144370
|
+
return;
|
|
144371
|
+
}
|
|
144372
|
+
if (this.disposing || this.handingOver) return;
|
|
144373
|
+
this.restartPolicy.continuingTurns.delete(sessionId);
|
|
144374
|
+
this.updateResumeDone(sessionId);
|
|
144375
|
+
if (requester && !continued.has(requester)) void this.reports.tellCutByUpdate(sessionId, requester);
|
|
144376
|
+
})
|
|
144377
|
+
);
|
|
144378
|
+
this.tellQuitNotices(() => true);
|
|
144379
|
+
}
|
|
144380
|
+
/** One turn the lists named is dealt with: a list goes once all its turns are (#1618, `UpdateResumeLists.done`) */
|
|
144381
|
+
updateResumeDone(sessionId) {
|
|
144382
|
+
this.updateLists?.done(sessionId);
|
|
144383
|
+
}
|
|
144384
|
+
/** A turn the start took up that is not continued after all: the hello stops naming it (#1618) */
|
|
144385
|
+
notContinued(sessionId) {
|
|
144386
|
+
this.continuedAfterUpdate = this.continuedAfterUpdate.filter((c) => c.sessionId !== sessionId);
|
|
144387
|
+
}
|
|
144388
|
+
/**
|
|
144389
|
+
* A linked hub stops this host to update it, roll it back or put a version back (`host.stop { forUpdate }`, #1618): as the
|
|
144390
|
+
* sessions are let go of (`disposeAll`), the ones in a turn are listed for the host that starts next, which continues them
|
|
144391
|
+
*/
|
|
144392
|
+
stopForUpdate() {
|
|
144393
|
+
this.stoppingForUpdate = true;
|
|
144394
|
+
}
|
|
144395
|
+
/** The turns this host continues after an update cut them (#1618), for the hello a linked hub reads */
|
|
144396
|
+
updateContinued() {
|
|
144397
|
+
return [...this.continuedAfterUpdate];
|
|
144398
|
+
}
|
|
143687
144399
|
/** A turn a quit cut in a session whose work the person owns: reported to them. */
|
|
143688
144400
|
tellPersonOfInterruption(sessionId) {
|
|
143689
144401
|
if (!this.meta.has(sessionId)) return;
|
|
@@ -145100,10 +145812,14 @@ var SessionManager = class {
|
|
|
145100
145812
|
async disposeEverything() {
|
|
145101
145813
|
this.disposing = true;
|
|
145102
145814
|
this.leaveHost("Centralu is shutting down");
|
|
145815
|
+
const cut2 = this.stoppingForUpdate && this.keeperOpts.updateResume ? cutTurns({ sessions: this.listSessions(), terminals: 0, commandRuns: 0 }) : [];
|
|
145103
145816
|
for (const [id3, run4] of this.record.streams) this.record.flushStream(id3, run4);
|
|
145104
145817
|
await Promise.allSettled([...[...this.handles.values()].map((h) => h.dispose()), ...this.merges.mergeChecks]);
|
|
145105
145818
|
await Promise.allSettled([...this.adapters.values()].map((a) => a.settle?.()));
|
|
145106
145819
|
for (const id3 of [...this.record.streams.keys()]) this.record.closeStream(id3);
|
|
145820
|
+
if (cut2.length && this.keeperOpts.updateResume) {
|
|
145821
|
+
recordUpdateCut(this.keeperOpts.updateResume, cut2.map((id3) => ({ id: id3, seq: this.store.messages.nextSeq(id3) - 1 })));
|
|
145822
|
+
}
|
|
145107
145823
|
for (const id3 of this.handles.keys()) this.externals.stampExternalSynced(id3);
|
|
145108
145824
|
this.handles.clear();
|
|
145109
145825
|
}
|
|
@@ -148499,8 +149215,8 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
148499
149215
|
},
|
|
148500
149216
|
"agents.commands": async (p) => mgr2.listCommands(RpcMethods["agents.commands"].params.parse(p).sessionId),
|
|
148501
149217
|
"agents.usage": async (p) => {
|
|
148502
|
-
const { tool, limits } = RpcMethods["agents.usage"].params.parse(p);
|
|
148503
|
-
return mgr2.usageFor(tool, { limits });
|
|
149218
|
+
const { tool, limits, settle } = RpcMethods["agents.usage"].params.parse(p);
|
|
149219
|
+
return mgr2.usageFor(tool, { limits, settle });
|
|
148504
149220
|
},
|
|
148505
149221
|
"usage.history": async (p) => mgr2.usageHistory(RpcMethods["usage.history"].params.parse(p)),
|
|
148506
149222
|
"agents.account": async (p) => mgr2.accountFor(RpcMethods["agents.account"].params.parse(p).tool),
|
|
@@ -148752,14 +149468,14 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
148752
149468
|
if (!stopHost) {
|
|
148753
149469
|
throw rpcError("This host runs under the Centralu app, which stops it: quit the app instead");
|
|
148754
149470
|
}
|
|
148755
|
-
const { ifIdle } = RpcMethods["host.stop"].params.parse(p ?? {});
|
|
149471
|
+
const { ifIdle, forUpdate } = RpcMethods["host.stop"].params.parse(p ?? {});
|
|
148756
149472
|
if (ifIdle) {
|
|
148757
149473
|
if (!stopGate) throw rpcError("This host cannot tell whether it is idle");
|
|
148758
149474
|
const running2 = stopGate.busy();
|
|
148759
149475
|
if (running2) return { ok: false, ...running2 };
|
|
148760
149476
|
stopGate.refuse();
|
|
148761
149477
|
}
|
|
148762
|
-
setTimeout(stopHost, 50);
|
|
149478
|
+
setTimeout(() => stopHost({ forUpdate: forUpdate === true }), 50);
|
|
148763
149479
|
return { ok: true };
|
|
148764
149480
|
},
|
|
148765
149481
|
"host.handover": async (p) => {
|
|
@@ -148832,7 +149548,7 @@ function onExternalAppListChanged(apps, emit) {
|
|
|
148832
149548
|
}
|
|
148833
149549
|
|
|
148834
149550
|
// packages/agent-host/src/update-apply.ts
|
|
148835
|
-
import { randomUUID as
|
|
149551
|
+
import { randomUUID as randomUUID26 } from "node:crypto";
|
|
148836
149552
|
var ANSWER_MS = 5e3;
|
|
148837
149553
|
var OUTCOME_MS = 2e4;
|
|
148838
149554
|
var GONE_GRACE_MS = 5e3;
|
|
@@ -148866,7 +149582,7 @@ var UpdateApplyBroker = class {
|
|
|
148866
149582
|
/** Asks every window whether its "Apply now" could relaunch now; resolves with the first ready one */
|
|
148867
149583
|
probe() {
|
|
148868
149584
|
if (this.opts.windows() === 0) return Promise.resolve({ ready: false, error: NO_WINDOW });
|
|
148869
|
-
const requestId =
|
|
149585
|
+
const requestId = randomUUID26();
|
|
148870
149586
|
return new Promise((resolve21) => {
|
|
148871
149587
|
const p = {
|
|
148872
149588
|
requestId,
|
|
@@ -148889,7 +149605,7 @@ var UpdateApplyBroker = class {
|
|
|
148889
149605
|
if (!p || p.answered.has(client)) return { ticket: null };
|
|
148890
149606
|
p.answered.add(client);
|
|
148891
149607
|
if (a.ready) {
|
|
148892
|
-
const ticket =
|
|
149608
|
+
const ticket = randomUUID26();
|
|
148893
149609
|
p.finish({ ready: true, requestId: a.requestId, ticket, client, ...a.version ? { version: a.version } : {} });
|
|
148894
149610
|
return { ticket };
|
|
148895
149611
|
}
|
|
@@ -150289,7 +151005,7 @@ var SessionSleeper = class {
|
|
|
150289
151005
|
};
|
|
150290
151006
|
|
|
150291
151007
|
// packages/agent-host/src/host-requests.ts
|
|
150292
|
-
import { randomUUID as
|
|
151008
|
+
import { randomUUID as randomUUID27 } from "node:crypto";
|
|
150293
151009
|
|
|
150294
151010
|
// packages/agent-host/src/host-request-targets.ts
|
|
150295
151011
|
function asItRuns(method, params) {
|
|
@@ -150524,7 +151240,7 @@ var HostRequests = class {
|
|
|
150524
151240
|
if (cannot) return Promise.resolve({ decision: null, outcome: cannot });
|
|
150525
151241
|
const createdAt = this.now();
|
|
150526
151242
|
const request = {
|
|
150527
|
-
requestId:
|
|
151243
|
+
requestId: randomUUID27(),
|
|
150528
151244
|
method,
|
|
150529
151245
|
question,
|
|
150530
151246
|
client: from.client === null ? null : quotedName2(from.client),
|
|
@@ -150844,7 +151560,8 @@ function wireEndings(o) {
|
|
|
150844
151560
|
}
|
|
150845
151561
|
|
|
150846
151562
|
// packages/agent-host/src/lifecycle/stopped-mark.ts
|
|
150847
|
-
import {
|
|
151563
|
+
import { randomUUID as randomUUID28 } from "node:crypto";
|
|
151564
|
+
import { readdirSync as readdirSync15, readFileSync as readFileSync23, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
150848
151565
|
import { join as join56 } from "node:path";
|
|
150849
151566
|
var STOPPED_MARK_FILE = "stopped.json";
|
|
150850
151567
|
function clearStoppedMark(dataDir) {
|
|
@@ -150860,6 +151577,22 @@ function markQuit(dataDir, now = Date.now()) {
|
|
|
150860
151577
|
} catch {
|
|
150861
151578
|
}
|
|
150862
151579
|
}
|
|
151580
|
+
function updateResumeFolder(dataDir) {
|
|
151581
|
+
const NAME3 = /^update-resume\.[0-9a-f-]{36}\.json$/;
|
|
151582
|
+
return {
|
|
151583
|
+
names: () => readdirSync15(dataDir).filter((n) => NAME3.test(n)),
|
|
151584
|
+
read: (name) => {
|
|
151585
|
+
try {
|
|
151586
|
+
return readFileSync23(join56(dataDir, name), "utf8");
|
|
151587
|
+
} catch (err) {
|
|
151588
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") return null;
|
|
151589
|
+
throw err;
|
|
151590
|
+
}
|
|
151591
|
+
},
|
|
151592
|
+
add: (text2) => writeFileSync11(join56(dataDir, `${UPDATE_RESUME_PREFIX}${randomUUID28()}.json`), text2, { flag: "wx" }),
|
|
151593
|
+
remove: (name) => rmSync7(join56(dataDir, name), { force: true })
|
|
151594
|
+
};
|
|
151595
|
+
}
|
|
150863
151596
|
|
|
150864
151597
|
// packages/agent-host/src/swap-control.ts
|
|
150865
151598
|
var KEEPS_AGENTS_ACROSS_SWAP = true;
|
|
@@ -150996,7 +151729,7 @@ async function runDrain(bound, deps) {
|
|
|
150996
151729
|
}
|
|
150997
151730
|
|
|
150998
151731
|
// packages/agent-host/src/links/install.ts
|
|
150999
|
-
import { existsSync as existsSync27, readFileSync as
|
|
151732
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24 } from "node:fs";
|
|
151000
151733
|
import { hostname as hostname4 } from "node:os";
|
|
151001
151734
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
151002
151735
|
import { gzipSync } from "node:zlib";
|
|
@@ -151376,8 +152109,8 @@ function detachCommand(spec, from = null) {
|
|
|
151376
152109
|
const name = from === null ? null : hubNameForRemote(from);
|
|
151377
152110
|
return remoteCommand(spec, name ? `serve --detach --from ${name}` : "serve --detach");
|
|
151378
152111
|
}
|
|
151379
|
-
function stopCommand(spec) {
|
|
151380
|
-
return remoteCommand(spec, "serve --stop",
|
|
152112
|
+
function stopCommand(spec, o) {
|
|
152113
|
+
return remoteCommand(spec, "serve --stop", o.forUpdate ? "update" : "stop");
|
|
151381
152114
|
}
|
|
151382
152115
|
function serveCommand(spec) {
|
|
151383
152116
|
return remoteCommand(spec, "serve");
|
|
@@ -151385,15 +152118,15 @@ function serveCommand(spec) {
|
|
|
151385
152118
|
function autostartCommand(spec, action) {
|
|
151386
152119
|
return remoteCommand(spec, `serve --autostart ${action}`);
|
|
151387
152120
|
}
|
|
151388
|
-
function remoteCommand(spec, args, hubStop =
|
|
152121
|
+
function remoteCommand(spec, args, hubStop = null) {
|
|
151389
152122
|
const custom2 = spec.command?.trim() || null;
|
|
151390
152123
|
if (spec.shell === "powershell") {
|
|
151391
152124
|
if (custom2) return powershell(`${custom2} ${args}`);
|
|
151392
152125
|
return powershell(
|
|
151393
|
-
`${hubStop ? "$env:CENTRALU_STOP_BY = 'hub'; " : ""}${MANAGED_PS}; if (Test-Path $m) { & $m ${args} } elseif (Get-Command centralu.cmd -ErrorAction SilentlyContinue) { & centralu.cmd ${args} } else { $l = Join-Path $d 'bin\\centralu.cmd'; if (Test-Path $l) { & $l ${args} } else { '${NOT_FOUND2}' } }`
|
|
152126
|
+
`${hubStop ? "$env:CENTRALU_STOP_BY = 'hub'; " : ""}${hubStop === "update" ? "$env:CENTRALU_STOP_FOR = 'update'; " : ""}${MANAGED_PS}; if (Test-Path $m) { & $m ${args} } elseif (Get-Command centralu.cmd -ErrorAction SilentlyContinue) { & centralu.cmd ${args} } else { $l = Join-Path $d 'bin\\centralu.cmd'; if (Test-Path $l) { & $l ${args} } else { '${NOT_FOUND2}' } }`
|
|
151394
152127
|
);
|
|
151395
152128
|
}
|
|
151396
|
-
const lookup = `${hubStop ? "CENTRALU_STOP_BY=hub; export CENTRALU_STOP_BY; " : ""}${MANAGED_SH}; if [ -x "$m" ]; then exec "$m" ${args}; elif command -v centralu >/dev/null 2>&1; then exec centralu ${args}; elif [ -x "$d/bin/centralu" ]; then exec "$d/bin/centralu" ${args}; else echo ${NOT_FOUND2}; exit 127; fi`;
|
|
152129
|
+
const lookup = `${hubStop ? "CENTRALU_STOP_BY=hub; export CENTRALU_STOP_BY; " : ""}${hubStop === "update" ? "CENTRALU_STOP_FOR=update; export CENTRALU_STOP_FOR; " : ""}${MANAGED_SH}; if [ -x "$m" ]; then exec "$m" ${args}; elif command -v centralu >/dev/null 2>&1; then exec centralu ${args}; elif [ -x "$d/bin/centralu" ]; then exec "$d/bin/centralu" ${args}; else echo ${NOT_FOUND2}; exit 127; fi`;
|
|
151397
152130
|
if (spec.shell === "posix") return custom2 ? `${custom2} ${args}` : `sh -c '${lookup}'`;
|
|
151398
152131
|
const distro = distroOf(spec);
|
|
151399
152132
|
const script = custom2 ? `${custom2} ${args}` : `PATH=$(printf %s "$PATH" | tr : '\\n' | grep -v '^/mnt/' | paste -sd: -); ${lookup}`;
|
|
@@ -151681,7 +152414,7 @@ var SSH_STDIN_READY_MS = SSH_CONNECT_ALLOWANCE_MS + SSH_POWERSHELL_START_MS;
|
|
|
151681
152414
|
var SSH_STDIN_ACK_MS = 3e4;
|
|
151682
152415
|
var FORWARD_STDERR_KEEP = 16 * 1024;
|
|
151683
152416
|
var CLIXML_MARK = /<\/?(?:Objs|Obj|S|MS|PR|TN|TNRef|Props|I32|I64|B|AV)[\s>/]|_x00[0-9A-F]{2}_/;
|
|
151684
|
-
var
|
|
152417
|
+
var ANSI2 = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b[@-_]?/g;
|
|
151685
152418
|
function powershellErrorLine(line) {
|
|
151686
152419
|
const l = line.trim();
|
|
151687
152420
|
if (l === "+" || l.startsWith("+ ")) return null;
|
|
@@ -151693,7 +152426,7 @@ function powershellErrorLine(line) {
|
|
|
151693
152426
|
function clixmlErrorText(xml) {
|
|
151694
152427
|
return [...xml.matchAll(/<S S="Error">([\s\S]*?)<\/S>/g)].map(
|
|
151695
152428
|
(m) => m[1].replace(/_x([0-9A-Fa-f]{4})_/g, (_, hex3) => String.fromCharCode(parseInt(hex3, 16))).replace(/&(lt|gt|quot|apos|amp);/g, (_, e) => ({ lt: "<", gt: ">", quot: '"', apos: "'", amp: "&" })[e])
|
|
151696
|
-
).join("").replace(
|
|
152429
|
+
).join("").replace(ANSI2, "").split(/\r?\n/).flatMap((l) => {
|
|
151697
152430
|
const kept = powershellErrorLine(l);
|
|
151698
152431
|
return kept === null ? [] : [kept];
|
|
151699
152432
|
}).join("\n");
|
|
@@ -151721,7 +152454,7 @@ function withoutClixml(s) {
|
|
|
151721
152454
|
text2 = end < 0 ? "" : text2.slice(end + "</Objs>".length);
|
|
151722
152455
|
}
|
|
151723
152456
|
parts.push(plainPart(text2));
|
|
151724
|
-
return parts.join("").replace(
|
|
152457
|
+
return parts.join("").replace(ANSI2, "").split(/\r?\n/);
|
|
151725
152458
|
}
|
|
151726
152459
|
function lastLine(s, ansi = ansiText) {
|
|
151727
152460
|
const lines = withoutClixml(s).map((l) => l.trim()).filter((l) => l && !l.startsWith("**") && !/^Node\.js v\d/.test(l) && !/^at /.test(l));
|
|
@@ -152036,7 +152769,7 @@ async function installRemote(o) {
|
|
|
152036
152769
|
function bundled(name, source) {
|
|
152037
152770
|
for (const url2 of [new URL(`./${name}`, import.meta.url), new URL(source, import.meta.url)]) {
|
|
152038
152771
|
const file2 = fileURLToPath6(url2);
|
|
152039
|
-
if (existsSync27(file2)) return
|
|
152772
|
+
if (existsSync27(file2)) return readFileSync24(file2, "utf8");
|
|
152040
152773
|
}
|
|
152041
152774
|
throw new Error(`${name} is missing from this host`);
|
|
152042
152775
|
}
|
|
@@ -152620,7 +153353,7 @@ var RemoteClient = class {
|
|
|
152620
153353
|
this.cursorValid = true;
|
|
152621
153354
|
this.ready = true;
|
|
152622
153355
|
this.attempt = 0;
|
|
152623
|
-
this.on.hello?.({ resync, newLifetime: !sameLifetime, protocolVersion: frame.protocolVersion, ...frame.build ? { build: frame.build } : {}, ...typeof frame.elevated === "boolean" ? { elevated: frame.elevated } : {}, ...frame.logon ? { logon: frame.logon } : {}, ...frame.handover ? { handover: frame.handover } : {} });
|
|
153356
|
+
this.on.hello?.({ resync, newLifetime: !sameLifetime, protocolVersion: frame.protocolVersion, ...frame.build ? { build: frame.build } : {}, ...typeof frame.elevated === "boolean" ? { elevated: frame.elevated } : {}, ...frame.logon ? { logon: frame.logon } : {}, ...frame.handover ? { handover: frame.handover } : {}, ...frame.updateContinued ? { updateContinued: frame.updateContinued } : {} });
|
|
152624
153357
|
return;
|
|
152625
153358
|
}
|
|
152626
153359
|
if (frame.kind === "res") {
|
|
@@ -152916,6 +153649,11 @@ var LinkedMachineConnection = class {
|
|
|
152916
153649
|
logon = null;
|
|
152917
153650
|
/** A sign-in's serve waiting to take over from the remote host, as its hello or `host_handover` last said (#1224) */
|
|
152918
153651
|
handover = null;
|
|
153652
|
+
/**
|
|
153653
|
+
* The turns the remote host said in its last hello it continues after an update cut them, with when its start took each
|
|
153654
|
+
* up, on its clock (#1618); null: a host from before that, which continues none
|
|
153655
|
+
*/
|
|
153656
|
+
updateContinued = null;
|
|
152919
153657
|
/** The floor could not be read from this host once already; said once in the log */
|
|
152920
153658
|
floorUnreadable = false;
|
|
152921
153659
|
/**
|
|
@@ -153060,6 +153798,14 @@ var LinkedMachineConnection = class {
|
|
|
153060
153798
|
/** The link has just connected, its hello and lists read; `LinkedMachine` looks at the versions then (#1508) */
|
|
153061
153799
|
connectedNow() {
|
|
153062
153800
|
}
|
|
153801
|
+
/** Whether the remote host continues the turns an update's stop cuts (#1618): its last hello said which it continues */
|
|
153802
|
+
continuesCutTurns() {
|
|
153803
|
+
return this.updateContinued !== null;
|
|
153804
|
+
}
|
|
153805
|
+
/** When the remote host took up this session's turn (its own id) after an update cut it (#1618); null when it did not */
|
|
153806
|
+
continuedAfterUpdate(sessionId) {
|
|
153807
|
+
return this.updateContinued?.get(sessionId) ?? null;
|
|
153808
|
+
}
|
|
153063
153809
|
info() {
|
|
153064
153810
|
return {
|
|
153065
153811
|
id: this.record.id,
|
|
@@ -153565,6 +154311,7 @@ var LinkedMachineConnection = class {
|
|
|
153565
154311
|
}
|
|
153566
154312
|
this.logon = logon2;
|
|
153567
154313
|
this.handover = h.handover ?? null;
|
|
154314
|
+
this.updateContinued = h.updateContinued ? new Map(h.updateContinued.map((c) => [c.sessionId, c.at])) : null;
|
|
153568
154315
|
this.switched = null;
|
|
153569
154316
|
this.switchTried = false;
|
|
153570
154317
|
this.switchFailure = null;
|
|
@@ -153686,8 +154433,8 @@ function parseStopLine(stdout) {
|
|
|
153686
154433
|
}
|
|
153687
154434
|
return null;
|
|
153688
154435
|
}
|
|
153689
|
-
async function stopRemoteHost(c) {
|
|
153690
|
-
const r = await c.exec(stopCommand(c.spec));
|
|
154436
|
+
async function stopRemoteHost(c, o) {
|
|
154437
|
+
const r = await c.exec(stopCommand(c.spec, o));
|
|
153691
154438
|
if (r.stdout.includes(NOT_FOUND2)) return { wasRunning: false };
|
|
153692
154439
|
const answer = parseStopLine(r.stdout);
|
|
153693
154440
|
if (!answer) {
|
|
@@ -153739,7 +154486,7 @@ var restoredNote = (version2, target) => `The store on ${target} was put back as
|
|
|
153739
154486
|
async function startOrRestore(c, to, from) {
|
|
153740
154487
|
const why = await startAndCheck(c, to.version);
|
|
153741
154488
|
if (!why || !isLocalBuildVersion(from?.version) || isLocalBuildVersion(to.version)) return { why };
|
|
153742
|
-
await stopRemoteHost(c).catch(() => void 0);
|
|
154489
|
+
await stopRemoteHost(c, { forUpdate: true }).catch(() => void 0);
|
|
153743
154490
|
let restored = false;
|
|
153744
154491
|
try {
|
|
153745
154492
|
restored = await restoreStore(c, to);
|
|
@@ -153757,7 +154504,7 @@ async function putBack(c, to, why, was, restore = false) {
|
|
|
153757
154504
|
const log = tail ? ` The end of its host.log: ${tail}` : "";
|
|
153758
154505
|
const back = was.current ? `Centralu ${was.current.version}` : "the Centralu that was there";
|
|
153759
154506
|
try {
|
|
153760
|
-
await stopRemoteHost(c).catch(() => void 0);
|
|
154507
|
+
await stopRemoteHost(c, { forUpdate: true }).catch(() => void 0);
|
|
153761
154508
|
await setPointers(c, to, was.current, was.previous);
|
|
153762
154509
|
if (restore && was.current) await restoreStore(c, was.current);
|
|
153763
154510
|
if (was.running) await c.start();
|
|
@@ -153778,7 +154525,7 @@ async function switchRemote(c, next, was, keep, o = {}) {
|
|
|
153778
154525
|
const stoppedTurns = await c.beforeStop?.() ?? null;
|
|
153779
154526
|
await c.hold();
|
|
153780
154527
|
c.step("stop");
|
|
153781
|
-
const { wasRunning } = await stopRemoteHost(c);
|
|
154528
|
+
const { wasRunning } = await stopRemoteHost(c, { forUpdate: true });
|
|
153782
154529
|
const saveStore = o.saveStore === true && was.current !== null;
|
|
153783
154530
|
if (saveStore && was.current) {
|
|
153784
154531
|
c.step("backup");
|
|
@@ -153801,7 +154548,7 @@ async function switchRemote(c, next, was, keep, o = {}) {
|
|
|
153801
154548
|
async function rollbackRemote(c, there) {
|
|
153802
154549
|
await c.hold();
|
|
153803
154550
|
c.step("stop");
|
|
153804
|
-
const { wasRunning } = await stopRemoteHost(c);
|
|
154551
|
+
const { wasRunning } = await stopRemoteHost(c, { forUpdate: true });
|
|
153805
154552
|
c.step("switch");
|
|
153806
154553
|
await setPointers(c, there.previous, there.previous, null);
|
|
153807
154554
|
const { why, note } = await startOrRestore(c, there.previous, there.current);
|
|
@@ -153827,7 +154574,7 @@ function uninstallCommand(spec) {
|
|
|
153827
154574
|
async function uninstallRemote(c) {
|
|
153828
154575
|
await c.hold();
|
|
153829
154576
|
c.step("stop");
|
|
153830
|
-
const { wasRunning } = await stopRemoteHost(c);
|
|
154577
|
+
const { wasRunning } = await stopRemoteHost(c, { forUpdate: false });
|
|
153831
154578
|
c.step("autostart");
|
|
153832
154579
|
await removeAutostart(c);
|
|
153833
154580
|
c.step("remove");
|
|
@@ -153840,7 +154587,6 @@ var linkError2 = (message) => rpcError(message);
|
|
|
153840
154587
|
var ACTIVITY_POLL_MS2 = 15e3;
|
|
153841
154588
|
var ACTIVITY_SETTLE_MS = 3e3;
|
|
153842
154589
|
var ACTIVITY_UNREAD_RETRIES = 2;
|
|
153843
|
-
var OPEN_WORK_WAIT_MS = 30 * 6e4;
|
|
153844
154590
|
var FOLLOW_RETRY_MS = 10 * 6e4;
|
|
153845
154591
|
var FOLLOW_TRIES = 2;
|
|
153846
154592
|
var reasonOf = (signal) => signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
|
|
@@ -153895,9 +154641,10 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
153895
154641
|
}
|
|
153896
154642
|
/**
|
|
153897
154643
|
* `machines.update` (update.ts, plan §10.5): install beside, wait until no turn runs there (`when: 'idle'`, the default, #1399),
|
|
153898
|
-
* stop, switch, start, check, or put back. `when: 'now'`: no wait, the turns running are stopped
|
|
153899
|
-
*
|
|
153900
|
-
*
|
|
154644
|
+
* stop, switch, start, check, or put back. `when: 'now'`: no wait, the turns running are stopped, the answer says so, and the
|
|
154645
|
+
* next host continues each of them once (#1618, `stopAtOnce`). `unasked`: an update nobody approved, which automatic updates
|
|
154646
|
+
* cover (#1508): the hub's own catch-up (`followHub`), or an agent's call that went without a card (`followsHub`). It goes at
|
|
154647
|
+
* once too (owner, 2026-10-26, #1618: a machine always busy never updated), and stops no host it cannot ask what runs
|
|
153901
154648
|
*/
|
|
153902
154649
|
update(opts = {}) {
|
|
153903
154650
|
return opts.unasked === true ? this.unaskedRun(() => this.updateNow(opts)) : this.updateNow(opts);
|
|
@@ -153929,7 +154676,6 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
153929
154676
|
cancelUpdate() {
|
|
153930
154677
|
const wait = this.idleWait;
|
|
153931
154678
|
if (!wait) return false;
|
|
153932
|
-
if (this.followRun && this.follow === this.followRun) Object.assign(this.follow, { tries: FOLLOW_TRIES, cancelled: true });
|
|
153933
154679
|
wait.abort(new Error(`The update of ${this.name} was cancelled while it waited for the sessions there to finish; nothing was switched`));
|
|
153934
154680
|
return true;
|
|
153935
154681
|
}
|
|
@@ -153947,35 +154693,18 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
153947
154693
|
* every answer failed is read again twice, then not waited for either: waiting on what was never seen would never end. Once an
|
|
153948
154694
|
* answer has come, though, the wait goes on through failed readings (the row says `wait_unread`) and the host is stopped only on
|
|
153949
154695
|
* a successful reading of none in a turn, since a busy or dropped remote reads as failed too; Cancel or Update now are the ways
|
|
153950
|
-
* out. `now
|
|
153951
|
-
*
|
|
153952
|
-
* `unasked` (#1508): an update nobody approved stops nothing it cannot see, so a machine that cannot be told what runs there is
|
|
153953
|
-
* not updated by it (the person's Update still can), and it gives up once automatic updates go off or the hub hands over
|
|
153954
|
-
* to another build (`LinkDeps.autoUpdate`). Either way it ends before the host is held, so nothing was switched
|
|
154696
|
+
* out. `now`, and every update nobody approved (`unasked`, #1508; at once since #1618): `stopAtOnce`.
|
|
153955
154697
|
*/
|
|
153956
154698
|
async beforeStop(c, when, unasked = false) {
|
|
153957
|
-
if (when === "now")
|
|
153958
|
-
const running2 = turnsRunning(await this.activity());
|
|
153959
|
-
if (running2 === 0) return null;
|
|
153960
|
-
const note = `Updating stopped Centralu on ${this.name} with ${running2} ${running2 === 1 ? "session" : "sessions"} in a turn; they resume on the new version, and a turn in progress was lost`;
|
|
153961
|
-
this.deps.log?.(`[links] ${this.name}: ${note}`);
|
|
153962
|
-
return note;
|
|
153963
|
-
}
|
|
154699
|
+
if (when === "now" || unasked) return this.stopAtOnce(unasked);
|
|
153964
154700
|
const wait = new AbortController();
|
|
153965
154701
|
this.idleWait = wait;
|
|
153966
154702
|
const poll = this.deps.activityPollMs ?? ACTIVITY_POLL_MS2;
|
|
153967
154703
|
const settle = Math.min(poll, ACTIVITY_SETTLE_MS);
|
|
153968
|
-
const stillOn = () => {
|
|
153969
|
-
if (unasked && this.deps.autoUpdate?.() !== true) {
|
|
153970
|
-
throw new Error(`Automatic updates went off, or this computer began switching to another build, while the update of ${this.name} waited; nothing was switched`);
|
|
153971
|
-
}
|
|
153972
|
-
};
|
|
153973
154704
|
const read = async () => {
|
|
153974
154705
|
wait.signal.throwIfAborted();
|
|
153975
|
-
stillOn();
|
|
153976
154706
|
const r = await this.readActivity();
|
|
153977
154707
|
wait.signal.throwIfAborted();
|
|
153978
|
-
stillOn();
|
|
153979
154708
|
return r;
|
|
153980
154709
|
};
|
|
153981
154710
|
try {
|
|
@@ -153992,32 +154721,17 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
153992
154721
|
const unread = () => show("wait_unread", "what runs there cannot be read now; the update keeps waiting, since the sessions there may be in a turn (Cancel ends it)");
|
|
153993
154722
|
const busy = (a) => {
|
|
153994
154723
|
const turns = turnsRunning(a);
|
|
153995
|
-
|
|
153996
|
-
if (turns > 0) show("wait_idle", `the update waits for ${turns} ${turns === 1 ? "session" : "sessions"} in a turn`, turns);
|
|
153997
|
-
else show("wait_open", `the update waits for ${n} open ${n === 1 ? "terminal or project command" : "terminals and project commands"}, which an update this computer starts by itself closes none of`, n);
|
|
154724
|
+
show("wait_idle", `the update waits for ${turns} ${turns === 1 ? "session" : "sessions"} in a turn`, turns);
|
|
153998
154725
|
};
|
|
153999
|
-
const open6 = (a) => unasked ? a.terminals + a.commandRuns : 0;
|
|
154000
|
-
const holding = (a) => turnsRunning(a) + open6(a);
|
|
154001
|
-
let openSince = null;
|
|
154002
|
-
const now = this.deps.now ?? Date.now;
|
|
154003
|
-
const openLimit = this.deps.openWorkWaitMs ?? OPEN_WORK_WAIT_MS;
|
|
154004
154726
|
for (; ; ) {
|
|
154005
154727
|
const r = await read();
|
|
154006
154728
|
if (r.activity) {
|
|
154007
154729
|
seen = true;
|
|
154008
154730
|
blind = 0;
|
|
154009
|
-
if (
|
|
154010
|
-
openSince ??= now();
|
|
154011
|
-
if (now() - openSince >= openLimit) {
|
|
154012
|
-
throw new Error(
|
|
154013
|
-
`Terminals or project commands stayed open on ${this.name}, and an update this computer starts by itself closes none of them; nothing was switched`
|
|
154014
|
-
);
|
|
154015
|
-
}
|
|
154016
|
-
} else openSince = null;
|
|
154017
|
-
if (holding(r.activity) === 0) {
|
|
154731
|
+
if (turnsRunning(r.activity) === 0) {
|
|
154018
154732
|
await pause(settle, wait.signal);
|
|
154019
154733
|
const again = await read();
|
|
154020
|
-
if (again.activity &&
|
|
154734
|
+
if (again.activity && turnsRunning(again.activity) === 0) {
|
|
154021
154735
|
if (shown3) c.step("stop");
|
|
154022
154736
|
return null;
|
|
154023
154737
|
}
|
|
@@ -154027,7 +154741,6 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154027
154741
|
} else if (seen) {
|
|
154028
154742
|
unread();
|
|
154029
154743
|
} else if (r.cannot || ++blind > ACTIVITY_UNREAD_RETRIES) {
|
|
154030
|
-
if (unasked) throw new Error(this.blindSentence());
|
|
154031
154744
|
this.deps.log?.(`[links] ${this.name}: it cannot be told what runs there, so the update does not wait for it`);
|
|
154032
154745
|
return null;
|
|
154033
154746
|
}
|
|
@@ -154038,14 +154751,38 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154038
154751
|
}
|
|
154039
154752
|
}
|
|
154040
154753
|
/**
|
|
154041
|
-
*
|
|
154042
|
-
*
|
|
154754
|
+
* An update that does not wait (#1618): the person's `when: 'now'`, and every update nobody approved (owner, 2026-10-26: the
|
|
154755
|
+
* hub's own catch-up used to wait for the turns there, and for its terminals and project commands, and a machine always busy
|
|
154756
|
+
* never updated). It reads what runs there once, for the answer's sentence, and the host is stopped: the turns running end,
|
|
154757
|
+
* and terminals and project commands close. Which turns that stop ends the stopping host lists itself, as it stops
|
|
154758
|
+
* (`host.stop { forUpdate }`, update.ts `stopRemoteHost`), and the host the update starts continues each of them once.
|
|
154759
|
+
*
|
|
154760
|
+
* An update nobody approved stops no host it cannot ask what runs (#1508): a reading that fails is tried again as
|
|
154761
|
+
* `probeActivity` does, then the update fails before the hold, nothing switched. The person's `now` goes on unread
|
|
154762
|
+
*/
|
|
154763
|
+
async stopAtOnce(unasked) {
|
|
154764
|
+
const a = unasked ? await this.probeActivity() : await this.activity();
|
|
154765
|
+
const running2 = turnsRunning(a);
|
|
154766
|
+
const open6 = a ? a.terminals + a.commandRuns : 0;
|
|
154767
|
+
const goesOn = this.continuesCutTurns();
|
|
154768
|
+
const after = goesOn ? "Centralu continues each of those turns on the new version" : "they resume on the new version, and a turn in progress was lost";
|
|
154769
|
+
if (unasked) this.deps.log?.(`[links] ${this.name}: updating it now, since automatic updates are on${running2 ? `; ${running2} ${running2 === 1 ? "session" : "sessions"} in a turn there: ${after}` : ""}`);
|
|
154770
|
+
if (running2 === 0) return null;
|
|
154771
|
+
const closed = open6 ? `, and ${open6} open ${open6 === 1 ? "terminal or project command" : "terminals and project commands"} there closed` : "";
|
|
154772
|
+
const note = `Updating stopped Centralu on ${this.name} with ${running2} ${running2 === 1 ? "session" : "sessions"} in a turn; ${after}${closed}`;
|
|
154773
|
+
this.deps.log?.(`[links] ${this.name}: ${note}`);
|
|
154774
|
+
return note;
|
|
154775
|
+
}
|
|
154776
|
+
/**
|
|
154777
|
+
* Before an update nobody approved installs anything (#1508 review), and again right before it stops the host (#1618): what
|
|
154778
|
+
* runs there must be readable. A failed reading is tried again twice, a poll apart, as the wait does
|
|
154779
|
+
* (`ACTIVITY_UNREAD_RETRIES`); a host that cannot be asked at all is not
|
|
154043
154780
|
*/
|
|
154044
154781
|
async probeActivity() {
|
|
154045
154782
|
const poll = this.deps.activityPollMs ?? ACTIVITY_POLL_MS2;
|
|
154046
154783
|
for (let i = 0; ; i++) {
|
|
154047
154784
|
const r = await this.readActivity();
|
|
154048
|
-
if (r.activity) return;
|
|
154785
|
+
if (r.activity) return r.activity;
|
|
154049
154786
|
if (r.cannot || i >= ACTIVITY_UNREAD_RETRIES) throw linkError2(this.blindSentence());
|
|
154050
154787
|
await pause(poll, new AbortController().signal);
|
|
154051
154788
|
}
|
|
@@ -154060,8 +154797,6 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154060
154797
|
* failed, and the retry's timer. `due`: the retry's time has come, and it runs at the next chance (connected, not busy)
|
|
154061
154798
|
*/
|
|
154062
154799
|
follow = null;
|
|
154063
|
-
/** The hub's own update that runs now, if one does */
|
|
154064
|
-
followRun = null;
|
|
154065
154800
|
/**
|
|
154066
154801
|
* Whether an idle update of this machine is the hub's catch-up that needs no one's approval (#1508): the hub's automatic
|
|
154067
154802
|
* updates are on (`LinkDeps.autoUpdate`), this hub can update it (`operationRefusal`), the person did not remove what the
|
|
@@ -154076,7 +154811,7 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154076
154811
|
const hub = this.deps.hub.version;
|
|
154077
154812
|
if (!v || v.hub.version !== hub || isLocalBuildVersion(hub) || isLocalBuildVersion(v.remote.version)) return false;
|
|
154078
154813
|
const f = this.follow;
|
|
154079
|
-
if (f?.version === hub &&
|
|
154814
|
+
if (f?.version === hub && f.tries >= FOLLOW_TRIES) return false;
|
|
154080
154815
|
return v.older === "remote";
|
|
154081
154816
|
}
|
|
154082
154817
|
/** At every connect: a machine found on an older release follows the hub (#1508) */
|
|
@@ -154084,9 +154819,10 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154084
154819
|
this.followHub();
|
|
154085
154820
|
}
|
|
154086
154821
|
/**
|
|
154087
|
-
* Starts the hub's own
|
|
154088
|
-
*
|
|
154089
|
-
*
|
|
154822
|
+
* Starts the hub's own update of this machine (#1508) when it follows the hub (`followsHub`), the link is up and nothing
|
|
154823
|
+
* else runs there: once per version of the hub's, and once more `FOLLOW_RETRY_MS` after a failure. It does not wait for
|
|
154824
|
+
* what runs there (#1618, `stopAtOnce`): the turns it ends continue on the new version, and terminals and project
|
|
154825
|
+
* commands close. The steps show on the row as the person's do
|
|
154090
154826
|
*/
|
|
154091
154827
|
followHub() {
|
|
154092
154828
|
if (this.stopped || this.busy || this.status !== "connected" || !this.followsHub()) return;
|
|
@@ -154097,13 +154833,13 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154097
154833
|
this.follow = { version: version2, tries: 0, failed: null, retryAt: null, due: false };
|
|
154098
154834
|
}
|
|
154099
154835
|
this.deps.log?.(
|
|
154100
|
-
`[links] ${this.name}: runs Centralu ${this.versions?.remote.version ?? "an older version"}, older than this computer's ${version2}; updating it
|
|
154836
|
+
`[links] ${this.name}: runs Centralu ${this.versions?.remote.version ?? "an older version"}, older than this computer's ${version2}; updating it, since automatic updates are on`
|
|
154101
154837
|
);
|
|
154102
|
-
void this.update({ when: "
|
|
154838
|
+
void this.update({ when: "now", unasked: true }).catch(() => void 0);
|
|
154103
154839
|
}
|
|
154104
154840
|
/**
|
|
154105
154841
|
* Runs an update nobody approved as one try for this hub's version (#1508 review): the hub's own catch-up and an agent's
|
|
154106
|
-
* card-less `machines.update` or `install_machine` alike, so
|
|
154842
|
+
* card-less `machines.update` or `install_machine` alike, so two failures spend the version for both
|
|
154107
154843
|
* (`followsHub`). A failure shows on the row and, with a try left, is tried once more `FOLLOW_RETRY_MS` later. A call refused
|
|
154108
154844
|
* because something else runs there spends nothing
|
|
154109
154845
|
*/
|
|
@@ -154120,7 +154856,6 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154120
154856
|
f.tries += 1;
|
|
154121
154857
|
f.due = false;
|
|
154122
154858
|
f.retryAt = null;
|
|
154123
|
-
this.followRun = f;
|
|
154124
154859
|
try {
|
|
154125
154860
|
const r = await run4();
|
|
154126
154861
|
if (this.follow === f) {
|
|
@@ -154130,8 +154865,8 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154130
154865
|
return r;
|
|
154131
154866
|
} catch (err) {
|
|
154132
154867
|
if (this.follow === f && !this.stopped) {
|
|
154133
|
-
f.failed =
|
|
154134
|
-
if (
|
|
154868
|
+
f.failed = errorMessage(err);
|
|
154869
|
+
if (f.tries < FOLLOW_TRIES) {
|
|
154135
154870
|
const ms3 = this.deps.followRetryMs ?? FOLLOW_RETRY_MS;
|
|
154136
154871
|
f.retryAt = Date.now() + ms3;
|
|
154137
154872
|
f.timer = setTimeout(() => {
|
|
@@ -154144,8 +154879,6 @@ var LinkedMachine = class extends LinkedMachineConnection {
|
|
|
154144
154879
|
this.deps.broadcast({ type: "machine_status", machine: this.info() });
|
|
154145
154880
|
}
|
|
154146
154881
|
throw err;
|
|
154147
|
-
} finally {
|
|
154148
|
-
if (this.followRun === f) this.followRun = null;
|
|
154149
154882
|
}
|
|
154150
154883
|
}
|
|
154151
154884
|
info() {
|
|
@@ -154479,7 +155212,7 @@ function probeTarget(target, opts = {}) {
|
|
|
154479
155212
|
}
|
|
154480
155213
|
|
|
154481
155214
|
// packages/agent-host/src/links/ssh-config.ts
|
|
154482
|
-
import { readdirSync as
|
|
155215
|
+
import { readdirSync as readdirSync16, readFileSync as readFileSync25, statSync as statSync15 } from "node:fs";
|
|
154483
155216
|
import { homedir as homedir16 } from "node:os";
|
|
154484
155217
|
import { isAbsolute as isAbsolute11, join as join57, normalize, parse as parse5, sep as sep11 } from "node:path";
|
|
154485
155218
|
var MAX_DEPTH = 16;
|
|
@@ -154552,7 +155285,7 @@ function expandGlob(path2) {
|
|
|
154552
155285
|
}
|
|
154553
155286
|
let names;
|
|
154554
155287
|
try {
|
|
154555
|
-
names =
|
|
155288
|
+
names = readdirSync16(base2);
|
|
154556
155289
|
} catch {
|
|
154557
155290
|
continue;
|
|
154558
155291
|
}
|
|
@@ -154589,7 +155322,7 @@ function readSshConfig(opts) {
|
|
|
154589
155322
|
}
|
|
154590
155323
|
let text2;
|
|
154591
155324
|
try {
|
|
154592
|
-
text2 =
|
|
155325
|
+
text2 = readFileSync25(file2, "utf8");
|
|
154593
155326
|
} catch (err) {
|
|
154594
155327
|
if (depth > 0 && err.code !== "ENOENT") notes.push(`${tildePath(file2, opts.home)} could not be read`);
|
|
154595
155328
|
return;
|
|
@@ -155294,6 +156027,13 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
155294
156027
|
machineWaitsForPerson(machineId) {
|
|
155295
156028
|
return machineById(machineId)?.waitsForPerson === true;
|
|
155296
156029
|
},
|
|
156030
|
+
continuesCutTurns(machineId) {
|
|
156031
|
+
return machineById(machineId)?.continuesCutTurns?.() === true;
|
|
156032
|
+
},
|
|
156033
|
+
continuedAfterUpdate(sessionId) {
|
|
156034
|
+
const at2 = splitQualified(sessionId);
|
|
156035
|
+
return (at2 && machineById(at2.machine)?.continuedAfterUpdate?.(at2.id)) ?? null;
|
|
156036
|
+
},
|
|
155297
156037
|
async backgroundTasks(machineId) {
|
|
155298
156038
|
const m = machineById(machineId);
|
|
155299
156039
|
if (!m || !m.reachable) return null;
|
|
@@ -156665,7 +157405,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
|
|
|
156665
157405
|
console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
|
|
156666
157406
|
process.exit(1);
|
|
156667
157407
|
}) : launch.token;
|
|
156668
|
-
var BUILD = true ? "
|
|
157408
|
+
var BUILD = true ? "6e6bc62" : "dev";
|
|
156669
157409
|
var { values } = parseArgs({
|
|
156670
157410
|
options: {
|
|
156671
157411
|
port: { type: "string", default: "5175" },
|
|
@@ -156744,6 +157484,7 @@ if (!lock.ok) {
|
|
|
156744
157484
|
var ownership = lock;
|
|
156745
157485
|
process.on("exit", ownership.release);
|
|
156746
157486
|
if (dbPath !== ":memory:") clearStoppedMark(dirname26(dbPath));
|
|
157487
|
+
var updateResume = dbPath === ":memory:" ? void 0 : updateResumeFolder(dirname26(dbPath));
|
|
156747
157488
|
start.at("hold signals");
|
|
156748
157489
|
var signals = holdSignals(process, (line) => console.error(line));
|
|
156749
157490
|
start.at("store");
|
|
@@ -156778,7 +157519,7 @@ var mgr = new SessionManager(
|
|
|
156778
157519
|
(sessionId) => bridgeAddress(launch.frontDoor, port, token, sessionId),
|
|
156779
157520
|
// Worktrees are created next to the data folder — dev and the packaged app never touch each other's worktrees
|
|
156780
157521
|
join58(dirname26(dbPath), "worktrees"),
|
|
156781
|
-
held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {}
|
|
157522
|
+
{ ...held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {}, ...updateResume ? { updateResume } : {} }
|
|
156782
157523
|
);
|
|
156783
157524
|
mgr.useProjectsHome(() => projectsHome());
|
|
156784
157525
|
mgr.useMemoryGate(new MemoryGate());
|
|
@@ -157005,8 +157746,9 @@ if (launch.underKeeper) {
|
|
|
157005
157746
|
}
|
|
157006
157747
|
var stopNow = null;
|
|
157007
157748
|
var stopAsked = false;
|
|
157008
|
-
var stopFromRequest = () => {
|
|
157009
|
-
console.error(
|
|
157749
|
+
var stopFromRequest = (o) => {
|
|
157750
|
+
console.error(`[agent-host] asked to stop (centralu serve --stop${o.forUpdate ? ", for an update" : ""})`);
|
|
157751
|
+
if (o.forUpdate) mgr.stopForUpdate();
|
|
157010
157752
|
if (stopNow) stopNow();
|
|
157011
157753
|
else stopAsked = true;
|
|
157012
157754
|
};
|
|
@@ -157081,7 +157823,7 @@ var router = new Router({
|
|
|
157081
157823
|
inlineViews,
|
|
157082
157824
|
heldPids: held2?.heldPids,
|
|
157083
157825
|
machines: links,
|
|
157084
|
-
stopHost: launch.startedByServe && !launch.underKeeper ? () => stopFromRequest() : void 0,
|
|
157826
|
+
stopHost: launch.startedByServe && !launch.underKeeper ? (o) => stopFromRequest(o) : void 0,
|
|
157085
157827
|
// `host.stop {ifIdle}`: the check and the refusal of new calls in one step (#1224, lifecycle/stop-gate.ts)
|
|
157086
157828
|
stopGate: launch.startedByServe && !launch.underKeeper ? createStopGate({ drain: hostDrain, activity: () => activityCounts(activity()) }) : void 0,
|
|
157087
157829
|
// A sign-in's serve says it waits to take over, or gave up (#1224): kept for every hello, and told to who is connected
|
|
@@ -157139,6 +157881,8 @@ var server = new HostServer({
|
|
|
157139
157881
|
// Which Windows logon it runs in, for a hub linked to it (#1224); asked with the elevation
|
|
157140
157882
|
logon: () => logon,
|
|
157141
157883
|
handover: () => handover,
|
|
157884
|
+
// The turns this host continued after an update cut them, for a linked hub's callers (#1618)
|
|
157885
|
+
updateContinued: () => mgr.updateContinued(),
|
|
157142
157886
|
// A planned swap waits for running RPCs, within a bound (#280 step 3, drain.ts)
|
|
157143
157887
|
drain: hostDrain
|
|
157144
157888
|
});
|