@proagentstore/cli 0.4.101 → 0.4.103
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/browser-runner/coding/headless.js +22 -4
- package/dist/index.js +19 -5
- package/package.json +1 -1
|
@@ -424,8 +424,9 @@ export class HeadlessSession {
|
|
|
424
424
|
}
|
|
425
425
|
if (this.procAlive)
|
|
426
426
|
return;
|
|
427
|
-
// A new process has read none of what the old one was sent.
|
|
427
|
+
// A new process has read none of what the old one was sent, and owns none of its half-read line.
|
|
428
428
|
this.pendingInputs = [];
|
|
429
|
+
this.buf = "";
|
|
429
430
|
const args = this.adapter.buildLaunchArgs(this.cmdArgs, this.claudeSessionId);
|
|
430
431
|
const proc = spawn(this.cmdBin, args, {
|
|
431
432
|
cwd: this.config.workDir,
|
|
@@ -446,8 +447,17 @@ export class HeadlessSession {
|
|
|
446
447
|
});
|
|
447
448
|
// Swallow EPIPE when writing to a process that just exited (one-shot turn).
|
|
448
449
|
proc.stdin?.on("error", () => { });
|
|
449
|
-
|
|
450
|
+
// FENCED to this process (#1019). A stopped process can outlive its replacement — it may ignore the
|
|
451
|
+
// signal, or flush buffered output on its way out — and its late `result` would otherwise be read as
|
|
452
|
+
// the NEW process's turn end: crediting the new turn's input as consumed, filing its report and
|
|
453
|
+
// marking it idle while it is still working.
|
|
454
|
+
proc.stdout?.on("data", (d) => {
|
|
455
|
+
if (this.proc === proc)
|
|
456
|
+
this.onStdout(d.toString("utf8"));
|
|
457
|
+
});
|
|
450
458
|
proc.stderr?.on("data", (d) => {
|
|
459
|
+
if (this.proc !== proc)
|
|
460
|
+
return;
|
|
451
461
|
this.lastOutputAt = Date.now();
|
|
452
462
|
this.sawOutputSinceInput = true;
|
|
453
463
|
const text = d.toString("utf8").trim();
|
|
@@ -591,6 +601,7 @@ export class HeadlessSession {
|
|
|
591
601
|
}
|
|
592
602
|
}
|
|
593
603
|
this.proc = proc;
|
|
604
|
+
this.buf = ""; // the replaced turn's half-written line is not this turn's (#1019)
|
|
594
605
|
// Without an 'error' listener a spawn failure (binary not on PATH) is an uncaught
|
|
595
606
|
// exception that takes the whole runner down, not just this session.
|
|
596
607
|
proc.on("error", (err) => {
|
|
@@ -604,8 +615,15 @@ export class HeadlessSession {
|
|
|
604
615
|
// real reason instead of retrying a binary that does not exist.
|
|
605
616
|
this.spawnFailed = true;
|
|
606
617
|
});
|
|
607
|
-
|
|
608
|
-
proc.
|
|
618
|
+
// Fenced like the persistent process (#1019): a turn this one replaced must not speak for it.
|
|
619
|
+
proc.stdout?.on("data", (d) => {
|
|
620
|
+
if (this.proc === proc)
|
|
621
|
+
this.onStdout(d.toString());
|
|
622
|
+
});
|
|
623
|
+
proc.stderr?.on("data", (d) => {
|
|
624
|
+
if (this.proc === proc)
|
|
625
|
+
this.onStdout(d.toString());
|
|
626
|
+
});
|
|
609
627
|
// The enforced ceiling (#391). `unref` so a pending timer can never keep the runner's
|
|
610
628
|
// event loop alive past its own shutdown.
|
|
611
629
|
const maxTurnMs = this.config.maxTurnMs ?? MAX_ONE_SHOT_TURN_MS;
|
package/dist/index.js
CHANGED
|
@@ -1766,10 +1766,12 @@ var AutomaticUpdateController = class {
|
|
|
1766
1766
|
if (action === "cancel") return this.cancel();
|
|
1767
1767
|
if (action === "start") this.schedule(true);
|
|
1768
1768
|
}
|
|
1769
|
-
/**
|
|
1769
|
+
/**
|
|
1770
|
+
* Testable immediate entry point; production reaches it through the scheduled timer. Authority is
|
|
1771
|
+
* NOT required to get here: a failed refresh clears it, and the retry is what re-establishes it.
|
|
1772
|
+
*/
|
|
1770
1773
|
async runNow() {
|
|
1771
|
-
|
|
1772
|
-
if (this.checking || this.waiting || !current.authoritative || !current.value.autoUpdate) return;
|
|
1774
|
+
if (this.checking || this.waiting || !this.retryable()) return;
|
|
1773
1775
|
this.checking = true;
|
|
1774
1776
|
try {
|
|
1775
1777
|
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
@@ -1787,12 +1789,22 @@ var AutomaticUpdateController = class {
|
|
|
1787
1789
|
async installManually(plan) {
|
|
1788
1790
|
return this.install(plan, false);
|
|
1789
1791
|
}
|
|
1792
|
+
/** May observe work, install or restart: only a freshly confirmed owner ON. */
|
|
1790
1793
|
enabled() {
|
|
1791
1794
|
const { value, authoritative } = this.deps.policy();
|
|
1792
1795
|
return authoritative && value.autoUpdate;
|
|
1793
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* May keep a timer to re-read the policy: anything but an explicit OFF (#1020). The relay clears
|
|
1799
|
+
* authority before every refresh, so a failed one left `enabled()` false and `schedule` refused —
|
|
1800
|
+
* no timer, until a heartbeat happened to restore authority. The retry only refreshes; every
|
|
1801
|
+
* install still passes `enabled()` and the relay's final admission.
|
|
1802
|
+
*/
|
|
1803
|
+
retryable() {
|
|
1804
|
+
return this.deps.policy().value.autoUpdate;
|
|
1805
|
+
}
|
|
1794
1806
|
schedule(initial = false) {
|
|
1795
|
-
if (!this.
|
|
1807
|
+
if (!this.retryable() || this.timer || this.waiting) return;
|
|
1796
1808
|
const delay = initial ? Math.round(5e3 + (this.deps.random ?? Math.random)() * 25e3) : nextAutoUpdateDelayMs(this.failures, this.deps.random);
|
|
1797
1809
|
this.timer = this.setTimer(() => {
|
|
1798
1810
|
this.timer = null;
|
|
@@ -1813,6 +1825,7 @@ var AutomaticUpdateController = class {
|
|
|
1813
1825
|
* until the next policy transition. `schedule` itself refuses once the policy is disabled.
|
|
1814
1826
|
*/
|
|
1815
1827
|
offline() {
|
|
1828
|
+
if (!this.retryable()) return;
|
|
1816
1829
|
this.failures++;
|
|
1817
1830
|
this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
1818
1831
|
this.schedule();
|
|
@@ -1826,7 +1839,7 @@ var AutomaticUpdateController = class {
|
|
|
1826
1839
|
const facts = await this.deps.facts();
|
|
1827
1840
|
this.checkAt = this.now();
|
|
1828
1841
|
if (facts.observations?.length) this.observations = [...this.observations, ...facts.observations].slice(-MAX_OBSERVATIONS);
|
|
1829
|
-
if (!this.enabled()) return;
|
|
1842
|
+
if (!this.enabled()) return this.offline();
|
|
1830
1843
|
const plan = this.deps.plan(facts);
|
|
1831
1844
|
this.decisionAt = this.now();
|
|
1832
1845
|
this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
|
|
@@ -1858,6 +1871,7 @@ var AutomaticUpdateController = class {
|
|
|
1858
1871
|
defer(latest) {
|
|
1859
1872
|
if (this.waiting) return;
|
|
1860
1873
|
this.waiting = true;
|
|
1874
|
+
if (this.timer) this.clearTimer(this.timer);
|
|
1861
1875
|
this.timer = this.setTimer(() => {
|
|
1862
1876
|
this.timer = null;
|
|
1863
1877
|
this.nextRetryDueAt = null;
|