@phnx-labs/agents-cli 1.20.77 → 1.20.82
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 +589 -0
- package/README.md +27 -25
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +74 -0
- package/dist/commands/doctor.js +718 -40
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -8
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +195 -11
- package/dist/commands/routines.js +16 -4
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +108 -34
- package/dist/commands/sessions-browser.d.ts +45 -3
- package/dist/commands/sessions-browser.js +118 -12
- package/dist/commands/sessions-export.d.ts +3 -0
- package/dist/commands/sessions-export.js +13 -10
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.d.ts +15 -1
- package/dist/commands/sessions-picker.js +90 -7
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +126 -1
- package/dist/commands/sessions.js +365 -42
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +9 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +8 -13
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -23
- package/dist/lib/agents.js +72 -100
- package/dist/lib/auto-pull.d.ts +16 -8
- package/dist/lib/auto-pull.js +23 -28
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +26 -40
- package/dist/lib/daemon.js +63 -123
- package/dist/lib/devices/fleet-divergence.d.ts +101 -0
- package/dist/lib/devices/fleet-divergence.js +188 -0
- package/dist/lib/devices/fleet-inventory.d.ts +19 -0
- package/dist/lib/devices/fleet-inventory.js +57 -0
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/devices/health-report.d.ts +10 -2
- package/dist/lib/devices/health-report.js +32 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.js +35 -8
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +27 -14
- package/dist/lib/git.js +138 -43
- package/dist/lib/hooks.d.ts +42 -0
- package/dist/lib/hooks.js +137 -93
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/remote-cmd.js +2 -0
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +2 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +20 -0
- package/dist/lib/routines.js +6 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +71 -29
- package/dist/lib/sandbox.js +0 -16
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/secrets/agent.d.ts +18 -0
- package/dist/lib/secrets/agent.js +131 -17
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +12 -9
- package/dist/lib/secrets/bundles.js +47 -31
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/scope.d.ts +26 -0
- package/dist/lib/secrets/scope.js +29 -0
- package/dist/lib/secrets/session-store.d.ts +10 -0
- package/dist/lib/secrets/session-store.js +64 -11
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +117 -26
- package/dist/lib/session/active.js +199 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +41 -1
- package/dist/lib/session/db.js +173 -8
- package/dist/lib/session/digest.js +126 -21
- package/dist/lib/session/discover.d.ts +19 -2
- package/dist/lib/session/discover.js +32 -11
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/origin-machine.d.ts +18 -0
- package/dist/lib/session/origin-machine.js +34 -0
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/state.d.ts +1 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/smart-launch.d.ts +86 -0
- package/dist/lib/smart-launch.js +172 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
- package/dist/lib/secrets/account-token.d.ts +0 -20
- package/dist/lib/secrets/account-token.js +0 -64
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness scoping for an unlocked bundle — the shared vocabulary of the broker
|
|
3
|
+
* (agent.ts), the durable session store (session-store.ts), and the read path
|
|
4
|
+
* (bundles.ts).
|
|
5
|
+
*
|
|
6
|
+
* A grant is stored under a scope and read under a scope; the two must agree or
|
|
7
|
+
* the bundle is invisible. `agents secrets unlock --for <agent>` exists to NARROW
|
|
8
|
+
* a grant to one harness, so an unlock without it is global by definition.
|
|
9
|
+
*
|
|
10
|
+
* This module deliberately has NO imports: agent.ts and session-store.ts already
|
|
11
|
+
* import each other, and hanging the scope constants off either one would close
|
|
12
|
+
* that cycle — under ESM a cyclic `const` read can land in the temporal dead zone
|
|
13
|
+
* and throw at runtime even though tsc is happy.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Scope of an unlock that was not narrowed with `--for`: readable by every
|
|
17
|
+
* harness. Not a valid harness name, so it can never collide with one.
|
|
18
|
+
*/
|
|
19
|
+
export declare const GLOBAL_HARNESS = "*";
|
|
20
|
+
/**
|
|
21
|
+
* Scopes a reader consults, most specific first: its own harness, then the global
|
|
22
|
+
* grant. This is the resolution order of the scoped-grant model — a narrow
|
|
23
|
+
* `--for claude` unlock stays claude-only while an unscoped unlock serves
|
|
24
|
+
* everyone — not a fallback papering over a miss.
|
|
25
|
+
*/
|
|
26
|
+
export declare function bundleScopeChain(harness: string | undefined): string[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness scoping for an unlocked bundle — the shared vocabulary of the broker
|
|
3
|
+
* (agent.ts), the durable session store (session-store.ts), and the read path
|
|
4
|
+
* (bundles.ts).
|
|
5
|
+
*
|
|
6
|
+
* A grant is stored under a scope and read under a scope; the two must agree or
|
|
7
|
+
* the bundle is invisible. `agents secrets unlock --for <agent>` exists to NARROW
|
|
8
|
+
* a grant to one harness, so an unlock without it is global by definition.
|
|
9
|
+
*
|
|
10
|
+
* This module deliberately has NO imports: agent.ts and session-store.ts already
|
|
11
|
+
* import each other, and hanging the scope constants off either one would close
|
|
12
|
+
* that cycle — under ESM a cyclic `const` read can land in the temporal dead zone
|
|
13
|
+
* and throw at runtime even though tsc is happy.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Scope of an unlock that was not narrowed with `--for`: readable by every
|
|
17
|
+
* harness. Not a valid harness name, so it can never collide with one.
|
|
18
|
+
*/
|
|
19
|
+
export const GLOBAL_HARNESS = '*';
|
|
20
|
+
/**
|
|
21
|
+
* Scopes a reader consults, most specific first: its own harness, then the global
|
|
22
|
+
* grant. This is the resolution order of the scoped-grant model — a narrow
|
|
23
|
+
* `--for claude` unlock stays claude-only while an unscoped unlock serves
|
|
24
|
+
* everyone — not a fallback papering over a miss.
|
|
25
|
+
*/
|
|
26
|
+
export function bundleScopeChain(harness) {
|
|
27
|
+
const own = harness || GLOBAL_HARNESS;
|
|
28
|
+
return own === GLOBAL_HARNESS ? [GLOBAL_HARNESS] : [own, GLOBAL_HARNESS];
|
|
29
|
+
}
|
|
@@ -74,6 +74,16 @@ export declare function readIndex(): SessionIndex;
|
|
|
74
74
|
export declare function writeIndex(index: SessionIndex): void;
|
|
75
75
|
/** Persist one unlocked bundle: write its blob no-ACL and record it in the index. */
|
|
76
76
|
export declare function saveSession(name: string, entry: SessionEntry): void;
|
|
77
|
+
/**
|
|
78
|
+
* Read a session honoring the scope chain: the caller's own harness first, then
|
|
79
|
+
* the global grant (see bundleScopeChain in scope.ts). This is the durable-store
|
|
80
|
+
* twin of the broker's `get` — both must resolve identically, or a bundle would
|
|
81
|
+
* be readable from RAM but not after a restart.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveSession(name: string, now?: number, harness?: string): {
|
|
84
|
+
entry: SessionEntry;
|
|
85
|
+
harness: string;
|
|
86
|
+
} | null;
|
|
77
87
|
/** Read one session blob by known name. Null when absent/expired/malformed. */
|
|
78
88
|
export declare function loadSession(name: string, now?: number, harness?: string): SessionEntry | null;
|
|
79
89
|
/** Remove every persisted harness grant for one bundle. */
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* persistence is an optimization, not a correctness dependency.
|
|
26
26
|
*/
|
|
27
27
|
import { getKeychainToken, setKeychainToken, deleteKeychainToken, isKeychainBackendOverridden } from './index.js';
|
|
28
|
+
import { GLOBAL_HARNESS, bundleScopeChain } from './scope.js';
|
|
28
29
|
/** Prefix for all durable session items (device-local, no-ACL). */
|
|
29
30
|
export const SESSION_ITEM_PREFIX = 'agents-cli.session.';
|
|
30
31
|
/** Fixed-name index item — the ONLY thing we ever need to find without a known
|
|
@@ -116,7 +117,7 @@ export function saveSession(name, entry) {
|
|
|
116
117
|
if (!shouldPersist())
|
|
117
118
|
return;
|
|
118
119
|
try {
|
|
119
|
-
const harness = entry.harness ||
|
|
120
|
+
const harness = entry.harness || GLOBAL_HARNESS;
|
|
120
121
|
const key = `${harness}:${name}`;
|
|
121
122
|
setKeychainToken(sessionBlobItem(name, harness), JSON.stringify({ ...entry, harness }), { noAcl: true });
|
|
122
123
|
writeIndex(upsertEntry(readIndex(), key, { expiresAt: entry.expiresAt, sleepPersist: entry.sleepPersist, harness }));
|
|
@@ -125,8 +126,22 @@ export function saveSession(name, entry) {
|
|
|
125
126
|
/* best-effort — persistence is an optimization */
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Read a session honoring the scope chain: the caller's own harness first, then
|
|
131
|
+
* the global grant (see bundleScopeChain in scope.ts). This is the durable-store
|
|
132
|
+
* twin of the broker's `get` — both must resolve identically, or a bundle would
|
|
133
|
+
* be readable from RAM but not after a restart.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
|
|
136
|
+
for (const scope of bundleScopeChain(harness)) {
|
|
137
|
+
const entry = loadSession(name, now, scope);
|
|
138
|
+
if (entry)
|
|
139
|
+
return { entry, harness: scope };
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
128
143
|
/** Read one session blob by known name. Null when absent/expired/malformed. */
|
|
129
|
-
export function loadSession(name, now = Date.now(), harness =
|
|
144
|
+
export function loadSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
|
|
130
145
|
if (!shouldPersist())
|
|
131
146
|
return null;
|
|
132
147
|
try {
|
|
@@ -154,7 +169,7 @@ export function deleteBundleSessions(name) {
|
|
|
154
169
|
const scopedName = key.includes(':') ? key.split(':').slice(1).join(':') : key;
|
|
155
170
|
if (scopedName !== name)
|
|
156
171
|
continue;
|
|
157
|
-
const harness = meta.harness ||
|
|
172
|
+
const harness = meta.harness || GLOBAL_HARNESS;
|
|
158
173
|
try {
|
|
159
174
|
deleteKeychainToken(key.includes(':') ? sessionBlobItem(name, harness) : `${SESSION_ITEM_PREFIX}${name}`);
|
|
160
175
|
}
|
|
@@ -164,7 +179,7 @@ export function deleteBundleSessions(name) {
|
|
|
164
179
|
writeIndex(next);
|
|
165
180
|
}
|
|
166
181
|
/** Delete one bundle's session blob and prune it from the index. */
|
|
167
|
-
export function deleteSession(name, harness =
|
|
182
|
+
export function deleteSession(name, harness = GLOBAL_HARNESS) {
|
|
168
183
|
if (!shouldPersist())
|
|
169
184
|
return;
|
|
170
185
|
try {
|
|
@@ -185,7 +200,7 @@ export function deleteAllSessions() {
|
|
|
185
200
|
const bundleName = name.includes(':') ? name.split(':').slice(1).join(':') : name;
|
|
186
201
|
try {
|
|
187
202
|
deleteKeychainToken(name.includes(':')
|
|
188
|
-
? sessionBlobItem(bundleName, meta.harness ||
|
|
203
|
+
? sessionBlobItem(bundleName, meta.harness || GLOBAL_HARNESS)
|
|
189
204
|
: `${SESSION_ITEM_PREFIX}${bundleName}`);
|
|
190
205
|
}
|
|
191
206
|
catch { /* keep going */ }
|
|
@@ -214,23 +229,61 @@ export function rehydrateSessions(now = Date.now()) {
|
|
|
214
229
|
try {
|
|
215
230
|
const raw = getKeychainToken(`${SESSION_ITEM_PREFIX}${key}`);
|
|
216
231
|
const legacy = JSON.parse(raw);
|
|
217
|
-
setKeychainToken(sessionBlobItem(key,
|
|
232
|
+
setKeychainToken(sessionBlobItem(key, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
|
|
218
233
|
deleteKeychainToken(`${SESSION_ITEM_PREFIX}${key}`);
|
|
219
|
-
migratedIndex = upsertEntry(removeEntry(migratedIndex, key),
|
|
234
|
+
migratedIndex = upsertEntry(removeEntry(migratedIndex, key), `${GLOBAL_HARNESS}:${key}`, {
|
|
220
235
|
expiresAt: meta.expiresAt,
|
|
221
236
|
sleepPersist: meta.sleepPersist,
|
|
222
|
-
harness:
|
|
237
|
+
harness: GLOBAL_HARNESS,
|
|
223
238
|
});
|
|
224
239
|
}
|
|
225
240
|
catch { /* malformed/absent legacy entry is pruned below */ }
|
|
226
241
|
}
|
|
242
|
+
// Second source migration: `cli`-scoped grants predate the global scope. `cli`
|
|
243
|
+
// was never a harness — it was the default when no AGENTS_AGENT_NAME was set,
|
|
244
|
+
// i.e. "unlocked from a terminal for general use", which is exactly the global
|
|
245
|
+
// grant. Rewriting them means an unlock a user already paid Touch ID for keeps
|
|
246
|
+
// working after upgrade instead of silently becoming unreadable to every agent.
|
|
247
|
+
for (const [key, meta] of Object.entries(migratedIndex.bundles)) {
|
|
248
|
+
if (!key.startsWith('cli:'))
|
|
249
|
+
continue;
|
|
250
|
+
const bundleName = key.slice('cli:'.length);
|
|
251
|
+
const globalKey = `${GLOBAL_HARNESS}:${bundleName}`;
|
|
252
|
+
// A global grant for this bundle can already exist: the user re-runs
|
|
253
|
+
// `unlock` on the new code (which writes the global scope) before the
|
|
254
|
+
// broker restarts to run this migration. The existing global grant is the
|
|
255
|
+
// NEWER one, so the stale `cli` entry is discarded, never merged over it —
|
|
256
|
+
// overwriting would restore a superseded token and, if the stale entry had
|
|
257
|
+
// expired, hand its expiry to the fresh grant so the prune below deletes a
|
|
258
|
+
// valid unlock outright.
|
|
259
|
+
if (migratedIndex.bundles[globalKey]) {
|
|
260
|
+
try {
|
|
261
|
+
deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
|
|
262
|
+
}
|
|
263
|
+
catch { /* already gone */ }
|
|
264
|
+
migratedIndex = removeEntry(migratedIndex, key);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const raw = getKeychainToken(sessionBlobItem(bundleName, 'cli'));
|
|
269
|
+
const legacy = JSON.parse(raw);
|
|
270
|
+
setKeychainToken(sessionBlobItem(bundleName, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
|
|
271
|
+
deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
|
|
272
|
+
migratedIndex = upsertEntry(removeEntry(migratedIndex, key), globalKey, {
|
|
273
|
+
expiresAt: meta.expiresAt,
|
|
274
|
+
sleepPersist: meta.sleepPersist,
|
|
275
|
+
harness: GLOBAL_HARNESS,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch { /* malformed/absent entry is pruned below */ }
|
|
279
|
+
}
|
|
227
280
|
writeIndex(migratedIndex);
|
|
228
281
|
index.bundles = migratedIndex.bundles;
|
|
229
282
|
const { survivors, expiredNames } = pruneExpired(index, now);
|
|
230
283
|
for (const name of expiredNames) {
|
|
231
284
|
const meta = index.bundles[name];
|
|
232
285
|
try {
|
|
233
|
-
deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness ||
|
|
286
|
+
deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
|
|
234
287
|
}
|
|
235
288
|
catch { /* keep going */ }
|
|
236
289
|
}
|
|
@@ -239,7 +292,7 @@ export function rehydrateSessions(now = Date.now()) {
|
|
|
239
292
|
for (const name of selectRehydratable(survivors, now)) {
|
|
240
293
|
const meta = survivors.bundles[name];
|
|
241
294
|
const bundleName = name.split(':').slice(1).join(':');
|
|
242
|
-
const entry = loadSession(bundleName, now, meta.harness ||
|
|
295
|
+
const entry = loadSession(bundleName, now, meta.harness || GLOBAL_HARNESS);
|
|
243
296
|
if (entry)
|
|
244
297
|
out.push({ name: bundleName, entry });
|
|
245
298
|
}
|
|
@@ -259,7 +312,7 @@ export function pruneSessionsOnSleep() {
|
|
|
259
312
|
for (const name of deletedNames) {
|
|
260
313
|
const meta = readIndex().bundles[name];
|
|
261
314
|
try {
|
|
262
|
-
deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness ||
|
|
315
|
+
deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
|
|
263
316
|
}
|
|
264
317
|
catch { /* keep going */ }
|
|
265
318
|
}
|
package/dist/lib/secrets/sync.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as crypto from 'crypto';
|
|
|
14
14
|
import { deleteKeychainToken, getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from './index.js';
|
|
15
15
|
import { readBundle, writeBundle, keychainItemsForBundle, validateBundleName, } from './bundles.js';
|
|
16
16
|
import { rushSyncBackend } from './drivers/rush.js';
|
|
17
|
-
import {
|
|
17
|
+
import { emitSecretAudit } from './audit.js';
|
|
18
18
|
// PBKDF2 cost. 600k SHA-256 iters matches OWASP 2023+ guidance and keeps a
|
|
19
19
|
// passphrase prompt under a second on the hardware the CLI targets.
|
|
20
20
|
const PBKDF2_ITER = 600_000;
|
|
@@ -190,8 +190,8 @@ export async function pushBundle(name, opts) {
|
|
|
190
190
|
// bundle off-machine — the most sensitive read there is. It bypasses
|
|
191
191
|
// readAndResolveBundleEnv, so audit it explicitly. Values never enter the
|
|
192
192
|
// payload; only the bundle name and how many keys were read.
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
emitSecretAudit({
|
|
194
|
+
event: 'secrets.get',
|
|
195
195
|
bundle: name,
|
|
196
196
|
operation: 'sync push',
|
|
197
197
|
source: 'sync-push',
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { CloudTaskStatus } from '../cloud/types.js';
|
|
2
2
|
import { type PidSessionEntry } from './pid-registry.js';
|
|
3
3
|
import { type HookSessionIndex } from './hook-sessions.js';
|
|
4
|
-
import { type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
|
|
5
|
-
import type
|
|
4
|
+
import { type SessionState, type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
|
|
5
|
+
import { type SessionAttachment } from './types.js';
|
|
6
6
|
import { type SessionProvenance } from './provenance.js';
|
|
7
7
|
import { type DeviceRegistry } from '../devices/registry.js';
|
|
8
8
|
import { type Presence } from './detached.js';
|
|
9
|
+
/**
|
|
10
|
+
* The owner (actor id) to show for a session in `--active`. Prefers the actor
|
|
11
|
+
* recorded on the live-attribution source (the pid registry / teammate record),
|
|
12
|
+
* but falls back to the durable per-session actor sidecar — written at spawn and,
|
|
13
|
+
* unlike the pid entry, NOT overwritten by the SessionStart hook's own by-pid
|
|
14
|
+
* write. Without this fallback a real `agents run` shows no owner whenever the
|
|
15
|
+
* hook's actor-less entry wins the by-pid file (RUSH-2018 fix).
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveOwner(pidActor: string | null | undefined, sessionId: string | undefined): string | undefined;
|
|
9
18
|
/**
|
|
10
19
|
* Per-PID `lsof` probes run bounded and staggered rather than as one parallel
|
|
11
20
|
* fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
|
|
@@ -15,14 +24,27 @@ import { type Presence } from './detached.js';
|
|
|
15
24
|
export declare const LSOF_CONCURRENCY = 4;
|
|
16
25
|
export type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
|
|
17
26
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* you
|
|
23
|
-
*
|
|
27
|
+
* Every status here is COMPUTED by the framework from observable signals — PID
|
|
28
|
+
* liveness and transcript last-write (mtime) — never self-reported by the agent.
|
|
29
|
+
*
|
|
30
|
+
* - `running` / `idle` / `input_required` — a live process's working / stopped
|
|
31
|
+
* / waiting-on-you activity, from its transcript (see {@link computeLiveSignals}).
|
|
32
|
+
* - `queued` — dispatched, work in the pipeline (cloud/headless launch).
|
|
33
|
+
* - `closed` — the PID is dead. The process has exited; report that, don't
|
|
34
|
+
* fabricate `idle` ("done, waiting for you") for a process that is simply gone.
|
|
35
|
+
* - `abandoned` — no transcript write in {@link ABANDONED_STALE_MS} (days): the
|
|
36
|
+
* session is dangling, whether its PID is dead (long gone) or still alive but
|
|
37
|
+
* stuck making no progress. A soft signal — it clears the moment it writes again.
|
|
38
|
+
* - `unknown` — the residual: genuinely NO signal (no PID info AND no file).
|
|
39
|
+
*
|
|
40
|
+
* A LIVE, fresh process is never `unknown` — "the process is alive" is itself a
|
|
41
|
+
* positive signal, so an opaque/unparseable live harness resolves to `running`
|
|
42
|
+
* (its honest floor), and every tracked harness with a locatable, parseable
|
|
43
|
+
* transcript (claude, codex, grok, droid, rush, gemini, kimi, hermes, opencode,
|
|
44
|
+
* antigravity) gets a real working/waiting/idle from its own parser — see
|
|
45
|
+
* {@link computeLiveSignals}, {@link lifecycleStatus} and {@link resolveFallbackStatus}.
|
|
24
46
|
*/
|
|
25
|
-
export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'unknown';
|
|
47
|
+
export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'closed' | 'abandoned' | 'unknown';
|
|
26
48
|
export interface ActiveSession {
|
|
27
49
|
context: ActiveContext;
|
|
28
50
|
kind: string;
|
|
@@ -109,6 +131,14 @@ export interface ActiveSession {
|
|
|
109
131
|
* Absent for cloud sessions (no local pid) and any pid whose env is unreadable.
|
|
110
132
|
*/
|
|
111
133
|
provenance?: SessionProvenance;
|
|
134
|
+
/**
|
|
135
|
+
* Who initiated this session — the resolved actor id stamped at spawn
|
|
136
|
+
* (`resolveActor().id`, read back from the pid registry / teammate record).
|
|
137
|
+
* A tailnet login/email for a resolved human, `UNRESOLVED@<host>` when it
|
|
138
|
+
* couldn't be determined, absent when the launch predates actor stamping.
|
|
139
|
+
* Surfaced as the owner column in `--active` (RUSH-2018).
|
|
140
|
+
*/
|
|
141
|
+
owner?: string;
|
|
112
142
|
/**
|
|
113
143
|
* The machine this session runs on, as a normalized device id (machineId()
|
|
114
144
|
* form). Set when merging cross-machine results so the grouped `--active`
|
|
@@ -172,6 +202,16 @@ export interface ActiveQueryOptions {
|
|
|
172
202
|
* transcript just because a GUI app service with the same basename is alive.
|
|
173
203
|
*/
|
|
174
204
|
export declare const ACTIVE_SESSION_STALE_MS: number;
|
|
205
|
+
/**
|
|
206
|
+
* A session whose transcript hasn't been written in this long is ABANDONED /
|
|
207
|
+
* dangling — the framework can no longer treat it as live work, whether its PID
|
|
208
|
+
* is dead (long gone) or still alive but making no progress (a hung agent).
|
|
209
|
+
* Two days: past a normal work gap, well short of a session legitimately kept
|
|
210
|
+
* open across a long weekend. Deliberately far wider than
|
|
211
|
+
* {@link ACTIVE_SESSION_STALE_MS} (which bounds transcript-borrowing, a different
|
|
212
|
+
* concern) — this is the lifecycle threshold, not the freshness window.
|
|
213
|
+
*/
|
|
214
|
+
export declare const ABANDONED_STALE_MS: number;
|
|
175
215
|
/**
|
|
176
216
|
* Resolve an agent kind from a process's reported executable. `comm` may be an
|
|
177
217
|
* absolute path (shim-launched agents), and Windows image names carry an
|
|
@@ -189,6 +229,13 @@ export declare function agentKindFromComm(commRaw: string): string | undefined;
|
|
|
189
229
|
* the start time can't be read, we keep the existence answer.
|
|
190
230
|
*/
|
|
191
231
|
export declare function isPidAlive(pid: number, startedAtMs?: number): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Resolve a Claude transcript for `cwd` across the given project roots, newest
|
|
234
|
+
* mtime winning when the same id/cwd resolves in more than one version home
|
|
235
|
+
* (the actively-written copy is the newest). Pure over `projectRoots`, so it is
|
|
236
|
+
* testable against temp dirs without touching the real home directory.
|
|
237
|
+
*/
|
|
238
|
+
export declare function pickClaudeSessionFileAcrossRoots(projectRoots: string[], cwd: string, sessionId?: string): string | undefined;
|
|
192
239
|
/**
|
|
193
240
|
* Pick a Claude transcript file within a project dir.
|
|
194
241
|
*
|
|
@@ -212,30 +259,74 @@ export declare function sessionFileTimes(sessionFile: string | undefined): {
|
|
|
212
259
|
birthtimeMs?: number;
|
|
213
260
|
mtimeMs?: number;
|
|
214
261
|
};
|
|
262
|
+
/**
|
|
263
|
+
* The lifecycle status computed purely from the two hard signals the framework
|
|
264
|
+
* always has — PID liveness and transcript last-write (mtime) — independent of
|
|
265
|
+
* anything the agent self-reports or what its last transcript turn happened to
|
|
266
|
+
* look like. Returns the definitive lifecycle label when one applies, or
|
|
267
|
+
* `undefined` when the process is live and fresh (so the caller uses the richer
|
|
268
|
+
* activity-derived status instead).
|
|
269
|
+
*
|
|
270
|
+
* - No write in {@link ABANDONED_STALE_MS} ⇒ `abandoned` (dangling): a
|
|
271
|
+
* days-stale session is not live work, whether its PID is dead (long gone)
|
|
272
|
+
* or still alive but stuck. Checked first — it outranks a bare `closed`.
|
|
273
|
+
* - Otherwise, PID dead ⇒ `closed`: the process has exited. This is the fix
|
|
274
|
+
* for the old "dead PID reports idle" lie — `idle` reads as "done, waiting
|
|
275
|
+
* for you", but a dead process is done, period.
|
|
276
|
+
* - Otherwise (alive + fresh) ⇒ `undefined`: defer to the activity engine.
|
|
277
|
+
*/
|
|
278
|
+
export declare function lifecycleStatus(pidAlive: boolean, mtimeMs: number | undefined, nowMs?: number): ActiveStatus | undefined;
|
|
215
279
|
/**
|
|
216
280
|
* The ONE place a fallback status is decided when no rich transcript state is
|
|
217
|
-
* available —
|
|
218
|
-
*
|
|
219
|
-
*
|
|
281
|
+
* available — an opaque kind we cannot parse (cursor, openclaw), or a transcript
|
|
282
|
+
* whose parse/tail was empty or unreadable. Honest by construction: computed from
|
|
283
|
+
* PID + mtime, never a fabricated `idle`.
|
|
220
284
|
*
|
|
221
|
-
* -
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* UI reads as "done and waiting"), and it never lies as `running` either.
|
|
230
|
-
* - No transcript and the process is not known alive → nothing to report ⇒ `idle`.
|
|
285
|
+
* - Dead PID, or a days-stale transcript ⇒ {@link lifecycleStatus} (`closed` /
|
|
286
|
+
* `abandoned`). A dead process is `closed`, not the old fabricated `idle`
|
|
287
|
+
* (which the UI reads as "done, waiting for you"); a dead process whose file
|
|
288
|
+
* also vanished is still `closed` (death is a definitive answer, not `unknown`).
|
|
289
|
+
* - Live + fresh ⇒ `running`. A live process is, at minimum, running: we may
|
|
290
|
+
* not see WHAT an opaque harness (or an empty tail) is doing, but the process
|
|
291
|
+
* being alive is a positive signal — never a blank `unknown` (the old blanket
|
|
292
|
+
* bug for every non-claude/codex live agent) nor a downgraded `idle`.
|
|
231
293
|
*/
|
|
232
|
-
export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean): ActiveStatus;
|
|
294
|
+
export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean, nowMs?: number): ActiveStatus;
|
|
233
295
|
/**
|
|
234
|
-
* Locate the live transcript for an agent process. Claude files are keyed by
|
|
235
|
-
*
|
|
236
|
-
*
|
|
296
|
+
* Locate the live transcript for an agent process. Claude files are keyed by cwd
|
|
297
|
+
* (+ optional session uuid), so they resolve straight off disk. Every OTHER
|
|
298
|
+
* tracked harness — Codex (date-partitioned), plus grok / droid / rush / gemini /
|
|
299
|
+
* kimi / hermes / opencode / antigravity (per-session dirs, SQLite, single-JSON)
|
|
300
|
+
* — is resolved through the session index by cwd: the newest indexed transcript
|
|
301
|
+
* for that cwd, bounded by ACTIVE_SESSION_STALE_MS so a live pid never borrows a
|
|
302
|
+
* weeks-old transcript. This is what lets a live NON-claude/codex agent get a real
|
|
303
|
+
* status instead of falling through to `unknown` (the file feeds
|
|
304
|
+
* {@link computeLiveSignals}). An opaque kind we don't track (cursor) still yields
|
|
305
|
+
* undefined here and degrades honestly to a live `running`.
|
|
237
306
|
*/
|
|
238
307
|
export declare function findSessionFileForKind(kind: string, cwd?: string, sessionId?: string): string | undefined;
|
|
308
|
+
/** Live per-session signals derived from one transcript-tail read. */
|
|
309
|
+
interface LiveSignals {
|
|
310
|
+
/** Rich inferred state (activity/preview/badges). Undefined when unreadable. */
|
|
311
|
+
state?: SessionState;
|
|
312
|
+
/** Rolling output-token throughput (tokens/sec). Undefined when no usage in the tail. */
|
|
313
|
+
tokPerSec?: number;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Derive the inferred state (working / waiting / idle + preview/badges) and, for
|
|
317
|
+
* the two harnesses whose raw lines carry it, the output-token throughput.
|
|
318
|
+
*
|
|
319
|
+
* Claude/Codex take the fast bounded byte-tail ({@link readSessionTailWithRaw}) —
|
|
320
|
+
* the hot path, and the only two that also yield throughput (their raw lines
|
|
321
|
+
* carry usage the event model drops). EVERY OTHER tracked harness (grok, droid,
|
|
322
|
+
* rush, gemini, kimi, hermes, opencode, antigravity) is parsed with its own
|
|
323
|
+
* parser and run through the SAME {@link inferSessionState}, so a live
|
|
324
|
+
* non-claude/codex agent gets a real working/waiting/idle instead of the blanket
|
|
325
|
+
* `unknown` it used to fall through to. An opaque/untracked kind (cursor) or an
|
|
326
|
+
* unreadable/empty transcript yields an empty signal set, and the caller's
|
|
327
|
+
* {@link resolveFallbackStatus} reports the honest live floor (`running`).
|
|
328
|
+
*/
|
|
329
|
+
export declare function computeLiveSignals(kind: string, sessionFile: string | undefined, cwd: string | undefined, pidAlive: boolean): LiveSignals;
|
|
239
330
|
/** Live teams teammates. Reuses AgentManager which already polls PIDs via `kill -0`. */
|
|
240
331
|
export declare function listTeamsActive(): Promise<ActiveSession[]>;
|
|
241
332
|
/** Live editor-terminal agents across every IDE window. */
|