@phnx-labs/agents-cli 1.20.74 → 1.20.77
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 +1055 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/exec.js +83 -13
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +38 -8
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +286 -15
- package/dist/commands/secrets.js +102 -37
- package/dist/commands/sessions-browser.js +12 -2
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +184 -16
- package/dist/commands/sessions.d.ts +56 -14
- package/dist/commands/sessions.js +322 -62
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +110 -37
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +15 -6
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +24 -4
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +17 -6
- package/dist/lib/activity.js +466 -40
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +70 -6
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.js +4 -0
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +114 -11
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +37 -1
- package/dist/lib/devices/fleet.js +36 -2
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +82 -0
- package/dist/lib/exec.js +218 -6
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +16 -0
- package/dist/lib/hosts/dispatch.js +26 -4
- package/dist/lib/hosts/passthrough.js +9 -2
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +32 -8
- package/dist/lib/hosts/remote-session-id.d.ts +45 -0
- package/dist/lib/hosts/remote-session-id.js +84 -0
- package/dist/lib/hosts/run-target.d.ts +17 -5
- package/dist/lib/hosts/run-target.js +30 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +40 -5
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +2 -0
- package/dist/lib/import.js +35 -2
- package/dist/lib/menubar/install-menubar.d.ts +29 -0
- package/dist/lib/menubar/install-menubar.js +59 -1
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +6 -2
- package/dist/lib/overdue.js +10 -36
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routine-notify.d.ts +76 -0
- package/dist/lib/routine-notify.js +190 -0
- package/dist/lib/routines-placement.d.ts +38 -0
- package/dist/lib/routines-placement.js +79 -0
- package/dist/lib/routines-project.d.ts +97 -0
- package/dist/lib/routines-project.js +349 -0
- package/dist/lib/routines.d.ts +94 -0
- package/dist/lib/routines.js +93 -9
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +242 -20
- package/dist/lib/sandbox.js +10 -0
- package/dist/lib/secrets/account-token.d.ts +20 -0
- package/dist/lib/secrets/account-token.js +64 -0
- package/dist/lib/secrets/agent.d.ts +74 -4
- package/dist/lib/secrets/agent.js +181 -107
- package/dist/lib/secrets/bundles.d.ts +54 -6
- package/dist/lib/secrets/bundles.js +230 -34
- package/dist/lib/secrets/index.d.ts +26 -3
- package/dist/lib/secrets/index.js +42 -12
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +124 -7
- package/dist/lib/session/active.js +390 -56
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +54 -0
- package/dist/lib/session/db.js +320 -19
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +478 -3
- package/dist/lib/session/discover.js +1740 -472
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +141 -32
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/prompt.d.ts +7 -0
- package/dist/lib/session/prompt.js +37 -0
- package/dist/lib/session/provenance.d.ts +7 -0
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +40 -26
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +99 -13
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +20 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +3 -0
- package/dist/lib/state.d.ts +8 -4
- package/dist/lib/state.js +21 -37
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +47 -2
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +9 -2
- package/dist/lib/versions.js +37 -8
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actor provenance -- who initiated a run.
|
|
3
|
+
*
|
|
4
|
+
* One shared account means every session, commit, and event otherwise shows up
|
|
5
|
+
* as the same person. `resolveActor()` answers "which human is behind this run"
|
|
6
|
+
* by looking at how the process was reached:
|
|
7
|
+
*
|
|
8
|
+
* - Over SSH (the shared-fleet case): `tailscale whois` the SSH client IP to
|
|
9
|
+
* the connecting tailnet identity -- a real name + login email.
|
|
10
|
+
* - Locally (non-SSH): we can't honestly say who is at the box, so the id is
|
|
11
|
+
* `UNRESOLVED@<host>` and no personal git identity is claimed.
|
|
12
|
+
* - Inherited: a child spawn trusts the `AGENTS_ACTOR*` env its parent
|
|
13
|
+
* stamped rather than re-resolving, so the whole spawn tree shares one actor.
|
|
14
|
+
*
|
|
15
|
+
* The resolved actor rides the child-process env (`actorEnv()`, merged into
|
|
16
|
+
* `buildExecEnv`). When the actor is a resolved human, that env also carries
|
|
17
|
+
* `GIT_AUTHOR_*` / `GIT_COMMITTER_*`, so the agent's own `git commit` is credited
|
|
18
|
+
* to the person instead of the shared account.
|
|
19
|
+
*
|
|
20
|
+
* The optional `actors:` map in agents.yaml enriches or overrides a resolved
|
|
21
|
+
* identity (pin a work email, add a github handle, mark an entry as an agent).
|
|
22
|
+
*/
|
|
23
|
+
import { spawnSync } from 'child_process';
|
|
24
|
+
import { machineId } from './machine-id.js';
|
|
25
|
+
import { parseSshConnection } from './session/provenance.js';
|
|
26
|
+
import { readMeta } from './state.js';
|
|
27
|
+
/** Hard cap on the whois shell-out — it runs on the spawn hot path. */
|
|
28
|
+
const WHOIS_TIMEOUT_MS = 2000;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the tailnet identity behind an IP via `tailscale whois`. Returns
|
|
31
|
+
* undefined when tailscale is absent, the peer is unknown, the call fails, or it
|
|
32
|
+
* exceeds WHOIS_TIMEOUT_MS -- every one of those falls back to an unresolved
|
|
33
|
+
* actor, never an error and never a hang (a wedged tailscaled is timed out, not
|
|
34
|
+
* waited on, since this sits in buildExecEnv on every agent spawn).
|
|
35
|
+
*/
|
|
36
|
+
function tailscaleWhois(ip) {
|
|
37
|
+
try {
|
|
38
|
+
const res = spawnSync('tailscale', ['whois', '--json', ip], {
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
timeout: WHOIS_TIMEOUT_MS,
|
|
42
|
+
});
|
|
43
|
+
// A timeout leaves status null (child killed by SIGTERM) -- the status guard
|
|
44
|
+
// below treats it as an unresolved actor, same as any other failure.
|
|
45
|
+
if (res.status !== 0 || !res.stdout)
|
|
46
|
+
return undefined;
|
|
47
|
+
const data = JSON.parse(res.stdout);
|
|
48
|
+
const up = data.UserProfile;
|
|
49
|
+
if (!up)
|
|
50
|
+
return undefined;
|
|
51
|
+
return { login: up.LoginName, displayName: up.DisplayName };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Read the actors map from config, tolerant of a missing/unreadable config. */
|
|
58
|
+
function readActors() {
|
|
59
|
+
try {
|
|
60
|
+
return readMeta().actors ?? {};
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Find the actors-map entry for a resolved tailnet login. Matches on an entry's
|
|
68
|
+
* explicit `login`, its map key, or its `email` (all case-insensitive).
|
|
69
|
+
*/
|
|
70
|
+
function findActorConfig(login, actors) {
|
|
71
|
+
const needle = login.toLowerCase();
|
|
72
|
+
for (const [key, cfg] of Object.entries(actors)) {
|
|
73
|
+
const candidates = [cfg.login, key, cfg.email].filter((v) => !!v);
|
|
74
|
+
if (candidates.some((c) => c.toLowerCase() === needle))
|
|
75
|
+
return cfg;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Map a resolved tailnet identity (+ the actors map) to a ResolvedActor. Pure --
|
|
81
|
+
* the impure whois/config reads happen in computeActor -- so the enrich/override
|
|
82
|
+
* path is fully testable. No login (local, or an unnameable peer) yields the
|
|
83
|
+
* honest `UNRESOLVED@<host>`; a login without a config entry still credits git
|
|
84
|
+
* from the tailnet DisplayName + login email.
|
|
85
|
+
*/
|
|
86
|
+
export function actorFromIdentity(who, host, actors) {
|
|
87
|
+
const login = who?.login;
|
|
88
|
+
if (!login) {
|
|
89
|
+
return { id: `UNRESOLVED@${host}`, kind: 'human' };
|
|
90
|
+
}
|
|
91
|
+
const cfg = findActorConfig(login, actors);
|
|
92
|
+
const emailFromLogin = login.includes('@') ? login : undefined;
|
|
93
|
+
return {
|
|
94
|
+
id: login,
|
|
95
|
+
kind: cfg?.kind ?? 'human',
|
|
96
|
+
name: cfg?.name ?? who?.displayName,
|
|
97
|
+
email: cfg?.email ?? emailFromLogin,
|
|
98
|
+
github: cfg?.github,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Reconstruct an actor an ancestor process already resolved into the env. */
|
|
102
|
+
function inheritedActor(env) {
|
|
103
|
+
const id = env.AGENTS_ACTOR;
|
|
104
|
+
if (!id)
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
kind: env.AGENTS_ACTOR_KIND === 'agent' ? 'agent' : 'human',
|
|
109
|
+
name: env.AGENTS_ACTOR_NAME || undefined,
|
|
110
|
+
email: env.AGENTS_ACTOR_EMAIL || undefined,
|
|
111
|
+
github: env.AGENTS_ACTOR_GITHUB || undefined,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Compute the actor for a given environment. Pure with respect to `env` (the
|
|
116
|
+
* only impurity is the `tailscale whois` / config read on the fresh-SSH path),
|
|
117
|
+
* so tests can drive every branch by passing an env explicitly.
|
|
118
|
+
*/
|
|
119
|
+
export function computeActor(env = process.env) {
|
|
120
|
+
const inherited = inheritedActor(env);
|
|
121
|
+
if (inherited)
|
|
122
|
+
return inherited;
|
|
123
|
+
const ssh = env.SSH_CONNECTION ? parseSshConnection(env.SSH_CONNECTION) : undefined;
|
|
124
|
+
const who = ssh?.clientIp ? tailscaleWhois(ssh.clientIp) : undefined;
|
|
125
|
+
return actorFromIdentity(who, machineId(), readActors());
|
|
126
|
+
}
|
|
127
|
+
let cached;
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the actor for the current process, cached for the process lifetime
|
|
130
|
+
* (the SSH `whois` shell-out runs at most once).
|
|
131
|
+
*/
|
|
132
|
+
export function resolveActor() {
|
|
133
|
+
if (!cached)
|
|
134
|
+
cached = computeActor(process.env);
|
|
135
|
+
return cached;
|
|
136
|
+
}
|
|
137
|
+
/** Clear the per-process cache. For tests, and for env changes within a run. */
|
|
138
|
+
export function resetActorCache() {
|
|
139
|
+
cached = undefined;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The env an actor propagates to child processes. Always carries the actor id +
|
|
143
|
+
* kind (so children inherit and don't re-resolve). For a resolved human with a
|
|
144
|
+
* real name and email, it also carries `GIT_AUTHOR_*` / `GIT_COMMITTER_*` so the
|
|
145
|
+
* agent's own commits are credited to the person, not the shared account. An
|
|
146
|
+
* unresolved actor sets no git identity -- git keeps its ambient config.
|
|
147
|
+
*/
|
|
148
|
+
export function actorEnv(actor) {
|
|
149
|
+
const env = {
|
|
150
|
+
AGENTS_ACTOR: actor.id,
|
|
151
|
+
AGENTS_ACTOR_KIND: actor.kind,
|
|
152
|
+
};
|
|
153
|
+
if (actor.name)
|
|
154
|
+
env.AGENTS_ACTOR_NAME = actor.name;
|
|
155
|
+
if (actor.email)
|
|
156
|
+
env.AGENTS_ACTOR_EMAIL = actor.email;
|
|
157
|
+
if (actor.github)
|
|
158
|
+
env.AGENTS_ACTOR_GITHUB = actor.github;
|
|
159
|
+
if (actor.kind === 'human' && actor.name && actor.email) {
|
|
160
|
+
env.GIT_AUTHOR_NAME = actor.name;
|
|
161
|
+
env.GIT_AUTHOR_EMAIL = actor.email;
|
|
162
|
+
env.GIT_COMMITTER_NAME = actor.name;
|
|
163
|
+
env.GIT_COMMITTER_EMAIL = actor.email;
|
|
164
|
+
}
|
|
165
|
+
return env;
|
|
166
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// The production VersionProvider: the only fs/meta-coupled file in the engine.
|
|
2
2
|
// Everything else in this module is pure and testable without it.
|
|
3
|
-
import { listInstalledVersions, getGlobalDefault, getProjectVersion, isVersionInstalled, } from '../versions.js';
|
|
3
|
+
import { listInstalledVersions, getGlobalDefault, getIsolatedDefault, getProjectVersion, isVersionInstalled, } from '../versions.js';
|
|
4
4
|
export const defaultVersionProvider = {
|
|
5
5
|
listInstalled: (agent) => listInstalledVersions(agent),
|
|
6
6
|
getProjectVersion: (agent, cwd) => getProjectVersion(agent, cwd),
|
|
7
7
|
getGlobalDefault: (agent) => getGlobalDefault(agent),
|
|
8
|
+
getIsolatedDefault: (agent) => getIsolatedDefault(agent),
|
|
8
9
|
isInstalled: (agent, version) => isVersionInstalled(agent, version),
|
|
9
10
|
};
|
|
@@ -66,6 +66,15 @@ export function resolveAgentTargets(spec, provider, opts = {}) {
|
|
|
66
66
|
push(agent, glob, 'global-default');
|
|
67
67
|
continue;
|
|
68
68
|
}
|
|
69
|
+
// An isolated-only agent never has a global default — that is the point — so
|
|
70
|
+
// without this step `--agents codex` threw "No default version set" at a user
|
|
71
|
+
// who had explicitly run `agents use codex@<v>`. resolveVersion already had
|
|
72
|
+
// this fallback; the two resolvers had drifted apart.
|
|
73
|
+
const iso = provider.getIsolatedDefault(agent);
|
|
74
|
+
if (iso) {
|
|
75
|
+
push(agent, iso, 'isolated-default');
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
69
78
|
const installed = provider.listInstalled(agent);
|
|
70
79
|
if (installed.length === 0) {
|
|
71
80
|
push(agent, null, 'none');
|
|
@@ -81,13 +90,17 @@ export function resolveAgentTargets(spec, provider, opts = {}) {
|
|
|
81
90
|
}
|
|
82
91
|
throw new AgentSpecError(`No default version set for ${name}. Specify one (${agent}@<version>) or set it: agents use ${agent}@<version>.`, 'no-default', agent, installed);
|
|
83
92
|
}
|
|
84
|
-
// ----- @pinned / @default: the configured global
|
|
93
|
+
// ----- @pinned / @default: the configured default (global, else isolated) -----
|
|
85
94
|
if (qualifier === 'pinned' || qualifier === 'default') {
|
|
86
|
-
const
|
|
95
|
+
const glob = provider.getGlobalDefault(agent);
|
|
96
|
+
// Report which kind it was: an isolated default owns none of the launcher /
|
|
97
|
+
// shim / config-symlink machinery a global default does, and callers that log
|
|
98
|
+
// the source shouldn't claim otherwise.
|
|
99
|
+
const def = glob ?? provider.getIsolatedDefault(agent);
|
|
87
100
|
if (!def) {
|
|
88
101
|
throw new AgentSpecError(`No default version set for ${name}. Run: agents use ${agent}@<version>`, 'no-default', agent, provider.listInstalled(agent));
|
|
89
102
|
}
|
|
90
|
-
push(agent, def, 'global-default(@pinned)');
|
|
103
|
+
push(agent, def, glob ? 'global-default(@pinned)' : 'isolated-default');
|
|
91
104
|
continue;
|
|
92
105
|
}
|
|
93
106
|
// ----- @all: every installed version -----
|
|
@@ -168,7 +181,8 @@ export function resolveListFilter(agent, qualifier, provider, opts = {}) {
|
|
|
168
181
|
const q = qualifier?.trim();
|
|
169
182
|
if (!q || q === 'any')
|
|
170
183
|
return undefined;
|
|
171
|
-
if (q === 'default' || q === 'pinned')
|
|
172
|
-
return provider.getGlobalDefault(agent) ?? undefined;
|
|
184
|
+
if (q === 'default' || q === 'pinned') {
|
|
185
|
+
return provider.getGlobalDefault(agent) ?? provider.getIsolatedDefault(agent) ?? undefined;
|
|
186
|
+
}
|
|
173
187
|
return resolveSingleAgentTarget(`${agent}@${q}`, provider, { ...opts, availableAgents: [agent] }).version;
|
|
174
188
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentId } from '../types.js';
|
|
2
2
|
/** How a resolved version was arrived at — for provenance, display, and error rendering. */
|
|
3
|
-
export type VersionSource = 'explicit' | 'project-pin' | 'global-default' | 'global-default(@pinned)' | 'sole-installed' | 'newest-installed' | 'alias-latest' | 'alias-oldest' | 'none';
|
|
3
|
+
export type VersionSource = 'explicit' | 'project-pin' | 'global-default' | 'isolated-default' | 'global-default(@pinned)' | 'sole-installed' | 'newest-installed' | 'alias-latest' | 'alias-oldest' | 'none';
|
|
4
4
|
export interface AgentTarget {
|
|
5
5
|
agent: AgentId;
|
|
6
6
|
/** Resolved exact version, or null when the agent has no installed versions yet. */
|
|
@@ -32,6 +32,14 @@ export interface VersionProvider {
|
|
|
32
32
|
getProjectVersion(agent: AgentId, cwd: string): string | null;
|
|
33
33
|
/** The configured global default version, or null. */
|
|
34
34
|
getGlobalDefault(agent: AgentId): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* The preferred isolated copy. Kept separate from getGlobalDefault on purpose: a
|
|
37
|
+
* global default owns the launcher, the bare shim and the real ~/.<agent> config
|
|
38
|
+
* symlink, and an isolated version must never acquire any of those. Folding the
|
|
39
|
+
* two together here would hand an isolated version back to every caller that
|
|
40
|
+
* reasonably assumes a global default means "the one that owns the launcher".
|
|
41
|
+
*/
|
|
42
|
+
getIsolatedDefault(agent: AgentId): string | null;
|
|
35
43
|
/** Whether an exact version is installed. */
|
|
36
44
|
isInstalled(agent: AgentId, version: string): boolean;
|
|
37
45
|
}
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -265,6 +265,27 @@ export declare function __resetAntigravityKeychainCacheForTest(): void;
|
|
|
265
265
|
* Sync, no Keychain, no network — safe on the `agents run` hot path.
|
|
266
266
|
*/
|
|
267
267
|
export declare function isClaudeCredentialFileBlank(base: string, platform?: NodeJS.Platform): boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Whether a Claude version home holds its OWN on-disk credential — a non-blank
|
|
270
|
+
* `.claude/.credentials.json` (the store Claude Code writes on login off macOS)
|
|
271
|
+
* or a `.claude/.oauth_token` file (the keychain-less shim fallback, shims.ts).
|
|
272
|
+
*
|
|
273
|
+
* RUSH-1979: the routines daemon injects one ambient `CLAUDE_CODE_OAUTH_TOKEN`
|
|
274
|
+
* (RUSH-1759) so a token-less default account still authenticates. But Claude —
|
|
275
|
+
* and the Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that
|
|
276
|
+
* env var over a pinned account's config dir, so when balanced rotation pins a
|
|
277
|
+
* specific account's `CLAUDE_CONFIG_DIR` the ambient token shadows it: the pool
|
|
278
|
+
* is inert and every fire 401s on the one stale token. A routine spawn drops the
|
|
279
|
+
* ambient token when the pinned account has its own credential (this returns
|
|
280
|
+
* true) so the account authenticates itself; when it does not (the RUSH-1759
|
|
281
|
+
* default), the injected token is kept.
|
|
282
|
+
*
|
|
283
|
+
* Off-darwin only, mirroring {@link isClaudeCredentialFileBlank}: on macOS the
|
|
284
|
+
* login Keychain is the canonical store and probing it raises an auth sheet, and
|
|
285
|
+
* there the daemon injects no token to shadow (broker-only, usually absent), so
|
|
286
|
+
* returning false changes nothing. Sync, no Keychain, no network.
|
|
287
|
+
*/
|
|
288
|
+
export declare function claudeHomeHasOwnCredential(base: string, platform?: NodeJS.Platform): boolean;
|
|
268
289
|
export declare function getAccountInfo(agentId: AgentId, home?: string): Promise<AccountInfo>;
|
|
269
290
|
/**
|
|
270
291
|
* Determine when the agent was last used by checking session file mtimes,
|
package/dist/lib/agents.js
CHANGED
|
@@ -320,8 +320,8 @@ export const AGENTS = {
|
|
|
320
320
|
instructionsFile: 'AGENTS.md',
|
|
321
321
|
format: 'markdown',
|
|
322
322
|
variableSyntax: '$ARGUMENTS',
|
|
323
|
-
supportsHooks:
|
|
324
|
-
capabilities: { hooks:
|
|
323
|
+
supportsHooks: true,
|
|
324
|
+
capabilities: { hooks: { since: '0.3.130' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] },
|
|
325
325
|
},
|
|
326
326
|
openclaw: {
|
|
327
327
|
id: 'openclaw',
|
|
@@ -479,6 +479,8 @@ export const AGENTS = {
|
|
|
479
479
|
// ~/.grok/hooks/ (+ project .grok/hooks/) — events PreToolUse, PostToolUse, etc.
|
|
480
480
|
// Plugins live in ~/.grok/plugins/ with marketplaces. Permissions: --allow/--deny
|
|
481
481
|
// CLI flags or [permission] TOML block in ~/.grok/config.toml.
|
|
482
|
+
// Workflows (native Rhai orchestration) enabled by default as of v0.2.111 —
|
|
483
|
+
// projected into ~/.grok/workflows/<name>.rhai (under GROK_HOME isolation).
|
|
482
484
|
grok: {
|
|
483
485
|
id: 'grok',
|
|
484
486
|
name: 'Grok',
|
|
@@ -487,8 +489,12 @@ export const AGENTS = {
|
|
|
487
489
|
npmPackage: '',
|
|
488
490
|
installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash',
|
|
489
491
|
configDir: path.join(HOME, '.grok'),
|
|
490
|
-
|
|
491
|
-
|
|
492
|
+
// Grok discovers file-based slash commands from ~/.agents/commands/ (the
|
|
493
|
+
// cross-agent compat dir it mirrors from Claude Code) as well as the legacy
|
|
494
|
+
// ~/.claude/commands/ symlink. We write there directly so the per-agent
|
|
495
|
+
// install path and the central user repo stay in sync.
|
|
496
|
+
commandsDir: path.join(HOME, '.agents', 'commands'),
|
|
497
|
+
commandsSubdir: path.join('..', '.agents', 'commands'),
|
|
492
498
|
skillsDir: path.join(HOME, '.grok', 'skills'),
|
|
493
499
|
hooksDir: path.join(HOME, '.grok', 'hooks'),
|
|
494
500
|
instructionsFile: 'AGENTS.md',
|
|
@@ -502,11 +508,13 @@ export const AGENTS = {
|
|
|
502
508
|
mcpHeaders: false,
|
|
503
509
|
allowlist: true, // maps to Grok's granular Bash/Edit/Write/Read/Grep/WebFetch/MCPTool rules
|
|
504
510
|
skills: true,
|
|
505
|
-
commands:
|
|
511
|
+
commands: true, // Grok >= 0.2.111: file-based slash commands from ~/.agents/commands/ (docs) + ~/.claude/commands/ (Claude Code compat)
|
|
506
512
|
plugins: true,
|
|
507
513
|
subagents: true, // ~/.grok/agents/*.md (Claude-compatible agent defs)
|
|
508
514
|
rules: { file: 'AGENTS.md' },
|
|
509
|
-
workflows
|
|
515
|
+
// Native workflows (`.rhai` under ~/.grok/workflows/) shipped on-by-default
|
|
516
|
+
// in v0.2.111 (2026-07-22). See transformWorkflowForGrok in workflows.ts.
|
|
517
|
+
workflows: { since: '0.2.111' },
|
|
510
518
|
memory: true,
|
|
511
519
|
modes: ['plan', 'edit', 'skip'],
|
|
512
520
|
// grok's `--permission-mode plan` silently stalls a headless `-p` run at
|
|
@@ -583,6 +591,24 @@ export const AGENTS = {
|
|
|
583
591
|
// `.factory-plugin/` (it also reads `.claude-plugin/` for compatibility) — set
|
|
584
592
|
// pluginManifestDir so syncPluginToVersion mirrors the manifest into it, the
|
|
585
593
|
// same pattern codex uses with `.codex-plugin`.
|
|
594
|
+
//
|
|
595
|
+
// Workflows / Factory Missions (RUSH-1864, probed droid v0.177.0; ticket cited
|
|
596
|
+
// v0.161.0 — self-updating, no pinnable semver): Factory ships genuine multi-
|
|
597
|
+
// step orchestration ("Missions") via `/missions` (interactive) and
|
|
598
|
+
// `droid exec --mission` (headless; optional `-f <prompt.md>` is a *prompt*
|
|
599
|
+
// file, not a named mission template). Docs:
|
|
600
|
+
// https://docs.factory.ai/cli/features/missions and
|
|
601
|
+
// https://docs.factory.ai/docs/droid-exec/overview (Mission Mode).
|
|
602
|
+
//
|
|
603
|
+
// Mission state may land under `~/.factory/missions/<sessionId>/` at runtime
|
|
604
|
+
// (`getMissionsDir` = join(home, ".factory", "missions") + per-session
|
|
605
|
+
// mission.md / features.json / progress_log.jsonl) — that is **session
|
|
606
|
+
// runtime state**, not a discovery directory of installable named workflows.
|
|
607
|
+
// There is no discoverMission / template registry, and a cold install has no
|
|
608
|
+
// `~/.factory/missions/` until a mission is run. agents-cli's workflows
|
|
609
|
+
// capability means "sync WORKFLOW.md into an auto-discovered dir" — Droid
|
|
610
|
+
// has no such dir, so workflows stays false (invoke-only; do not fabricate a
|
|
611
|
+
// writer target).
|
|
586
612
|
droid: {
|
|
587
613
|
id: 'droid',
|
|
588
614
|
name: 'Droid',
|
|
@@ -615,6 +641,7 @@ export const AGENTS = {
|
|
|
615
641
|
plugins: true,
|
|
616
642
|
subagents: true,
|
|
617
643
|
rules: { file: 'AGENTS.md' },
|
|
644
|
+
// Factory Missions are invoke-only — no installable discovery dir (RUSH-1864).
|
|
618
645
|
workflows: false,
|
|
619
646
|
memory: false,
|
|
620
647
|
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
@@ -1377,6 +1404,43 @@ export function isClaudeCredentialFileBlank(base, platform = process.platform) {
|
|
|
1377
1404
|
return false;
|
|
1378
1405
|
}
|
|
1379
1406
|
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Whether a Claude version home holds its OWN on-disk credential — a non-blank
|
|
1409
|
+
* `.claude/.credentials.json` (the store Claude Code writes on login off macOS)
|
|
1410
|
+
* or a `.claude/.oauth_token` file (the keychain-less shim fallback, shims.ts).
|
|
1411
|
+
*
|
|
1412
|
+
* RUSH-1979: the routines daemon injects one ambient `CLAUDE_CODE_OAUTH_TOKEN`
|
|
1413
|
+
* (RUSH-1759) so a token-less default account still authenticates. But Claude —
|
|
1414
|
+
* and the Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that
|
|
1415
|
+
* env var over a pinned account's config dir, so when balanced rotation pins a
|
|
1416
|
+
* specific account's `CLAUDE_CONFIG_DIR` the ambient token shadows it: the pool
|
|
1417
|
+
* is inert and every fire 401s on the one stale token. A routine spawn drops the
|
|
1418
|
+
* ambient token when the pinned account has its own credential (this returns
|
|
1419
|
+
* true) so the account authenticates itself; when it does not (the RUSH-1759
|
|
1420
|
+
* default), the injected token is kept.
|
|
1421
|
+
*
|
|
1422
|
+
* Off-darwin only, mirroring {@link isClaudeCredentialFileBlank}: on macOS the
|
|
1423
|
+
* login Keychain is the canonical store and probing it raises an auth sheet, and
|
|
1424
|
+
* there the daemon injects no token to shadow (broker-only, usually absent), so
|
|
1425
|
+
* returning false changes nothing. Sync, no Keychain, no network.
|
|
1426
|
+
*/
|
|
1427
|
+
export function claudeHomeHasOwnCredential(base, platform = process.platform) {
|
|
1428
|
+
if (platform === 'darwin')
|
|
1429
|
+
return false;
|
|
1430
|
+
if (fs.existsSync(path.join(base, '.claude', '.oauth_token')))
|
|
1431
|
+
return true;
|
|
1432
|
+
try {
|
|
1433
|
+
const raw = fs.readFileSync(path.join(base, '.claude', '.credentials.json'), 'utf-8');
|
|
1434
|
+
const oauth = JSON.parse(raw).claudeAiOauth;
|
|
1435
|
+
if (!oauth)
|
|
1436
|
+
return false;
|
|
1437
|
+
const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
|
|
1438
|
+
return nonEmpty(oauth.accessToken) || nonEmpty(oauth.refreshToken);
|
|
1439
|
+
}
|
|
1440
|
+
catch {
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1380
1444
|
export async function getAccountInfo(agentId, home) {
|
|
1381
1445
|
const base = home || os.homedir();
|
|
1382
1446
|
const empty = {
|
|
@@ -24,3 +24,5 @@ export declare class CodexCloudProvider implements CloudProvider {
|
|
|
24
24
|
cancel(_taskId: string): Promise<void>;
|
|
25
25
|
message(_taskId: string, _content: string): Promise<void>;
|
|
26
26
|
}
|
|
27
|
+
/** Extract a task ID from codex CLI output (JSON field, key:value line, or UUID pattern). */
|
|
28
|
+
export declare function extractTaskId(output: string): string | undefined;
|
package/dist/lib/cloud/codex.js
CHANGED
|
@@ -127,8 +127,19 @@ export class CodexCloudProvider {
|
|
|
127
127
|
if (code !== 0) {
|
|
128
128
|
throw new Error(`codex cloud exec failed: ${stderr || stdout}`);
|
|
129
129
|
}
|
|
130
|
-
//
|
|
131
|
-
|
|
130
|
+
// The task id is the ONLY handle to the just-created execution — status,
|
|
131
|
+
// list, and the session-index reconcile all key on it. Codex prints it to
|
|
132
|
+
// stdout (JSON or a task_/id: line); some builds route it to stderr, so scan
|
|
133
|
+
// both. A synthetic `codex-<ts>` id was the old fallback here — it can never
|
|
134
|
+
// match the real execution, so it silently broke every follow-up. If the id
|
|
135
|
+
// genuinely can't be parsed, fail loudly and point at `agents cloud list`
|
|
136
|
+
// (which recovers the newest execution) rather than persist a bogus id.
|
|
137
|
+
const taskId = extractTaskId(stdout) ?? extractTaskId(stderr);
|
|
138
|
+
if (!taskId) {
|
|
139
|
+
throw new Error('codex cloud exec did not report a task id — the run may have dispatched, but ' +
|
|
140
|
+
'without the id it cannot be tracked. Find it with `agents cloud list --provider codex`, ' +
|
|
141
|
+
`then \`agents cloud status <id>\`.\nRaw output: ${(stdout || stderr).trim().slice(0, 400)}`);
|
|
142
|
+
}
|
|
132
143
|
const now = new Date().toISOString();
|
|
133
144
|
return {
|
|
134
145
|
id: taskId,
|
|
@@ -232,7 +243,7 @@ export class CodexCloudProvider {
|
|
|
232
243
|
}
|
|
233
244
|
}
|
|
234
245
|
/** Extract a task ID from codex CLI output (JSON field, key:value line, or UUID pattern). */
|
|
235
|
-
function extractTaskId(output) {
|
|
246
|
+
export function extractTaskId(output) {
|
|
236
247
|
// Try JSON first
|
|
237
248
|
try {
|
|
238
249
|
const data = JSON.parse(output);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register cloud-dispatched tasks into the LOCAL session index, keyed by the
|
|
3
|
+
* real execution id, so a launch is mappable to a session immediately.
|
|
4
|
+
*
|
|
5
|
+
* The cloud store (`tasks.db`) and the session index (`sessions` table) were two
|
|
6
|
+
* disjoint stores: a `agents cloud run` wrote only the store, and the session
|
|
7
|
+
* index learned about the run only later, via `discoverCloudSessions` hitting the
|
|
8
|
+
* proxy. Until that discovery ran there was no launch→session mapping at all —
|
|
9
|
+
* the run was orphaned in `agents sessions` exactly like the non-Claude host runs.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors hosts/session-index.ts: the transcript is remote (fetched on
|
|
12
|
+
* demand by session/cloud.ts), so the row is registered with an EMPTY `file_path`
|
|
13
|
+
* — the sentinel db.js's stale-row filter treats as "always live" — and a
|
|
14
|
+
* `[cloud/<status>]` label. The row is written at dispatch and refreshed on every
|
|
15
|
+
* status poll so the label tracks the task's lifecycle. Non-session-agent cloud
|
|
16
|
+
* providers (a task whose agent has no transcript format) are skipped: there is
|
|
17
|
+
* nothing to resolve or resume by id.
|
|
18
|
+
*/
|
|
19
|
+
import type { CloudTask } from './types.js';
|
|
20
|
+
/** Context a cloud task needs to become a session row — the LOCAL dir it was launched from. */
|
|
21
|
+
export interface CloudSessionContext {
|
|
22
|
+
/** Local directory `agents cloud run` was invoked from. Defaults to process.cwd(). */
|
|
23
|
+
cwd?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register (or refresh) a cloud task in the local session index. No-op when the
|
|
27
|
+
* task's agent isn't a known session agent, or its id isn't a usable execution id
|
|
28
|
+
* (never a fabricated `codex-<ts>` — codex.ts fails loud instead of minting one,
|
|
29
|
+
* but guard here too so a bad id can never seed a bogus row). Best-effort: a
|
|
30
|
+
* failed index write must never break the dispatch/poll it rides on.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerCloudSession(task: CloudTask, ctx?: CloudSessionContext): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register cloud-dispatched tasks into the LOCAL session index, keyed by the
|
|
3
|
+
* real execution id, so a launch is mappable to a session immediately.
|
|
4
|
+
*
|
|
5
|
+
* The cloud store (`tasks.db`) and the session index (`sessions` table) were two
|
|
6
|
+
* disjoint stores: a `agents cloud run` wrote only the store, and the session
|
|
7
|
+
* index learned about the run only later, via `discoverCloudSessions` hitting the
|
|
8
|
+
* proxy. Until that discovery ran there was no launch→session mapping at all —
|
|
9
|
+
* the run was orphaned in `agents sessions` exactly like the non-Claude host runs.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors hosts/session-index.ts: the transcript is remote (fetched on
|
|
12
|
+
* demand by session/cloud.ts), so the row is registered with an EMPTY `file_path`
|
|
13
|
+
* — the sentinel db.js's stale-row filter treats as "always live" — and a
|
|
14
|
+
* `[cloud/<status>]` label. The row is written at dispatch and refreshed on every
|
|
15
|
+
* status poll so the label tracks the task's lifecycle. Non-session-agent cloud
|
|
16
|
+
* providers (a task whose agent has no transcript format) are skipped: there is
|
|
17
|
+
* nothing to resolve or resume by id.
|
|
18
|
+
*/
|
|
19
|
+
import { upsertSession } from '../session/db.js';
|
|
20
|
+
import { isSessionTrackedAgent } from '../session/types.js';
|
|
21
|
+
import { deriveShortId } from '../session/short-id.js';
|
|
22
|
+
/** The execution-id charset the session index will accept as a row id. */
|
|
23
|
+
const EXECUTION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
24
|
+
/**
|
|
25
|
+
* Register (or refresh) a cloud task in the local session index. No-op when the
|
|
26
|
+
* task's agent isn't a known session agent, or its id isn't a usable execution id
|
|
27
|
+
* (never a fabricated `codex-<ts>` — codex.ts fails loud instead of minting one,
|
|
28
|
+
* but guard here too so a bad id can never seed a bogus row). Best-effort: a
|
|
29
|
+
* failed index write must never break the dispatch/poll it rides on.
|
|
30
|
+
*/
|
|
31
|
+
export function registerCloudSession(task, ctx = {}) {
|
|
32
|
+
if (!task.agent || !isSessionTrackedAgent(task.agent))
|
|
33
|
+
return;
|
|
34
|
+
if (!task.id || !EXECUTION_ID_RE.test(task.id))
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
upsertSession({
|
|
38
|
+
id: task.id,
|
|
39
|
+
shortId: deriveShortId(task.id),
|
|
40
|
+
agent: task.agent,
|
|
41
|
+
timestamp: task.createdAt,
|
|
42
|
+
lastActivity: task.updatedAt,
|
|
43
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
44
|
+
project: task.repo ?? task.repos?.[0],
|
|
45
|
+
// Remote transcript — no local file yet. Empty file_path is the sentinel
|
|
46
|
+
// the DB stale-filter treats as "always live" (see hosts/session-index.ts).
|
|
47
|
+
filePath: '',
|
|
48
|
+
topic: task.prompt.split('\n')[0]?.slice(0, 120) || undefined,
|
|
49
|
+
// Mirrors session/cloud.ts's discovered label, so the dispatch-time row and
|
|
50
|
+
// a later proxy-discovered row read the same in `agents sessions`.
|
|
51
|
+
label: `[cloud/${task.status}]${task.branch ? ` ${task.branch}` : ''}`,
|
|
52
|
+
prUrl: task.prUrl,
|
|
53
|
+
}, '');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* index write is best-effort; the task is already persisted in the cloud store */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* remote provider each time.
|
|
7
7
|
*/
|
|
8
8
|
import type { CloudTask, CloudProviderId, CloudTaskStatus } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Release the tasks.db connection so the file (and its WAL sidecars) can be
|
|
11
|
+
* removed. The mirror of `closeDB()` in `../session/db.ts`, which this module
|
|
12
|
+
* had no counterpart for — on Windows an open handle makes the file
|
|
13
|
+
* un-unlinkable, so anything tearing down a temp home must close this too.
|
|
14
|
+
*/
|
|
15
|
+
export declare function closeStore(): void;
|
|
9
16
|
/** Persist a task snapshot, replacing any existing row with the same ID. */
|
|
10
17
|
export declare function insertTask(task: CloudTask): void;
|
|
11
18
|
/** Update a task's status and optionally patch summary, PR URL, or branch. */
|
package/dist/lib/cloud/store.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import Database from '../sqlite.js';
|
|
11
11
|
import { getCloudDir } from '../state.js';
|
|
12
|
+
import { registerCloudSession } from './session-index.js';
|
|
12
13
|
const CLOUD_DIR = getCloudDir();
|
|
13
14
|
const DB_PATH = path.join(CLOUD_DIR, 'tasks.db');
|
|
14
15
|
const SCHEMA = `
|
|
@@ -41,12 +42,29 @@ function db() {
|
|
|
41
42
|
_db.exec(SCHEMA);
|
|
42
43
|
return _db;
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Release the tasks.db connection so the file (and its WAL sidecars) can be
|
|
47
|
+
* removed. The mirror of `closeDB()` in `../session/db.ts`, which this module
|
|
48
|
+
* had no counterpart for — on Windows an open handle makes the file
|
|
49
|
+
* un-unlinkable, so anything tearing down a temp home must close this too.
|
|
50
|
+
*/
|
|
51
|
+
export function closeStore() {
|
|
52
|
+
if (_db) {
|
|
53
|
+
_db.close();
|
|
54
|
+
_db = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
44
57
|
/** Persist a task snapshot, replacing any existing row with the same ID. */
|
|
45
58
|
export function insertTask(task) {
|
|
46
59
|
db().prepare(`
|
|
47
60
|
INSERT OR REPLACE INTO tasks (id, provider, status, agent, prompt, repo, branch, pr_url, summary, created_at, updated_at)
|
|
48
61
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
49
62
|
`).run(task.id, task.provider, task.status, task.agent ?? null, task.prompt, task.repo ?? null, task.branch ?? null, task.prUrl ?? null, task.summary ?? null, task.createdAt, task.updatedAt);
|
|
63
|
+
// Every cloud dispatch flows through here, so this is the one chokepoint that
|
|
64
|
+
// reconciles the cloud store with the session index: register a session row
|
|
65
|
+
// keyed by the real execution id so the launch is mappable to a session
|
|
66
|
+
// immediately, not only after a later proxy discovery (see session-index.ts).
|
|
67
|
+
registerCloudSession(task);
|
|
50
68
|
}
|
|
51
69
|
/** Update a task's status and optionally patch summary, PR URL, or branch. */
|
|
52
70
|
export function updateTaskStatus(id, status, extra) {
|
|
@@ -67,6 +85,13 @@ export function updateTaskStatus(id, status, extra) {
|
|
|
67
85
|
}
|
|
68
86
|
params.push(id);
|
|
69
87
|
db().prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
88
|
+
// Keep the session index in lockstep with the store on every poll: refresh the
|
|
89
|
+
// row so its `[cloud/<status>]` label tracks the task's lifecycle (and picks up
|
|
90
|
+
// a newly-opened PR url). Reads the just-written row so the label reflects the
|
|
91
|
+
// update we just made, not a stale snapshot.
|
|
92
|
+
const updated = getTaskById(id);
|
|
93
|
+
if (updated)
|
|
94
|
+
registerCloudSession(updated);
|
|
70
95
|
}
|
|
71
96
|
/** Fetch a single task by its provider-assigned ID, or null if not found locally. */
|
|
72
97
|
export function getTaskById(id) {
|
|
@@ -38,6 +38,10 @@ export function copyDirStrippingAgentsSymlinks(source, dest, agentsDir) {
|
|
|
38
38
|
const inside = agentsDir + path.sep;
|
|
39
39
|
fs.cpSync(source, dest, {
|
|
40
40
|
recursive: true,
|
|
41
|
+
// `force: true` is Node's default, but Bun drops it when a `filter` is supplied —
|
|
42
|
+
// existing files are then silently left alone. `dist/bin/agents` is bun-compiled,
|
|
43
|
+
// so this is a production path, not just a test artifact. State it explicitly.
|
|
44
|
+
force: true,
|
|
41
45
|
filter: (src) => {
|
|
42
46
|
try {
|
|
43
47
|
const st = fs.lstatSync(src);
|
package/dist/lib/daemon.d.ts
CHANGED
|
@@ -81,6 +81,15 @@ export declare function runDaemon(): Promise<void>;
|
|
|
81
81
|
export declare function readDaemonClaudeOAuthToken(opts?: {
|
|
82
82
|
allowPrompt?: boolean;
|
|
83
83
|
}): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* Read the FULL `claude` bundle env — the main `CLAUDE_CODE_OAUTH_TOKEN` plus any
|
|
86
|
+
* per-account `CLAUDE_CODE_OAUTH_TOKEN_<slug>` setup-tokens. Same resolution and
|
|
87
|
+
* never-throws contract as {@link readDaemonClaudeOAuthToken}; returns `{}` when
|
|
88
|
+
* the bundle can't be read (broker-only headless miss, absent bundle, etc.).
|
|
89
|
+
*/
|
|
90
|
+
export declare function readDaemonClaudeBundleEnv(opts?: {
|
|
91
|
+
allowPrompt?: boolean;
|
|
92
|
+
}): Record<string, string>;
|
|
84
93
|
/**
|
|
85
94
|
* Write a launchd plist or systemd unit with owner-only permissions atomically.
|
|
86
95
|
*
|
|
@@ -139,7 +148,7 @@ export declare function ensureDaemonStarted(): {
|
|
|
139
148
|
* the daemon then passes it to every routine run it spawns. An already-set
|
|
140
149
|
* value (e.g. inherited from launchd) is left untouched.
|
|
141
150
|
*/
|
|
142
|
-
export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv,
|
|
151
|
+
export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv, bundleEnv?: Record<string, string>): NodeJS.ProcessEnv;
|
|
143
152
|
/**
|
|
144
153
|
* Resolve how to launch the daemon: `node <entry> __daemon-run`, matching the
|
|
145
154
|
* exact form that works under a direct `__daemon-run`.
|