@phnx-labs/agents-cli 1.22.26 → 1.22.28
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 +293 -0
- package/README.md +80 -27
- package/dist/bin/agents +0 -0
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/doctor.js +24 -10
- package/dist/commands/exec.js +71 -26
- package/dist/commands/hosts.js +4 -3
- package/dist/commands/insights.js +39 -5
- package/dist/commands/output.js +100 -25
- package/dist/commands/perf.d.ts +10 -0
- package/dist/commands/perf.js +14 -6
- package/dist/commands/projects.js +24 -10
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +31 -4
- package/dist/commands/run-account-picker.d.ts +37 -0
- package/dist/commands/run-account-picker.js +101 -17
- package/dist/commands/secrets.js +86 -2
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +73 -7
- package/dist/commands/sessions.js +391 -49
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.js +22 -11
- package/dist/index.js +16 -20
- package/dist/lib/activity.js +45 -99
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +56 -3
- package/dist/lib/cloud/host.js +1 -0
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +15 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/resolve-target.d.ts +1 -0
- package/dist/lib/devices/resolve-target.js +9 -2
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +2 -2
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.js +9 -7
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/git.d.ts +9 -3
- package/dist/lib/git.js +39 -22
- package/dist/lib/hooks/profile.d.ts +12 -1
- package/dist/lib/hooks/profile.js +6 -1
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +14 -7
- package/dist/lib/hosts/dispatch.d.ts +10 -0
- package/dist/lib/hosts/dispatch.js +85 -25
- package/dist/lib/hosts/logs.js +11 -1
- package/dist/lib/hosts/passthrough.d.ts +27 -0
- package/dist/lib/hosts/passthrough.js +19 -10
- package/dist/lib/hosts/progress.d.ts +12 -1
- package/dist/lib/hosts/progress.js +41 -8
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +45 -4
- package/dist/lib/hosts/ready.js +124 -20
- package/dist/lib/hosts/reconcile.d.ts +2 -1
- package/dist/lib/hosts/reconcile.js +24 -7
- package/dist/lib/hosts/reconnect.d.ts +45 -12
- package/dist/lib/hosts/reconnect.js +94 -36
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +4 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +3 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/menubar/snapshot.js +15 -2
- package/dist/lib/models.d.ts +42 -6
- package/dist/lib/models.js +55 -153
- package/dist/lib/perf/db.js +16 -3
- package/dist/lib/perf/types.d.ts +12 -1
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/project-probe.d.ts +22 -3
- package/dist/lib/project-probe.js +105 -17
- package/dist/lib/project-status.d.ts +9 -0
- package/dist/lib/project-status.js +15 -0
- package/dist/lib/redact.js +8 -3
- package/dist/lib/refresh.js +22 -11
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/rotate.d.ts +22 -0
- package/dist/lib/rotate.js +26 -0
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/runner.d.ts +9 -1
- package/dist/lib/runner.js +182 -17
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +19 -3
- package/dist/lib/secrets/agent.js +61 -22
- package/dist/lib/secrets/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +2 -0
- package/dist/lib/secrets/bundles.d.ts +3 -3
- package/dist/lib/secrets/bundles.js +15 -5
- package/dist/lib/secrets/filestore.d.ts +2 -0
- package/dist/lib/secrets/filestore.js +4 -0
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/session-store.d.ts +7 -0
- package/dist/lib/secrets/session-store.js +21 -0
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/active.d.ts +49 -0
- package/dist/lib/session/active.js +175 -11
- package/dist/lib/session/bash-command.d.ts +16 -0
- package/dist/lib/session/bash-command.js +65 -0
- package/dist/lib/session/db.d.ts +23 -2
- package/dist/lib/session/db.js +86 -20
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +124 -32
- package/dist/lib/session/insights.d.ts +19 -0
- package/dist/lib/session/insights.js +78 -10
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-bundle.js +1 -1
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +34 -1
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/staleness/detectors/skills.d.ts +2 -0
- package/dist/lib/staleness/detectors/skills.js +17 -2
- package/dist/lib/staleness/index.d.ts +10 -1
- package/dist/lib/staleness/index.js +28 -3
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/teams/agents.d.ts +1 -0
- package/dist/lib/teams/agents.js +38 -11
- package/dist/lib/teams/placement-probe.d.ts +1 -1
- package/dist/lib/teams/placement-probe.js +24 -24
- package/dist/lib/teams/remoteWorktree.d.ts +11 -7
- package/dist/lib/teams/remoteWorktree.js +29 -27
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/versions.d.ts +1 -0
- package/dist/lib/versions.js +40 -22
- package/package.json +3 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner phone notification on routine FAILURE (RUSH-2288).
|
|
3
|
+
*
|
|
4
|
+
* The desktop lifecycle notifications (routine-notify.ts) never leave the
|
|
5
|
+
* machine, so a failed scheduled routine on a headless fleet box was invisible
|
|
6
|
+
* until someone happened to look. The per-routine prompt pattern (a prompt that
|
|
7
|
+
* ends by shelling `agents notify`) does not close the gap either: when the
|
|
8
|
+
* routine's OWN agent fails to spawn (`auth_failed`) that prompt never runs, so
|
|
9
|
+
* the one failure most worth surfacing is the one that goes unheard.
|
|
10
|
+
*
|
|
11
|
+
* So the DAEMON delivers on failure, from the SAME owner channel stack `agents
|
|
12
|
+
* notify` uses (humans.yaml owner channels, or the legacy `notify.owner`),
|
|
13
|
+
* calling the channel providers IN-PROCESS — no shelling out to `ssh mac-mini
|
|
14
|
+
* agents notify`. When the primary owner channel cannot deliver from this box it
|
|
15
|
+
* walks the remaining configured owner channels as fallbacks (OpenClaw, a second
|
|
16
|
+
* channel, …). Telegram is never used.
|
|
17
|
+
*
|
|
18
|
+
* Only failures notify. A green routine of any kind stays silent — this is an
|
|
19
|
+
* ADDITIONAL failures-only lane, and the desktop thresholds (which still ping a
|
|
20
|
+
* green agent/workflow finish on the local screen) are unchanged. Delivery is
|
|
21
|
+
* deduped per job+runId so a run is announced to the owner at most once.
|
|
22
|
+
*
|
|
23
|
+
* The message BUILDERS (`routineFinishOwnerText` / `routineStartFailedOwnerText`)
|
|
24
|
+
* are pure and unit-tested; `notifyOwnerRoutineFinish` /
|
|
25
|
+
* `notifyOwnerRoutineStartFailed` are the daemon glue that reads config and
|
|
26
|
+
* delivers.
|
|
27
|
+
*/
|
|
28
|
+
import * as os from 'os';
|
|
29
|
+
import { routineKind } from './routine-notify.js';
|
|
30
|
+
import { readMeta } from './state.js';
|
|
31
|
+
import { getOwnerFromHumans } from './humans.js';
|
|
32
|
+
import { readOwnerDest } from './channels/send.js';
|
|
33
|
+
import { registerBuiltinProviders } from './channels/providers/index.js';
|
|
34
|
+
import { lookupTransport } from './channels/resolve.js';
|
|
35
|
+
/** Transports that are Telegram in any form — never delivered to (owner rule). */
|
|
36
|
+
const TELEGRAM_TRANSPORTS = new Set(['telegram', 'openclaw-telegram']);
|
|
37
|
+
/** Human label for the routine body ("agent claude", "workflow deploy", "command"). */
|
|
38
|
+
function routineLabel(r) {
|
|
39
|
+
const kind = routineKind(r);
|
|
40
|
+
if (kind === 'command')
|
|
41
|
+
return 'command';
|
|
42
|
+
if (kind === 'workflow')
|
|
43
|
+
return `workflow ${r.workflow}`;
|
|
44
|
+
return `agent ${r.agent ?? 'unknown'}`;
|
|
45
|
+
}
|
|
46
|
+
/** The failure reason line for a terminal run: timeout / error message / exit code. */
|
|
47
|
+
function failureReason(meta) {
|
|
48
|
+
if (meta.status === 'timeout')
|
|
49
|
+
return 'Timed out';
|
|
50
|
+
if (meta.errorMessage)
|
|
51
|
+
return meta.errorMessage;
|
|
52
|
+
return `Exited with code ${meta.exitCode ?? '?'}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Owner-phone text for a routine FINISH, or null when the run did NOT fail. Only
|
|
56
|
+
* `failed` and `timeout` are failures worth an owner ping — a `completed` run
|
|
57
|
+
* (any kind) is silent, and `running`/`missed` never reach this path from the
|
|
58
|
+
* daemon's finish hook. The text is a short, phone-sized pointer: what failed,
|
|
59
|
+
* why, and on which box (so the owner knows where to look), well under the
|
|
60
|
+
* `user-message-guard` length ceiling.
|
|
61
|
+
*/
|
|
62
|
+
export function routineFinishOwnerText(meta, host) {
|
|
63
|
+
if (meta.status !== 'failed' && meta.status !== 'timeout')
|
|
64
|
+
return null;
|
|
65
|
+
return `Routine failed: ${meta.jobName}\n${failureReason(meta)}\n${routineLabel(meta)} · ${host}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Owner-phone text for a routine that failed to even START (the daemon's
|
|
69
|
+
* pre-spawn catch — `executeJobDetached` threw before a child existed, e.g.
|
|
70
|
+
* `auth_failed`). Never null: a broken start is always a failure worth the ping,
|
|
71
|
+
* for every routine kind, and it is exactly the case the per-routine `agents
|
|
72
|
+
* notify` prompt can never cover because that prompt never ran.
|
|
73
|
+
*/
|
|
74
|
+
export function routineStartFailedOwnerText(config, error, host) {
|
|
75
|
+
return `Routine failed to start: ${config.name}\n${error}\n${routineLabel(config)} · ${host}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* True when a channel would deliver over Telegram. Checks, in order:
|
|
79
|
+
* 1. the channel id itself (`telegram`);
|
|
80
|
+
* 2. the humans.yaml `transport` field on the channel entry (when provided);
|
|
81
|
+
* 3. `notify.transports[id]` remapping (or the id as the default transport name).
|
|
82
|
+
* Any of those landing on a Telegram-delivering provider is excluded.
|
|
83
|
+
*/
|
|
84
|
+
function isTelegramChannel(channelId, meta, humansTransport) {
|
|
85
|
+
if (channelId.trim().toLowerCase() === 'telegram')
|
|
86
|
+
return true;
|
|
87
|
+
if (humansTransport && TELEGRAM_TRANSPORTS.has(humansTransport.trim().toLowerCase())) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const transport = meta.notify?.transports?.[channelId] ?? channelId;
|
|
91
|
+
return TELEGRAM_TRANSPORTS.has(transport.trim().toLowerCase());
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Ordered owner delivery plan for a failure ping: the primary owner destination
|
|
95
|
+
* (the same one `agents notify` resolves) first, then every other configured
|
|
96
|
+
* owner channel as a fallback. Deduped by (channel, to). Telegram channels and
|
|
97
|
+
* intrusive channels (a voice call is too much for a routine failure) are
|
|
98
|
+
* excluded entirely — so an owner whose ONLY channel is Telegram gets an empty
|
|
99
|
+
* plan and no ping, which is the intended "silence beats Telegram" outcome.
|
|
100
|
+
*
|
|
101
|
+
* The same filters apply to the primary and the fallback list — a policy that
|
|
102
|
+
* points normal severity at a voice channel must not auto-call the owner on
|
|
103
|
+
* every routine failure.
|
|
104
|
+
*/
|
|
105
|
+
export function ownerFailureDeliveryPlan(meta) {
|
|
106
|
+
const plan = [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const ownerChannels = getOwnerFromHumans()?.channels ?? [];
|
|
109
|
+
const byId = new Map(ownerChannels.map((ch) => [ch.id, ch]));
|
|
110
|
+
const push = (channel, to) => {
|
|
111
|
+
const c = channel?.trim();
|
|
112
|
+
const t = to?.trim();
|
|
113
|
+
if (!c || !t)
|
|
114
|
+
return;
|
|
115
|
+
const entry = byId.get(c);
|
|
116
|
+
if (entry?.intrusive)
|
|
117
|
+
return;
|
|
118
|
+
if (isTelegramChannel(c, meta, entry?.transport))
|
|
119
|
+
return;
|
|
120
|
+
const key = `${c} ${t}`;
|
|
121
|
+
if (seen.has(key))
|
|
122
|
+
return;
|
|
123
|
+
seen.add(key);
|
|
124
|
+
plan.push({ channel: c, to: t });
|
|
125
|
+
};
|
|
126
|
+
// Primary = the destination `agents notify` would resolve (humans policy →
|
|
127
|
+
// first addressable → legacy notify.owner). Same filters as the fallbacks.
|
|
128
|
+
const primary = readOwnerDest(meta);
|
|
129
|
+
if (primary)
|
|
130
|
+
push(primary.channel, primary.to);
|
|
131
|
+
// Fallbacks = the remaining configured owner channels, in declared order.
|
|
132
|
+
for (const ch of ownerChannels) {
|
|
133
|
+
push(ch.id, ch.to);
|
|
134
|
+
}
|
|
135
|
+
return plan;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Deliver `text` to the owner over the failure plan, in-process, trying each
|
|
139
|
+
* channel until one accepts. Uses the non-dying `lookupTransport` (never
|
|
140
|
+
* `resolveTransport`, which `die()`s the process) so a daemon survives a bad
|
|
141
|
+
* channel name. Best-effort by contract: the daemon wraps this in try/catch, but
|
|
142
|
+
* it also never throws for a delivery failure — it returns the attempt log.
|
|
143
|
+
*/
|
|
144
|
+
export async function deliverOwnerFailure(text, meta) {
|
|
145
|
+
const plan = ownerFailureDeliveryPlan(meta);
|
|
146
|
+
const attempts = [];
|
|
147
|
+
if (plan.length === 0)
|
|
148
|
+
return { delivered: false, attempts };
|
|
149
|
+
registerBuiltinProviders();
|
|
150
|
+
for (const dest of plan) {
|
|
151
|
+
const { provider, error } = lookupTransport(dest.channel, meta);
|
|
152
|
+
if (!provider) {
|
|
153
|
+
attempts.push({ channel: dest.channel, ok: false, error });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const result = await provider.send(text, {
|
|
158
|
+
target: dest.to,
|
|
159
|
+
from: 'routines',
|
|
160
|
+
ownerScoped: true,
|
|
161
|
+
});
|
|
162
|
+
attempts.push({ channel: dest.channel, ok: result.ok, error: result.error });
|
|
163
|
+
if (result.ok)
|
|
164
|
+
return { delivered: true, channel: dest.channel, attempts };
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
attempts.push({ channel: dest.channel, ok: false, error: err.message });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { delivered: false, attempts };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Per-process dedup of failure pings, keyed by job+runId (finishes) or the
|
|
174
|
+
* synthetic start-failed key. A run is announced to the owner at most once even
|
|
175
|
+
* if the finish hook fires twice or a later sweep re-finalizes the same record.
|
|
176
|
+
* Bounded so a long-lived daemon never grows it without limit.
|
|
177
|
+
*/
|
|
178
|
+
const notifiedFailures = new Set();
|
|
179
|
+
const MAX_DEDUP_KEYS = 1000;
|
|
180
|
+
/**
|
|
181
|
+
* Claim a dedup key for an in-flight delivery. Returns false when this
|
|
182
|
+
* job+runId was already delivered (or is already in flight). Callers MUST
|
|
183
|
+
* {@link releaseFailureKey} when delivery fails so a later tick can retry —
|
|
184
|
+
* claiming before send without a release would suppress permanent failures
|
|
185
|
+
* forever (RUSH-2288 review).
|
|
186
|
+
*/
|
|
187
|
+
function claimFailureKey(key) {
|
|
188
|
+
if (notifiedFailures.has(key))
|
|
189
|
+
return false;
|
|
190
|
+
if (notifiedFailures.size >= MAX_DEDUP_KEYS)
|
|
191
|
+
notifiedFailures.clear();
|
|
192
|
+
notifiedFailures.add(key);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
/** Drop a key so a failed delivery can be retried on the next finish hook. */
|
|
196
|
+
function releaseFailureKey(key) {
|
|
197
|
+
notifiedFailures.delete(key);
|
|
198
|
+
}
|
|
199
|
+
/** Test-only: reset the dedup set so cases don't leak state into each other. */
|
|
200
|
+
export function __resetOwnerFailureDedup() {
|
|
201
|
+
notifiedFailures.clear();
|
|
202
|
+
}
|
|
203
|
+
/** A green run / deduped skip attempted no channel. */
|
|
204
|
+
const NO_DELIVERY = { delivered: false, attempts: [] };
|
|
205
|
+
/**
|
|
206
|
+
* Daemon glue: on a routine FINISH, ping the owner IFF the run failed. Green
|
|
207
|
+
* runs return early (no ping). Deduped per job+runId — claimed for the
|
|
208
|
+
* in-flight window so two finish hooks don't double-text, released when no
|
|
209
|
+
* channel accepted so a later sweep can retry. Best-effort.
|
|
210
|
+
*/
|
|
211
|
+
export async function notifyOwnerRoutineFinish(meta) {
|
|
212
|
+
const text = routineFinishOwnerText(meta, os.hostname());
|
|
213
|
+
if (!text)
|
|
214
|
+
return NO_DELIVERY; // green / non-terminal → silent
|
|
215
|
+
const key = `${meta.jobName} ${meta.runId}`;
|
|
216
|
+
if (!claimFailureKey(key))
|
|
217
|
+
return NO_DELIVERY;
|
|
218
|
+
const result = await deliverOwnerFailure(text, readMeta());
|
|
219
|
+
if (!result.delivered)
|
|
220
|
+
releaseFailureKey(key);
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Daemon glue: on a pre-spawn failure (no run record, so no finish hook and no
|
|
225
|
+
* runId), ping the owner. Not deduped — a pre-spawn failure is one scheduled
|
|
226
|
+
* fire on the cron cadence (never a per-second storm), and each such fire is a
|
|
227
|
+
* distinct failure the owner should hear about. Best-effort.
|
|
228
|
+
*/
|
|
229
|
+
export async function notifyOwnerRoutineStartFailed(config, error) {
|
|
230
|
+
const text = routineStartFailedOwnerText(config, error, os.hostname());
|
|
231
|
+
return deliverOwnerFailure(text, readMeta());
|
|
232
|
+
}
|
package/dist/lib/routines.d.ts
CHANGED
|
@@ -313,6 +313,16 @@ export interface RunMeta {
|
|
|
313
313
|
jobName: string;
|
|
314
314
|
runId: string;
|
|
315
315
|
agent?: AgentId;
|
|
316
|
+
/**
|
|
317
|
+
* Resolved agent version this run launched under. Re-pointed to each failover
|
|
318
|
+
* attempt's version as the single-shot chain advances (runner.ts), so it names
|
|
319
|
+
* the version that actually ran and wrote the transcript. Recorded so
|
|
320
|
+
* `archiveRoutineTranscripts` can find the transcript in the per-version home a
|
|
321
|
+
* config-dir-relocating agent (claude/codex) writes to, and so account
|
|
322
|
+
* attribution can name the home that ran (RUSH-2271). Unset for
|
|
323
|
+
* command/self-updating runs.
|
|
324
|
+
*/
|
|
325
|
+
version?: string;
|
|
316
326
|
workflow?: string;
|
|
317
327
|
/** The shell command that ran, for command-mode routines (no agent). */
|
|
318
328
|
command?: string;
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -30,7 +30,15 @@ export declare class RoutineAlreadyRunningError extends Error {
|
|
|
30
30
|
export declare function routineSpawnCwd(config: Pick<JobConfig, 'repo'>, configuredRoot?: string | undefined): string;
|
|
31
31
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|
|
32
32
|
export declare function buildJobCommand(config: JobConfig, resolvedPrompt: string): string[];
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Record every transcript file already present in the child's transcript dirs BEFORE
|
|
35
|
+
* the run spawns. When the transcript lands in a shared per-version home, this baseline
|
|
36
|
+
* is what lets the archiver copy only the file THIS run produced instead of sweeping in
|
|
37
|
+
* every sibling session that home holds (which would mis-tag them all `origin='routine'`
|
|
38
|
+
* under this run's name). Called once per run, before spawn.
|
|
39
|
+
*/
|
|
40
|
+
export declare function snapshotRoutineTranscriptBase(meta: Pick<RunMeta, 'jobName' | 'agent' | 'version'>, runDir: string, overlayHome?: string): void;
|
|
41
|
+
export declare function archiveRoutineTranscripts(meta: Pick<RunMeta, 'jobName' | 'runId' | 'agent' | 'version'>, runDir: string, overlayHome?: string): void;
|
|
34
42
|
/** Pre-flight version/account selection for a routine job. */
|
|
35
43
|
export interface RoutineLaunchPlan {
|
|
36
44
|
/** Ordered attempts: primary first, then same-agent failover accounts. */
|
package/dist/lib/runner.js
CHANGED
|
@@ -18,7 +18,8 @@ import * as fs from 'fs';
|
|
|
18
18
|
import * as path from 'path';
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import { resolveJobPrompt, parseTimeout, writeRunMeta, listRuns, getJobRunsDir, getRunDir, checkJobDeviceEligibility, finalizeRunMeta, } from './routines.js';
|
|
21
|
-
import { getRunsDir } from './state.js';
|
|
21
|
+
import { getRunsDir, getUserAgentsDir } from './state.js';
|
|
22
|
+
import { shortCodexHome } from './codex-home.js';
|
|
22
23
|
import { prepareJobHome, buildSpawnEnv, getJobHomePath } from './sandbox.js';
|
|
23
24
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
24
25
|
import { createTimer, redactPrompt } from './events.js';
|
|
@@ -36,7 +37,7 @@ import { resolveClaudeSetupToken } from './claude-account-token.js';
|
|
|
36
37
|
import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, } from './rotate.js';
|
|
37
38
|
import { readAuthHealth, isDeadVerdict } from './auth-health.js';
|
|
38
39
|
import { machineId } from './machine-id.js';
|
|
39
|
-
import { isSelfUpdatingAgent, ROUTINE_AGENT_COMMANDS as AGENT_COMMANDS } from './agents.js';
|
|
40
|
+
import { isSelfUpdatingAgent, ROUTINE_AGENT_COMMANDS as AGENT_COMMANDS, ROUTINE_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from './agents.js';
|
|
40
41
|
import { expandLocalHome, getProjectRoot } from './project-root.js';
|
|
41
42
|
export class RoutineAlreadyRunningError extends Error {
|
|
42
43
|
constructor(jobName, runId) {
|
|
@@ -309,6 +310,94 @@ function appendModelAndReasoning(cmd, config) {
|
|
|
309
310
|
function generateRunId() {
|
|
310
311
|
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
311
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Agents whose config dir `buildExecEnv` relocates OUT of the sandbox overlay HOME
|
|
315
|
+
* into their per-version home (exec.ts: CLAUDE_CONFIG_DIR / CODEX_HOME). A routine
|
|
316
|
+
* spawned for one of these writes its transcript under the version home, NOT the overlay
|
|
317
|
+
* the sandbox generated — so the archiver has to read it there. Both are version-pinned
|
|
318
|
+
* (never self-updating), so `RunMeta.version` names a real home.
|
|
319
|
+
*
|
|
320
|
+
* Scoped to the agents whose archived transcript the DISCOVERY side can already index as
|
|
321
|
+
* origin='routine' — `readRoutineArchiveMeta` (session/discover.ts) has a branch for
|
|
322
|
+
* claude and codex but not kimi. Kimi also relocates (KIMI_CODE_HOME) and hits the same
|
|
323
|
+
* bug, but archiving it here without a discovery branch would only copy files that are
|
|
324
|
+
* never indexed; adding a kimi reader (its session spans state.json + wire.jsonl under a
|
|
325
|
+
* `session_<uuid>` dir) is the separate follow-up that lets kimi join this set. Muse
|
|
326
|
+
* (XDG, self-updating) and copilot (no transcript spec) are likewise out of scope.
|
|
327
|
+
*/
|
|
328
|
+
const CONFIG_DIR_RELOCATED_AGENTS = new Set(['claude', 'codex']);
|
|
329
|
+
/** Whether this run's transcript lands in a SHARED per-version home (accumulating every
|
|
330
|
+
* session that version+account ever ran) rather than the run's own disposable overlay. */
|
|
331
|
+
function usesSharedTranscriptHome(agent, version) {
|
|
332
|
+
return Boolean(version) && CONFIG_DIR_RELOCATED_AGENTS.has(agent);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The directories the child actually writes a transcript to for one spec — resolved to
|
|
336
|
+
* match `buildExecEnv` (exec.ts), the single decision-point for where the transcript
|
|
337
|
+
* lands, so the archiver can never drift from it. For a config-dir-relocated agent that
|
|
338
|
+
* is the per-version home (`.claude`, `.codex`, …); for everyone else it is the sandbox
|
|
339
|
+
* overlay HOME. Codex may run from a SUN_LEN-safe short home on macOS (codex-home.ts),
|
|
340
|
+
* so both candidates are returned and the caller skips whichever does not exist.
|
|
341
|
+
*/
|
|
342
|
+
function routineTranscriptSourceRoots(agent, version, overlayHome, spec) {
|
|
343
|
+
if (usesSharedTranscriptHome(agent, version)) {
|
|
344
|
+
const roots = [path.join(getVersionHomePath(agent, version), ...spec.root)];
|
|
345
|
+
if (agent === 'codex') {
|
|
346
|
+
roots.push(path.join(shortCodexHome(getUserAgentsDir(), version), ...spec.root.slice(1)));
|
|
347
|
+
}
|
|
348
|
+
return roots;
|
|
349
|
+
}
|
|
350
|
+
return [path.join(overlayHome, ...spec.root)];
|
|
351
|
+
}
|
|
352
|
+
/** Path of the per-run baseline recorded before spawn (see {@link snapshotRoutineTranscriptBase}). */
|
|
353
|
+
function transcriptBasePath(runDir) {
|
|
354
|
+
return path.join(runDir, '.transcript-base.json');
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Record every transcript file already present in the child's transcript dirs BEFORE
|
|
358
|
+
* the run spawns. When the transcript lands in a shared per-version home, this baseline
|
|
359
|
+
* is what lets the archiver copy only the file THIS run produced instead of sweeping in
|
|
360
|
+
* every sibling session that home holds (which would mis-tag them all `origin='routine'`
|
|
361
|
+
* under this run's name). Called once per run, before spawn.
|
|
362
|
+
*/
|
|
363
|
+
export function snapshotRoutineTranscriptBase(meta, runDir, overlayHome) {
|
|
364
|
+
if (!meta.agent)
|
|
365
|
+
return;
|
|
366
|
+
const specs = ROUTINE_TRANSCRIPT_SPECS[meta.agent];
|
|
367
|
+
if (!specs)
|
|
368
|
+
return;
|
|
369
|
+
const home = overlayHome ?? getJobHomePath(meta.jobName);
|
|
370
|
+
const preexisting = new Set();
|
|
371
|
+
for (const spec of specs) {
|
|
372
|
+
for (const root of routineTranscriptSourceRoots(meta.agent, meta.version, home, spec)) {
|
|
373
|
+
if (!fs.existsSync(root))
|
|
374
|
+
continue;
|
|
375
|
+
for (const f of walkForFiles(root, spec.ext, 100_000))
|
|
376
|
+
preexisting.add(f);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
381
|
+
fs.writeFileSync(transcriptBasePath(runDir), JSON.stringify([...preexisting]), { mode: 0o600 });
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// Best-effort: a missing baseline is handled conservatively at archive time
|
|
385
|
+
// (a shared home is skipped rather than bulk-copied).
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/** Read the pre-spawn baseline. `null` means none was recorded (older run or a failed
|
|
389
|
+
* snapshot) — the archiver then refuses to bulk-copy a shared home. */
|
|
390
|
+
function readTranscriptBase(runDir) {
|
|
391
|
+
try {
|
|
392
|
+
const arr = JSON.parse(fs.readFileSync(transcriptBasePath(runDir), 'utf-8'));
|
|
393
|
+
if (Array.isArray(arr))
|
|
394
|
+
return new Set(arr.filter((x) => typeof x === 'string'));
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// No baseline file, or it was unreadable/corrupt.
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
312
401
|
export function archiveRoutineTranscripts(meta, runDir, overlayHome) {
|
|
313
402
|
if (!meta.agent)
|
|
314
403
|
return;
|
|
@@ -316,24 +405,35 @@ export function archiveRoutineTranscripts(meta, runDir, overlayHome) {
|
|
|
316
405
|
if (!specs)
|
|
317
406
|
return;
|
|
318
407
|
const home = overlayHome ?? getJobHomePath(meta.jobName);
|
|
408
|
+
const shared = usesSharedTranscriptHome(meta.agent, meta.version);
|
|
409
|
+
const base = readTranscriptBase(runDir);
|
|
410
|
+
// A shared per-version home holds every session that version+account ran, so without a
|
|
411
|
+
// per-run baseline we cannot tell this run's transcript from a sibling's — copy nothing
|
|
412
|
+
// rather than mis-tag them all. A disposable overlay is this run's alone, so copy all.
|
|
413
|
+
if (shared && base === null)
|
|
414
|
+
return;
|
|
319
415
|
for (const spec of specs) {
|
|
320
|
-
const sourceRoot = path.join(home, ...spec.root);
|
|
321
|
-
if (!fs.existsSync(sourceRoot))
|
|
322
|
-
continue;
|
|
323
416
|
const destRoot = path.join(runDir, 'sessions', meta.agent, spec.root[spec.root.length - 1]);
|
|
324
|
-
for (const
|
|
325
|
-
|
|
326
|
-
if (rel.startsWith('..') || path.isAbsolute(rel))
|
|
417
|
+
for (const sourceRoot of routineTranscriptSourceRoots(meta.agent, meta.version, home, spec)) {
|
|
418
|
+
if (!fs.existsSync(sourceRoot))
|
|
327
419
|
continue;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
420
|
+
for (const sourcePath of walkForFiles(sourceRoot, spec.ext, 100_000)) {
|
|
421
|
+
// Skip files that predate this run (a sibling session in the shared home).
|
|
422
|
+
if (base?.has(sourcePath))
|
|
423
|
+
continue;
|
|
424
|
+
const rel = path.relative(sourceRoot, sourcePath);
|
|
425
|
+
if (rel.startsWith('..') || path.isAbsolute(rel))
|
|
426
|
+
continue;
|
|
427
|
+
const destPath = path.join(destRoot, rel);
|
|
428
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
429
|
+
try {
|
|
430
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
431
|
+
fs.chmodSync(destPath, 0o600);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// The process already reached a terminal state; a concurrently removed
|
|
435
|
+
// transcript should not rewrite that outcome.
|
|
436
|
+
}
|
|
337
437
|
}
|
|
338
438
|
}
|
|
339
439
|
}
|
|
@@ -667,11 +767,53 @@ function injectRoutineActor(env, config) {
|
|
|
667
767
|
env.AGENTS_ACTOR = config.actor;
|
|
668
768
|
return env;
|
|
669
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* `routines add`/`routines edit` refuse to create a routine pinned to a
|
|
772
|
+
* hard-deprecated harness (commands/routines.ts), but that check runs only at
|
|
773
|
+
* CLI-write time. A routine YAML written before the harness was deprecated, or
|
|
774
|
+
* edited directly on disk / synced from another device, has no equivalent gate
|
|
775
|
+
* at fire time — the daemon would happily build and spawn `gemini …` against a
|
|
776
|
+
* backend Google retired. Fail loud here, before any version/account
|
|
777
|
+
* resolution or sandbox prep, so a legacy routine skips with the same message
|
|
778
|
+
* every other entry point (`sync`, `exec`, `import`, `run-cloud`, `versions`)
|
|
779
|
+
* already shows instead of a doomed, confusing spawn failure.
|
|
780
|
+
*/
|
|
781
|
+
function hardDeprecationRunFailure(config, agent) {
|
|
782
|
+
const runId = generateRunId();
|
|
783
|
+
const runDir = getRunDir(config.name, runId);
|
|
784
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
785
|
+
const reason = hardDeprecationError(agent);
|
|
786
|
+
const meta = {
|
|
787
|
+
jobName: config.name,
|
|
788
|
+
runId,
|
|
789
|
+
...runProvenance(config),
|
|
790
|
+
agent,
|
|
791
|
+
pid: null,
|
|
792
|
+
spawnedAt: Date.now(),
|
|
793
|
+
status: 'running',
|
|
794
|
+
startedAt: new Date().toISOString(),
|
|
795
|
+
completedAt: null,
|
|
796
|
+
exitCode: null,
|
|
797
|
+
};
|
|
798
|
+
writeRunMeta(meta);
|
|
799
|
+
process.stderr.write(`[agents] routine ${config.name}: ${reason}\n`);
|
|
800
|
+
finalizeRunMeta(meta, 'failed', 1, { errorMessage: reason });
|
|
801
|
+
writeRunMeta(meta);
|
|
802
|
+
return meta;
|
|
803
|
+
}
|
|
670
804
|
export async function executeJob(config, deps) {
|
|
671
805
|
const eligibility = checkJobDeviceEligibility(config);
|
|
672
806
|
if (eligibility) {
|
|
673
807
|
throw new Error(eligibility.message);
|
|
674
808
|
}
|
|
809
|
+
// Hard-deprecated harness (e.g. gemini): fail loud before placement, version/
|
|
810
|
+
// account resolution, or sandbox prep — local, host, AND cloud placement all
|
|
811
|
+
// share this one gate (a hard-deprecated agent has no `cloudProvider` entry,
|
|
812
|
+
// so cloud placement would otherwise silently fall back to the default
|
|
813
|
+
// provider instead of refusing). See hardDeprecationRunFailure.
|
|
814
|
+
if (!config.workflow && config.agent && ROUTINE_AGENT_IDS.includes(config.agent) && isAgentHardDeprecated(config.agent)) {
|
|
815
|
+
return { meta: hardDeprecationRunFailure(config, config.agent), reportPath: null };
|
|
816
|
+
}
|
|
675
817
|
// Placement (hostStrategy / bare host:) — body may run on another machine
|
|
676
818
|
// over SSH or in the cloud; local version selection / sandbox / spawn then
|
|
677
819
|
// do not apply. Sync callers (manual `routines run`, catchup) follow the
|
|
@@ -724,6 +866,7 @@ export async function executeJob(config, deps) {
|
|
|
724
866
|
runId,
|
|
725
867
|
...runProvenance(config),
|
|
726
868
|
agent: effectiveAgent,
|
|
869
|
+
version: primaryVersion,
|
|
727
870
|
...(config.workflow ? { workflow: config.workflow } : {}),
|
|
728
871
|
pid: null,
|
|
729
872
|
spawnedAt: Date.now(),
|
|
@@ -733,6 +876,9 @@ export async function executeJob(config, deps) {
|
|
|
733
876
|
exitCode: null,
|
|
734
877
|
};
|
|
735
878
|
writeRunMeta(meta);
|
|
879
|
+
// Baseline the child's transcript dirs BEFORE spawning so archiveRoutineTranscripts
|
|
880
|
+
// copies only THIS run's transcript out of a shared per-version home (RUSH-2271).
|
|
881
|
+
snapshotRoutineTranscriptBase(meta, runDir, overlayHome);
|
|
736
882
|
// Auth preflight: if the last live probe rejected this (agent, version)'s
|
|
737
883
|
// token (verdict `revoked`), the run is guaranteed to fail auth — fail fast
|
|
738
884
|
// before spawning instead of producing a doomed run + poisoned report. Cache-
|
|
@@ -808,6 +954,13 @@ export async function executeJob(config, deps) {
|
|
|
808
954
|
if (i === 0) {
|
|
809
955
|
process.stderr.write(`[agents] routine ${config.name}: running ${label}\n`);
|
|
810
956
|
}
|
|
957
|
+
// A rate-limit failover spawns the NEXT chain entry, whose version/account has
|
|
958
|
+
// its own per-version transcript home. Re-point meta.version at the attempt that
|
|
959
|
+
// is about to run and re-baseline that home, so the archiver reads the transcript
|
|
960
|
+
// where THIS attempt writes it — not chain[0]'s home (RUSH-2271). The pre-loop
|
|
961
|
+
// snapshot already covered chain[0]; this makes every later attempt correct too.
|
|
962
|
+
meta.version = attemptVersion;
|
|
963
|
+
snapshotRoutineTranscriptBase(meta, runDir, overlayHome);
|
|
811
964
|
const viaAgentsRun = dispatchesViaAgentsRun(config);
|
|
812
965
|
const cmd = viaAgentsRun
|
|
813
966
|
? baseCmd
|
|
@@ -1151,6 +1304,14 @@ export async function executeJobDetached(config, hooks) {
|
|
|
1151
1304
|
return withRoutineLaunchClaim(config, () => executeJobDetachedClaimed(config, hooks));
|
|
1152
1305
|
}
|
|
1153
1306
|
async function executeJobDetachedClaimed(config, hooks) {
|
|
1307
|
+
// Hard-deprecated harness (e.g. gemini): fail loud before placement, version/
|
|
1308
|
+
// account resolution, or sandbox prep — local, host, AND cloud placement all
|
|
1309
|
+
// share this one gate (a hard-deprecated agent has no `cloudProvider` entry,
|
|
1310
|
+
// so cloud placement would otherwise silently fall back to the default
|
|
1311
|
+
// provider instead of refusing). See hardDeprecationRunFailure.
|
|
1312
|
+
if (!config.workflow && config.agent && ROUTINE_AGENT_IDS.includes(config.agent) && isAgentHardDeprecated(config.agent)) {
|
|
1313
|
+
return hardDeprecationRunFailure(config, config.agent);
|
|
1314
|
+
}
|
|
1154
1315
|
// Placement (hostStrategy / bare host:) — dispatch off-box and return; the
|
|
1155
1316
|
// monitor finalizes host: runs, cloud runs stay terminal when dispatch ends.
|
|
1156
1317
|
// Either way the in-process onFinish hook does not fire for off-box routines
|
|
@@ -1223,6 +1384,7 @@ async function executeJobDetachedClaimed(config, hooks) {
|
|
|
1223
1384
|
runId,
|
|
1224
1385
|
...runProvenance(config),
|
|
1225
1386
|
agent: effectiveAgent,
|
|
1387
|
+
version,
|
|
1226
1388
|
...(config.workflow ? { workflow: config.workflow } : {}),
|
|
1227
1389
|
pid: null,
|
|
1228
1390
|
spawnedAt: Date.now(),
|
|
@@ -1232,6 +1394,9 @@ async function executeJobDetachedClaimed(config, hooks) {
|
|
|
1232
1394
|
completedAt: null,
|
|
1233
1395
|
exitCode: null,
|
|
1234
1396
|
};
|
|
1397
|
+
// Baseline the child's transcript dirs BEFORE spawning so archiveRoutineTranscripts
|
|
1398
|
+
// copies only THIS run's transcript out of a shared per-version home (RUSH-2271).
|
|
1399
|
+
snapshotRoutineTranscriptBase(meta, runDir, overlayHome);
|
|
1235
1400
|
// Auth preflight (mirrors executeJob): with no injected token, a daemon-fired
|
|
1236
1401
|
// Claude routine authenticates via the pinned account's own CLAUDE_CONFIG_DIR
|
|
1237
1402
|
// login. If the last live probe rejected that (agent, version)'s token, the run
|
package/dist/lib/sandbox.d.ts
CHANGED
|
@@ -31,5 +31,3 @@ export declare function symlinkAllowedDirs(overlayHome: string, dirs: string[]):
|
|
|
31
31
|
export declare function generateClaudeConfig(overlayHome: string, config: JobConfig): void;
|
|
32
32
|
/** Generate a Codex config.toml in the overlay with model and approval-mode settings. */
|
|
33
33
|
export declare function generateCodexConfig(overlayHome: string, config: JobConfig): void;
|
|
34
|
-
/** Generate a Gemini settings.json in the overlay from the job's config block. */
|
|
35
|
-
export declare function generateGeminiConfig(overlayHome: string, config: JobConfig): void;
|
package/dist/lib/sandbox.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as os from 'os';
|
|
12
|
-
import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from './gemini-settings.js';
|
|
13
12
|
import { getRoutinesDir, getUserAgentsDir } from './state.js';
|
|
14
13
|
import { safeJoin } from './paths.js';
|
|
15
14
|
import { createLink } from './platform/index.js';
|
|
@@ -91,15 +90,14 @@ export function prepareJobHome(config) {
|
|
|
91
90
|
const overlayHome = getJobHomePath(config.name);
|
|
92
91
|
cleanJobHome(config.name);
|
|
93
92
|
fs.mkdirSync(overlayHome, { recursive: true });
|
|
93
|
+
// gemini is hard-deprecated: runner.ts rejects a gemini routine before it
|
|
94
|
+
// ever reaches prepareJobHome, so there is deliberately no branch here.
|
|
94
95
|
if (config.agent === 'claude') {
|
|
95
96
|
generateClaudeConfig(overlayHome, config);
|
|
96
97
|
}
|
|
97
98
|
else if (config.agent === 'codex') {
|
|
98
99
|
generateCodexConfig(overlayHome, config);
|
|
99
100
|
}
|
|
100
|
-
else if (config.agent === 'gemini') {
|
|
101
|
-
generateGeminiConfig(overlayHome, config);
|
|
102
|
-
}
|
|
103
101
|
else if (config.agent === 'cursor') {
|
|
104
102
|
generateCursorConfig(overlayHome);
|
|
105
103
|
}
|
|
@@ -270,18 +268,3 @@ export function generateCodexConfig(overlayHome, config) {
|
|
|
270
268
|
}
|
|
271
269
|
fs.writeFileSync(path.join(codexDir, 'config.toml'), lines.join('\n') + '\n', 'utf-8');
|
|
272
270
|
}
|
|
273
|
-
/** Generate a Gemini settings.json in the overlay from the job's config block. */
|
|
274
|
-
export function generateGeminiConfig(overlayHome, config) {
|
|
275
|
-
const settingsPath = path.join(overlayHome, '.gemini', 'settings.json');
|
|
276
|
-
updateGeminiSettings(settingsPath, (settings) => {
|
|
277
|
-
if (config.config?.model) {
|
|
278
|
-
settings.model = config.config.model;
|
|
279
|
-
}
|
|
280
|
-
if (config.config) {
|
|
281
|
-
for (const [key, value] of Object.entries(config.config)) {
|
|
282
|
-
settings[key] = value;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
setGeminiAutoUpdateDisabled(settings);
|
|
286
|
-
});
|
|
287
|
-
}
|
|
Binary file
|
|
Binary file
|