@phnx-labs/agents-cli 1.22.65 → 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 +39 -0
- package/README.md +7 -2
- package/dist/bootstrap.js +3 -3
- package/dist/commands/apply.js +9 -2
- 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/known-hosts.d.ts +15 -0
- package/dist/lib/devices/known-hosts.js +18 -0
- 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 +120 -102
- 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
package/dist/commands/resume.js
CHANGED
|
@@ -7,7 +7,25 @@ import { spawn } from 'child_process';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { resolveSessionMetadataValue } from './sessions.js';
|
|
9
9
|
import { sessionOwnerDevice, consumeResumePinned, RESUME_PINNED_ENV } from '../lib/session/resume-owner.js';
|
|
10
|
+
import { machineId } from '../lib/machine-id.js';
|
|
10
11
|
export const RESUME_SOURCE_ENV = 'AGENTS_RESUME_SOURCE_JSON';
|
|
12
|
+
/**
|
|
13
|
+
* The source a dead-remote local fallback resumes from: the same session, but
|
|
14
|
+
* with `machine` rewritten to THIS box so the delegated `agents run --resume`
|
|
15
|
+
* resolves recovery locally (`sessionRecoveryPeer` returns undefined) instead of
|
|
16
|
+
* bouncing back to the unreachable owner. Because no local version home owns the
|
|
17
|
+
* peer's transcript, that local recovery lands on a labelled `/continue` replay
|
|
18
|
+
* from the synced mirror — the only way to continue a session whose owning device
|
|
19
|
+
* is gone. Owner-approved prefer-device, fall-back-local (PHNX-3626).
|
|
20
|
+
*
|
|
21
|
+
* Safe against the RUSH-2022 "silent local resume forks live state" hazard by
|
|
22
|
+
* PRECONDITION: this is reached only after `runOnPeer` proved the owner
|
|
23
|
+
* unreachable, so there is no live process on the peer to fork, and the fallback
|
|
24
|
+
* is announced with a log line — never silent.
|
|
25
|
+
*/
|
|
26
|
+
export function resumeLocalFallbackSource(session, self = machineId()) {
|
|
27
|
+
return { ...session, machine: self };
|
|
28
|
+
}
|
|
11
29
|
/**
|
|
12
30
|
* The argv to re-run this resume on the machine that owns the session.
|
|
13
31
|
*
|
|
@@ -165,14 +183,30 @@ export async function runStrictResume(sessionId, prompt, options) {
|
|
|
165
183
|
},
|
|
166
184
|
sessionId: outcome.session.id,
|
|
167
185
|
});
|
|
168
|
-
if (rc === 'no-target') {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
if (rc === 'no-target' || rc === 'unreachable') {
|
|
187
|
+
// Prefer-device, fall back to local (PHNX-3626): the owning device is
|
|
188
|
+
// unreachable — either not a dialable registered device ('no-target') or
|
|
189
|
+
// registered but offline/asleep so the SSH connection itself failed
|
|
190
|
+
// ('unreachable'). Either way there is no live harness to reach OR to fork,
|
|
191
|
+
// so continue the session HERE from its synced mirror rather than
|
|
192
|
+
// dead-ending. The local recovery resolves this to a labelled `/continue`
|
|
193
|
+
// replay (no local home owns the peer's transcript), the honest degradation.
|
|
194
|
+
if (!options.quiet) {
|
|
195
|
+
process.stderr.write(chalk.yellow(`[agents] session ${outcome.session.shortId} belongs to ${owner}, which is unreachable → resuming locally (/continue replay from the synced transcript)\n`));
|
|
196
|
+
}
|
|
197
|
+
process.exitCode = await delegateLocalResume(resumeLocalFallbackSource(outcome.session), prompt, options);
|
|
172
198
|
}
|
|
173
199
|
return;
|
|
174
200
|
}
|
|
175
|
-
|
|
201
|
+
process.exitCode = await delegateLocalResume(outcome.session, prompt, options);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Spawn the delegated local `agents run --resume` for a session this box owns
|
|
205
|
+
* (or is falling back to). The run command remains the sole executor; recovery
|
|
206
|
+
* (native vs `/continue`) is resolved there. Returns the child's exit code.
|
|
207
|
+
*/
|
|
208
|
+
async function delegateLocalResume(session, prompt, options) {
|
|
209
|
+
const args = buildResumeRunArgs(session, prompt, options);
|
|
176
210
|
const child = spawn(process.execPath, [process.argv[1], ...args], {
|
|
177
211
|
stdio: 'inherit',
|
|
178
212
|
env: {
|
|
@@ -180,12 +214,11 @@ export async function runStrictResume(sessionId, prompt, options) {
|
|
|
180
214
|
// Avoid repeating the fleet lookup in the delegated local `run`
|
|
181
215
|
// process. The value is metadata-only and is not forwarded over SSH;
|
|
182
216
|
// the owner performs its own local SQLite lookup.
|
|
183
|
-
[RESUME_SOURCE_ENV]: JSON.stringify(
|
|
217
|
+
[RESUME_SOURCE_ENV]: JSON.stringify(session),
|
|
184
218
|
},
|
|
185
219
|
});
|
|
186
|
-
|
|
220
|
+
return new Promise((resolve) => {
|
|
187
221
|
child.once('error', () => resolve(127));
|
|
188
222
|
child.once('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
189
223
|
});
|
|
190
|
-
process.exitCode = exitCode;
|
|
191
224
|
}
|
|
@@ -10,8 +10,9 @@ import ora from 'ora';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as yaml from 'yaml';
|
|
13
|
-
import { isDaemonRunning, signalDaemonReload, startDaemon,
|
|
14
|
-
import {
|
|
13
|
+
import { isDaemonRunning, signalDaemonReload, startDaemon, readDaemonLog, getDaemonStatus, getDaemonLogPath, } from '../lib/daemon/daemon.js';
|
|
14
|
+
import { isDaemonServiceEnabled, setDaemonServiceEnabled } from '../lib/daemon-services.js';
|
|
15
|
+
import { assertSchedulerEnabled, assertDaemonEnabled, isDaemonEnabled, isSchedulerEnabled, } from '../lib/device-config.js';
|
|
15
16
|
import { parseAgentVersionSpec, isAgentHardDeprecated, hardDeprecationError, ROUTINE_AGENT_IDS } from '../lib/agents.js';
|
|
16
17
|
import { isCustomHarnessName } from '../lib/profiles.js';
|
|
17
18
|
import { RUN_STRATEGIES, normalizeRunStrategy } from '../lib/accounting/rotate.js';
|
|
@@ -313,10 +314,17 @@ function ensureSchedulerRunning(opts = {}) {
|
|
|
313
314
|
console.error(chalk.yellow(err.message));
|
|
314
315
|
return;
|
|
315
316
|
}
|
|
317
|
+
const wasServiceEnabled = isDaemonServiceEnabled('scheduler');
|
|
318
|
+
if (!wasServiceEnabled)
|
|
319
|
+
setDaemonServiceEnabled('scheduler', true);
|
|
316
320
|
if (isDaemonRunning()) {
|
|
317
|
-
signalDaemonReload();
|
|
318
|
-
if (!
|
|
319
|
-
|
|
321
|
+
const reloaded = signalDaemonReload();
|
|
322
|
+
if (!reloaded) {
|
|
323
|
+
console.error(chalk.yellow('Scheduler service enabled, but live reload is unavailable. Restart deliberately with: agents daemon restart'));
|
|
324
|
+
}
|
|
325
|
+
else if (!opts.quiet) {
|
|
326
|
+
log(chalk.gray(wasServiceEnabled ? 'Scheduler reloaded' : 'Scheduler service enabled and reloaded'));
|
|
327
|
+
}
|
|
320
328
|
return;
|
|
321
329
|
}
|
|
322
330
|
const result = startDaemon();
|
|
@@ -2162,7 +2170,7 @@ export function registerRoutinesCommands(program) {
|
|
|
2162
2170
|
// Scheduler lifecycle — usually auto-managed by `routines add`, exposed here for manual control.
|
|
2163
2171
|
routinesCmd
|
|
2164
2172
|
.command('start')
|
|
2165
|
-
.description('
|
|
2173
|
+
.description('Enable and reload the scheduler service. Usually unnecessary — it auto-starts when you add your first routine.')
|
|
2166
2174
|
.action(() => {
|
|
2167
2175
|
try {
|
|
2168
2176
|
// A manual start on a disabled device refuses with the same message the
|
|
@@ -2176,13 +2184,16 @@ export function registerRoutinesCommands(program) {
|
|
|
2176
2184
|
console.error(chalk.red(err.message));
|
|
2177
2185
|
process.exit(1);
|
|
2178
2186
|
}
|
|
2187
|
+
setDaemonServiceEnabled('scheduler', true);
|
|
2179
2188
|
const result = startDaemon();
|
|
2180
2189
|
if (result.method === 'already-running') {
|
|
2181
2190
|
// Signal a reload even here: if the daemon booted while this device had
|
|
2182
2191
|
// scheduler.enabled=false, the reload re-evaluates the gate and boots
|
|
2183
2192
|
// the scheduler — a manual start heals a scheduler-less daemon.
|
|
2184
|
-
signalDaemonReload();
|
|
2185
|
-
console.log(
|
|
2193
|
+
const reloaded = signalDaemonReload();
|
|
2194
|
+
console.log(reloaded
|
|
2195
|
+
? chalk.yellow(`Scheduler service enabled (shared daemon PID: ${result.pid}) — reloaded`)
|
|
2196
|
+
: chalk.yellow(`Scheduler service enabled, but live reload is unavailable. Restart deliberately with: agents daemon restart`));
|
|
2186
2197
|
}
|
|
2187
2198
|
else if (result.pid) {
|
|
2188
2199
|
console.log(chalk.green(`Scheduler started (PID: ${result.pid})`));
|
|
@@ -2193,25 +2204,31 @@ export function registerRoutinesCommands(program) {
|
|
|
2193
2204
|
});
|
|
2194
2205
|
routinesCmd
|
|
2195
2206
|
.command('stop')
|
|
2196
|
-
.description('Stop the
|
|
2207
|
+
.description('Stop only the scheduler service. The shared daemon and its browser, secrets, usage, and monitoring services stay running.')
|
|
2197
2208
|
.action(() => {
|
|
2198
|
-
|
|
2199
|
-
|
|
2209
|
+
const daemonRunning = isDaemonRunning();
|
|
2210
|
+
setDaemonServiceEnabled('scheduler', false);
|
|
2211
|
+
if (daemonRunning && !signalDaemonReload()) {
|
|
2212
|
+
console.log(chalk.yellow('Scheduler service disabled, but live reload is unavailable. It will stay off after the next daemon start.'));
|
|
2200
2213
|
return;
|
|
2201
2214
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2215
|
+
console.log(daemonRunning
|
|
2216
|
+
? chalk.green('Scheduler service stopped; shared daemon is still running')
|
|
2217
|
+
: chalk.yellow('Scheduler service disabled; shared daemon was already stopped'));
|
|
2204
2218
|
});
|
|
2205
2219
|
routinesCmd
|
|
2206
2220
|
.command('status')
|
|
2207
|
-
.description('Show scheduler
|
|
2208
|
-
.option('--json', 'Emit machine-readable scheduler + per-routine status (owner device, last fire, last error, in-flight run)')
|
|
2221
|
+
.description('Show scheduler service state, shared-daemon state, enabled routines, and upcoming runs.')
|
|
2222
|
+
.option('--json', 'Emit machine-readable scheduler service + device gate + daemon + per-routine status (owner device, last fire, last error, in-flight run)')
|
|
2209
2223
|
.action((options) => {
|
|
2210
2224
|
try {
|
|
2211
2225
|
monitorRunningJobs();
|
|
2212
2226
|
}
|
|
2213
2227
|
catch { /* best-effort orphan reap */ }
|
|
2214
2228
|
const status = getDaemonStatus();
|
|
2229
|
+
const schedulerServiceEnabled = isDaemonServiceEnabled('scheduler');
|
|
2230
|
+
const schedulerDeviceEnabled = isSchedulerEnabled();
|
|
2231
|
+
const schedulerState = schedulerServiceEnabled && schedulerDeviceEnabled ? status.state : 'stopped';
|
|
2215
2232
|
// The daemon-owned status surface (PHNX-3215): scheduler truth plus, per
|
|
2216
2233
|
// routine, its single owner device, last fire outcome + error, and any
|
|
2217
2234
|
// in-flight spawn — the fields `list --json` (definition-shaped) does not
|
|
@@ -2221,7 +2238,10 @@ export function registerRoutinesCommands(program) {
|
|
|
2221
2238
|
writeJson({
|
|
2222
2239
|
device: machineId(),
|
|
2223
2240
|
scheduler: {
|
|
2224
|
-
state:
|
|
2241
|
+
state: schedulerState,
|
|
2242
|
+
serviceEnabled: schedulerServiceEnabled,
|
|
2243
|
+
deviceEnabled: schedulerDeviceEnabled,
|
|
2244
|
+
daemonState: status.state,
|
|
2225
2245
|
pid: status.pid,
|
|
2226
2246
|
binaryPath: status.binaryPath,
|
|
2227
2247
|
heartbeat: status.heartbeat
|
|
@@ -2238,12 +2258,13 @@ export function registerRoutinesCommands(program) {
|
|
|
2238
2258
|
return;
|
|
2239
2259
|
}
|
|
2240
2260
|
console.log(chalk.bold('Scheduler\n'));
|
|
2241
|
-
const stateLabel =
|
|
2261
|
+
const stateLabel = schedulerState === 'running'
|
|
2242
2262
|
? chalk.green('running')
|
|
2243
|
-
:
|
|
2263
|
+
: schedulerState === 'wedged'
|
|
2244
2264
|
? chalk.red('wedged')
|
|
2245
2265
|
: chalk.gray('stopped');
|
|
2246
2266
|
console.log(` Status: ${stateLabel}`);
|
|
2267
|
+
console.log(` Daemon: ${status.running ? chalk.green('running') : chalk.gray('stopped')}`);
|
|
2247
2268
|
if (status.pid)
|
|
2248
2269
|
console.log(` PID: ${status.pid}`);
|
|
2249
2270
|
if (status.binaryPath)
|
|
@@ -2256,9 +2277,9 @@ export function registerRoutinesCommands(program) {
|
|
|
2256
2277
|
const enabled = jobs.filter((j) => j.enabled);
|
|
2257
2278
|
console.log(` Routines: ${enabled.length} enabled / ${jobs.length} total`);
|
|
2258
2279
|
if (status.state === 'wedged') {
|
|
2259
|
-
console.log(chalk.red('\n The daemon is wedged (heartbeat stale). Restart with: agents
|
|
2280
|
+
console.log(chalk.red('\n The shared daemon is wedged (heartbeat stale). Restart deliberately with: agents daemon restart'));
|
|
2260
2281
|
}
|
|
2261
|
-
if (
|
|
2282
|
+
if (schedulerState === 'running' && enabled.length > 0) {
|
|
2262
2283
|
const scheduler = new JobScheduler(async () => { });
|
|
2263
2284
|
scheduler.loadAll();
|
|
2264
2285
|
const scheduled = scheduler.listScheduled();
|
|
@@ -2269,7 +2290,7 @@ export function registerRoutinesCommands(program) {
|
|
|
2269
2290
|
}
|
|
2270
2291
|
scheduler.stopAll();
|
|
2271
2292
|
}
|
|
2272
|
-
else if (
|
|
2293
|
+
else if (schedulerState !== 'running' && jobs.length > 0) {
|
|
2273
2294
|
console.log(chalk.gray('\n Start the scheduler to begin firing routines: agents routines start'));
|
|
2274
2295
|
}
|
|
2275
2296
|
});
|
|
@@ -35,7 +35,7 @@ export function registerSessionsOptimizeCommand(sessionsCmd) {
|
|
|
35
35
|
notes: `
|
|
36
36
|
- FTS5 appends a segment on every insert and tombstones every delete; the scanner delete+inserts a session's docs on each rescan and never self-merges, so \`tool_call_text_data\` / \`session_text_data\` bloat with unmerged segments — GBs of index for tens of MB of content, and queries slow down.
|
|
37
37
|
- This runs FTS5 \`'optimize'\`: it merges every segment into one and purges tombstones. Non-destructive — no searchable content is lost.
|
|
38
|
-
- Reclaimed space becomes reusable free pages inside the DB file. To return it to the OS,
|
|
38
|
+
- Reclaimed space becomes reusable free pages inside the DB file. To return it to the OS, use the operator lifecycle command \`agents daemon stop\`, run \`VACUUM\` against \`~/.agents/.history/sessions/sessions.db\`, then \`agents daemon start\`. \`agents routines stop\` disables only the scheduler service.
|
|
39
39
|
`,
|
|
40
40
|
});
|
|
41
41
|
}
|
|
@@ -53,7 +53,7 @@ import { itemPicker } from '../lib/picker.js';
|
|
|
53
53
|
import { resolveSessionAlias } from '../lib/session/actor-sidecar.js';
|
|
54
54
|
import { listInstalledVersions, resolveVersionAliasLoose } from '../lib/installations/versions.js';
|
|
55
55
|
import { getAgentsInvocation } from '../lib/daemon/daemon.js';
|
|
56
|
-
import { sessionRecoveryRunArgs } from '../lib/session/recovery.js';
|
|
56
|
+
import { sessionAgentSupportsResume, sessionRecoveryRunArgs } from '../lib/session/recovery.js';
|
|
57
57
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
58
58
|
import { sessionPicker, buildPreview, loadSessionPreviewDigest, transcriptOnPeerOf, formatTodoCompact, githubRepoUrlFromCwd, } from './sessions-picker.js';
|
|
59
59
|
import { setHelpSections } from '../lib/help.js';
|
|
@@ -4062,6 +4062,8 @@ function versionedAliasIfPresent(agent, version) {
|
|
|
4062
4062
|
return null;
|
|
4063
4063
|
}
|
|
4064
4064
|
export function buildResumeCommand(session) {
|
|
4065
|
+
if (!sessionAgentSupportsResume(session.agent))
|
|
4066
|
+
return null;
|
|
4065
4067
|
switch (session.agent) {
|
|
4066
4068
|
// opencode sessions are shared across versions, so resume is deliberately NOT
|
|
4067
4069
|
// version-pinned — it always goes through the plain launcher.
|
|
@@ -4079,16 +4081,7 @@ export function buildResumeCommand(session) {
|
|
|
4079
4081
|
}
|
|
4080
4082
|
return resumeArgv(session.agent, session.id, cli);
|
|
4081
4083
|
}
|
|
4082
|
-
|
|
4083
|
-
case 'antigravity':
|
|
4084
|
-
case 'openclaw':
|
|
4085
|
-
case 'rush':
|
|
4086
|
-
case 'hermes':
|
|
4087
|
-
case 'grok':
|
|
4088
|
-
case 'kimi':
|
|
4089
|
-
case 'droid':
|
|
4090
|
-
case 'cursor':
|
|
4091
|
-
// Grok (and some others) sessions are captured artifacts, not resumable the same way.
|
|
4084
|
+
default:
|
|
4092
4085
|
return null;
|
|
4093
4086
|
}
|
|
4094
4087
|
}
|
package/dist/commands/share.d.ts
CHANGED
|
@@ -80,7 +80,9 @@ export declare function runShareEdit(target: string, opts: {
|
|
|
80
80
|
removeMeta?: string[];
|
|
81
81
|
/** Change the page's visibility in place (public | unlisted | me | org).
|
|
82
82
|
* A metadata-only rewrite like label/meta — the body is untouched, so no
|
|
83
|
-
* revision is created. me/org require a Phoenix session.
|
|
83
|
+
* revision is created. me/org require a Phoenix session. `private` is
|
|
84
|
+
* rejected here — token-gating needs a fresh viewer key only the publish
|
|
85
|
+
* path mints (PHNX-3654). */
|
|
84
86
|
visibility?: ShareVisibility;
|
|
85
87
|
force?: boolean;
|
|
86
88
|
fetchEdit?: typeof fetch;
|
|
@@ -116,7 +118,7 @@ export declare function runShareList(opts?: {
|
|
|
116
118
|
/** Visibility filter. `public` (default) lists only public gallery pages;
|
|
117
119
|
* `unlisted`/`me`/`org` include that owner's hidden pages; `all` includes
|
|
118
120
|
* every active page. Hidden scopes send the owner's bearer and `scope=mine`. */
|
|
119
|
-
scope?: 'public' | 'unlisted' | 'me' | 'org' | 'all';
|
|
121
|
+
scope?: 'public' | 'unlisted' | 'private' | 'me' | 'org' | 'all';
|
|
120
122
|
}): Promise<ShareListResult>;
|
|
121
123
|
/** Client-side filtering over an already-fetched listing (RUSH-2683) — the
|
|
122
124
|
* Worker has no query surface for this, so it narrows the fetched set instead
|
|
@@ -127,7 +129,7 @@ export declare function applyShareListFilters(result: ShareListResult, filters:
|
|
|
127
129
|
session?: string;
|
|
128
130
|
label?: string;
|
|
129
131
|
meta?: Record<string, string>;
|
|
130
|
-
scope?: 'public' | 'unlisted' | 'me' | 'org' | 'all';
|
|
132
|
+
scope?: 'public' | 'unlisted' | 'private' | 'me' | 'org' | 'all';
|
|
131
133
|
}): ShareListResult;
|
|
132
134
|
export declare function formatShareList(result: ShareListResult, json?: boolean): string;
|
|
133
135
|
/** One retained prior version of a slug, as reported by the Worker's
|
package/dist/commands/share.js
CHANGED
|
@@ -11,7 +11,7 @@ import { Argument, Option } from 'commander';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_SHARE_DOMAIN, DEFAULT_WORKER_NAME, generateWriteToken, readCloudflareCreds, readShareConfig, readWriteToken, readWriteTokenEnv, readWriteTokenFromBundle, storeWriteToken, writeShareConfig, } from '../lib/share/config.js';
|
|
13
13
|
import { addCustomDomain, configureBucketLifecycle, createBucket, deployWorker, enableWorkersDev, findZoneId, hashWorkerScript, putWorkerSecret, updateWorker, WORKER_PHOENIX_ID_BASE_SECRET, setWorkerSecret, } from '../lib/share/provision.js';
|
|
14
|
-
import { publishFile, resolveShareUsername, parseMetaEntries, sanitizeLabel, resolveShareVisibility, scanShareContent, formatSensitiveContentError, SHARE_VISIBILITY_LEVELS, } from '../lib/share/publish.js';
|
|
14
|
+
import { publishFile, resolveShareUsername, parseMetaEntries, sanitizeLabel, resolveShareVisibility, scanShareContent, formatSensitiveContentError, unlistedNotPrivateWarning, SHARE_VISIBILITY_LEVELS, PUBLISH_VISIBILITY_LEVELS, } from '../lib/share/publish.js';
|
|
15
15
|
import { deleteShare, resolveDeleteTarget } from '../lib/share/delete.js';
|
|
16
16
|
import { renderWorkerBundle } from '../lib/share/worker-template.js';
|
|
17
17
|
import { analyticsEnabled } from '../lib/share/analytics.js';
|
|
@@ -37,7 +37,11 @@ export function formatSharePublishResult(result, json = false) {
|
|
|
37
37
|
lines.push(chalk.dim(' expires never'));
|
|
38
38
|
const visibility = result.visibility ?? (result.unlisted ? 'unlisted' : 'public');
|
|
39
39
|
if (visibility === 'unlisted') {
|
|
40
|
-
lines.push(chalk.dim(' visibility: unlisted (noindex, hidden from gallery)'));
|
|
40
|
+
lines.push(chalk.dim(' visibility: unlisted (noindex, hidden from gallery — NOT authenticated)'));
|
|
41
|
+
}
|
|
42
|
+
else if (visibility === 'private') {
|
|
43
|
+
lines.push(chalk.dim(' visibility: private (token-gated — 404 without the key; hidden from gallery)'));
|
|
44
|
+
lines.push(chalk.yellow(' the key is in the URL above (?k=…) — treat the whole link as a secret'));
|
|
41
45
|
}
|
|
42
46
|
else if (visibility === 'me' || visibility === 'org') {
|
|
43
47
|
lines.push(chalk.dim(` visibility: ${visibility} (login required, hidden from gallery)`));
|
|
@@ -57,6 +61,14 @@ export function shareTemplateStatus(cfg) {
|
|
|
57
61
|
return cfg.templateHash === hashWorkerScript(renderWorkerBundle().script) ? 'current' : 'outdated';
|
|
58
62
|
}
|
|
59
63
|
export async function runShareEdit(target, opts) {
|
|
64
|
+
// Token-gated `private` can't be set in place: the metadata-edit route carries
|
|
65
|
+
// no viewer token, so re-stamping visibility=private alone would leave the page
|
|
66
|
+
// gated by a hash that doesn't exist — inaccessible to everyone (PHNX-3654).
|
|
67
|
+
// Fail loud pointing at the publish path, which mints the key.
|
|
68
|
+
if (opts.visibility === 'private') {
|
|
69
|
+
throw new Error("Can't change a page to 'private' in place — a token-gated link needs a fresh viewer key. " +
|
|
70
|
+
"Re-publish it with 'agents artifacts share <file> --protected' instead.");
|
|
71
|
+
}
|
|
60
72
|
// Same public-listing gate as publish: label + every metadata value are
|
|
61
73
|
// world-readable in the gallery and `share list --list-json`.
|
|
62
74
|
if (opts.force !== true) {
|
|
@@ -158,7 +170,10 @@ export function parseShareListing(user, body) {
|
|
|
158
170
|
host: item.host == null ? null : String(item.host),
|
|
159
171
|
repo: item.repo == null ? null : String(item.repo),
|
|
160
172
|
revisionCount: typeof item.revisionCount === 'number' ? item.revisionCount : 0,
|
|
161
|
-
visibility: item.visibility === 'unlisted' ||
|
|
173
|
+
visibility: item.visibility === 'unlisted' ||
|
|
174
|
+
item.visibility === 'private' ||
|
|
175
|
+
item.visibility === 'me' ||
|
|
176
|
+
item.visibility === 'org'
|
|
162
177
|
? item.visibility
|
|
163
178
|
: 'public',
|
|
164
179
|
meta: parseMetaField(item.meta),
|
|
@@ -609,11 +624,12 @@ export function registerShareCommands(artifactsCmd) {
|
|
|
609
624
|
.option('--slug <slug>', 'URL slug override (default: stable slug of the artifact title, then filename)')
|
|
610
625
|
.option('--github-user <user>', 'GitHub username for the share namespace (default: resolved from gh/git config; ignored on the managed endpoint)')
|
|
611
626
|
.option('--expire <spec>', "auto-expire (default 30d). e.g. 12h, 30d, 2026-08-01, or 'never'")
|
|
612
|
-
.addOption(new Option('--visibility <level>', 'public | unlisted | me | org (default public). unlisted is
|
|
613
|
-
.choices([
|
|
627
|
+
.addOption(new Option('--visibility <level>', 'public | unlisted | private | me | org (default public). unlisted is an UNAUTHENTICATED capability URL; private is token-gated (404 without the key); me/org require a Phoenix session. All but public are hidden from the gallery')
|
|
628
|
+
.choices([...PUBLISH_VISIBILITY_LEVELS])
|
|
614
629
|
.default('public'))
|
|
615
|
-
.addOption(new Option('--
|
|
616
|
-
.addOption(new Option('--
|
|
630
|
+
.addOption(new Option('--protected', 'token-gated link (= --visibility private): the URL carries a secret key and returns 404 without it — the authenticated alternative to --unlisted'))
|
|
631
|
+
.addOption(new Option('--unlisted', 'hidden alias of --visibility unlisted (obscurity, NOT authentication)').hideHelp())
|
|
632
|
+
.addOption(new Option('--private', 'hidden alias of --visibility unlisted (obscurity, NOT authentication — for real read-auth use --protected)').hideHelp())
|
|
617
633
|
.option('--force', 'publish even when the file contains emails or credential-shaped strings')
|
|
618
634
|
.option('--no-cover', 'skip the OG preview image (HTML pages get one by default)')
|
|
619
635
|
.option('--no-analytics', 'skip injecting the Cloudflare Web Analytics beacon')
|
|
@@ -637,13 +653,21 @@ export function registerShareCommands(artifactsCmd) {
|
|
|
637
653
|
const visibility = resolveShareVisibility({
|
|
638
654
|
visibility: opts.visibility,
|
|
639
655
|
unlisted: opts.unlisted === true || opts.private === true,
|
|
656
|
+
protected: opts.protected === true,
|
|
640
657
|
});
|
|
658
|
+
// unlisted (incl. its --private alias) is obscurity, not read-auth — warn
|
|
659
|
+
// loudly before the publish so a "private" link is never mistaken for a
|
|
660
|
+
// gated one (PHNX-3654). Skipped for --json so machine output stays clean.
|
|
661
|
+
if (visibility === 'unlisted' && !opts.json) {
|
|
662
|
+
console.error(chalk.yellow(`⚠ ${unlistedNotPrivateWarning()}`));
|
|
663
|
+
}
|
|
641
664
|
const result = await publishFile(file, {
|
|
642
665
|
slug: opts.slug,
|
|
643
666
|
githubUser: opts.githubUser,
|
|
644
667
|
expire: opts.expire,
|
|
645
668
|
visibility,
|
|
646
669
|
unlisted: visibility === 'unlisted',
|
|
670
|
+
protected: visibility === 'private',
|
|
647
671
|
force: opts.force,
|
|
648
672
|
cover: opts.cover,
|
|
649
673
|
analytics: opts.analytics,
|
|
@@ -929,8 +953,8 @@ Prefer to change visibility without a browser? Use 'agents artifacts share visib
|
|
|
929
953
|
// so the listing filter is named `--scope` here; `--all` is the convenience
|
|
930
954
|
// alias for `--scope all`.
|
|
931
955
|
.option('--for-user <user>', 'GitHub username whose namespace to list (default: resolved from gh/git config)')
|
|
932
|
-
.addOption(new Option('--scope <level>', 'visibility filter: public (default), unlisted, me, org, or all')
|
|
933
|
-
.choices(['public', 'unlisted', 'me', 'org', 'all'])
|
|
956
|
+
.addOption(new Option('--scope <level>', 'visibility filter: public (default), unlisted, private, me, org, or all')
|
|
957
|
+
.choices(['public', 'unlisted', 'private', 'me', 'org', 'all'])
|
|
934
958
|
.default('public'))
|
|
935
959
|
.option('--all', "list every page including hidden unlisted/me/org shares (alias for --scope all)")
|
|
936
960
|
.option('--agent <name>', 'filter to shares published by this agent/harness (case-insensitive)')
|
|
@@ -25,10 +25,12 @@ export declare function foldRegistryCandidates(agent: AgentId, inputs: RunCandid
|
|
|
25
25
|
* provider can authenticate `agent` and whose credential is present locally
|
|
26
26
|
* (RUSH-3182). This is what makes a setup-token / API-key account balance-eligible.
|
|
27
27
|
*
|
|
28
|
-
* Passed as the `collect` argument to {@link resolveRunVersion} on the RUN path
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Passed as the `collect` argument to {@link resolveRunVersion} on the RUN path,
|
|
29
|
+
* and used by session recovery (`resolveSessionRecovery`, PHNX-3626) so an
|
|
30
|
+
* origin-account limit can rotate to a healthy provider account and stay NATIVE.
|
|
31
|
+
* The other consumers of {@link collectRunCandidates} (watchdog, teams
|
|
32
|
+
* placement) keep calling it directly and are unaffected. The existing
|
|
33
|
+
* {@link pickBalancedCandidate} selects over the combined list, so the
|
|
32
34
|
* revoked / rate-limit / exhausted logic is unchanged.
|
|
33
35
|
*/
|
|
34
36
|
export declare function collectRunCandidatesForRun(agent: AgentId): Promise<RotateCandidate[]>;
|
|
@@ -63,10 +63,12 @@ export function foldRegistryCandidates(agent, inputs) {
|
|
|
63
63
|
* provider can authenticate `agent` and whose credential is present locally
|
|
64
64
|
* (RUSH-3182). This is what makes a setup-token / API-key account balance-eligible.
|
|
65
65
|
*
|
|
66
|
-
* Passed as the `collect` argument to {@link resolveRunVersion} on the RUN path
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* Passed as the `collect` argument to {@link resolveRunVersion} on the RUN path,
|
|
67
|
+
* and used by session recovery (`resolveSessionRecovery`, PHNX-3626) so an
|
|
68
|
+
* origin-account limit can rotate to a healthy provider account and stay NATIVE.
|
|
69
|
+
* The other consumers of {@link collectRunCandidates} (watchdog, teams
|
|
70
|
+
* placement) keep calling it directly and are unaffected. The existing
|
|
71
|
+
* {@link pickBalancedCandidate} selects over the combined list, so the
|
|
70
72
|
* revoked / rate-limit / exhausted logic is unchanged.
|
|
71
73
|
*/
|
|
72
74
|
export async function collectRunCandidatesForRun(agent) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `agents __usage-ingest` receiver (PHNX-3392 usage-sync).
|
|
3
3
|
*
|
|
4
|
-
* A headed peer
|
|
4
|
+
* A legacy headed peer may pipe a {@link UsageSyncPayload} JSON envelope to our
|
|
5
5
|
* stdin; we merge its identity-keyed rows into the local usage cache newest-wins
|
|
6
6
|
* ({@link ingestPeerClaudeUsageRows}). Hidden internal verb — intercepted in
|
|
7
7
|
* index.ts before bootstrap, so it never triggers an update check or a detached
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* accepting a wrong shape, but a busy cache lock degrades to best-effort inside
|
|
13
13
|
* `ingestPeerClaudeUsageRows` like every other cache writer.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* New daemon ticks use the fleet-shared store instead. This compatibility
|
|
16
|
+
* receiver remains for older installed versions. The payload arrives on stdin,
|
|
17
|
+
* EXCEPT on a Windows receiver: the `agents.ps1`
|
|
16
18
|
* shim does not forward ssh-piped stdin to the node process, so the pusher writes
|
|
17
19
|
* the payload to a temp file and passes `agents __usage-ingest --from <path>`
|
|
18
20
|
* (the same workaround the secrets push uses — `buildWindowsStdinImportCommand`).
|
|
@@ -1,107 +1,52 @@
|
|
|
1
|
-
import { type DeviceProfile } from '../devices/registry.js';
|
|
2
|
-
import { type ConfiguredDeviceRole } from '../device-config.js';
|
|
3
|
-
import { type CachedUsageSnapshot } from './usage.js';
|
|
4
|
-
/** How long a single peer push may take before it is abandoned for this tick. */
|
|
5
|
-
export declare const USAGE_PUSH_DEADLINE_MS = 20000;
|
|
6
|
-
export declare const USAGE_PULL_DEADLINE_MS = 20000;
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
2
|
+
* Fleet distribution of identity-keyed Claude usage through the user repo.
|
|
3
|
+
*
|
|
4
|
+
* A headed device can read authoritative usage; a worker's setup-token cannot.
|
|
5
|
+
* Headed daemons therefore publish one snapshot into their conflict-free
|
|
6
|
+
* `~/.agents/devices/<device>/daemon-state.json`. The daemon's bounded Git
|
|
7
|
+
* transport delivers those files automatically; workers merge newest-wins.
|
|
8
|
+
* There is deliberately no device-to-device SSH in this module or its tick.
|
|
14
9
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
import { type ConfiguredDeviceRole } from '../device-config.js';
|
|
11
|
+
import { type CachedUsageSnapshot } from './usage.js';
|
|
12
|
+
/** Legacy hidden ingest/export envelope kept for older fleet CLI compatibility. */
|
|
17
13
|
export interface UsageSyncPayload {
|
|
18
14
|
v: 1;
|
|
19
15
|
rows: Record<string, CachedUsageSnapshot>;
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/** Live-ish reachability from the tailscale snapshot; offline peers are skipped. */
|
|
27
|
-
online: boolean;
|
|
28
|
-
/** Managed known-hosts pin — an unpinned host is skipped, never TOFU-accepted. */
|
|
29
|
-
pinned: boolean;
|
|
17
|
+
export interface PublishUsageSnapshotOptions {
|
|
18
|
+
userAgentsDir?: string;
|
|
19
|
+
cachePath?: string;
|
|
20
|
+
role?: ConfiguredDeviceRole;
|
|
21
|
+
device?: string;
|
|
30
22
|
}
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
reason: string;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Decide, per peer, whether to push the local usage snapshot. Pure.
|
|
41
|
-
*
|
|
42
|
-
* - `selfIsPublisher` is false on a `worker`/unmarked box: it has no authoritative
|
|
43
|
-
* usage to publish, so every peer is skipped.
|
|
44
|
-
* - `hasLocalRows` false means the local cache is empty (nothing to teach yet).
|
|
45
|
-
* - A HEADED peer is skipped — it reads its own usage; pushing risks nothing
|
|
46
|
-
* (the merge is newest-wins) but is wasted work, and keeping the fan-out to
|
|
47
|
-
* consumers only makes the intent legible.
|
|
48
|
-
*/
|
|
49
|
-
export declare function planUsagePush(selfIsPublisher: boolean, hasLocalRows: boolean, targets: UsagePushTarget[]): UsageSyncPlanItem[];
|
|
50
|
-
export interface UsageSyncResult {
|
|
51
|
-
pushed: string[];
|
|
52
|
-
skipped: Array<{
|
|
53
|
-
device: string;
|
|
54
|
-
reason: string;
|
|
55
|
-
}>;
|
|
56
|
-
errors: Array<{
|
|
57
|
-
device: string;
|
|
58
|
-
message: string;
|
|
59
|
-
}>;
|
|
23
|
+
export interface PublishUsageSnapshotResult {
|
|
24
|
+
published: boolean;
|
|
25
|
+
changed: boolean;
|
|
26
|
+
skipped: string | null;
|
|
27
|
+
error: string | null;
|
|
28
|
+
path: string | null;
|
|
60
29
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/** Deliver the serialized payload to one peer. Default: ssh `__usage-ingest`. */
|
|
70
|
-
push?: (device: DeviceProfile, payload: string) => {
|
|
71
|
-
ok: boolean;
|
|
72
|
-
message?: string;
|
|
73
|
-
};
|
|
30
|
+
/** Publish this headed device's stable usage snapshot into its owned store file. */
|
|
31
|
+
export declare function publishUsageSnapshotToSharedStore(options?: PublishUsageSnapshotOptions): PublishUsageSnapshotResult;
|
|
32
|
+
export interface ConsumeUsageSnapshotsOptions {
|
|
33
|
+
userAgentsDir?: string;
|
|
34
|
+
cachePath?: string;
|
|
35
|
+
role?: ConfiguredDeviceRole;
|
|
36
|
+
device?: string;
|
|
37
|
+
roles?: Record<string, ConfiguredDeviceRole>;
|
|
74
38
|
}
|
|
75
|
-
export interface
|
|
76
|
-
|
|
39
|
+
export interface ConsumeUsageSnapshotsResult {
|
|
40
|
+
sources: string[];
|
|
77
41
|
merged: number;
|
|
78
42
|
skipped: string | null;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
listDevices?: () => DeviceProfile[];
|
|
84
|
-
listRoles?: () => Record<string, ConfiguredDeviceRole>;
|
|
85
|
-
isPinned?: (name: string) => boolean;
|
|
86
|
-
exportRows?: () => Record<string, CachedUsageSnapshot>;
|
|
87
|
-
ingestRows?: (rows: Record<string, CachedUsageSnapshot>) => number;
|
|
88
|
-
/** Injectable clock for the staleness gate; defaults to `Date.now`. */
|
|
89
|
-
now?: () => number;
|
|
90
|
-
/** Read the versioned payload from the primary. Default: ssh `__usage-export`. */
|
|
91
|
-
pull?: (device: DeviceProfile) => {
|
|
92
|
-
ok: boolean;
|
|
93
|
-
stdout?: string;
|
|
94
|
-
message?: string;
|
|
95
|
-
};
|
|
43
|
+
errors: Array<{
|
|
44
|
+
device: string;
|
|
45
|
+
message: string;
|
|
46
|
+
}>;
|
|
96
47
|
}
|
|
97
48
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* a `desktop` is used only when the fleet has no personal device.
|
|
101
|
-
*/
|
|
102
|
-
export declare function pullUsageFromPrimary(deps?: UsagePullDeps): UsagePullResult;
|
|
103
|
-
/**
|
|
104
|
-
* Push the local identity-keyed usage snapshot to every reachable, pinned,
|
|
105
|
-
* non-headed peer. A no-op on a non-headed box or when the local cache is empty.
|
|
49
|
+
* Read headed publishers from the local user-repo checkout and merge one
|
|
50
|
+
* newest-wins batch into the worker cache. No network or subprocess is touched.
|
|
106
51
|
*/
|
|
107
|
-
export declare function
|
|
52
|
+
export declare function consumeUsageSnapshotsFromSharedStore(options?: ConsumeUsageSnapshotsOptions): ConsumeUsageSnapshotsResult;
|