@phnx-labs/agents-cli 1.20.34 → 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 +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- 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-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- 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 +196 -91
- 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.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- 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/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- 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/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- 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 +183 -23
- 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 +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- 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 +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- 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 +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- 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 +22 -0
- package/dist/lib/hosts/tasks.js +29 -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/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- 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/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- 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 +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- 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 +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -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/registry.js +25 -9
- 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/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- 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/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- 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/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
package/dist/lib/plugins.js
CHANGED
|
@@ -18,7 +18,7 @@ import { listInstalledVersions, getVersionHomePath } from './versions.js';
|
|
|
18
18
|
import { AGENTS, agentConfigDirName } from './agents.js';
|
|
19
19
|
import { capableAgents, isCapable } from './capabilities.js';
|
|
20
20
|
import { shouldInstallCommandAsSkill, installCommandSkillToVersion } from './command-skills.js';
|
|
21
|
-
import { copyPluginToMarketplace, syncMarketplaceManifest, registerMarketplace, unregisterMarketplace, addPluginToSettings, removePluginFromSettings, removePluginFromMarketplace, marketplaceIsEmpty, removeEmptyMarketplaceDir, isInstalledInMarketplace, marketplaceRoot, discoverMarketplaces, marketplaceNameFor, MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, } from './plugin-marketplace.js';
|
|
21
|
+
import { copyPluginToMarketplace, syncMarketplaceManifest, registerMarketplace, unregisterMarketplace, addPluginToSettings, removePluginFromSettings, removePluginFromMarketplace, registerDroidInstalledPlugin, unregisterDroidInstalledPlugin, isDroidPluginInstalled, registerCopilotInstalledPlugin, unregisterCopilotInstalledPlugin, marketplaceIsEmpty, removeEmptyMarketplaceDir, isInstalledInMarketplace, marketplaceRoot, discoverMarketplaces, marketplaceNameFor, MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, } from './plugin-marketplace.js';
|
|
22
22
|
const PLUGIN_MANIFEST_DIR = '.claude-plugin';
|
|
23
23
|
const PLUGIN_MANIFEST_FILE = 'plugin.json';
|
|
24
24
|
const USER_CONFIG_FILE = '.user-config.json';
|
|
@@ -172,16 +172,26 @@ export function loadPluginManifest(pluginRoot) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
export function validatePluginName(name) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
if (!name || name.length === 0 || name === '.' || name === '..') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const normalized = path.normalize(name);
|
|
179
|
+
if (normalized === '.' || normalized === '' || normalized === '..') {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (/[/\\]/.test(name) || name.includes('\0')) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (path.basename(normalized) !== name) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
179
189
|
}
|
|
180
190
|
export function assertPluginTargetContained(targetRoot, pluginsDir) {
|
|
181
191
|
const resolvedPluginsDir = path.resolve(pluginsDir);
|
|
182
192
|
const resolvedTargetRoot = path.resolve(targetRoot);
|
|
183
|
-
if (resolvedTargetRoot
|
|
184
|
-
|
|
193
|
+
if (resolvedTargetRoot === resolvedPluginsDir
|
|
194
|
+
|| !resolvedTargetRoot.startsWith(`${resolvedPluginsDir}${path.sep}`)) {
|
|
185
195
|
throw new Error(`Plugin install target escapes plugins directory: ${targetRoot}`);
|
|
186
196
|
}
|
|
187
197
|
}
|
|
@@ -416,7 +426,7 @@ export function marketplaceSpecForName(name, cwd = process.cwd()) {
|
|
|
416
426
|
* carries, not just the user one.
|
|
417
427
|
*/
|
|
418
428
|
function listVersionMarketplaceNames(agent, versionHome) {
|
|
419
|
-
const dir = path.join(versionHome,
|
|
429
|
+
const dir = path.join(versionHome, agentConfigDirName(agent), 'plugins', 'marketplaces');
|
|
420
430
|
if (!fs.existsSync(dir))
|
|
421
431
|
return [];
|
|
422
432
|
try {
|
|
@@ -444,6 +454,11 @@ function listVersionMarketplaceNames(agent, versionHome) {
|
|
|
444
454
|
* MCP servers, bin/, settings.json, and permissions once the plugin lives at the
|
|
445
455
|
* native install path and is marked enabled — see
|
|
446
456
|
* https://code.claude.com/docs/en/plugins.
|
|
457
|
+
*
|
|
458
|
+
* Droid (Factory CLI) reuses this same marketplace layout but additionally
|
|
459
|
+
* requires an installed_plugins.json registry entry and a "local"-source
|
|
460
|
+
* known_marketplaces.json entry before `droid plugin list` will see the plugin
|
|
461
|
+
* (steps handled by registerMarketplace's droid branch + step 5c below).
|
|
447
462
|
*/
|
|
448
463
|
export function syncPluginToVersion(plugin, agent, versionHome, options = {}) {
|
|
449
464
|
const result = {
|
|
@@ -491,9 +506,25 @@ export function syncPluginToVersion(plugin, agent, versionHome, options = {}) {
|
|
|
491
506
|
// .mcp.json, settings.json, permissions/) are only auto-enabled when the
|
|
492
507
|
// caller explicitly opts in. addPluginToSettings does no gating — that moved
|
|
493
508
|
// here, where plugin capabilities are inspected.
|
|
494
|
-
|
|
509
|
+
const enablePlugin = options.allowExecSurfaces === true || !hasPluginExecSurfaces(inspectPluginCapabilities(plugin.root));
|
|
510
|
+
if (enablePlugin) {
|
|
495
511
|
addPluginToSettings(plugin.name, marketplaceName, agent, versionHome);
|
|
496
512
|
}
|
|
513
|
+
// 5c. Droid diverges from Claude's marketplace+enabledPlugins model: it only
|
|
514
|
+
// "sees" a plugin that also has an entry in installed_plugins.json (with
|
|
515
|
+
// the marketplace registered as source "local"). Register the install so
|
|
516
|
+
// `droid plugin list` shows it — Active when enabled, Inactive otherwise.
|
|
517
|
+
if (agent === 'droid') {
|
|
518
|
+
registerDroidInstalledPlugin(plugin.name, marketplaceName, installDir, plugin.manifest.version, agent, versionHome);
|
|
519
|
+
}
|
|
520
|
+
// 5d. Copilot, like Droid, only "sees" a plugin that also has an entry in its
|
|
521
|
+
// auto-managed config.json#installedPlugins (registerMarketplace already
|
|
522
|
+
// wrote settings.json#extraKnownMarketplaces, and addPluginToSettings the
|
|
523
|
+
// enabledPlugins flag). cache_path points at the marketplace copy; the
|
|
524
|
+
// `enabled` flag mirrors the exec-surface trust gate above.
|
|
525
|
+
if (agent === 'copilot') {
|
|
526
|
+
registerCopilotInstalledPlugin(plugin.name, marketplaceName, installDir, plugin.manifest.version, enablePlugin, agent, versionHome);
|
|
527
|
+
}
|
|
497
528
|
// 5b. Convert plugin commands/ to skills for agents that dropped command support
|
|
498
529
|
// (Codex >= 0.117.0). Skill name is prefixed with plugin name to avoid
|
|
499
530
|
// collision with standalone command skills.
|
|
@@ -718,7 +749,15 @@ function migrateLegacyFlatLayout(plugin, agent, versionHome) {
|
|
|
718
749
|
export function isPluginSynced(plugin, agent, versionHome) {
|
|
719
750
|
if (!isCapable(agent, 'plugins'))
|
|
720
751
|
return false;
|
|
721
|
-
|
|
752
|
+
const spec = marketplaceSpecForName(plugin.marketplace);
|
|
753
|
+
if (!isInstalledInMarketplace(plugin.name, spec, agent, versionHome))
|
|
754
|
+
return false;
|
|
755
|
+
// Droid additionally requires its installed_plugins.json registry entry —
|
|
756
|
+
// without it the marketplace copy is invisible to `droid plugin list`.
|
|
757
|
+
if (agent === 'droid') {
|
|
758
|
+
return isDroidPluginInstalled(plugin.name, marketplaceNameFor(spec), agent, versionHome);
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
722
761
|
}
|
|
723
762
|
// ─── Removal ─────────────────────────────────────────────────────────────────
|
|
724
763
|
/**
|
|
@@ -746,6 +785,12 @@ export function removePluginFromVersion(pluginName, pluginRoot, agent, versionHo
|
|
|
746
785
|
removedAny = true;
|
|
747
786
|
}
|
|
748
787
|
removePluginFromSettings(pluginName, name, agent, versionHome);
|
|
788
|
+
if (agent === 'droid') {
|
|
789
|
+
unregisterDroidInstalledPlugin(pluginName, name, agent, versionHome);
|
|
790
|
+
}
|
|
791
|
+
if (agent === 'copilot') {
|
|
792
|
+
unregisterCopilotInstalledPlugin(pluginName, name, agent, versionHome);
|
|
793
|
+
}
|
|
749
794
|
// Refresh marketplace.json so it reflects what's left under plugins/.
|
|
750
795
|
syncMarketplaceManifest(spec, agent, versionHome);
|
|
751
796
|
// If we just removed the last plugin, drop the marketplace dir and the
|
|
@@ -930,6 +975,12 @@ export function cleanOrphanedPluginSkills(agent, versionHome, activePluginNames,
|
|
|
930
975
|
fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
|
|
931
976
|
fs.renameSync(path.join(mktPluginsDir, entry.name), trashDest);
|
|
932
977
|
removePluginFromSettings(entry.name, name, agent, versionHome);
|
|
978
|
+
if (agent === 'droid') {
|
|
979
|
+
unregisterDroidInstalledPlugin(entry.name, name, agent, versionHome);
|
|
980
|
+
}
|
|
981
|
+
if (agent === 'copilot') {
|
|
982
|
+
unregisterCopilotInstalledPlugin(entry.name, name, agent, versionHome);
|
|
983
|
+
}
|
|
933
984
|
removed.push(entry.name);
|
|
934
985
|
trashedHere = true;
|
|
935
986
|
}
|
package/dist/lib/profiles.d.ts
CHANGED
|
@@ -22,6 +22,14 @@ export interface Profile {
|
|
|
22
22
|
description?: string;
|
|
23
23
|
preset?: string;
|
|
24
24
|
provider?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Optional secondary model retried on the same host when the primary model
|
|
27
|
+
* env value hits a rate limit. Reuses the `--fallback` cascade in
|
|
28
|
+
* `runWithFallback` (src/lib/exec.ts) — the swap is expressed as an
|
|
29
|
+
* envOverride on a same-agent FallbackEntry, so only the model env var
|
|
30
|
+
* changes; auth, base URL, and every other profile env value are preserved.
|
|
31
|
+
*/
|
|
32
|
+
fallback_model?: string;
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Stable, machine-readable summary used by `agents view` and `--json`.
|
|
@@ -59,6 +67,13 @@ export declare function profileHostLabel(profile: Profile): string;
|
|
|
59
67
|
export declare function profileProviderLabel(profile: Profile): string;
|
|
60
68
|
/** Return the configured model env value for display. */
|
|
61
69
|
export declare function profileModelLabel(profile: Profile): string;
|
|
70
|
+
/**
|
|
71
|
+
* Return the env var key that carries this profile's model (e.g.
|
|
72
|
+
* `ANTHROPIC_MODEL`), or null when the profile has no recognizable model env.
|
|
73
|
+
* `fallback_model` swaps THIS key so provider selection, auth, and base URL
|
|
74
|
+
* are all preserved on retry.
|
|
75
|
+
*/
|
|
76
|
+
export declare function profileModelEnvKey(profile: Profile): string | null;
|
|
62
77
|
/**
|
|
63
78
|
* Build a non-secret auth identity/status label for list surfaces.
|
|
64
79
|
*
|
|
@@ -89,6 +104,16 @@ export interface ResolvedProfileRun {
|
|
|
89
104
|
version?: string;
|
|
90
105
|
env: Record<string, string>;
|
|
91
106
|
profileName: string;
|
|
107
|
+
/**
|
|
108
|
+
* Same-host model swap for the `--fallback` cascade. Present only when the
|
|
109
|
+
* profile declares `fallback_model` AND the profile has an identifiable
|
|
110
|
+
* model env key to swap. `envKey` names the var (e.g. `ANTHROPIC_MODEL`),
|
|
111
|
+
* `model` is the value to write on the retry attempt.
|
|
112
|
+
*/
|
|
113
|
+
fallbackModel?: {
|
|
114
|
+
envKey: string;
|
|
115
|
+
model: string;
|
|
116
|
+
};
|
|
92
117
|
}
|
|
93
118
|
/**
|
|
94
119
|
* Resolve a name into (agent, version, env). Throws if the name is not a
|
package/dist/lib/profiles.js
CHANGED
|
@@ -111,16 +111,25 @@ const MODEL_ENV_KEYS = [
|
|
|
111
111
|
];
|
|
112
112
|
/** Return the configured model env value for display. */
|
|
113
113
|
export function profileModelLabel(profile) {
|
|
114
|
+
const key = profileModelEnvKey(profile);
|
|
115
|
+
return key ? profile.env[key] : '-';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Return the env var key that carries this profile's model (e.g.
|
|
119
|
+
* `ANTHROPIC_MODEL`), or null when the profile has no recognizable model env.
|
|
120
|
+
* `fallback_model` swaps THIS key so provider selection, auth, and base URL
|
|
121
|
+
* are all preserved on retry.
|
|
122
|
+
*/
|
|
123
|
+
export function profileModelEnvKey(profile) {
|
|
114
124
|
for (const key of MODEL_ENV_KEYS) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return value;
|
|
125
|
+
if (profile.env[key])
|
|
126
|
+
return key;
|
|
118
127
|
}
|
|
119
128
|
for (const [key, value] of Object.entries(profile.env)) {
|
|
120
129
|
if ((key === 'MODEL' || key.endsWith('_MODEL')) && value)
|
|
121
|
-
return
|
|
130
|
+
return key;
|
|
122
131
|
}
|
|
123
|
-
return
|
|
132
|
+
return null;
|
|
124
133
|
}
|
|
125
134
|
function decodeJwtPayload(token) {
|
|
126
135
|
const parts = token.split('.');
|
|
@@ -239,12 +248,19 @@ export function resolveProfileEnv(profile) {
|
|
|
239
248
|
*/
|
|
240
249
|
export function resolveProfileForRun(name) {
|
|
241
250
|
const profile = readProfile(name);
|
|
242
|
-
|
|
251
|
+
const resolved = {
|
|
243
252
|
agent: profile.host.agent,
|
|
244
253
|
version: profile.host.version,
|
|
245
254
|
env: resolveProfileEnv(profile),
|
|
246
255
|
profileName: profile.name,
|
|
247
256
|
};
|
|
257
|
+
if (profile.fallback_model) {
|
|
258
|
+
const envKey = profileModelEnvKey(profile);
|
|
259
|
+
if (envKey) {
|
|
260
|
+
resolved.fallbackModel = { envKey, model: profile.fallback_model };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return resolved;
|
|
248
264
|
}
|
|
249
265
|
/**
|
|
250
266
|
* Look up the preset a profile was created from, if any. Used by
|
package/dist/lib/pty-client.js
CHANGED
|
@@ -10,6 +10,7 @@ import { spawn, execSync } from 'child_process';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { getSocketPath, getPtyLogPath, isPtyServerRunning } from './pty-server.js';
|
|
13
|
+
import { backgroundSpawnOptions } from './platform/process.js';
|
|
13
14
|
const CONNECT_TIMEOUT_MS = 5000;
|
|
14
15
|
const RESPONSE_TIMEOUT_MS = 30000;
|
|
15
16
|
const IS_WINDOWS = process.platform === 'win32';
|
|
@@ -38,7 +39,7 @@ async function ensureServer() {
|
|
|
38
39
|
const logFd = fs.openSync(logPath, 'a');
|
|
39
40
|
const child = spawn(bin, args, {
|
|
40
41
|
stdio: ['ignore', logFd, logFd],
|
|
41
|
-
|
|
42
|
+
...backgroundSpawnOptions({ fdStdio: true }),
|
|
42
43
|
});
|
|
43
44
|
child.unref();
|
|
44
45
|
fs.closeSync(logFd);
|
package/dist/lib/rotate.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* authentication tokens so users maintain active sessions across version switches.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentId, RunStrategy } from './types.js';
|
|
8
|
+
import type { FallbackEntry } from './exec.js';
|
|
8
9
|
import { type AccountInfo } from './agents.js';
|
|
9
10
|
import { type UsageSnapshot } from './usage.js';
|
|
10
11
|
export interface RotateCandidate {
|
|
@@ -101,3 +102,63 @@ export declare function resolveRunVersion(agent: AgentId, strategy: RunStrategy,
|
|
|
101
102
|
version: string | null;
|
|
102
103
|
rotation: RotateResult | null;
|
|
103
104
|
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Cap on the number of healthy accounts a single run will re-dispatch through
|
|
107
|
+
* after a mid-run rate limit. Bounds the synthesized chain so a machine signed
|
|
108
|
+
* into many accounts can't turn one 429 into an unbounded cascade of retries.
|
|
109
|
+
*/
|
|
110
|
+
export declare const DEFAULT_ROTATION_FAILOVER_LIMIT = 3;
|
|
111
|
+
/**
|
|
112
|
+
* Synthesize a same-agent, cross-account fallback chain from a pre-flight
|
|
113
|
+
* rotation result (issue #348: mid-run rate-limit failover).
|
|
114
|
+
*
|
|
115
|
+
* The account rotation picks ONE version pre-spawn; today a 429 mid-run kills
|
|
116
|
+
* the run with no recovery. `runWithFallback` + `detectRateLimit` already
|
|
117
|
+
* re-dispatch to the NEXT chain entry on a rate limit and hand off the session
|
|
118
|
+
* via `/continue <id>` — but only for explicit `--fallback` chains. This turns
|
|
119
|
+
* the OTHER healthy rotation candidates (every account except the one already
|
|
120
|
+
* picked as the primary) into `FallbackEntry`s so that SAME machinery re-runs
|
|
121
|
+
* the task on the next healthy account of the same agent when the primary 429s.
|
|
122
|
+
*
|
|
123
|
+
* Each account is a distinct installed version (its own home/auth), so the
|
|
124
|
+
* entries are same-agent, different-version — exactly what runWithFallback
|
|
125
|
+
* spawns and what buildFallbackPrompt continues (claude→claude via `/continue`).
|
|
126
|
+
* Candidates are consumed in `rotation.healthy` order, which is sorted by
|
|
127
|
+
* remaining capacity (most headroom first, see compareCandidates), so failover
|
|
128
|
+
* prefers the freshest account.
|
|
129
|
+
*
|
|
130
|
+
* Returns `[]` when there is no rotation (pinned strategy) or the picked account
|
|
131
|
+
* is the only healthy one — so single-account users and non-rotation runs are
|
|
132
|
+
* completely unchanged.
|
|
133
|
+
*/
|
|
134
|
+
export declare function rotationFailoverChain(rotation: RotateResult | null, pickedVersion: string, limit?: number): FallbackEntry[];
|
|
135
|
+
/**
|
|
136
|
+
* Whether a run is eligible to have a mid-run rate-limit failover chain armed
|
|
137
|
+
* (issue #348). Failover injects synthesized `FallbackEntry`s into the same
|
|
138
|
+
* `fallback` array that `--fallback` uses — so it must NOT arm for run shapes
|
|
139
|
+
* that reject a non-empty fallback chain, or the run hard-exits on a flag the
|
|
140
|
+
* user never passed. Specifically:
|
|
141
|
+
*
|
|
142
|
+
* - `acp` and `loop` runs bail with "not compatible with --fallback yet" the
|
|
143
|
+
* moment `fallback.length > 0` (src/commands/exec.ts), so arming failover
|
|
144
|
+
* would break a previously-working `agents run … --loop` / `--acp`.
|
|
145
|
+
* - `resumeCheckpoint` runs take the loop path (same guard).
|
|
146
|
+
* - `interactive` / no-prompt runs can't be re-dispatched headlessly.
|
|
147
|
+
* - `explicitFallback` (a user `--fallback` chain OR a profile fallback already
|
|
148
|
+
* unshifted) defines its own recovery; don't layer rotation failover on top.
|
|
149
|
+
* - `hasRotation`/`hasVersion` gate on an actual pre-flight rotation having
|
|
150
|
+
* picked an account, so pinned and non-rotation runs are untouched.
|
|
151
|
+
*
|
|
152
|
+
* Pure so the arming matrix is unit-testable without invoking the run command.
|
|
153
|
+
*/
|
|
154
|
+
export interface FailoverArmingContext {
|
|
155
|
+
hasRotation: boolean;
|
|
156
|
+
hasVersion: boolean;
|
|
157
|
+
hasPrompt: boolean;
|
|
158
|
+
explicitFallback: boolean;
|
|
159
|
+
interactive: boolean;
|
|
160
|
+
acp: boolean;
|
|
161
|
+
loop: boolean;
|
|
162
|
+
resumeCheckpoint: boolean;
|
|
163
|
+
}
|
|
164
|
+
export declare function shouldArmRotationFailover(ctx: FailoverArmingContext): boolean;
|
package/dist/lib/rotate.js
CHANGED
|
@@ -341,3 +341,55 @@ export async function resolveRunVersion(agent, strategy, cwd = process.cwd()) {
|
|
|
341
341
|
}
|
|
342
342
|
return { version: fallback, rotation: null };
|
|
343
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Cap on the number of healthy accounts a single run will re-dispatch through
|
|
346
|
+
* after a mid-run rate limit. Bounds the synthesized chain so a machine signed
|
|
347
|
+
* into many accounts can't turn one 429 into an unbounded cascade of retries.
|
|
348
|
+
*/
|
|
349
|
+
export const DEFAULT_ROTATION_FAILOVER_LIMIT = 3;
|
|
350
|
+
/**
|
|
351
|
+
* Synthesize a same-agent, cross-account fallback chain from a pre-flight
|
|
352
|
+
* rotation result (issue #348: mid-run rate-limit failover).
|
|
353
|
+
*
|
|
354
|
+
* The account rotation picks ONE version pre-spawn; today a 429 mid-run kills
|
|
355
|
+
* the run with no recovery. `runWithFallback` + `detectRateLimit` already
|
|
356
|
+
* re-dispatch to the NEXT chain entry on a rate limit and hand off the session
|
|
357
|
+
* via `/continue <id>` — but only for explicit `--fallback` chains. This turns
|
|
358
|
+
* the OTHER healthy rotation candidates (every account except the one already
|
|
359
|
+
* picked as the primary) into `FallbackEntry`s so that SAME machinery re-runs
|
|
360
|
+
* the task on the next healthy account of the same agent when the primary 429s.
|
|
361
|
+
*
|
|
362
|
+
* Each account is a distinct installed version (its own home/auth), so the
|
|
363
|
+
* entries are same-agent, different-version — exactly what runWithFallback
|
|
364
|
+
* spawns and what buildFallbackPrompt continues (claude→claude via `/continue`).
|
|
365
|
+
* Candidates are consumed in `rotation.healthy` order, which is sorted by
|
|
366
|
+
* remaining capacity (most headroom first, see compareCandidates), so failover
|
|
367
|
+
* prefers the freshest account.
|
|
368
|
+
*
|
|
369
|
+
* Returns `[]` when there is no rotation (pinned strategy) or the picked account
|
|
370
|
+
* is the only healthy one — so single-account users and non-rotation runs are
|
|
371
|
+
* completely unchanged.
|
|
372
|
+
*/
|
|
373
|
+
export function rotationFailoverChain(rotation, pickedVersion, limit = DEFAULT_ROTATION_FAILOVER_LIMIT) {
|
|
374
|
+
if (!rotation || limit <= 0)
|
|
375
|
+
return [];
|
|
376
|
+
const chain = [];
|
|
377
|
+
for (const candidate of rotation.healthy) {
|
|
378
|
+
if (candidate.version === pickedVersion)
|
|
379
|
+
continue; // the primary account
|
|
380
|
+
chain.push({ agent: candidate.agent, version: candidate.version });
|
|
381
|
+
if (chain.length >= limit)
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
return chain;
|
|
385
|
+
}
|
|
386
|
+
export function shouldArmRotationFailover(ctx) {
|
|
387
|
+
return (ctx.hasRotation &&
|
|
388
|
+
ctx.hasVersion &&
|
|
389
|
+
ctx.hasPrompt &&
|
|
390
|
+
!ctx.explicitFallback &&
|
|
391
|
+
!ctx.interactive &&
|
|
392
|
+
!ctx.acp &&
|
|
393
|
+
!ctx.loop &&
|
|
394
|
+
!ctx.resumeCheckpoint);
|
|
395
|
+
}
|
package/dist/lib/routines.d.ts
CHANGED
|
@@ -7,16 +7,50 @@
|
|
|
7
7
|
* scheduling.
|
|
8
8
|
*/
|
|
9
9
|
import type { AgentId } from './types.js';
|
|
10
|
+
import type { LoopConfig } from './loop.js';
|
|
10
11
|
/** Tool/site/directory allow-list for sandboxed job execution. */
|
|
11
12
|
export interface JobAllowConfig {
|
|
12
13
|
tools?: string[];
|
|
13
14
|
sites?: string[];
|
|
14
15
|
dirs?: string[];
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
+
/** GitHub webhook events a routine can be triggered by. */
|
|
18
|
+
export type GithubTriggerEvent = 'pull_request' | 'push' | 'issue_comment' | 'workflow_run';
|
|
19
|
+
/** Canonical set of accepted GitHub trigger events — single source for validation. */
|
|
20
|
+
export declare const GITHUB_TRIGGER_EVENTS: readonly GithubTriggerEvent[];
|
|
21
|
+
/**
|
|
22
|
+
* Map a user-facing `--on` alias to a canonical GitHub trigger event.
|
|
23
|
+
* Accepts the canonical names plus friendly shortcuts (e.g. `pr`, `pr_opened`
|
|
24
|
+
* → `pull_request`, `comment` → `issue_comment`). Returns null when unknown.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeTriggerEvent(input: string): GithubTriggerEvent | null;
|
|
27
|
+
/**
|
|
28
|
+
* Event-based fire condition for a routine — an alternative (or complement) to
|
|
29
|
+
* `schedule`. Currently only `github_event`: an incoming GitHub webhook whose
|
|
30
|
+
* event (and optional repo/branch) match fires the job through the same
|
|
31
|
+
* dispatch path a cron fire uses. See `src/lib/triggers/webhook.ts`.
|
|
32
|
+
*/
|
|
33
|
+
export interface JobTrigger {
|
|
34
|
+
type: 'github_event';
|
|
35
|
+
event: GithubTriggerEvent;
|
|
36
|
+
/** `owner/name` — when set, only payloads for this repo match. */
|
|
37
|
+
repo?: string;
|
|
38
|
+
/** git branch (ref short name) — when set, only payloads for this branch match. */
|
|
39
|
+
branch?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Full configuration for a routine (persisted as YAML).
|
|
43
|
+
*
|
|
44
|
+
* A job fires on a `schedule` (cron), on a `trigger` (event/webhook), or both.
|
|
45
|
+
* `schedule` remains a first-class field; trigger-only jobs omit it and are
|
|
46
|
+
* skipped by the cron scheduler (they fire only via the webhook receiver).
|
|
47
|
+
*/
|
|
17
48
|
export interface JobConfig {
|
|
18
49
|
name: string;
|
|
19
|
-
|
|
50
|
+
/** Cron expression. Optional when `trigger` is set (event-only routine). */
|
|
51
|
+
schedule?: string;
|
|
52
|
+
/** Event/webhook fire condition. Optional when `schedule` is set. */
|
|
53
|
+
trigger?: JobTrigger;
|
|
20
54
|
agent: AgentId;
|
|
21
55
|
workflow?: string;
|
|
22
56
|
mode: 'plan' | 'edit' | 'auto' | 'skip' | 'full';
|
|
@@ -33,6 +67,8 @@ export interface JobConfig {
|
|
|
33
67
|
version?: string;
|
|
34
68
|
runOnce?: boolean;
|
|
35
69
|
endAt?: string;
|
|
70
|
+
/** When set, executeJob runs this job through the loop driver instead of once. */
|
|
71
|
+
loop?: LoopConfig;
|
|
36
72
|
}
|
|
37
73
|
/** Metadata for a single job execution, persisted as JSON in the run directory. */
|
|
38
74
|
export interface RunMeta {
|
|
@@ -67,6 +103,8 @@ export declare function deleteJob(name: string): boolean;
|
|
|
67
103
|
export declare function setJobEnabled(name: string, enabled: boolean): void;
|
|
68
104
|
/** Validate a partial job config, returning a list of human-readable errors. */
|
|
69
105
|
export declare function validateJob(config: Partial<JobConfig>): string[];
|
|
106
|
+
/** Validate a job trigger block, returning a list of human-readable errors. */
|
|
107
|
+
export declare function validateTrigger(trigger: unknown): string[];
|
|
70
108
|
/** True when a job's endAt has already elapsed. False when endAt is unset or in the future. */
|
|
71
109
|
export declare function isPastEndAt(config: Pick<JobConfig, 'endAt'>, now?: Date): boolean;
|
|
72
110
|
/** Expand built-in and user-defined template variables in a job's prompt string. */
|
package/dist/lib/routines.js
CHANGED
|
@@ -13,6 +13,33 @@ import { Cron } from 'croner';
|
|
|
13
13
|
import { getRoutinesDir, getRunsDir, ensureAgentsDir, getProjectRoutinesDir } from './state.js';
|
|
14
14
|
import { safeJoin } from './paths.js';
|
|
15
15
|
import { ALL_AGENT_IDS } from './agents.js';
|
|
16
|
+
/** Canonical set of accepted GitHub trigger events — single source for validation. */
|
|
17
|
+
export const GITHUB_TRIGGER_EVENTS = [
|
|
18
|
+
'pull_request',
|
|
19
|
+
'push',
|
|
20
|
+
'issue_comment',
|
|
21
|
+
'workflow_run',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Map a user-facing `--on` alias to a canonical GitHub trigger event.
|
|
25
|
+
* Accepts the canonical names plus friendly shortcuts (e.g. `pr`, `pr_opened`
|
|
26
|
+
* → `pull_request`, `comment` → `issue_comment`). Returns null when unknown.
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeTriggerEvent(input) {
|
|
29
|
+
const key = input.trim().toLowerCase();
|
|
30
|
+
const aliases = {
|
|
31
|
+
pull_request: 'pull_request',
|
|
32
|
+
pr: 'pull_request',
|
|
33
|
+
pr_opened: 'pull_request',
|
|
34
|
+
pull: 'pull_request',
|
|
35
|
+
push: 'push',
|
|
36
|
+
issue_comment: 'issue_comment',
|
|
37
|
+
comment: 'issue_comment',
|
|
38
|
+
workflow_run: 'workflow_run',
|
|
39
|
+
workflow: 'workflow_run',
|
|
40
|
+
};
|
|
41
|
+
return aliases[key] ?? null;
|
|
42
|
+
}
|
|
16
43
|
/** Default values applied to every job config when fields are omitted. */
|
|
17
44
|
const JOB_DEFAULTS = {
|
|
18
45
|
mode: 'plan',
|
|
@@ -137,18 +164,28 @@ export function validateJob(config) {
|
|
|
137
164
|
if (!config.name || typeof config.name !== 'string') {
|
|
138
165
|
errors.push('name is required');
|
|
139
166
|
}
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
const hasSchedule = Boolean(config.schedule && typeof config.schedule === 'string');
|
|
168
|
+
const hasTrigger = config.trigger !== undefined;
|
|
169
|
+
if (!hasSchedule && !hasTrigger) {
|
|
170
|
+
errors.push('schedule (cron expression) or trigger is required');
|
|
142
171
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
new Cron(config.schedule);
|
|
172
|
+
if (config.schedule !== undefined) {
|
|
173
|
+
if (typeof config.schedule !== 'string') {
|
|
174
|
+
errors.push('schedule must be a cron expression string');
|
|
147
175
|
}
|
|
148
|
-
|
|
149
|
-
|
|
176
|
+
else {
|
|
177
|
+
// Validate cron expression is parseable
|
|
178
|
+
try {
|
|
179
|
+
new Cron(config.schedule);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
errors.push(`invalid cron expression: "${config.schedule}"`);
|
|
183
|
+
}
|
|
150
184
|
}
|
|
151
185
|
}
|
|
186
|
+
if (config.trigger !== undefined) {
|
|
187
|
+
errors.push(...validateTrigger(config.trigger));
|
|
188
|
+
}
|
|
152
189
|
const hasAgent = Boolean(config.agent && typeof config.agent === 'string');
|
|
153
190
|
const hasWorkflow = Boolean(config.workflow && typeof config.workflow === 'string');
|
|
154
191
|
if (!hasAgent && !hasWorkflow) {
|
|
@@ -184,6 +221,27 @@ export function validateJob(config) {
|
|
|
184
221
|
}
|
|
185
222
|
return errors;
|
|
186
223
|
}
|
|
224
|
+
/** Validate a job trigger block, returning a list of human-readable errors. */
|
|
225
|
+
export function validateTrigger(trigger) {
|
|
226
|
+
const errors = [];
|
|
227
|
+
if (!trigger || typeof trigger !== 'object') {
|
|
228
|
+
return ['trigger must be an object'];
|
|
229
|
+
}
|
|
230
|
+
const t = trigger;
|
|
231
|
+
if (t.type !== 'github_event') {
|
|
232
|
+
errors.push("trigger.type must be 'github_event'");
|
|
233
|
+
}
|
|
234
|
+
if (!t.event || !GITHUB_TRIGGER_EVENTS.includes(t.event)) {
|
|
235
|
+
errors.push(`trigger.event must be one of: ${GITHUB_TRIGGER_EVENTS.join(', ')}`);
|
|
236
|
+
}
|
|
237
|
+
if (t.repo !== undefined && (typeof t.repo !== 'string' || !/^[^/\s]+\/[^/\s]+$/.test(t.repo))) {
|
|
238
|
+
errors.push('trigger.repo must be in owner/name form');
|
|
239
|
+
}
|
|
240
|
+
if (t.branch !== undefined && typeof t.branch !== 'string') {
|
|
241
|
+
errors.push('trigger.branch must be a string');
|
|
242
|
+
}
|
|
243
|
+
return errors;
|
|
244
|
+
}
|
|
187
245
|
function isParseableDate(value) {
|
|
188
246
|
if (!value.trim())
|
|
189
247
|
return false;
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { JobConfig, RunMeta } from './routines.js';
|
|
10
10
|
import type { AgentId } from './types.js';
|
|
11
|
+
import type { LoopDeps } from './loop.js';
|
|
11
12
|
/** Result of a completed job execution, including metadata and optional report. */
|
|
12
13
|
export interface RunResult {
|
|
13
14
|
meta: RunMeta;
|
|
@@ -15,8 +16,16 @@ export interface RunResult {
|
|
|
15
16
|
}
|
|
16
17
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|
|
17
18
|
export declare function buildJobCommand(config: JobConfig, resolvedPrompt: string): string[];
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Execute a job synchronously (waits for completion or timeout before resolving).
|
|
21
|
+
*
|
|
22
|
+
* When `config.loop` is set the job is routed through the loop driver (`runLoop`
|
|
23
|
+
* from loop.ts) instead of a single spawn — same driver as `agents run --loop` and
|
|
24
|
+
* workflow `loop:` blocks (issue #400). The optional `deps` parameter provides
|
|
25
|
+
* injectable seams (runIteration, sleep, writeCheckpoint) used by tests; production
|
|
26
|
+
* callers omit it and get the defaults.
|
|
27
|
+
*/
|
|
28
|
+
export declare function executeJob(config: JobConfig, deps?: LoopDeps): Promise<RunResult>;
|
|
20
29
|
/** Spawn a job as a detached process and return immediately with run metadata. */
|
|
21
30
|
export declare function executeJobDetached(config: JobConfig): Promise<RunMeta>;
|
|
22
31
|
/** Extract the final assistant message from a stream-JSON log file as a markdown report. */
|