@peakflo/agent-harness 0.2.11-rc.2 → 0.2.11-rc.4
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/adapters/pi-adapter.js +8 -1
- package/dist/capabilities.d.ts +4 -1
- package/dist/capabilities.js +8 -1
- package/dist/github-proxy.d.ts +20 -1
- package/dist/github-proxy.js +22 -0
- package/dist/github-workspace.d.ts +3 -0
- package/dist/github-workspace.js +7 -2
- package/dist/harness.d.ts +13 -0
- package/dist/harness.js +53 -3
- package/dist/health.d.ts +19 -1
- package/dist/health.js +31 -1
- package/dist/runtime/browser-takeover.d.ts +32 -0
- package/dist/runtime/browser-takeover.js +59 -0
- package/dist/runtime/knowledge-tools.d.ts +10 -0
- package/dist/runtime/knowledge-tools.js +13 -7
- package/dist/runtime/loopback-peer.d.ts +97 -0
- package/dist/runtime/loopback-peer.js +245 -0
- package/dist/runtime/mcp-auth-proxy.d.ts +101 -0
- package/dist/runtime/mcp-auth-proxy.js +482 -14
- package/dist/runtime/session-runner.d.ts +32 -0
- package/dist/runtime/session-runner.js +79 -2
- package/package.json +1 -1
|
@@ -634,6 +634,18 @@ class SessionRunner {
|
|
|
634
634
|
*/
|
|
635
635
|
approvalWaitMs = 0;
|
|
636
636
|
approvalWaitStartedAt = null;
|
|
637
|
+
/**
|
|
638
|
+
* Total wall-clock this turn has spent with its browser call HELD because a
|
|
639
|
+
* person controls the chat browser (takeover). Excluded from the idle guard
|
|
640
|
+
* and added back onto the hard ceiling exactly like approval time, but it
|
|
641
|
+
* never opens an approval and the approval timeout never applies: a takeover
|
|
642
|
+
* lasts as long as the person needs (owner decision), and the turn's abort
|
|
643
|
+
* and hard ceiling (minus the pause) still end it.
|
|
644
|
+
*/
|
|
645
|
+
browserPauseMs = 0;
|
|
646
|
+
browserPauseStartedAt = null;
|
|
647
|
+
/** When the last browser pause ended; the idle clock restarts from here. */
|
|
648
|
+
browserPauseEndedAt = null;
|
|
637
649
|
constructor(deps) {
|
|
638
650
|
this.deps = deps;
|
|
639
651
|
this.logger = deps.logger ?? console;
|
|
@@ -650,6 +662,44 @@ class SessionRunner {
|
|
|
650
662
|
isAwaitingApproval() {
|
|
651
663
|
return this.openApproval !== null || this.openQuestionPartId !== null;
|
|
652
664
|
}
|
|
665
|
+
/** True while a browser call is held because a person controls the browser. */
|
|
666
|
+
isBrowserPaused() {
|
|
667
|
+
return this.browserPauseStartedAt !== null;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Start or stop the browser-takeover pause clock. Called by the harness from
|
|
671
|
+
* the loopback MCP proxy's hold hook. Returns false when this runner has no
|
|
672
|
+
* live turn for `sessionId` — the caller should then cancel the hold rather
|
|
673
|
+
* than keep an agent call parked for a turn that is gone.
|
|
674
|
+
*/
|
|
675
|
+
setBrowserPaused(sessionId, paused) {
|
|
676
|
+
if (paused) {
|
|
677
|
+
if (!this.turnContext || this.turnContext.sessionId !== sessionId) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (this.browserPauseStartedAt === null) {
|
|
681
|
+
this.browserPauseStartedAt = Date.now();
|
|
682
|
+
this.deps.onBrowserPauseChange?.(sessionId, true);
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
this.closeBrowserPause(sessionId);
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
closeBrowserPause(sessionId) {
|
|
690
|
+
if (this.browserPauseStartedAt === null)
|
|
691
|
+
return;
|
|
692
|
+
const now = Date.now();
|
|
693
|
+
this.browserPauseMs += now - this.browserPauseStartedAt;
|
|
694
|
+
this.browserPauseStartedAt = null;
|
|
695
|
+
this.browserPauseEndedAt = now;
|
|
696
|
+
this.deps.onBrowserPauseChange?.(sessionId, false);
|
|
697
|
+
}
|
|
698
|
+
/** Wall-clock spent in the browser pause we are in RIGHT NOW, if any. */
|
|
699
|
+
liveBrowserPauseMs() {
|
|
700
|
+
const startedAt = this.browserPauseStartedAt;
|
|
701
|
+
return startedAt === null ? 0 : Date.now() - startedAt;
|
|
702
|
+
}
|
|
653
703
|
/**
|
|
654
704
|
* Run the leased turn to completion, or until `signal` aborts it.
|
|
655
705
|
*
|
|
@@ -1305,6 +1355,9 @@ class SessionRunner {
|
|
|
1305
1355
|
this.turnContext = null;
|
|
1306
1356
|
this.deferredSteers.length = 0;
|
|
1307
1357
|
this.closeApproval(session.sessionId);
|
|
1358
|
+
// A held browser call cannot outlive its turn; the harness cancels the
|
|
1359
|
+
// proxy hold on its side too.
|
|
1360
|
+
this.closeBrowserPause(session.sessionId);
|
|
1308
1361
|
this.deps.knowledge?.host.bindTurn(session.sessionId, null);
|
|
1309
1362
|
this.closeQuestion(session.sessionId);
|
|
1310
1363
|
this.emittedApprovals.clear();
|
|
@@ -1577,6 +1630,7 @@ class SessionRunner {
|
|
|
1577
1630
|
const idleLimitMs = session.limits.maxTurnIdleMs ?? DEFAULT_MAX_TURN_IDLE_MS;
|
|
1578
1631
|
let lastProgressAt = Date.now();
|
|
1579
1632
|
let approvalWaitAtLastProgress = this.approvalWaitMs;
|
|
1633
|
+
let browserPauseAtLastProgress = this.browserPauseMs;
|
|
1580
1634
|
try {
|
|
1581
1635
|
while (!signal.aborted) {
|
|
1582
1636
|
const now = Date.now();
|
|
@@ -1640,7 +1694,11 @@ class SessionRunner {
|
|
|
1640
1694
|
// turn stopped to ask a person something is blocked on them, not
|
|
1641
1695
|
// running away, and ten minutes is an entirely ordinary time for a
|
|
1642
1696
|
// person to be away from their desk.
|
|
1643
|
-
|
|
1697
|
+
// A browser takeover is the same kind of intentional pause.
|
|
1698
|
+
const pausedMs = this.approvalWaitMs +
|
|
1699
|
+
this.liveApprovalWaitMs() +
|
|
1700
|
+
this.browserPauseMs +
|
|
1701
|
+
this.liveBrowserPauseMs();
|
|
1644
1702
|
if (hardDeadline !== null && now > hardDeadline + pausedMs) {
|
|
1645
1703
|
timedOut = true;
|
|
1646
1704
|
break;
|
|
@@ -1743,6 +1801,7 @@ class SessionRunner {
|
|
|
1743
1801
|
// here, not double-counting wait time already excluded once.
|
|
1744
1802
|
lastProgressAt = Date.now();
|
|
1745
1803
|
approvalWaitAtLastProgress = this.approvalWaitMs;
|
|
1804
|
+
browserPauseAtLastProgress = this.browserPauseMs;
|
|
1746
1805
|
continue;
|
|
1747
1806
|
}
|
|
1748
1807
|
const status = await adapter.getStatus(backendSessionId, config);
|
|
@@ -1780,9 +1839,27 @@ class SessionRunner {
|
|
|
1780
1839
|
const approvalWaitSinceProgress = this.approvalWaitMs -
|
|
1781
1840
|
approvalWaitAtLastProgress +
|
|
1782
1841
|
this.liveApprovalWaitMs();
|
|
1783
|
-
|
|
1842
|
+
// A handback restarts the idle clock: the agent gets a full idle
|
|
1843
|
+
// window after the person returns control, as after real progress.
|
|
1844
|
+
if (this.browserPauseEndedAt !== null &&
|
|
1845
|
+
this.browserPauseEndedAt > lastProgressAt) {
|
|
1846
|
+
lastProgressAt = this.browserPauseEndedAt;
|
|
1847
|
+
approvalWaitAtLastProgress = this.approvalWaitMs;
|
|
1848
|
+
browserPauseAtLastProgress = this.browserPauseMs;
|
|
1849
|
+
}
|
|
1850
|
+
// Browser takeover time since the last progress, by the same rule.
|
|
1851
|
+
// Skipped outright while held: the idle clock restarts when the
|
|
1852
|
+
// person hands back, never mid-hold.
|
|
1853
|
+
const browserPauseSinceProgress = this.browserPauseMs -
|
|
1854
|
+
browserPauseAtLastProgress +
|
|
1855
|
+
this.liveBrowserPauseMs();
|
|
1856
|
+
const idleElapsed = Date.now() -
|
|
1857
|
+
lastProgressAt -
|
|
1858
|
+
approvalWaitSinceProgress -
|
|
1859
|
+
browserPauseSinceProgress;
|
|
1784
1860
|
if (this.approvalWaitStartedAt === null &&
|
|
1785
1861
|
this.openQuestionPartId === null &&
|
|
1862
|
+
this.browserPauseStartedAt === null &&
|
|
1786
1863
|
idleElapsed > idleLimitMs) {
|
|
1787
1864
|
this.logger.warn(`[SessionRunner] session ${session.sessionId} had no progress for ` +
|
|
1788
1865
|
`${Math.round(idleElapsed / 1000)}s (limit ${Math.round(idleLimitMs / 1000)}s)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peakflo/agent-harness",
|
|
3
|
-
"version": "0.2.11-rc.
|
|
3
|
+
"version": "0.2.11-rc.4",
|
|
4
4
|
"description": "Headless Peakflo coding-agent harness. One codebase, three hosting modes: a Daytona cloud sandbox, a customer VM, and the 20x desktop.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|