@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
package/dist/lib/events.d.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* - Permissions: logs dir is 0700, files are 0600 (owner-only)
|
|
12
12
|
* - Performance tracking: withTiming() wrapper for any async function
|
|
13
13
|
*/
|
|
14
|
+
import { type ActorKind } from './actor.js';
|
|
14
15
|
export type EventLevel = 'audit' | 'warn' | 'info' | 'debug';
|
|
15
|
-
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created' | 'status.posted' | 'file.edited' | 'error' | 'warn' | 'info' | 'debug';
|
|
16
|
+
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.unlocked' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created' | 'status.posted' | 'file.edited' | 'error' | 'warn' | 'info' | 'debug';
|
|
16
17
|
export declare function levelFor(event: EventType): EventLevel;
|
|
17
18
|
export interface EventMeta {
|
|
18
19
|
ts: string;
|
|
@@ -30,6 +31,10 @@ export interface EventMeta {
|
|
|
30
31
|
osUser: string;
|
|
31
32
|
transport: 'local' | 'ssh';
|
|
32
33
|
sshClientIp?: string;
|
|
34
|
+
/** Resolved actor id — which human/agent is behind this event (RUSH-2020). */
|
|
35
|
+
actor?: string;
|
|
36
|
+
/** Actor kind (`human`/`agent`). */
|
|
37
|
+
kind?: ActorKind;
|
|
33
38
|
}
|
|
34
39
|
export interface EventPayload {
|
|
35
40
|
agent?: string;
|
|
@@ -196,6 +201,8 @@ export interface EventStats {
|
|
|
196
201
|
byEvent: Record<string, number>;
|
|
197
202
|
byModule: Record<string, number>;
|
|
198
203
|
byUser: Record<string, number>;
|
|
204
|
+
/** Event counts grouped by resolved actor id (the human/agent behind them). */
|
|
205
|
+
byActor: Record<string, number>;
|
|
199
206
|
fileCount: number;
|
|
200
207
|
totalBytes: number;
|
|
201
208
|
}
|
package/dist/lib/events.js
CHANGED
|
@@ -19,6 +19,7 @@ import { gzipSync, gunzipSync } from 'node:zlib';
|
|
|
19
19
|
import { parseSshConnection } from './session/provenance.js';
|
|
20
20
|
import { ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
21
21
|
import { getUserAgentsDir } from './state.js';
|
|
22
|
+
import { resolveActor } from './actor.js';
|
|
22
23
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
23
24
|
// Resolved lazily: events.ts is imported transitively by most CLI surfaces, and
|
|
24
25
|
// import itself must stay side-effect free. Tests may override the exact path.
|
|
@@ -51,7 +52,7 @@ const DIR_MODE = 0o700;
|
|
|
51
52
|
const FILE_MODE = 0o600;
|
|
52
53
|
const AUDIT_EVENTS = new Set([
|
|
53
54
|
'command.start', 'command.end',
|
|
54
|
-
'secrets.get', 'secrets.set', 'secrets.delete', 'secrets.rename',
|
|
55
|
+
'secrets.get', 'secrets.unlocked', 'secrets.set', 'secrets.delete', 'secrets.rename',
|
|
55
56
|
'teams.create', 'teams.add', 'teams.start', 'teams.complete', 'teams.disband',
|
|
56
57
|
'cloud.dispatch', 'cloud.complete', 'cloud.cancel', 'cloud.message',
|
|
57
58
|
'version.install', 'version.switch', 'version.remove',
|
|
@@ -119,6 +120,7 @@ const SENSITIVE_PAYLOAD_KEY = /password|secret|token|api[-_]?key|auth/i;
|
|
|
119
120
|
const RESERVED_META_KEYS = new Set([
|
|
120
121
|
'ts', 'tz', 'tzName', 'hostname', 'platform', 'arch', 'pid', 'ppid',
|
|
121
122
|
'event', 'level', 'caller', 'session', 'osUser', 'transport', 'sshClientIp',
|
|
123
|
+
'actor', 'kind',
|
|
122
124
|
]);
|
|
123
125
|
function promptMarker(value) {
|
|
124
126
|
const { prompt_length, prompt_sha256 } = redactPrompt(value);
|
|
@@ -276,10 +278,13 @@ function auditOrigin() {
|
|
|
276
278
|
// Container/edge cases where the uid has no passwd entry.
|
|
277
279
|
}
|
|
278
280
|
const ssh = process.env.SSH_CONNECTION ? parseSshConnection(process.env.SSH_CONNECTION) : undefined;
|
|
281
|
+
const actor = resolveActor();
|
|
279
282
|
_origin = {
|
|
280
283
|
osUser,
|
|
281
284
|
transport: ssh ? 'ssh' : 'local',
|
|
282
285
|
...(ssh ? { sshClientIp: ssh.clientIp } : {}),
|
|
286
|
+
actor: actor.id,
|
|
287
|
+
kind: actor.kind,
|
|
283
288
|
};
|
|
284
289
|
return _origin;
|
|
285
290
|
}
|
|
@@ -693,6 +698,7 @@ export function stats(options = {}) {
|
|
|
693
698
|
const byEvent = {};
|
|
694
699
|
const byModule = {};
|
|
695
700
|
const byUser = {};
|
|
701
|
+
const byActor = {};
|
|
696
702
|
for (const r of records) {
|
|
697
703
|
const lvl = r.level ?? levelFor(r.event);
|
|
698
704
|
byLevel[lvl] = (byLevel[lvl] ?? 0) + 1;
|
|
@@ -701,6 +707,8 @@ export function stats(options = {}) {
|
|
|
701
707
|
byModule[r.module] = (byModule[r.module] ?? 0) + 1;
|
|
702
708
|
const user = `${r.osUser ?? '?'}@${r.hostname}`;
|
|
703
709
|
byUser[user] = (byUser[user] ?? 0) + 1;
|
|
710
|
+
if (r.actor)
|
|
711
|
+
byActor[r.actor] = (byActor[r.actor] ?? 0) + 1;
|
|
704
712
|
}
|
|
705
713
|
let fileCount = 0;
|
|
706
714
|
let totalBytes = 0;
|
|
@@ -723,6 +731,7 @@ export function stats(options = {}) {
|
|
|
723
731
|
byEvent,
|
|
724
732
|
byModule,
|
|
725
733
|
byUser,
|
|
734
|
+
byActor,
|
|
726
735
|
fileCount,
|
|
727
736
|
totalBytes,
|
|
728
737
|
};
|
package/dist/lib/exec.js
CHANGED
|
@@ -20,6 +20,7 @@ import { getShimsDir, getHistoryDir } from './state.js';
|
|
|
20
20
|
import { resolveCodexHome } from './codex-home.js';
|
|
21
21
|
import { readCodexConfiguredModel } from './shims.js';
|
|
22
22
|
import { writePidSessionEntry, extractSessionIdArg } from './session/pid-registry.js';
|
|
23
|
+
import { writeSessionActorRecord } from './session/actor-sidecar.js';
|
|
23
24
|
import { loadHookSessionIndex, resolveHookSessionId } from './session/hook-sessions.js';
|
|
24
25
|
import { sessionIdMarkerLine } from './hosts/session-marker.js';
|
|
25
26
|
import { recordRunName } from './session/run-names.js';
|
|
@@ -563,13 +564,6 @@ export const AGENT_COMMANDS = {
|
|
|
563
564
|
},
|
|
564
565
|
modelFlag: '--model',
|
|
565
566
|
},
|
|
566
|
-
forge: {
|
|
567
|
-
base: ['forge'],
|
|
568
|
-
promptFlag: 'positional',
|
|
569
|
-
modeFlags: {
|
|
570
|
-
edit: [],
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
567
|
};
|
|
574
568
|
/**
|
|
575
569
|
* Whether `agent` has a native resume form (Tier 1). Derived solely from the
|
|
@@ -904,15 +898,28 @@ export async function execShimPassthrough(agent, rawArgs, cwd, pinnedVersion) {
|
|
|
904
898
|
// wrapper, not the agent binary — the active scan resolves that by
|
|
905
899
|
// walking the candidate's ancestors (readAncestorSessionEntry).
|
|
906
900
|
if (child.pid) {
|
|
901
|
+
const passthroughSessionId = extractSessionIdArg(rawArgs);
|
|
907
902
|
writePidSessionEntry({
|
|
908
903
|
pid: child.pid,
|
|
909
904
|
agent,
|
|
910
|
-
sessionId:
|
|
905
|
+
sessionId: passthroughSessionId,
|
|
911
906
|
cwd,
|
|
907
|
+
actor: resolveActor().id,
|
|
908
|
+
initiatedBy: resolveActor().kind,
|
|
912
909
|
launchId,
|
|
913
910
|
terminalId: process.env.AGENT_TERMINAL_ID,
|
|
914
911
|
startedAtMs: Date.now(),
|
|
915
912
|
});
|
|
913
|
+
// Durable sessionId -> actor record (RUSH-2019) so the scanner can attribute
|
|
914
|
+
// this session to a person after the pid dies. Best-effort; no-ops without id.
|
|
915
|
+
if (passthroughSessionId) {
|
|
916
|
+
writeSessionActorRecord({
|
|
917
|
+
sessionId: passthroughSessionId,
|
|
918
|
+
actor: resolveActor().id,
|
|
919
|
+
initiatedBy: resolveActor().kind,
|
|
920
|
+
startedAtMs: Date.now(),
|
|
921
|
+
});
|
|
922
|
+
}
|
|
916
923
|
}
|
|
917
924
|
child.on('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
918
925
|
child.on('error', (err) => {
|
|
@@ -1059,6 +1066,8 @@ async function runInTmux(options, executable, args) {
|
|
|
1059
1066
|
agent: options.agent,
|
|
1060
1067
|
sessionId: options.sessionId,
|
|
1061
1068
|
cwd,
|
|
1069
|
+
actor: resolveActor().id,
|
|
1070
|
+
initiatedBy: resolveActor().kind,
|
|
1062
1071
|
// spawnAgent injected AGENT_LAUNCH_ID into options.env before delegating
|
|
1063
1072
|
// here; record the same id so the hook (running under the pane-leaf agent
|
|
1064
1073
|
// pid) reconciles by launchId. This pane's pid usually IS the agent pid,
|
|
@@ -1068,6 +1077,14 @@ async function runInTmux(options, executable, args) {
|
|
|
1068
1077
|
tmuxPane: pane,
|
|
1069
1078
|
startedAtMs: Date.now(),
|
|
1070
1079
|
});
|
|
1080
|
+
if (options.sessionId) {
|
|
1081
|
+
writeSessionActorRecord({
|
|
1082
|
+
sessionId: options.sessionId,
|
|
1083
|
+
actor: resolveActor().id,
|
|
1084
|
+
initiatedBy: resolveActor().kind,
|
|
1085
|
+
startedAtMs: Date.now(),
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1071
1088
|
}
|
|
1072
1089
|
// Recap a dead pane's tail into THIS shell's stderr. The pane-died hook
|
|
1073
1090
|
// detaches the client the instant the agent exits, so a fast failure (a
|
|
@@ -1279,11 +1296,21 @@ async function spawnAgent(options) {
|
|
|
1279
1296
|
agent: options.agent,
|
|
1280
1297
|
sessionId: options.sessionId,
|
|
1281
1298
|
cwd: options.cwd || process.cwd(),
|
|
1299
|
+
actor: resolveActor().id,
|
|
1300
|
+
initiatedBy: resolveActor().kind,
|
|
1282
1301
|
launchId,
|
|
1283
1302
|
terminalId: process.env.AGENT_TERMINAL_ID,
|
|
1284
1303
|
tmuxPane: process.env.TMUX_PANE,
|
|
1285
1304
|
startedAtMs: Date.now(),
|
|
1286
1305
|
});
|
|
1306
|
+
if (options.sessionId) {
|
|
1307
|
+
writeSessionActorRecord({
|
|
1308
|
+
sessionId: options.sessionId,
|
|
1309
|
+
actor: resolveActor().id,
|
|
1310
|
+
initiatedBy: resolveActor().kind,
|
|
1311
|
+
startedAtMs: Date.now(),
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1287
1314
|
// Mark startup time (time from function call to process spawn)
|
|
1288
1315
|
timer.mark('startup');
|
|
1289
1316
|
let budgetKilled = false;
|
package/dist/lib/feed-post.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActivityEvent } from './activity.js';
|
|
1
|
+
import { type ActivityEvent, type Attachment } from './activity.js';
|
|
2
2
|
import { type PidSessionEntry } from './session/pid-registry.js';
|
|
3
3
|
/** Soft cap so a runaway agent can't flood the activity lane with essays. */
|
|
4
4
|
export declare const STATUS_POST_MAX_CHARS = 500;
|
|
@@ -7,8 +7,16 @@ export interface FeedPostInput {
|
|
|
7
7
|
text: string;
|
|
8
8
|
/** Override session id (escape hatch for scripts/tests). Prefer auto-resolve. */
|
|
9
9
|
sessionId?: string;
|
|
10
|
+
/** Generic artifacts to attach: local paths (copied for durability) or URLs. */
|
|
11
|
+
attach?: string[];
|
|
10
12
|
/** Override activity root (tests). */
|
|
11
13
|
activityRoot?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Override the attachments store root (tests). Local files are copied under
|
|
16
|
+
* `<attachmentsRoot>/<sessionId>/<updateId>/`. Defaults to
|
|
17
|
+
* `~/.agents/.history/attachments`.
|
|
18
|
+
*/
|
|
19
|
+
attachmentsRoot?: string;
|
|
12
20
|
/** Override env for identity resolution (tests). */
|
|
13
21
|
env?: NodeJS.ProcessEnv;
|
|
14
22
|
/** Override cwd stamp (defaults to process.cwd() / env). */
|
|
@@ -55,6 +63,25 @@ export declare function resolvePostIdentity(input: Pick<FeedPostInput, 'sessionI
|
|
|
55
63
|
export declare function walkPidRegistry(startPid: number, getParent: (pid: number) => number | undefined, readEntry: (pid: number) => PidSessionEntry | undefined): PidSessionEntry | undefined;
|
|
56
64
|
/** Best-effort parent pid of `pid` (Linux /proc, else `ps`). */
|
|
57
65
|
export declare function parentPidOf(pid: number): number | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Turn one `--attach` token into an {@link Attachment}. Remote URLs become
|
|
68
|
+
* `link` (or a media kind when the extension is unambiguous). Local files are
|
|
69
|
+
* classified by extension and, when `copyRoot` is set, copied under
|
|
70
|
+
* `<copyRoot>/<sessionId>/<updateId>/<basename>` so the link survives a worktree
|
|
71
|
+
* delete — the stored `href` then points at the durable copy. A missing local
|
|
72
|
+
* file is dropped (fail-open) rather than throwing.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildAttachment(token: string, ctx: {
|
|
75
|
+
copyRoot?: string;
|
|
76
|
+
sessionId: string;
|
|
77
|
+
updateId: string;
|
|
78
|
+
}): Attachment | undefined;
|
|
79
|
+
/** Classify + store every `--attach` token; empty/failed tokens are dropped. */
|
|
80
|
+
export declare function buildAttachments(tokens: string[] | undefined, ctx: {
|
|
81
|
+
copyRoot?: string;
|
|
82
|
+
sessionId: string;
|
|
83
|
+
updateId: string;
|
|
84
|
+
}): Attachment[];
|
|
58
85
|
export declare function normalizeStatusText(text: string): string;
|
|
59
86
|
/**
|
|
60
87
|
* Append a `status.posted` milestone for the calling agent.
|
package/dist/lib/feed-post.js
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { spawnSync } from 'child_process';
|
|
18
|
-
import { appendActivityEvent, } from './activity.js';
|
|
18
|
+
import { appendActivityEvent, projectFromCwd, } from './activity.js';
|
|
19
|
+
import { getHistoryDir } from './state.js';
|
|
19
20
|
import { machineId } from './machine-id.js';
|
|
20
21
|
import { isValidMailboxId } from './mailbox.js';
|
|
21
22
|
import { listPidSessionEntries, readPidSessionEntry, } from './session/pid-registry.js';
|
|
@@ -141,6 +142,104 @@ export function parentPidOf(pid) {
|
|
|
141
142
|
}
|
|
142
143
|
return undefined;
|
|
143
144
|
}
|
|
145
|
+
/** Extension → (kind, mediaType) for attachment classification. */
|
|
146
|
+
const MEDIA_BY_EXT = {
|
|
147
|
+
'.png': { kind: 'image', mediaType: 'image/png' },
|
|
148
|
+
'.jpg': { kind: 'image', mediaType: 'image/jpeg' },
|
|
149
|
+
'.jpeg': { kind: 'image', mediaType: 'image/jpeg' },
|
|
150
|
+
'.gif': { kind: 'image', mediaType: 'image/gif' },
|
|
151
|
+
'.svg': { kind: 'image', mediaType: 'image/svg+xml' },
|
|
152
|
+
'.webp': { kind: 'image', mediaType: 'image/webp' },
|
|
153
|
+
'.wav': { kind: 'audio', mediaType: 'audio/wav' },
|
|
154
|
+
'.mp3': { kind: 'audio', mediaType: 'audio/mpeg' },
|
|
155
|
+
'.m4a': { kind: 'audio', mediaType: 'audio/mp4' },
|
|
156
|
+
'.aac': { kind: 'audio', mediaType: 'audio/aac' },
|
|
157
|
+
'.ogg': { kind: 'audio', mediaType: 'audio/ogg' },
|
|
158
|
+
'.flac': { kind: 'audio', mediaType: 'audio/flac' },
|
|
159
|
+
'.mp4': { kind: 'video', mediaType: 'video/mp4' },
|
|
160
|
+
'.mov': { kind: 'video', mediaType: 'video/quicktime' },
|
|
161
|
+
'.webm': { kind: 'video', mediaType: 'video/webm' },
|
|
162
|
+
'.mkv': { kind: 'video', mediaType: 'video/x-matroska' },
|
|
163
|
+
};
|
|
164
|
+
/** True when the token is an http(s) URL (a remote attachment / link). */
|
|
165
|
+
function isRemoteUrl(token) {
|
|
166
|
+
return /^https?:\/\//i.test(token.trim());
|
|
167
|
+
}
|
|
168
|
+
function mediaForExt(ext) {
|
|
169
|
+
const hit = MEDIA_BY_EXT[ext.toLowerCase()];
|
|
170
|
+
return hit ? { kind: hit.kind, mediaType: hit.mediaType } : { kind: 'file' };
|
|
171
|
+
}
|
|
172
|
+
/** A short, filesystem-safe id grouping one post's copied attachments. */
|
|
173
|
+
function newUpdateId(ts) {
|
|
174
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
175
|
+
const stamp = ts.replace(/[^0-9]/g, '').slice(0, 14) || 'post';
|
|
176
|
+
return `${stamp}-${rand}`;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Turn one `--attach` token into an {@link Attachment}. Remote URLs become
|
|
180
|
+
* `link` (or a media kind when the extension is unambiguous). Local files are
|
|
181
|
+
* classified by extension and, when `copyRoot` is set, copied under
|
|
182
|
+
* `<copyRoot>/<sessionId>/<updateId>/<basename>` so the link survives a worktree
|
|
183
|
+
* delete — the stored `href` then points at the durable copy. A missing local
|
|
184
|
+
* file is dropped (fail-open) rather than throwing.
|
|
185
|
+
*/
|
|
186
|
+
export function buildAttachment(token, ctx) {
|
|
187
|
+
const value = token.trim();
|
|
188
|
+
if (!value)
|
|
189
|
+
return undefined;
|
|
190
|
+
if (isRemoteUrl(value)) {
|
|
191
|
+
const ext = path.extname(new URL(value).pathname);
|
|
192
|
+
const media = mediaForExt(ext);
|
|
193
|
+
const att = { kind: media.kind === 'file' ? 'link' : media.kind, href: value };
|
|
194
|
+
const name = path.basename(new URL(value).pathname);
|
|
195
|
+
if (name)
|
|
196
|
+
att.name = name;
|
|
197
|
+
if (media.mediaType)
|
|
198
|
+
att.mediaType = media.mediaType;
|
|
199
|
+
return att;
|
|
200
|
+
}
|
|
201
|
+
const abs = path.resolve(value);
|
|
202
|
+
let stat;
|
|
203
|
+
try {
|
|
204
|
+
stat = fs.statSync(abs);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return undefined; // dropped: local file does not exist
|
|
208
|
+
}
|
|
209
|
+
if (!stat.isFile())
|
|
210
|
+
return undefined;
|
|
211
|
+
const media = mediaForExt(path.extname(abs));
|
|
212
|
+
const name = path.basename(abs);
|
|
213
|
+
let href = abs;
|
|
214
|
+
if (ctx.copyRoot) {
|
|
215
|
+
try {
|
|
216
|
+
const destDir = path.join(ctx.copyRoot, ctx.sessionId, ctx.updateId);
|
|
217
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
218
|
+
const dest = path.join(destDir, name);
|
|
219
|
+
fs.copyFileSync(abs, dest);
|
|
220
|
+
href = dest;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
href = abs; // copy failed: fall back to the original path
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const att = { kind: media.kind, href, name, bytes: stat.size };
|
|
227
|
+
if (media.mediaType)
|
|
228
|
+
att.mediaType = media.mediaType;
|
|
229
|
+
return att;
|
|
230
|
+
}
|
|
231
|
+
/** Classify + store every `--attach` token; empty/failed tokens are dropped. */
|
|
232
|
+
export function buildAttachments(tokens, ctx) {
|
|
233
|
+
if (!tokens?.length)
|
|
234
|
+
return [];
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const token of tokens) {
|
|
237
|
+
const att = buildAttachment(token, ctx);
|
|
238
|
+
if (att)
|
|
239
|
+
out.push(att);
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
144
243
|
export function normalizeStatusText(text) {
|
|
145
244
|
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
146
245
|
if (!collapsed)
|
|
@@ -163,8 +262,15 @@ export function postFeedStatus(input) {
|
|
|
163
262
|
throw new Error('No session id. Run from an agents-cli session '
|
|
164
263
|
+ '(AGENT_SESSION_ID / AGENTS_MAILBOX_DIR / pid registry), or pass --session <id>.');
|
|
165
264
|
}
|
|
265
|
+
const ts = input.ts ?? new Date().toISOString();
|
|
266
|
+
const project = projectFromCwd(identity.cwd);
|
|
267
|
+
const attachments = buildAttachments(input.attach, {
|
|
268
|
+
copyRoot: input.attachmentsRoot ?? path.join(getHistoryDir(), 'attachments'),
|
|
269
|
+
sessionId: identity.sessionId,
|
|
270
|
+
updateId: newUpdateId(ts),
|
|
271
|
+
});
|
|
166
272
|
const event = {
|
|
167
|
-
ts
|
|
273
|
+
ts,
|
|
168
274
|
event: 'status.posted',
|
|
169
275
|
sessionId: identity.sessionId,
|
|
170
276
|
mailboxId: identity.mailboxId,
|
|
@@ -174,10 +280,12 @@ export function postFeedStatus(input) {
|
|
|
174
280
|
agent: identity.agent,
|
|
175
281
|
tool: 'feed.post',
|
|
176
282
|
detail,
|
|
283
|
+
...(project ? { project } : {}),
|
|
177
284
|
...(identity.pid !== undefined ? { pid: identity.pid } : {}),
|
|
178
285
|
...(identity.launchId ? { launchId: identity.launchId } : {}),
|
|
179
286
|
...(identity.terminalId ? { terminalId: identity.terminalId } : {}),
|
|
180
287
|
...(identity.tmuxPane ? { tmuxPane: identity.tmuxPane } : {}),
|
|
288
|
+
...(attachments.length ? { attachments } : {}),
|
|
181
289
|
};
|
|
182
290
|
appendActivityEvent(event, input.activityRoot);
|
|
183
291
|
return {
|
package/dist/lib/fleet/apply.js
CHANGED
|
@@ -12,7 +12,7 @@ import { sshTargetFor } from '../devices/connect.js';
|
|
|
12
12
|
import { readyProbe, bootstrapAgentsCli } from '../hosts/ready.js';
|
|
13
13
|
import { buildRemoteAgentsInvocation } from '../hosts/remote-cmd.js';
|
|
14
14
|
import { sshExec } from '../ssh-exec.js';
|
|
15
|
-
import { isPropagatableAgent, KEYCHAIN_BOUND_ON_MAC, buildAuthBundle, } from './auth-sync.js';
|
|
15
|
+
import { isPropagatableAgent, hasPortableAuthFiles, isCredentialSafeToPropagate, KEYCHAIN_BOUND_ON_MAC, buildAuthBundle, } from './auth-sync.js';
|
|
16
16
|
/** Strip a version suffix from an agent spec: `claude@latest` -> `claude`. */
|
|
17
17
|
export function agentIdOf(spec) {
|
|
18
18
|
return spec.split('@')[0].trim();
|
|
@@ -148,6 +148,13 @@ export function diffFleet(desired, probes, ctx) {
|
|
|
148
148
|
if (canPushLogin(id, probe.platform, ctx.sourceAuth)) {
|
|
149
149
|
rowActions.push({ device: d.device, kind: 'push-login', agent: id, detail: `propagate ${id} login` });
|
|
150
150
|
}
|
|
151
|
+
else if (hasPortableAuthFiles(id) && !isCredentialSafeToPropagate(id)) {
|
|
152
|
+
// Single-use rotating refresh tokens (e.g. droid/WorkOS) must never be
|
|
153
|
+
// copied across boxes — the first refresh invalidates every other copy.
|
|
154
|
+
// Surface per-machine login guidance instead of silently propagating.
|
|
155
|
+
loginBlocked.push(id);
|
|
156
|
+
rowActions.push({ device: d.device, kind: 'needs-login', agent: id, detail: `${id} login uses a single-use rotating refresh token — log in on ${d.device} itself` });
|
|
157
|
+
}
|
|
151
158
|
else if (isPropagatableAgent(id) &&
|
|
152
159
|
(ctx.sourceAuth.bound.has(id) || (probe.platform === 'macos' && KEYCHAIN_BOUND_ON_MAC.has(id)))) {
|
|
153
160
|
// A login-propagation candidate we still can't push: the source token
|
|
@@ -27,6 +27,23 @@ interface AuthFileSpec {
|
|
|
27
27
|
export declare const FLEET_AUTH_FILES: Record<string, AuthFileSpec[]>;
|
|
28
28
|
/** Agents whose macOS credentials live in the ACL-bound login keychain. */
|
|
29
29
|
export declare const KEYCHAIN_BOUND_ON_MAC: ReadonlySet<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Agents whose OAuth credentials rely on single-use refresh tokens that rotate
|
|
32
|
+
* server-side on every exchange. Copying these credential files across machines
|
|
33
|
+
* is fatal: the first refresh on any box invalidates every other holder's token,
|
|
34
|
+
* collapsing the fleet to a single working login (droid/WorkOS collapsed 10 boxes
|
|
35
|
+
* to 1 overnight — RUSH-1958). Add any newly-discovered single-use-rotation
|
|
36
|
+
* harness here; the predicate below is the one place the propagation decision is
|
|
37
|
+
* made. See also `remote-login.ts` and `usage.ts` for the per-machine-login policy.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SINGLE_USE_ROTATING_REFRESH_AGENTS: ReadonlySet<string>;
|
|
40
|
+
/** True when `agent`'s portable credential file(s) are safe to copy between
|
|
41
|
+
* machines. Single-use rotating refresh tokens are never safe; keychain-bound
|
|
42
|
+
* tokens are handled separately by the caller via {@link KEYCHAIN_BOUND_ON_MAC}. */
|
|
43
|
+
export declare function isCredentialSafeToPropagate(agent: string): boolean;
|
|
44
|
+
/** True when the agent stores credentials in portable files we can read. This
|
|
45
|
+
* does NOT mean it is safe to propagate — check {@link isCredentialSafeToPropagate}. */
|
|
46
|
+
export declare function hasPortableAuthFiles(agent: string): boolean;
|
|
30
47
|
/** Which agents `apply` can propagate auth for at all. */
|
|
31
48
|
export declare function isPropagatableAgent(agent: string): boolean;
|
|
32
49
|
/**
|
|
@@ -48,7 +65,7 @@ export interface LoginFlow {
|
|
|
48
65
|
/**
|
|
49
66
|
* The exact command that starts the login flow on the remote box, run as
|
|
50
67
|
* `ssh -tt <box> <loginCommand>`. Bare `<cli>` for agents whose device flow
|
|
51
|
-
* starts on launch (droid, kimi,
|
|
68
|
+
* starts on launch (droid, kimi, antigravity), `<cli> login` for
|
|
52
69
|
* codex/grok, `<cli> auth login` for opencode.
|
|
53
70
|
*/
|
|
54
71
|
loginCommand: string;
|
|
@@ -23,7 +23,6 @@ import * as path from 'path';
|
|
|
23
23
|
export const FLEET_AUTH_FILES = {
|
|
24
24
|
claude: [{ rel: '.claude/.credentials.json', mode: 0o600 }],
|
|
25
25
|
codex: [{ rel: '.codex/auth.json', mode: 0o600 }],
|
|
26
|
-
gemini: [{ rel: '.gemini/oauth_creds.json', mode: 0o600 }],
|
|
27
26
|
grok: [{ rel: '.grok/auth.json', mode: 0o600 }],
|
|
28
27
|
kimi: [{ rel: '.kimi-code/credentials/kimi-code.json', mode: 0o600 }],
|
|
29
28
|
opencode: [{ rel: '.local/share/opencode/auth.json', mode: 0o600 }],
|
|
@@ -35,9 +34,30 @@ export const FLEET_AUTH_FILES = {
|
|
|
35
34
|
};
|
|
36
35
|
/** Agents whose macOS credentials live in the ACL-bound login keychain. */
|
|
37
36
|
export const KEYCHAIN_BOUND_ON_MAC = new Set(['claude', 'antigravity']);
|
|
37
|
+
/**
|
|
38
|
+
* Agents whose OAuth credentials rely on single-use refresh tokens that rotate
|
|
39
|
+
* server-side on every exchange. Copying these credential files across machines
|
|
40
|
+
* is fatal: the first refresh on any box invalidates every other holder's token,
|
|
41
|
+
* collapsing the fleet to a single working login (droid/WorkOS collapsed 10 boxes
|
|
42
|
+
* to 1 overnight — RUSH-1958). Add any newly-discovered single-use-rotation
|
|
43
|
+
* harness here; the predicate below is the one place the propagation decision is
|
|
44
|
+
* made. See also `remote-login.ts` and `usage.ts` for the per-machine-login policy.
|
|
45
|
+
*/
|
|
46
|
+
export const SINGLE_USE_ROTATING_REFRESH_AGENTS = new Set(['droid']);
|
|
47
|
+
/** True when `agent`'s portable credential file(s) are safe to copy between
|
|
48
|
+
* machines. Single-use rotating refresh tokens are never safe; keychain-bound
|
|
49
|
+
* tokens are handled separately by the caller via {@link KEYCHAIN_BOUND_ON_MAC}. */
|
|
50
|
+
export function isCredentialSafeToPropagate(agent) {
|
|
51
|
+
return !SINGLE_USE_ROTATING_REFRESH_AGENTS.has(agent);
|
|
52
|
+
}
|
|
53
|
+
/** True when the agent stores credentials in portable files we can read. This
|
|
54
|
+
* does NOT mean it is safe to propagate — check {@link isCredentialSafeToPropagate}. */
|
|
55
|
+
export function hasPortableAuthFiles(agent) {
|
|
56
|
+
return agent in FLEET_AUTH_FILES;
|
|
57
|
+
}
|
|
38
58
|
/** Which agents `apply` can propagate auth for at all. */
|
|
39
59
|
export function isPropagatableAgent(agent) {
|
|
40
|
-
return agent
|
|
60
|
+
return hasPortableAuthFiles(agent) && isCredentialSafeToPropagate(agent);
|
|
41
61
|
}
|
|
42
62
|
/** The success-file for an agent — the primary portable credential (first spec). */
|
|
43
63
|
function successFileFor(agent) {
|
|
@@ -107,14 +127,6 @@ export const FLEET_LOGIN_FLOWS = {
|
|
|
107
127
|
flowType: 'loopback',
|
|
108
128
|
successFile: successFileFor('antigravity'),
|
|
109
129
|
},
|
|
110
|
-
// gemini: bare `gemini`, Google login on launch. Not characterized as a
|
|
111
|
-
// device-code flow (typically loopback) and no captured pattern — mark unknown
|
|
112
|
-
// so it is flagged non-remotable rather than mis-driven.
|
|
113
|
-
gemini: {
|
|
114
|
-
loginCommand: 'gemini',
|
|
115
|
-
flowType: 'unknown',
|
|
116
|
-
successFile: successFileFor('gemini'),
|
|
117
|
-
},
|
|
118
130
|
// opencode: `opencode auth login`. No captured pattern — unknown/non-remotable.
|
|
119
131
|
opencode: {
|
|
120
132
|
loginCommand: 'opencode auth login',
|
|
@@ -144,7 +156,9 @@ export function snapshotAuth(agents, opts) {
|
|
|
144
156
|
for (const agent of agents) {
|
|
145
157
|
const specs = FLEET_AUTH_FILES[agent];
|
|
146
158
|
if (!specs)
|
|
147
|
-
continue; //
|
|
159
|
+
continue; // no portable file — caller surfaces separately if desired
|
|
160
|
+
if (!isCredentialSafeToPropagate(agent))
|
|
161
|
+
continue; // single-use rotating refresh tokens are never copied
|
|
148
162
|
if (opts.platform === 'darwin' && KEYCHAIN_BOUND_ON_MAC.has(agent)) {
|
|
149
163
|
bound.push(agent);
|
|
150
164
|
continue;
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* each verification URL + user code, and surfacing them all in ONE local browser
|
|
10
10
|
* page so the human enters codes back-to-back instead of babysitting N terminals.
|
|
11
11
|
*
|
|
12
|
+
* The propagation gate lives in `auth-sync.ts`: {@link isCredentialSafeToPropagate}
|
|
13
|
+
* and {@link SINGLE_USE_ROTATING_REFRESH_AGENTS} are the single place where the
|
|
14
|
+
* "is this credential safe to copy?" decision is made, shared by `agents apply`
|
|
15
|
+
* and any other propagation path.
|
|
16
|
+
*
|
|
12
17
|
* Layering — pure vs I/O, so the valuable logic is unit-testable without SSH:
|
|
13
18
|
* - `scrapeLogin` / `classifyLoginFlow` / `selectLoginTargets` /
|
|
14
19
|
* `buildRemoteLoginSshCommand` / `buildDashboardHtml` are PURE.
|
package/dist/lib/git.d.ts
CHANGED
|
@@ -86,6 +86,19 @@ export declare function clonePackage(source: string): Promise<{
|
|
|
86
86
|
}>;
|
|
87
87
|
/** Get the short commit hash (8 chars) of the latest commit in a repo. */
|
|
88
88
|
export declare function getRepoCommit(repoPath: string): Promise<string>;
|
|
89
|
+
/** Compact, self-contained state of a git repo — branch, short HEAD, and a
|
|
90
|
+
* dirty flag — for cross-device comparison (RUSH-2027). Synchronous and
|
|
91
|
+
* best-effort: a non-repo or unreadable path yields `null` fields, never a
|
|
92
|
+
* throw, so a device's `doctor --json` payload always serializes. */
|
|
93
|
+
export interface RepoStateSnapshot {
|
|
94
|
+
branch: string | null;
|
|
95
|
+
head: string | null;
|
|
96
|
+
dirty: boolean;
|
|
97
|
+
}
|
|
98
|
+
/** Read {@link RepoStateSnapshot} for `repoPath` using plumbing commands so the
|
|
99
|
+
* result is stable across git versions and never mutates the tree. Returns null
|
|
100
|
+
* when the path is not a git worktree. */
|
|
101
|
+
export declare function readRepoState(repoPath: string): RepoStateSnapshot | null;
|
|
89
102
|
/**
|
|
90
103
|
* Get the current GitHub username using gh CLI.
|
|
91
104
|
* Returns null if gh is not installed or user is not authenticated.
|
|
@@ -235,20 +248,6 @@ export declare function displayHomePath(dir: string): string;
|
|
|
235
248
|
* branch reconciles instead of failing with "Need to specify how to reconcile
|
|
236
249
|
* divergent branches".
|
|
237
250
|
*/
|
|
238
|
-
/**
|
|
239
|
-
* Auto-commit the machine's OWN generated per-device meta file
|
|
240
|
-
* (`devices/<machineId>/agents.yaml`) if it's the dirty state blocking a pull.
|
|
241
|
-
*
|
|
242
|
-
* That file is committed + synced (so every machine can introspect every other
|
|
243
|
-
* machine's pins), but each box rewrites its own copy whenever a pin changes —
|
|
244
|
-
* leaving the tree perpetually dirty and wedging `agents repo pull` (which
|
|
245
|
-
* refuses a dirty tree). This durably commits just that one path (explicit
|
|
246
|
-
* pathspec) so the pull can proceed; genuine user edits to OTHER files are left
|
|
247
|
-
* untouched and still (correctly) block the pull. No-op when the meta path isn't
|
|
248
|
-
* inside `dir` (system/extra repos) or isn't dirty. Returns the committed rel
|
|
249
|
-
* path, or null. `metaAbs` is injectable for tests; defaults to the live path.
|
|
250
|
-
*/
|
|
251
|
-
export declare function commitOwnDeviceMeta(dir: string, metaAbs?: string): Promise<string | null>;
|
|
252
251
|
export declare function pullRepo(dir: string): Promise<{
|
|
253
252
|
success: boolean;
|
|
254
253
|
commit: string;
|
|
@@ -328,3 +327,17 @@ export declare function tryAutoPull(dir: string): Promise<{
|
|
|
328
327
|
pulled: boolean;
|
|
329
328
|
error?: string;
|
|
330
329
|
}>;
|
|
330
|
+
/**
|
|
331
|
+
* How many commits `dir`'s checked-out branch is behind its upstream, read from
|
|
332
|
+
* the LAST-FETCHED remote-tracking ref — no network call. Returns null when the
|
|
333
|
+
* dir is not a git repo, has no upstream configured, or git errors.
|
|
334
|
+
*
|
|
335
|
+
* Used by `agents doctor` to flag a source layer (`~/.agents`, `~/.agents/.system`)
|
|
336
|
+
* that is reconciled against stale truth. Staleness relative to origin is a
|
|
337
|
+
* background auto-pull concern; this surfaces the same fact synchronously in the
|
|
338
|
+
* per-version verdict.
|
|
339
|
+
*/
|
|
340
|
+
export declare function commitsBehindUpstream(dir: string): {
|
|
341
|
+
behind: number;
|
|
342
|
+
branch: string;
|
|
343
|
+
} | null;
|