@tokenfactory/acc-runner 0.40.14 → 0.40.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/companion-run.d.ts +0 -2
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +65 -16
- package/dist/companion-run.js.map +1 -1
- package/dist/companion.d.ts +41 -1
- package/dist/companion.d.ts.map +1 -1
- package/dist/companion.js +73 -0
- package/dist/companion.js.map +1 -1
- package/dist/watch.d.ts +90 -2
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +240 -126
- package/dist/watch.js.map +1 -1
- package/package.json +1 -1
package/dist/watch.js
CHANGED
|
@@ -43,6 +43,14 @@ import { acquireSingletonLock, singletonRunnerId, SingletonLockHeldError, } from
|
|
|
43
43
|
import { getClaudeVersion, recordTaskClaudeVersion, } from "./runtime/version-drift.js";
|
|
44
44
|
import { autoUpgradeDisabled, maybeSelfUpgrade, maybeSelfUpdateOn426, } from "./runtime/self-upgrade.js";
|
|
45
45
|
import { postRunnerStateMessage } from "./messaging.js";
|
|
46
|
+
// AS-3 (RESUME-WIRE): drive the live capacity pause/resume through the
|
|
47
|
+
// ResumeController (pause-until reset OR account-change OR ping) instead of a
|
|
48
|
+
// fixed pause-until-reset setTimeout, and off the AS-2 account-probe cadence.
|
|
49
|
+
import { ResumeController } from "./capacity/resume-controller.js";
|
|
50
|
+
import { RUNNER_CAPACITY_PAUSED_VERB, RUNNER_CAPACITY_RESUME_VERB, } from "./capacity/fleet-pause-verbs.js";
|
|
51
|
+
import { AccountProbeScheduler } from "./engines/account-probe.js";
|
|
52
|
+
import { getChatEngine } from "./engines/registry.js";
|
|
53
|
+
import { unknownAccountIdentity } from "./engines/account-identity.js";
|
|
46
54
|
import { checkVersion, compareSemver } from "./version-check.js";
|
|
47
55
|
import { PACKAGE_VERSION } from "./pkg-version.js";
|
|
48
56
|
import { resolveAuthMode, describeAuthMode, authPrecedenceHint } from "./provider-auth.js";
|
|
@@ -1119,12 +1127,24 @@ export async function watchCommand(options = {}) {
|
|
|
1119
1127
|
pausedCapacity: false,
|
|
1120
1128
|
pausedUntil: null,
|
|
1121
1129
|
capacityTriggerTaskId: null,
|
|
1122
|
-
|
|
1130
|
+
capacityPauseDetail: "",
|
|
1131
|
+
// AS-3 (RESUME-WIRE): assigned immediately below once `state` exists (the
|
|
1132
|
+
// controller's seams close over `state`). `undefined as unknown as` mirrors
|
|
1133
|
+
// the channel/timer bootstrap fields above.
|
|
1134
|
+
resumeController: undefined,
|
|
1135
|
+
accountProbe: null,
|
|
1136
|
+
resumeAccountKey: null,
|
|
1123
1137
|
capacityProbe: false,
|
|
1124
1138
|
capacityBackoffMs: options.capacityBackoffMs ?? DEFAULT_CAPACITY_BACKOFF_MS,
|
|
1125
1139
|
resumeJitterMs: options.resumeJitterMs ?? DEFAULT_RESUME_JITTER_MS,
|
|
1126
1140
|
singletonLock,
|
|
1127
1141
|
};
|
|
1142
|
+
// AS-3 (RESUME-WIRE): build the capacity resume governor + account-probe
|
|
1143
|
+
// cadence NOW, before subscribeChannel / the first pump, so a capacity signal
|
|
1144
|
+
// on the very first claim routes through the controller. Awaited so the
|
|
1145
|
+
// starting account is seeded before any claim can 429. Both timers unref so
|
|
1146
|
+
// the probe/poll cadence never holds the process open on its own.
|
|
1147
|
+
await wireCapacityResume(state, taskRunnerFactory, options);
|
|
1128
1148
|
subscribeChannel(state, taskRunnerFactory);
|
|
1129
1149
|
// v0.34-B / v0.62 (T-62-1): one-shot startup claim scan. The 5-minute
|
|
1130
1150
|
// pollSince watermark (v0.30-B) can miss a task dispatched (runner_id set)
|
|
@@ -1279,8 +1299,10 @@ export async function watchCommand(options = {}) {
|
|
|
1279
1299
|
clearInterval(state.idleTimer);
|
|
1280
1300
|
if (state.selfUpgradeTimer)
|
|
1281
1301
|
clearInterval(state.selfUpgradeTimer); // FIX-K
|
|
1282
|
-
|
|
1283
|
-
|
|
1302
|
+
// AS-3 (RESUME-WIRE): cancel the resume poll + account-probe cadence
|
|
1303
|
+
// (replaces the v0.56 fixed pause-until-reset timer).
|
|
1304
|
+
state.resumeController.stop();
|
|
1305
|
+
state.accountProbe?.stop();
|
|
1284
1306
|
// v0.10 T-49-2: cancel every in-flight task (replaces v0.9 single cancel).
|
|
1285
1307
|
for (const ctrl of state.running.values()) {
|
|
1286
1308
|
ctrl.cancel();
|
|
@@ -1763,142 +1785,234 @@ consecutive) {
|
|
|
1763
1785
|
})();
|
|
1764
1786
|
}
|
|
1765
1787
|
/**
|
|
1766
|
-
*
|
|
1788
|
+
* AS-3 (RESUME-WIRE): enter a capacity pause.
|
|
1767
1789
|
*
|
|
1768
|
-
* Triggered when a task OR a review reports capacity_exhausted (Claude
|
|
1769
|
-
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1790
|
+
* Triggered when a task OR a review reports capacity_exhausted (Claude account
|
|
1791
|
+
* out of session/usage capacity). Since AS-3 the actual pause is owned by the
|
|
1792
|
+
* ResumeController: this helper resolves the reset instant (stated reset, else
|
|
1793
|
+
* the default backoff, plus the v0.56 spread jitter), stashes the trigger task +
|
|
1794
|
+
* cause for the pause seams, and hands the cap to `reportCapacity`. The
|
|
1795
|
+
* controller folds it onto the current account's window (extend-only), ENTERS
|
|
1796
|
+
* the pause through the stopClaims/setServerPause seams (which stop claiming and
|
|
1797
|
+
* emit the canonical `runner.capacity_paused` event + bus message), and owns the
|
|
1798
|
+
* resume timing — reset elapsed OR a logged-in-account change — REPLACING the
|
|
1799
|
+
* fixed pause-until-reset `setTimeout`.
|
|
1778
1800
|
*
|
|
1779
|
-
* Idempotent: a
|
|
1780
|
-
* the
|
|
1781
|
-
* after the first one can't shorten the wait.
|
|
1801
|
+
* Idempotent: a straggler cap while already paused only ever pushes the reset
|
|
1802
|
+
* later (the window fold's extend-only rule), never shortens the wait.
|
|
1782
1803
|
*/
|
|
1783
1804
|
function enterCapacityPause(state, triggerTaskId, resumeAtIso, detail) {
|
|
1784
1805
|
const now = Date.now();
|
|
1785
1806
|
const parsed = resumeAtIso ? Date.parse(resumeAtIso) : NaN;
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
//
|
|
1789
|
-
if (state.pausedCapacity && state.pausedUntil !== null && resumeMs <= state.pausedUntil) {
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
state.pausedCapacity = true;
|
|
1793
|
-
state.pausedUntil = resumeMs;
|
|
1794
|
-
// FH-1a: remember which task drove the pause so resumeFromCapacity can emit
|
|
1795
|
-
// a task-scoped `task.capacity_resumed` for it once the window reopens.
|
|
1796
|
-
state.capacityTriggerTaskId = triggerTaskId;
|
|
1797
|
-
if (state.pauseTimer) {
|
|
1798
|
-
clearTimeout(state.pauseTimer);
|
|
1799
|
-
state.pauseTimer = null;
|
|
1800
|
-
}
|
|
1807
|
+
const baseResumeMs = Number.isFinite(parsed) && parsed > now ? parsed : now + state.capacityBackoffMs;
|
|
1808
|
+
// Preserve the v0.56 resume jitter (spreads a fleet's simultaneous resume)
|
|
1809
|
+
// by folding it into the reset instant the controller waits on.
|
|
1801
1810
|
const jitter = state.resumeJitterMs > 0 ? Math.floor(Math.random() * state.resumeJitterMs) : 0;
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
});
|
|
1819
|
-
}
|
|
1820
|
-
catch { /* best-effort */ }
|
|
1821
|
-
})();
|
|
1822
|
-
// Runner-level bus message so operators see the paused status + reason on
|
|
1823
|
-
// /messages even though heartbeat_runner itself can't carry a detail field.
|
|
1824
|
-
// Anchored to the triggering task so the RPC can resolve the org scope
|
|
1825
|
-
// (post_agent_message looks up org_id from acc.tasks for a non-null id).
|
|
1826
|
-
void postRunnerStateMessage(state.supabase, {
|
|
1827
|
-
task_id: triggerTaskId,
|
|
1828
|
-
sender_id: state.session.runner_id,
|
|
1829
|
-
state: "blocked",
|
|
1830
|
-
protocol: "info",
|
|
1831
|
-
payload: {
|
|
1832
|
-
capacity_paused: true,
|
|
1833
|
-
runner_id: state.session.runner_id,
|
|
1834
|
-
resume_at: resumeAtFinal,
|
|
1835
|
-
source,
|
|
1836
|
-
detail,
|
|
1837
|
-
},
|
|
1838
|
-
kind: "blocked",
|
|
1839
|
-
}).catch((err) => {
|
|
1840
|
-
process.stderr.write(`[acc-runner] capacity pause bus post failed: ${err.message}\n`);
|
|
1811
|
+
const resumeMs = baseResumeMs + jitter;
|
|
1812
|
+
// FH-1a: remember which task drove the pause so the resume path can emit a
|
|
1813
|
+
// task-scoped `task.capacity_resumed` for it once the window reopens; stash
|
|
1814
|
+
// the non-secret cause so the setServerPause seam can carry it into the
|
|
1815
|
+
// canonical pause payload + bus message (the seam only sees {account, reset}).
|
|
1816
|
+
state.capacityTriggerTaskId = triggerTaskId;
|
|
1817
|
+
state.capacityPauseDetail = detail;
|
|
1818
|
+
// Stop claiming SYNCHRONOUSLY (the controller's stopClaims seam runs on the
|
|
1819
|
+
// serialized action tail a microtask later; pump()/pumpReviews() read this
|
|
1820
|
+
// flag between their own awaits, so setting it here closes the gap and keeps
|
|
1821
|
+
// the v0.56 "no new claim the instant a 429 lands" guarantee byte-identical).
|
|
1822
|
+
state.pausedCapacity = true;
|
|
1823
|
+
state.resumeController.reportCapacity({
|
|
1824
|
+
accountKey: state.resumeAccountKey ?? state.session.runner_id,
|
|
1825
|
+
resetAtMs: resumeMs,
|
|
1826
|
+
detail,
|
|
1841
1827
|
});
|
|
1842
|
-
state.pauseTimer = setTimeout(() => {
|
|
1843
|
-
void resumeFromCapacity(state);
|
|
1844
|
-
}, delay);
|
|
1845
|
-
state.pauseTimer.unref?.();
|
|
1846
1828
|
}
|
|
1847
1829
|
/**
|
|
1848
|
-
*
|
|
1830
|
+
* AS-3 (RESUME-WIRE): the ResumeController side-effect seams, backed by the REAL
|
|
1831
|
+
* runner path (log_activity RPCs + pollOnce/pump). Exported so the account-switch
|
|
1832
|
+
* rig / real-path tests drive the controller through the SAME seams the live
|
|
1833
|
+
* runner uses, not hand-written fakes.
|
|
1834
|
+
*
|
|
1835
|
+
* - setServerPause → emit the canonical `runner.capacity_paused` activity
|
|
1836
|
+
* event (the verb api/_lib/fleet-pause.ts actually reads,
|
|
1837
|
+
* fixing the drift) + the operator bus message; mark the
|
|
1838
|
+
* account probe paused so it re-probes at the pause cadence.
|
|
1839
|
+
* - stopClaims → set pausedCapacity so pump()/pumpReviews() stop claiming.
|
|
1840
|
+
* - clearServerPause → emit the canonical `runner.capacity_resume` (clears the
|
|
1841
|
+
* derived fleet_pause) + the FH-1a task-scoped
|
|
1842
|
+
* `task.capacity_resumed`; unpause the probe.
|
|
1843
|
+
* - restartClaims → arm the single-probe clamp and re-drive pollOnce + pump
|
|
1844
|
+
* (the clamp bounds the first claim to one task; a
|
|
1845
|
+
* non-capacity completion releases the held reviews).
|
|
1849
1846
|
*
|
|
1850
|
-
*
|
|
1851
|
-
*
|
|
1852
|
-
* full task concurrency and reviews. If the probe itself 429s, the
|
|
1853
|
-
* completion handler re-enters the pause; if it succeeds, the probe clamp
|
|
1854
|
-
* clears and normal claiming resumes.
|
|
1847
|
+
* In-flight sessions are NEVER touched — the seams only gate CLAIMING, so a task
|
|
1848
|
+
* already running finishes normally across a pause / resume / account swap.
|
|
1855
1849
|
*/
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1850
|
+
export function buildResumeControllerDeps(state, factory) {
|
|
1851
|
+
return {
|
|
1852
|
+
setServerPause: async ({ account, resetAtMs }) => {
|
|
1853
|
+
state.pausedUntil = resetAtMs;
|
|
1854
|
+
const resumeAtIso = resetAtMs !== null ? new Date(resetAtMs).toISOString() : null;
|
|
1855
|
+
const detail = state.capacityPauseDetail;
|
|
1856
|
+
const triggerTaskId = state.capacityTriggerTaskId;
|
|
1857
|
+
state.accountProbe?.setPaused(true);
|
|
1858
|
+
process.stderr.write(chalk.yellow(`[acc-runner] PAUSED (capacity) on ${account}: ${detail}. Not claiming ` +
|
|
1859
|
+
`tasks or reviews; resuming at ${resumeAtIso ?? "reset / account change"}.\n`));
|
|
1860
|
+
try {
|
|
1861
|
+
await state.supabase.rpc("log_activity", {
|
|
1862
|
+
p_verb: RUNNER_CAPACITY_PAUSED_VERB,
|
|
1863
|
+
p_target_id: state.session.runner_id,
|
|
1864
|
+
p_payload: {
|
|
1865
|
+
runner_id: state.session.runner_id,
|
|
1866
|
+
resume_at: resumeAtIso,
|
|
1867
|
+
reason: detail,
|
|
1868
|
+
account,
|
|
1869
|
+
version: PACKAGE_VERSION,
|
|
1870
|
+
},
|
|
1871
|
+
p_target_type: "runner",
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
catch { /* best-effort */ }
|
|
1875
|
+
// Runner-level bus message so operators see the paused status + reason on
|
|
1876
|
+
// /messages. Anchored to the triggering task so the RPC can resolve the
|
|
1877
|
+
// org scope (post_agent_message looks up org_id from acc.tasks for a
|
|
1878
|
+
// non-null id); skipped for a review/probe pause with no task anchor.
|
|
1879
|
+
if (triggerTaskId) {
|
|
1880
|
+
await postRunnerStateMessage(state.supabase, {
|
|
1881
|
+
task_id: triggerTaskId,
|
|
1882
|
+
sender_id: state.session.runner_id,
|
|
1883
|
+
state: "blocked",
|
|
1884
|
+
protocol: "info",
|
|
1885
|
+
payload: {
|
|
1886
|
+
capacity_paused: true,
|
|
1887
|
+
runner_id: state.session.runner_id,
|
|
1888
|
+
resume_at: resumeAtIso,
|
|
1889
|
+
detail,
|
|
1890
|
+
},
|
|
1891
|
+
kind: "blocked",
|
|
1892
|
+
}).catch((err) => {
|
|
1893
|
+
process.stderr.write(`[acc-runner] capacity pause bus post failed: ${err.message}\n`);
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
},
|
|
1897
|
+
stopClaims: () => {
|
|
1898
|
+
state.pausedCapacity = true;
|
|
1899
|
+
},
|
|
1900
|
+
clearServerPause: async () => {
|
|
1901
|
+
try {
|
|
1902
|
+
await state.supabase.rpc("log_activity", {
|
|
1903
|
+
p_verb: RUNNER_CAPACITY_RESUME_VERB,
|
|
1904
|
+
p_target_id: state.session.runner_id,
|
|
1905
|
+
p_payload: { runner_id: state.session.runner_id, version: PACKAGE_VERSION },
|
|
1906
|
+
p_target_type: "runner",
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
catch { /* best-effort */ }
|
|
1910
|
+
// FH-1a: task-scoped resume audit for the task that drove the pause, then
|
|
1911
|
+
// clear it so a later review-triggered pause doesn't re-emit a stale id.
|
|
1912
|
+
const resumedTaskId = state.capacityTriggerTaskId;
|
|
1913
|
+
state.capacityTriggerTaskId = null;
|
|
1914
|
+
if (resumedTaskId) {
|
|
1915
|
+
try {
|
|
1916
|
+
await state.supabase.rpc("log_activity", {
|
|
1917
|
+
p_verb: "task.capacity_resumed",
|
|
1918
|
+
p_target_id: resumedTaskId,
|
|
1919
|
+
p_payload: { task_id: resumedTaskId, probe: true, version: PACKAGE_VERSION },
|
|
1920
|
+
p_target_type: "task",
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
catch { /* best-effort */ }
|
|
1924
|
+
}
|
|
1925
|
+
state.accountProbe?.setPaused(false);
|
|
1926
|
+
},
|
|
1927
|
+
restartClaims: async () => {
|
|
1928
|
+
if (state.stopped)
|
|
1929
|
+
return;
|
|
1930
|
+
state.pausedCapacity = false;
|
|
1931
|
+
state.pausedUntil = null;
|
|
1932
|
+
// RVU-2 (FU-RVU2R): a full pause supersedes the soft claim-pause; clear it
|
|
1933
|
+
// so the probe window (task limit pinned to 1) isn't zeroed out.
|
|
1934
|
+
state.capacityClaimPaused = false;
|
|
1935
|
+
// Single-probe clamp — claim exactly ONE task to confirm the window
|
|
1936
|
+
// reopened before unleashing full concurrency + reviews.
|
|
1937
|
+
state.capacityProbe = true;
|
|
1938
|
+
console.log(chalk.gray(`[acc-runner] capacity window reopened — resuming with a single probe task.`));
|
|
1939
|
+
// Poll first (a released task may have been re-queued elsewhere), then
|
|
1940
|
+
// pump — the probe clamp bounds it to one task.
|
|
1941
|
+
await pollOnce(state, factory);
|
|
1942
|
+
void pump(state, factory);
|
|
1943
|
+
},
|
|
1944
|
+
onLog: (line) => {
|
|
1945
|
+
process.stderr.write(`${line}\n`);
|
|
1946
|
+
},
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* AS-3 (RESUME-WIRE): construct + start the ResumeController and the AS-1/AS-2
|
|
1951
|
+
* account-probe cadence, and stitch them together — each account-probe broadcast
|
|
1952
|
+
* feeds `observeAccount()` so a keychain login swap converges within one probe
|
|
1953
|
+
* cycle instead of waiting on the stale account's reset. Both use an UNREF'd
|
|
1954
|
+
* timer so the poll / probe cadence never holds the process open by itself.
|
|
1955
|
+
*/
|
|
1956
|
+
async function wireCapacityResume(state, factory, options) {
|
|
1957
|
+
const unrefTimer = {
|
|
1958
|
+
setTimer: (fn, ms) => {
|
|
1959
|
+
const h = setTimeout(fn, ms);
|
|
1960
|
+
h.unref?.();
|
|
1961
|
+
return h;
|
|
1962
|
+
},
|
|
1963
|
+
clearTimer: (h) => clearTimeout(h),
|
|
1964
|
+
};
|
|
1965
|
+
// The account-identity probe: injected in tests; the resolved chat engine's
|
|
1966
|
+
// accountIdentity() otherwise (an honest per-host synthetic account when no
|
|
1967
|
+
// real login introspects). Never throws — a failed probe degrades to unknown
|
|
1968
|
+
// here, and to a logged no-op on the scheduler's later cadence probes.
|
|
1969
|
+
const chatEngine = getChatEngine();
|
|
1970
|
+
const probe = options.accountProbe ??
|
|
1971
|
+
(async () => {
|
|
1972
|
+
if (chatEngine.accountIdentity)
|
|
1973
|
+
return chatEngine.accountIdentity();
|
|
1974
|
+
return unknownAccountIdentity({
|
|
1975
|
+
engineId: chatEngine.id,
|
|
1976
|
+
channel: chatEngine.authMode,
|
|
1977
|
+
detail: "no account-identity probe on the resolved chat engine",
|
|
1893
1978
|
});
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
//
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1979
|
+
});
|
|
1980
|
+
// Seed the controller's starting account BEFORE it starts, so a cap that
|
|
1981
|
+
// lands before the scheduler's first cadence probe still folds onto — and
|
|
1982
|
+
// pauses — the right account (reportCapacity only pauses the observed account).
|
|
1983
|
+
const initial = await probe().catch(() => null);
|
|
1984
|
+
state.resumeAccountKey = initial?.synthetic_account ?? null;
|
|
1985
|
+
state.resumeController = new ResumeController({
|
|
1986
|
+
timer: unrefTimer,
|
|
1987
|
+
pollIntervalMs: options.capacityPollMs ?? 60_000,
|
|
1988
|
+
...(options.capacityAdoptionDebounceMs !== undefined
|
|
1989
|
+
? { adoptionDebounceMs: options.capacityAdoptionDebounceMs }
|
|
1990
|
+
: {}),
|
|
1991
|
+
// A login swap resolves via the account-change branch (no request burned);
|
|
1992
|
+
// reset-elapsed drives the same-account resume. No probe ping wired.
|
|
1993
|
+
...buildResumeControllerDeps(state, factory),
|
|
1994
|
+
});
|
|
1995
|
+
state.resumeController.start(state.resumeAccountKey);
|
|
1996
|
+
state.accountProbe = new AccountProbeScheduler({
|
|
1997
|
+
probe,
|
|
1998
|
+
timer: unrefTimer,
|
|
1999
|
+
pauseTickMs: options.accountProbeTickMs ?? 60_000,
|
|
2000
|
+
...(options.accountProbeHeartbeatMs !== undefined
|
|
2001
|
+
? { heartbeatMs: options.accountProbeHeartbeatMs }
|
|
2002
|
+
: {}),
|
|
2003
|
+
emit: (broadcast) => {
|
|
2004
|
+
state.resumeAccountKey = broadcast.identity.synthetic_account;
|
|
2005
|
+
// Fast path for a login swap: an immediate re-evaluation so a swap
|
|
2006
|
+
// converges within one probe cycle rather than the stale account's reset.
|
|
2007
|
+
state.resumeController.observeAccount(broadcast.identity.synthetic_account);
|
|
2008
|
+
},
|
|
2009
|
+
onLog: (line) => {
|
|
2010
|
+
process.stderr.write(`${line}\n`);
|
|
2011
|
+
},
|
|
2012
|
+
});
|
|
2013
|
+
// Start the probe cadence (fires the startup probe immediately, then the
|
|
2014
|
+
// pause-tick / heartbeat cadence per its four-trigger schedule).
|
|
2015
|
+
state.accountProbe.start();
|
|
1902
2016
|
}
|
|
1903
2017
|
/**
|
|
1904
2018
|
* v0.10 T-49-2: concurrent task pump.
|