@phnx-labs/agents-cli 1.22.66 → 1.22.68
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 +26 -0
- package/README.md +7 -2
- package/dist/bootstrap.js +3 -3
- package/dist/commands/browser.js +46 -21
- package/dist/commands/daemon-test-harness.d.ts +1 -1
- package/dist/commands/daemon-test-harness.js +2 -2
- package/dist/commands/daemon.js +21 -22
- package/dist/commands/exec.js +40 -0
- package/dist/commands/monitors.js +2 -2
- package/dist/commands/repo.js +57 -19
- package/dist/commands/resume.d.ts +16 -0
- package/dist/commands/resume.js +41 -8
- package/dist/commands/routines.js +42 -21
- package/dist/commands/sessions-optimize.js +1 -1
- package/dist/commands/sessions.js +4 -11
- package/dist/commands/share.d.ts +5 -3
- package/dist/commands/share.js +33 -9
- package/dist/lib/accounting/account-pool-collect.d.ts +6 -4
- package/dist/lib/accounting/account-pool-collect.js +6 -4
- package/dist/lib/accounting/usage-ingest.js +4 -2
- package/dist/lib/accounting/usage-sync.d.ts +38 -93
- package/dist/lib/accounting/usage-sync.js +66 -210
- package/dist/lib/accounting/usage.d.ts +11 -3
- package/dist/lib/accounting/usage.js +23 -10
- package/dist/lib/auth-health.d.ts +8 -0
- package/dist/lib/auth-health.js +4 -4
- package/dist/lib/browser/caller-identity.d.ts +12 -0
- package/dist/lib/browser/caller-identity.js +19 -0
- package/dist/lib/browser/ipc.d.ts +37 -32
- package/dist/lib/browser/ipc.js +146 -94
- package/dist/lib/browser/task-index.d.ts +10 -2
- package/dist/lib/browser/task-index.js +22 -3
- package/dist/lib/daemon/account-state-daemon-service.d.ts +49 -9
- package/dist/lib/daemon/account-state-daemon-service.js +81 -18
- package/dist/lib/daemon/auth-sync-service.d.ts +4 -4
- package/dist/lib/daemon/auth-sync-service.js +17 -6
- package/dist/lib/daemon/catchup-service.d.ts +51 -0
- package/dist/lib/daemon/catchup-service.js +51 -0
- package/dist/lib/daemon/daemon.d.ts +12 -22
- package/dist/lib/daemon/daemon.js +463 -176
- package/dist/lib/daemon/runner.js +2 -0
- package/dist/lib/daemon/service.d.ts +22 -4
- package/dist/lib/daemon/service.js +2 -2
- package/dist/lib/daemon/supervisor.d.ts +55 -15
- package/dist/lib/daemon/supervisor.js +119 -29
- package/dist/lib/daemon/usage-sync-service.d.ts +4 -6
- package/dist/lib/daemon/usage-sync-service.js +22 -18
- package/dist/lib/daemon-health.js +36 -31
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +12 -2
- package/dist/lib/daemon-ticks.d.ts +9 -6
- package/dist/lib/daemon-ticks.js +14 -8
- package/dist/lib/devices/registry.js +2 -0
- package/dist/lib/exec.js +11 -0
- package/dist/lib/feed/watch.d.ts +3 -0
- package/dist/lib/feed/watch.js +13 -3
- package/dist/lib/fleet-shared-repo-sync.d.ts +36 -0
- package/dist/lib/fleet-shared-repo-sync.js +333 -0
- package/dist/lib/fleet-shared-state.d.ts +38 -0
- package/dist/lib/fleet-shared-state.js +105 -0
- package/dist/lib/hosts/remote-cmd.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.js +12 -3
- package/dist/lib/lock-compromise.d.ts +8 -0
- package/dist/lib/lock-compromise.js +12 -0
- package/dist/lib/monitors/engine.d.ts +2 -1
- package/dist/lib/monitors/engine.js +9 -2
- package/dist/lib/refresh-coordinator.js +2 -0
- package/dist/lib/run-trace-sync.d.ts +13 -0
- package/dist/lib/run-trace-sync.js +77 -0
- package/dist/lib/secrets/push.d.ts +10 -0
- package/dist/lib/secrets/push.js +86 -7
- package/dist/lib/secrets/remote.d.ts +18 -6
- package/dist/lib/secrets/remote.js +29 -4
- package/dist/lib/secrets/reserved-sync.d.ts +28 -27
- package/dist/lib/secrets/reserved-sync.js +119 -101
- package/dist/lib/session/active.d.ts +3 -1
- package/dist/lib/session/active.js +2 -0
- package/dist/lib/session/actor-sidecar.d.ts +12 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +5 -2
- package/dist/lib/session/db.js +54 -22
- package/dist/lib/session/discover.d.ts +12 -3
- package/dist/lib/session/discover.js +106 -25
- package/dist/lib/session/prompt.d.ts +11 -0
- package/dist/lib/session/prompt.js +25 -0
- package/dist/lib/session/recovery.d.ts +30 -2
- package/dist/lib/session/recovery.js +56 -4
- package/dist/lib/session/remote/remote-list.d.ts +17 -1
- package/dist/lib/session/remote/remote-list.js +29 -4
- package/dist/lib/session/remote/watch.d.ts +25 -2
- package/dist/lib/session/remote/watch.js +185 -11
- package/dist/lib/session/session-cache.d.ts +2 -1
- package/dist/lib/session/session-cache.js +1 -0
- package/dist/lib/session/types.d.ts +2 -0
- package/dist/lib/share/publish.d.ts +55 -9
- package/dist/lib/share/publish.js +87 -9
- package/dist/lib/share/worker-template.js +100 -8
- package/dist/lib/ssh-exec.d.ts +2 -0
- package/dist/lib/ssh-exec.js +20 -4
- package/dist/lib/teams/registry.js +2 -0
- package/dist/lib/testdata/daemon-health-writer.d.ts +1 -0
- package/dist/lib/testdata/daemon-health-writer.js +8 -0
- package/dist/lib/traces/sync.d.ts +7 -0
- package/dist/lib/traces/sync.js +9 -0
- package/dist/lib/usage-refresh.d.ts +8 -2
- package/dist/lib/usage-refresh.js +3 -3
- package/package.json +1 -1
- package/dist/lib/account-state-service.d.ts +0 -21
- package/dist/lib/account-state-service.js +0 -60
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import * as fs from 'fs';
|
|
24
24
|
import * as path from 'path';
|
|
25
25
|
import { getDaemonDir } from './state.js';
|
|
26
|
+
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
26
27
|
const HEALTH_FILE = 'health.json';
|
|
27
28
|
/** Stable subsystem identifiers shared by the daemon (writer) and `agents daemon` (reader). */
|
|
28
29
|
export const SUBSYSTEM_SECRETS_BROKER = 'secrets-broker';
|
|
@@ -61,15 +62,19 @@ function readAll() {
|
|
|
61
62
|
* dropped health update, never escape as an unhandled rejection that would
|
|
62
63
|
* hit the process-wide handler and take down every OTHER service too.
|
|
63
64
|
*/
|
|
64
|
-
function
|
|
65
|
+
function updateAll(update) {
|
|
65
66
|
try {
|
|
66
67
|
const healthPath = getHealthPath();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
ensureLockTarget(healthPath, '{}');
|
|
69
|
+
withFileLock(healthPath, () => {
|
|
70
|
+
const records = readAll();
|
|
71
|
+
update(records);
|
|
72
|
+
atomicWriteFileSync(healthPath, JSON.stringify(records), { encoding: 'utf-8', mode: 0o600 });
|
|
73
|
+
try {
|
|
74
|
+
fs.chmodSync(healthPath, 0o600);
|
|
75
|
+
}
|
|
76
|
+
catch { /* best effort */ }
|
|
77
|
+
});
|
|
73
78
|
}
|
|
74
79
|
catch { /* see docblock above — health recording must never crash a caller */ }
|
|
75
80
|
}
|
|
@@ -78,23 +83,23 @@ function blankRecord(subsystem) {
|
|
|
78
83
|
}
|
|
79
84
|
/** Record a successful subsystem check-in — clears the failure streak. */
|
|
80
85
|
export function recordSubsystemOk(subsystem, at = new Date().toISOString()) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
updateAll((all) => {
|
|
87
|
+
const existing = all[subsystem] ?? blankRecord(subsystem);
|
|
88
|
+
all[subsystem] = { ...existing, subsystem, consecutiveFailures: 0, lastOkAt: at };
|
|
89
|
+
});
|
|
85
90
|
}
|
|
86
91
|
/** Record a subsystem failure — bumps the consecutive-failure streak. */
|
|
87
92
|
export function recordSubsystemError(subsystem, error, at = new Date().toISOString()) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
updateAll((all) => {
|
|
94
|
+
const existing = all[subsystem] ?? blankRecord(subsystem);
|
|
95
|
+
all[subsystem] = {
|
|
96
|
+
...existing,
|
|
97
|
+
subsystem,
|
|
98
|
+
lastError: error,
|
|
99
|
+
lastErrorAt: at,
|
|
100
|
+
consecutiveFailures: existing.consecutiveFailures + 1,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
98
103
|
}
|
|
99
104
|
/**
|
|
100
105
|
* Refine the reason on an already-counted failure, without bumping the streak.
|
|
@@ -109,12 +114,12 @@ export function recordSubsystemError(subsystem, error, at = new Date().toISOStri
|
|
|
109
114
|
* subsystem is left alone rather than given a blank record to decorate.
|
|
110
115
|
*/
|
|
111
116
|
export function recordSubsystemErrorReason(subsystem, error, at = new Date().toISOString()) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
updateAll((all) => {
|
|
118
|
+
const existing = all[subsystem];
|
|
119
|
+
if (!existing)
|
|
120
|
+
return;
|
|
121
|
+
all[subsystem] = { ...existing, lastError: error, lastErrorAt: at };
|
|
122
|
+
});
|
|
118
123
|
}
|
|
119
124
|
/**
|
|
120
125
|
* Record a `ServiceSupervisor` lifecycle-state transition, without touching
|
|
@@ -123,10 +128,10 @@ export function recordSubsystemErrorReason(subsystem, error, at = new Date().toI
|
|
|
123
128
|
* status` runs as a separate process from the daemon (see module docblock).
|
|
124
129
|
*/
|
|
125
130
|
export function recordSubsystemState(subsystem, state) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
updateAll((all) => {
|
|
132
|
+
const existing = all[subsystem] ?? blankRecord(subsystem);
|
|
133
|
+
all[subsystem] = { ...existing, subsystem, state };
|
|
134
|
+
});
|
|
130
135
|
}
|
|
131
136
|
/** Read one subsystem's health record, or null if it has never reported in. */
|
|
132
137
|
export function readSubsystemHealth(subsystem) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* enabled without pulling in the whole daemon lifecycle.
|
|
8
8
|
*/
|
|
9
9
|
/** Every service the daemon can host. IDs are kebab-case and stable. */
|
|
10
|
-
export type DaemonServiceId = 'secrets-broker' | 'scheduler' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'keychain-reap' | 'account-state' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'browser-task-reap' | 'session-state';
|
|
10
|
+
export type DaemonServiceId = 'secrets-broker' | 'scheduler' | 'catchup' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'keychain-reap' | 'account-state' | 'account-auth' | 'watchdog' | 'device-probe' | 'state-dir-check' | 'session-index' | 'auth-sync' | 'usage-sync' | 'daemon-heartbeat' | 'tmux-reap' | 'browser-task-reap' | 'session-state';
|
|
11
11
|
/** Human-readable metadata for each service. */
|
|
12
12
|
export interface DaemonServiceDef {
|
|
13
13
|
id: DaemonServiceId;
|
|
@@ -22,6 +22,11 @@ export const DAEMON_SERVICES = [
|
|
|
22
22
|
title: 'Routine scheduler',
|
|
23
23
|
description: 'Fires cron-scheduled routines and catches up missed fires.',
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
id: 'catchup',
|
|
27
|
+
title: 'Catch-up recovery',
|
|
28
|
+
description: 'Supervised pass that re-runs routines whose scheduled fire this device missed (sleep, wedge, or suspend). No-ops while the scheduler gate is off.',
|
|
29
|
+
},
|
|
25
30
|
{
|
|
26
31
|
id: 'monitors',
|
|
27
32
|
title: 'Monitor engine',
|
|
@@ -49,8 +54,13 @@ export const DAEMON_SERVICES = [
|
|
|
49
54
|
},
|
|
50
55
|
{
|
|
51
56
|
id: 'account-state',
|
|
52
|
-
title: 'Account
|
|
53
|
-
description: 'Refreshes account quota/usage
|
|
57
|
+
title: 'Account usage refresh',
|
|
58
|
+
description: 'Refreshes account quota/usage on its own tick (PHNX-3608: independent circuit breaker from account-auth).',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'account-auth',
|
|
62
|
+
title: 'Account auth refresh',
|
|
63
|
+
description: 'Publishes this host\'s fleet-status row and refreshes auth health on its own slower tick (PHNX-3608: independent circuit breaker from account-state).',
|
|
54
64
|
},
|
|
55
65
|
{
|
|
56
66
|
id: 'watchdog',
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Daemon account-state tick bodies.
|
|
3
3
|
*
|
|
4
4
|
* These two bodies are the `refreshUsage` / `refreshAuth` implementations the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* routines and are never fired through the scheduler — the
|
|
8
|
-
* authentication health as first-party device state
|
|
5
|
+
* supervised `AccountStateDaemonService` (daemon/account-state-daemon-service.ts)
|
|
6
|
+
* runs on its tick, in-process (usage every tick, auth on the slower ~3 min
|
|
7
|
+
* cadence). They are NOT routines and are never fired through the scheduler — the
|
|
8
|
+
* daemon owns usage and authentication health as first-party device state
|
|
9
|
+
* (RUSH-2451); the supervisor bounds each tick with a deadline + AbortSignal so a
|
|
10
|
+
* hung refresh is abandoned and restarted instead of latching (PHNX-3608).
|
|
9
11
|
*
|
|
10
12
|
* `refreshLocalFleetAuthState` is also called by `agents fleet`/`ssh` surfaces
|
|
11
13
|
* that need a fresh local auth snapshot on demand; provider-level work is guarded
|
|
@@ -59,11 +61,12 @@ export declare function shouldReuseCachedAuthProbe(force: boolean, cached: AuthP
|
|
|
59
61
|
*/
|
|
60
62
|
export declare function refreshLocalFleetAuthState(opts?: {
|
|
61
63
|
force?: boolean;
|
|
64
|
+
signal?: AbortSignal;
|
|
62
65
|
}): Promise<{
|
|
63
66
|
row: FleetStatusRow;
|
|
64
67
|
authRows: import('./auth-health.js').AuthProbeRow[];
|
|
65
68
|
}>;
|
|
66
|
-
export declare function runFleetCacheWarmTick(): Promise<void>;
|
|
69
|
+
export declare function runFleetCacheWarmTick(signal?: AbortSignal): Promise<void>;
|
|
67
70
|
/**
|
|
68
71
|
* Usage refresh: keep the usage cache the `agents run` router reads
|
|
69
72
|
* (RUSH-2061, readOnly hot path) fresh, WITHOUT the hot path ever fetching.
|
|
@@ -71,7 +74,7 @@ export declare function runFleetCacheWarmTick(): Promise<void>;
|
|
|
71
74
|
* accounts it holds credentials for, straight from the provider APIs
|
|
72
75
|
* (RUSH-3193 #15; no cross-host broadcast).
|
|
73
76
|
*/
|
|
74
|
-
export declare function runUsageRefreshTick(): Promise<void>;
|
|
77
|
+
export declare function runUsageRefreshTick(signal?: AbortSignal): Promise<void>;
|
|
75
78
|
/**
|
|
76
79
|
* Active-sessions warm (RUSH-2062 / RUSH-2484): publish THIS host's live session
|
|
77
80
|
* rows so `agents sessions watch` (and the extension that tails it) receive
|
package/dist/lib/daemon-ticks.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Daemon account-state tick bodies.
|
|
3
3
|
*
|
|
4
4
|
* These two bodies are the `refreshUsage` / `refreshAuth` implementations the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* routines and are never fired through the scheduler — the
|
|
8
|
-
* authentication health as first-party device state
|
|
5
|
+
* supervised `AccountStateDaemonService` (daemon/account-state-daemon-service.ts)
|
|
6
|
+
* runs on its tick, in-process (usage every tick, auth on the slower ~3 min
|
|
7
|
+
* cadence). They are NOT routines and are never fired through the scheduler — the
|
|
8
|
+
* daemon owns usage and authentication health as first-party device state
|
|
9
|
+
* (RUSH-2451); the supervisor bounds each tick with a deadline + AbortSignal so a
|
|
10
|
+
* hung refresh is abandoned and restarted instead of latching (PHNX-3608).
|
|
9
11
|
*
|
|
10
12
|
* `refreshLocalFleetAuthState` is also called by `agents fleet`/`ssh` surfaces
|
|
11
13
|
* that need a fresh local auth snapshot on demand; provider-level work is guarded
|
|
@@ -62,6 +64,7 @@ export function shouldReuseCachedAuthProbe(force, cached, now, maxAgeMs = AUTH_P
|
|
|
62
64
|
*/
|
|
63
65
|
export async function refreshLocalFleetAuthState(opts) {
|
|
64
66
|
const force = opts?.force === true;
|
|
67
|
+
const signal = opts?.signal;
|
|
65
68
|
const { machineId } = await import('./machine-id.js');
|
|
66
69
|
const { probeLocalFleetAuth, readFleetAuthRows, writeFleetAuthRows } = await import('./auth-health.js');
|
|
67
70
|
const { getCliVersion } = await import('./version.js');
|
|
@@ -89,7 +92,7 @@ export async function refreshLocalFleetAuthState(opts) {
|
|
|
89
92
|
// Fleet status publishes every tick regardless — it does not ride that endpoint.
|
|
90
93
|
const cached = readFleetAuthRows(self);
|
|
91
94
|
const reuse = shouldReuseCachedAuthProbe(force, cached, requestedAt);
|
|
92
|
-
const authRows = reuse ? cached : await probeLocalFleetAuth({ cliVersion: getCliVersion(), forceLive: force });
|
|
95
|
+
const authRows = reuse ? cached : await probeLocalFleetAuth({ cliVersion: getCliVersion(), forceLive: force, signal });
|
|
93
96
|
if (!reuse)
|
|
94
97
|
writeFleetAuthRows(self, authRows);
|
|
95
98
|
const row = await publishLocalFleetStatus(self);
|
|
@@ -97,8 +100,8 @@ export async function refreshLocalFleetAuthState(opts) {
|
|
|
97
100
|
},
|
|
98
101
|
});
|
|
99
102
|
}
|
|
100
|
-
export async function runFleetCacheWarmTick() {
|
|
101
|
-
const result = await refreshLocalFleetAuthState();
|
|
103
|
+
export async function runFleetCacheWarmTick(signal) {
|
|
104
|
+
const result = await refreshLocalFleetAuthState({ signal });
|
|
102
105
|
// A waiter receives the already-published fleet row. The auth-row count is
|
|
103
106
|
// available only to the process that performed the provider probes.
|
|
104
107
|
const row = result.row;
|
|
@@ -112,7 +115,7 @@ export async function runFleetCacheWarmTick() {
|
|
|
112
115
|
* accounts it holds credentials for, straight from the provider APIs
|
|
113
116
|
* (RUSH-3193 #15; no cross-host broadcast).
|
|
114
117
|
*/
|
|
115
|
-
export async function runUsageRefreshTick() {
|
|
118
|
+
export async function runUsageRefreshTick(signal) {
|
|
116
119
|
const { runUsageRefresh, buildLocalUsageAccounts } = await import('./usage-refresh.js');
|
|
117
120
|
const { writeClaudeUsageCache, readClaudeUsageCache } = await import('./accounting/usage.js');
|
|
118
121
|
const { usageRateLimitedUntil } = await import('./usage-backoff.js');
|
|
@@ -124,6 +127,9 @@ export async function runUsageRefreshTick() {
|
|
|
124
127
|
// so a recent capture means the account is already fresh at zero API cost —
|
|
125
128
|
// the refresher re-derives headroom from it and skips the API fetch.
|
|
126
129
|
readCachedSnapshot: (usageKey) => readClaudeUsageCache(usageKey),
|
|
130
|
+
// Thread the supervisor deadline into each provider fetch so the tick's I/O
|
|
131
|
+
// is bounded by deadlineMs, not just each fetch's own 5s timeout (PHNX-3608).
|
|
132
|
+
signal,
|
|
127
133
|
});
|
|
128
134
|
const { listProfiles } = await import('./profiles.js');
|
|
129
135
|
const { refreshDueByokUsage } = await import('./byok-usage.js');
|
|
@@ -21,6 +21,7 @@ import { getDevicesRegistryPath, readMeta, updateMeta } from '../state.js';
|
|
|
21
21
|
import { atomicWriteJsonSync } from '../fs-atomic.js';
|
|
22
22
|
import { machineId } from '../machine-id.js';
|
|
23
23
|
import { addIgnoredEntry, unionDeviceIgnored } from './device-docs.js';
|
|
24
|
+
import { logAndContinueOnLockCompromised } from '../lock-compromise.js';
|
|
24
25
|
/**
|
|
25
26
|
* Whether a fan-out should dial this device, honouring the preference stated on
|
|
26
27
|
* {@link DeviceProfile.reachability}: the live SSH probe wins over the cached
|
|
@@ -141,6 +142,7 @@ async function withRegistryLock(p, fn) {
|
|
|
141
142
|
const release = await lockfile.lock(p, {
|
|
142
143
|
retries: { retries: 60, minTimeout: 25, maxTimeout: 250, factor: 1.5 },
|
|
143
144
|
stale: 10_000,
|
|
145
|
+
onCompromised: logAndContinueOnLockCompromised('devices registry'),
|
|
144
146
|
});
|
|
145
147
|
try {
|
|
146
148
|
return await fn();
|
package/dist/lib/exec.js
CHANGED
|
@@ -409,6 +409,17 @@ export function buildExecEnv(options) {
|
|
|
409
409
|
// re-parsing harness-specific transcripts.
|
|
410
410
|
result.AGENTS_RUN_MODE = resolveHeadlessMode(options.agent, normalizeMode(options.mode), resolveInteractive(options), options.modeWarningContext, options.modeWarningState);
|
|
411
411
|
result.AGENTS_HISTORY_DIR = getHistoryDir();
|
|
412
|
+
// Durable origin version for a later native resume. The SessionStart hook joins
|
|
413
|
+
// this to the harness's real session id (like AGENTS_RUN_MODE), so recovery can
|
|
414
|
+
// pin the exact origin version even when the transcript carries no derivable one
|
|
415
|
+
// (codex's `.codex-homes/<version>/` home) — the "version not recorded" fallback
|
|
416
|
+
// to `/continue` this closes (PHNX-3626). Resolve the concrete version-home id
|
|
417
|
+
// the same way the spawn path does; only stamp a real one.
|
|
418
|
+
if (options.agent) {
|
|
419
|
+
const runVersion = options.version ?? resolveVersion(options.agent, options.cwd || process.cwd());
|
|
420
|
+
if (runVersion)
|
|
421
|
+
result.AGENTS_RUN_VERSION = runVersion;
|
|
422
|
+
}
|
|
412
423
|
// So activity / feed posts stamp the right harness without re-detecting.
|
|
413
424
|
// A custom-harness run (`agents run deepseek`) must stamp the PROFILE name,
|
|
414
425
|
// not the host CLI (`claude`) — otherwise sessions and feed posts cannot
|
package/dist/lib/feed/watch.d.ts
CHANGED
package/dist/lib/feed/watch.js
CHANGED
|
@@ -21,13 +21,20 @@ export class FeedWatchState {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
async function attentionFor(agent) {
|
|
24
|
-
|
|
24
|
+
// Durable Previous rows share the operator stream for Sessions history, but
|
|
25
|
+
// they are not live work and must never synthesize Needs-you attention.
|
|
26
|
+
if (!agent.sessionId || agent.previous || agent.context === 'recent')
|
|
25
27
|
return undefined;
|
|
26
28
|
const blockId = blockIdForSession(agent.sessionId);
|
|
27
29
|
// ActiveSession.host names the terminal app; the feed contract's host is the
|
|
28
30
|
// device scope. Normalize only the reconciler input so lifecycle/PR keys are
|
|
29
31
|
// routable across the fleet while the projected agent row stays compatible.
|
|
30
|
-
const session = {
|
|
32
|
+
const session = {
|
|
33
|
+
...agent,
|
|
34
|
+
context: agent.context,
|
|
35
|
+
host: agent.sourceDevice,
|
|
36
|
+
viewingIn: undefined,
|
|
37
|
+
};
|
|
31
38
|
return reconcileAttention({
|
|
32
39
|
block: readBlock(blockId), session,
|
|
33
40
|
resolution: readResolution(blockId),
|
|
@@ -49,7 +56,10 @@ export async function projectSessionEnvelope(event, state) {
|
|
|
49
56
|
];
|
|
50
57
|
}
|
|
51
58
|
if (event.type === 'remove')
|
|
52
|
-
return [
|
|
59
|
+
return [
|
|
60
|
+
state.emit({ type: 'agent.remove', scope: event.scope, rowKey: event.rowKey }),
|
|
61
|
+
state.emit({ type: 'attention.remove', scope: event.scope, rowKey: event.rowKey }),
|
|
62
|
+
];
|
|
53
63
|
if (event.type === 'scope')
|
|
54
64
|
return [state.emit({ type: 'scope', capturedAt: event.capturedAt, scope: event.scope, status: event.status, ...(event.reason ? { reason: event.reason } : {}) })];
|
|
55
65
|
return [state.emit({ type: 'heartbeat', capturedAt: event.capturedAt, scope: event.scope })];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const FLEET_SHARED_REPO_SYNC_DEADLINE_MS = 45000;
|
|
2
|
+
export declare const FLEET_SHARED_REPO_KILL_GRACE_MS = 250;
|
|
3
|
+
export interface BoundedProcessResult {
|
|
4
|
+
code: number | null;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
timedOut: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface FleetSharedRepoSyncOptions {
|
|
10
|
+
userAgentsDir?: string;
|
|
11
|
+
device?: string;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/** Test-only redirection for the untracked cross-process lock target. */
|
|
14
|
+
lockPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FleetSharedRepoSyncResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
committed: boolean;
|
|
19
|
+
pushed: boolean;
|
|
20
|
+
commit: string | null;
|
|
21
|
+
timedOut: boolean;
|
|
22
|
+
skipped: string | null;
|
|
23
|
+
error: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** Run one real process asynchronously with a hard wall-clock/process-tree bound. */
|
|
26
|
+
export declare function runBoundedProcess(command: string, args: string[], options: {
|
|
27
|
+
cwd: string;
|
|
28
|
+
timeoutMs: number;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
}): Promise<BoundedProcessResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Commit/pull/push this device's shared state under one cross-process lock.
|
|
33
|
+
* Callers publish their local fields first and consume peer fields only after
|
|
34
|
+
* this resolves successfully.
|
|
35
|
+
*/
|
|
36
|
+
export declare function syncFleetSharedStateRepo(options?: FleetSharedRepoSyncOptions): Promise<FleetSharedRepoSyncResult>;
|