@phnx-labs/agents-cli 1.22.115 → 1.22.116
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 +149 -14
- package/README.md +1 -1
- package/dist/commands/browser.js +11 -0
- package/dist/commands/exec.js +212 -141
- package/dist/commands/feed.js +65 -14
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +157 -3
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-term.d.ts +24 -0
- package/dist/commands/setup-term.js +70 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +12 -4
- package/dist/commands/ssh.js +1 -69
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.js +2 -2
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +4 -3
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +208 -36
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +58 -3
- package/dist/lib/feed/hub-server.js +306 -54
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +21 -0
- package/dist/lib/hosts/remote-cmd.js +26 -2
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/secrets-cli.d.ts +3 -3
- package/dist/lib/secrets-cli.js +1 -1
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/setup-tool-install.js +2 -1
- package/dist/lib/setup-tool-status.d.ts +1 -1
- package/dist/lib/setup-tool-status.js +6 -1
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/package.json +1 -1
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
|
@@ -2,7 +2,9 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { buildRoutineListJson } from '../scheduling/routines.js';
|
|
4
4
|
import { backfillActiveRowsFromIndex, isRunningLiveSession, serializeActiveSessionsForJson, serializeSessionsJson } from '../session/active.js';
|
|
5
|
-
import { getConfigValue, loadAutoLaunchPreferences } from '../device-config.js';
|
|
5
|
+
import { getConfigValue, listConfiguredDeviceRoles, loadAutoLaunchPreferences } from '../device-config.js';
|
|
6
|
+
import { filterAutoPool } from '../devices/pool.js';
|
|
7
|
+
import { isFreshDeviceStats, readStatsCache } from '../devices/stats-cache.js';
|
|
6
8
|
import { MENUBAR_MENU_PROPERTIES } from '../config-keys.js';
|
|
7
9
|
import { migrateMenubarPreferencesFromUserDefaults } from './migrate-prefs.js';
|
|
8
10
|
import { loadDevices } from '../devices/registry.js';
|
|
@@ -35,12 +37,20 @@ export function buildMenuPreferences() {
|
|
|
35
37
|
*/
|
|
36
38
|
async function buildMenubarDevices() {
|
|
37
39
|
const reg = await loadDevices();
|
|
40
|
+
const roster = Object.keys(reg);
|
|
38
41
|
// Pass the roster so a fleet-wide default (fleet.defaults.config) reaches
|
|
39
42
|
// devices that have no doc of their own.
|
|
40
|
-
const prefs = loadAutoLaunchPreferences(
|
|
43
|
+
const prefs = loadAutoLaunchPreferences(roster);
|
|
44
|
+
const roles = listConfiguredDeviceRoles(roster);
|
|
45
|
+
// The placement verdict from the one canonical filter — not a re-implementation
|
|
46
|
+
// of the role rule (`devices/pool.ts` owns it).
|
|
47
|
+
const autoEligible = new Set(filterAutoPool(roster, { roles, autoLaunch: prefs }));
|
|
48
|
+
// Cache read only: no ssh, no probe, so this still rides the existing snapshot
|
|
49
|
+
// poll (docs/menubar.md: the menu bar must not probe the fleet per render).
|
|
50
|
+
const stats = readStatsCache();
|
|
41
51
|
const interactiveHost = getConfigValue('interactive.host').value;
|
|
42
52
|
const self = machineId();
|
|
43
|
-
return
|
|
53
|
+
return roster
|
|
44
54
|
.sort()
|
|
45
55
|
.map((name) => ({
|
|
46
56
|
name,
|
|
@@ -50,8 +60,37 @@ async function buildMenubarDevices() {
|
|
|
50
60
|
interactive: name === interactiveHost,
|
|
51
61
|
isLocal: name === self,
|
|
52
62
|
preferred: prefs[name]?.preferred === true,
|
|
63
|
+
role: roles[name] ?? 'unknown',
|
|
64
|
+
autoEligible: autoEligible.has(name),
|
|
65
|
+
stats: projectDeviceStats(stats[name]),
|
|
53
66
|
}));
|
|
54
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Project one cached {@link DeviceStats} row into the snapshot shape, or `null`
|
|
70
|
+
* when the device has no cached reading at all.
|
|
71
|
+
*
|
|
72
|
+
* `?? null` rather than a default: an absent number means "not observed", and the
|
|
73
|
+
* menu renders that as unavailable. A 0 would be indistinguishable from a real
|
|
74
|
+
* measurement of an idle box or a full disk.
|
|
75
|
+
*/
|
|
76
|
+
export function projectDeviceStats(row) {
|
|
77
|
+
if (!row)
|
|
78
|
+
return null;
|
|
79
|
+
return {
|
|
80
|
+
reachable: row.reachable,
|
|
81
|
+
observedAt: new Date(row.fetchedAt).toISOString(),
|
|
82
|
+
stale: !isFreshDeviceStats(row),
|
|
83
|
+
cpus: row.ncpu ?? null,
|
|
84
|
+
memTotalBytes: row.memTotalBytes ?? null,
|
|
85
|
+
memFreeBytes: row.memFreeBytes ?? null,
|
|
86
|
+
memPercent: row.memPercent ?? null,
|
|
87
|
+
diskTotalBytes: row.diskTotalBytes ?? null,
|
|
88
|
+
diskFreeBytes: row.diskFreeBytes ?? null,
|
|
89
|
+
diskUsedPercent: row.diskUsedPercent ?? null,
|
|
90
|
+
loadPercent: row.loadPercent ?? null,
|
|
91
|
+
specsObservedAt: row.specsFetchedAt ? new Date(row.specsFetchedAt).toISOString() : null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
55
94
|
export function readLastWatchdogTick(stateDir = path.join(getRuntimeStateDir(), 'watchdog')) {
|
|
56
95
|
try {
|
|
57
96
|
return JSON.parse(fs.readFileSync(path.join(stateDir, 'last-tick.json'), 'utf-8'));
|
package/dist/lib/open-url.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* The OS handler has none of that.
|
|
10
10
|
*
|
|
11
11
|
* Before this seam existed, `agents browser navigate` honoured the configured
|
|
12
|
-
* profile and nothing else did: `
|
|
12
|
+
* profile and nothing else did: `devices lease`, `feedback`, and
|
|
13
13
|
* the browser-session artifact opener each shelled straight to `open`/`xdg-open`,
|
|
14
14
|
* so every one of them landed in whatever the OS handler happened to be. This
|
|
15
|
-
* module replaces all of those call sites; do not add a
|
|
15
|
+
* module replaces all of those call sites; do not add a new raw `open`.
|
|
16
16
|
*
|
|
17
17
|
* Never throws. A viewer that cannot be reached degrades to the OS handler with
|
|
18
18
|
* one stderr line naming the reason, and a total failure returns `via: 'none'`
|
package/dist/lib/projects.d.ts
CHANGED
|
@@ -207,6 +207,29 @@ export declare function projectDirsAbs(def: ProjectDef, opts: {
|
|
|
207
207
|
* home-relative variant lands (see docs/concepts.md).
|
|
208
208
|
*/
|
|
209
209
|
export declare function projectNameForCwd(cwd: string | undefined, defs: ProjectDef[]): string | undefined;
|
|
210
|
+
export declare function listProjectDefsCached(): ProjectDef[];
|
|
211
|
+
/** Drop the {@link listProjectDefsCached} memo — for tests that rewrite the dir within one mtime tick. */
|
|
212
|
+
export declare function resetProjectDefsCache(): void;
|
|
213
|
+
/**
|
|
214
|
+
* The **confirmed** project for a working directory, or `undefined` when the
|
|
215
|
+
* association is not confirmed (PHNX-3999 F08/F09).
|
|
216
|
+
*
|
|
217
|
+
* Confirmed means exactly one thing: a registered project definition
|
|
218
|
+
* ({@link projectNameForCwd}) whose root contains this path. Being inside *some*
|
|
219
|
+
* git repository is NOT a project association — a repo nobody registered, a
|
|
220
|
+
* checkout of someone else's code, or a loose directory that merely has a
|
|
221
|
+
* `.git` in it would each invent a project name out of a folder, which is the
|
|
222
|
+
* wrong-grouping the owner's recording shows at 01:30–01:56. So is a bare
|
|
223
|
+
* directory basename ({@link resolveProjectKey}, which always answers).
|
|
224
|
+
*
|
|
225
|
+
* A consumer renders `undefined` as Uncategorized and keeps the session
|
|
226
|
+
* reachable in its full list — nothing is hidden by not being grouped.
|
|
227
|
+
*
|
|
228
|
+
* Use {@link resolveProjectNameForCwd} instead when you want a best-effort
|
|
229
|
+
* bucket KEY for joining rows; use this when the answer is shown to a person as
|
|
230
|
+
* "this work belongs to that project".
|
|
231
|
+
*/
|
|
232
|
+
export declare function confirmedProjectForCwd(cwd: string | undefined | null, defs?: ProjectDef[]): string | undefined;
|
|
210
233
|
/**
|
|
211
234
|
* The canonical project label for a cwd, for every surface that buckets work by
|
|
212
235
|
* project (the activity timeline, feed posts, the sessions overview): the
|
package/dist/lib/projects.js
CHANGED
|
@@ -455,6 +455,84 @@ export function projectNameForCwd(cwd, defs) {
|
|
|
455
455
|
}
|
|
456
456
|
return best ?? weakBest;
|
|
457
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Definition list, memoized against a stamp over the definition FILES, for the
|
|
460
|
+
* per-tick readers (PHNX-3999).
|
|
461
|
+
*
|
|
462
|
+
* {@link listProjectDefs} parses every `<name>.yaml`, which is the right cost for
|
|
463
|
+
* a command but not for a row builder on the `feed watch` / `sessions watch`
|
|
464
|
+
* stream — that runs twice a second for as long as an editor window is open (see
|
|
465
|
+
* `AGENTS.md`, every tick is budgeted). One `readdir` plus one `stat` per
|
|
466
|
+
* definition replaces N YAML parses.
|
|
467
|
+
*
|
|
468
|
+
* The stamp is each file's name + mtime + size, NOT the directory's mtime: editing
|
|
469
|
+
* a definition in place — retargeting a project's `root`, which is exactly what
|
|
470
|
+
* changes which sessions belong to it — moves the FILE's mtime and leaves the
|
|
471
|
+
* directory's untouched, so a directory stamp would serve a stale association for
|
|
472
|
+
* the life of the process. An unreadable directory is not cached: the next call
|
|
473
|
+
* retries.
|
|
474
|
+
*/
|
|
475
|
+
let projectDefsMemo = null;
|
|
476
|
+
function projectDefsStamp() {
|
|
477
|
+
try {
|
|
478
|
+
const dir = getProjectsDir();
|
|
479
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.yaml')).sort();
|
|
480
|
+
return files
|
|
481
|
+
.map((f) => {
|
|
482
|
+
try {
|
|
483
|
+
const st = fs.statSync(path.join(dir, f));
|
|
484
|
+
return `${f}:${st.mtimeMs}:${st.size}`;
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// Removed between readdir and stat — its absence is part of the stamp.
|
|
488
|
+
return `${f}:gone`;
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
.join('\n');
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
export function listProjectDefsCached() {
|
|
498
|
+
const stamp = projectDefsStamp();
|
|
499
|
+
// No projects dir (or unreadable): nothing is confirmed, and nothing to cache.
|
|
500
|
+
if (stamp === null)
|
|
501
|
+
return [];
|
|
502
|
+
if (projectDefsMemo && projectDefsMemo.stamp === stamp)
|
|
503
|
+
return projectDefsMemo.defs;
|
|
504
|
+
const defs = listProjectDefs();
|
|
505
|
+
projectDefsMemo = { stamp, defs };
|
|
506
|
+
return defs;
|
|
507
|
+
}
|
|
508
|
+
/** Drop the {@link listProjectDefsCached} memo — for tests that rewrite the dir within one mtime tick. */
|
|
509
|
+
export function resetProjectDefsCache() {
|
|
510
|
+
projectDefsMemo = null;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* The **confirmed** project for a working directory, or `undefined` when the
|
|
514
|
+
* association is not confirmed (PHNX-3999 F08/F09).
|
|
515
|
+
*
|
|
516
|
+
* Confirmed means exactly one thing: a registered project definition
|
|
517
|
+
* ({@link projectNameForCwd}) whose root contains this path. Being inside *some*
|
|
518
|
+
* git repository is NOT a project association — a repo nobody registered, a
|
|
519
|
+
* checkout of someone else's code, or a loose directory that merely has a
|
|
520
|
+
* `.git` in it would each invent a project name out of a folder, which is the
|
|
521
|
+
* wrong-grouping the owner's recording shows at 01:30–01:56. So is a bare
|
|
522
|
+
* directory basename ({@link resolveProjectKey}, which always answers).
|
|
523
|
+
*
|
|
524
|
+
* A consumer renders `undefined` as Uncategorized and keeps the session
|
|
525
|
+
* reachable in its full list — nothing is hidden by not being grouped.
|
|
526
|
+
*
|
|
527
|
+
* Use {@link resolveProjectNameForCwd} instead when you want a best-effort
|
|
528
|
+
* bucket KEY for joining rows; use this when the answer is shown to a person as
|
|
529
|
+
* "this work belongs to that project".
|
|
530
|
+
*/
|
|
531
|
+
export function confirmedProjectForCwd(cwd, defs = listProjectDefsCached()) {
|
|
532
|
+
if (!cwd)
|
|
533
|
+
return undefined;
|
|
534
|
+
return projectNameForCwd(cwd, defs);
|
|
535
|
+
}
|
|
458
536
|
/**
|
|
459
537
|
* The canonical project label for a cwd, for every surface that buckets work by
|
|
460
538
|
* project (the activity timeline, feed posts, the sessions overview): the
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare const SECRETS_CLI_NAME = "secrets";
|
|
2
2
|
export declare const SECRETS_CLI_PACKAGE = "@phnx-labs/secrets-cli";
|
|
3
3
|
/** Published standalone `agents setup secrets` installs; bump with the protocol. */
|
|
4
|
-
export declare const SECRETS_CLI_VERSION = "0.1.
|
|
5
|
-
export declare const SECRETS_CLI_SPEC = "@phnx-labs/secrets-cli@0.1.
|
|
6
|
-
export declare const SECRETS_CLI_INSTALL_HINT = "npm i -g @phnx-labs/secrets-cli@0.1.
|
|
4
|
+
export declare const SECRETS_CLI_VERSION = "0.1.5";
|
|
5
|
+
export declare const SECRETS_CLI_SPEC = "@phnx-labs/secrets-cli@0.1.5";
|
|
6
|
+
export declare const SECRETS_CLI_INSTALL_HINT = "npm i -g @phnx-labs/secrets-cli@0.1.5";
|
|
7
7
|
/**
|
|
8
8
|
* True when `$SECRETS_BIN` is set or a real `secrets` executable is on PATH
|
|
9
9
|
* outside agents-cli's shims dir. Does not spawn the binary.
|
package/dist/lib/secrets-cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { findInPath } from './agent-spec/agents.js';
|
|
|
15
15
|
export const SECRETS_CLI_NAME = 'secrets';
|
|
16
16
|
export const SECRETS_CLI_PACKAGE = '@phnx-labs/secrets-cli';
|
|
17
17
|
/** Published standalone `agents setup secrets` installs; bump with the protocol. */
|
|
18
|
-
export const SECRETS_CLI_VERSION = '0.1.
|
|
18
|
+
export const SECRETS_CLI_VERSION = '0.1.5';
|
|
19
19
|
export const SECRETS_CLI_SPEC = `${SECRETS_CLI_PACKAGE}@${SECRETS_CLI_VERSION}`;
|
|
20
20
|
export const SECRETS_CLI_INSTALL_HINT = `npm i -g ${SECRETS_CLI_SPEC}`;
|
|
21
21
|
/**
|
|
@@ -71,6 +71,7 @@ export declare function activeSessionProjectKey(s: Pick<ActiveSession, 'cwd' | '
|
|
|
71
71
|
export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<Omit<ActiveSession, 'viewingIn'> & {
|
|
72
72
|
ticketId: string | null;
|
|
73
73
|
project: string;
|
|
74
|
+
confirmedProject: string | null;
|
|
74
75
|
prLink: string | null;
|
|
75
76
|
viewingIn: string | null;
|
|
76
77
|
}>;
|
|
@@ -38,6 +38,7 @@ import { computeTokPerSec } from './throughput.js';
|
|
|
38
38
|
import { inferSessionState } from './state.js';
|
|
39
39
|
import { isSessionTrackedAgent, SESSION_AGENTS, AG_TMUX_NAME_RE } from './types.js';
|
|
40
40
|
import { AGENTS } from '../agents.js';
|
|
41
|
+
import { confirmedProjectForCwd, listProjectDefsCached } from '../projects.js';
|
|
41
42
|
import { detectProvenance } from './provenance.js';
|
|
42
43
|
import { loadDevices } from '../devices/registry.js';
|
|
43
44
|
import { machineId, normalizeHost } from '../machine-id.js';
|
|
@@ -227,10 +228,17 @@ export function activeSessionProjectKey(s) {
|
|
|
227
228
|
return s.context === 'cloud' ? 'cloud' : 'other';
|
|
228
229
|
}
|
|
229
230
|
export function serializeActiveSessionsForJson(sessions) {
|
|
231
|
+
// One definition read for the whole batch, not per row (PHNX-3999 F08/F09).
|
|
232
|
+
const defs = listProjectDefsCached();
|
|
230
233
|
return sessions.map((s) => ({
|
|
231
234
|
...s,
|
|
232
235
|
ticketId: s.ticket?.id ?? null,
|
|
233
236
|
project: activeSessionProjectKey(s),
|
|
237
|
+
// The project a person is shown this row under, or null for Uncategorized.
|
|
238
|
+
// `project` above is the always-present join KEY (basename of the cwd, or the
|
|
239
|
+
// explicit cloud/other bucket) and stays exactly as it was — grouping by it is
|
|
240
|
+
// what filed unbound directories as projects of their own.
|
|
241
|
+
confirmedProject: confirmedProjectForCwd(s.cwd, defs) ?? null,
|
|
234
242
|
prLink: s.pr?.url ?? null,
|
|
235
243
|
viewingIn: viewingInLabel(s) ?? null,
|
|
236
244
|
}));
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -31,8 +31,13 @@ export declare const SCHEMA_VERSION = 50;
|
|
|
31
31
|
* v4 (PHNX-3621 leftover) invalidates Grok rows stamped before the bounded
|
|
32
32
|
* chat_history.jsonl prefix read filled firstUserMessage.
|
|
33
33
|
* v5 (PHNX-3939) restores first-meta Codex fork ownership, including cold files.
|
|
34
|
+
* v6 (PHNX-3999) invalidates rows whose stored `label` is harness scaffolding — a
|
|
35
|
+
* `<bash-input>` shell echo, a `<command-name>` wrapper, a bare `/clear` — which
|
|
36
|
+
* `cleanGeneratedSessionLabel` now rejects. The label is written at index time, so
|
|
37
|
+
* without this bump every already-indexed session keeps showing the junk title
|
|
38
|
+
* until its transcript happens to change.
|
|
34
39
|
*/
|
|
35
|
-
export declare const CONTENT_INDEX_VERSION =
|
|
40
|
+
export declare const CONTENT_INDEX_VERSION = 6;
|
|
36
41
|
/**
|
|
37
42
|
* Bumping this invalidates every cached facet row without touching the schema
|
|
38
43
|
* version, so a change to the extraction logic (a new metric, a corrected bucket)
|
|
@@ -106,7 +111,9 @@ export interface QueryOptions {
|
|
|
106
111
|
plugin?: string;
|
|
107
112
|
}
|
|
108
113
|
/** Open (or return the cached) sessions database, applying migrations as needed. */
|
|
109
|
-
export declare function getDB(): Database.Database;
|
|
114
|
+
export declare function getDB(initialBusyTimeoutMs?: number): Database.Database;
|
|
115
|
+
/** Bound synchronous cache contention without changing other database callers. */
|
|
116
|
+
export declare function withSessionDBTimeout<T>(timeoutMs: number, operation: () => T): T;
|
|
110
117
|
/** Close the cached database connection. */
|
|
111
118
|
export declare function closeDB(): void;
|
|
112
119
|
interface FtsOptimizeResult {
|
|
@@ -428,6 +435,60 @@ export declare function writeSessionPreviewCache<T>(entry: {
|
|
|
428
435
|
fileSize: number | null;
|
|
429
436
|
preview: T;
|
|
430
437
|
}): void;
|
|
438
|
+
/** Bump when the cached remote preview envelope shape changes so cached rows recompute (PHNX-3999 v1). */
|
|
439
|
+
export declare const REMOTE_PREVIEW_SCHEMA_VERSION = 1;
|
|
440
|
+
/**
|
|
441
|
+
* Per-envelope cache-write cap. The bounded fields above make a well-formed
|
|
442
|
+
* envelope small; this exists to refuse an outlier (a version-skewed peer, or
|
|
443
|
+
* a future field that forgets to bound itself) rather than let ONE session
|
|
444
|
+
* blow the total budget. A refused write is NOT an error — the caller still
|
|
445
|
+
* gets the live envelope for this call, it simply is not persisted, so the
|
|
446
|
+
* next call re-fetches instead of silently caching a giant blob.
|
|
447
|
+
*/
|
|
448
|
+
export declare const REMOTE_PREVIEW_ENVELOPE_MAX_BYTES: number;
|
|
449
|
+
export interface RemotePreviewCacheRow {
|
|
450
|
+
/** When the envelope currently stored here (if `ok`) was fetched. */
|
|
451
|
+
fetchedAt: number;
|
|
452
|
+
/** Whether `envelope` is a real, successfully-fetched payload. */
|
|
453
|
+
ok: boolean;
|
|
454
|
+
envelope?: unknown;
|
|
455
|
+
failureReason?: string;
|
|
456
|
+
consecutiveFailures: number;
|
|
457
|
+
/** Epoch ms before which a fresh fetch attempt should be skipped (negative backoff). */
|
|
458
|
+
nextAttemptAt: number;
|
|
459
|
+
/** The caller's own last-observed `--revision` cursor, or undefined if no
|
|
460
|
+
* caller has ever supplied one for this (device, sessionId) pair. */
|
|
461
|
+
lastCallerRevision?: string;
|
|
462
|
+
}
|
|
463
|
+
/** Read the durable cached remote preview row for one (device, sessionId) pair,
|
|
464
|
+
* regardless of whether it currently holds a successful envelope. Undefined
|
|
465
|
+
* means this box has never attempted (or recorded) a fetch for that pair. */
|
|
466
|
+
export declare function readRemotePreviewCache(device: string, sessionId: string): RemotePreviewCacheRow | undefined;
|
|
467
|
+
/**
|
|
468
|
+
* Record the caller's own `--revision` cursor for one (device, sessionId)
|
|
469
|
+
* pair, independent of any fetch. A no-op if no row exists yet (nothing to
|
|
470
|
+
* compare against on a session this box has never fetched). This is what lets
|
|
471
|
+
* "same revision as last time" serve the durable cache with zero SSH
|
|
472
|
+
* indefinitely — the comparison is against THIS value, never against the
|
|
473
|
+
* envelope's own `details.sourceRevision` (which may be a different format).
|
|
474
|
+
*/
|
|
475
|
+
export declare function writeRemotePreviewCallerRevision(device: string, sessionId: string, revision: string): void;
|
|
476
|
+
/**
|
|
477
|
+
* Record a successful remote fetch: replaces the payload and resets backoff.
|
|
478
|
+
* An envelope over {@link REMOTE_PREVIEW_ENVELOPE_MAX_BYTES} is refused —
|
|
479
|
+
* this is a write-path bound, not a request failure: the caller already has
|
|
480
|
+
* the live envelope for this call from the fetch that produced it, this only
|
|
481
|
+
* decides whether it is worth persisting.
|
|
482
|
+
*/
|
|
483
|
+
export declare function writeRemotePreviewCacheSuccess(device: string, sessionId: string, envelope: unknown, fetchedAt?: number, revision?: string): void;
|
|
484
|
+
/**
|
|
485
|
+
* Record a failed remote fetch attempt with exponential backoff, WITHOUT
|
|
486
|
+
* discarding a prior good envelope — a session that answered once and is now
|
|
487
|
+
* offline still degrades to that last-good payload (read back via
|
|
488
|
+
* {@link readRemotePreviewCache}'s `ok`/`envelope`), annotated stale by the
|
|
489
|
+
* caller, rather than losing it the moment one attempt fails.
|
|
490
|
+
*/
|
|
491
|
+
export declare function writeRemotePreviewCacheFailure(device: string, sessionId: string, reason: string, backoffMs: (consecutiveFailures: number) => number, now?: number): void;
|
|
431
492
|
/**
|
|
432
493
|
* The session's durable user-turn text, as stored in the `session_text` FTS
|
|
433
494
|
* `content` column at scan time (all harnesses, keyed by session_id). This is
|
|
@@ -510,7 +571,10 @@ export declare function readSessionTimelineEntry(id: string): SessionTimelineCac
|
|
|
510
571
|
* read path. Deliberately does NOT parse the resume state: a live row needs the
|
|
511
572
|
* 8 steps and the request, never the fold's bookkeeping.
|
|
512
573
|
*/
|
|
513
|
-
export declare function readSessionTimelineAny(id: string
|
|
574
|
+
export declare function readSessionTimelineAny(id: string, stamp?: {
|
|
575
|
+
fileMtimeMs: number;
|
|
576
|
+
fileSize: number;
|
|
577
|
+
}): SessionTimelineProjection | undefined;
|
|
514
578
|
/** Persist a folded timeline against the exact transcript bytes it was folded to. */
|
|
515
579
|
export declare function writeSessionTimeline(entry: {
|
|
516
580
|
id: string;
|