@phnx-labs/agents-cli 1.22.83 → 1.22.85
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 +96 -0
- package/README.md +80 -53
- package/dist/bootstrap.d.ts +7 -6
- package/dist/bootstrap.js +20 -27
- package/dist/cli/command-registry.js +5 -1
- package/dist/commands/accounts.d.ts +6 -13
- package/dist/commands/accounts.js +330 -143
- package/dist/commands/apply.js +1 -1
- package/dist/commands/auth-mint.d.ts +7 -3
- package/dist/commands/auth-mint.js +21 -9
- package/dist/commands/auth.js +2 -3
- package/dist/commands/browser.js +10 -10
- package/dist/commands/daemon.d.ts +7 -3
- package/dist/commands/daemon.js +40 -25
- package/dist/commands/doctor.js +28 -2
- package/dist/commands/exec.js +87 -39
- package/dist/commands/fleet-capture.js +21 -2
- package/dist/commands/harness-wizard.js +2 -2
- package/dist/commands/lease.js +7 -12
- package/dist/commands/profiles.d.ts +2 -2
- package/dist/commands/profiles.js +20 -13
- package/dist/commands/repo.js +3 -3
- package/dist/commands/run-account-picker.d.ts +3 -3
- package/dist/commands/run-account-picker.js +29 -30
- package/dist/commands/secrets-passthrough.d.ts +20 -0
- package/dist/commands/secrets-passthrough.js +43 -0
- package/dist/commands/sessions-trace.d.ts +11 -0
- package/dist/commands/sessions-trace.js +71 -0
- package/dist/commands/setup-accounts.d.ts +3 -2
- package/dist/commands/setup-accounts.js +4 -4
- package/dist/commands/setup-secrets.d.ts +16 -21
- package/dist/commands/setup-secrets.js +43 -224
- package/dist/commands/ssh.js +2 -2
- package/dist/commands/sync.js +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.js +43 -40
- package/dist/commands/versions.d.ts +11 -11
- package/dist/commands/versions.js +131 -83
- package/dist/commands/view.d.ts +1 -9
- package/dist/commands/view.js +41 -55
- package/dist/commands/webhook.js +5 -5
- package/dist/commands/workflows.js +2 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +13 -42
- package/dist/lib/account-capabilities.d.ts +1 -1
- package/dist/lib/account-capabilities.js +1 -1
- package/dist/lib/account-catalog.d.ts +121 -6
- package/dist/lib/account-catalog.js +421 -7
- package/dist/lib/account-registry.d.ts +8 -30
- package/dist/lib/account-registry.js +36 -79
- package/dist/lib/account-schema.d.ts +1 -1
- package/dist/lib/account-schema.js +1 -1
- package/dist/lib/accounting/account-pool-collect.js +16 -4
- package/dist/lib/accounting/rotate.d.ts +20 -7
- package/dist/lib/accounting/rotate.js +89 -13
- package/dist/lib/accounting/usage.js +11 -16
- package/dist/lib/accounts/add.d.ts +138 -0
- package/dist/lib/accounts/add.js +651 -0
- package/dist/lib/accounts/migrate.d.ts +117 -0
- package/dist/lib/accounts/migrate.js +536 -0
- package/dist/lib/accounts/slots.d.ts +13 -0
- package/dist/lib/accounts/slots.js +100 -0
- package/dist/lib/agent-spec/agents.d.ts +16 -0
- package/dist/lib/agent-spec/agents.js +32 -4
- package/dist/lib/app-bundle-install.js +5 -4
- package/dist/lib/auth-health.d.ts +3 -0
- package/dist/lib/auth-health.js +11 -0
- package/dist/lib/auth-mint.d.ts +52 -16
- package/dist/lib/auth-mint.js +117 -39
- package/dist/lib/browser/chrome.d.ts +1 -1
- package/dist/lib/browser/chrome.js +5 -5
- package/dist/lib/byok-usage.js +3 -3
- package/dist/lib/claude-account-token.d.ts +47 -3
- package/dist/lib/claude-account-token.js +172 -50
- package/dist/lib/cloud/antigravity.js +4 -4
- package/dist/lib/cloud/cursor.js +4 -4
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +6 -6
- package/dist/lib/crabbox/runtimes.d.ts +3 -3
- package/dist/lib/crabbox/runtimes.js +5 -5
- package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
- package/dist/lib/daemon/account-state-daemon-service.js +199 -3
- package/dist/lib/daemon/auth-sync-service.js +28 -1
- package/dist/lib/daemon/daemon.d.ts +6 -16
- package/dist/lib/daemon/daemon.js +52 -97
- package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
- package/dist/lib/daemon/daemon.test-fixture.js +22 -40
- package/dist/lib/daemon/harness-update-service.d.ts +1 -1
- package/dist/lib/daemon/harness-update-service.js +1 -1
- package/dist/lib/daemon/runner.d.ts +15 -3
- package/dist/lib/daemon/runner.js +49 -24
- package/dist/lib/daemon-health.d.ts +1 -2
- package/dist/lib/daemon-health.js +7 -6
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +1 -11
- package/dist/lib/daemon-ticks.js +23 -2
- package/dist/lib/daemon-webhooks.d.ts +8 -7
- package/dist/lib/daemon-webhooks.js +10 -9
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/harness-inventory.d.ts +39 -0
- package/dist/lib/devices/harness-inventory.js +126 -4
- package/dist/lib/doctor-diff.js +2 -1
- package/dist/lib/exec-account-home.d.ts +38 -0
- package/dist/lib/exec-account-home.js +164 -0
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +101 -31
- package/dist/lib/feed/activity-stream.d.ts +60 -0
- package/dist/lib/feed/activity-stream.js +271 -0
- package/dist/lib/feed/activity.d.ts +7 -0
- package/dist/lib/feed/activity.js +10 -4
- package/dist/lib/feed/watch.d.ts +9 -3
- package/dist/lib/feed/watch.js +72 -36
- package/dist/lib/fleet/apply.d.ts +2 -2
- package/dist/lib/fleet/apply.js +4 -4
- package/dist/lib/fleet/auth-sync.js +4 -3
- package/dist/lib/fleet-shared-repo-sync.js +6 -9
- package/dist/lib/fleet-shared-state.d.ts +6 -0
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapter.js +10 -6
- package/dist/lib/harness/adapters/grok.js +8 -3
- package/dist/lib/harness/adapters/muse.js +1 -1
- package/dist/lib/harness/adapters/opencode.js +12 -3
- package/dist/lib/harness-auth-capabilities.d.ts +61 -0
- package/dist/lib/harness-auth-capabilities.js +52 -0
- package/dist/lib/helper-versions.d.ts +6 -4
- package/dist/lib/helper-versions.js +5 -4
- package/dist/lib/hosts/credential-transport.d.ts +10 -0
- package/dist/lib/hosts/credential-transport.js +37 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +13 -6
- package/dist/lib/identity/client.d.ts +3 -3
- package/dist/lib/identity/client.js +3 -3
- package/dist/lib/installations/index.d.ts +1 -1
- package/dist/lib/installations/index.js +1 -1
- package/dist/lib/installations/migrate.js +9 -0
- package/dist/lib/installations/resolve.js +1 -1
- package/dist/lib/installations/shims.d.ts +15 -0
- package/dist/lib/installations/shims.js +69 -0
- package/dist/lib/installations/store.d.ts +43 -0
- package/dist/lib/installations/store.js +75 -7
- package/dist/lib/installations/versions.js +1 -1
- package/dist/lib/menubar/install-menubar.d.ts +3 -3
- package/dist/lib/menubar/install-menubar.js +3 -3
- package/dist/lib/native-accounts.d.ts +35 -0
- package/dist/lib/native-accounts.js +41 -0
- package/dist/lib/net-close.d.ts +11 -0
- package/dist/lib/net-close.js +25 -0
- package/dist/lib/openclaw-keychain.js +27 -1
- package/dist/lib/profiles.js +22 -9
- package/dist/lib/project-resources.js +1 -1
- package/dist/lib/reserved-stores.d.ts +53 -0
- package/dist/lib/reserved-stores.js +120 -0
- package/dist/lib/secrets-client.d.ts +191 -0
- package/dist/lib/secrets-client.js +710 -0
- package/dist/lib/secrets-policy.d.ts +222 -0
- package/dist/lib/secrets-policy.js +483 -0
- package/dist/lib/secrets-types.d.ts +188 -0
- package/dist/lib/secrets-types.js +24 -0
- package/dist/lib/service-manifest.js +3 -4
- package/dist/lib/session/active.d.ts +38 -4
- package/dist/lib/session/active.js +36 -4
- package/dist/lib/session/bash-command.js +10 -0
- package/dist/lib/session/db.d.ts +65 -2
- package/dist/lib/session/db.js +178 -18
- package/dist/lib/session/mirror.d.ts +5 -0
- package/dist/lib/session/mirror.js +166 -0
- package/dist/lib/session/parse.d.ts +49 -0
- package/dist/lib/session/parse.js +324 -30
- package/dist/lib/session/prompt.d.ts +93 -2
- package/dist/lib/session/prompt.js +266 -15
- package/dist/lib/session/remote/peer-stream.d.ts +47 -0
- package/dist/lib/session/remote/peer-stream.js +142 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +41 -42
- package/dist/lib/session/session-cache.d.ts +9 -0
- package/dist/lib/session/session-cache.js +40 -2
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/timeline-pass.d.ts +129 -0
- package/dist/lib/session/timeline-pass.js +323 -0
- package/dist/lib/session/timeline.d.ts +182 -0
- package/dist/lib/session/timeline.js +636 -0
- package/dist/lib/session/types.d.ts +155 -1
- package/dist/lib/sha256-asset.d.ts +11 -16
- package/dist/lib/sha256-asset.js +11 -16
- package/dist/lib/share/config.js +14 -5
- package/dist/lib/signin-badge.d.ts +26 -0
- package/dist/lib/signin-badge.js +42 -0
- package/dist/lib/staleness/detectors/workflows.js +3 -105
- package/dist/lib/staleness/writers/workflows.js +2 -1
- package/dist/lib/state.d.ts +7 -4
- package/dist/lib/state.js +18 -7
- package/dist/lib/summarizer/pass.d.ts +2 -0
- package/dist/lib/summarizer/pass.js +14 -1
- package/dist/lib/summarizer/summarize.d.ts +7 -0
- package/dist/lib/summarizer/summarize.js +3 -0
- package/dist/lib/sync-umbrella.js +2 -2
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +74 -33
- package/dist/lib/view-types.d.ts +6 -3
- package/dist/lib/workflows-registry.d.ts +71 -0
- package/dist/lib/workflows-registry.js +280 -0
- package/dist/lib/workflows.d.ts +21 -21
- package/dist/lib/workflows.js +12 -327
- package/package.json +2 -3
- package/scripts/postinstall.js +4 -29
- package/dist/commands/secrets-import.d.ts +0 -18
- package/dist/commands/secrets-import.js +0 -74
- package/dist/commands/secrets-migrate.d.ts +0 -25
- package/dist/commands/secrets-migrate.js +0 -334
- package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
- package/dist/commands/secrets-rotate-passphrase.js +0 -96
- package/dist/commands/secrets-sync.d.ts +0 -11
- package/dist/commands/secrets-sync.js +0 -153
- package/dist/commands/secrets-vault.d.ts +0 -10
- package/dist/commands/secrets-vault.js +0 -130
- package/dist/commands/secrets.d.ts +0 -212
- package/dist/commands/secrets.js +0 -3030
- package/dist/lib/accounts/connect.d.ts +0 -170
- package/dist/lib/accounts/connect.js +0 -383
- package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
- package/dist/lib/daemon/keychain-reap-service.js +0 -32
- package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
- package/dist/lib/daemon/secrets-broker-service.js +0 -51
- package/dist/lib/secrets/agent.d.ts +0 -358
- package/dist/lib/secrets/agent.js +0 -1291
- package/dist/lib/secrets/audit.d.ts +0 -46
- package/dist/lib/secrets/audit.js +0 -101
- package/dist/lib/secrets/bundles.d.ts +0 -290
- package/dist/lib/secrets/bundles.js +0 -1547
- package/dist/lib/secrets/download-keychain.d.ts +0 -47
- package/dist/lib/secrets/download-keychain.js +0 -70
- package/dist/lib/secrets/drivers/rush.d.ts +0 -14
- package/dist/lib/secrets/drivers/rush.js +0 -90
- package/dist/lib/secrets/fallback.d.ts +0 -48
- package/dist/lib/secrets/fallback.js +0 -48
- package/dist/lib/secrets/filestore.d.ts +0 -222
- package/dist/lib/secrets/filestore.js +0 -1099
- package/dist/lib/secrets/headless.d.ts +0 -43
- package/dist/lib/secrets/headless.js +0 -56
- package/dist/lib/secrets/icloud-import.d.ts +0 -79
- package/dist/lib/secrets/icloud-import.js +0 -206
- package/dist/lib/secrets/index.d.ts +0 -451
- package/dist/lib/secrets/index.js +0 -1568
- package/dist/lib/secrets/install-helper.d.ts +0 -72
- package/dist/lib/secrets/install-helper.js +0 -245
- package/dist/lib/secrets/lease.d.ts +0 -25
- package/dist/lib/secrets/lease.js +0 -44
- package/dist/lib/secrets/linux.d.ts +0 -77
- package/dist/lib/secrets/linux.js +0 -393
- package/dist/lib/secrets/list-filter.d.ts +0 -109
- package/dist/lib/secrets/list-filter.js +0 -261
- package/dist/lib/secrets/mcp.d.ts +0 -93
- package/dist/lib/secrets/mcp.js +0 -211
- package/dist/lib/secrets/profiles.d.ts +0 -10
- package/dist/lib/secrets/profiles.js +0 -13
- package/dist/lib/secrets/push.d.ts +0 -133
- package/dist/lib/secrets/push.js +0 -273
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
- package/dist/lib/secrets/rc-hygiene.js +0 -143
- package/dist/lib/secrets/read-backoff.d.ts +0 -27
- package/dist/lib/secrets/read-backoff.js +0 -64
- package/dist/lib/secrets/reaper.d.ts +0 -90
- package/dist/lib/secrets/reaper.js +0 -243
- package/dist/lib/secrets/remote.d.ts +0 -152
- package/dist/lib/secrets/remote.js +0 -344
- package/dist/lib/secrets/reserved-sync.d.ts +0 -66
- package/dist/lib/secrets/reserved-sync.js +0 -147
- package/dist/lib/secrets/scope.d.ts +0 -26
- package/dist/lib/secrets/scope.js +0 -29
- package/dist/lib/secrets/session-store.d.ts +0 -107
- package/dist/lib/secrets/session-store.js +0 -342
- package/dist/lib/secrets/sync-backend.d.ts +0 -48
- package/dist/lib/secrets/sync-backend.js +0 -13
- package/dist/lib/secrets/sync-commands.d.ts +0 -21
- package/dist/lib/secrets/sync-commands.js +0 -21
- package/dist/lib/secrets/sync.d.ts +0 -48
- package/dist/lib/secrets/sync.js +0 -237
- package/dist/lib/secrets/unlock-hints.d.ts +0 -27
- package/dist/lib/secrets/unlock-hints.js +0 -36
- package/dist/lib/secrets/usage-db.d.ts +0 -46
- package/dist/lib/secrets/usage-db.js +0 -96
- package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
- package/dist/lib/secrets/vault-age-helper.js +0 -34
- package/dist/lib/secrets/vault.d.ts +0 -49
- package/dist/lib/secrets/vault.js +0 -399
- package/dist/lib/secrets/windows.d.ts +0 -81
- package/dist/lib/secrets/windows.js +0 -556
- package/scripts/install-helper.js +0 -97
- /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
- /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-shared types for the standalone `secrets` CLI (PHNX-3989).
|
|
3
|
+
*
|
|
4
|
+
* The standalone engine (`@phnx-labs/secrets-cli`, extracted from what used to
|
|
5
|
+
* be `cli/src/lib/secrets/**`) owns every one of these shapes; this module only
|
|
6
|
+
* re-declares them so `secrets-client.ts` and its consumers keep exact static
|
|
7
|
+
* types across the process-client boundary with no runtime dependency on the
|
|
8
|
+
* (now deleted) in-repo engine. Keep byte-for-byte in sync with
|
|
9
|
+
* `secrets-cli/src/schema.ts` / `src/core/bundles.ts` — this is the one seam
|
|
10
|
+
* both sides legitimately re-declare rather than share a package for (see the
|
|
11
|
+
* `secrets-client.ts` docblock).
|
|
12
|
+
*/
|
|
13
|
+
// --- bundle document ---------------------------------------------------
|
|
14
|
+
export const SECRET_TYPES = [
|
|
15
|
+
'api-key',
|
|
16
|
+
'token',
|
|
17
|
+
'password',
|
|
18
|
+
'url',
|
|
19
|
+
'database-url',
|
|
20
|
+
'ssh-key',
|
|
21
|
+
'certificate',
|
|
22
|
+
'webhook',
|
|
23
|
+
'note',
|
|
24
|
+
];
|
|
@@ -27,10 +27,9 @@
|
|
|
27
27
|
* module is the single place both rules live, so a new manifest has one obvious
|
|
28
28
|
* thing to call.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* of its own — so it has no HOME to bake and was deliberately left alone.
|
|
30
|
+
* A third consumer that used to keep its own literal `SERVICE_LABEL` for a
|
|
31
|
+
* retire-only `bootout` — the in-repo secrets broker's launchd cleanup — moved
|
|
32
|
+
* out of this repo entirely with the standalone `secrets` engine (PHNX-3989).
|
|
34
33
|
*/
|
|
35
34
|
import * as crypto from 'crypto';
|
|
36
35
|
import * as os from 'os';
|
|
@@ -3,7 +3,7 @@ import { type PidSessionEntry } from './pid-registry.js';
|
|
|
3
3
|
import { type HookSessionIndex } from './hook-sessions.js';
|
|
4
4
|
import { type UserPromptKind } from './prompt.js';
|
|
5
5
|
import { type SessionState, type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
|
|
6
|
-
import { type SessionAgentId, type SessionAttachment, type SessionMeta } from './types.js';
|
|
6
|
+
import { type SessionAgentId, type SessionAttachment, type SessionFiles, type SessionMeta, type SessionRequest, type SessionTimeline } from './types.js';
|
|
7
7
|
import { type SessionProvenance } from './provenance.js';
|
|
8
8
|
import { type DeviceRegistry } from '../devices/registry.js';
|
|
9
9
|
import { type Presence } from './detached.js';
|
|
@@ -55,7 +55,7 @@ export declare function attributedSetLostPids(prev: Set<number>, next: Set<numbe
|
|
|
55
55
|
export declare function filterCachedUnattributed(sessions: ActiveSession[], attributed: Set<number>, alive: (pid: number, startedAtMs?: number) => boolean): ActiveSession[];
|
|
56
56
|
type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
|
|
57
57
|
/** The SessionMeta fields the live-row backfill reads — the enrichment a running process cannot report. */
|
|
58
|
-
export type BackfillMeta = Pick<SessionMeta, 'version' | 'account' | 'timestamp' | 'label' | 'firstUserMessage' | 'ticketId' | 'prUrl' | 'prNumber' | 'origin' | 'routineName' | 'harness' | 'tokenCount' | 'durationMs' | 'subAgentCount'>;
|
|
58
|
+
export type BackfillMeta = Pick<SessionMeta, 'version' | 'account' | 'timestamp' | 'label' | 'firstUserMessage' | 'lastUserMessage' | 'ticketId' | 'prUrl' | 'prNumber' | 'origin' | 'routineName' | 'harness' | 'tokenCount' | 'durationMs' | 'subAgentCount'>;
|
|
59
59
|
export declare function backfillActiveRowsFromMeta(sessions: ActiveSession[], metaById: Map<string, BackfillMeta>): void;
|
|
60
60
|
export declare function backfillActiveRowsFromIndex(sessions: ActiveSession[]): void;
|
|
61
61
|
export declare function isRunningLiveSession(s: ActiveSession): boolean;
|
|
@@ -140,6 +140,13 @@ export interface ActiveSession {
|
|
|
140
140
|
topic?: string;
|
|
141
141
|
/** Full, cleaned first genuine user turn, backfilled from the session index. */
|
|
142
142
|
firstUserMessage?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Full, cleaned LATEST genuine user turn, backfilled from the session index
|
|
145
|
+
* beside {@link firstUserMessage}. This is what {@link deriveSessionRecap}
|
|
146
|
+
* classifies — a `/continue`d or redirected session's real request is the last
|
|
147
|
+
* thing the user said, not the first (PHNX-3939).
|
|
148
|
+
*/
|
|
149
|
+
lastUserMessage?: string;
|
|
143
150
|
/**
|
|
144
151
|
* The row's shown title — WHAT the session is, best-source-wins (RUSH-3011).
|
|
145
152
|
* The ladder ({@link deriveSessionRecap}): a `/rename` or harness `label` →
|
|
@@ -207,6 +214,22 @@ export interface ActiveSession {
|
|
|
207
214
|
spawnedTeam?: string;
|
|
208
215
|
/** Files/screenshots attached to the session prompt. */
|
|
209
216
|
attachments?: SessionAttachment[];
|
|
217
|
+
/**
|
|
218
|
+
* The session's operative request: the LATEST genuine user turn, tidied but
|
|
219
|
+
* never rewritten (PHNX-3939) — prose separated from screenshot paths, clip
|
|
220
|
+
* references, `@dir` mentions and pasted terminal echo. Folded by the daemon
|
|
221
|
+
* timeline pass and merged from the `session_timelines` cache; the sidebar's
|
|
222
|
+
* Request card reads it straight off the row.
|
|
223
|
+
*/
|
|
224
|
+
request?: SessionRequest;
|
|
225
|
+
/**
|
|
226
|
+
* Narration-anchored steps — what the agent has been doing, in its own words
|
|
227
|
+
* (PHNX-3939). Last 8 in full plus a fold of everything older. Produced by the
|
|
228
|
+
* daemon's timeline pass, never on the request path.
|
|
229
|
+
*/
|
|
230
|
+
timeline?: SessionTimeline;
|
|
231
|
+
/** Files the session created, modified or deleted; ≤ 8 rows plus a total. */
|
|
232
|
+
files?: SessionFiles;
|
|
210
233
|
/** Total tokens and elapsed duration from the indexed transcript. */
|
|
211
234
|
tokenCount?: number;
|
|
212
235
|
durationMs?: number;
|
|
@@ -906,13 +929,24 @@ export declare function sessionProcessIsLocal(s: Pick<ActiveSession, 'machine' |
|
|
|
906
929
|
*/
|
|
907
930
|
export declare function sessionProcessHost(s: Pick<ActiveSession, 'machine' | 'offloadedFrom'>, self: string): string | undefined;
|
|
908
931
|
export declare function foldHostLink(rows: ActiveSession[]): void;
|
|
909
|
-
/**
|
|
910
|
-
|
|
932
|
+
/**
|
|
933
|
+
* Labels win, then the last assistant line, then the prompt.
|
|
934
|
+
*
|
|
935
|
+
* The prompt rung classifies the RAW latest genuine user turn, not the
|
|
936
|
+
* already-collapsed `topic` (PHNX-3939). Classifying `topic` was how a `/model`
|
|
937
|
+
* echo or a skill body became a session's displayed title: `extractSessionTopic`
|
|
938
|
+
* accepted text `cleanFirstUserMessage` rejected, and the recap then dressed up
|
|
939
|
+
* that scaffolding as the user's words. It also never saw the row's attachments,
|
|
940
|
+
* so an image-only turn could not be recognized. A row that has already been
|
|
941
|
+
* merged with a daemon-folded {@link ActiveSession.request} uses it directly.
|
|
942
|
+
*/
|
|
943
|
+
export declare function deriveSessionRecap(row: Pick<ActiveSession, 'label' | 'topic' | 'tail' | 'firstUserMessage' | 'lastUserMessage' | 'attachments' | 'request'>): {
|
|
911
944
|
title?: string;
|
|
912
945
|
recapSource?: RecapSource;
|
|
913
946
|
userPromptClean?: string;
|
|
914
947
|
userPromptKind?: UserPromptKind;
|
|
915
948
|
lastAgentLine?: string;
|
|
949
|
+
request?: SessionRequest;
|
|
916
950
|
};
|
|
917
951
|
/** Fold the recap ladder onto every row (see {@link deriveSessionRecap}). */
|
|
918
952
|
export declare function foldRecap(rows: ActiveSession[]): void;
|
|
@@ -30,7 +30,7 @@ import { loadHookSessionIndex, resolveHookSessionRecord, readStateSessionRecord
|
|
|
30
30
|
import { buildClaudeLabelMap, getAgentSessionDirs } from './discover.js';
|
|
31
31
|
import { buildRunNameMap } from './run-names.js';
|
|
32
32
|
import { latestSessionFileForCwd, findSessionsByShortIds, findSessionMachinesByIds, getSessionById } from './db.js';
|
|
33
|
-
import { extractSessionTopic, classifyUserPrompt } from './prompt.js';
|
|
33
|
+
import { extractSessionTopic, classifyUserPrompt, tidyRequest } from './prompt.js';
|
|
34
34
|
import { readSessionTailWithRaw } from './tail.js';
|
|
35
35
|
import { parseSession } from './parse.js';
|
|
36
36
|
import { computeTokPerSec } from './throughput.js';
|
|
@@ -151,6 +151,8 @@ export function backfillActiveRowsFromMeta(sessions, metaById) {
|
|
|
151
151
|
s.label = m.label;
|
|
152
152
|
if (!s.firstUserMessage && m.firstUserMessage)
|
|
153
153
|
s.firstUserMessage = m.firstUserMessage;
|
|
154
|
+
if (!s.lastUserMessage && m.lastUserMessage)
|
|
155
|
+
s.lastUserMessage = m.lastUserMessage;
|
|
154
156
|
if (!s.ticket && m.ticketId)
|
|
155
157
|
s.ticket = { id: m.ticketId, url: linearIssueUrl(m.ticketId) };
|
|
156
158
|
if (!s.pr && m.prUrl)
|
|
@@ -2101,10 +2103,31 @@ function recapLine(s, max = 120) {
|
|
|
2101
2103
|
return undefined;
|
|
2102
2104
|
return t.length > max ? t.slice(0, max - 1).trimEnd() + '…' : t;
|
|
2103
2105
|
}
|
|
2104
|
-
/**
|
|
2106
|
+
/**
|
|
2107
|
+
* Labels win, then the last assistant line, then the prompt.
|
|
2108
|
+
*
|
|
2109
|
+
* The prompt rung classifies the RAW latest genuine user turn, not the
|
|
2110
|
+
* already-collapsed `topic` (PHNX-3939). Classifying `topic` was how a `/model`
|
|
2111
|
+
* echo or a skill body became a session's displayed title: `extractSessionTopic`
|
|
2112
|
+
* accepted text `cleanFirstUserMessage` rejected, and the recap then dressed up
|
|
2113
|
+
* that scaffolding as the user's words. It also never saw the row's attachments,
|
|
2114
|
+
* so an image-only turn could not be recognized. A row that has already been
|
|
2115
|
+
* merged with a daemon-folded {@link ActiveSession.request} uses it directly.
|
|
2116
|
+
*/
|
|
2105
2117
|
export function deriveSessionRecap(row) {
|
|
2106
2118
|
const lastAgentLine = recapLine(row.tail?.length ? row.tail[row.tail.length - 1] : undefined);
|
|
2107
|
-
const
|
|
2119
|
+
const raw = row.lastUserMessage ?? row.firstUserMessage ?? row.topic ?? '';
|
|
2120
|
+
// No `turns` here on purpose: this tidy sees ONE turn, not the transcript, so
|
|
2121
|
+
// it cannot count them. The daemon fold sets it (see SessionRequest.turns).
|
|
2122
|
+
const tidied = row.request ?? tidyRequest(raw, { attachments: row.attachments });
|
|
2123
|
+
// No genuine turn to show (a synthetic-only transcript, or nothing indexed
|
|
2124
|
+
// yet): fall back to the legacy classifier over whatever `topic` holds rather
|
|
2125
|
+
// than showing an empty prompt rung.
|
|
2126
|
+
const fallback = tidied ? undefined : classifyUserPrompt(row.topic ?? '', {
|
|
2127
|
+
hasImageAttachment: row.attachments?.some((a) => a.mediaType?.startsWith('image/')),
|
|
2128
|
+
});
|
|
2129
|
+
const userPromptClean = tidied?.headline ?? fallback?.clean;
|
|
2130
|
+
const userPromptKind = tidied?.kind ?? fallback?.kind;
|
|
2108
2131
|
const label = recapLine(row.label);
|
|
2109
2132
|
const prompt = recapLine(userPromptClean || row.topic);
|
|
2110
2133
|
let title;
|
|
@@ -2121,7 +2144,14 @@ export function deriveSessionRecap(row) {
|
|
|
2121
2144
|
title = prompt;
|
|
2122
2145
|
recapSource = 'prompt';
|
|
2123
2146
|
}
|
|
2124
|
-
return {
|
|
2147
|
+
return {
|
|
2148
|
+
title,
|
|
2149
|
+
recapSource,
|
|
2150
|
+
userPromptClean: recapLine(userPromptClean),
|
|
2151
|
+
userPromptKind,
|
|
2152
|
+
lastAgentLine,
|
|
2153
|
+
...(tidied ? { request: tidied } : {}),
|
|
2154
|
+
};
|
|
2125
2155
|
}
|
|
2126
2156
|
/** Fold the recap ladder onto every row (see {@link deriveSessionRecap}). */
|
|
2127
2157
|
export function foldRecap(rows) {
|
|
@@ -2132,6 +2162,8 @@ export function foldRecap(rows) {
|
|
|
2132
2162
|
s.userPromptClean = recap.userPromptClean;
|
|
2133
2163
|
s.userPromptKind = recap.userPromptKind;
|
|
2134
2164
|
s.lastAgentLine = recap.lastAgentLine;
|
|
2165
|
+
if (recap.request)
|
|
2166
|
+
s.request = recap.request;
|
|
2135
2167
|
}
|
|
2136
2168
|
}
|
|
2137
2169
|
/**
|
|
@@ -456,6 +456,16 @@ export function detectBashMilestone(command) {
|
|
|
456
456
|
if (lower.includes('pr merge'))
|
|
457
457
|
return { event: 'pr.merged', detail: 'gh pr merge' };
|
|
458
458
|
}
|
|
459
|
+
// The repo's own toolchain: spawning a team and rendering an artifact are the
|
|
460
|
+
// two things an operator looks for in a session's history alongside a commit
|
|
461
|
+
// or a PR, and both are shell-driven (PHNX-3939).
|
|
462
|
+
if (info.tool === 'agents' && info.subcommand === 'teams'
|
|
463
|
+
&& /\bteams\s+(create|add|start)\b/.test(lower)) {
|
|
464
|
+
return { event: 'team.spawned', detail: 'agents teams' };
|
|
465
|
+
}
|
|
466
|
+
if (/\bartifacts\s+render\b/.test(lower)) {
|
|
467
|
+
return { event: 'artifact.rendered', detail: 'artifacts render' };
|
|
468
|
+
}
|
|
459
469
|
return null;
|
|
460
470
|
}
|
|
461
471
|
/**
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* BM25 ranking, and label-first search for /rename'd sessions.
|
|
8
8
|
*/
|
|
9
9
|
import Database from '../sqlite.js';
|
|
10
|
-
import type { SessionAgentId, SessionCheckpoint, SessionChecklistItem, SessionEvent, SessionMeta, SummaryState } from './types.js';
|
|
10
|
+
import type { SessionAgentId, SessionCheckpoint, SessionChecklistItem, SessionEvent, SessionFiles, SessionMeta, SessionRequest, SessionTimeline, SummaryState } from './types.js';
|
|
11
11
|
import { type IndexedToolCall } from './tool-calls.js';
|
|
12
12
|
import { type ToolScanResumePoint } from './tool-store.js';
|
|
13
|
+
import { type TimelineState } from './timeline.js';
|
|
13
14
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
14
15
|
* assert against the constant instead of hardcoding a number that every bump
|
|
15
16
|
* then has to chase (docs/sessions.md calls the constant the source of truth). */
|
|
16
|
-
export declare const SCHEMA_VERSION =
|
|
17
|
+
export declare const SCHEMA_VERSION = 48;
|
|
17
18
|
/**
|
|
18
19
|
* Bump to force the content extractor (assistant-answer text, alongside the
|
|
19
20
|
* user-prompt text every harness already accumulates) to re-derive on every
|
|
@@ -478,6 +479,46 @@ export declare function writeSessionSummary(entry: {
|
|
|
478
479
|
fileSize: number | null;
|
|
479
480
|
summary: SessionSummaryEntry;
|
|
480
481
|
}): void;
|
|
482
|
+
/**
|
|
483
|
+
* The daemon-computed timeline stored in `session_timelines` (PHNX-3939): the
|
|
484
|
+
* bounded projection a row merges, plus the request and files derived in the
|
|
485
|
+
* same fold.
|
|
486
|
+
*/
|
|
487
|
+
export interface SessionTimelineProjection {
|
|
488
|
+
timeline: SessionTimeline;
|
|
489
|
+
request?: SessionRequest;
|
|
490
|
+
files?: SessionFiles;
|
|
491
|
+
}
|
|
492
|
+
/** One folded timeline, as the pass produces it for writing. */
|
|
493
|
+
export interface SessionTimelineEntry extends SessionTimelineProjection {
|
|
494
|
+
state: TimelineState;
|
|
495
|
+
}
|
|
496
|
+
/** One cached row as READ back: the folded entry plus when it was folded. */
|
|
497
|
+
export interface SessionTimelineCacheRow extends SessionTimelineEntry {
|
|
498
|
+
/** Epoch ms this row was folded — the pass's re-parse rate limit reads it. */
|
|
499
|
+
computedAt: number;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Read the projection AND the resume state for one session, whatever the
|
|
503
|
+
* transcript stamp — the daemon pass's read, which needs the state to continue
|
|
504
|
+
* from and re-stats the file itself.
|
|
505
|
+
*/
|
|
506
|
+
export declare function readSessionTimelineEntry(id: string): SessionTimelineCacheRow | undefined;
|
|
507
|
+
/**
|
|
508
|
+
* Read only the bounded projection for one session — the display merge, on the
|
|
509
|
+
* read path. Deliberately does NOT parse the resume state: a live row needs the
|
|
510
|
+
* 8 steps and the request, never the fold's bookkeeping.
|
|
511
|
+
*/
|
|
512
|
+
export declare function readSessionTimelineAny(id: string): SessionTimelineProjection | undefined;
|
|
513
|
+
/** Persist a folded timeline against the exact transcript bytes it was folded to. */
|
|
514
|
+
export declare function writeSessionTimeline(entry: {
|
|
515
|
+
id: string;
|
|
516
|
+
fileMtimeMs: number | null;
|
|
517
|
+
fileSize: number | null;
|
|
518
|
+
timeline: SessionTimelineEntry;
|
|
519
|
+
/** Fold time. Injected by the pass so its re-parse interval is testable without the wall clock. */
|
|
520
|
+
computedAtMs?: number;
|
|
521
|
+
}): void;
|
|
481
522
|
/** A local-origin session, projected to the compact fields the fleet mirror publishes. */
|
|
482
523
|
export interface LocalMirrorSource {
|
|
483
524
|
id: string;
|
|
@@ -495,6 +536,8 @@ export interface LocalMirrorSource {
|
|
|
495
536
|
prUrl: string | null;
|
|
496
537
|
/** Daemon-computed summary (PHNX-3939), so peers carry it without a transcript. */
|
|
497
538
|
summary: SessionSummaryEntry | null;
|
|
539
|
+
/** Daemon-folded request/timeline/files (PHNX-3939), same reason as {@link summary}. */
|
|
540
|
+
timeline: SessionTimelineProjection | null;
|
|
498
541
|
}
|
|
499
542
|
/**
|
|
500
543
|
* The most-recently-active sessions whose transcript is genuinely local to this
|
|
@@ -521,6 +564,8 @@ export interface MirrorSessionUpsert {
|
|
|
521
564
|
prUrl?: string | null;
|
|
522
565
|
/** Daemon-computed summary carried from the publishing peer (PHNX-3939). */
|
|
523
566
|
summary?: SessionSummaryEntry | null;
|
|
567
|
+
/** Daemon-folded request/timeline/files carried from the publishing peer (PHNX-3939). */
|
|
568
|
+
timeline?: SessionTimelineProjection | null;
|
|
524
569
|
}
|
|
525
570
|
/**
|
|
526
571
|
* Write one peer session's digest into the local `sessions` index as a mirror
|
|
@@ -772,6 +817,22 @@ export declare function buildFtsQuery(input: string): {
|
|
|
772
817
|
* so "higher = better" is consistent across all tiers.
|
|
773
818
|
*/
|
|
774
819
|
export declare function ftsSearch(input: string, limit?: number): FtsHit[];
|
|
820
|
+
/**
|
|
821
|
+
* Rewrite file_path for sessions whose path starts with any of `moves.from`,
|
|
822
|
+
* replacing that prefix with `moves.to` and clearing the matching scan_ledger
|
|
823
|
+
* entries so the next scan re-indexes from the new location.
|
|
824
|
+
*
|
|
825
|
+
* One transaction for the whole batch (PHNX-3940 T7): a migration that moves
|
|
826
|
+
* several homes into slots and several version dirs into trash must not leave
|
|
827
|
+
* the index half-rewritten. Longest `from` wins when prefixes nest, and each
|
|
828
|
+
* session row is rewritten at most once.
|
|
829
|
+
*
|
|
830
|
+
* Returns the number of session rows updated.
|
|
831
|
+
*/
|
|
832
|
+
export declare function reindexMovedSessionPaths(moves: ReadonlyArray<{
|
|
833
|
+
from: string;
|
|
834
|
+
to: string;
|
|
835
|
+
}>): number;
|
|
775
836
|
/**
|
|
776
837
|
* Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
|
|
777
838
|
* it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
|
|
@@ -782,6 +843,8 @@ export declare function ftsSearch(input: string, limit?: number): FtsHit[];
|
|
|
782
843
|
* Returns the number of session rows updated.
|
|
783
844
|
*/
|
|
784
845
|
export declare function updateSessionFilePaths(oldPrefix: string, newPrefix: string): number;
|
|
846
|
+
/** Count indexed sessions whose transcript path sits under `prefix`. */
|
|
847
|
+
export declare function countSessionsWithFilePrefix(prefix: string): number;
|
|
785
848
|
/** Per-kind capture tallies for a browser task. Counts only -- never the bytes. */
|
|
786
849
|
interface BrowserCaptureCounts {
|
|
787
850
|
screenshot: number;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -22,13 +22,14 @@ import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTr
|
|
|
22
22
|
import { resolveResource } from '../resources.js';
|
|
23
23
|
import { discoverPlugins } from '../plugins/plugins.js';
|
|
24
24
|
import { machineId } from '../machine-id.js';
|
|
25
|
-
import { firstUserMessageFromEvents } from './prompt.js';
|
|
25
|
+
import { firstUserMessageFromEvents, lastUserMessageFromEvents } from './prompt.js';
|
|
26
|
+
import { emptyTimelineState, TIMELINE_EXTRACTOR_VERSION } from './timeline.js';
|
|
26
27
|
const SESSIONS_DIR = getSessionsDir();
|
|
27
28
|
const DB_PATH = getSessionsDbPath();
|
|
28
29
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
29
30
|
* assert against the constant instead of hardcoding a number that every bump
|
|
30
31
|
* then has to chase (docs/sessions.md calls the constant the source of truth). */
|
|
31
|
-
export const SCHEMA_VERSION =
|
|
32
|
+
export const SCHEMA_VERSION = 48;
|
|
32
33
|
/**
|
|
33
34
|
* Bump to force the content extractor (assistant-answer text, alongside the
|
|
34
35
|
* user-prompt text every harness already accumulates) to re-derive on every
|
|
@@ -103,6 +104,7 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
103
104
|
git_branch TEXT,
|
|
104
105
|
topic TEXT,
|
|
105
106
|
first_user_message TEXT,
|
|
107
|
+
last_user_message TEXT,
|
|
106
108
|
label TEXT,
|
|
107
109
|
message_count INTEGER,
|
|
108
110
|
token_count INTEGER,
|
|
@@ -420,6 +422,27 @@ CREATE TABLE IF NOT EXISTS session_summaries (
|
|
|
420
422
|
summary_json TEXT NOT NULL
|
|
421
423
|
);
|
|
422
424
|
|
|
425
|
+
-- Daemon-computed narration-anchored timeline (PHNX-3939). Same lazy,
|
|
426
|
+
-- stamp-validated cache shape as session_summaries, with one addition: the fold
|
|
427
|
+
-- is INCREMENTAL, so the row carries both the bounded projection the display
|
|
428
|
+
-- path reads and the resume state the pass folds the next appended bytes onto.
|
|
429
|
+
--
|
|
430
|
+
-- Two columns rather than one on purpose. projection_json is small and bounded
|
|
431
|
+
-- (8 steps + counters + the tidied request + 8 file rows) and is what a session
|
|
432
|
+
-- row merges on the read path; state_json holds the whole TimelineState (byte
|
|
433
|
+
-- offset, open step, pending call ids, per-path file ledger) and is read ONLY by
|
|
434
|
+
-- the daemon pass. Folding one blob would make every row merge parse the resume
|
|
435
|
+
-- state it has no use for.
|
|
436
|
+
CREATE TABLE IF NOT EXISTS session_timelines (
|
|
437
|
+
session_id TEXT PRIMARY KEY,
|
|
438
|
+
file_mtime_ms INTEGER,
|
|
439
|
+
file_size INTEGER,
|
|
440
|
+
extractor_version INTEGER NOT NULL,
|
|
441
|
+
computed_at INTEGER NOT NULL,
|
|
442
|
+
projection_json TEXT NOT NULL,
|
|
443
|
+
state_json TEXT NOT NULL
|
|
444
|
+
);
|
|
445
|
+
|
|
423
446
|
-- Durable metadata for one browser task (RUSH-2549). The browser daemon's
|
|
424
447
|
-- tasks.json is LIVE state: saveTaskState writes the in-memory task map, so
|
|
425
448
|
-- stopping a task drops its entry and a daemon restart empties the file. That is
|
|
@@ -1286,6 +1309,25 @@ function migrateSchema(db, fromVersion) {
|
|
|
1286
1309
|
// The idx_sessions_mirror_synced index is created unconditionally after this
|
|
1287
1310
|
// block (fresh DBs skip migrations), alongside idx_sessions_last_activity.
|
|
1288
1311
|
}
|
|
1312
|
+
if (fromVersion < 48) {
|
|
1313
|
+
// v47 -> v48: retain the LATEST genuine user turn beside the first
|
|
1314
|
+
// (PHNX-3939). For a /continue, a redirect, or an interrupted-and-restated
|
|
1315
|
+
// session the operative request is the last thing the user said, and the row
|
|
1316
|
+
// title / sidebar Request card show that.
|
|
1317
|
+
//
|
|
1318
|
+
// Deliberately WITHOUT a CONTENT_INDEX_VERSION bump, unlike the v45
|
|
1319
|
+
// first_user_message column. That bump forces a re-parse of every indexed
|
|
1320
|
+
// transcript on the next scan; measured on this fleet it pinned the daemon
|
|
1321
|
+
// long enough to blow the session-index and session-state tick deadlines and
|
|
1322
|
+
// park both services. It also buys nothing here: the value can only CHANGE
|
|
1323
|
+
// when the transcript changes, so the ordinary incremental scan fills it in
|
|
1324
|
+
// for exactly the sessions where it matters, and the sidebar's request comes
|
|
1325
|
+
// from the daemon-folded `session_timelines` projection rather than this
|
|
1326
|
+
// column. An old row simply keeps NULL until its next write.
|
|
1327
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
|
|
1328
|
+
if (!cols.has('last_user_message'))
|
|
1329
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN last_user_message TEXT`);
|
|
1330
|
+
}
|
|
1289
1331
|
if (fromVersion < 47) {
|
|
1290
1332
|
// v46 -> v47: carry the actor's Phoenix id (PHNX-3798). Additive, write-once
|
|
1291
1333
|
// launch metadata joined from the actor sidecar — never transcript-derived,
|
|
@@ -1842,7 +1884,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1842
1884
|
INSERT INTO sessions (
|
|
1843
1885
|
id, short_id, agent, harness, origin, routine_name, routine_run_id,
|
|
1844
1886
|
version, account, account_key, account_org, mode, timestamp, last_activity,
|
|
1845
|
-
project, cwd, git_branch, topic, first_user_message, label, message_count, token_count,
|
|
1887
|
+
project, cwd, git_branch, topic, first_user_message, last_user_message, label, message_count, token_count,
|
|
1846
1888
|
output_tokens, input_tokens, cache_read_tokens, cache_write_tokens,
|
|
1847
1889
|
cost_usd, cost_usd_nocache, duration_ms, model, tool_call_count,
|
|
1848
1890
|
file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
|
|
@@ -1853,7 +1895,7 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1853
1895
|
) VALUES (
|
|
1854
1896
|
@id, @short_id, @agent, @harness, @origin, @routine_name, @routine_run_id,
|
|
1855
1897
|
@version, @account, @account_key, @account_org, @mode, @timestamp, @last_activity,
|
|
1856
|
-
@project, @cwd, @git_branch, @topic, @first_user_message, @label, @message_count, @token_count,
|
|
1898
|
+
@project, @cwd, @git_branch, @topic, @first_user_message, @last_user_message, @label, @message_count, @token_count,
|
|
1857
1899
|
@output_tokens, @input_tokens, @cache_read_tokens, @cache_write_tokens,
|
|
1858
1900
|
@cost_usd, @cost_usd_nocache, @duration_ms, @model, @tool_call_count,
|
|
1859
1901
|
@file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
|
|
@@ -1891,6 +1933,10 @@ const upsertSessionStmt = (db) => db.prepare(`
|
|
|
1891
1933
|
git_branch = excluded.git_branch,
|
|
1892
1934
|
topic = excluded.topic,
|
|
1893
1935
|
first_user_message = COALESCE(excluded.first_user_message, sessions.first_user_message),
|
|
1936
|
+
-- Unlike the first turn, the LATEST one moves: a new turn must replace the
|
|
1937
|
+
-- stored value, so this is excluded-wins with a COALESCE only to keep a
|
|
1938
|
+
-- known value when a rescan cannot re-derive one.
|
|
1939
|
+
last_user_message = COALESCE(excluded.last_user_message, sessions.last_user_message),
|
|
1894
1940
|
-- Never let an empty/placeholder incoming label clobber a good stored one.
|
|
1895
1941
|
-- A real incoming label (non-empty after trim) still wins; a blank one keeps
|
|
1896
1942
|
-- the label seeded by --name (seedLabelsFromNames) or refined by an agent
|
|
@@ -2110,6 +2156,7 @@ function enrichMetaFromEvents(meta, events) {
|
|
|
2110
2156
|
return {
|
|
2111
2157
|
...meta,
|
|
2112
2158
|
firstUserMessage: meta.firstUserMessage ?? firstUserMessageFromEvents(events),
|
|
2159
|
+
lastUserMessage: meta.lastUserMessage ?? lastUserMessageFromEvents(events),
|
|
2113
2160
|
todos: extractTodoProgressFromEvents(events),
|
|
2114
2161
|
recentDirectoriesTouched: extractRecentDirectoriesTouched(events, meta.cwd),
|
|
2115
2162
|
...fanOutCounts(events, meta.agent),
|
|
@@ -2201,6 +2248,7 @@ export function upsertSession(meta, content, scan, assistantContent = '') {
|
|
|
2201
2248
|
git_branch: meta.gitBranch ?? null,
|
|
2202
2249
|
topic: meta.topic ?? null,
|
|
2203
2250
|
first_user_message: meta.firstUserMessage ?? null,
|
|
2251
|
+
last_user_message: meta.lastUserMessage ?? null,
|
|
2204
2252
|
label: meta.label ?? null,
|
|
2205
2253
|
message_count: meta.messageCount ?? null,
|
|
2206
2254
|
token_count: meta.tokenCount ?? null,
|
|
@@ -2441,6 +2489,7 @@ export function upsertSessionsBatch(entries) {
|
|
|
2441
2489
|
git_branch: meta.gitBranch ?? null,
|
|
2442
2490
|
topic: meta.topic ?? null,
|
|
2443
2491
|
first_user_message: meta.firstUserMessage ?? null,
|
|
2492
|
+
last_user_message: meta.lastUserMessage ?? null,
|
|
2444
2493
|
label: meta.label ?? null,
|
|
2445
2494
|
message_count: meta.messageCount ?? null,
|
|
2446
2495
|
token_count: meta.tokenCount ?? null,
|
|
@@ -2690,6 +2739,7 @@ function rowToMeta(row) {
|
|
|
2690
2739
|
mode: isSessionRunMode(row.mode) ? row.mode : undefined,
|
|
2691
2740
|
topic: row.topic ?? undefined,
|
|
2692
2741
|
firstUserMessage: row.first_user_message ?? undefined,
|
|
2742
|
+
lastUserMessage: row.last_user_message ?? undefined,
|
|
2693
2743
|
label: row.label ?? undefined,
|
|
2694
2744
|
isTeamOrigin: row.is_team_origin === 1,
|
|
2695
2745
|
prUrl: row.pr_url ?? undefined,
|
|
@@ -3417,6 +3467,68 @@ export function writeSessionSummary(entry) {
|
|
|
3417
3467
|
summary_json = excluded.summary_json
|
|
3418
3468
|
`).run(entry.id, entry.fileMtimeMs, entry.fileSize, SESSION_SUMMARY_EXTRACTOR_VERSION, Date.now(), JSON.stringify(entry.summary));
|
|
3419
3469
|
}
|
|
3470
|
+
function parseTimelineProjection(json) {
|
|
3471
|
+
try {
|
|
3472
|
+
return JSON.parse(json);
|
|
3473
|
+
}
|
|
3474
|
+
catch {
|
|
3475
|
+
return undefined;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Read the projection AND the resume state for one session, whatever the
|
|
3480
|
+
* transcript stamp — the daemon pass's read, which needs the state to continue
|
|
3481
|
+
* from and re-stats the file itself.
|
|
3482
|
+
*/
|
|
3483
|
+
export function readSessionTimelineEntry(id) {
|
|
3484
|
+
const row = getDB().prepare(`
|
|
3485
|
+
SELECT projection_json AS projectionJson, state_json AS stateJson, computed_at AS computedAt
|
|
3486
|
+
FROM session_timelines
|
|
3487
|
+
WHERE session_id = ? AND extractor_version = ?
|
|
3488
|
+
`).get(id, TIMELINE_EXTRACTOR_VERSION);
|
|
3489
|
+
if (!row)
|
|
3490
|
+
return undefined;
|
|
3491
|
+
const projection = parseTimelineProjection(row.projectionJson);
|
|
3492
|
+
if (!projection)
|
|
3493
|
+
return undefined;
|
|
3494
|
+
try {
|
|
3495
|
+
return { ...projection, state: JSON.parse(row.stateJson), computedAt: row.computedAt };
|
|
3496
|
+
}
|
|
3497
|
+
catch {
|
|
3498
|
+
return undefined;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Read only the bounded projection for one session — the display merge, on the
|
|
3503
|
+
* read path. Deliberately does NOT parse the resume state: a live row needs the
|
|
3504
|
+
* 8 steps and the request, never the fold's bookkeeping.
|
|
3505
|
+
*/
|
|
3506
|
+
export function readSessionTimelineAny(id) {
|
|
3507
|
+
const row = getDB().prepare(`
|
|
3508
|
+
SELECT projection_json AS projectionJson
|
|
3509
|
+
FROM session_timelines
|
|
3510
|
+
WHERE session_id = ? AND extractor_version = ?
|
|
3511
|
+
`).get(id, TIMELINE_EXTRACTOR_VERSION);
|
|
3512
|
+
if (!row)
|
|
3513
|
+
return undefined;
|
|
3514
|
+
return parseTimelineProjection(row.projectionJson);
|
|
3515
|
+
}
|
|
3516
|
+
/** Persist a folded timeline against the exact transcript bytes it was folded to. */
|
|
3517
|
+
export function writeSessionTimeline(entry) {
|
|
3518
|
+
const { state, ...projection } = entry.timeline;
|
|
3519
|
+
getDB().prepare(`
|
|
3520
|
+
INSERT INTO session_timelines
|
|
3521
|
+
(session_id, file_mtime_ms, file_size, extractor_version, computed_at, projection_json, state_json)
|
|
3522
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3523
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
3524
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
3525
|
+
file_size = excluded.file_size,
|
|
3526
|
+
extractor_version = excluded.extractor_version,
|
|
3527
|
+
computed_at = excluded.computed_at,
|
|
3528
|
+
projection_json = excluded.projection_json,
|
|
3529
|
+
state_json = excluded.state_json
|
|
3530
|
+
`).run(entry.id, entry.fileMtimeMs, entry.fileSize, TIMELINE_EXTRACTOR_VERSION, entry.computedAtMs ?? Date.now(), JSON.stringify(projection), JSON.stringify(state));
|
|
3531
|
+
}
|
|
3420
3532
|
/**
|
|
3421
3533
|
* The most-recently-active sessions whose transcript is genuinely local to this
|
|
3422
3534
|
* box — a real `file_path`, not a peer mirror — for publishing to the fleet
|
|
@@ -3444,6 +3556,7 @@ export function queryLocalOriginSessionsForMirror(self, limit) {
|
|
|
3444
3556
|
// Ride the daemon-computed summary alongside the digest so a peer renders it
|
|
3445
3557
|
// without a transcript (PHNX-3939); only a summarized session carries one.
|
|
3446
3558
|
summary: readSessionSummaryAny(r.id) ?? null,
|
|
3559
|
+
timeline: readSessionTimelineAny(r.id) ?? null,
|
|
3447
3560
|
}));
|
|
3448
3561
|
}
|
|
3449
3562
|
/**
|
|
@@ -3525,6 +3638,18 @@ export function upsertMirrorSession(row, source, syncedAt) {
|
|
|
3525
3638
|
if (row.summary) {
|
|
3526
3639
|
writeSessionSummary({ id: row.id, fileMtimeMs: null, fileSize: null, summary: row.summary });
|
|
3527
3640
|
}
|
|
3641
|
+
// Same for the peer's folded timeline (PHNX-3939): the projection is what a row
|
|
3642
|
+
// renders, and the resume state is deliberately EMPTY — this box never folds a
|
|
3643
|
+
// peer's transcript (it does not have one), so storing a foreign byte offset
|
|
3644
|
+
// would be a resume point into a file that is not here.
|
|
3645
|
+
if (row.timeline) {
|
|
3646
|
+
writeSessionTimeline({
|
|
3647
|
+
id: row.id,
|
|
3648
|
+
fileMtimeMs: null,
|
|
3649
|
+
fileSize: null,
|
|
3650
|
+
timeline: { ...row.timeline, state: emptyTimelineState() },
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3528
3653
|
return true;
|
|
3529
3654
|
}
|
|
3530
3655
|
/**
|
|
@@ -3542,12 +3667,14 @@ export function pruneMirrorSessions(cutoffMs) {
|
|
|
3542
3667
|
const delText = db.prepare(`DELETE FROM session_text WHERE session_id = ?`);
|
|
3543
3668
|
const delPreview = db.prepare(`DELETE FROM session_preview_cache WHERE session_id = ?`);
|
|
3544
3669
|
const delSummary = db.prepare(`DELETE FROM session_summaries WHERE session_id = ?`);
|
|
3670
|
+
const delTimeline = db.prepare(`DELETE FROM session_timelines WHERE session_id = ?`);
|
|
3545
3671
|
const txn = db.transaction(() => {
|
|
3546
3672
|
for (const { id } of stale) {
|
|
3547
3673
|
delRow.run(id);
|
|
3548
3674
|
delText.run(id);
|
|
3549
3675
|
delPreview.run(id);
|
|
3550
3676
|
delSummary.run(id);
|
|
3677
|
+
delTimeline.run(id);
|
|
3551
3678
|
}
|
|
3552
3679
|
});
|
|
3553
3680
|
txn();
|
|
@@ -4190,6 +4317,44 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4190
4317
|
hits.sort((a, b) => b.score - a.score);
|
|
4191
4318
|
return hits.slice(0, limit);
|
|
4192
4319
|
}
|
|
4320
|
+
/**
|
|
4321
|
+
* Rewrite file_path for sessions whose path starts with any of `moves.from`,
|
|
4322
|
+
* replacing that prefix with `moves.to` and clearing the matching scan_ledger
|
|
4323
|
+
* entries so the next scan re-indexes from the new location.
|
|
4324
|
+
*
|
|
4325
|
+
* One transaction for the whole batch (PHNX-3940 T7): a migration that moves
|
|
4326
|
+
* several homes into slots and several version dirs into trash must not leave
|
|
4327
|
+
* the index half-rewritten. Longest `from` wins when prefixes nest, and each
|
|
4328
|
+
* session row is rewritten at most once.
|
|
4329
|
+
*
|
|
4330
|
+
* Returns the number of session rows updated.
|
|
4331
|
+
*/
|
|
4332
|
+
export function reindexMovedSessionPaths(moves) {
|
|
4333
|
+
const usable = moves.filter((m) => m.from && m.from !== m.to);
|
|
4334
|
+
if (usable.length === 0)
|
|
4335
|
+
return 0;
|
|
4336
|
+
const db = getDB();
|
|
4337
|
+
const sorted = [...usable].sort((a, b) => b.from.length - a.from.length);
|
|
4338
|
+
return db.transaction(() => {
|
|
4339
|
+
let n = 0;
|
|
4340
|
+
const seen = new Set();
|
|
4341
|
+
const update = db.prepare(`UPDATE sessions SET file_path = ? WHERE id = ?`);
|
|
4342
|
+
const dropLedger = db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`);
|
|
4343
|
+
const select = db.prepare(`SELECT id, file_path FROM sessions WHERE file_path LIKE ?`);
|
|
4344
|
+
for (const { from, to } of sorted) {
|
|
4345
|
+
const rows = select.all(from + '%');
|
|
4346
|
+
for (const { id, file_path } of rows) {
|
|
4347
|
+
if (seen.has(id))
|
|
4348
|
+
continue;
|
|
4349
|
+
seen.add(id);
|
|
4350
|
+
update.run(to + file_path.slice(from.length), id);
|
|
4351
|
+
dropLedger.run(canonicalLedgerKey(file_path));
|
|
4352
|
+
n++;
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
return n;
|
|
4356
|
+
})();
|
|
4357
|
+
}
|
|
4193
4358
|
/**
|
|
4194
4359
|
* Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
|
|
4195
4360
|
* it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
|
|
@@ -4200,21 +4365,16 @@ export function ftsSearch(input, limit = 200) {
|
|
|
4200
4365
|
* Returns the number of session rows updated.
|
|
4201
4366
|
*/
|
|
4202
4367
|
export function updateSessionFilePaths(oldPrefix, newPrefix) {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
if (
|
|
4368
|
+
return reindexMovedSessionPaths([{ from: oldPrefix, to: newPrefix }]);
|
|
4369
|
+
}
|
|
4370
|
+
/** Count indexed sessions whose transcript path sits under `prefix`. */
|
|
4371
|
+
export function countSessionsWithFilePrefix(prefix) {
|
|
4372
|
+
if (!prefix)
|
|
4208
4373
|
return 0;
|
|
4209
|
-
const
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`).run(canonicalLedgerKey(file_path));
|
|
4214
|
-
}
|
|
4215
|
-
});
|
|
4216
|
-
txn();
|
|
4217
|
-
return rows.length;
|
|
4374
|
+
const row = getDB()
|
|
4375
|
+
.prepare(`SELECT COUNT(*) AS c FROM sessions WHERE file_path LIKE ?`)
|
|
4376
|
+
.get(prefix + '%');
|
|
4377
|
+
return row.c;
|
|
4218
4378
|
}
|
|
4219
4379
|
/**
|
|
4220
4380
|
* Upsert one browser task's durable metadata.
|
|
@@ -26,6 +26,11 @@ export declare const SESSION_MIRROR_MAX_ROWS = 200;
|
|
|
26
26
|
export declare const SESSION_MIRROR_SNIPPET_MAX = 280;
|
|
27
27
|
/** Mirror rows older than this since their last sync are pruned (staleness/size ceiling). */
|
|
28
28
|
export declare const SESSION_MIRROR_MAX_AGE_MS: number;
|
|
29
|
+
/** Caps for the timeline block on a published mirror row. */
|
|
30
|
+
export declare const SESSION_MIRROR_MAX_STEPS = 8;
|
|
31
|
+
export declare const SESSION_MIRROR_STEP_TEXT_MAX = 160;
|
|
32
|
+
export declare const SESSION_MIRROR_REQUEST_MAX = 2000;
|
|
33
|
+
export declare const SESSION_MIRROR_MAX_FILES = 8;
|
|
29
34
|
export interface PublishSessionMirrorOptions {
|
|
30
35
|
userAgentsDir?: string;
|
|
31
36
|
device?: string;
|