@phnx-labs/agents-cli 1.22.40 → 1.22.42
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 +36 -0
- package/README.md +14 -3
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +0 -1
- package/dist/cli/command-registry.d.ts +2 -0
- package/dist/cli/command-registry.js +5 -2
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.js +1 -1
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +104 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.d.ts +15 -0
- package/dist/commands/browser.js +145 -78
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +2 -2
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/feed.js +4 -12
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +174 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +1 -1
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/secrets.js +87 -97
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.d.ts +1 -0
- package/dist/commands/sessions-picker.js +27 -5
- package/dist/commands/sessions-share.d.ts +25 -0
- package/dist/commands/sessions-share.js +166 -0
- package/dist/commands/sessions.js +2 -0
- package/dist/commands/setup-browser.js +5 -5
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +4 -4
- package/dist/commands/setup.js +5 -3
- package/dist/commands/sync.js +1 -1
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +1 -1
- package/dist/commands/webhook.js +14 -9
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- package/dist/lib/agent-spec/agents.d.ts +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/cdp.js +4 -0
- package/dist/lib/browser/chrome.js +12 -1
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +11 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +77 -4
- package/dist/lib/browser/profiles.js +121 -12
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +91 -30
- package/dist/lib/browser/service.js +342 -207
- package/dist/lib/browser/types.d.ts +96 -4
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/daemon/daemon.js +1 -1
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
- package/dist/lib/{runner.js → daemon/runner.js} +59 -118
- package/dist/lib/daemon-webhooks.d.ts +3 -1
- package/dist/lib/daemon-webhooks.js +12 -7
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +3 -3
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- package/dist/lib/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +39 -202
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -0
- package/dist/lib/harness/adapter.d.ts +160 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +12 -114
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +1 -126
- package/dist/lib/installations/versions.js +5 -465
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- package/dist/lib/observe-aliases.d.ts +2 -2
- package/dist/lib/observe-aliases.js +2 -11
- package/dist/lib/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/project-key.js +7 -0
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/scheduling/routines.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +1 -1
- package/dist/lib/secrets/bundles.js +1 -1
- package/dist/lib/secrets/headless.d.ts +16 -0
- package/dist/lib/secrets/headless.js +21 -0
- package/dist/lib/secrets/remote.d.ts +9 -7
- package/dist/lib/secrets/remote.js +18 -9
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- package/dist/lib/session/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/share-html.d.ts +55 -0
- package/dist/lib/session/share-html.js +319 -0
- package/dist/lib/settings-manifest.d.ts +2 -0
- package/dist/lib/settings-manifest.js +81 -3
- package/dist/lib/share/publish.d.ts +38 -0
- package/dist/lib/share/publish.js +81 -8
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +2 -1
- package/dist/lib/startup/command-registry.js +5 -3
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/registry.js +2 -21
- package/dist/lib/triggers/handlers.d.ts +37 -2
- package/dist/lib/triggers/handlers.js +57 -6
- package/dist/lib/triggers/webhook.d.ts +80 -6
- package/dist/lib/triggers/webhook.js +128 -4
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/wrap.d.ts +33 -0
- package/dist/lib/wrap.js +70 -0
- package/package.json +1 -1
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// and PATH-order problem are POSIX; Windows resolves via the registry PATH).
|
|
6
6
|
import { resultOf } from '../types.js';
|
|
7
7
|
import { AGENTS } from '../../agents.js';
|
|
8
|
-
import { getPathShadowingExecutable, adoptShadowingLauncher, listAgentsWithNonIsolatedInstalledVersions, } from '../../shims.js';
|
|
8
|
+
import { getPathShadowingExecutable, adoptShadowingLauncher, listAgentsWithNonIsolatedInstalledVersions, } from '../../installations/shims.js';
|
|
9
9
|
import { getGlobalDefault } from '../../installations/versions.js';
|
|
10
10
|
export const shadowingCheck = {
|
|
11
11
|
id: 'shadowing',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// background so the shim schema settles without user-facing churn.
|
|
5
5
|
import { resultOf } from '../types.js';
|
|
6
6
|
import { AGENTS } from '../../agents.js';
|
|
7
|
-
import { createShim, ensureShimCurrent, ensureVersionedAliasCurrent, isShimCurrent, isVersionedAliasCurrent, removeShim, shimExists, shimPointsAtLiveInstall, removeLegacyUserShim, listAgentsWithInstalledVersions, listAgentsWithNonIsolatedInstalledVersions, listShimFileNames, pruneOrphanedCommandShim, } from '../../shims.js';
|
|
7
|
+
import { createShim, ensureShimCurrent, ensureVersionedAliasCurrent, isShimCurrent, isVersionedAliasCurrent, removeShim, shimExists, shimPointsAtLiveInstall, removeLegacyUserShim, listAgentsWithInstalledVersions, listAgentsWithNonIsolatedInstalledVersions, listShimFileNames, pruneOrphanedCommandShim, } from '../../installations/shims.js';
|
|
8
8
|
import { listInstalledVersions } from '../../installations/versions.js';
|
|
9
9
|
export const shimsCheck = {
|
|
10
10
|
id: 'shims',
|
|
@@ -644,6 +644,29 @@ export declare function scanClaudeSessionIncremental(filePath: string, fromOffse
|
|
|
644
644
|
newOffset: number;
|
|
645
645
|
toolCalls: IndexedToolCall[];
|
|
646
646
|
}>;
|
|
647
|
+
/**
|
|
648
|
+
* Decide full-vs-incremental for one Claude file and parse it uniformly, always
|
|
649
|
+
* returning a finalized scan plus the continuation to persist. Both branches run
|
|
650
|
+
* through the SAME reducer (via {@link scanClaudeSessionIncremental}), so the row
|
|
651
|
+
* an append produces is identical to a from-scratch full reparse by construction
|
|
652
|
+
* (the B-1 parity harness proves this at the function level).
|
|
653
|
+
*
|
|
654
|
+
* INCREMENTAL when a prior continuation exists AND the file grew past the
|
|
655
|
+
* persisted offset AND its mtime did not go backwards — an in-place append.
|
|
656
|
+
* FULL (from byte 0, fresh state) otherwise: cold start (no prior), truncation /
|
|
657
|
+
* rewrite (size shrank to at or below the offset), or a clock rewind / restore
|
|
658
|
+
* (mtime older than the last parse). A FULL parse still produces a continuation,
|
|
659
|
+
* so the file's very next append can go incremental.
|
|
660
|
+
*
|
|
661
|
+
* `mode` is returned so the caller (and tests) can confirm which branch ran.
|
|
662
|
+
*/
|
|
663
|
+
export declare function scanClaudeSessionResumable(filePath: string, prior: ClaudeParserState | null, currentFileMtimeMs: number, currentFileSize: number, priorFileMtimeMs?: number): Promise<{
|
|
664
|
+
scan: ClaudeSessionScan;
|
|
665
|
+
newState: ClaudeParserState;
|
|
666
|
+
newOffset: number;
|
|
667
|
+
toolCalls: IndexedToolCall[];
|
|
668
|
+
mode: 'full' | 'incremental';
|
|
669
|
+
}>;
|
|
647
670
|
/** Test seam: read the (incremental, full) Claude parse counters. */
|
|
648
671
|
export declare function __claudeScanBranchCountsForTest(): {
|
|
649
672
|
incremental: number;
|
|
@@ -776,6 +799,22 @@ export declare function scanCodexSessionIncremental(filePath: string, fromOffset
|
|
|
776
799
|
newOffset: number;
|
|
777
800
|
toolCalls: IndexedToolCall[];
|
|
778
801
|
}>;
|
|
802
|
+
/**
|
|
803
|
+
* Decide full-vs-incremental for one Codex rollout and parse it uniformly,
|
|
804
|
+
* always returning a finalized scan plus the continuation to persist. Both
|
|
805
|
+
* branches run through the SAME reducer (via {@link scanCodexSessionIncremental}),
|
|
806
|
+
* so an append produces a row identical to a from-scratch full reparse by
|
|
807
|
+
* construction. INCREMENTAL when a prior continuation exists AND the file grew
|
|
808
|
+
* past the persisted offset AND its mtime did not go backwards; FULL (from byte
|
|
809
|
+
* 0, fresh state) otherwise (cold start, truncation/rewrite, clock rewind).
|
|
810
|
+
*/
|
|
811
|
+
export declare function scanCodexSessionResumable(filePath: string, prior: CodexParserState | null, currentFileMtimeMs: number, currentFileSize: number, priorFileMtimeMs?: number): Promise<{
|
|
812
|
+
scan: CodexSessionScan;
|
|
813
|
+
newState: CodexParserState;
|
|
814
|
+
newOffset: number;
|
|
815
|
+
toolCalls: IndexedToolCall[];
|
|
816
|
+
mode: 'full' | 'incremental';
|
|
817
|
+
}>;
|
|
779
818
|
/** Test seam: read the (incremental, full) Codex parse counters. */
|
|
780
819
|
export declare function __codexScanBranchCountsForTest(): {
|
|
781
820
|
incremental: number;
|
|
@@ -22,7 +22,7 @@ import { AGENTS, agentConfigDirName, getCliVersion, resolveOpenCodeAccountId } f
|
|
|
22
22
|
import { walkForFilesWithStat } from '../fs-walk.js';
|
|
23
23
|
import { hasCommand } from '../cli-resources.js';
|
|
24
24
|
import { execFileShellSpec } from '../platform/exec.js';
|
|
25
|
-
import { getConfigSymlinkVersion } from '../shims.js';
|
|
25
|
+
import { getConfigSymlinkVersion } from '../installations/shims.js';
|
|
26
26
|
import { SESSION_AGENTS } from './types.js';
|
|
27
27
|
import { deriveShortId } from './short-id.js';
|
|
28
28
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
@@ -3491,7 +3491,7 @@ function freshClaudeParserState() {
|
|
|
3491
3491
|
*
|
|
3492
3492
|
* `mode` is returned so the caller (and tests) can confirm which branch ran.
|
|
3493
3493
|
*/
|
|
3494
|
-
async function scanClaudeSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3494
|
+
export async function scanClaudeSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3495
3495
|
// File size + mtime cannot distinguish an APPEND from an in-place rewrite or a
|
|
3496
3496
|
// restore that dropped DIFFERENT, larger content at the same path: both grow
|
|
3497
3497
|
// the file and move mtime forward. Resuming from the stored offset across that
|
|
@@ -3977,7 +3977,7 @@ async function codexSessionIdentityAt(filePath, maxBytes = 1_048_576) {
|
|
|
3977
3977
|
* past the persisted offset AND its mtime did not go backwards; FULL (from byte
|
|
3978
3978
|
* 0, fresh state) otherwise (cold start, truncation/rewrite, clock rewind).
|
|
3979
3979
|
*/
|
|
3980
|
-
async function scanCodexSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3980
|
+
export async function scanCodexSessionResumable(filePath, prior, currentFileMtimeMs, currentFileSize, priorFileMtimeMs) {
|
|
3981
3981
|
// File size + mtime cannot distinguish an APPEND from an in-place rewrite or a
|
|
3982
3982
|
// restore that dropped a DIFFERENT, larger rollout at the same path: both grow
|
|
3983
3983
|
// the file and move mtime forward. Resuming from the stored offset across that
|
|
@@ -117,53 +117,35 @@ function truncateNormalizedToolOutput(output, maxChars) {
|
|
|
117
117
|
return output;
|
|
118
118
|
return `${output.slice(0, maxChars)}\n\n[Output truncated: ${output.length - maxChars} characters omitted.]`;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Registry-dispatch table: each `SessionAgentId` to its offline transcript
|
|
122
|
+
* parser. Replaces the per-harness `switch` — the harness axis of Move 3. Kept
|
|
123
|
+
* as its own table (not on the HarnessAdapter registry) because its id domain is
|
|
124
|
+
* `SessionAgentId`: `rush` is a session agent with no `AgentId`, and the offline
|
|
125
|
+
* transcript reader is a deliberately separate concern from live team events.
|
|
126
|
+
* The `Record` is total, so a new session harness must add an entry here.
|
|
127
|
+
*/
|
|
128
|
+
const TRANSCRIPT_PARSERS = {
|
|
129
|
+
claude: (filePath, opts) => parseClaude(filePath, opts),
|
|
130
|
+
codex: (filePath) => parseCodex(filePath),
|
|
131
|
+
gemini: (filePath) => parseGemini(filePath),
|
|
132
|
+
antigravity: (filePath) => parseAntigravity(filePath),
|
|
133
|
+
opencode: (filePath) => parseOpenCode(filePath),
|
|
134
|
+
grok: (filePath) => parseGrok(filePath),
|
|
135
|
+
rush: (filePath) => parseRush(filePath),
|
|
136
|
+
openclaw: () => [], // OpenClaw sessions don't have parseable files yet
|
|
137
|
+
hermes: (filePath) => parseHermes(filePath),
|
|
138
|
+
kimi: (filePath) => parseKimi(filePath),
|
|
139
|
+
droid: (filePath) => parseDroid(filePath),
|
|
140
|
+
cursor: (filePath) => parseCursor(filePath),
|
|
141
|
+
muse: (filePath) => parseMuse(filePath),
|
|
142
|
+
};
|
|
120
143
|
export function parseSession(filePath, agent, opts = {}) {
|
|
121
144
|
const detected = agent || detectAgent(filePath);
|
|
122
145
|
if (!detected) {
|
|
123
146
|
throw new Error(`Cannot detect agent type from path: ${filePath}`);
|
|
124
147
|
}
|
|
125
|
-
|
|
126
|
-
switch (detected) {
|
|
127
|
-
case 'claude':
|
|
128
|
-
events = parseClaude(filePath, opts);
|
|
129
|
-
break;
|
|
130
|
-
case 'codex':
|
|
131
|
-
events = parseCodex(filePath);
|
|
132
|
-
break;
|
|
133
|
-
case 'gemini':
|
|
134
|
-
events = parseGemini(filePath);
|
|
135
|
-
break;
|
|
136
|
-
case 'antigravity':
|
|
137
|
-
events = parseAntigravity(filePath);
|
|
138
|
-
break;
|
|
139
|
-
case 'opencode':
|
|
140
|
-
events = parseOpenCode(filePath);
|
|
141
|
-
break;
|
|
142
|
-
case 'grok':
|
|
143
|
-
events = parseGrok(filePath);
|
|
144
|
-
break;
|
|
145
|
-
case 'rush':
|
|
146
|
-
events = parseRush(filePath);
|
|
147
|
-
break;
|
|
148
|
-
case 'openclaw':
|
|
149
|
-
events = [];
|
|
150
|
-
break; // OpenClaw sessions don't have parseable files yet
|
|
151
|
-
case 'hermes':
|
|
152
|
-
events = parseHermes(filePath);
|
|
153
|
-
break;
|
|
154
|
-
case 'kimi':
|
|
155
|
-
events = parseKimi(filePath);
|
|
156
|
-
break;
|
|
157
|
-
case 'droid':
|
|
158
|
-
events = parseDroid(filePath);
|
|
159
|
-
break;
|
|
160
|
-
case 'cursor':
|
|
161
|
-
events = parseCursor(filePath);
|
|
162
|
-
break;
|
|
163
|
-
case 'muse':
|
|
164
|
-
events = parseMuse(filePath);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
148
|
+
const events = TRANSCRIPT_PARSERS[detected](filePath, opts);
|
|
167
149
|
// Chokepoint: every string field that originated in an untrusted session
|
|
168
150
|
// file gets stripped of terminal escapes here, so renderers downstream can
|
|
169
151
|
// safely splat values into chalk/console output. Same pass flags
|
|
@@ -5,7 +5,7 @@ import { isSelfHost } from '../devices/self-host.js';
|
|
|
5
5
|
import { nativeResume } from '../exec.js';
|
|
6
6
|
import { machineId, normalizeHost } from '../machine-id.js';
|
|
7
7
|
import { collectRunCandidates, formatNoHealthyAccountError, pickBalancedCandidate, readinessFromCandidate, } from '../accounting/rotate.js';
|
|
8
|
-
import { getVersionHomePath } from '../installations/
|
|
8
|
+
import { getVersionHomePath } from '../installations/store.js';
|
|
9
9
|
export class SessionRecoveryError extends Error {
|
|
10
10
|
constructor(message) {
|
|
11
11
|
super(message);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { SessionMeta } from './types.js';
|
|
2
|
+
export interface SessionHtmlOptions {
|
|
3
|
+
/** Whether the Markdown was rendered with redaction on — shown in the footer. */
|
|
4
|
+
redacted?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** One metadata chip in the page header. */
|
|
7
|
+
interface Chip {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function escapeHtml(text: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Lift every folded reasoning block out of the Markdown, leaving a sentinel.
|
|
14
|
+
*
|
|
15
|
+
* `renderer.html` escapes raw HTML unconditionally — the transcript body is model
|
|
16
|
+
* and tool output, so nothing in it may round-trip as markup. That also escaped
|
|
17
|
+
* the `<details>` wrapper `--reasoning fold` emits, which is why the disclosure
|
|
18
|
+
* element has to be reconstructed here instead of allowlisted through.
|
|
19
|
+
*
|
|
20
|
+
* Allowlisting was tried twice and is the wrong shape: marked hands the same
|
|
21
|
+
* markup over as several different raw strings depending on context (a whole-token
|
|
22
|
+
* gate passed the opening tag and escaped the closing one in a real document), and
|
|
23
|
+
* a bare `<details>` in ordinary prose is a complete INLINE html token — so prose
|
|
24
|
+
* that merely mentions the tag would emit a live, unclosed element that swallows
|
|
25
|
+
* every later turn into it. Reconstructing both ends here makes the pairing
|
|
26
|
+
* structural: it cannot be unbalanced by anything the transcript contains.
|
|
27
|
+
*/
|
|
28
|
+
export declare function liftFoldBlocks(markdown: string): {
|
|
29
|
+
text: string;
|
|
30
|
+
blocks: string[];
|
|
31
|
+
};
|
|
32
|
+
/** Human duration — "13 minutes", never "12m 49s". */
|
|
33
|
+
export declare function formatDuration(ms: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* The chips shown under the title.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately excludes `account` (an email — the publish-time sensitive-content
|
|
38
|
+
* scan rejects those, and correctly), `cwd`, and `machine`: a published transcript
|
|
39
|
+
* should not carry the operator's identity or local paths in its chrome. Host and
|
|
40
|
+
* repo already ride in the share object's provenance metadata, which is the right
|
|
41
|
+
* home for them.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildChips(session: SessionMeta): Chip[];
|
|
44
|
+
/**
|
|
45
|
+
* The page's `<title>` — `deriveLabel()` reads this as the share's gallery label.
|
|
46
|
+
*
|
|
47
|
+
* The document heading is derived from the session's first prompt, which in a real
|
|
48
|
+
* session is routinely a pasted URL plus a file path plus the actual request. Left
|
|
49
|
+
* whole it renders as a three-line wall above the transcript and an unreadable
|
|
50
|
+
* gallery row, so it is collapsed to one line and cut at a word boundary. `--label`
|
|
51
|
+
* overrides it outright.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sessionPageTitle(session: SessionMeta, markdown: string): string;
|
|
54
|
+
export declare function renderSessionHtmlDocument(session: SessionMeta, markdown: string, options?: SessionHtmlOptions): string;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap an already-rendered session transcript in ONE self-contained HTML page for
|
|
3
|
+
* `agents sessions share`.
|
|
4
|
+
*
|
|
5
|
+
* Takes the Markdown document as an argument rather than producing it, so the
|
|
6
|
+
* decisions that make a transcript safe to publish — redaction, synthetic-turn
|
|
7
|
+
* filtering, tool-output truncation, reasoning visibility — stay in
|
|
8
|
+
* `renderSessionMarkdownDocument()` and the HTML path can never drift from the
|
|
9
|
+
* Markdown one. That also keeps this a pure presentation function with no import
|
|
10
|
+
* back into the command layer.
|
|
11
|
+
*
|
|
12
|
+
* Self-contained on purpose: an inline <style>, no external assets, no CDN, no
|
|
13
|
+
* dependency on the artifacts-cli host CLI (which is not configured on every box,
|
|
14
|
+
* RUSH-2728). The page is uploaded verbatim to the share Worker.
|
|
15
|
+
*
|
|
16
|
+
* Terminal-coded per the agents-cli brand (#0a0a0a bg, #a3e635 lime accent,
|
|
17
|
+
* JetBrains Mono), with a light theme under `prefers-color-scheme: light` and an
|
|
18
|
+
* in-page toggle so a published link is readable in bright light and dim alike.
|
|
19
|
+
*/
|
|
20
|
+
import { marked, Renderer } from 'marked';
|
|
21
|
+
export function escapeHtml(text) {
|
|
22
|
+
return text
|
|
23
|
+
.replace(/&/g, '&')
|
|
24
|
+
.replace(/</g, '<')
|
|
25
|
+
.replace(/>/g, '>')
|
|
26
|
+
.replace(/"/g, '"')
|
|
27
|
+
.replace(/'/g, ''');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A transcript is untrusted text: it carries whatever the model wrote and whatever
|
|
31
|
+
* a tool printed. marked passes raw HTML through by default, so a session that
|
|
32
|
+
* merely *discussed* a `<script>` tag would ship an executable one on a public URL.
|
|
33
|
+
* Escaping the html token's raw source neutralizes that without mangling the
|
|
34
|
+
* visible text, and non-http(s) link schemes are dropped so `javascript:` cannot
|
|
35
|
+
* ride in through a Markdown link.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* The only raw HTML the transcript renderer itself emits: `--reasoning fold` wraps
|
|
39
|
+
* each thinking block in a disclosure element (`session/render.ts` — the `fold`
|
|
40
|
+
* branch pushes `<details>\n<summary>Reasoning</summary>` and `</details>`).
|
|
41
|
+
*
|
|
42
|
+
* Escaping those along with everything else published `<details>` as visible
|
|
43
|
+
* text and no collapsing, so `--reasoning fold` shipped broken. They are allowed
|
|
44
|
+
* back through by EXACT match on the token's raw source, not by parsing tags:
|
|
45
|
+
* attribute-free `details`/`summary` carry no script, no URL, and no event handler,
|
|
46
|
+
* and an exact-string gate cannot be widened by crafted input the way a tag parser
|
|
47
|
+
* can. Anything else — including `<details onclick=…>` — still escapes.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Sentinel standing in for one folded reasoning block while the document goes
|
|
51
|
+
* through marked. Deliberately not valid Markdown or HTML, and any pre-existing
|
|
52
|
+
* occurrence in the transcript is neutralized before substitution.
|
|
53
|
+
*/
|
|
54
|
+
const FOLD_SENTINEL = 'aGeNtSfOlDbLoCk';
|
|
55
|
+
// The neutralization below is only sound because this literal is BORDER-FREE: its
|
|
56
|
+
// first character occurs nowhere else in it, so no occurrence can overlap another
|
|
57
|
+
// and splitting on it cannot leave a fresh one behind. A bordered literal (`aba`
|
|
58
|
+
// splits into a leftover `a` + `ba`) is defeatable. Keep that property if you
|
|
59
|
+
// ever change the string.
|
|
60
|
+
/** The exact block `session/render.ts` pushes on its `fold` branch. */
|
|
61
|
+
const FOLD_BLOCK = /<details>\n<summary>Reasoning<\/summary>\n\n([\s\S]*?)\n\n<\/details>/g;
|
|
62
|
+
/**
|
|
63
|
+
* Lift every folded reasoning block out of the Markdown, leaving a sentinel.
|
|
64
|
+
*
|
|
65
|
+
* `renderer.html` escapes raw HTML unconditionally — the transcript body is model
|
|
66
|
+
* and tool output, so nothing in it may round-trip as markup. That also escaped
|
|
67
|
+
* the `<details>` wrapper `--reasoning fold` emits, which is why the disclosure
|
|
68
|
+
* element has to be reconstructed here instead of allowlisted through.
|
|
69
|
+
*
|
|
70
|
+
* Allowlisting was tried twice and is the wrong shape: marked hands the same
|
|
71
|
+
* markup over as several different raw strings depending on context (a whole-token
|
|
72
|
+
* gate passed the opening tag and escaped the closing one in a real document), and
|
|
73
|
+
* a bare `<details>` in ordinary prose is a complete INLINE html token — so prose
|
|
74
|
+
* that merely mentions the tag would emit a live, unclosed element that swallows
|
|
75
|
+
* every later turn into it. Reconstructing both ends here makes the pairing
|
|
76
|
+
* structural: it cannot be unbalanced by anything the transcript contains.
|
|
77
|
+
*/
|
|
78
|
+
export function liftFoldBlocks(markdown) {
|
|
79
|
+
const blocks = [];
|
|
80
|
+
// A transcript that literally contains the sentinel must not be able to forge a
|
|
81
|
+
// disclosure element, so break any occurrence before inserting our own.
|
|
82
|
+
const safe = markdown.split(FOLD_SENTINEL).join(`${FOLD_SENTINEL[0]}${FOLD_SENTINEL.slice(1)}`);
|
|
83
|
+
const text = safe.replace(FOLD_BLOCK, (_match, inner) => {
|
|
84
|
+
blocks.push(inner);
|
|
85
|
+
return `${FOLD_SENTINEL}${blocks.length - 1}${FOLD_SENTINEL}`;
|
|
86
|
+
});
|
|
87
|
+
return { text, blocks };
|
|
88
|
+
}
|
|
89
|
+
function safeRenderer() {
|
|
90
|
+
const renderer = new Renderer();
|
|
91
|
+
renderer.html = ({ raw }) => escapeHtml(raw);
|
|
92
|
+
const isSafeHref = (href) => /^(https?:|mailto:|#|\/)/i.test(href.trim());
|
|
93
|
+
renderer.link = ({ href, title, tokens }) => {
|
|
94
|
+
const text = renderer.parser.parseInline(tokens);
|
|
95
|
+
if (!isSafeHref(href))
|
|
96
|
+
return text;
|
|
97
|
+
const attrs = title ? ` title="${escapeHtml(title)}"` : '';
|
|
98
|
+
return `<a href="${escapeHtml(href)}"${attrs} rel="noopener noreferrer nofollow">${text}</a>`;
|
|
99
|
+
};
|
|
100
|
+
renderer.image = ({ href, title, text }) => {
|
|
101
|
+
if (!isSafeHref(href))
|
|
102
|
+
return escapeHtml(text);
|
|
103
|
+
const attrs = title ? ` title="${escapeHtml(title)}"` : '';
|
|
104
|
+
return `<img src="${escapeHtml(href)}" alt="${escapeHtml(text)}"${attrs} loading="lazy" />`;
|
|
105
|
+
};
|
|
106
|
+
return renderer;
|
|
107
|
+
}
|
|
108
|
+
/** Human duration — "13 minutes", never "12m 49s". */
|
|
109
|
+
export function formatDuration(ms) {
|
|
110
|
+
const minutes = Math.round(ms / 60_000);
|
|
111
|
+
if (minutes < 1)
|
|
112
|
+
return 'under a minute';
|
|
113
|
+
if (minutes < 60)
|
|
114
|
+
return `${minutes} minute${minutes === 1 ? '' : 's'}`;
|
|
115
|
+
const hours = Math.round(ms / 3_600_000);
|
|
116
|
+
if (hours < 24)
|
|
117
|
+
return `${hours} hour${hours === 1 ? '' : 's'}`;
|
|
118
|
+
const days = Math.round(ms / 86_400_000);
|
|
119
|
+
return `${days} day${days === 1 ? '' : 's'}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The chips shown under the title.
|
|
123
|
+
*
|
|
124
|
+
* Deliberately excludes `account` (an email — the publish-time sensitive-content
|
|
125
|
+
* scan rejects those, and correctly), `cwd`, and `machine`: a published transcript
|
|
126
|
+
* should not carry the operator's identity or local paths in its chrome. Host and
|
|
127
|
+
* repo already ride in the share object's provenance metadata, which is the right
|
|
128
|
+
* home for them.
|
|
129
|
+
*/
|
|
130
|
+
export function buildChips(session) {
|
|
131
|
+
const chips = [{ label: 'agent', value: session.agent }];
|
|
132
|
+
if (session.model)
|
|
133
|
+
chips.push({ label: 'model', value: session.model });
|
|
134
|
+
if (session.mode)
|
|
135
|
+
chips.push({ label: 'mode', value: session.mode });
|
|
136
|
+
if (session.project)
|
|
137
|
+
chips.push({ label: 'project', value: session.project });
|
|
138
|
+
if (session.gitBranch)
|
|
139
|
+
chips.push({ label: 'branch', value: session.gitBranch });
|
|
140
|
+
if (session.ticketId)
|
|
141
|
+
chips.push({ label: 'ticket', value: session.ticketId });
|
|
142
|
+
const date = (session.timestamp || '').slice(0, 10);
|
|
143
|
+
if (date)
|
|
144
|
+
chips.push({ label: 'date', value: date });
|
|
145
|
+
if (session.durationMs)
|
|
146
|
+
chips.push({ label: 'duration', value: formatDuration(session.durationMs) });
|
|
147
|
+
if (session.messageCount)
|
|
148
|
+
chips.push({ label: 'turns', value: String(session.messageCount) });
|
|
149
|
+
if (session.toolCallCount)
|
|
150
|
+
chips.push({ label: 'tools', value: String(session.toolCallCount) });
|
|
151
|
+
return chips;
|
|
152
|
+
}
|
|
153
|
+
/** Longest title that stays one readable line in the header and the gallery. */
|
|
154
|
+
const TITLE_MAX = 90;
|
|
155
|
+
/**
|
|
156
|
+
* The page's `<title>` — `deriveLabel()` reads this as the share's gallery label.
|
|
157
|
+
*
|
|
158
|
+
* The document heading is derived from the session's first prompt, which in a real
|
|
159
|
+
* session is routinely a pasted URL plus a file path plus the actual request. Left
|
|
160
|
+
* whole it renders as a three-line wall above the transcript and an unreadable
|
|
161
|
+
* gallery row, so it is collapsed to one line and cut at a word boundary. `--label`
|
|
162
|
+
* overrides it outright.
|
|
163
|
+
*/
|
|
164
|
+
export function sessionPageTitle(session, markdown) {
|
|
165
|
+
const heading = /^#\s+(.+)$/m.exec(markdown)?.[1]?.replace(/\s+/g, ' ').trim();
|
|
166
|
+
if (!heading)
|
|
167
|
+
return `${session.agent} session ${session.shortId || session.id}`;
|
|
168
|
+
if (heading.length <= TITLE_MAX)
|
|
169
|
+
return heading;
|
|
170
|
+
const cut = heading.slice(0, TITLE_MAX);
|
|
171
|
+
const lastSpace = cut.lastIndexOf(' ');
|
|
172
|
+
// A single unbroken token longer than the cap (a URL) has no boundary to cut on;
|
|
173
|
+
// a hard slice beats returning the whole thing.
|
|
174
|
+
return `${(lastSpace > TITLE_MAX / 2 ? cut.slice(0, lastSpace) : cut).trimEnd()}…`;
|
|
175
|
+
}
|
|
176
|
+
export function renderSessionHtmlDocument(session, markdown, options = {}) {
|
|
177
|
+
const title = sessionPageTitle(session, markdown);
|
|
178
|
+
// The <h1> is re-rendered as the page header, so drop it from the body to
|
|
179
|
+
// avoid printing the title twice.
|
|
180
|
+
const { text, blocks } = liftFoldBlocks(markdown.replace(/^#\s+.+\n/, ''));
|
|
181
|
+
const render = (md) => marked.parse(md, { renderer: safeRenderer(), async: false });
|
|
182
|
+
// Reconstruct each disclosure element around its own separately-rendered
|
|
183
|
+
// content, so the open/close pairing is ours and cannot be unbalanced by the
|
|
184
|
+
// transcript.
|
|
185
|
+
const body = blocks.reduce((html, inner, i) => html.replace(new RegExp(`<p>${FOLD_SENTINEL}${i}${FOLD_SENTINEL}</p>`),
|
|
186
|
+
// A FUNCTION replacement, never a string one. String.replace expands `$&`,
|
|
187
|
+
// `` $` ``, `$'` and `$n` inside a string replacement, and marked turns
|
|
188
|
+
// `& < > " '` into entities — so reasoning text containing `$'` (bash
|
|
189
|
+
// ANSI-C quoting), `$&` (sed), or `$<` (make) would splice the matched
|
|
190
|
+
// sentinel back into the page and destroy the character after it. A
|
|
191
|
+
// function's return value is inserted verbatim.
|
|
192
|
+
() => `<details><summary>Reasoning</summary>\n${render(inner)}</details>`), render(text));
|
|
193
|
+
const chips = buildChips(session)
|
|
194
|
+
.map((c) => `<span class="chip"><span class="k">${escapeHtml(c.label)}</span>${escapeHtml(c.value)}</span>`)
|
|
195
|
+
.join('\n ');
|
|
196
|
+
const redacted = options.redacted !== false;
|
|
197
|
+
return `<!DOCTYPE html>
|
|
198
|
+
<html lang="en" data-theme="auto">
|
|
199
|
+
<head>
|
|
200
|
+
<meta charset="utf-8" />
|
|
201
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
202
|
+
<meta name="robots" content="noindex" />
|
|
203
|
+
<title>${escapeHtml(title)}</title>
|
|
204
|
+
<style>
|
|
205
|
+
:root {
|
|
206
|
+
--bg: #0a0a0a; --panel: #121212; --border: #262626; --fg: #e5e5e5;
|
|
207
|
+
--dim: #737373; --accent: #a3e635; --quote: #1a1a1a;
|
|
208
|
+
}
|
|
209
|
+
html[data-theme="light"] {
|
|
210
|
+
--bg: #fafafa; --panel: #ffffff; --border: #e5e5e5; --fg: #171717;
|
|
211
|
+
--dim: #737373; --accent: #4d7c0f; --quote: #f5f5f5;
|
|
212
|
+
}
|
|
213
|
+
@media (prefers-color-scheme: light) {
|
|
214
|
+
html[data-theme="auto"] {
|
|
215
|
+
--bg: #fafafa; --panel: #ffffff; --border: #e5e5e5; --fg: #171717;
|
|
216
|
+
--dim: #737373; --accent: #4d7c0f; --quote: #f5f5f5;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
* { box-sizing: border-box; }
|
|
220
|
+
body {
|
|
221
|
+
margin: 0; background: var(--bg); color: var(--fg);
|
|
222
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, sans-serif;
|
|
223
|
+
font-size: 15px; line-height: 1.65;
|
|
224
|
+
}
|
|
225
|
+
header {
|
|
226
|
+
border-bottom: 1px solid var(--border); padding: 28px 20px 20px;
|
|
227
|
+
}
|
|
228
|
+
header .inner, main { max-width: 900px; margin: 0 auto; }
|
|
229
|
+
header .mark {
|
|
230
|
+
color: var(--accent); font-weight: 700; letter-spacing: .5px; font-size: 12px;
|
|
231
|
+
text-transform: uppercase; font-family: ui-monospace, "JetBrains Mono", Menlo, monospace;
|
|
232
|
+
}
|
|
233
|
+
header h1 { font-size: 24px; line-height: 1.3; margin: 10px 0 14px; }
|
|
234
|
+
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
235
|
+
.chip {
|
|
236
|
+
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 11px;
|
|
237
|
+
color: var(--fg); background: var(--panel); border: 1px solid var(--border);
|
|
238
|
+
border-radius: 10px; padding: 2px 9px;
|
|
239
|
+
}
|
|
240
|
+
.chip .k { color: var(--dim); margin-right: 6px; }
|
|
241
|
+
.toggle {
|
|
242
|
+
float: right; cursor: pointer; background: none; border: 1px solid var(--border);
|
|
243
|
+
color: var(--dim); border-radius: 6px; padding: 2px 8px; font-size: 14px;
|
|
244
|
+
}
|
|
245
|
+
main { padding: 24px 20px 64px; }
|
|
246
|
+
h2 {
|
|
247
|
+
font-size: 13px; color: var(--accent); border-bottom: 1px solid var(--border);
|
|
248
|
+
padding-bottom: 6px; margin: 36px 0 14px; text-transform: uppercase;
|
|
249
|
+
letter-spacing: 1px; font-family: ui-monospace, "JetBrains Mono", Menlo, monospace;
|
|
250
|
+
}
|
|
251
|
+
h3 { font-size: 15px; margin: 26px 0 8px; }
|
|
252
|
+
h4 { font-size: 13px; color: var(--dim); margin: 20px 0 6px; font-weight: 600; }
|
|
253
|
+
a { color: var(--accent); }
|
|
254
|
+
blockquote {
|
|
255
|
+
margin: 0 0 20px; padding: 12px 16px; background: var(--quote);
|
|
256
|
+
border-left: 2px solid var(--border); border-radius: 0 6px 6px 0; color: var(--dim);
|
|
257
|
+
}
|
|
258
|
+
blockquote p { margin: 0 0 6px; }
|
|
259
|
+
blockquote p:last-child { margin: 0; }
|
|
260
|
+
pre {
|
|
261
|
+
background: var(--panel); border: 1px solid var(--border); border-radius: 6px;
|
|
262
|
+
padding: 12px 14px; overflow-x: auto;
|
|
263
|
+
}
|
|
264
|
+
code {
|
|
265
|
+
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12.5px;
|
|
266
|
+
}
|
|
267
|
+
:not(pre) > code {
|
|
268
|
+
background: var(--panel); border: 1px solid var(--border);
|
|
269
|
+
border-radius: 4px; padding: 1px 5px;
|
|
270
|
+
}
|
|
271
|
+
hr { border: none; border-top: 1px solid var(--border); margin: 32px 0; }
|
|
272
|
+
details { margin: 0 0 12px; }
|
|
273
|
+
summary { cursor: pointer; color: var(--dim); font-size: 13px; }
|
|
274
|
+
table { border-collapse: collapse; width: 100%; font-size: 13px; }
|
|
275
|
+
th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }
|
|
276
|
+
footer {
|
|
277
|
+
max-width: 900px; margin: 0 auto; padding: 0 20px 48px;
|
|
278
|
+
color: var(--dim); font-size: 12px;
|
|
279
|
+
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace;
|
|
280
|
+
}
|
|
281
|
+
footer a { color: var(--dim); }
|
|
282
|
+
</style>
|
|
283
|
+
</head>
|
|
284
|
+
<body>
|
|
285
|
+
<header>
|
|
286
|
+
<div class="inner">
|
|
287
|
+
<button class="toggle" id="theme" title="Toggle light and dark">◙</button>
|
|
288
|
+
<div class="mark">agents session</div>
|
|
289
|
+
<h1>${escapeHtml(title)}</h1>
|
|
290
|
+
<div class="chips">
|
|
291
|
+
${chips}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</header>
|
|
295
|
+
<main>
|
|
296
|
+
${body}
|
|
297
|
+
</main>
|
|
298
|
+
<footer>
|
|
299
|
+
${redacted ? 'Secret-redacted transcript' : 'Unredacted transcript'} rendered by
|
|
300
|
+
<a href="https://agents-cli.sh">agents-cli</a> · <code>agents sessions share</code>
|
|
301
|
+
</footer>
|
|
302
|
+
<script>
|
|
303
|
+
(function () {
|
|
304
|
+
var root = document.documentElement;
|
|
305
|
+
var saved = null;
|
|
306
|
+
try { saved = localStorage.getItem('agents-share-theme'); } catch (e) {}
|
|
307
|
+
if (saved) root.setAttribute('data-theme', saved);
|
|
308
|
+
document.getElementById('theme').addEventListener('click', function () {
|
|
309
|
+
var dark = getComputedStyle(root).getPropertyValue('--bg').trim() === '#0a0a0a';
|
|
310
|
+
var next = dark ? 'light' : 'dark';
|
|
311
|
+
root.setAttribute('data-theme', next);
|
|
312
|
+
try { localStorage.setItem('agents-share-theme', next); } catch (e) {}
|
|
313
|
+
});
|
|
314
|
+
})();
|
|
315
|
+
</script>
|
|
316
|
+
</body>
|
|
317
|
+
</html>
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* version home. It never overwrites a value the target already has: scalars
|
|
14
14
|
* keep the target's value, objects merge recursively, arrays union. That makes
|
|
15
15
|
* the operation idempotent and safe to run on every `agents add` / `agents use`.
|
|
16
|
+
* (One scoped exception: the 'claude-trust' strategy promotes a stamped-default
|
|
17
|
+
* `hasTrustDialogAccepted: false` to `true` — see the note on that entry.)
|
|
16
18
|
*/
|
|
17
19
|
import type { AgentId } from './types.js';
|
|
18
20
|
export interface CarryForwardResult {
|