@phnx-labs/agents-cli 1.22.75 → 1.22.76
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 +117 -0
- package/README.md +20 -8
- package/dist/bootstrap.js +7 -7
- package/dist/cli/command-registry.js +5 -0
- package/dist/commands/artifacts-setup.js +1 -1
- package/dist/commands/artifacts.js +1 -1
- package/dist/commands/auth.js +7 -1
- package/dist/commands/browser.js +104 -10
- package/dist/commands/commands.js +7 -6
- package/dist/commands/config.js +27 -4
- package/dist/commands/cost.js +6 -4
- package/dist/commands/doctor.d.ts +6 -5
- package/dist/commands/doctor.js +32 -274
- package/dist/commands/exec.d.ts +2 -0
- package/dist/commands/exec.js +9 -2
- package/dist/commands/harness.d.ts +1 -0
- package/dist/commands/harness.js +11 -3
- package/dist/commands/hooks.js +7 -6
- package/dist/commands/mcp.js +7 -6
- package/dist/commands/memory.js +7 -7
- package/dist/commands/monitors.js +3 -2
- package/dist/commands/open.d.ts +25 -12
- package/dist/commands/open.js +24 -10
- package/dist/commands/permissions.js +7 -6
- package/dist/commands/plugins.js +21 -17
- package/dist/commands/route.js +33 -16
- package/dist/commands/rules.js +7 -12
- package/dist/commands/sessions-share.js +1 -1
- package/dist/commands/setup-watchdog.js +2 -2
- package/dist/commands/setup.js +22 -1
- package/dist/commands/share.js +26 -10
- package/dist/commands/skills.js +7 -6
- package/dist/commands/subagents.js +7 -6
- package/dist/commands/sync.js +81 -10
- package/dist/commands/view.js +4 -1
- package/dist/commands/watchdog.d.ts +1 -1
- package/dist/commands/watchdog.js +10 -10
- package/dist/commands/webhook.d.ts +4 -0
- package/dist/commands/webhook.js +22 -4
- package/dist/commands/workflows.js +7 -6
- package/dist/lib/accounting/rotate.d.ts +3 -1
- package/dist/lib/accounting/rotate.js +8 -4
- package/dist/lib/auth-health.d.ts +2 -0
- package/dist/lib/auth-health.js +2 -0
- package/dist/lib/browser/chrome.d.ts +21 -0
- package/dist/lib/browser/chrome.js +60 -3
- package/dist/lib/browser/drivers/local.d.ts +21 -0
- package/dist/lib/browser/drivers/local.js +102 -9
- package/dist/lib/browser/profiles.d.ts +29 -1
- package/dist/lib/browser/profiles.js +50 -1
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/config-keys.d.ts +7 -2
- package/dist/lib/config-keys.js +17 -2
- package/dist/lib/daemon/daemon.js +8 -0
- package/dist/lib/daemon/session-summarizer-service.d.ts +24 -0
- package/dist/lib/daemon/session-summarizer-service.js +39 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/daemon-ticks.d.ts +2 -2
- package/dist/lib/daemon-ticks.js +2 -1
- package/dist/lib/deeplink/register.js +10 -9
- package/dist/lib/deeplink/url.d.ts +4 -4
- package/dist/lib/deeplink/url.js +4 -4
- package/dist/lib/device-config.js +25 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -4
- package/dist/lib/devices/doctor-findings.js +14 -8
- package/dist/lib/devices/registry.js +2 -0
- package/dist/lib/devices/stats-cache.d.ts +4 -0
- package/dist/lib/devices/stats-cache.js +19 -0
- package/dist/lib/drift-sync.d.ts +3 -1
- package/dist/lib/drift-sync.js +16 -5
- package/dist/lib/exec.d.ts +2 -0
- package/dist/lib/exec.js +16 -1
- package/dist/lib/fleet-shared-state.d.ts +8 -0
- package/dist/lib/heal.d.ts +4 -3
- package/dist/lib/heal.js +5 -4
- package/dist/lib/hosts/ready.d.ts +1 -1
- package/dist/lib/hosts/ready.js +16 -4
- package/dist/lib/hosts/reconnect.js +4 -2
- package/dist/lib/identity/client.d.ts +6 -0
- package/dist/lib/identity/index.d.ts +16 -0
- package/dist/lib/identity/index.js +25 -1
- package/dist/lib/profiles.d.ts +2 -0
- package/dist/lib/profiles.js +28 -9
- package/dist/lib/reconcile-and-repair.d.ts +109 -0
- package/dist/lib/reconcile-and-repair.js +267 -0
- package/dist/lib/routers.d.ts +12 -1
- package/dist/lib/routers.js +30 -1
- package/dist/lib/scheduling/routines.js +8 -2
- package/dist/lib/session/active.d.ts +13 -0
- package/dist/lib/session/db.d.ts +47 -7
- package/dist/lib/session/db.js +114 -12
- package/dist/lib/session/mirror.js +58 -0
- package/dist/lib/session/remote/watch.js +22 -2
- package/dist/lib/session/session-cache.d.ts +19 -0
- package/dist/lib/session/session-cache.js +46 -0
- package/dist/lib/session/types.d.ts +34 -0
- package/dist/lib/share/backend.d.ts +6 -4
- package/dist/lib/share/backend.js +10 -8
- package/dist/lib/share/config.d.ts +4 -3
- package/dist/lib/share/config.js +10 -1
- package/dist/lib/share/delete.d.ts +1 -1
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/html.d.ts +1 -1
- package/dist/lib/share/html.js +1 -1
- package/dist/lib/share/provision.d.ts +1 -1
- package/dist/lib/share/provision.js +2 -2
- package/dist/lib/share/publish.d.ts +23 -7
- package/dist/lib/share/publish.js +58 -12
- package/dist/lib/share/worker-template.js +221 -60
- package/dist/lib/startup/command-registry.js +2 -2
- package/dist/lib/state.d.ts +15 -0
- package/dist/lib/state.js +29 -7
- package/dist/lib/summarizer/config.d.ts +46 -0
- package/dist/lib/summarizer/config.js +83 -0
- package/dist/lib/summarizer/pass.d.ts +45 -0
- package/dist/lib/summarizer/pass.js +112 -0
- package/dist/lib/summarizer/summarize.d.ts +68 -0
- package/dist/lib/summarizer/summarize.js +120 -0
- package/dist/lib/teams/agents.d.ts +4 -3
- package/dist/lib/teams/agents.js +12 -4
- package/dist/lib/teams/scheduler.d.ts +4 -2
- package/dist/lib/teams/scheduler.js +6 -6
- package/dist/lib/tmux/session.d.ts +2 -0
- package/dist/lib/tmux/session.js +7 -1
- package/dist/lib/types.d.ts +20 -0
- package/dist/lib/verbs.d.ts +23 -0
- package/dist/lib/verbs.js +24 -0
- package/dist/lib/view-types.d.ts +4 -0
- package/dist/lib/watchdog/rotate.d.ts +1 -1
- package/dist/lib/watchdog/rotate.js +1 -1
- package/package.json +1 -1
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
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, SessionEvent, SessionMeta } from './types.js';
|
|
10
|
+
import type { SessionAgentId, SessionCheckpoint, SessionChecklistItem, SessionEvent, SessionMeta, SummaryState } from './types.js';
|
|
11
11
|
import { type IndexedToolCall } from './tool-calls.js';
|
|
12
12
|
import { type ToolScanResumePoint } from './tool-store.js';
|
|
13
13
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
@@ -43,6 +43,8 @@ export declare const INSIGHTS_EXTRACTOR_VERSION = 7;
|
|
|
43
43
|
export declare const SESSION_TOPIC_EXTRACTOR_VERSION = 2;
|
|
44
44
|
/** Bump when classifyPhenotype's output changes so cached phenotypes recompute (PHNX-3327 v1). */
|
|
45
45
|
export declare const SESSION_PHENOTYPE_EXTRACTOR_VERSION = 1;
|
|
46
|
+
/** Bump when the summarizer output shape changes so cached summaries recompute (PHNX-3939 v1). */
|
|
47
|
+
export declare const SESSION_SUMMARY_EXTRACTOR_VERSION = 1;
|
|
46
48
|
/** File stat snapshot used to detect changes between scan runs. */
|
|
47
49
|
export interface ScanStamp {
|
|
48
50
|
fileMtimeMs: number;
|
|
@@ -337,8 +339,9 @@ export declare function countSessions(options?: QueryOptions): number;
|
|
|
337
339
|
/** One grouped row in a cost/duration rollup. */
|
|
338
340
|
interface UsageRollupRow {
|
|
339
341
|
/**
|
|
340
|
-
* Grouping key value: the agent id, project name,
|
|
341
|
-
*
|
|
342
|
+
* Grouping key value: the agent id, project name, shortened model id,
|
|
343
|
+
* ISO date (YYYY-MM-DD), or account identity
|
|
344
|
+
* (`claude:org=<uuid>` / `unattributed:<reason>`).
|
|
342
345
|
*/
|
|
343
346
|
key: string;
|
|
344
347
|
/**
|
|
@@ -443,6 +446,38 @@ export declare function readSessionContent(id: string): string | undefined;
|
|
|
443
446
|
* survives for this session.
|
|
444
447
|
*/
|
|
445
448
|
export declare function readArchivedSessionPreview<T>(id: string): T | undefined;
|
|
449
|
+
/** The daemon-computed summary stored in `session_summaries` (PHNX-3939). */
|
|
450
|
+
export interface SessionSummaryEntry {
|
|
451
|
+
goal?: string;
|
|
452
|
+
checkpoints?: SessionCheckpoint[];
|
|
453
|
+
summaryChecklist?: SessionChecklistItem[];
|
|
454
|
+
summaryState: SummaryState;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Read one summary only when it matches the transcript bytes on disk (the
|
|
458
|
+
* reuse-or-recompute stamp the SessionSummarizerService gates on). Mirrors
|
|
459
|
+
* {@link readSessionPreviewCache}.
|
|
460
|
+
*/
|
|
461
|
+
export declare function readSessionSummary(id: string, sourceStamp: {
|
|
462
|
+
fileMtimeMs: number | null;
|
|
463
|
+
fileSize: number | null;
|
|
464
|
+
}): SessionSummaryEntry | undefined;
|
|
465
|
+
/**
|
|
466
|
+
* Read the latest stored summary by session id, regardless of the transcript
|
|
467
|
+
* stamp — the low-cost merge read used by the display path (a live row through
|
|
468
|
+
* {@link applyImmutableMemo} or a history/mirror row) to surface the
|
|
469
|
+
* last-computed summary without a model call. A slightly-stale summary is the
|
|
470
|
+
* best available until the service recomputes; the SERVICE uses the stamped
|
|
471
|
+
* {@link readSessionSummary} to decide whether to recompute.
|
|
472
|
+
*/
|
|
473
|
+
export declare function readSessionSummaryAny(id: string): SessionSummaryEntry | undefined;
|
|
474
|
+
/** Persist a computed summary against the exact transcript bytes it was derived from. */
|
|
475
|
+
export declare function writeSessionSummary(entry: {
|
|
476
|
+
id: string;
|
|
477
|
+
fileMtimeMs: number | null;
|
|
478
|
+
fileSize: number | null;
|
|
479
|
+
summary: SessionSummaryEntry;
|
|
480
|
+
}): void;
|
|
446
481
|
/** A local-origin session, projected to the compact fields the fleet mirror publishes. */
|
|
447
482
|
export interface LocalMirrorSource {
|
|
448
483
|
id: string;
|
|
@@ -458,6 +493,8 @@ export interface LocalMirrorSource {
|
|
|
458
493
|
timestamp: string;
|
|
459
494
|
ticketId: string | null;
|
|
460
495
|
prUrl: string | null;
|
|
496
|
+
/** Daemon-computed summary (PHNX-3939), so peers carry it without a transcript. */
|
|
497
|
+
summary: SessionSummaryEntry | null;
|
|
461
498
|
}
|
|
462
499
|
/**
|
|
463
500
|
* The most-recently-active sessions whose transcript is genuinely local to this
|
|
@@ -482,6 +519,8 @@ export interface MirrorSessionUpsert {
|
|
|
482
519
|
timestamp: string;
|
|
483
520
|
ticketId?: string | null;
|
|
484
521
|
prUrl?: string | null;
|
|
522
|
+
/** Daemon-computed summary carried from the publishing peer (PHNX-3939). */
|
|
523
|
+
summary?: SessionSummaryEntry | null;
|
|
485
524
|
}
|
|
486
525
|
/**
|
|
487
526
|
* Write one peer session's digest into the local `sessions` index as a mirror
|
|
@@ -504,7 +543,7 @@ export declare function upsertMirrorSession(row: MirrorSessionUpsert, source: st
|
|
|
504
543
|
export declare function pruneMirrorSessions(cutoffMs: number): number;
|
|
505
544
|
/** Plugin provenance already indexed for resources used by one session. */
|
|
506
545
|
export declare function getSessionPlugins(id: string): string[];
|
|
507
|
-
export type UsageRollupGroup = 'agent' | 'project' | 'day' | 'account';
|
|
546
|
+
export type UsageRollupGroup = 'agent' | 'project' | 'day' | 'model' | 'account';
|
|
508
547
|
/**
|
|
509
548
|
* Smart-launch affinity priors: group sessions by origin machine, harness, or
|
|
510
549
|
* joint (machine + agent). Ordered by launch count desc.
|
|
@@ -543,9 +582,10 @@ export declare function queryAffinityRollup(options: {
|
|
|
543
582
|
}): AffinityRow[];
|
|
544
583
|
/**
|
|
545
584
|
* Aggregate cost / duration / tokens across sessions, grouped by agent,
|
|
546
|
-
* project, or calendar day. Honors the same
|
|
547
|
-
* (agent, since/until, team-origin) so
|
|
548
|
-
*
|
|
585
|
+
* project, shortened model id, account, or calendar day. Honors the same
|
|
586
|
+
* filter shape as querySessions (agent, since/until, team-origin) so
|
|
587
|
+
* `agents insights cost --since 7d --by day` lines up with what
|
|
588
|
+
* `agents sessions` would list. Ordered by cost desc.
|
|
549
589
|
*/
|
|
550
590
|
export declare function queryUsageRollup(options: QueryOptions & {
|
|
551
591
|
groupBy: UsageRollupGroup;
|
package/dist/lib/session/db.js
CHANGED
|
@@ -405,6 +405,21 @@ CREATE TABLE IF NOT EXISTS session_preview_cache (
|
|
|
405
405
|
preview_json TEXT NOT NULL
|
|
406
406
|
);
|
|
407
407
|
|
|
408
|
+
-- Daemon-computed per-session summary (goal / checkpoints / checklist), PHNX-3939.
|
|
409
|
+
-- Modeled exactly on session_preview_cache: a lazy, stamp-validated cache keyed on
|
|
410
|
+
-- (session_id, file_mtime_ms, file_size) so a row is reused verbatim until the
|
|
411
|
+
-- transcript bytes change. Written ONLY by the background SessionSummarizerService
|
|
412
|
+
-- (or consumed from a peer's fleet mirror); the display/merge path reads it, never
|
|
413
|
+
-- computes. Independent of SCHEMA_VERSION, like the other lazy caches above.
|
|
414
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
415
|
+
session_id TEXT PRIMARY KEY,
|
|
416
|
+
file_mtime_ms INTEGER,
|
|
417
|
+
file_size INTEGER,
|
|
418
|
+
extractor_version INTEGER NOT NULL,
|
|
419
|
+
computed_at INTEGER NOT NULL,
|
|
420
|
+
summary_json TEXT NOT NULL
|
|
421
|
+
);
|
|
422
|
+
|
|
408
423
|
-- Durable metadata for one browser task (RUSH-2549). The browser daemon's
|
|
409
424
|
-- tasks.json is LIVE state: saveTaskState writes the in-memory task map, so
|
|
410
425
|
-- stopping a task drops its entry and a daemon restart empties the file. That is
|
|
@@ -484,6 +499,8 @@ export const SESSION_TOPIC_EXTRACTOR_VERSION = 2;
|
|
|
484
499
|
const PREVIEW_EXTRACTOR_VERSION = 2;
|
|
485
500
|
/** Bump when classifyPhenotype's output changes so cached phenotypes recompute (PHNX-3327 v1). */
|
|
486
501
|
export const SESSION_PHENOTYPE_EXTRACTOR_VERSION = 1;
|
|
502
|
+
/** Bump when the summarizer output shape changes so cached summaries recompute (PHNX-3939 v1). */
|
|
503
|
+
export const SESSION_SUMMARY_EXTRACTOR_VERSION = 1;
|
|
487
504
|
let dbInstance = null;
|
|
488
505
|
/**
|
|
489
506
|
* Apply schema migrations from `fromVersion` → SCHEMA_VERSION. The new
|
|
@@ -3340,6 +3357,66 @@ export function readArchivedSessionPreview(id) {
|
|
|
3340
3357
|
return undefined;
|
|
3341
3358
|
}
|
|
3342
3359
|
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Read one summary only when it matches the transcript bytes on disk (the
|
|
3362
|
+
* reuse-or-recompute stamp the SessionSummarizerService gates on). Mirrors
|
|
3363
|
+
* {@link readSessionPreviewCache}.
|
|
3364
|
+
*/
|
|
3365
|
+
export function readSessionSummary(id, sourceStamp) {
|
|
3366
|
+
const row = getDB().prepare(`
|
|
3367
|
+
SELECT summary_json AS summaryJson
|
|
3368
|
+
FROM session_summaries
|
|
3369
|
+
WHERE session_id = ?
|
|
3370
|
+
AND extractor_version = ?
|
|
3371
|
+
AND file_mtime_ms IS ?
|
|
3372
|
+
AND file_size IS ?
|
|
3373
|
+
`).get(id, SESSION_SUMMARY_EXTRACTOR_VERSION, sourceStamp.fileMtimeMs, sourceStamp.fileSize);
|
|
3374
|
+
if (!row)
|
|
3375
|
+
return undefined;
|
|
3376
|
+
try {
|
|
3377
|
+
return JSON.parse(row.summaryJson);
|
|
3378
|
+
}
|
|
3379
|
+
catch {
|
|
3380
|
+
return undefined;
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* Read the latest stored summary by session id, regardless of the transcript
|
|
3385
|
+
* stamp — the low-cost merge read used by the display path (a live row through
|
|
3386
|
+
* {@link applyImmutableMemo} or a history/mirror row) to surface the
|
|
3387
|
+
* last-computed summary without a model call. A slightly-stale summary is the
|
|
3388
|
+
* best available until the service recomputes; the SERVICE uses the stamped
|
|
3389
|
+
* {@link readSessionSummary} to decide whether to recompute.
|
|
3390
|
+
*/
|
|
3391
|
+
export function readSessionSummaryAny(id) {
|
|
3392
|
+
const row = getDB().prepare(`
|
|
3393
|
+
SELECT summary_json AS summaryJson
|
|
3394
|
+
FROM session_summaries
|
|
3395
|
+
WHERE session_id = ? AND extractor_version = ?
|
|
3396
|
+
`).get(id, SESSION_SUMMARY_EXTRACTOR_VERSION);
|
|
3397
|
+
if (!row)
|
|
3398
|
+
return undefined;
|
|
3399
|
+
try {
|
|
3400
|
+
return JSON.parse(row.summaryJson);
|
|
3401
|
+
}
|
|
3402
|
+
catch {
|
|
3403
|
+
return undefined;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
/** Persist a computed summary against the exact transcript bytes it was derived from. */
|
|
3407
|
+
export function writeSessionSummary(entry) {
|
|
3408
|
+
getDB().prepare(`
|
|
3409
|
+
INSERT INTO session_summaries
|
|
3410
|
+
(session_id, file_mtime_ms, file_size, extractor_version, computed_at, summary_json)
|
|
3411
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
3412
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
3413
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
3414
|
+
file_size = excluded.file_size,
|
|
3415
|
+
extractor_version = excluded.extractor_version,
|
|
3416
|
+
computed_at = excluded.computed_at,
|
|
3417
|
+
summary_json = excluded.summary_json
|
|
3418
|
+
`).run(entry.id, entry.fileMtimeMs, entry.fileSize, SESSION_SUMMARY_EXTRACTOR_VERSION, Date.now(), JSON.stringify(entry.summary));
|
|
3419
|
+
}
|
|
3343
3420
|
/**
|
|
3344
3421
|
* The most-recently-active sessions whose transcript is genuinely local to this
|
|
3345
3422
|
* box — a real `file_path`, not a peer mirror — for publishing to the fleet
|
|
@@ -3364,6 +3441,9 @@ export function queryLocalOriginSessionsForMirror(self, limit) {
|
|
|
3364
3441
|
id: r.id, shortId: r.short_id, agent: r.agent, version: r.version, machine: r.machine,
|
|
3365
3442
|
cwd: r.cwd, topic: r.topic, firstUserMessage: r.first_user_message, label: r.label,
|
|
3366
3443
|
lastActivity: r.last_activity, timestamp: r.timestamp, ticketId: r.ticket_id, prUrl: r.pr_url,
|
|
3444
|
+
// Ride the daemon-computed summary alongside the digest so a peer renders it
|
|
3445
|
+
// without a transcript (PHNX-3939); only a summarized session carries one.
|
|
3446
|
+
summary: readSessionSummaryAny(r.id) ?? null,
|
|
3367
3447
|
}));
|
|
3368
3448
|
}
|
|
3369
3449
|
/**
|
|
@@ -3437,6 +3517,14 @@ export function upsertMirrorSession(row, source, syncedAt) {
|
|
|
3437
3517
|
INSERT INTO session_text (session_id, label, topic, project, content, assistant)
|
|
3438
3518
|
VALUES (?, ?, ?, '', ?, '')
|
|
3439
3519
|
`).run(row.id, row.label ?? '', row.topic ?? '', row.firstUser ?? '');
|
|
3520
|
+
// Carry the peer's daemon-computed summary into the same session_summaries
|
|
3521
|
+
// cache the local merge reads (PHNX-3939), so a peer session renders its
|
|
3522
|
+
// goal/checkpoints/checklist inline with no transcript. Stamp is null: the
|
|
3523
|
+
// display merge reads by id (readSessionSummaryAny), and this box's own
|
|
3524
|
+
// summarizer service only ever recomputes LOCAL live sessions, never a peer's.
|
|
3525
|
+
if (row.summary) {
|
|
3526
|
+
writeSessionSummary({ id: row.id, fileMtimeMs: null, fileSize: null, summary: row.summary });
|
|
3527
|
+
}
|
|
3440
3528
|
return true;
|
|
3441
3529
|
}
|
|
3442
3530
|
/**
|
|
@@ -3453,11 +3541,13 @@ export function pruneMirrorSessions(cutoffMs) {
|
|
|
3453
3541
|
const delRow = db.prepare(`DELETE FROM sessions WHERE id = ?`);
|
|
3454
3542
|
const delText = db.prepare(`DELETE FROM session_text WHERE session_id = ?`);
|
|
3455
3543
|
const delPreview = db.prepare(`DELETE FROM session_preview_cache WHERE session_id = ?`);
|
|
3544
|
+
const delSummary = db.prepare(`DELETE FROM session_summaries WHERE session_id = ?`);
|
|
3456
3545
|
const txn = db.transaction(() => {
|
|
3457
3546
|
for (const { id } of stale) {
|
|
3458
3547
|
delRow.run(id);
|
|
3459
3548
|
delText.run(id);
|
|
3460
3549
|
delPreview.run(id);
|
|
3550
|
+
delSummary.run(id);
|
|
3461
3551
|
}
|
|
3462
3552
|
});
|
|
3463
3553
|
txn();
|
|
@@ -3526,9 +3616,10 @@ export function queryAffinityRollup(options) {
|
|
|
3526
3616
|
}
|
|
3527
3617
|
/**
|
|
3528
3618
|
* Aggregate cost / duration / tokens across sessions, grouped by agent,
|
|
3529
|
-
* project, or calendar day. Honors the same
|
|
3530
|
-
* (agent, since/until, team-origin) so
|
|
3531
|
-
*
|
|
3619
|
+
* project, shortened model id, account, or calendar day. Honors the same
|
|
3620
|
+
* filter shape as querySessions (agent, since/until, team-origin) so
|
|
3621
|
+
* `agents insights cost --since 7d --by day` lines up with what
|
|
3622
|
+
* `agents sessions` would list. Ordered by cost desc.
|
|
3532
3623
|
*/
|
|
3533
3624
|
export function queryUsageRollup(options) {
|
|
3534
3625
|
const db = getDB();
|
|
@@ -3537,15 +3628,26 @@ export function queryUsageRollup(options) {
|
|
|
3537
3628
|
? 'agent'
|
|
3538
3629
|
: options.groupBy === 'project'
|
|
3539
3630
|
? `IFNULL(NULLIF(project, ''), '(no project)')`
|
|
3540
|
-
: options.groupBy === '
|
|
3541
|
-
//
|
|
3542
|
-
//
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3631
|
+
: options.groupBy === 'model'
|
|
3632
|
+
// Match shortenModel(): remove Claude's redundant harness prefix and
|
|
3633
|
+
// an optional eight-digit release-date suffix before grouping.
|
|
3634
|
+
? `IFNULL(NULLIF(CASE
|
|
3635
|
+
WHEN substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -9, 1) = '-'
|
|
3636
|
+
AND length(substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -8)) = 8
|
|
3637
|
+
AND substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -8) NOT GLOB '*[^0-9]*'
|
|
3638
|
+
THEN substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, 1,
|
|
3639
|
+
length(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END) - 9)
|
|
3640
|
+
ELSE CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END
|
|
3641
|
+
END, ''), '(unknown)')`
|
|
3642
|
+
: options.groupBy === 'account'
|
|
3643
|
+
// A NULL account_key means this harness has no account attribution yet —
|
|
3644
|
+
// the mechanism is Claude-only today (see lib/session/claude-accounts.ts).
|
|
3645
|
+
// Bucket per agent so the rows are named honestly instead of being called
|
|
3646
|
+
// "not indexed", which they are not, and instead of joining a real account.
|
|
3647
|
+
? `IFNULL(NULLIF(account_key, ''), 'unattributed:' || agent)`
|
|
3648
|
+
// ISO timestamps are lexicographically date-sortable; the date is the
|
|
3649
|
+
// first 10 chars (YYYY-MM-DD).
|
|
3650
|
+
: `substr(timestamp, 1, 10)`;
|
|
3549
3651
|
const sql = `
|
|
3550
3652
|
SELECT
|
|
3551
3653
|
${keyExpr} AS key,
|
|
@@ -62,6 +62,27 @@ export async function publishSessionMirrorToSharedStore(options = {}) {
|
|
|
62
62
|
timestamp: s.timestamp,
|
|
63
63
|
...(s.ticketId ? { ticketId: s.ticketId } : {}),
|
|
64
64
|
...(s.prUrl ? { prUrl: s.prUrl } : {}),
|
|
65
|
+
// Ride the daemon-computed summary (PHNX-3939) so a peer renders the goal /
|
|
66
|
+
// checkpoints / checklist inline — still no transcript, keeping the mirror light.
|
|
67
|
+
...(s.summary?.goal ? { goal: snippet(s.summary.goal, 400) } : {}),
|
|
68
|
+
// Bound on publish exactly as toMirrorSummary bounds on consume (50/100 items,
|
|
69
|
+
// 400-char text, 40-char `at`) so this box's own model output can't ride raw
|
|
70
|
+
// into the git-synced fleet state file — matching the snippet() cap on goal above.
|
|
71
|
+
...(s.summary?.checkpoints
|
|
72
|
+
? {
|
|
73
|
+
checkpoints: s.summary.checkpoints
|
|
74
|
+
.slice(0, 50)
|
|
75
|
+
.map((c) => ({ text: String(c.text).slice(0, 400), at: String(c.at).slice(0, 40) })),
|
|
76
|
+
}
|
|
77
|
+
: {}),
|
|
78
|
+
...(s.summary?.summaryChecklist
|
|
79
|
+
? {
|
|
80
|
+
summaryChecklist: s.summary.summaryChecklist
|
|
81
|
+
.slice(0, 100)
|
|
82
|
+
.map((c) => ({ text: String(c.text).slice(0, 400), done: Boolean(c.done) })),
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
85
|
+
...(s.summary?.summaryState ? { summaryState: s.summary.summaryState } : {}),
|
|
65
86
|
capturedAt,
|
|
66
87
|
}));
|
|
67
88
|
try {
|
|
@@ -111,6 +132,43 @@ function toUpsert(raw) {
|
|
|
111
132
|
timestamp,
|
|
112
133
|
ticketId: cap(r.ticketId, 64),
|
|
113
134
|
prUrl: cap(r.prUrl, 512),
|
|
135
|
+
summary: toMirrorSummary(r),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** One published summary state, validated back into the union or undefined. */
|
|
139
|
+
function toSummaryState(v) {
|
|
140
|
+
return v === 'pending' || v === 'ready' || v === 'skipped' ? v : undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Validate an untrusted peer's published summary fields into a bounded
|
|
144
|
+
* {@link SessionSummaryEntry} (PHNX-3939), or undefined when it carries none.
|
|
145
|
+
* Bounds mirror the publish side so a hostile/oversized peer can't bloat the
|
|
146
|
+
* local cache. Only a row with a resolvable `summaryState` is kept.
|
|
147
|
+
*/
|
|
148
|
+
function toMirrorSummary(r) {
|
|
149
|
+
const summaryState = toSummaryState(r.summaryState);
|
|
150
|
+
if (!summaryState)
|
|
151
|
+
return undefined;
|
|
152
|
+
const goal = isString(r.goal) ? r.goal.slice(0, 400) : undefined;
|
|
153
|
+
const checkpoints = Array.isArray(r.checkpoints)
|
|
154
|
+
? r.checkpoints
|
|
155
|
+
.filter((c) => Boolean(c) && typeof c === 'object' && !Array.isArray(c))
|
|
156
|
+
.filter((c) => isString(c.text) && isString(c.at))
|
|
157
|
+
.slice(0, 50)
|
|
158
|
+
.map((c) => ({ text: String(c.text).slice(0, 400), at: String(c.at).slice(0, 40) }))
|
|
159
|
+
: undefined;
|
|
160
|
+
const summaryChecklist = Array.isArray(r.summaryChecklist)
|
|
161
|
+
? r.summaryChecklist
|
|
162
|
+
.filter((c) => Boolean(c) && typeof c === 'object' && !Array.isArray(c))
|
|
163
|
+
.filter((c) => isString(c.text))
|
|
164
|
+
.slice(0, 100)
|
|
165
|
+
.map((c) => ({ text: String(c.text).slice(0, 400), done: Boolean(c.done) }))
|
|
166
|
+
: undefined;
|
|
167
|
+
return {
|
|
168
|
+
summaryState,
|
|
169
|
+
...(goal ? { goal } : {}),
|
|
170
|
+
...(checkpoints && checkpoints.length ? { checkpoints } : {}),
|
|
171
|
+
...(summaryChecklist && summaryChecklist.length ? { summaryChecklist } : {}),
|
|
114
172
|
};
|
|
115
173
|
}
|
|
116
174
|
/**
|
|
@@ -9,10 +9,10 @@ import { machineId, normalizeHost } from '../../machine-id.js';
|
|
|
9
9
|
import { SSH_OPTS, controlOpts, shellQuote } from '../../ssh-exec.js';
|
|
10
10
|
import { buildWindowsAgentsCommand, remoteShellFor } from '../../hosts/remote-cmd.js';
|
|
11
11
|
import { isReapableOrphan } from '../active.js';
|
|
12
|
-
import { querySessions } from '../db.js';
|
|
12
|
+
import { querySessions, readSessionSummaryAny } from '../db.js';
|
|
13
13
|
import { linearIssueUrl } from '../linear.js';
|
|
14
14
|
import { sessionAgentSupportsResume } from '../recovery.js';
|
|
15
|
-
import { activeSessionsJournalPath, activeSessionJournalIdentity, readActiveSessionsCache, noteActiveSessionsJournalReader, } from '../session-cache.js';
|
|
15
|
+
import { activeSessionsJournalPath, activeSessionJournalIdentity, readActiveSessionsCache, noteActiveSessionsJournalReader, resolveStreamSummaryState, } from '../session-cache.js';
|
|
16
16
|
export const SESSION_WATCH_VERSION = 1;
|
|
17
17
|
export const SESSION_WATCH_HEARTBEAT_MS = 15_000;
|
|
18
18
|
export const SESSION_WATCH_PREVIOUS_LIMIT = 50;
|
|
@@ -36,6 +36,10 @@ export function toSessionWatchRow(scope, row) {
|
|
|
36
36
|
: null;
|
|
37
37
|
return {
|
|
38
38
|
...row,
|
|
39
|
+
// The summarizer's per-row state is delivered on the stream: a live row with
|
|
40
|
+
// no computed summary reads `pending` when the summarizer is on, `skipped`
|
|
41
|
+
// when it is off (PHNX-3939). goal/checkpoints/summaryChecklist rode `...row`.
|
|
42
|
+
summaryState: resolveStreamSummaryState(row.summaryState),
|
|
39
43
|
rowKey,
|
|
40
44
|
sourceDevice: scope,
|
|
41
45
|
previous,
|
|
@@ -67,6 +71,18 @@ export function toPreviousSessionWatchRow(scope, session) {
|
|
|
67
71
|
const worktree = session.worktreeSlug && session.cwd
|
|
68
72
|
? { slug: session.worktreeSlug, path: session.cwd, ...(session.gitBranch ? { branch: session.gitBranch } : {}) }
|
|
69
73
|
: undefined;
|
|
74
|
+
// Project the daemon-computed summary (PHNX-3939) onto the history row from the
|
|
75
|
+
// transcript-keyed cache — the same store the live merge reads, so a closed or
|
|
76
|
+
// fleet-mirrored session carries its goal/checkpoints/checklist with no
|
|
77
|
+
// transcript re-parse and no model call. Prefer a value already on the meta row.
|
|
78
|
+
const summary = session.goal !== undefined || session.summaryState !== undefined
|
|
79
|
+
? {
|
|
80
|
+
goal: session.goal,
|
|
81
|
+
checkpoints: session.checkpoints,
|
|
82
|
+
summaryChecklist: session.summaryChecklist,
|
|
83
|
+
summaryState: session.summaryState,
|
|
84
|
+
}
|
|
85
|
+
: readSessionSummaryAny(session.id);
|
|
70
86
|
return {
|
|
71
87
|
context: 'recent',
|
|
72
88
|
kind: session.agent,
|
|
@@ -88,6 +104,10 @@ export function toPreviousSessionWatchRow(scope, session) {
|
|
|
88
104
|
...(session.tokenCount != null ? { tokenCount: session.tokenCount } : {}),
|
|
89
105
|
...(session.durationMs != null ? { durationMs: session.durationMs } : {}),
|
|
90
106
|
...(session.subAgentCount != null ? { subAgentCount: session.subAgentCount } : {}),
|
|
107
|
+
...(summary?.goal ? { goal: summary.goal } : {}),
|
|
108
|
+
...(summary?.checkpoints ? { checkpoints: summary.checkpoints } : {}),
|
|
109
|
+
...(summary?.summaryChecklist ? { summaryChecklist: summary.summaryChecklist } : {}),
|
|
110
|
+
summaryState: resolveStreamSummaryState(summary?.summaryState),
|
|
91
111
|
startedAtMs,
|
|
92
112
|
lastActivityMs,
|
|
93
113
|
status: 'closed',
|
|
@@ -171,6 +171,25 @@ export declare function updateImmutableMemos(sessions: ReadonlyArray<ActiveSessi
|
|
|
171
171
|
* mtime matches. Never overwrites a field the live gather already set, and
|
|
172
172
|
* never copies live-status keys.
|
|
173
173
|
*/
|
|
174
|
+
/**
|
|
175
|
+
* Merge the daemon-computed session summary (PHNX-3939) onto a live row from the
|
|
176
|
+
* transcript-keyed `session_summaries` cache. This is the low-cost display merge:
|
|
177
|
+
* one indexed by-id read, NEVER a model call — the background
|
|
178
|
+
* SessionSummarizerService is the only producer. It fills only what the cache
|
|
179
|
+
* holds; the `pending`/`skipped` default for a row with no summary yet is applied
|
|
180
|
+
* at the watch-stream boundary ({@link resolveStreamSummaryState}), so a
|
|
181
|
+
* non-stream ActiveSession consumer is unaffected. Best-effort: a DB error leaves
|
|
182
|
+
* the row untouched.
|
|
183
|
+
*/
|
|
184
|
+
export declare function mergeSessionSummary(s: ActiveSession): ActiveSession;
|
|
185
|
+
/**
|
|
186
|
+
* The `summaryState` a watch-stream row carries when the merge left it unset:
|
|
187
|
+
* `pending` while the summarizer is READY (enabled AND has an endpoint, so a
|
|
188
|
+
* summary is genuinely coming), `skipped` when it is off OR enabled-but-
|
|
189
|
+
* unconfigured (nothing will ever populate the cache). This is the only place
|
|
190
|
+
* the readiness flag is read on the read path, and it is memoized (PHNX-3939).
|
|
191
|
+
*/
|
|
192
|
+
export declare function resolveStreamSummaryState(current: import('./types.js').SummaryState | undefined, nowMs?: number): import('./types.js').SummaryState;
|
|
174
193
|
export declare function applyImmutableMemo(s: ActiveSession): ActiveSession;
|
|
175
194
|
export interface LoadLocalActiveSessionsOptions {
|
|
176
195
|
/** Skip the cache and re-gather (the force-refresh path). */
|
|
@@ -28,6 +28,8 @@ import * as path from 'path';
|
|
|
28
28
|
import { createMemoryCache } from '../memory-cache.js';
|
|
29
29
|
import { getCacheDir } from '../state.js';
|
|
30
30
|
import { backfillActiveRowsFromIndex, sessionProcessIsLocal } from './active.js';
|
|
31
|
+
import { readSessionSummaryAny } from './db.js';
|
|
32
|
+
import { isSummarizerReady } from '../summarizer/config.js';
|
|
31
33
|
/** Snapshot file under `getCacheDir()` (regenerable, gitignored). */
|
|
32
34
|
const SNAPSHOT_FILE = '.active-sessions.json';
|
|
33
35
|
/** Immutable-field memo file (keyed by sessionId + transcript mtime). */
|
|
@@ -423,9 +425,53 @@ export function updateImmutableMemos(sessions, nowMs = Date.now()) {
|
|
|
423
425
|
* mtime matches. Never overwrites a field the live gather already set, and
|
|
424
426
|
* never copies live-status keys.
|
|
425
427
|
*/
|
|
428
|
+
/**
|
|
429
|
+
* Merge the daemon-computed session summary (PHNX-3939) onto a live row from the
|
|
430
|
+
* transcript-keyed `session_summaries` cache. This is the low-cost display merge:
|
|
431
|
+
* one indexed by-id read, NEVER a model call — the background
|
|
432
|
+
* SessionSummarizerService is the only producer. It fills only what the cache
|
|
433
|
+
* holds; the `pending`/`skipped` default for a row with no summary yet is applied
|
|
434
|
+
* at the watch-stream boundary ({@link resolveStreamSummaryState}), so a
|
|
435
|
+
* non-stream ActiveSession consumer is unaffected. Best-effort: a DB error leaves
|
|
436
|
+
* the row untouched.
|
|
437
|
+
*/
|
|
438
|
+
export function mergeSessionSummary(s) {
|
|
439
|
+
if (!s.sessionId)
|
|
440
|
+
return s;
|
|
441
|
+
try {
|
|
442
|
+
const stored = readSessionSummaryAny(s.sessionId);
|
|
443
|
+
if (!stored)
|
|
444
|
+
return s;
|
|
445
|
+
if (s.goal === undefined && stored.goal !== undefined)
|
|
446
|
+
s.goal = stored.goal;
|
|
447
|
+
if (s.checkpoints === undefined && stored.checkpoints !== undefined)
|
|
448
|
+
s.checkpoints = stored.checkpoints;
|
|
449
|
+
if (s.summaryChecklist === undefined && stored.summaryChecklist !== undefined)
|
|
450
|
+
s.summaryChecklist = stored.summaryChecklist;
|
|
451
|
+
if (s.summaryState === undefined)
|
|
452
|
+
s.summaryState = stored.summaryState;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
// best-effort: never let the summary merge break the gather path.
|
|
456
|
+
}
|
|
457
|
+
return s;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The `summaryState` a watch-stream row carries when the merge left it unset:
|
|
461
|
+
* `pending` while the summarizer is READY (enabled AND has an endpoint, so a
|
|
462
|
+
* summary is genuinely coming), `skipped` when it is off OR enabled-but-
|
|
463
|
+
* unconfigured (nothing will ever populate the cache). This is the only place
|
|
464
|
+
* the readiness flag is read on the read path, and it is memoized (PHNX-3939).
|
|
465
|
+
*/
|
|
466
|
+
export function resolveStreamSummaryState(current, nowMs = Date.now()) {
|
|
467
|
+
if (current)
|
|
468
|
+
return current;
|
|
469
|
+
return isSummarizerReady(nowMs) ? 'pending' : 'skipped';
|
|
470
|
+
}
|
|
426
471
|
export function applyImmutableMemo(s) {
|
|
427
472
|
if (!s.sessionId)
|
|
428
473
|
return s;
|
|
474
|
+
mergeSessionSummary(s);
|
|
429
475
|
const mtime = transcriptMtimeMs(s);
|
|
430
476
|
if (mtime === null)
|
|
431
477
|
return s;
|
|
@@ -126,6 +126,28 @@ export interface TodoProgress {
|
|
|
126
126
|
/** The in-progress item's activeForm (falls back to its content). The live step. */
|
|
127
127
|
activeForm?: string;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* One progress checkpoint in a daemon-computed session summary (PHNX-3939):
|
|
131
|
+
* a short line of what happened, stamped with an ISO time. Newest last.
|
|
132
|
+
*/
|
|
133
|
+
export interface SessionCheckpoint {
|
|
134
|
+
text: string;
|
|
135
|
+
/** ISO timestamp for this checkpoint. */
|
|
136
|
+
at: string;
|
|
137
|
+
}
|
|
138
|
+
/** One item in a daemon-computed session summary checklist (PHNX-3939). */
|
|
139
|
+
export interface SessionChecklistItem {
|
|
140
|
+
text: string;
|
|
141
|
+
done: boolean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Lifecycle of a session's daemon-computed summary (PHNX-3939):
|
|
145
|
+
* - `pending` — the summarizer is enabled and this session has no summary yet.
|
|
146
|
+
* - `ready` — a summary was computed and merged onto the row.
|
|
147
|
+
* - `skipped` — the summarizer is disabled/unconfigured, or a compute error
|
|
148
|
+
* left no summary; the row deliberately carries no goal/checkpoints.
|
|
149
|
+
*/
|
|
150
|
+
export type SummaryState = 'pending' | 'ready' | 'skipped';
|
|
129
151
|
/** Metadata attached when a session was spawned by `agents teams`. */
|
|
130
152
|
export interface TeamOrigin {
|
|
131
153
|
/** Teammate name if set, otherwise first 8 chars of the agent UUID. */
|
|
@@ -400,6 +422,18 @@ export interface SessionMeta {
|
|
|
400
422
|
archived?: boolean;
|
|
401
423
|
/** Epoch ms the transcript file was first confirmed gone (pairs with {@link archived}). */
|
|
402
424
|
archivedAt?: number;
|
|
425
|
+
/**
|
|
426
|
+
* Daemon-computed 1–2 line goal extracted from the session's first user turn
|
|
427
|
+
* (PHNX-3939). Never on the request path — produced by the background
|
|
428
|
+
* `SessionSummarizerService` and merged from the `session_summaries` cache.
|
|
429
|
+
*/
|
|
430
|
+
goal?: string;
|
|
431
|
+
/** Daemon-computed progress checkpoints, newest last (PHNX-3939). */
|
|
432
|
+
checkpoints?: SessionCheckpoint[];
|
|
433
|
+
/** Daemon-computed detailed checklist for the session (PHNX-3939). */
|
|
434
|
+
summaryChecklist?: SessionChecklistItem[];
|
|
435
|
+
/** Lifecycle of the daemon-computed summary; `skipped` when disabled (PHNX-3939). */
|
|
436
|
+
summaryState?: SummaryState;
|
|
403
437
|
/** Terms that matched the current search query */
|
|
404
438
|
_matchedTerms?: string[];
|
|
405
439
|
/** BM25 relevance score from the most recent content-index search */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Two legitimate principals, picked once at publish time:
|
|
5
5
|
* - **managed**: the caller is signed in to Phoenix (`readSession()`), no
|
|
6
6
|
* explicit BYO override. Token is the Phoenix access_token; endpoint is
|
|
7
|
-
* the already-live `share.
|
|
7
|
+
* the already-live `share.getrush.ai`. Zero Cloudflare setup.
|
|
8
8
|
* - **byo**: the existing static WRITE_TOKEN path (`readShareConfig()` +
|
|
9
9
|
* `readWriteToken()`). Unchanged for power users / custom domains / fleet
|
|
10
10
|
* injection of `SHARE_WRITE_TOKEN`.
|
|
@@ -68,9 +68,11 @@ export declare function handleFromEmail(email: string | undefined): string;
|
|
|
68
68
|
export declare function managedShareHandle(session: Pick<PhoenixSession, 'email' | 'userId'>): string;
|
|
69
69
|
export declare function managedShareBaseUrl(): string;
|
|
70
70
|
/**
|
|
71
|
-
* True when this config is the platform managed endpoint (`share.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* True when this config is the platform managed endpoint (`share.getrush.ai`, or a
|
|
72
|
+
* legacy alias — `share.agents-cli.sh` / `artifacts.prix.dev` — still bound to the
|
|
73
|
+
* same Worker). The
|
|
74
|
+
* deploy path uses this — not "the caller is signed in" — to decide whether to
|
|
75
|
+
* bind `PHOENIX_ID_BASE` on the Worker.
|
|
74
76
|
*/
|
|
75
77
|
export declare function isManagedShareEndpoint(cfg: {
|
|
76
78
|
baseUrl?: string;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Two legitimate principals, picked once at publish time:
|
|
5
5
|
* - **managed**: the caller is signed in to Phoenix (`readSession()`), no
|
|
6
6
|
* explicit BYO override. Token is the Phoenix access_token; endpoint is
|
|
7
|
-
* the already-live `share.
|
|
7
|
+
* the already-live `share.getrush.ai`. Zero Cloudflare setup.
|
|
8
8
|
* - **byo**: the existing static WRITE_TOKEN path (`readShareConfig()` +
|
|
9
9
|
* `readWriteToken()`). Unchanged for power users / custom domains / fleet
|
|
10
10
|
* injection of `SHARE_WRITE_TOKEN`.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { PHOENIX_ID_BASE, readSession } from '../identity/client.js';
|
|
21
21
|
import { selectStorageBackendKind } from '../storage/selection.js';
|
|
22
|
-
import { DEFAULT_SHARE_DOMAIN, readShareConfig, readWriteToken, readWriteTokenEnv, } from './config.js';
|
|
22
|
+
import { DEFAULT_SHARE_DOMAIN, LEGACY_MANAGED_SHARE_DOMAINS, readShareConfig, readWriteToken, readWriteTokenEnv, } from './config.js';
|
|
23
23
|
/** Env var that forces the BYO path. Value must be exactly `byo`. */
|
|
24
24
|
export const SHARE_BACKEND_ENV = 'AGENTS_SHARE_BACKEND';
|
|
25
25
|
/** URL-safe namespace: lowercase `[a-z0-9-]+`. Matches the Worker's sanitize.
|
|
@@ -55,15 +55,17 @@ function shareEndpointHost(value) {
|
|
|
55
55
|
return value.replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* True when this config is the platform managed endpoint (`share.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* True when this config is the platform managed endpoint (`share.getrush.ai`, or a
|
|
59
|
+
* legacy alias — `share.agents-cli.sh` / `artifacts.prix.dev` — still bound to the
|
|
60
|
+
* same Worker). The
|
|
61
|
+
* deploy path uses this — not "the caller is signed in" — to decide whether to
|
|
62
|
+
* bind `PHOENIX_ID_BASE` on the Worker.
|
|
61
63
|
*/
|
|
62
64
|
export function isManagedShareEndpoint(cfg) {
|
|
63
|
-
const
|
|
64
|
-
if (cfg.domain && shareEndpointHost(cfg.domain)
|
|
65
|
+
const managedHosts = new Set([DEFAULT_SHARE_DOMAIN, ...LEGACY_MANAGED_SHARE_DOMAINS].map((h) => h.toLowerCase()));
|
|
66
|
+
if (cfg.domain && managedHosts.has(shareEndpointHost(cfg.domain)))
|
|
65
67
|
return true;
|
|
66
|
-
if (cfg.baseUrl && shareEndpointHost(cfg.baseUrl)
|
|
68
|
+
if (cfg.baseUrl && managedHosts.has(shareEndpointHost(cfg.baseUrl)))
|
|
67
69
|
return true;
|
|
68
70
|
return false;
|
|
69
71
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export interface ShareConfig {
|
|
2
|
-
/** Public base, e.g. `https://share.
|
|
2
|
+
/** Public base, e.g. `https://share.getrush.ai` or `https://agent-share.<acct>.workers.dev`. */
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
accountId: string;
|
|
5
5
|
workerName: string;
|
|
6
6
|
bucketName: string;
|
|
7
|
-
/** Custom domain when mapped (e.g. `share.
|
|
7
|
+
/** Custom domain when mapped (e.g. `share.getrush.ai`). */
|
|
8
8
|
domain?: string;
|
|
9
9
|
/** Cloudflare Web Analytics token injected into published HTML pages. */
|
|
10
10
|
analyticsToken?: string;
|
|
@@ -19,7 +19,8 @@ export declare const SHARE_TOKEN_ENV_KEY = "SHARE_WRITE_TOKEN";
|
|
|
19
19
|
export declare const DEFAULT_CF_BUNDLE = "cloudflare";
|
|
20
20
|
export declare const DEFAULT_WORKER_NAME = "agents-share";
|
|
21
21
|
export declare const DEFAULT_BUCKET_NAME = "agents-share";
|
|
22
|
-
export declare const DEFAULT_SHARE_DOMAIN = "share.
|
|
22
|
+
export declare const DEFAULT_SHARE_DOMAIN = "share.getrush.ai";
|
|
23
|
+
export declare const LEGACY_MANAGED_SHARE_DOMAINS: readonly ["share.agents-cli.sh", "artifacts.prix.dev"];
|
|
23
24
|
/** The write token may be injected ephemerally into fleet/cloud agents. */
|
|
24
25
|
export declare function readWriteTokenEnv(env?: NodeJS.ProcessEnv): string | null;
|
|
25
26
|
/**
|