@phnx-labs/agents-cli 1.20.35 → 1.20.36
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/CHANGELOG.md +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +156 -75
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.js +20 -8
- package/dist/commands/sessions.d.ts +71 -1
- package/dist/commands/sessions.js +365 -37
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +112 -8
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +57 -0
- package/dist/lib/daemon.js +192 -16
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +14 -0
- package/dist/lib/hosts/tasks.js +15 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugins.js +16 -6
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +54 -0
- package/dist/lib/session/active.js +190 -19
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +111 -28
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.js +19 -7
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +16 -2
- package/dist/lib/session/sync/config.d.ts +1 -15
- package/dist/lib/session/sync/config.js +4 -20
- package/dist/lib/session/types.d.ts +17 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -11,7 +11,7 @@ import { connectSSH, shellQuote } from './drivers/ssh.js';
|
|
|
11
11
|
import { clearProfileRuntime } from './runtime-state.js';
|
|
12
12
|
import { resolveDomainSkill } from './domain-skills.js';
|
|
13
13
|
import { generateTaskId, generateShortId, generateTaskName, } from './types.js';
|
|
14
|
-
import { getRefs, resolveRefToCoords } from './refs.js';
|
|
14
|
+
import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
|
|
15
15
|
import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focusNode } from './input.js';
|
|
16
16
|
import { typeEditorText } from './editor.js';
|
|
17
17
|
import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
|
|
@@ -672,6 +672,31 @@ export class BrowserService {
|
|
|
672
672
|
{ width: 0, height: 0 };
|
|
673
673
|
return { path: finalPath, bytes: buffer.length, width: dims.width, height: dims.height };
|
|
674
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* Export the current tab as a PDF via CDP `Page.printToPDF`. Reuses the
|
|
677
|
+
* screenshot session + tab resolution so `--tab`, path sandboxing, and the
|
|
678
|
+
* auto-path (`sessions/<task>/<ts>.pdf`) all behave identically to
|
|
679
|
+
* `screenshot`. `printBackground: true` matches Chrome's default print flow
|
|
680
|
+
* — without it, dark-mode pages render on a blank sheet.
|
|
681
|
+
*/
|
|
682
|
+
async printToPdf(taskId, tabHint, outputPath) {
|
|
683
|
+
const { conn, task } = await this.findTask(taskId);
|
|
684
|
+
const shortId = tabHint ? await this.resolveTabHint(conn, task, tabHint) : this.resolveCurrentTab(task);
|
|
685
|
+
const cdpTargetId = this.getCdpTargetId(task, shortId);
|
|
686
|
+
const target = await this.getTarget(conn, cdpTargetId);
|
|
687
|
+
if (!target) {
|
|
688
|
+
throw new Error(`Tab ${shortId} not found`);
|
|
689
|
+
}
|
|
690
|
+
const sessionId = await this.getSessionId(conn, target.targetId);
|
|
691
|
+
const { data } = (await conn.cdp.send('Page.printToPDF', { printBackground: true, preferCSSPageSize: true }, sessionId));
|
|
692
|
+
const buffer = Buffer.from(data, 'base64');
|
|
693
|
+
const sessionsDir = path.join(getBrowserRuntimeDir(), 'sessions', task.name);
|
|
694
|
+
const automaticPath = path.join(sessionsDir, `${Date.now()}.pdf`);
|
|
695
|
+
const finalPath = resolveScreenshotOutputPath(outputPath, automaticPath);
|
|
696
|
+
await fs.promises.mkdir(path.dirname(finalPath), { recursive: true });
|
|
697
|
+
await fs.promises.writeFile(finalPath, buffer);
|
|
698
|
+
return { path: finalPath, bytes: buffer.length };
|
|
699
|
+
}
|
|
675
700
|
// ─── Recording ──────────────────────────────────────────────────────────────
|
|
676
701
|
//
|
|
677
702
|
// CDP `Page.startScreencast` emits a JPEG frame per `everyNthFrame`. We pipe
|
|
@@ -722,7 +747,7 @@ export class BrowserService {
|
|
|
722
747
|
'-vsync', 'vfr',
|
|
723
748
|
'-y',
|
|
724
749
|
outputPath,
|
|
725
|
-
], { stdio: ['pipe', 'ignore', 'pipe'] });
|
|
750
|
+
], { stdio: ['pipe', 'ignore', 'pipe'], windowsHide: true });
|
|
726
751
|
// Wait for the spawn to confirm (or fail) before we wire CDP frames into a
|
|
727
752
|
// dead pipe. ENOENT from a missing ffmpeg surfaces here as a real error
|
|
728
753
|
// instead of a silently empty .webm.
|
|
@@ -880,7 +905,6 @@ export class BrowserService {
|
|
|
880
905
|
return { recording: false };
|
|
881
906
|
return { recording: true, path: rec.outputPath, elapsedMs: Date.now() - rec.startedAt };
|
|
882
907
|
}
|
|
883
|
-
refsCache = new Map();
|
|
884
908
|
async refs(taskId, tabHint, opts = {}) {
|
|
885
909
|
const { conn, task } = await this.findTask(taskId);
|
|
886
910
|
const shortId = tabHint ? await this.resolveTabHint(conn, task, tabHint) : this.resolveCurrentTab(task);
|
|
@@ -889,7 +913,51 @@ export class BrowserService {
|
|
|
889
913
|
if (!target)
|
|
890
914
|
throw new Error(`Tab ${shortId} not found`);
|
|
891
915
|
const sessionId = await this.getSessionId(conn, target.targetId);
|
|
892
|
-
|
|
916
|
+
const result = await getRefs(conn.cdp, sessionId, opts);
|
|
917
|
+
// Snapshot the stable descriptors AND the opts they were numbered against
|
|
918
|
+
// so a later click/type can self-heal a drifted ref by rebuilding with the
|
|
919
|
+
// same filter. `refs()` is the sole owner of this cache — actions read it,
|
|
920
|
+
// never overwrite it. Persist to tasks.json so it survives a daemon
|
|
921
|
+
// restart.
|
|
922
|
+
this.cacheRefDescriptors(task, shortId, result.nodeMap, result.opts);
|
|
923
|
+
await this.saveTaskState(task.profile, conn.tasks);
|
|
924
|
+
return { refs: result.refs, nodeMap: result.nodeMap };
|
|
925
|
+
}
|
|
926
|
+
/** Record the last ref listing (descriptors + opts) for a tab into state. */
|
|
927
|
+
cacheRefDescriptors(task, shortId, nodeMap, opts) {
|
|
928
|
+
if (!task.refDescriptors)
|
|
929
|
+
task.refDescriptors = {};
|
|
930
|
+
task.refDescriptors[shortId] = { descriptors: describeRefs(nodeMap), opts };
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Re-resolve a caller-supplied ref against a freshly-built node map, healing
|
|
934
|
+
* it back to the right element when the integer ref has drifted since the
|
|
935
|
+
* cached `refs` listing. Shared by `click` and `type` so both self-heal
|
|
936
|
+
* identically. Returns the ref to act on plus, when a heal occurred, the
|
|
937
|
+
* {@link HealInfo} to surface. Throws when the cached element is gone.
|
|
938
|
+
*/
|
|
939
|
+
resolveHealedRef(snapshot, nodeMap, ref) {
|
|
940
|
+
const cached = snapshot?.descriptors.find((d) => d.ref === ref);
|
|
941
|
+
if (!cached)
|
|
942
|
+
return { targetRef: ref };
|
|
943
|
+
const fresh = nodeMap.get(ref);
|
|
944
|
+
const stillMatches = fresh !== undefined &&
|
|
945
|
+
fresh.role === cached.role &&
|
|
946
|
+
fresh.name === cached.name &&
|
|
947
|
+
fresh.backendNodeId !== undefined;
|
|
948
|
+
if (stillMatches)
|
|
949
|
+
return { targetRef: ref };
|
|
950
|
+
const newRef = healRef(cached, nodeMap);
|
|
951
|
+
if (newRef === null) {
|
|
952
|
+
throw new Error(`Ref ${ref} (${cached.role} "${cached.name}") could not be re-resolved: ` +
|
|
953
|
+
`no matching element on the current page. Re-run 'browser refs' to ` +
|
|
954
|
+
`refresh the ref numbers, or act by position with 'browser click --at X,Y'.`);
|
|
955
|
+
}
|
|
956
|
+
if (newRef === ref)
|
|
957
|
+
return { targetRef: ref };
|
|
958
|
+
console.error(`[browser] self-healed ref ${ref} -> ${newRef} (${cached.role} "${cached.name}") — ` +
|
|
959
|
+
`cached descriptor re-matched after the ref drifted`);
|
|
960
|
+
return { targetRef: newRef, healed: { from: ref, to: newRef, role: cached.role, name: cached.name } };
|
|
893
961
|
}
|
|
894
962
|
async click(taskId, ref, tabHint) {
|
|
895
963
|
const { conn, task } = await this.findTask(taskId);
|
|
@@ -899,8 +967,38 @@ export class BrowserService {
|
|
|
899
967
|
if (!target)
|
|
900
968
|
throw new Error(`Tab ${shortId} not found`);
|
|
901
969
|
const sessionId = await this.getSessionId(conn, target.targetId);
|
|
902
|
-
|
|
903
|
-
|
|
970
|
+
// Rebuild the node map with the SAME opts the cached listing was numbered
|
|
971
|
+
// against, so the caller's ref lands on the element they saw in `browser
|
|
972
|
+
// refs`. Rebuilding with a different filter (the old interactive:false)
|
|
973
|
+
// renumbers every ref and defeats self-heal on the second click. Default
|
|
974
|
+
// to the user-facing interactive numbering when no listing was cached yet.
|
|
975
|
+
const snapshot = task.refDescriptors?.[shortId];
|
|
976
|
+
const buildOpts = snapshot?.opts ?? { interactive: true, limit: 500 };
|
|
977
|
+
const { nodeMap } = await getRefs(conn.cdp, sessionId, buildOpts);
|
|
978
|
+
// Self-heal: the integer ref is positional and drifts on re-render. If the
|
|
979
|
+
// fresh node at this position no longer matches the cached descriptor,
|
|
980
|
+
// re-resolve (by attrs/proximity-tie-broken role+name) BEFORE clicking the
|
|
981
|
+
// wrong element. The cache is owned by refs() and NOT rewritten here — the
|
|
982
|
+
// caller's ref numbers stay anchored to the listing they came from.
|
|
983
|
+
const { targetRef, healed } = this.resolveHealedRef(snapshot, nodeMap, ref);
|
|
984
|
+
const { x, y } = await resolveRefToCoords(conn.cdp, sessionId, nodeMap, targetRef);
|
|
985
|
+
await clickAtCoords(conn.cdp, sessionId, x, y);
|
|
986
|
+
return healed ? { healed } : {};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Click raw viewport coordinates, bypassing ref resolution entirely. Backs
|
|
990
|
+
* `browser click --at X,Y` — the escape hatch when the accessibility tree
|
|
991
|
+
* exposes no usable ref (canvas apps, custom-drawn UI) and the caller has
|
|
992
|
+
* located the target from a screenshot.
|
|
993
|
+
*/
|
|
994
|
+
async clickAt(taskId, x, y, tabHint) {
|
|
995
|
+
const { conn, task } = await this.findTask(taskId);
|
|
996
|
+
const shortId = tabHint ? await this.resolveTabHint(conn, task, tabHint) : this.resolveCurrentTab(task);
|
|
997
|
+
const cdpTargetId = this.getCdpTargetId(task, shortId);
|
|
998
|
+
const target = await this.getTarget(conn, cdpTargetId);
|
|
999
|
+
if (!target)
|
|
1000
|
+
throw new Error(`Tab ${shortId} not found`);
|
|
1001
|
+
const sessionId = await this.getSessionId(conn, target.targetId);
|
|
904
1002
|
await clickAtCoords(conn.cdp, sessionId, x, y);
|
|
905
1003
|
}
|
|
906
1004
|
async type(taskId, ref, text, tabHint, clear) {
|
|
@@ -911,8 +1009,14 @@ export class BrowserService {
|
|
|
911
1009
|
if (!target)
|
|
912
1010
|
throw new Error(`Tab ${shortId} not found`);
|
|
913
1011
|
const sessionId = await this.getSessionId(conn, target.targetId);
|
|
914
|
-
|
|
915
|
-
|
|
1012
|
+
// Same self-healing story as click(): rebuild against the cached listing's
|
|
1013
|
+
// opts so refs line up with what the user saw, then heal a drifted ref
|
|
1014
|
+
// before typing into the wrong field.
|
|
1015
|
+
const snapshot = task.refDescriptors?.[shortId];
|
|
1016
|
+
const buildOpts = snapshot?.opts ?? { interactive: true, limit: 500 };
|
|
1017
|
+
const { nodeMap } = await getRefs(conn.cdp, sessionId, buildOpts);
|
|
1018
|
+
const { targetRef } = this.resolveHealedRef(snapshot, nodeMap, ref);
|
|
1019
|
+
const node = nodeMap.get(targetRef);
|
|
916
1020
|
if (!node)
|
|
917
1021
|
throw new Error(`Ref ${ref} not found`);
|
|
918
1022
|
if (node.editor) {
|
|
@@ -78,6 +78,15 @@ export interface Task {
|
|
|
78
78
|
currentTabId?: string;
|
|
79
79
|
createdAt: number;
|
|
80
80
|
pid: number;
|
|
81
|
+
/**
|
|
82
|
+
* Per-tab snapshot of the last ref listing captured for that tab
|
|
83
|
+
* (shortId -> {descriptors, opts}). Persisted to tasks.json so a later
|
|
84
|
+
* `click`/`type <ref>` can self-heal a drifted ref — the cached `opts` let
|
|
85
|
+
* the action rebuild its node map with the SAME accessibility filter the
|
|
86
|
+
* listing was numbered against. Owned by `refs()`; actions never overwrite
|
|
87
|
+
* it. See RefSnapshot / RefDescriptor.
|
|
88
|
+
*/
|
|
89
|
+
refDescriptors?: Record<string, import('./refs.js').RefSnapshot>;
|
|
81
90
|
}
|
|
82
91
|
export interface TabInfo {
|
|
83
92
|
id: string;
|
|
@@ -118,7 +127,7 @@ export interface HistoricalTask {
|
|
|
118
127
|
domains: string[];
|
|
119
128
|
tabCount: number;
|
|
120
129
|
}
|
|
121
|
-
export type IPCAction = 'start' | 'record-start' | 'record-stop' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'scroll' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download' | 'upload' | 'getAppLogs' | 'version';
|
|
130
|
+
export type IPCAction = 'start' | 'record-start' | 'record-stop' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'pdf' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'scroll' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download' | 'upload' | 'getAppLogs' | 'version';
|
|
122
131
|
export interface IPCRequest {
|
|
123
132
|
action: IPCAction;
|
|
124
133
|
task?: string;
|
|
@@ -129,6 +138,8 @@ export interface IPCRequest {
|
|
|
129
138
|
expr?: string;
|
|
130
139
|
path?: string;
|
|
131
140
|
ref?: number;
|
|
141
|
+
atX?: number;
|
|
142
|
+
atY?: number;
|
|
132
143
|
text?: string;
|
|
133
144
|
key?: string;
|
|
134
145
|
scrollX?: number;
|
|
@@ -194,6 +205,8 @@ export interface IPCResponse {
|
|
|
194
205
|
height?: number;
|
|
195
206
|
refs?: string;
|
|
196
207
|
nodes?: RefNodeJson[];
|
|
208
|
+
/** Human-readable note surfaced to the CLI (e.g. a self-heal notice on click). */
|
|
209
|
+
message?: string;
|
|
197
210
|
port?: number;
|
|
198
211
|
pid?: number;
|
|
199
212
|
fps?: number;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live budget kill-switch for `agents cloud` dispatch (issue #399).
|
|
3
|
+
*
|
|
4
|
+
* Follow-up to #346, which shipped a pre-flight budget gate for cloud runs but
|
|
5
|
+
* no live mid-run enforcement — a runaway cloud task would keep spending until
|
|
6
|
+
* the user hit Ctrl-C. This wires the same `makeLiveSpendWatcher` the local
|
|
7
|
+
* headless run uses (src/lib/exec.ts) into the cloud SSE stream: each `usage`
|
|
8
|
+
* event from the provider feeds the watcher, and on breach we call
|
|
9
|
+
* `provider.cancel(taskId)` to terminate server-side.
|
|
10
|
+
*
|
|
11
|
+
* Wrapper shape mirrors `renderStream`'s input — it takes an
|
|
12
|
+
* `AsyncIterable<CloudEvent>` and yields the same events downstream. Dormant
|
|
13
|
+
* (returns the source stream unchanged) when no caps are configured.
|
|
14
|
+
*/
|
|
15
|
+
import type { CloudProvider, CloudEvent } from '../cloud/types.js';
|
|
16
|
+
import { type BreachInfo } from './enforce.js';
|
|
17
|
+
/** Result surface exposed to the caller so it can act on a mid-stream breach. */
|
|
18
|
+
export interface CloudBudgetGate {
|
|
19
|
+
/** True once a cap crossed and cancel() was invoked. */
|
|
20
|
+
breached(): boolean;
|
|
21
|
+
breach(): BreachInfo | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a cloud event stream with a live budget watcher. On a `usage` event we
|
|
25
|
+
* feed the shared watcher; on the first breach we call `provider.cancel(taskId)`
|
|
26
|
+
* and forward a synthetic `status:'cancelled'` + `error` frame so the renderer
|
|
27
|
+
* shows why the task stopped. Returns null when no caps are configured; callers
|
|
28
|
+
* fall through to the raw stream in that case.
|
|
29
|
+
*/
|
|
30
|
+
export declare function wrapStreamWithBudgetGate(args: {
|
|
31
|
+
provider: CloudProvider;
|
|
32
|
+
taskId: string;
|
|
33
|
+
/** Project attribution key — repo slug for Rush, or cwd. */
|
|
34
|
+
project: string;
|
|
35
|
+
/** Agent the dispatch runs under (for per_agent cap accounting). */
|
|
36
|
+
agent: string;
|
|
37
|
+
/** cwd used to resolve the effective budget config. */
|
|
38
|
+
cwd?: string;
|
|
39
|
+
}): {
|
|
40
|
+
wrap: (source: AsyncIterable<CloudEvent>) => AsyncIterable<CloudEvent>;
|
|
41
|
+
gate: CloudBudgetGate;
|
|
42
|
+
} | null;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { capsFromConfig, makeLiveSpendWatcher, } from './enforce.js';
|
|
2
|
+
import { resolveBudgetConfig, hasAnyCap } from './config.js';
|
|
3
|
+
import { loadLedger, localDay, spendForDay, spendForProject } from './ledger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a cloud event stream with a live budget watcher. On a `usage` event we
|
|
6
|
+
* feed the shared watcher; on the first breach we call `provider.cancel(taskId)`
|
|
7
|
+
* and forward a synthetic `status:'cancelled'` + `error` frame so the renderer
|
|
8
|
+
* shows why the task stopped. Returns null when no caps are configured; callers
|
|
9
|
+
* fall through to the raw stream in that case.
|
|
10
|
+
*/
|
|
11
|
+
export function wrapStreamWithBudgetGate(args) {
|
|
12
|
+
const cfg = resolveBudgetConfig(args.cwd);
|
|
13
|
+
if (!hasAnyCap(cfg))
|
|
14
|
+
return null;
|
|
15
|
+
const today = localDay();
|
|
16
|
+
const entries = loadLedger();
|
|
17
|
+
const caps = capsFromConfig(cfg, {
|
|
18
|
+
daySpend: spendForDay(today, entries),
|
|
19
|
+
projectSpend: spendForProject(args.project, entries),
|
|
20
|
+
});
|
|
21
|
+
let firstBreach = null;
|
|
22
|
+
const watcher = makeLiveSpendWatcher({
|
|
23
|
+
caps,
|
|
24
|
+
onBreach: (b) => {
|
|
25
|
+
firstBreach = b;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
async function* wrap(source) {
|
|
29
|
+
try {
|
|
30
|
+
for await (const event of source) {
|
|
31
|
+
if (event.type === 'usage') {
|
|
32
|
+
watcher.feedUsage({
|
|
33
|
+
agent: args.agent,
|
|
34
|
+
model: event.model,
|
|
35
|
+
inputTokens: event.inputTokens ?? 0,
|
|
36
|
+
outputTokens: event.outputTokens ?? 0,
|
|
37
|
+
});
|
|
38
|
+
if (watcher.breached() && firstBreach) {
|
|
39
|
+
// Cancel server-side FIRST so we stop the meter; then surface the
|
|
40
|
+
// breach to the renderer as an error + cancelled status so the CLI
|
|
41
|
+
// exits with a visible reason (not a silent stream close).
|
|
42
|
+
try {
|
|
43
|
+
await args.provider.cancel(args.taskId);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
// Best-effort — even if cancel fails, break the stream: the
|
|
47
|
+
// caller sees the error frame and can retry manually.
|
|
48
|
+
yield {
|
|
49
|
+
type: 'error',
|
|
50
|
+
message: `[budget] cap ${firstBreach.cap} exceeded ($${firstBreach.spend.toFixed(2)} > $${firstBreach.limit.toFixed(2)}); cancel FAILED: ${err.message}`,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
yield { type: 'status', status: 'cancelled', timestamp: new Date().toISOString() };
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
yield {
|
|
57
|
+
type: 'error',
|
|
58
|
+
message: `[budget] cap ${firstBreach.cap} exceeded ($${firstBreach.spend.toFixed(2)} > $${firstBreach.limit.toFixed(2)}) — cancelled cloud task ${args.taskId}`,
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
yield { type: 'status', status: 'cancelled', timestamp: new Date().toISOString() };
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
yield event;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
watcher.dispose();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
wrap,
|
|
74
|
+
gate: {
|
|
75
|
+
breached: () => watcher.breached(),
|
|
76
|
+
breach: () => firstBreach,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentManager } from '../teams/agents.js';
|
|
2
|
+
import { type BreachInfo } from './enforce.js';
|
|
3
|
+
/** Public surface of the team-scoped budget watcher. */
|
|
4
|
+
export interface TeamBudgetWatcher {
|
|
5
|
+
/** Feed any new usage events from every running teammate's stdout.log. */
|
|
6
|
+
poll(): Promise<void>;
|
|
7
|
+
/** True once any cap has been crossed (mirrors LiveSpendWatcher). */
|
|
8
|
+
breached(): boolean;
|
|
9
|
+
/** The first breach seen, or null if none yet. */
|
|
10
|
+
breach(): BreachInfo | null;
|
|
11
|
+
/** Release references and stop tapping streams. Idempotent. */
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a team-scoped budget watcher for the given team. Returns null when the
|
|
16
|
+
* effective budget config has no caps — matching the local watcher's dormant
|
|
17
|
+
* contract so callers pay nothing when the feature is off.
|
|
18
|
+
*
|
|
19
|
+
* The watcher is seeded with prior-day and prior-project spend from the ledger
|
|
20
|
+
* so a partway-through day counts today's earlier runs against `per_day`.
|
|
21
|
+
* Each `poll()` reads new bytes from every running teammate's stdout.log and
|
|
22
|
+
* feeds parsed usage events into the shared watcher — `onBreach` fires exactly
|
|
23
|
+
* once, mirroring `makeLiveSpendWatcher`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createTeamBudgetWatcher(args: {
|
|
26
|
+
manager: AgentManager;
|
|
27
|
+
team: string;
|
|
28
|
+
/** Project/cwd used to (a) resolve budget config and (b) seed project spend. */
|
|
29
|
+
cwd: string;
|
|
30
|
+
onBreach: (breach: BreachInfo) => void;
|
|
31
|
+
}): TeamBudgetWatcher | null;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live budget kill-switch for `agents teams` supervisor (issue #399).
|
|
3
|
+
*
|
|
4
|
+
* Follow-up to #346, which wired `makeLiveSpendWatcher` into local `agents run`
|
|
5
|
+
* only. Teams spawns each teammate as its own `agents run --headless --json`
|
|
6
|
+
* process, so per-teammate `per_run` caps already fire from those child
|
|
7
|
+
* watchers. What was still missing was aggregate cross-teammate enforcement
|
|
8
|
+
* (`per_project` / `per_day` / `per_agent` combined), because the ledger only
|
|
9
|
+
* gets written on child close — a long-running teammate would blow the shared
|
|
10
|
+
* cap without any single child watcher noticing.
|
|
11
|
+
*
|
|
12
|
+
* This module reuses `makeLiveSpendWatcher` verbatim: on each supervisor wave
|
|
13
|
+
* we tail every running teammate's stdout.log through `extractUsageEvents`
|
|
14
|
+
* (the same primitive the local run path uses in src/lib/exec.ts), feed the
|
|
15
|
+
* usage events into ONE shared watcher, and expose `breached()` so the
|
|
16
|
+
* supervisor can call `stopByTask` to terminate the whole team.
|
|
17
|
+
*
|
|
18
|
+
* Dormant (returns null) when no caps are configured — same zero-cost contract
|
|
19
|
+
* as the local watcher.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import { capsFromConfig, extractUsageEvents, makeLiveSpendWatcher, } from './enforce.js';
|
|
23
|
+
import { resolveBudgetConfig, hasAnyCap } from './config.js';
|
|
24
|
+
import { loadLedger, localDay, spendForDay, spendForProject } from './ledger.js';
|
|
25
|
+
/**
|
|
26
|
+
* Build a team-scoped budget watcher for the given team. Returns null when the
|
|
27
|
+
* effective budget config has no caps — matching the local watcher's dormant
|
|
28
|
+
* contract so callers pay nothing when the feature is off.
|
|
29
|
+
*
|
|
30
|
+
* The watcher is seeded with prior-day and prior-project spend from the ledger
|
|
31
|
+
* so a partway-through day counts today's earlier runs against `per_day`.
|
|
32
|
+
* Each `poll()` reads new bytes from every running teammate's stdout.log and
|
|
33
|
+
* feeds parsed usage events into the shared watcher — `onBreach` fires exactly
|
|
34
|
+
* once, mirroring `makeLiveSpendWatcher`.
|
|
35
|
+
*/
|
|
36
|
+
export function createTeamBudgetWatcher(args) {
|
|
37
|
+
const cfg = resolveBudgetConfig(args.cwd);
|
|
38
|
+
if (!hasAnyCap(cfg))
|
|
39
|
+
return null;
|
|
40
|
+
const today = localDay();
|
|
41
|
+
const entries = loadLedger();
|
|
42
|
+
const caps = capsFromConfig(cfg, {
|
|
43
|
+
daySpend: spendForDay(today, entries),
|
|
44
|
+
projectSpend: spendForProject(args.cwd, entries),
|
|
45
|
+
});
|
|
46
|
+
let firstBreach = null;
|
|
47
|
+
const watcher = makeLiveSpendWatcher({
|
|
48
|
+
caps,
|
|
49
|
+
onBreach: (b) => {
|
|
50
|
+
firstBreach = b;
|
|
51
|
+
args.onBreach(b);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const cursors = new Map();
|
|
55
|
+
let disposed = false;
|
|
56
|
+
return {
|
|
57
|
+
async poll() {
|
|
58
|
+
if (disposed || watcher.breached())
|
|
59
|
+
return;
|
|
60
|
+
const teammates = await args.manager.listByTask(args.team);
|
|
61
|
+
for (const agent of teammates) {
|
|
62
|
+
// Only tap running local teammates: PENDING has no output yet, cloud
|
|
63
|
+
// teammates emit through a different channel (their own SSE stream),
|
|
64
|
+
// and terminal states produce no new bytes.
|
|
65
|
+
if (agent.status !== 'running')
|
|
66
|
+
continue;
|
|
67
|
+
if (agent.cloudProvider)
|
|
68
|
+
continue;
|
|
69
|
+
const stdoutPath = await agent.getStdoutPath();
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = fs.statSync(stdoutPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue; // File not yet created — nothing to read.
|
|
76
|
+
}
|
|
77
|
+
const cursor = cursors.get(agent.agentId) ?? { offset: 0, pending: '' };
|
|
78
|
+
if (stat.size <= cursor.offset) {
|
|
79
|
+
cursors.set(agent.agentId, cursor);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const fd = fs.openSync(stdoutPath, 'r');
|
|
83
|
+
try {
|
|
84
|
+
const toRead = stat.size - cursor.offset;
|
|
85
|
+
const buffer = Buffer.alloc(toRead);
|
|
86
|
+
const bytesRead = fs.readSync(fd, buffer, 0, toRead, cursor.offset);
|
|
87
|
+
const chunk = buffer.toString('utf-8', 0, bytesRead);
|
|
88
|
+
cursor.offset += bytesRead;
|
|
89
|
+
const { events, rest } = extractUsageEvents(chunk, cursor.pending, undefined, agent.agentType);
|
|
90
|
+
cursor.pending = rest;
|
|
91
|
+
for (const ev of events) {
|
|
92
|
+
watcher.feedUsage(ev);
|
|
93
|
+
if (watcher.breached())
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
fs.closeSync(fd);
|
|
99
|
+
}
|
|
100
|
+
cursors.set(agent.agentId, cursor);
|
|
101
|
+
if (watcher.breached())
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
breached: () => watcher.breached(),
|
|
106
|
+
breach: () => firstBreach,
|
|
107
|
+
dispose() {
|
|
108
|
+
if (disposed)
|
|
109
|
+
return;
|
|
110
|
+
disposed = true;
|
|
111
|
+
watcher.dispose();
|
|
112
|
+
cursors.clear();
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/lib/cloud/codex.js
CHANGED
|
@@ -104,6 +104,10 @@ export class CodexCloudProvider {
|
|
|
104
104
|
cancel: false,
|
|
105
105
|
message: false,
|
|
106
106
|
multiRepo: false,
|
|
107
|
+
// `codex cloud exec --env <id> <prompt>` is the only dispatch surface the
|
|
108
|
+
// codex CLI exposes — it has no flag to attach images or ride-along skills,
|
|
109
|
+
// and the env bundles its own context at creation time. Both stay false
|
|
110
|
+
// until the upstream CLI grows an attachment surface.
|
|
107
111
|
skills: false,
|
|
108
112
|
images: false,
|
|
109
113
|
};
|
package/dist/lib/cloud/rush.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auth: reads the session token from ~/.rush/user.yaml (written by `rush login`).
|
|
5
5
|
* Requires the Rush GitHub App installed on the target repo.
|
|
6
6
|
*/
|
|
7
|
-
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
7
|
+
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities, ImageAttachment, SkillRef } from './types.js';
|
|
8
8
|
export declare const RUSH_CONSENT_PATH: string;
|
|
9
9
|
export declare function hasRushUploadConsent(accountFingerprint: string, opts?: DispatchOptions, consentPath?: string): boolean;
|
|
10
10
|
/** One version's entry in the account manifest sent on every dispatch. */
|
|
@@ -63,6 +63,17 @@ export declare function buildDispatchBody(input: {
|
|
|
63
63
|
}>;
|
|
64
64
|
accountManifest?: AccountManifest | null;
|
|
65
65
|
accountTokens?: AccountTokenEntry[] | null;
|
|
66
|
+
/**
|
|
67
|
+
* Skill ride-alongs so the cloud pod isn't context-blind. Forwarded verbatim
|
|
68
|
+
* as `skills` so the Factory Floor can mount them by id/version before the
|
|
69
|
+
* agent runs. Omitted when empty.
|
|
70
|
+
*/
|
|
71
|
+
skills?: SkillRef[] | null;
|
|
72
|
+
/**
|
|
73
|
+
* Base64 image attachments for vision dispatch. Sliced to
|
|
74
|
+
* MAX_IMAGES_PER_DISPATCH — extras are dropped, never sent. Omitted when empty.
|
|
75
|
+
*/
|
|
76
|
+
images?: ImageAttachment[] | null;
|
|
66
77
|
}): Record<string, unknown>;
|
|
67
78
|
/** A single account registered in Rush Cloud's multi-account rotation pool. */
|
|
68
79
|
export interface RemoteAccount {
|
package/dist/lib/cloud/rush.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as os from 'os';
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
12
|
import { getCloudDir } from '../state.js';
|
|
13
|
-
import { resolveDispatchRepos } from './types.js';
|
|
13
|
+
import { resolveDispatchRepos, MAX_IMAGES_PER_DISPATCH } from './types.js';
|
|
14
14
|
import { parseSSE } from './stream.js';
|
|
15
15
|
import { listInstalledVersions, getVersionHomePath } from '../versions.js';
|
|
16
16
|
import { getAccountInfo } from '../agents.js';
|
|
@@ -286,6 +286,12 @@ export function buildDispatchBody(input) {
|
|
|
286
286
|
if (input.accountTokens && input.accountTokens.length > 0) {
|
|
287
287
|
body.account_tokens = input.accountTokens;
|
|
288
288
|
}
|
|
289
|
+
if (input.skills && input.skills.length > 0) {
|
|
290
|
+
body.skills = input.skills;
|
|
291
|
+
}
|
|
292
|
+
if (input.images && input.images.length > 0) {
|
|
293
|
+
body.images = input.images.slice(0, MAX_IMAGES_PER_DISPATCH);
|
|
294
|
+
}
|
|
289
295
|
return body;
|
|
290
296
|
}
|
|
291
297
|
/** Fetch all Claude accounts in this user's Rush Cloud rotation pool (no tokens). */
|
|
@@ -332,8 +338,8 @@ export class RushCloudProvider {
|
|
|
332
338
|
cancel: true,
|
|
333
339
|
message: true,
|
|
334
340
|
multiRepo: true,
|
|
335
|
-
skills:
|
|
336
|
-
images:
|
|
341
|
+
skills: true,
|
|
342
|
+
images: true,
|
|
337
343
|
};
|
|
338
344
|
}
|
|
339
345
|
async dispatch(options) {
|
|
@@ -387,6 +393,8 @@ export class RushCloudProvider {
|
|
|
387
393
|
resolvedRepos,
|
|
388
394
|
accountManifest,
|
|
389
395
|
strategy,
|
|
396
|
+
skills: options.skills,
|
|
397
|
+
images: options.images,
|
|
390
398
|
});
|
|
391
399
|
let res = await api('POST', '/api/v1/cloud-runs', token, body);
|
|
392
400
|
// Server detects drift (new account or rotated token) by comparing the
|
|
@@ -429,6 +437,8 @@ export class RushCloudProvider {
|
|
|
429
437
|
resolvedRepos,
|
|
430
438
|
accountManifest,
|
|
431
439
|
accountTokens,
|
|
440
|
+
skills: options.skills,
|
|
441
|
+
images: options.images,
|
|
432
442
|
});
|
|
433
443
|
res = await api('POST', '/api/v1/cloud-runs', token, retryBody);
|
|
434
444
|
// Persist consent on first successful upload so we don't re-prompt
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Factory) implement, plus the shared task and event types that flow through
|
|
6
6
|
* the dispatch pipeline.
|
|
7
7
|
*/
|
|
8
|
+
import type { JobTrigger } from '../routines.js';
|
|
8
9
|
/**
|
|
9
10
|
* Identifier for a supported cloud dispatch backend.
|
|
10
11
|
*
|
|
@@ -119,6 +120,14 @@ export declare const MAX_IMAGES_PER_DISPATCH = 5;
|
|
|
119
120
|
export interface DispatchOptions {
|
|
120
121
|
prompt: string;
|
|
121
122
|
agent?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Event/webhook trigger to register with this dispatch. Set by
|
|
125
|
+
* `agents cloud run --on <event>`: instead of dispatching immediately, the
|
|
126
|
+
* dispatch is persisted as a trigger-bound routine so the local webhook
|
|
127
|
+
* receiver can fire it when the event arrives. Remote firing of the trigger
|
|
128
|
+
* is a follow-up; today the flag parses, validates, and persists.
|
|
129
|
+
*/
|
|
130
|
+
trigger?: JobTrigger;
|
|
122
131
|
/**
|
|
123
132
|
* Legacy single-repo target. Still honored: if `repos` is empty, this
|
|
124
133
|
* becomes the only repo. Providers that support multi-repo treat
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComputerClient } from '../computer-rpc.js';
|
|
2
|
+
import { type AppInfo } from '../../commands/computer-actions.js';
|
|
3
|
+
import type { VerbDispatcher } from './loop.js';
|
|
4
|
+
declare function rpcMethodFor(verb: string): string;
|
|
5
|
+
declare function toRpcParams(verb: string, input: Record<string, unknown>, pid?: number): Record<string, unknown>;
|
|
6
|
+
export declare function makeVerbDispatcher(client: ComputerClient): VerbDispatcher;
|
|
7
|
+
export { rpcMethodFor, toRpcParams };
|
|
8
|
+
export type { AppInfo };
|