@agentproto/runtime 3.6.1 → 4.1.0
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/README.md +8 -3
- package/dist/app-ui-build.d.ts +96 -0
- package/dist/app-ui-build.mjs +250 -0
- package/dist/app-ui-build.mjs.map +1 -0
- package/dist/app-ui-delivery.d.ts +75 -0
- package/dist/app-ui-delivery.mjs +144 -0
- package/dist/app-ui-delivery.mjs.map +1 -0
- package/dist/app-ui-placeholder.d.ts +73 -0
- package/dist/app-ui-placeholder.mjs +89 -0
- package/dist/app-ui-placeholder.mjs.map +1 -0
- package/dist/catalog-models.d.ts +5 -3
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/config-schema.d.ts +134 -0
- package/dist/config-schema.mjs +3697 -0
- package/dist/config-schema.mjs.map +1 -0
- package/dist/config.d.ts +22 -2
- package/dist/config.mjs +3615 -4
- package/dist/config.mjs.map +1 -1
- package/dist/{context-continuity-ib9_bVYM.d.ts → context-continuity-Bn99UtVC.d.ts} +40 -1
- package/dist/{heartbeat-COGpMrJS.d.ts → heartbeat-BJtxbDb9.d.ts} +13 -1
- package/dist/heartbeat.d.ts +1 -1
- package/dist/index.d.ts +3057 -151
- package/dist/index.mjs +17669 -7188
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-imports.d.ts +2 -1
- package/dist/resume-strategies.mjs +27 -1
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{session-config-DbWP9RRj.d.ts → session-config-ByfVeEQJ.d.ts} +1 -1
- package/dist/session-presence.mjs +3613 -6
- package/dist/session-presence.mjs.map +1 -1
- package/dist/session-story.d.ts +10 -0
- package/dist/{spawn-defaults-CwI9Puwy.d.ts → spawn-defaults-CZj39Znq.d.ts} +146 -2
- package/dist/user-presets.d.ts +8 -2
- package/dist/user-presets.mjs +2 -1
- package/dist/user-presets.mjs.map +1 -1
- package/docs/TRANSMITTER.md +356 -0
- package/docs/mcp-tools/agent_start.md +476 -0
- package/docs/mcp-tools/app_run.md +47 -0
- package/package.json +47 -23
package/dist/index.d.ts
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
import { DoctypeSpec } from '@agentproto/manifest';
|
|
2
|
-
import { B as BuildHeartbeatAgent } from './heartbeat-
|
|
3
|
-
export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-
|
|
2
|
+
import { B as BuildHeartbeatAgent } from './heartbeat-BJtxbDb9.js';
|
|
3
|
+
export { H as HeartbeatAgent, a as HeartbeatRunner, R as RuntimeEvent, b as RuntimeEvents, p as parseDuration } from './heartbeat-BJtxbDb9.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { AcpMcpServer, AcpPermissionResolution } from '@agentproto/acp';
|
|
6
6
|
import { SessionMode } from '@agentproto/acp/client';
|
|
7
7
|
export { SessionMode } from '@agentproto/acp/client';
|
|
8
8
|
import { ChildProcess } from 'node:child_process';
|
|
9
|
-
import { R as RouteSpec, S as SessionConfig, P as Posture, C as ContextProfile, E as EffortLevel, A as AuthMethod, a as CanonicalPosture } from './session-config-
|
|
10
|
-
export { D as DeclaredAdapterMode, c as composeMode, d as decomposeMode } from './session-config-
|
|
9
|
+
import { R as RouteSpec, S as SessionConfig, P as Posture, C as ContextProfile, E as EffortLevel, A as AuthMethod, a as CanonicalPosture } from './session-config-ByfVeEQJ.js';
|
|
10
|
+
export { D as DeclaredAdapterMode, c as composeMode, d as decomposeMode } from './session-config-ByfVeEQJ.js';
|
|
11
11
|
import { CostBudget, AuthProfile, CostBudgetScope } from '@agentproto/auth';
|
|
12
12
|
export { CostBudget, CostBudgetScope } from '@agentproto/auth';
|
|
13
|
-
import { R as ResolvedContextContinuityPolicy } from './context-continuity-
|
|
13
|
+
import { S as SpawnBrowserMode, R as ResolveHeadlessBrowser, C as ContextContinuityPolicy, a as ResolvedContextContinuityPolicy } from './context-continuity-Bn99UtVC.js';
|
|
14
14
|
import { SandboxMode } from '@agentproto/command-sandbox';
|
|
15
15
|
import { AdapterHandle, SetupField, AdapterResolver, AdapterLister, CredsStore, HarnessCapabilities, AdapterEntry } from '@agentproto/provider-kit';
|
|
16
|
-
import { R as ResolvedAuthSpec,
|
|
17
|
-
export {
|
|
16
|
+
import { S as SpawnDefaultsConfig, D as DefaultsAdapterAuthConfig, R as ResolvedAuthSpec, a as DeclaredAdapterOption, A as AdapterAuthDescriptor } from './spawn-defaults-CZj39Znq.js';
|
|
17
|
+
export { b as AuthEcho, c as AuthResolutionError, C as CLAUDE_CODE_OAUTH_SOURCE, d as CredentialSource, e as DefaultsAdapterConfig, f as DeferredToolsConfig, g as ResolveSubscriptionCredentialInput, h as ResolvedSpawnAuthMaterial, i as ResolvedSpawnDefaults, j as SubscriptionCredentialResolution, k as SubscriptionSourceError, l as credentialFingerprint, n as normalizeSkillsOption, r as resolveAuthSpec, m as resolveDeferredToolsGatewayOption, o as resolveSpawnDefaults, p as resolveSubscriptionCredential } from './spawn-defaults-CZj39Znq.js';
|
|
18
18
|
import { CatalogModelsQuery, CatalogModelsResponse } from './catalog-models.js';
|
|
19
19
|
export { CatalogAdapterInput, CatalogAdapterModelInput, CatalogPricing, CatalogProduct, CatalogRoute, CatalogRouteSummary, CatalogVendor, buildCatalogModels } from './catalog-models.js';
|
|
20
20
|
import { FooterSession } from './pr-provenance.js';
|
|
21
21
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
|
-
import { SandboxProvider } from '@agentproto/sandbox';
|
|
22
|
+
import { SandboxProvider, SandboxSpec } from '@agentproto/sandbox';
|
|
23
23
|
import { BrainManager, ExportedSessionLike } from '@agentproto/workspace-brain';
|
|
24
24
|
export { BrainManager, BrainStats, IngestReport, IngestResult } from '@agentproto/workspace-brain';
|
|
25
25
|
import * as _agentproto_tool from '@agentproto/tool';
|
|
26
26
|
import { ToolContext } from '@agentproto/tool';
|
|
27
27
|
import { DriverHandle } from '@agentproto/driver';
|
|
28
|
-
|
|
28
|
+
import { Attestation, LedgerKey, RubricDigest, ReviewPrRef, LaneResult, ReviewLaneExecutor, LaneOutcome } from '@agentproto/review';
|
|
29
|
+
import { UserPreset } from './user-presets.js';
|
|
30
|
+
export { UserPresetsFile, deleteUserPreset, getUserPreset, listUserPresets, loadUserPresets, saveUserPreset, userPresetsPath } from './user-presets.js';
|
|
31
|
+
import { WorktreeIsolationMode, SpawnAttachMode, SpawnDedupeMode } from './config.js';
|
|
29
32
|
import { ResolvedModel } from '@agentproto/model-catalog';
|
|
30
33
|
import { FrameSink, E2eFrameSink } from '@agentproto/acp/tunnel';
|
|
31
34
|
import { DaemonIdentity } from '@agentproto/secrets/identity';
|
|
32
|
-
import { WorktreeIsolationMode } from './config.js';
|
|
33
35
|
import { WorkspaceFs } from './workspace-fs.js';
|
|
34
36
|
export { createWorkspaceFs } from './workspace-fs.js';
|
|
35
37
|
export { ConversationMeta, ConversationStore, ConversationTurn, fileConversationStore } from './conversations.js';
|
|
@@ -449,13 +451,64 @@ type UsageSource = "adapter" | "computed" | "no-pricing" | "none";
|
|
|
449
451
|
interface TokenPricing {
|
|
450
452
|
inputPer1M: number;
|
|
451
453
|
outputPer1M: number;
|
|
454
|
+
/** Multiplier on `inputPer1M` for cache-read tokens (Anthropic: ~0.1).
|
|
455
|
+
* Absent = 1 (no discount), same default as the catalog. */
|
|
456
|
+
cacheReadMultiplier?: number;
|
|
457
|
+
/** Multiplier on `inputPer1M` for cache-creation tokens (Anthropic:
|
|
458
|
+
* ~1.25). Absent = 1. */
|
|
459
|
+
cacheWriteMultiplier?: number;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Token-detail and activity fields shared by every usage shape below. All
|
|
463
|
+
* optional and never zero-filled: absent means "this source doesn't report
|
|
464
|
+
* it", a present 0 is a measured zero.
|
|
465
|
+
*
|
|
466
|
+
* - `cacheReadTokens` / `cacheWriteTokens`: prompt-cache reads / cache
|
|
467
|
+
* creation, cumulative. Kept OUT of `tokensIn` (which is uncached input
|
|
468
|
+
* only when a cache split is known).
|
|
469
|
+
* - `reasoningTokens`: thinking/reasoning tokens, cumulative. A subset of
|
|
470
|
+
* `tokensOut` for providers that bill them as output (Anthropic).
|
|
471
|
+
* - `turns`: completed turns (the descriptor's `turnsCompleted`).
|
|
472
|
+
* - `toolCalls`: tool calls the daemon observed, cumulative.
|
|
473
|
+
* - `durationMs`: wall time spent inside turns, cumulative.
|
|
474
|
+
*/
|
|
475
|
+
interface UsageDetail {
|
|
476
|
+
cacheReadTokens?: number;
|
|
477
|
+
cacheWriteTokens?: number;
|
|
478
|
+
reasoningTokens?: number;
|
|
479
|
+
turns?: number;
|
|
480
|
+
toolCalls?: number;
|
|
481
|
+
durationMs?: number;
|
|
452
482
|
}
|
|
483
|
+
/** Copy only the present (`number`) `UsageDetail` fields of `src`. */
|
|
484
|
+
declare function pickUsageDetail(src: UsageDetail): UsageDetail;
|
|
453
485
|
/** Pluggable pricing accessor — defaults to the in-repo catalog's
|
|
454
486
|
* `resolvePricing`, overridable in tests. Returns undefined for an
|
|
455
487
|
* unknown model (the caller then tags `no-pricing`). */
|
|
456
488
|
type PricingResolver = (model: string) => TokenPricing | undefined;
|
|
489
|
+
/**
|
|
490
|
+
* What an adapter's usage reader (`readUsage`: hermes state.db, opencode.db,
|
|
491
|
+
* the claude-code transcript JSONL) returns. Every field is cumulative for
|
|
492
|
+
* the session and optional; `null` from the reader means "nothing found".
|
|
493
|
+
*/
|
|
494
|
+
interface UsageReadResult {
|
|
495
|
+
model?: string;
|
|
496
|
+
costUsd?: number;
|
|
497
|
+
tokensIn?: number;
|
|
498
|
+
tokensOut?: number;
|
|
499
|
+
cacheReadTokens?: number;
|
|
500
|
+
cacheWriteTokens?: number;
|
|
501
|
+
reasoningTokens?: number;
|
|
502
|
+
}
|
|
503
|
+
/** Where a reader should look for the session's native store — the spawn's
|
|
504
|
+
* cwd and isolated provider config dir (`CLAUDE_CONFIG_DIR` for
|
|
505
|
+
* claude-code). Readers that key only on the session id ignore it. */
|
|
506
|
+
interface UsageReadContext {
|
|
507
|
+
cwd?: string;
|
|
508
|
+
configDir?: string;
|
|
509
|
+
}
|
|
457
510
|
/** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */
|
|
458
|
-
interface UsageComputeInput {
|
|
511
|
+
interface UsageComputeInput extends UsageDetail {
|
|
459
512
|
/** Requested model id — needed to look up per-token prices. */
|
|
460
513
|
model?: string;
|
|
461
514
|
/** Cost the adapter reported directly (readUsage or usage_update.cost).
|
|
@@ -469,7 +522,7 @@ interface UsageComputeInput {
|
|
|
469
522
|
/** The resolved usage snapshot — the shape `session_usage` returns and the
|
|
470
523
|
* durable `usage_snapshot` transcript record carries. Cost/token fields are
|
|
471
524
|
* omitted when absent so a missing value never reads as a measured zero. */
|
|
472
|
-
interface SessionUsage {
|
|
525
|
+
interface SessionUsage extends UsageDetail {
|
|
473
526
|
model?: string;
|
|
474
527
|
costUsd?: number;
|
|
475
528
|
tokensIn?: number;
|
|
@@ -486,7 +539,7 @@ interface SessionUsage {
|
|
|
486
539
|
*/
|
|
487
540
|
declare function deriveSessionUsage(input: UsageComputeInput, resolve?: PricingResolver): SessionUsage;
|
|
488
541
|
/** Descriptor-shaped fields `projectSessionUsage` reads. */
|
|
489
|
-
interface UsageDescriptorFields {
|
|
542
|
+
interface UsageDescriptorFields extends Omit<UsageDetail, "turns"> {
|
|
490
543
|
model?: string;
|
|
491
544
|
costUsd?: number;
|
|
492
545
|
tokensIn?: number;
|
|
@@ -494,6 +547,8 @@ interface UsageDescriptorFields {
|
|
|
494
547
|
contextSize?: number;
|
|
495
548
|
contextUsed?: number;
|
|
496
549
|
usageSource?: UsageSource;
|
|
550
|
+
/** Surfaced as `turns`. */
|
|
551
|
+
turnsCompleted?: number;
|
|
497
552
|
}
|
|
498
553
|
/**
|
|
499
554
|
* Project a session descriptor's persisted usage fields into the
|
|
@@ -502,6 +557,37 @@ interface UsageDescriptorFields {
|
|
|
502
557
|
*/
|
|
503
558
|
declare function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage;
|
|
504
559
|
|
|
560
|
+
/**
|
|
561
|
+
* McpAppsHostService — the daemon as the MCP client that fetches MCP App
|
|
562
|
+
* UIs (spec 2026-01-26) on behalf of a session's chat UI.
|
|
563
|
+
*
|
|
564
|
+
* In a session the MCP client is the harness (claude-code, codex, …); it
|
|
565
|
+
* keeps a tool's `_meta.ui.resourceUri` and the `ui://` resource to itself,
|
|
566
|
+
* and the transcript only carries `mcp__<server>__<tool>` + input + result.
|
|
567
|
+
* So the chat asks the daemon, which resolves `<server>` for that session
|
|
568
|
+
* (mcp-app-resolve.ts), connects through a config-keyed pool
|
|
569
|
+
* (mcp-client-pool.ts), and answers three questions:
|
|
570
|
+
*
|
|
571
|
+
* uiIndex which of this server's tools declare a UI (+ app-only tools)
|
|
572
|
+
* readUi the HTML + `_meta.ui` of one of those `ui://` resources
|
|
573
|
+
* callTool an iframe-initiated `tools/call`, allowlisted and recorded
|
|
574
|
+
*
|
|
575
|
+
* None of these throw: every failure is a `status` (or an `isError`
|
|
576
|
+
* result). Failures are cached per (session, alias) for 60s so a
|
|
577
|
+
* transcript full of calls to a dead server costs one attempt a minute.
|
|
578
|
+
*/
|
|
579
|
+
|
|
580
|
+
/** Durable record of an iframe-initiated call, appended to the session's
|
|
581
|
+
* events.jsonl as `kind: "mcp_app_tool_call"`. Never carries args or
|
|
582
|
+
* results. */
|
|
583
|
+
interface McpAppToolCallRecord {
|
|
584
|
+
server: string;
|
|
585
|
+
tool: string;
|
|
586
|
+
originToolCallId: string;
|
|
587
|
+
isError: boolean;
|
|
588
|
+
durationMs: number;
|
|
589
|
+
}
|
|
590
|
+
|
|
505
591
|
/**
|
|
506
592
|
* ONE normalized shape for a finished tool/command call — the unification
|
|
507
593
|
* target for the two disjoint logs this repo has today: `recordCommand`'s
|
|
@@ -1685,6 +1771,41 @@ declare function createTaskLedger(opts: {
|
|
|
1685
1771
|
persist?: boolean;
|
|
1686
1772
|
}): TaskLedger;
|
|
1687
1773
|
|
|
1774
|
+
/**
|
|
1775
|
+
* The single place every stable `SessionDescriptor.endedReason` /
|
|
1776
|
+
* `SessionOutcome.termination.reason` value is declared, so a UI can render
|
|
1777
|
+
* a label for each one without chasing the call site that set it.
|
|
1778
|
+
*
|
|
1779
|
+
* Two families:
|
|
1780
|
+
* - operator-issued — set ONLY by an explicit `POST /sessions/:id/kill`
|
|
1781
|
+
* or the `agent_kill` MCP tool, distinguishing an operator's deliberate
|
|
1782
|
+
* stop from every automatic teardown below;
|
|
1783
|
+
* - internal/automatic — set by the daemon itself, without an operator
|
|
1784
|
+
* in the loop, for a reason the operator couldn't have expressed.
|
|
1785
|
+
*
|
|
1786
|
+
* Backward compatible by construction: this is a value list, not a closed
|
|
1787
|
+
* validator. A descriptor persisted by an older daemon (or a newer one an
|
|
1788
|
+
* older UI doesn't know about yet) carries a `reason` string outside this
|
|
1789
|
+
* list — {@link isKnownSessionEndReason} says so, and callers should fall
|
|
1790
|
+
* back to a generic label rather than throw.
|
|
1791
|
+
*/
|
|
1792
|
+
declare const SESSION_END_REASONS: readonly ["daemon-restart", "idle-reaped", "crashed", "operator-completed", "operator-stopped", "cost-cap-exceeded", "policy-cleanup", "parent-exited", "provider-limit", "forgotten"];
|
|
1793
|
+
type SessionEndReason = (typeof SESSION_END_REASONS)[number];
|
|
1794
|
+
/** True when `reason` is one of {@link SESSION_END_REASONS}. A label
|
|
1795
|
+
* lookup, not a wire-format gate — an unrecognized string is still a
|
|
1796
|
+
* valid `endedReason` from a daemon version this build doesn't know
|
|
1797
|
+
* about; callers should render it with a generic fallback, never reject
|
|
1798
|
+
* it. */
|
|
1799
|
+
declare function isKnownSessionEndReason(reason: string | undefined): reason is SessionEndReason;
|
|
1800
|
+
/**
|
|
1801
|
+
* Classify a driver-reported error message as a provider/subscription
|
|
1802
|
+
* usage cap rather than an ordinary tool/turn failure. Used wherever the
|
|
1803
|
+
* daemon would otherwise tag a dead session with the generic `"crashed"` /
|
|
1804
|
+
* `"error"` reason, so an operator sees "wait for your quota to reset"
|
|
1805
|
+
* instead of chasing a phantom bug.
|
|
1806
|
+
*/
|
|
1807
|
+
declare function isProviderLimitError(message: string | undefined): boolean;
|
|
1808
|
+
|
|
1688
1809
|
/**
|
|
1689
1810
|
* In-process pub/sub bus for session lifecycle events. Separate from
|
|
1690
1811
|
* RuntimeEvents (global daemon bus) — session events are scoped to
|
|
@@ -1695,7 +1816,7 @@ declare function createTaskLedger(opts: {
|
|
|
1695
1816
|
* and session_monitor MCP tool (long-poll multiplexed).
|
|
1696
1817
|
*/
|
|
1697
1818
|
|
|
1698
|
-
type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:awaiting-input-flagged" | "session:awaiting-question-answered" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:reaped" | "session:stalled" | "session:stall-cleared" | "session:watcher-attached" | "session:watcher-detached" | "session:bg-tasks-parked" | "session:bg-tasks-cleared" | "session:resumed" | "session:spawned" | "session:command-done" | "session:model-changed" | "session:config-changed" | "session:renamed" | "session:pinned-changed" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed" | "activity:changed" | "task:changed" | "workflow:gate-report" | "workflow:suspended" | "workflow:suspend-resumed" | "session:harness-warning";
|
|
1819
|
+
type SessionEventType = "session:turn-end" | "session:awaiting-input" | "session:awaiting-input-flagged" | "session:awaiting-question-answered" | "session:permission-request" | "session:permission-resolved" | "session:exited" | "session:reaped" | "session:stalled" | "session:stall-cleared" | "session:watcher-attached" | "session:watcher-detached" | "session:bg-tasks-parked" | "session:bg-tasks-cleared" | "session:bg-task" | "session:resumed" | "session:spawned" | "session:command-done" | "session:model-changed" | "session:config-changed" | "session:renamed" | "session:pinned-changed" | "session:message" | "policy:passed" | "policy:failed" | "policy:commit-ready" | "policy:committed" | "cron:fired" | "cron:succeeded" | "cron:failed" | "activity:changed" | "task:changed" | "workflow:gate-report" | "workflow:suspended" | "workflow:suspend-resumed" | "session:harness-warning";
|
|
1699
1820
|
/**
|
|
1700
1821
|
* Fixed severity vocabulary for a judge-gate finding (WP-D). Deliberately
|
|
1701
1822
|
* small and fixed so `policy_status` output is comparable across different
|
|
@@ -1769,6 +1890,25 @@ interface SessionTurnEndEvent {
|
|
|
1769
1890
|
* a normal, productive turn.
|
|
1770
1891
|
*/
|
|
1771
1892
|
empty?: boolean;
|
|
1893
|
+
/**
|
|
1894
|
+
* The in-band `error` stream event's message text observed during this
|
|
1895
|
+
* turn, when one occurred (see `SessionDescriptor.lastTurnErrorMessage`'s
|
|
1896
|
+
* doc). Present whenever `reason` is `"error"` AND the adapter emitted an
|
|
1897
|
+
* `error`-kind event before ending the stream — including the case where
|
|
1898
|
+
* the generator simply returned with no explicit turn-end and no thrown
|
|
1899
|
+
* exception, which is exactly what left an errored turn indistinguishable
|
|
1900
|
+
* from a clean one before this field existed. Absent when the adapter
|
|
1901
|
+
* reported `reason:"error"` on its own turn-end without a preceding
|
|
1902
|
+
* `error` event, and absent (not empty string) on every non-error turn.
|
|
1903
|
+
*/
|
|
1904
|
+
error?: string;
|
|
1905
|
+
/**
|
|
1906
|
+
* True when the turn was not started by a prompt: the agent woke on its
|
|
1907
|
+
* own (Claude Code's task-notification cycle after a background task
|
|
1908
|
+
* settled) and the registry tracked that work as a turn. Absent on an
|
|
1909
|
+
* ordinary prompted turn.
|
|
1910
|
+
*/
|
|
1911
|
+
autonomous?: boolean;
|
|
1772
1912
|
}
|
|
1773
1913
|
interface SessionAwaitingInputEvent {
|
|
1774
1914
|
type: "session:awaiting-input";
|
|
@@ -1865,15 +2005,14 @@ interface SessionExitedEvent {
|
|
|
1865
2005
|
status: "exited" | "killed" | "error";
|
|
1866
2006
|
label?: string;
|
|
1867
2007
|
ts: string;
|
|
1868
|
-
/** Mirrors `SessionDescriptor.endedReason` —
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1871
|
-
* `"
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
|
|
1876
|
-
reason?: "daemon-restart" | "idle-reaped" | "crashed";
|
|
2008
|
+
/** Mirrors `SessionDescriptor.endedReason` — see {@link SessionEndReason}
|
|
2009
|
+
* for the full, single-source enum (automatic teardowns like
|
|
2010
|
+
* `"daemon-restart"` / `"idle-reaped"` / `"crashed"` / `"cost-cap-exceeded"`
|
|
2011
|
+
* / `"provider-limit"`, and operator-issued `"operator-completed"` /
|
|
2012
|
+
* `"operator-stopped"`). Lets a watcher (completion-policy supervisor,
|
|
2013
|
+
* `session_monitor`) tell WHY this exit happened rather than just that it
|
|
2014
|
+
* did. Absent for a plain natural exit or an ordinary turn error. */
|
|
2015
|
+
reason?: SessionEndReason;
|
|
1877
2016
|
}
|
|
1878
2017
|
/**
|
|
1879
2018
|
* Emitted when the idle-session reaper (PR-6) retires a long-idle agent-cli
|
|
@@ -2024,6 +2163,29 @@ interface SessionBgTasksClearedEvent {
|
|
|
2024
2163
|
label?: string;
|
|
2025
2164
|
ts: string;
|
|
2026
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* One lifecycle edge of an agent's background task (a backgrounded Bash
|
|
2168
|
+
* command, a monitor, ...) as the agent itself reported it — over ACP, the
|
|
2169
|
+
* AIR `asyncTasks` extension (see @agentproto/acp's `background-task`
|
|
2170
|
+
* StreamEvent). Unlike {@link SessionBgTasksParkedEvent} (a turn-end
|
|
2171
|
+
* heuristic) this is the real lifecycle: `"started"` when the task is
|
|
2172
|
+
* announced, `"settled"` when it reaches a terminal `status`. Mid-life
|
|
2173
|
+
* progress updates only refresh `SessionDescriptor.backgroundTasks` and are
|
|
2174
|
+
* not emitted. Same bus distribution as every other lifecycle event.
|
|
2175
|
+
*/
|
|
2176
|
+
interface SessionBgTaskEvent {
|
|
2177
|
+
type: "session:bg-task";
|
|
2178
|
+
sessionId: string;
|
|
2179
|
+
phase: "started" | "settled";
|
|
2180
|
+
taskId: string;
|
|
2181
|
+
taskKind?: string;
|
|
2182
|
+
description?: string;
|
|
2183
|
+
outputFile?: string;
|
|
2184
|
+
status?: string;
|
|
2185
|
+
summary?: string;
|
|
2186
|
+
label?: string;
|
|
2187
|
+
ts: string;
|
|
2188
|
+
}
|
|
2027
2189
|
/**
|
|
2028
2190
|
* Emitted when a dead agent-cli row is brought back IN PLACE — same session
|
|
2029
2191
|
* id, same descriptor — by the lazy resume-on-prompt path (`maybeResumeAgent`)
|
|
@@ -2163,6 +2325,27 @@ interface SessionRenamedEvent {
|
|
|
2163
2325
|
renamedByUser?: boolean;
|
|
2164
2326
|
ts: string;
|
|
2165
2327
|
}
|
|
2328
|
+
/**
|
|
2329
|
+
* A typed inter-session message (`session-message.ts`) was accepted for
|
|
2330
|
+
* `sessionId` (the RECIPIENT) — once at send (no `delivered`) and again when
|
|
2331
|
+
* it's delivered into the recipient's context (`delivered` set). Lets
|
|
2332
|
+
* `session_monitor` / SSE / the VS Code panel react without polling.
|
|
2333
|
+
*/
|
|
2334
|
+
interface SessionMessageEvent {
|
|
2335
|
+
type: "session:message";
|
|
2336
|
+
sessionId: string;
|
|
2337
|
+
messageId: string;
|
|
2338
|
+
fromSessionId?: string;
|
|
2339
|
+
relation: "child" | "parent" | "sibling" | "human" | "system";
|
|
2340
|
+
kind: "report" | "question" | "blocker" | "done" | "notice";
|
|
2341
|
+
urgency: "fyi" | "next-turn" | "steer" | "interrupt";
|
|
2342
|
+
delivered?: {
|
|
2343
|
+
via: "wait" | "steer" | "turn" | "interrupt" | "inbox";
|
|
2344
|
+
at: string;
|
|
2345
|
+
turnSeq?: number;
|
|
2346
|
+
};
|
|
2347
|
+
ts: string;
|
|
2348
|
+
}
|
|
2166
2349
|
/**
|
|
2167
2350
|
* Emitted when an operator pins or unpins a session for list visibility
|
|
2168
2351
|
* (`POST /sessions/:id/pin`, the `session_set_pinned` MCP verb). Like
|
|
@@ -2356,16 +2539,22 @@ interface WorkflowApprovalResolvedEvent {
|
|
|
2356
2539
|
}
|
|
2357
2540
|
/**
|
|
2358
2541
|
* Emitted by the workflow runner (`workflow-runner.ts`) when a `kind:
|
|
2359
|
-
* "suspend"` step parks its run awaiting an external event
|
|
2360
|
-
*
|
|
2361
|
-
* (
|
|
2362
|
-
*
|
|
2542
|
+
* "suspend"` step parks its run awaiting an external event (`on` set, no
|
|
2543
|
+
* `reason`) — OR when an agent-backed step signals AIP-58 §3(a)
|
|
2544
|
+
* `run.requestInput` (`reason: "input-required"` + `prompt`/`schema?`, no
|
|
2545
|
+
* `on`). Either way the run's status flips to `awaiting-input` with a
|
|
2546
|
+
* durable `awaitingSuspend` record, and `workflow_escalation_resolve`'s
|
|
2547
|
+
* suspend form resumes it. Same bus distribution as every other lifecycle
|
|
2548
|
+
* event.
|
|
2363
2549
|
*/
|
|
2364
2550
|
interface WorkflowSuspendedEvent {
|
|
2365
2551
|
type: "workflow:suspended";
|
|
2366
2552
|
runId: string;
|
|
2367
2553
|
stepId: string;
|
|
2368
|
-
on
|
|
2554
|
+
on?: readonly string[];
|
|
2555
|
+
reason?: "input-required";
|
|
2556
|
+
prompt?: string;
|
|
2557
|
+
schema?: Record<string, unknown>;
|
|
2369
2558
|
ts: string;
|
|
2370
2559
|
}
|
|
2371
2560
|
/**
|
|
@@ -2393,7 +2582,7 @@ interface SessionHarnessWarningEvent {
|
|
|
2393
2582
|
label?: string;
|
|
2394
2583
|
ts: string;
|
|
2395
2584
|
}
|
|
2396
|
-
type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionAwaitingInputFlaggedEvent | SessionAwaitingQuestionAnsweredEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionReapedEvent | SessionStalledEvent | SessionStallClearedEvent | SessionWatcherAttachedEvent | SessionWatcherDetachedEvent | SessionBgTasksParkedEvent | SessionBgTasksClearedEvent | SessionResumedEvent | SessionSpawnedEvent | SessionCommandDoneEvent | SessionModelChangedEvent | SessionConfigChangedEvent | SessionRenamedEvent | SessionPinnedEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent | ActivityChangedEvent | TaskChangedEvent | WorkflowGateReportEvent | WorkflowApprovalRequestedEvent | WorkflowApprovalResolvedEvent | WorkflowSuspendedEvent | WorkflowSuspendResumedEvent | SessionHarnessWarningEvent;
|
|
2585
|
+
type SessionEvent = SessionTurnEndEvent | SessionAwaitingInputEvent | SessionAwaitingInputFlaggedEvent | SessionAwaitingQuestionAnsweredEvent | SessionPermissionRequestEvent | SessionPermissionResolvedEvent | SessionExitedEvent | SessionReapedEvent | SessionStalledEvent | SessionStallClearedEvent | SessionWatcherAttachedEvent | SessionWatcherDetachedEvent | SessionBgTasksParkedEvent | SessionBgTasksClearedEvent | SessionBgTaskEvent | SessionResumedEvent | SessionSpawnedEvent | SessionCommandDoneEvent | SessionModelChangedEvent | SessionConfigChangedEvent | SessionRenamedEvent | SessionPinnedEvent | SessionMessageEvent | PolicyPassedEvent | PolicyFailedEvent | PolicyCommitReadyEvent | PolicyCommittedEvent | CronFiredEvent | CronSucceededEvent | CronFailedEvent | ActivityChangedEvent | TaskChangedEvent | WorkflowGateReportEvent | WorkflowApprovalRequestedEvent | WorkflowApprovalResolvedEvent | WorkflowSuspendedEvent | WorkflowSuspendResumedEvent | SessionHarnessWarningEvent;
|
|
2397
2586
|
interface SessionEventBus {
|
|
2398
2587
|
emit(ev: SessionEvent): void;
|
|
2399
2588
|
/** Subscribe to a specific event type. Returns an unsubscribe fn. */
|
|
@@ -2639,50 +2828,276 @@ declare class TunnelRegistry {
|
|
|
2639
2828
|
private loadFromDisk;
|
|
2640
2829
|
}
|
|
2641
2830
|
|
|
2831
|
+
/**
|
|
2832
|
+
* LlmEndpointRegistry — supervised-sidecar manager for the
|
|
2833
|
+
* `@agentproto/llm-endpoint` proxy gateway.
|
|
2834
|
+
*
|
|
2835
|
+
* The daemon spawns the llm-endpoint bin as a CHILD PROCESS (never an
|
|
2836
|
+
* in-process import — the runtime package has zero dependency on
|
|
2837
|
+
* @agentproto/llm-endpoint, exactly like it never imports cloudflared).
|
|
2838
|
+
* This is the single-sidecar analog of {@link TunnelRegistry}: one endpoint,
|
|
2839
|
+
* one port, an in-memory descriptor that tracks pid / port / status /
|
|
2840
|
+
* startedAt, and a start / stop / status lifecycle exposed over MCP.
|
|
2841
|
+
*
|
|
2842
|
+
* Lifecycle (mirrors RemoteController's singleton style — created fresh each
|
|
2843
|
+
* boot, state in memory; the child dies with the daemon so there is nothing
|
|
2844
|
+
* to reconcile on restart):
|
|
2845
|
+
* start(input) → assemble env, spawn `node <bin> serve`, health-probe to
|
|
2846
|
+
* readiness, store descriptor. Idempotent: a call while the
|
|
2847
|
+
* endpoint is already running+healthy returns the existing
|
|
2848
|
+
* descriptor without spawning (like `start_browser`).
|
|
2849
|
+
* status() → descriptor + live `GET /v1/models` health probe.
|
|
2850
|
+
* stop() → SIGTERM the child, mark stopped (mirrors tunnel stop).
|
|
2851
|
+
* shutdown() → stop the child on daemon exit.
|
|
2852
|
+
*
|
|
2853
|
+
* Env on spawn (see {@link assembleLlmEndpointEnv}): the provider API keys
|
|
2854
|
+
* via `injectProviderKeysIntoEnv` (the same store llm-endpoint's own cli.ts
|
|
2855
|
+
* reads), `LLM_ENDPOINT_PORT` (default 18090), and `LLM_ENDPOINT_ACCESS_TOKENS`
|
|
2856
|
+
* when configured. Explicit env passed to `start` always wins.
|
|
2857
|
+
*/
|
|
2858
|
+
type LlmEndpointStatus = "starting" | "running" | "stopped" | "error";
|
|
2859
|
+
/** The `llm_endpoint_status` MCP contract — see {@link LlmEndpointRegistry.status}. */
|
|
2860
|
+
interface LlmEndpointStatusReport {
|
|
2861
|
+
running: boolean;
|
|
2862
|
+
pid: number | null;
|
|
2863
|
+
port: number | null;
|
|
2864
|
+
baseUrl: string | null;
|
|
2865
|
+
healthy: boolean;
|
|
2866
|
+
startedAt: string | null;
|
|
2867
|
+
status: LlmEndpointStatus | "never-started";
|
|
2868
|
+
/** Who is actually serving this endpoint. `"daemon"` — this registry's own
|
|
2869
|
+
* spawn (or nothing at all). `"external"` — a healthy process on the same
|
|
2870
|
+
* port that this registry did NOT spawn (e.g. a launchd KeepAlive
|
|
2871
|
+
* sidecar); `pid` is unknown (always `null`) and `injectedProviders`, if
|
|
2872
|
+
* present, was DERIVED by probing `/v1/models` rather than read off our
|
|
2873
|
+
* own key-injection bookkeeping. */
|
|
2874
|
+
owner: "daemon" | "external";
|
|
2875
|
+
/** Whether upstream links persisted via `llm_endpoint_set_upstream_link`
|
|
2876
|
+
* are actually applied to the process answering this port. Always
|
|
2877
|
+
* `false` for `owner:"external"` — this registry never spawned it, so it
|
|
2878
|
+
* never injected `LLM_ENDPOINT_PROFILE_<UPSTREAM>`; the operator must
|
|
2879
|
+
* configure the external service's own env directly. */
|
|
2880
|
+
linksApplied: boolean;
|
|
2881
|
+
lastError?: string;
|
|
2882
|
+
injectedProviders?: string[];
|
|
2883
|
+
linkedProviders?: string[];
|
|
2884
|
+
/** `@agentproto/llm-endpoint`'s own package version, read from the
|
|
2885
|
+
* resolved bin's `package.json` — best-effort, present only for
|
|
2886
|
+
* `owner:"daemon"` (the version of the code this registry would spawn /
|
|
2887
|
+
* did spawn). Undefined when unresolvable, or for `owner:"external"`
|
|
2888
|
+
* (an adopted process exposes no version of its own to probe). */
|
|
2889
|
+
version?: string;
|
|
2890
|
+
}
|
|
2891
|
+
interface LlmEndpointDescriptor {
|
|
2892
|
+
pid: number | null;
|
|
2893
|
+
port: number;
|
|
2894
|
+
baseUrl: string;
|
|
2895
|
+
status: LlmEndpointStatus;
|
|
2896
|
+
startedAt: string;
|
|
2897
|
+
stoppedAt?: string;
|
|
2898
|
+
lastError?: string;
|
|
2899
|
+
/** Provider names whose keys were injected into the child's env (never values). */
|
|
2900
|
+
injectedProviders?: string[];
|
|
2901
|
+
/** Upstream names mapped to an auth-profile link (`LLM_ENDPOINT_PROFILE_<P>`)
|
|
2902
|
+
* in the child's env. Profile IDS, not secrets — but only the provider names
|
|
2903
|
+
* are surfaced here, mirroring `injectedProviders`. */
|
|
2904
|
+
linkedProviders?: string[];
|
|
2905
|
+
/**
|
|
2906
|
+
* Only populated on the value returned by {@link LlmEndpointRegistry.start}
|
|
2907
|
+
* (never stored on the in-memory descriptor): `true` when the call reused an
|
|
2908
|
+
* already-running/starting child instead of spawning — so the caller can
|
|
2909
|
+
* tell a fresh `start` (new port/tokens applied) from a no-op idempotent hit
|
|
2910
|
+
* (old descriptor returned, nothing new applied). Mirrors `start_browser`.
|
|
2911
|
+
*/
|
|
2912
|
+
wasAlreadyRunning?: boolean;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/**
|
|
2916
|
+
* RemoteController — owns the gateway's "publish to the internet" state.
|
|
2917
|
+
*
|
|
2918
|
+
* One singleton per gateway. Holds:
|
|
2919
|
+
* - the current tunnel provider's child process (cloudflared, …)
|
|
2920
|
+
* - the bearer token gating /mcp once the tunnel is up
|
|
2921
|
+
* - persisted state in `<workspace>/.agentproto/remote.json`
|
|
2922
|
+
*
|
|
2923
|
+
* The controller is the *only* component that mutates auth at runtime.
|
|
2924
|
+
* The HTTP server reads auth via a getter (see `AuthSource` in
|
|
2925
|
+
* http-server.ts), so flipping `enable() / disable()` here takes effect
|
|
2926
|
+
* on the next request — no restart, no race.
|
|
2927
|
+
*
|
|
2928
|
+
* ## Provider model
|
|
2929
|
+
*
|
|
2930
|
+
* Each provider implements `RemoteProvider` (start / stop / status).
|
|
2931
|
+
* v1 ships only `quick` (Cloudflare Quick Tunnel — no API key, no DNS,
|
|
2932
|
+
* ephemeral `*.trycloudflare.com` URL). Named CF Tunnel and Tailscale
|
|
2933
|
+
* land as additional implementations.
|
|
2934
|
+
*
|
|
2935
|
+
* ## Token model
|
|
2936
|
+
*
|
|
2937
|
+
* On enable we generate a 32-byte random bearer token, return it once
|
|
2938
|
+
* to the caller, and persist only its sha256 hash to disk. Same model
|
|
2939
|
+
* as a Stripe API key — losing the plaintext means rotating, not
|
|
2940
|
+
* recovering. This keeps `.agentproto/remote.json` safe to commit by
|
|
2941
|
+
* accident (it leaks the URL, not the credential).
|
|
2942
|
+
*
|
|
2943
|
+
* ## Security note
|
|
2944
|
+
*
|
|
2945
|
+
* The CF API token (when used by named-tunnel provider) is *never*
|
|
2946
|
+
* accepted as MCP tool input. The controller reads it from
|
|
2947
|
+
* `~/.config/agentproto/cloudflare.json` or `$CLOUDFLARE_API_TOKEN`.
|
|
2948
|
+
* Otherwise a remote agent could exfiltrate it through `remote_enable`.
|
|
2949
|
+
*/
|
|
2950
|
+
|
|
2951
|
+
interface EnableInput {
|
|
2952
|
+
provider?: "quick";
|
|
2953
|
+
/**
|
|
2954
|
+
* Local port to expose. Defaults to the gateway's own port — i.e.
|
|
2955
|
+
* "publish this MCP server." Pass any other port to expose a
|
|
2956
|
+
* different local service (Supabase 54371, a Vite dev server, …)
|
|
2957
|
+
* without spinning up a separate tunnel manager.
|
|
2958
|
+
*/
|
|
2959
|
+
targetPort?: number;
|
|
2960
|
+
/**
|
|
2961
|
+
* Host the tunnel forwards to. Defaults to `127.0.0.1`. Setting it
|
|
2962
|
+
* to `localhost` resolves through DNS (IPv6 first on most systems);
|
|
2963
|
+
* keep `127.0.0.1` unless the target is explicitly IPv6-bound.
|
|
2964
|
+
*/
|
|
2965
|
+
targetHost?: string;
|
|
2966
|
+
}
|
|
2967
|
+
interface EnableResult {
|
|
2968
|
+
publicUrl: string;
|
|
2969
|
+
/**
|
|
2970
|
+
* Bearer token issued by this gateway. Only meaningful when the
|
|
2971
|
+
* tunnel exposes the gateway itself (default). For arbitrary-target
|
|
2972
|
+
* tunnels (`targetPort != gateway.port`) the daemon does not gate
|
|
2973
|
+
* the proxied service, and this is omitted.
|
|
2974
|
+
*/
|
|
2975
|
+
bearerToken?: string;
|
|
2976
|
+
/**
|
|
2977
|
+
* `<publicUrl>/mcp`. Only emitted when exposing the gateway.
|
|
2978
|
+
*/
|
|
2979
|
+
mcpEndpoint?: string;
|
|
2980
|
+
/**
|
|
2981
|
+
* Paste-ready `.mcp.json` snippet. Only emitted when exposing the
|
|
2982
|
+
* gateway.
|
|
2983
|
+
*/
|
|
2984
|
+
mcpConfigSnippet?: string;
|
|
2985
|
+
/**
|
|
2986
|
+
* A single link to open on a phone (PHONE-PLAN.md P1.2) — the token rides
|
|
2987
|
+
* in a URL *fragment* (`#token=`), never a `?` query string, since
|
|
2988
|
+
* fragments never reach a server (no Referer, no access/proxy log, no CDN
|
|
2989
|
+
* cache key). Two shapes, chosen by whether this daemon has the
|
|
2990
|
+
* `@agentik/session-chat` app installed with a UI:
|
|
2991
|
+
* - installed: `<publicUrl>/apps/@agentik/session-chat/ui#token=<t>` —
|
|
2992
|
+
* the phone lands directly in Control Center.
|
|
2993
|
+
* - not installed: `https://cli.agentproto.sh/panel#daemon=<publicUrl>&token=<t>`
|
|
2994
|
+
* — the hosted panel, which speaks the same fragment shape (see
|
|
2995
|
+
* agentproto/cli-site's `readFragmentConnection`).
|
|
2996
|
+
* Only emitted when exposing the gateway (same condition as `bearerToken`)
|
|
2997
|
+
* — a passthrough tunnel has no daemon UI to link to.
|
|
2998
|
+
*/
|
|
2999
|
+
phoneUrl?: string;
|
|
3000
|
+
provider: "quick";
|
|
3001
|
+
target: {
|
|
3002
|
+
host: string;
|
|
3003
|
+
port: number;
|
|
3004
|
+
};
|
|
3005
|
+
exposesGateway: boolean;
|
|
3006
|
+
/**
|
|
3007
|
+
* Present ONLY for passthrough tunnels (`exposesGateway === false`):
|
|
3008
|
+
* the daemon does not gate that traffic, so the public URL reaches the
|
|
3009
|
+
* upstream service with no auth from the daemon. A machine-readable flag
|
|
3010
|
+
* so UIs/agents can surface the exposure instead of it being buried in
|
|
3011
|
+
* the tool's prose description.
|
|
3012
|
+
*/
|
|
3013
|
+
warning?: string;
|
|
3014
|
+
}
|
|
3015
|
+
interface RemoteStatus {
|
|
3016
|
+
enabled: boolean;
|
|
3017
|
+
provider?: "quick";
|
|
3018
|
+
publicUrl?: string;
|
|
3019
|
+
pid?: number | null;
|
|
3020
|
+
createdAt?: string;
|
|
3021
|
+
target?: {
|
|
3022
|
+
host: string;
|
|
3023
|
+
port: number;
|
|
3024
|
+
};
|
|
3025
|
+
exposesGateway?: boolean;
|
|
3026
|
+
/** Last error from the provider, if any (since last enable). */
|
|
3027
|
+
lastError?: string;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
2642
3030
|
/**
|
|
2643
3031
|
* Daemon-side pairing registry (design: DESIGN §3/§4, PLAN deliverable 2).
|
|
2644
3032
|
*
|
|
2645
3033
|
* Owns everything the daemon needs to be *paired with* over an untrusted
|
|
2646
3034
|
* rendezvous:
|
|
2647
3035
|
*
|
|
2648
|
-
* - **Offer store** — in-memory, single-use, expiry-checked one-time
|
|
2649
|
-
* minted by `pair offer`. A daemon restart voids outstanding offers
|
|
2650
|
-
* (acceptable + documented). The
|
|
2651
|
-
*
|
|
3036
|
+
* - **Offer store** — in-memory, single-use, expiry-checked one-time offer
|
|
3037
|
+
* secrets minted by `pair offer`. A daemon restart voids outstanding offers
|
|
3038
|
+
* (acceptable + documented). The daemon parks on the offer's ROUTE token and
|
|
3039
|
+
* the handshake's `verifyAuthToken` predicate checks the sealed AUTH token,
|
|
3040
|
+
* SPENDING the offer on the first successful handshake (a wrong token spends
|
|
3041
|
+
* nothing).
|
|
2652
3042
|
* - **Pairings store** — `~/.agentproto/pairings.json` (0600, atomic write),
|
|
2653
3043
|
* one record per paired client: `{clientPub, name, fingerprint, createdAt,
|
|
2654
3044
|
* lastSeen, pairRoot, rendezvousUrl}`. `pairRoot` is the long-term shared
|
|
2655
3045
|
* secret (`derivePairRoot`) from which reconnect routing tokens derive.
|
|
2656
3046
|
* - **Rendezvous connections** — for a fresh offer, and for every persisted
|
|
2657
3047
|
* pairing on boot (autoconnect), the daemon dials the rendezvous *outbound*
|
|
2658
|
-
* (`side=daemon&t=<
|
|
3048
|
+
* (`side=daemon&t=<route>`), runs `daemonHandshakeOverSink` with the P1
|
|
2659
3049
|
* crypto, and — on success — serves the spliced, E2E-wrapped channel exactly
|
|
2660
3050
|
* like `serve --connect` serves a tunnel host. The serving path itself is
|
|
2661
3051
|
* injected (`serve`) so this module stays free of pty/adapter concerns and
|
|
2662
3052
|
* the CLI can reuse its `createTunnelServer` config verbatim.
|
|
2663
|
-
* - **Reconnect epochs** — a persisted pairing's standing connection
|
|
2664
|
-
* pairing-derived
|
|
2665
|
-
*
|
|
3053
|
+
* - **Reconnect epochs** — a persisted pairing's standing connection parks on
|
|
3054
|
+
* the pairing-derived route `HKDF(pairRoot, "rv-route"‖epoch)`, rotated per
|
|
3055
|
+
* UTC day. The daemon parks on BOTH the current and previous
|
|
2666
3056
|
* epoch tokens so a client whose clock straddles midnight still finds it;
|
|
2667
3057
|
* reconnect-with-backoff keeps the standing connection alive.
|
|
2668
|
-
* - **Revocation** — removing a pairing drops its rendezvous connections
|
|
2669
|
-
*
|
|
3058
|
+
* - **Revocation** — removing a pairing drops its rendezvous connections, so
|
|
3059
|
+
* it can no longer be served. For a grace period (`revokedGraceMs`, default
|
|
3060
|
+
* 14 days) the daemon keeps a *tombstone*: it still parks on the pairing's
|
|
3061
|
+
* epoch ROUTES, completes the (daemon-signed) handshake only for a hello
|
|
3062
|
+
* carrying that epoch's AUTH token, and answers with a single E2E-encrypted
|
|
3063
|
+
* `error{code:"pairing_revoked"}` frame before closing — never serving the
|
|
3064
|
+
* channel. That is what lets a client tell "unpaired" from "daemon
|
|
3065
|
+
* offline" (which both otherwise look like a park timeout at the broker)
|
|
3066
|
+
* and stop retrying. The signal is authenticated like every tunnel frame:
|
|
3067
|
+
* it arrives inside the AEAD channel of a handshake whose transcript the
|
|
3068
|
+
* client verified against the pinned daemon key, so the broker can't forge
|
|
3069
|
+
* it; and the broker, which knows the routes, can't elicit it either (no
|
|
3070
|
+
* auth token). A tombstone keeps only the per-epoch route + auth tokens for
|
|
3071
|
+
* its window — the pair root is dropped at revoke.
|
|
2670
3072
|
*
|
|
2671
3073
|
* ## Reconnect authentication (why no stable client key)
|
|
2672
3074
|
*
|
|
2673
|
-
*
|
|
2674
|
-
* handshake) — there is no persistent client identity key
|
|
2675
|
-
*
|
|
2676
|
-
*
|
|
2677
|
-
*
|
|
2678
|
-
*
|
|
2679
|
-
* `
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
3075
|
+
* The handshake carries only a client *ephemeral* key (regenerated per
|
|
3076
|
+
* handshake) — there is no persistent client identity key. So a reconnect
|
|
3077
|
+
* authenticates the client by **possession of the epoch AUTH token**
|
|
3078
|
+
* `HKDF(pairRoot, "rv-auth"‖epoch)`, which derives from `pairRoot` — a secret
|
|
3079
|
+
* only the paired client and daemon share (it fell out of the original ECDH).
|
|
3080
|
+
* The daemon parks on the pairing's epoch ROUTE, and on that connection the
|
|
3081
|
+
* `verifyAuthToken` predicate accepts a hello whose sealed auth equals the same
|
|
3082
|
+
* epoch's auth token (constant-time). The route and auth are separate one-way
|
|
3083
|
+
* HKDF outputs: the broker sees every route and can't compute an auth from it.
|
|
3084
|
+
* (pair/v1 used ONE value for both, so a broker could replay a logged route as
|
|
3085
|
+
* the proof and be served.) The stored `fingerprint` (of the first handshake's
|
|
3086
|
+
* ephemeral key) is a stable display/revocation handle, not an auth input.
|
|
3087
|
+
*
|
|
3088
|
+
* ## Legacy (pair/v1) pairings
|
|
3089
|
+
*
|
|
3090
|
+
* A v1 `pairings.json` is loaded with every record flagged `legacy`: listed and
|
|
3091
|
+
* revocable, never served. The daemon still parks on a legacy pairing's routes
|
|
3092
|
+
* (the epoch route is unchanged from v1) so that pairing's not-yet-upgraded
|
|
3093
|
+
* client reaches it and is told to re-pair — see `respondToLegacyHandshake` —
|
|
3094
|
+
* rather than timing out. v1 records are not upgraded in place: v1 let the
|
|
3095
|
+
* broker pair as a client, so any v1 record may be the broker's own.
|
|
2683
3096
|
*/
|
|
2684
3097
|
|
|
2685
|
-
|
|
3098
|
+
/** `pairings.json` format. v2 = pairings made under pair/v2 (route/auth
|
|
3099
|
+
* split). A v1 file still loads, every record flagged `legacy`. */
|
|
3100
|
+
declare const PAIRINGS_VERSION: 2;
|
|
2686
3101
|
/** One persisted pairing. Written verbatim to `pairings.json`. */
|
|
2687
3102
|
interface PairingRecord {
|
|
2688
3103
|
/** The client ephemeral public key from the FIRST handshake (a snapshot —
|
|
@@ -2701,6 +3116,17 @@ interface PairingRecord {
|
|
|
2701
3116
|
pairRoot: string;
|
|
2702
3117
|
/** Rendezvous endpoint to reconnect through. */
|
|
2703
3118
|
rendezvousUrl: string;
|
|
3119
|
+
/** Set on a pairing made under the retired pair/v1 protocol: listed and
|
|
3120
|
+
* revocable, never served — its client is told to re-pair. */
|
|
3121
|
+
legacy?: true;
|
|
3122
|
+
/** Set when this pairing was made under an offer minted with
|
|
3123
|
+
* `agentproto pair offer --host` (PR-C) — i.e. the offering daemon's own
|
|
3124
|
+
* intent, recorded server-side at handshake time from the OfferEntry it
|
|
3125
|
+
* itself created, never from the (unauthenticated) `scope` param a client
|
|
3126
|
+
* presented in the offer URL. Absent for an ordinary remote-control
|
|
3127
|
+
* pairing. Purely informational here — it does not change how this
|
|
3128
|
+
* pairing is served. */
|
|
3129
|
+
scope?: "host";
|
|
2704
3130
|
}
|
|
2705
3131
|
/** Mode of a served channel — first-contact offer vs. an established reconnect. */
|
|
2706
3132
|
type PairingChannelMode = "offer" | "reconnect";
|
|
@@ -2718,8 +3144,9 @@ interface PairingChannelHandle {
|
|
|
2718
3144
|
close(): Promise<void>;
|
|
2719
3145
|
}
|
|
2720
3146
|
interface CreatedOffer {
|
|
2721
|
-
/** The one-time offer
|
|
2722
|
-
|
|
3147
|
+
/** The one-time offer secret (base64url) — the URL's `s`. Never on the wire:
|
|
3148
|
+
* the broker only ever sees its derived route. */
|
|
3149
|
+
secret: string;
|
|
2723
3150
|
/** Offer expiry, unix seconds. */
|
|
2724
3151
|
exp: number;
|
|
2725
3152
|
/** The full `agentproto://pair?…` offer URL. */
|
|
@@ -2733,12 +3160,21 @@ interface CreatedOffer {
|
|
|
2733
3160
|
* falls back to `HOSTED_RENDEZVOUS_URL`. Surfaced so `pair offer` can flag,
|
|
2734
3161
|
* never silently, that the daemon is relaying through our infrastructure. */
|
|
2735
3162
|
rendezvousIsHostedDefault: boolean;
|
|
3163
|
+
/** Echoes `CreateOfferInput.scope`. `"host"` when minted with `--host`;
|
|
3164
|
+
* absent for a plain offer. */
|
|
3165
|
+
scope?: "host";
|
|
2736
3166
|
}
|
|
2737
3167
|
interface CreateOfferInput {
|
|
2738
3168
|
/** Time-to-live in ms. Default 10 minutes. */
|
|
2739
3169
|
ttlMs?: number;
|
|
2740
3170
|
/** Rendezvous URL override; falls back to the configured default. */
|
|
2741
3171
|
rendezvousUrl?: string;
|
|
3172
|
+
/** Mint a HOST-scoped offer (PR-C): the accepting side may register this
|
|
3173
|
+
* daemon as a driveable host (`devices add`), not just remote-control it.
|
|
3174
|
+
* Recorded on the resulting `OfferEntry`/`PairingRecord` server-side — this
|
|
3175
|
+
* is the authoritative record of what was granted, independent of the
|
|
3176
|
+
* `scope` param an accepting client's offer URL happens to carry. */
|
|
3177
|
+
scope?: "host";
|
|
2742
3178
|
}
|
|
2743
3179
|
interface PairingRegistryDeps {
|
|
2744
3180
|
/** Lazily load (create on first use) the daemon identity. Kept lazy so a
|
|
@@ -2772,23 +3208,184 @@ interface PairingRegistryDeps {
|
|
|
2772
3208
|
/** How long a parked daemon socket waits for a client hello before recycling
|
|
2773
3209
|
* the connection. Kept just under the broker's park timeout. Default 110s. */
|
|
2774
3210
|
handshakeTimeoutMs?: number;
|
|
3211
|
+
/** How long a revoked pairing is still answered with `pairing_revoked` (see
|
|
3212
|
+
* "Revocation" above). Default 14 days; `0` disables tombstones, so a
|
|
3213
|
+
* revoked client just stops finding the daemon. */
|
|
3214
|
+
revokedGraceMs?: number;
|
|
2775
3215
|
}
|
|
2776
3216
|
interface PairingRegistry {
|
|
2777
3217
|
/** Mint a one-time offer + start dialing the rendezvous for it. */
|
|
2778
3218
|
createOffer(input?: CreateOfferInput): Promise<CreatedOffer>;
|
|
2779
3219
|
/** All persisted pairings (copies). Loads `pairings.json` on first call. */
|
|
2780
3220
|
list(): Promise<PairingRecord[]>;
|
|
3221
|
+
/** Rename a pairing by fingerprint or name (cosmetic only — no effect on
|
|
3222
|
+
* auth or routing). Returns false when nothing matched; throws on an
|
|
3223
|
+
* empty `newName`. */
|
|
3224
|
+
rename(idOrName: string, newName: string): Promise<boolean>;
|
|
2781
3225
|
/** Drop a pairing by fingerprint or name; stops its rendezvous connections.
|
|
2782
3226
|
* Returns false when nothing matched. */
|
|
2783
3227
|
revoke(idOrName: string): Promise<boolean>;
|
|
3228
|
+
/** Is a channel (offer or reconnect) currently served for this fingerprint? */
|
|
3229
|
+
isOnline(fingerprint: string): boolean;
|
|
2784
3230
|
/** Start standing reconnect connections for every persisted pairing. Call
|
|
2785
3231
|
* after the gateway is up (so the injected `serve` can reach it). */
|
|
2786
3232
|
startAutoconnect(): Promise<void>;
|
|
2787
3233
|
/** Tear down every offer + reconnect connection and served channel. */
|
|
2788
3234
|
shutdown(): Promise<void>;
|
|
2789
3235
|
}
|
|
3236
|
+
/**
|
|
3237
|
+
* Read-only snapshot of `pairings.json` for callers that just want the
|
|
3238
|
+
* persisted records — no `dial`/`serve`/`loadIdentity` deps, no rendezvous
|
|
3239
|
+
* connections started (e.g. `agentproto doctor`'s devices check). Mirrors
|
|
3240
|
+
* `ensureLoaded`'s parse + v1-legacy flagging; never throws — a missing or
|
|
3241
|
+
* malformed file just yields `[]`.
|
|
3242
|
+
*/
|
|
3243
|
+
declare function readPairingsSnapshot(path?: string): Promise<PairingRecord[]>;
|
|
2790
3244
|
declare function createPairingRegistry(deps: PairingRegistryDeps): PairingRegistry;
|
|
2791
3245
|
|
|
3246
|
+
/**
|
|
3247
|
+
* Daemon-side HOST registry (DEVICES-PLAN PR-C) — the "client" half of
|
|
3248
|
+
* pair/v2, living on the daemon so it's reachable over both an HTTP route
|
|
3249
|
+
* and an MCP tool, not just CLI-local bookkeeping (`client-pairings.ts` /
|
|
3250
|
+
* `pair-transport.ts` are that CLI-local equivalent today).
|
|
3251
|
+
*
|
|
3252
|
+
* Where `pairing-registry.ts` is what a daemon uses to let OTHER machines
|
|
3253
|
+
* pair with and remote-control IT, `host-registry.ts` is what a daemon uses
|
|
3254
|
+
* to register and drive OTHER daemons — reverse pairing. `add()` accepts a
|
|
3255
|
+
* HOST-scoped offer (`agentproto pair offer --host`, offer-url.ts's
|
|
3256
|
+
* `scope`), runs the pair/v2 client handshake, and persists the resulting
|
|
3257
|
+
* pair root; `forwardHttp()` dials on demand, reconnecting over the current
|
|
3258
|
+
* (then previous) epoch token exactly like `pair-transport.ts`'s
|
|
3259
|
+
* `openPairChannel`, forwards one HTTP request over the resulting tunnel
|
|
3260
|
+
* client, and closes. There is no standing connection here — unlike
|
|
3261
|
+
* `PairingRegistry`, a `HostRegistry` never autoconnects or keeps a
|
|
3262
|
+
* reconnect loop parked at the broker; every reach-out is a fresh dial.
|
|
3263
|
+
* That also means shutdown needs no teardown: there is nothing standing to
|
|
3264
|
+
* tear down.
|
|
3265
|
+
*
|
|
3266
|
+
* ## The scope gate
|
|
3267
|
+
*
|
|
3268
|
+
* `add()` REFUSES (throws) any offer whose parsed `scope !== "host"` before
|
|
3269
|
+
* ever dialing anything. This is the default-deny gate PR-C exists to add:
|
|
3270
|
+
* an ordinary "remote-control me" offer (today's `pair offer`, no `--host`)
|
|
3271
|
+
* must not silently become a host registration, even though the underlying
|
|
3272
|
+
* wire capability is identical either way (see offer-url.ts's "Offer scope"
|
|
3273
|
+
* and pairing-registry.ts's `OfferEntry.scope` doc comment for why the
|
|
3274
|
+
* offering daemon's OWN server-side record — not the URL's plaintext
|
|
3275
|
+
* `scope` param — is what's authoritative about what it granted). There is
|
|
3276
|
+
* deliberately no override flag.
|
|
3277
|
+
*
|
|
3278
|
+
* Persisted to `hosts.json` (`~/.agentproto/hosts.json` by default) with the
|
|
3279
|
+
* same atomic-write discipline as `pairings.json` (mkdir, write to a
|
|
3280
|
+
* pid-keyed tmp file at 0600, chmod, rename) and the same lazy-load-on-
|
|
3281
|
+
* first-use pattern.
|
|
3282
|
+
*
|
|
3283
|
+
* ## Online tracking
|
|
3284
|
+
*
|
|
3285
|
+
* `isOnline(fingerprint)` mirrors `PairingRegistry.isOnline`: it's true only
|
|
3286
|
+
* while a `forwardHttp()` call for that fingerprint is in flight (a
|
|
3287
|
+
* reference count, incremented on dial and decremented in a `finally`).
|
|
3288
|
+
* There is no live probe backing `list()` — that would make `devices list`
|
|
3289
|
+
* slow/flaky over N hosts — so `list()`'s `online` is only ever true
|
|
3290
|
+
* immediately after (or during) an actual `forwardHttp`/`devices status`
|
|
3291
|
+
* call, never a background heartbeat. That's enough to make `devices list`
|
|
3292
|
+
* show the right thing right after `devices status`/`exec`, which is what
|
|
3293
|
+
* matters for this PR; a standing/autoconnect host connection with a real
|
|
3294
|
+
* liveness signal is PR-D's to build.
|
|
3295
|
+
*/
|
|
3296
|
+
|
|
3297
|
+
/** `hosts.json` format. */
|
|
3298
|
+
declare const HOSTS_VERSION: 1;
|
|
3299
|
+
/** One persisted host — a daemon this daemon can drive. */
|
|
3300
|
+
interface HostRecord {
|
|
3301
|
+
/** The target daemon's identity fingerprint (32 hex). */
|
|
3302
|
+
fingerprint: string;
|
|
3303
|
+
/** Human-facing label; defaults to the fingerprint. */
|
|
3304
|
+
name: string;
|
|
3305
|
+
/** Target daemon's static X25519 public key, standard base64 SPKI DER —
|
|
3306
|
+
* pinned at `add()` time. */
|
|
3307
|
+
daemonX25519Pub: string;
|
|
3308
|
+
/** Target daemon's static Ed25519 public key, standard base64 SPKI DER —
|
|
3309
|
+
* pinned at `add()` time. */
|
|
3310
|
+
daemonEd25519Pub: string;
|
|
3311
|
+
/** Rendezvous endpoint to reach the host through. */
|
|
3312
|
+
rendezvousUrl: string;
|
|
3313
|
+
/** Long-term shared secret (base64), from which epoch routing tokens
|
|
3314
|
+
* derive — same derivation as `PairingRecord.pairRoot`. */
|
|
3315
|
+
pairRoot: string;
|
|
3316
|
+
/** ISO-8601 first-add timestamp. */
|
|
3317
|
+
createdAt: string;
|
|
3318
|
+
/** ISO-8601 of the most recent successful `forwardHttp`. */
|
|
3319
|
+
lastSeen: string;
|
|
3320
|
+
/** Set on a host added under the retired pair/v1 protocol. Present for
|
|
3321
|
+
* parity with `PairingRecord`/`ClientPairing`; pair/v1 offers are refused
|
|
3322
|
+
* by `parseOfferUrl` before `add()` ever sees them, so this is currently
|
|
3323
|
+
* unreachable — kept so a future downgrade path has somewhere to flag it. */
|
|
3324
|
+
legacy?: true;
|
|
3325
|
+
}
|
|
3326
|
+
interface HostRegistryDeps {
|
|
3327
|
+
/** Dial a rendezvous WS and adapt it to a `FrameSink`. Same shape as
|
|
3328
|
+
* `PairingRegistryDeps.dial` — the CLI's `daemonDialRendezvous` satisfies
|
|
3329
|
+
* it directly. Rejects if the dial fails or `signal` aborts. */
|
|
3330
|
+
dial: (wsUrl: string, signal: AbortSignal) => Promise<FrameSink>;
|
|
3331
|
+
/** Path to `hosts.json`. Defaults to `~/.agentproto/hosts.json`. */
|
|
3332
|
+
hostsPath?: string;
|
|
3333
|
+
/** Injectable clock (ms). Defaults to Date.now. */
|
|
3334
|
+
now?: () => number;
|
|
3335
|
+
/** Diagnostic log sink. */
|
|
3336
|
+
log?: (line: string) => void;
|
|
3337
|
+
/** Rendezvous dial ceiling per attempt. Default 15s. */
|
|
3338
|
+
dialTimeoutMs?: number;
|
|
3339
|
+
/** Handshake ceiling per attempt. Default 15s. */
|
|
3340
|
+
handshakeTimeoutMs?: number;
|
|
3341
|
+
}
|
|
3342
|
+
interface ForwardHttpRequest {
|
|
3343
|
+
method: string;
|
|
3344
|
+
path: string;
|
|
3345
|
+
headers?: Record<string, string>;
|
|
3346
|
+
body?: Uint8Array;
|
|
3347
|
+
}
|
|
3348
|
+
interface ForwardHttpResponse {
|
|
3349
|
+
status: number;
|
|
3350
|
+
headers: Record<string, string>;
|
|
3351
|
+
body: Uint8Array;
|
|
3352
|
+
}
|
|
3353
|
+
interface HostRegistry {
|
|
3354
|
+
/**
|
|
3355
|
+
* Accept an offer URL and register the target daemon as a driveable host.
|
|
3356
|
+
* REFUSES (throws a plain `Error`) unless the parsed offer has
|
|
3357
|
+
* `scope === "host"` — no dial is attempted in that case. Otherwise: dials
|
|
3358
|
+
* the offer's rendezvous, runs the pair/v2 client handshake, verifies the
|
|
3359
|
+
* derived peer fingerprint matches the offer's `id` (defence in depth,
|
|
3360
|
+
* mirrors `pair-transport.ts`'s `acceptOffer`), derives the pair root, and
|
|
3361
|
+
* persists a `HostRecord` (upserted by fingerprint — re-adding replaces).
|
|
3362
|
+
*/
|
|
3363
|
+
add(offerUrl: string, name?: string): Promise<{
|
|
3364
|
+
fingerprint: string;
|
|
3365
|
+
name: string;
|
|
3366
|
+
rendezvousUrl: string;
|
|
3367
|
+
}>;
|
|
3368
|
+
/** All persisted hosts (copies). Loads `hosts.json` on first call. */
|
|
3369
|
+
list(): Promise<HostRecord[]>;
|
|
3370
|
+
/** Rename a host (fingerprint or current name). Returns false when nothing
|
|
3371
|
+
* matched; throws on an empty `newName`. */
|
|
3372
|
+
rename(idOrName: string, newName: string): Promise<boolean>;
|
|
3373
|
+
/** Drop a host by fingerprint or name. Returns false when nothing matched. */
|
|
3374
|
+
revoke(idOrName: string): Promise<boolean>;
|
|
3375
|
+
/** Is a `forwardHttp` call for this fingerprint in flight right now? */
|
|
3376
|
+
isOnline(fingerprint: string): boolean;
|
|
3377
|
+
/**
|
|
3378
|
+
* On-demand: dial the host, run the client handshake against the current
|
|
3379
|
+
* epoch's tokens (falling back to the previous epoch, bridging clock
|
|
3380
|
+
* skew — same two-attempt shape as `pair-transport.ts`'s
|
|
3381
|
+
* `openPairChannel`), forward one HTTP request over the resulting tunnel
|
|
3382
|
+
* client, then close. Throws with a re-pair hint if every attempt hangs up
|
|
3383
|
+
* on the hello (likely a pair/v1 peer).
|
|
3384
|
+
*/
|
|
3385
|
+
forwardHttp(idOrName: string, req: ForwardHttpRequest): Promise<ForwardHttpResponse>;
|
|
3386
|
+
}
|
|
3387
|
+
declare function createHostRegistry(deps: HostRegistryDeps): HostRegistry;
|
|
3388
|
+
|
|
2792
3389
|
/**
|
|
2793
3390
|
* Shared types for pluggable sandbox providers.
|
|
2794
3391
|
*
|
|
@@ -2832,6 +3429,17 @@ interface SandboxProviderHandle extends AdapterHandle {
|
|
|
2832
3429
|
* credentials (e.g. `local`).
|
|
2833
3430
|
*/
|
|
2834
3431
|
readonly setupFields?: readonly SetupField[];
|
|
3432
|
+
/**
|
|
3433
|
+
* The directory INSIDE the box a spawned agent session should default to
|
|
3434
|
+
* when the caller passed no explicit `cwd` — e.g. e2b/Box's login home
|
|
3435
|
+
* `/home/user`. Absent for a same-machine passthrough provider (`local`),
|
|
3436
|
+
* where the HOST's own resolved cwd is already a valid path in the box
|
|
3437
|
+
* (they're the same filesystem) and forwarding it as-is is correct.
|
|
3438
|
+
* Presence of this field is also the signal `bootSandboxAgentSession`
|
|
3439
|
+
* uses to reject an explicit HOST-only path (e.g. `/Volumes/...`) instead
|
|
3440
|
+
* of forwarding it into a box where it can never resolve.
|
|
3441
|
+
*/
|
|
3442
|
+
readonly defaultCwd?: string;
|
|
2835
3443
|
}
|
|
2836
3444
|
|
|
2837
3445
|
/**
|
|
@@ -3331,12 +3939,21 @@ interface OrchestratorGatewayDeps {
|
|
|
3331
3939
|
* spawn is rejected with `sandbox_provider_not_found`, exactly as the
|
|
3332
3940
|
* root gateway would be without it. */
|
|
3333
3941
|
resolveSandboxProvider?: SandboxProviderResolver;
|
|
3334
|
-
/**
|
|
3335
|
-
*
|
|
3336
|
-
*
|
|
3337
|
-
*
|
|
3338
|
-
*
|
|
3942
|
+
/** EXPLICIT override for config.json `defaults.agentPromptInterrupt` —
|
|
3943
|
+
* wins over the fresh per-call resolve (`resolveMessagingDefaults`,
|
|
3944
|
+
* see the factory body below). Tests inject this to pin a value without
|
|
3945
|
+
* touching the filesystem; production leaves it unset so every request
|
|
3946
|
+
* reads the live config, hot-applying a `config_set` change with no
|
|
3947
|
+
* restart. Forwarded to `registerSessionTools`. */
|
|
3339
3948
|
defaultAgentPromptInterrupt?: boolean;
|
|
3949
|
+
/** EXPLICIT override for config.json `defaults.messaging.allowSiblings` —
|
|
3950
|
+
* see `defaultAgentPromptInterrupt` above for the override/resolve
|
|
3951
|
+
* precedence. Forwarded to `registerAgentTools`. */
|
|
3952
|
+
messagingAllowSiblings?: boolean;
|
|
3953
|
+
/** EXPLICIT override for config.json `defaults.messaging.agentInterrupt` —
|
|
3954
|
+
* see `defaultAgentPromptInterrupt` above for the override/resolve
|
|
3955
|
+
* precedence. Forwarded to `registerAgentTools`. */
|
|
3956
|
+
messagingAgentInterrupt?: "allow" | "deny";
|
|
3340
3957
|
}
|
|
3341
3958
|
type OrchestratorMcpServerFactory = (scope: OrchestratorScope) => Promise<McpServer>;
|
|
3342
3959
|
/**
|
|
@@ -3391,7 +4008,7 @@ interface OrphanReaperRegistry {
|
|
|
3391
4008
|
list(opts?: {
|
|
3392
4009
|
includeArchived?: boolean;
|
|
3393
4010
|
}): readonly SessionDescriptor[];
|
|
3394
|
-
kill(id: string, signal?: NodeJS.Signals): boolean;
|
|
4011
|
+
kill(id: string, signal?: NodeJS.Signals, reason?: SessionEndReason): boolean;
|
|
3395
4012
|
}
|
|
3396
4013
|
/**
|
|
3397
4014
|
* Reap a dying parent's live descendants (WP-B — orphan reaping). A
|
|
@@ -3579,6 +4196,77 @@ declare function createActivityProjector(opts: {
|
|
|
3579
4196
|
resolvePrState?: PrStateResolver;
|
|
3580
4197
|
}): ActivityProjector;
|
|
3581
4198
|
|
|
4199
|
+
/**
|
|
4200
|
+
* Spawn-time role profiles — the primitive that decides whether a
|
|
4201
|
+
* spawned agent may itself delegate (spawn further children).
|
|
4202
|
+
*
|
|
4203
|
+
* A role is 3 layers:
|
|
4204
|
+
* - `disposition` — a system-prompt fragment. Soft: sets the mindset.
|
|
4205
|
+
* - `toolPolicy` — whether delegation tools are injected into the
|
|
4206
|
+
* child at all. HARD: enforced by the daemon at the `agent_start`
|
|
4207
|
+
* injection point (`session-spawn.ts`), not by the child's own
|
|
4208
|
+
* behaviour. A child cannot widen its own `toolPolicy` — not via
|
|
4209
|
+
* `promptAppend`, not by requesting `orchestrator` itself.
|
|
4210
|
+
* - `skills[]` — role-specific skill set (built-ins ship empty/
|
|
4211
|
+
* unset; a future pack-carried role can populate this).
|
|
4212
|
+
*
|
|
4213
|
+
* Why the asymmetry matters: a process can bring its own disposition
|
|
4214
|
+
* and skills (those are portable, could live in a skill/pack), but it
|
|
4215
|
+
* can never strip tools the daemon already injected into it. Only the
|
|
4216
|
+
* spawner controls that — which is exactly why `toolPolicy` must be
|
|
4217
|
+
* resolved and enforced here, at spawn time, rather than left to a
|
|
4218
|
+
* prompt instruction the model could rationalize past.
|
|
4219
|
+
*/
|
|
4220
|
+
|
|
4221
|
+
type DelegationPolicy = "allow" | "deny";
|
|
4222
|
+
interface RoleToolPolicy {
|
|
4223
|
+
delegation: DelegationPolicy;
|
|
4224
|
+
}
|
|
4225
|
+
interface RoleProfile {
|
|
4226
|
+
name: string;
|
|
4227
|
+
disposition: string;
|
|
4228
|
+
toolPolicy: RoleToolPolicy;
|
|
4229
|
+
skills?: string[];
|
|
4230
|
+
/**
|
|
4231
|
+
* Privilege level in the spawn lattice. Higher = more privileged.
|
|
4232
|
+
* The default spawn-gate rule (`canSpawn`) is non-escalation: a
|
|
4233
|
+
* parent may spawn a child only when `child.level <= parent.level`
|
|
4234
|
+
* — peers and subordinates are fine, spawning something MORE
|
|
4235
|
+
* privileged than yourself is not. (Unbounded same-level recursion —
|
|
4236
|
+
* e.g. supervisor spawning supervisor spawning supervisor — is a
|
|
4237
|
+
* pre-existing, deliberate pattern bounded separately by
|
|
4238
|
+
* `maxDepth`/`maxChildren`, not this lattice; see `spawnableRoles`
|
|
4239
|
+
* below for a role that also needs to cap recursion itself.)
|
|
4240
|
+
* `spawnableRoles` (below), when set, opts a role into a closed
|
|
4241
|
+
* allowlist instead — level is then only used for display/ordering.
|
|
4242
|
+
*/
|
|
4243
|
+
level: number;
|
|
4244
|
+
/**
|
|
4245
|
+
* Closed allowlist of role names this role may spawn, by name — the
|
|
4246
|
+
* opt-in "figé pipeline" mode. When set, `canSpawn` checks name
|
|
4247
|
+
* membership instead of the level comparison. Undefined (the
|
|
4248
|
+
* default, open mode) ⇒ non-escalation by `level`.
|
|
4249
|
+
*/
|
|
4250
|
+
spawnableRoles?: string[];
|
|
4251
|
+
/**
|
|
4252
|
+
* Role-level default for the daemon self-mount's deferred/lazy tool
|
|
4253
|
+
* loading (harness-parity item 3 — see `deferred-tools.ts`). Applied to
|
|
4254
|
+
* the injected `mcpServers` entry (`session-spawn.ts`'s
|
|
4255
|
+
* `shouldInjectDaemonSelfMount` path) as `?deferred=1|0` when the spawn
|
|
4256
|
+
* itself didn't pass an explicit `deferredTools` override. Undefined ⇒
|
|
4257
|
+
* no role-level opinion, the gateway's own boot-time default
|
|
4258
|
+
* (`defaults.mcp.deferredTools` in config.json) applies unchanged.
|
|
4259
|
+
*/
|
|
4260
|
+
deferredTools?: boolean;
|
|
4261
|
+
/**
|
|
4262
|
+
* Role-level default for `agent_start.browser` (see `browser-mount.ts`):
|
|
4263
|
+
* `"headless"` gives every spawn of this role its own isolated headless
|
|
4264
|
+
* Chrome. An explicit spawn `browser` wins; undefined ⇒ no opinion (the
|
|
4265
|
+
* user preset, then `defaults.spawn.browser`, decide).
|
|
4266
|
+
*/
|
|
4267
|
+
browser?: SpawnBrowserMode;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
3582
4270
|
/**
|
|
3583
4271
|
* Daemon-side AGENTS.md resolution + injection (WP-R2).
|
|
3584
4272
|
*
|
|
@@ -3607,7 +4295,44 @@ declare function createActivityProjector(opts: {
|
|
|
3607
4295
|
* absent — plus a standing cd-contract sentence regardless of mode.
|
|
3608
4296
|
*/
|
|
3609
4297
|
type AgentsMdMode = "inline" | "pointer" | "absent";
|
|
4298
|
+
/** The resolved AGENTS.md's absolute path; undefined when mode is "absent". */
|
|
4299
|
+
interface AgentsMdResolution {
|
|
4300
|
+
mode: AgentsMdMode;
|
|
4301
|
+
/** Absolute path of the resolved AGENTS.md. Undefined when mode is
|
|
4302
|
+
* `"absent"`. */
|
|
4303
|
+
path?: string;
|
|
4304
|
+
/** Full text of the AGENTS.md (inline mode only). */
|
|
4305
|
+
content?: string;
|
|
4306
|
+
/** The prompt block to inject (full inline text or a pointer), or
|
|
4307
|
+
* undefined when absent. Excludes the always-present cd-contract line. */
|
|
4308
|
+
block?: string;
|
|
4309
|
+
/** The standing cd-contract sentence, present in every mode. */
|
|
4310
|
+
contractLine: string;
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
interface WorkspaceRulesResolution {
|
|
4314
|
+
/** Absolute path of the resolved RULES.md. Undefined when absent (or a
|
|
4315
|
+
* read failure degraded to absent). */
|
|
4316
|
+
path?: string;
|
|
4317
|
+
/** Full text of the RULES.md, inlined. Present exactly when `block` is. */
|
|
4318
|
+
content?: string;
|
|
4319
|
+
/** The prompt block to inject, or undefined when absent. */
|
|
4320
|
+
block?: string;
|
|
4321
|
+
}
|
|
3610
4322
|
|
|
4323
|
+
/** The `appServe` request carried on `agent_start` — an in-box app dir plus
|
|
4324
|
+
* the port to serve its UI on. */
|
|
4325
|
+
interface SandboxAppServeSpec {
|
|
4326
|
+
/** Absolute path to the app's directory INSIDE the box (e.g.
|
|
4327
|
+
* `/home/user/apps/<slug>`). Must not contain a single quote — the
|
|
4328
|
+
* launcher shell-quotes it. */
|
|
4329
|
+
dir: string;
|
|
4330
|
+
/** Port `agentproto app serve` binds inside the box. Must also be exposed
|
|
4331
|
+
* by the provider (the spawn path appends it to `extraPorts` so a
|
|
4332
|
+
* boot-time exposure exists on providers that pre-resolve). Defaults to
|
|
4333
|
+
* `DEFAULT_APP_SERVE_PORT` when omitted. */
|
|
4334
|
+
port?: number;
|
|
4335
|
+
}
|
|
3611
4336
|
/** What a served app records on the session descriptor and echoes in the
|
|
3612
4337
|
* spawn result — non-secret, caller-visible. */
|
|
3613
4338
|
interface SessionAppServeInfo {
|
|
@@ -3833,6 +4558,460 @@ declare function loadWorktreeIsolation(loadCfg?: () => Promise<{
|
|
|
3833
4558
|
};
|
|
3834
4559
|
}>): Promise<WorktreeIsolationMode>;
|
|
3835
4560
|
|
|
4561
|
+
/**
|
|
4562
|
+
* Policy layer for `agent_start.attach` — the config-driven decision of WHO a
|
|
4563
|
+
* spawn nests under, kept deliberately separate (and pure) from the spawn
|
|
4564
|
+
* plumbing in `session-spawn.ts`, exactly as `worktree-isolation.ts` is for
|
|
4565
|
+
* `agent_start.worktree`.
|
|
4566
|
+
*
|
|
4567
|
+
* The problem it fixes: on the plain `/mcp` path (no scoped orchestrator
|
|
4568
|
+
* gateway), a spawn was attributed to a parent ONLY when the caller passed an
|
|
4569
|
+
* explicit `parentSessionId` hint — so a supervisor that spawned an executor
|
|
4570
|
+
* without wiring itself as an orchestrator produced a depth-0 ORPHAN. The
|
|
4571
|
+
* daemon already knows the caller's own id on that path (the trusted
|
|
4572
|
+
* `?callerSessionId=` query the self-ref `mcpServers` URL carries — PR 7 /
|
|
4573
|
+
* Gap 7), it just wasn't being used for lineage. This module decides whether
|
|
4574
|
+
* to use it.
|
|
4575
|
+
*
|
|
4576
|
+
* Trust boundary (unchanged from the hint): the scoped `/mcp/orchestrator`
|
|
4577
|
+
* gateway's token is the UNSPOOFABLE source — when a `callerScope` is present
|
|
4578
|
+
* the caller derives its parent from the token and this whole module is
|
|
4579
|
+
* bypassed (see `session-spawn.ts`). Everything here is descriptor-only
|
|
4580
|
+
* lineage on the anonymous root path; it never moves the gating `childDepth`
|
|
4581
|
+
* and so never relaxes a depth-gated worktree/role guard.
|
|
4582
|
+
*/
|
|
4583
|
+
|
|
4584
|
+
/**
|
|
4585
|
+
* The `agent_start.attach` field. `true` opts in to attaching under the
|
|
4586
|
+
* derived parent (the caller's own id / an explicit hint) even when the
|
|
4587
|
+
* policy is `on-request`; an object additionally pins an explicit parent id;
|
|
4588
|
+
* `false` forces an INDEPENDENT root (no parent, depth 0) regardless of
|
|
4589
|
+
* policy or derivable identity — the deliberate "launch detached" escape
|
|
4590
|
+
* hatch, mirroring `worktree: false`. Omitted ⇒ the policy mode decides.
|
|
4591
|
+
*/
|
|
4592
|
+
type AttachField = boolean | {
|
|
4593
|
+
parent?: string;
|
|
4594
|
+
};
|
|
4595
|
+
|
|
4596
|
+
/**
|
|
4597
|
+
* Shared `agent_start` spawn logic — orchestrator scoped sub-gateway
|
|
4598
|
+
* minting, `mcpServers` merge, hermes default-mcpServers safety net,
|
|
4599
|
+
* depth/quota checks. Extracted from `agent-tools.ts` (the MCP tool)
|
|
4600
|
+
* so the HTTP route can reuse it without re-implementing (and
|
|
4601
|
+
* re-drifting from) the same behaviour.
|
|
4602
|
+
*/
|
|
4603
|
+
|
|
4604
|
+
/** `agent_start.sandbox`'s inline-spec form, plus the PR3 reuse field. See
|
|
4605
|
+
* `SpawnAgentSessionInput.sandbox`. */
|
|
4606
|
+
type SandboxSpecInput = SandboxSpec & {
|
|
4607
|
+
reuse?: string;
|
|
4608
|
+
};
|
|
4609
|
+
/** Matches `RegisterAgentToolsOptions.buildOrchestratorMcp` in
|
|
4610
|
+
* agent-tools.ts — kept as its own alias here since that's the shape
|
|
4611
|
+
* actually passed in (opts required), not the more permissive
|
|
4612
|
+
* `OrchestratorInjector` (opts optional) from orchestrator-gateway.ts. */
|
|
4613
|
+
type BuildOrchestratorMcp = (opts: {
|
|
4614
|
+
tools?: readonly string[];
|
|
4615
|
+
caller?: OrchestratorScope;
|
|
4616
|
+
maxDepth?: number;
|
|
4617
|
+
maxChildren?: number;
|
|
4618
|
+
role?: string;
|
|
4619
|
+
}) => {
|
|
4620
|
+
entry: AcpMcpServer;
|
|
4621
|
+
bindLifecycle: (sessionId: string) => () => void;
|
|
4622
|
+
/** The minted scope — only `tools` (the effective, narrowed allowlist)
|
|
4623
|
+
* is read here, to tell whether the child can actually reach
|
|
4624
|
+
* `agent_start` through it (see `delegationReachFor`). Optional so
|
|
4625
|
+
* minimal injectors (tests) needn't return it. */
|
|
4626
|
+
scope?: {
|
|
4627
|
+
tools: ReadonlySet<string>;
|
|
4628
|
+
};
|
|
4629
|
+
};
|
|
4630
|
+
interface SpawnAgentSessionDeps {
|
|
4631
|
+
registry: SessionsRegistry;
|
|
4632
|
+
/** Required — callers must check for its absence themselves (the MCP
|
|
4633
|
+
* tool and HTTP route each have their own "not enabled" response
|
|
4634
|
+
* shape for the missing-resolver case). */
|
|
4635
|
+
resolveAgentAdapter: AgentAdapterResolver;
|
|
4636
|
+
/** Optional orchestrator-injection builder (WP3). See
|
|
4637
|
+
* `RegisterAgentToolsOptions.buildOrchestratorMcp` for the full
|
|
4638
|
+
* contract. Omitted → `orchestrator` is rejected with
|
|
4639
|
+
* `orchestrator_not_enabled`. */
|
|
4640
|
+
buildOrchestratorMcp?: BuildOrchestratorMcp;
|
|
4641
|
+
/** The daemon's own plain `/mcp` gateway URL. See
|
|
4642
|
+
* `RegisterAgentToolsOptions.daemonMcpUrl`. */
|
|
4643
|
+
daemonMcpUrl?: string;
|
|
4644
|
+
/** Calling orchestrator's scope (orchestrator WP4). See
|
|
4645
|
+
* `RegisterAgentToolsOptions.callerScope`. */
|
|
4646
|
+
callerScope?: OrchestratorScope;
|
|
4647
|
+
/** Optional webhook notifier — see `RegisterAgentToolsOptions.webhookNotifier`. */
|
|
4648
|
+
webhookNotifier?: WebhookNotifier;
|
|
4649
|
+
/** Loads config.json's `defaults` block (global + per-adapter skills/
|
|
4650
|
+
* options auto-applied at every spawn — see `resolveSpawnDefaults`).
|
|
4651
|
+
* Defaults to reading the real `~/.agentproto/config.json` via
|
|
4652
|
+
* `loadConfig` when omitted; tests inject a stub to avoid touching
|
|
4653
|
+
* the real file. */
|
|
4654
|
+
loadDefaultsConfig?: () => Promise<SpawnDefaultsConfig | undefined>;
|
|
4655
|
+
/** Builds the per-session headless-browser mount for `browser:
|
|
4656
|
+
* "headless"` (see `browser-mount.ts`). Defaults to the real resolver
|
|
4657
|
+
* (installs chrome-devtools-mcp on first use); tests inject a stub. */
|
|
4658
|
+
resolveHeadlessBrowser?: ResolveHeadlessBrowser;
|
|
4659
|
+
/** Loads the custom (pack-carried) role registry, merged with the
|
|
4660
|
+
* two built-ins by `resolveRole`/`canSpawn` — see `role.ts`'s
|
|
4661
|
+
* `mergeRoleRegistry`. Defaults to `loadDefaultRoleRegistry()`
|
|
4662
|
+
* (`~/.agentproto/roles/` + adapter-carried packs) when omitted;
|
|
4663
|
+
* tests inject a stub registry to avoid touching the real
|
|
4664
|
+
* filesystem. */
|
|
4665
|
+
loadRoleRegistry?: () => Promise<Record<string, RoleProfile>>;
|
|
4666
|
+
/** Resolves an `agent_start.sandbox` slug (or an inline spec's own
|
|
4667
|
+
* `.provider`) to a concrete sandbox provider handle. Required for
|
|
4668
|
+
* `input.sandbox` — omitted ⇒ `sandbox_provider_not_found`. */
|
|
4669
|
+
resolveSandboxProvider?: SandboxProviderResolver;
|
|
4670
|
+
/** Provision a git worktree and return the cwd the spawn should land in —
|
|
4671
|
+
* the injected port behind `agent_start.worktree` (see
|
|
4672
|
+
* `worktree-isolation.ts`). Wired at the composition root by a host that
|
|
4673
|
+
* depends on `@agentproto/worktree` (the CLI). Omitted ⇒ a spawn the
|
|
4674
|
+
* policy says to isolate fails with `worktree_provisioner_not_enabled`
|
|
4675
|
+
* rather than silently spawning unisolated. */
|
|
4676
|
+
provisionWorktree?: WorktreeProvisioner;
|
|
4677
|
+
/** Resolves the effective `worktrees.isolation` policy (env > config >
|
|
4678
|
+
* `on-request`). Defaults to reading the real `~/.agentproto/config.json`
|
|
4679
|
+
* via `loadWorktreeIsolation` when omitted; tests inject a stub to pin the
|
|
4680
|
+
* mode without touching the real file or env. */
|
|
4681
|
+
resolveWorktreeIsolation?: () => Promise<WorktreeIsolationMode>;
|
|
4682
|
+
/** Resolves the effective `spawn.attach` policy (env > config > `always`).
|
|
4683
|
+
* Defaults to `loadSpawnAttach` (reads the real config) when omitted;
|
|
4684
|
+
* tests inject a stub to pin the mode without touching env or the file. */
|
|
4685
|
+
resolveSpawnAttach?: () => Promise<SpawnAttachMode>;
|
|
4686
|
+
/** Resolves the effective `spawn.dedupe` policy (env > config > `always`).
|
|
4687
|
+
* Defaults to `loadSpawnDedupe` (reads the real config) when omitted;
|
|
4688
|
+
* tests inject a stub to pin the mode without touching env or the file.
|
|
4689
|
+
* See `spawn-dedupe.ts`. */
|
|
4690
|
+
resolveSpawnDedupe?: () => Promise<SpawnDedupeMode>;
|
|
4691
|
+
/** Resolves the effective `provenance.wrapGh` opt-in (env > config >
|
|
4692
|
+
* `false`). When it resolves true, a `gh` PATH shim is prepended to the
|
|
4693
|
+
* spawned session's env so any `gh pr create` it (or an adapter
|
|
4694
|
+
* subprocess) runs gets the daemon's provenance footer appended to the
|
|
4695
|
+
* created PR's body — see `gh-provenance-shim.ts`. Defaults to
|
|
4696
|
+
* `loadProvenanceWrapGh` (reads the real config) when omitted; tests
|
|
4697
|
+
* inject a stub to pin it without touching env or the file. */
|
|
4698
|
+
resolveProvenanceWrapGh?: () => Promise<boolean>;
|
|
4699
|
+
/** Resolves + shapes the AGENTS.md for a spawn's `cwd` (see
|
|
4700
|
+
* `agents-md.ts`): inline / pointer / absent, plus the standing cd-contract
|
|
4701
|
+
* line. Defaults to the real resolver over `loadAgentsMdInlineMaxKb`'s
|
|
4702
|
+
* config-derived threshold when omitted; tests inject a stub to avoid
|
|
4703
|
+
* touching the filesystem, git, and the real config on every spawn. */
|
|
4704
|
+
resolveAgentsMd?: (cwd: string, inlineMaxKb?: number) => Promise<AgentsMdResolution>;
|
|
4705
|
+
/** Resolves + shapes the per-workspace RULES.md for a spawn (see
|
|
4706
|
+
* `workspace-rules.ts`): the path of the workspace's `RULES.md`, inlined
|
|
4707
|
+
* in full when present, absent otherwise. Injected into EVERY spawn in
|
|
4708
|
+
* the workspace (root and nested — no depth gate). Defaults to the real
|
|
4709
|
+
* resolver over the workspace state bucket when omitted; tests inject a
|
|
4710
|
+
* stub to avoid touching the real `~/.agentproto` directory. */
|
|
4711
|
+
resolveWorkspaceRules?: (slug: string | undefined) => Promise<WorkspaceRulesResolution>;
|
|
4712
|
+
/** Feeds the adapter-capability spawn guard (`checkModelAdapterEligibility`)
|
|
4713
|
+
* — the SAME `listCatalogModels` the `catalog_models` MCP tool /
|
|
4714
|
+
* `GET /catalog/models` route already use (`RegisterAgentToolsOptions.
|
|
4715
|
+
* listCatalogModels`), reused here rather than re-derived. Omitted ⇒ the
|
|
4716
|
+
* guard is skipped — a spawn that would 404 upstream on an adapter's
|
|
4717
|
+
* manifest not covering the requested model still reaches the driver,
|
|
4718
|
+
* same as before this guard existed. */
|
|
4719
|
+
listCatalogModels?: CatalogModelsLister;
|
|
4720
|
+
/** Ensure the local `llm-endpoint` proxy sidecar (`LlmEndpointRegistry`)
|
|
4721
|
+
* is up before a spawn that bills through it. Without this hook, a spawn
|
|
4722
|
+
* whose resolved gateway is `"llm-endpoint"` assumed the sidecar was
|
|
4723
|
+
* already running (started externally, or via the `llm_endpoint_start`
|
|
4724
|
+
* MCP tool) — a fresh install with `features.llmEndpoint` on but nothing
|
|
4725
|
+
* having called that tool yet would spawn fine and only fail once the
|
|
4726
|
+
* adapter made its first request. Wired to the registry's own
|
|
4727
|
+
* (idempotent) `start()` at the composition root; omitted ⇒ unchanged
|
|
4728
|
+
* pre-existing behavior. Best-effort: a rejection here is swallowed by
|
|
4729
|
+
* the caller, not surfaced as a spawn failure — the adapter's own first
|
|
4730
|
+
* request is left to report the real connection error. */
|
|
4731
|
+
ensureLlmEndpointRunning?: () => Promise<void>;
|
|
4732
|
+
}
|
|
4733
|
+
interface SpawnAgentSessionInput {
|
|
4734
|
+
adapter: string;
|
|
4735
|
+
/** Canonical harness slug — recorded on the descriptor; defaults to `adapter`. */
|
|
4736
|
+
harness?: string;
|
|
4737
|
+
cwd?: string;
|
|
4738
|
+
workspaceSlug?: string;
|
|
4739
|
+
/** Trusted-loopback parent-lineage hint (WP-R1). Attributes this spawn to a
|
|
4740
|
+
* logical parent session so it nests under that node in the sessions tree,
|
|
4741
|
+
* filling the gap that otherwise leaves an anonymous `agent_start` (an
|
|
4742
|
+
* agent-to-agent spawn on the root `/mcp`, or a caller that IS a session but
|
|
4743
|
+
* didn't come through the scoped orchestrator gateway) a depth-0 orphan.
|
|
4744
|
+
*
|
|
4745
|
+
* Trust boundary: the scoped `/mcp/orchestrator` gateway derives parent from
|
|
4746
|
+
* its unspoofable scope token, so a hint arriving WITH a `callerScope` is
|
|
4747
|
+
* IGNORED — the scope always wins. The hint is honoured ONLY on the
|
|
4748
|
+
* anonymous root path (no `callerScope`), where there is no scope to
|
|
4749
|
+
* attribute from. `depth` is derived from the resolved parent descriptor
|
|
4750
|
+
* (parent's `depth + 1`, defaulting to 1 when the parent isn't registered)
|
|
4751
|
+
* rather than trusted from the caller. Descriptor-only: a hint never relaxes
|
|
4752
|
+
* the depth-gated worktree/role guards — declaring a logical parent is not
|
|
4753
|
+
* the same as being a nested (scoped) spawn. */
|
|
4754
|
+
parentSessionId?: string;
|
|
4755
|
+
/** Attach policy override for THIS spawn (`agent_start.attach`). `false`
|
|
4756
|
+
* forces an independent root even when a parent is derivable; `true` opts
|
|
4757
|
+
* in to attaching under the derived parent even under an `on-request`
|
|
4758
|
+
* policy; `{ parent }` pins an explicit parent. Omitted ⇒ the daemon's
|
|
4759
|
+
* `spawn.attach` policy decides. Resolved together with `parentSessionId`
|
|
4760
|
+
* and `autoParentSessionId` in `decideSpawnAttach` — see `spawn-attach.ts`.
|
|
4761
|
+
* Ignored WITH a `callerScope` (the scoped orchestrator token always wins). */
|
|
4762
|
+
attach?: AttachField;
|
|
4763
|
+
/** Daemon-DERIVED caller session id — the spawning session's own id, read
|
|
4764
|
+
* from the trusted `?callerSessionId=` query the self-ref `mcpServers` URL
|
|
4765
|
+
* carries (PR 7 / Gap 7), NOT from the caller's arguments. This is the
|
|
4766
|
+
* implicit auto-parent that makes attach-by-default work without the caller
|
|
4767
|
+
* passing its own id. Threaded from `registerAgentTools`' `callerSessionId`
|
|
4768
|
+
* option; lower precedence than an explicit `parentSessionId` hint. */
|
|
4769
|
+
autoParentSessionId?: string;
|
|
4770
|
+
/** Task-board pin for the spawned child, stamped onto its descriptor as
|
|
4771
|
+
* `meta.boardId`. The Task ledger's board resolution prefers this over
|
|
4772
|
+
* the `parentSessionId` lineage walk (see task-ledger.ts's
|
|
4773
|
+
* `resolveBoardId`), so a client spawning several depth-0 roots — a
|
|
4774
|
+
* cowork/operator harness fanning out executors with no shared lineage —
|
|
4775
|
+
* can join them all onto ONE board. Descriptor-only: it never enters the
|
|
4776
|
+
* depth/quota/role gates, and an explicit `boardId` on a task verb still
|
|
4777
|
+
* wins over it. */
|
|
4778
|
+
boardId?: string;
|
|
4779
|
+
/** Source label for this spawn (channel/harness). Descriptor-only. */
|
|
4780
|
+
origin?: string;
|
|
4781
|
+
/** Installed app id this spawn runs on behalf of (`app_run` sets this to
|
|
4782
|
+
* the app's own id). Forwarded to the child process as `APP_ID_ENV` — a
|
|
4783
|
+
* daemon-tool proxy the child builds (mastra-agent's `daemon-mcp-tools.ts`,
|
|
4784
|
+
* P7) auto-injects it into an `app_*` tool call that omits `appId`, since
|
|
4785
|
+
* the model driving the session has no way to know its own appId unless
|
|
4786
|
+
* told. Absent on a spawn made outside `app_run`. */
|
|
4787
|
+
appId?: string;
|
|
4788
|
+
/** Reattach to a pre-existing adapter-native session (claude-code's
|
|
4789
|
+
* conversation id, hermes' chat handle, …) instead of starting
|
|
4790
|
+
* blank. Not exposed on the MCP `agent_start` tool today — only the
|
|
4791
|
+
* HTTP route (`sessions restart`) passes this. */
|
|
4792
|
+
resumeSessionId?: string;
|
|
4793
|
+
/** The caller's initial ask — a plain string (the common case, composed
|
|
4794
|
+
* with the role/AGENTS.md/RULES.md preamble below into one string turn),
|
|
4795
|
+
* or a content block / block array (an attachment-bearing first message,
|
|
4796
|
+
* same loose shape `POST /sessions/:id/prompt` accepts). The block form
|
|
4797
|
+
* is a narrow path: composing daemon-synthesized preamble TEXT onto a
|
|
4798
|
+
* turn that opens with an image isn't meaningful string concatenation,
|
|
4799
|
+
* so it SKIPS the whole preamble composition below (role disposition,
|
|
4800
|
+
* AGENTS.md, RULES.md, parent-lineage line, posture-as-prompt fallback)
|
|
4801
|
+
* and is sent to the adapter verbatim — see `effectivePrompt`'s doc.
|
|
4802
|
+
* Acceptable because this path is human/UI-initiated (an attachment
|
|
4803
|
+
* typed into a chat composer), not the orchestrator-driven role spawns
|
|
4804
|
+
* that actually depend on the composed disposition text. */
|
|
4805
|
+
prompt?: string | Record<string, unknown> | unknown[];
|
|
4806
|
+
label?: string;
|
|
4807
|
+
/** Explicit session title (SPEC-3 FIX C, `agentproto sessions start
|
|
4808
|
+
* --title`). When set, it wins over the first-sentence derivation from
|
|
4809
|
+
* `prompt` — so a CLI-named session isn't limited to whatever its opening
|
|
4810
|
+
* prompt happened to say. Unlike `label`, it fills the `title` slot, so a
|
|
4811
|
+
* spawner `label` still out-ranks it in the display chain. */
|
|
4812
|
+
title?: string;
|
|
4813
|
+
mode?: string;
|
|
4814
|
+
/** Manifest-declared option id → value map (AIP-45 `options`), applied
|
|
4815
|
+
* at spawn time alongside `mode`. Forwarded verbatim to the driver's
|
|
4816
|
+
* `startSession({ options })` → `composeSpawn`'s option patches. */
|
|
4817
|
+
options?: Record<string, boolean | number | string>;
|
|
4818
|
+
/** Normalized, adapter-agnostic skill ids for this session. Merged with
|
|
4819
|
+
* config.json's `defaults.skills` / `defaults.adapters.<slug>.skills`
|
|
4820
|
+
* (global < per-adapter < this field, which REPLACES the union rather
|
|
4821
|
+
* than merging into it when provided — a deliberate exact set). Folded
|
|
4822
|
+
* into `options.skills` per the resolved adapter's declared `skills`
|
|
4823
|
+
* option shape (e.g. hermes' comma-joined string); adapters with no
|
|
4824
|
+
* such option (e.g. claude-code, which auto-discovers skills) ignore
|
|
4825
|
+
* it. See `resolveSpawnDefaults` / `normalizeSkillsOption`. */
|
|
4826
|
+
skills?: string[];
|
|
4827
|
+
model?: string;
|
|
4828
|
+
effort?: string;
|
|
4829
|
+
/** Decomposed route identity. This is canonical transport; `mode` remains
|
|
4830
|
+
* a legacy adapter projection only. */
|
|
4831
|
+
route?: RouteSpec;
|
|
4832
|
+
/** Named billing credential. Resolved from auth-profiles + keychain at the
|
|
4833
|
+
* final spawn boundary; the secret never crosses HTTP/MCP. */
|
|
4834
|
+
access?: {
|
|
4835
|
+
profileRef?: string;
|
|
4836
|
+
};
|
|
4837
|
+
posture?: Posture;
|
|
4838
|
+
contextProfile?: ContextProfile;
|
|
4839
|
+
/** A preloaded user preset. Callers resolve its id at their boundary so a
|
|
4840
|
+
* preset can also provide the adapter before this core is invoked. */
|
|
4841
|
+
preset?: UserPreset;
|
|
4842
|
+
/**
|
|
4843
|
+
* Deterministic billing-auth mode + EXPLICIT credential for adapters that
|
|
4844
|
+
* declare an env-var vocabulary for it (today: claude-code — see
|
|
4845
|
+
* `AgentCliAuth.modes` in `@agentproto/driver-agent-cli`). `mode` wins over
|
|
4846
|
+
* `~/.agentproto/config.json`'s `defaults.adapters.<slug>.auth.mode`
|
|
4847
|
+
* (default `"subscription"`); the credential (`token` for `"subscription"`,
|
|
4848
|
+
* `apiKey` for `"api-key"`) wins over the config field matching the
|
|
4849
|
+
* RESOLVED mode — see `resolveSpawnDefaults`. Never read from the ambient
|
|
4850
|
+
* shell env; never logged (only a fingerprint is recorded on the session
|
|
4851
|
+
* descriptor — see `credentialFingerprint`). Adapters that don't declare
|
|
4852
|
+
* the vocabulary ignore this field entirely.
|
|
4853
|
+
*/
|
|
4854
|
+
auth?: DefaultsAdapterAuthConfig;
|
|
4855
|
+
mcpServers?: AcpMcpServer[];
|
|
4856
|
+
orchestrator?: boolean | {
|
|
4857
|
+
tools?: string[];
|
|
4858
|
+
maxDepth?: number;
|
|
4859
|
+
maxChildren?: number;
|
|
4860
|
+
};
|
|
4861
|
+
notifyUrl?: string;
|
|
4862
|
+
wait?: boolean;
|
|
4863
|
+
maxCostUsd?: number;
|
|
4864
|
+
/** Windowed cost-budget cap (phase 4). DISTINCT from the scalar `maxCostUsd`
|
|
4865
|
+
* session-kill: this is recorded on the descriptor/runtime and drives an
|
|
4866
|
+
* auto-attached governance policy (`policy:failed` on windowed overage) — it
|
|
4867
|
+
* never kills the session. Omit ⇒ no windowed budget. */
|
|
4868
|
+
costBudget?: CostBudget;
|
|
4869
|
+
/** Opt-in auto-restart policy (restart-scheduler, PR-2). When set, an
|
|
4870
|
+
* unexpected death (`crashed` and/or `error`, per `restartPolicy.on`) is
|
|
4871
|
+
* proactively revived in place — see `RestartPolicy`'s doc in
|
|
4872
|
+
* `sessions.ts`. Omitted ⇒ today's lazy-resume-only behaviour. */
|
|
4873
|
+
restartPolicy?: RestartPolicy;
|
|
4874
|
+
/** Context-continuity policy for this session — controls warning,
|
|
4875
|
+
* opportunistic compaction, fresh-continuation, and hard-stop thresholds.
|
|
4876
|
+
* Resolved from global → per-adapter → per-model → explicit override. */
|
|
4877
|
+
contextContinuity?: ContextContinuityPolicy;
|
|
4878
|
+
/** Spawn-time role — `"executor"` | `"supervisor"` | omitted. See
|
|
4879
|
+
* `resolveRole` in `role.ts`. Omitted ⇒ derived from spawn depth
|
|
4880
|
+
* against `defaults.defaultRoleDepthCutoff` (default 1). A resolved
|
|
4881
|
+
* role with `toolPolicy.delegation === "deny"` is a HARD gate:
|
|
4882
|
+
* `orchestrator` is ignored outright and the hermes/default
|
|
4883
|
+
* full-gateway injection excludes `DELEGATION_TOOL_NAMES` — neither
|
|
4884
|
+
* this field's own request nor `promptAppend` can re-open it. */
|
|
4885
|
+
role?: string;
|
|
4886
|
+
/** One-off runtime text layered ON TOP of the resolved role's
|
|
4887
|
+
* disposition (never replacing it) and prepended to `prompt`. See
|
|
4888
|
+
* `composeRoleContext`. Cannot widen `toolPolicy` — see `role` above. */
|
|
4889
|
+
promptAppend?: string;
|
|
4890
|
+
/** Per-spawn override for the daemon self-mount's deferred/lazy
|
|
4891
|
+
* `tools/list` loading (harness-parity item 3 — see `deferred-tools.ts`).
|
|
4892
|
+
* Wins over the resolved role's own default (`RoleProfile.deferredTools`
|
|
4893
|
+
* — on for `executor`), which in turn only applies when this is
|
|
4894
|
+
* omitted. Threaded onto the injected `mcpServers` self-mount ref as
|
|
4895
|
+
* `?deferred=1|0` (`shouldInjectDaemonSelfMount` path below); has no
|
|
4896
|
+
* effect on a caller-supplied `mcpServers` (that URL is the caller's to
|
|
4897
|
+
* compose). Omitted entirely ⇒ no override at all — the gateway's own
|
|
4898
|
+
* boot-time `defaults.mcp.deferredTools` default applies unchanged. */
|
|
4899
|
+
deferredTools?: boolean;
|
|
4900
|
+
/** Give the agent its own isolated headless Chrome: a per-session
|
|
4901
|
+
* chrome-devtools-mcp stdio server appended to `mcpServers` (named
|
|
4902
|
+
* `browser`), plus a short prompt hint naming its tools. Wins over the
|
|
4903
|
+
* resolved role's `browser`, then the user preset's, then config
|
|
4904
|
+
* `defaults.spawn.browser`; all unset ⇒ off. Explicit `true` with a
|
|
4905
|
+
* `sandbox` spawn is rejected (no Chrome in the box image yet). */
|
|
4906
|
+
browser?: SpawnBrowserMode;
|
|
4907
|
+
/** Opt this session into Langfuse tracing (prompt/completion + tool spans +
|
|
4908
|
+
* tokens/cost). Effective opt-in is `trace ?? langfuseTracingDefault ?? false`
|
|
4909
|
+
* — see `SpawnAgentInput.trace` in sessions.ts. */
|
|
4910
|
+
trace?: boolean;
|
|
4911
|
+
/** Run this session inside a sandbox instead of on the host — a provider
|
|
4912
|
+
* slug from `list_sandbox_providers` (e.g. `"local"`, `"e2b"`), or an
|
|
4913
|
+
* inline AIP-36 `SandboxSpec`, optionally carrying `reuse: "<sandboxId>"`
|
|
4914
|
+
* to reconnect to an existing box instead of booting fresh. When set,
|
|
4915
|
+
* this branch resolves the provider, boots (or reconnects) the box,
|
|
4916
|
+
* spawns `adapter` on the BOX's own `agent_start`, and wires a
|
|
4917
|
+
* `SandboxAgentSessionProxy` in place of the local `resolveAgentAdapter`
|
|
4918
|
+
* path — see `sandbox-agent-session-proxy.ts`. */
|
|
4919
|
+
sandbox?: string | SandboxSpecInput;
|
|
4920
|
+
/** WP3 — serve an agentproto app's UI from INSIDE the sandbox box and
|
|
4921
|
+
* return its public URL on the descriptor + result. Requires `sandbox`
|
|
4922
|
+
* (a non-sandbox spawn carrying `appServe` is rejected): the app dir is
|
|
4923
|
+
* resolved INSIDE the box (e.g. `/home/user/apps/<slug>` on the
|
|
4924
|
+
* workstation image), installed via the box daemon's `app_install`, and
|
|
4925
|
+
* `agentproto app serve --host 0.0.0.0 --port <port>` is launched
|
|
4926
|
+
* detached through the box's `command_execute`. The port is appended to
|
|
4927
|
+
* the spec's `extraPorts` so the provider resolves its public URL (also
|
|
4928
|
+
* stamped into `sandboxPorts`), and the serve result lands on the
|
|
4929
|
+
* descriptor's `appServe` field. See `sandbox-app-serve.ts`. */
|
|
4930
|
+
appServe?: SandboxAppServeSpec;
|
|
4931
|
+
/** Isolate this session into its own git worktree instead of spawning in
|
|
4932
|
+
* `cwd` directly. `true` provisions a worktree with an auto-minted branch/
|
|
4933
|
+
* slug; `{ slug?, base? }` pins the slug and/or the base ref it's cut from.
|
|
4934
|
+
* Honoured only at spawn depth 0 (a nested spawn inherits the parent's
|
|
4935
|
+
* ground) and only for a `cwd` inside a git repo (nothing to isolate
|
|
4936
|
+
* otherwise ⇒ spawns plain). The daemon's `worktrees.isolation` policy can
|
|
4937
|
+
* force this on (`always`) or off (`never`) regardless — see
|
|
4938
|
+
* `worktree-isolation.ts`. Ignored entirely for a `sandbox` spawn (the box
|
|
4939
|
+
* already isolates). Cleanup is the human's job (`agentproto worktree
|
|
4940
|
+
* rm|archive|gc`); the tree is never auto-removed on session exit. */
|
|
4941
|
+
worktree?: WorktreeField;
|
|
4942
|
+
/** Acknowledge that spawning IN PLACE into a shared, dirty working tree is
|
|
4943
|
+
* intended, silencing the `sharedDirtyCwd` warning a nested implicit spawn
|
|
4944
|
+
* would otherwise emit (see `decideWorktreeIsolation`). Only meaningful for
|
|
4945
|
+
* a nested (depth > 0) spawn with no `worktree` request and no `sandbox`;
|
|
4946
|
+
* ignored everywhere else. Default false — the warning fires. */
|
|
4947
|
+
allowSharedCwd?: boolean;
|
|
4948
|
+
/** Start this session in permission-hold mode: every ACP permission request
|
|
4949
|
+
* is surfaced + parked in the cross-session inbox (`permissions_list` /
|
|
4950
|
+
* `permissions_respond`) instead of auto-answered. Threaded to the driver's
|
|
4951
|
+
* `startSession({ permissionHold })` and recorded on the descriptor. Ignored
|
|
4952
|
+
* for sandbox spawns (the box's own daemon owns permission handling).
|
|
4953
|
+
* Default false — unchanged auto-answer behaviour. */
|
|
4954
|
+
permissionHold?: boolean;
|
|
4955
|
+
/** Opt this child into the direct in-band crash notification: when it
|
|
4956
|
+
* crashes (`markCrashed`), the supervisor-notify subscriber
|
|
4957
|
+
* (`supervisor-notify.ts`) delivers a `[child-crashed] …` notice into its
|
|
4958
|
+
* `parentSessionId`, if any — enqueued immediately when the parent is
|
|
4959
|
+
* alive and idle, queued for its next turn when busy (never
|
|
4960
|
+
* interrupted). Recorded verbatim onto {@link SessionDescriptor
|
|
4961
|
+
* .notifyParentOnCrash}; see that field's doc. Default false — the free
|
|
4962
|
+
* external webhook path (`notifyUrl`) already fires regardless of this
|
|
4963
|
+
* flag. */
|
|
4964
|
+
notifyParentOnCrash?: boolean;
|
|
4965
|
+
/** Exempt this session from the idle-reaper (`isReapable` in
|
|
4966
|
+
* idle-reaper.ts) regardless of how long it sits idle. Stamped straight
|
|
4967
|
+
* onto the descriptor — see `SessionDescriptor.keepAlive`. Default false
|
|
4968
|
+
* — unchanged reap-eligible behaviour. Toggleable later via the
|
|
4969
|
+
* `session_set_keepalive` MCP verb. */
|
|
4970
|
+
keepAlive?: boolean;
|
|
4971
|
+
/** Caller-declared "this is the same logical spawn" token — a PROMISE, not
|
|
4972
|
+
* a guess. A second `agent_start` with the same `(adapter, cwd,
|
|
4973
|
+
* idempotencyKey)` within `SPAWN_CLAIM_WINDOW_MS` of a SUCCESSFUL spawn
|
|
4974
|
+
* returns that spawn's descriptor instead of forking a second process —
|
|
4975
|
+
* the fix for a retried call otherwise silently duplicating a live
|
|
4976
|
+
* agent. Always wins over the daemon's own derived (implicit) key when
|
|
4977
|
+
* both would apply — see `dedupe` and `spawn-dedupe.ts`. Omitting this
|
|
4978
|
+
* does NOT mean "spawn unconditionally" anymore: when the daemon's
|
|
4979
|
+
* `spawn.dedupe` policy is `"always"` (the default) and this spawn
|
|
4980
|
+
* carries a `label`, the daemon derives an implicit key in its place —
|
|
4981
|
+
* see `dedupe` below to opt out per-call. */
|
|
4982
|
+
idempotencyKey?: string;
|
|
4983
|
+
/** Per-call override for the daemon's `spawn.dedupe` policy (implicit
|
|
4984
|
+
* dedupe when no `idempotencyKey` was supplied — see `spawn-dedupe.ts`).
|
|
4985
|
+
* `false` is the escape hatch: never derive an implicit key for THIS
|
|
4986
|
+
* spawn, regardless of policy — mirrors `attach: false` / `worktree:
|
|
4987
|
+
* false`. `true` forces derivation even under an `"on-request"` policy,
|
|
4988
|
+
* mirroring `attach: true`. Omitted ⇒ the policy mode decides. Has no
|
|
4989
|
+
* effect when an explicit `idempotencyKey` is supplied (it already wins
|
|
4990
|
+
* outright) or when this spawn carries no `label` (nothing to derive
|
|
4991
|
+
* from either way). */
|
|
4992
|
+
dedupe?: boolean;
|
|
4993
|
+
/**
|
|
4994
|
+
* OS-level confinement for the adapter's OWN spawned process
|
|
4995
|
+
* (`@agentproto/command-sandbox` — macOS Seatbelt / Linux bubblewrap),
|
|
4996
|
+
* threaded verbatim to `resolved.startSession({ commandSandbox })` →
|
|
4997
|
+
* `AgentCliRuntime.start`. NOT the AIP-36 `sandbox` field above — that
|
|
4998
|
+
* boots a whole nested daemon on a different machine/box (remote-box
|
|
4999
|
+
* session provider); this wraps THIS host's spawn argv so the adapter's
|
|
5000
|
+
* own process tree can't read/write outside `cwd` (and, `"strict"`,
|
|
5001
|
+
* reach the network), confinement an ACP permission seam can never see
|
|
5002
|
+
* since it only covers tool calls the adapter chooses to report. Exposed
|
|
5003
|
+
* on the `agent_start` MCP tool schema (`agent-tools.ts`) as of PR 6b —
|
|
5004
|
+
* when omitted here, the driver falls back to the workspace's
|
|
5005
|
+
* `.agentproto/command-sandbox.json` `adapterSpawn` key (a key distinct
|
|
5006
|
+
* from the top-level `mode` that same file also carries for
|
|
5007
|
+
* `command_execute` — see `@agentproto/command-sandbox`'s module doc for
|
|
5008
|
+
* why the two axes are never shared), or stays unconfined if that's unset
|
|
5009
|
+
* too. Ignored for a `sandbox` spawn (the box's own daemon would need to
|
|
5010
|
+
* apply this itself).
|
|
5011
|
+
*/
|
|
5012
|
+
commandSandbox?: SandboxMode;
|
|
5013
|
+
}
|
|
5014
|
+
|
|
3836
5015
|
/**
|
|
3837
5016
|
* Shared kernel for the daemon's worktree-status query surface.
|
|
3838
5017
|
*
|
|
@@ -3853,9 +5032,28 @@ interface WorktreeStatusView {
|
|
|
3853
5032
|
branch: string | null;
|
|
3854
5033
|
class: "reclaim" | "salvage" | "hold";
|
|
3855
5034
|
reclaimable: boolean;
|
|
5035
|
+
/** Uncommitted work in the tree (modified, staged or untracked paths). */
|
|
5036
|
+
dirty: boolean;
|
|
5037
|
+
/** Per-kind counts, present only when `dirty`. */
|
|
5038
|
+
changes?: {
|
|
5039
|
+
modified: number;
|
|
5040
|
+
staged: number;
|
|
5041
|
+
untracked: number;
|
|
5042
|
+
};
|
|
5043
|
+
/** Ahead/behind vs the base ref the integration check compares against
|
|
5044
|
+
* (the repo's default branch, e.g. `origin/main`). `null` for a detached
|
|
5045
|
+
* tip or when git couldn't resolve the base. */
|
|
5046
|
+
base: null | {
|
|
5047
|
+
ref: string;
|
|
5048
|
+
ahead: number;
|
|
5049
|
+
behind: number;
|
|
5050
|
+
};
|
|
5051
|
+
/** Integration state; `number` whenever the forge matched a PR (open,
|
|
5052
|
+
* merged, partial), `url` when the host knows the forge's web URL. */
|
|
3856
5053
|
pr: null | {
|
|
3857
5054
|
state: string;
|
|
3858
5055
|
number?: number;
|
|
5056
|
+
url?: string;
|
|
3859
5057
|
};
|
|
3860
5058
|
sessions: Array<{
|
|
3861
5059
|
id: string;
|
|
@@ -3869,13 +5067,24 @@ interface WorktreeStatusView {
|
|
|
3869
5067
|
sessionCount: number;
|
|
3870
5068
|
};
|
|
3871
5069
|
}
|
|
5070
|
+
/** Narrows a lister call. `paths` computes only those worktrees (compared
|
|
5071
|
+
* resolved) instead of every worktree of the repo: one forge round-trip for
|
|
5072
|
+
* a per-session lookup, not one per worktree. */
|
|
5073
|
+
interface WorktreeStatusListOptions {
|
|
5074
|
+
paths?: readonly string[];
|
|
5075
|
+
}
|
|
3872
5076
|
/** Injected port: the runtime asks the host to list worktrees for a repo. */
|
|
3873
|
-
type WorktreeStatusLister = (repoRoot: string) => Promise<WorktreeStatusView[]>;
|
|
5077
|
+
type WorktreeStatusLister = (repoRoot: string, options?: WorktreeStatusListOptions) => Promise<WorktreeStatusView[]>;
|
|
5078
|
+
interface ToWorktreeStatusViewOptions {
|
|
5079
|
+
/** Web URL of PR `number` on this repo's forge, when the host can build
|
|
5080
|
+
* one (e.g. from a GitHub `origin` remote). */
|
|
5081
|
+
prUrl?: (number: number) => string | undefined;
|
|
5082
|
+
}
|
|
3874
5083
|
/**
|
|
3875
5084
|
* Pure projection from a raw `WorktreeStatusEntry` to the view the daemon
|
|
3876
5085
|
* surfaces. Kept in one place so the MCP tool and HTTP route cannot drift.
|
|
3877
5086
|
*/
|
|
3878
|
-
declare function toWorktreeStatusView(entry: unknown): WorktreeStatusView;
|
|
5087
|
+
declare function toWorktreeStatusView(entry: unknown, options?: ToWorktreeStatusViewOptions): WorktreeStatusView;
|
|
3879
5088
|
|
|
3880
5089
|
/**
|
|
3881
5090
|
* Shared kernel for the daemon's worktree-`gc` surface — the transport twin of
|
|
@@ -3910,7 +5119,7 @@ type WorktreeGcClass = "reclaim" | "salvage" | "hold";
|
|
|
3910
5119
|
* registration is still there, but the working directory is gone (see
|
|
3911
5120
|
* `WorktreeGcPlanEntryView.prunable`).
|
|
3912
5121
|
*/
|
|
3913
|
-
type WorktreeGcReclaimReason = "dep-bump" | "orphan" | "prunable";
|
|
5122
|
+
type WorktreeGcReclaimReason = "dep-bump" | "orphan" | "prunable" | "squash-merged" | "patch-merged" | "content-merged";
|
|
3914
5123
|
/**
|
|
3915
5124
|
* One entry of the dry-run plan — a runtime-local projection of a
|
|
3916
5125
|
* `GcPlanEntry`. `tree` / `integration` / `liveness` are flattened to their
|
|
@@ -4002,6 +5211,13 @@ interface WorktreeGcRunInput {
|
|
|
4002
5211
|
* command) omits it and gets exactly today's behavior.
|
|
4003
5212
|
*/
|
|
4004
5213
|
protectedPaths?: string[];
|
|
5214
|
+
/**
|
|
5215
|
+
* Worktree-relative paths whose dirt is known noise: a worktree dirty ONLY
|
|
5216
|
+
* on these reads clean (and is removed non-force after they are restored).
|
|
5217
|
+
* Omitted → the engine's default (`.opencode/package-lock.json`); `[]`
|
|
5218
|
+
* disables the allowlist.
|
|
5219
|
+
*/
|
|
5220
|
+
noisePaths?: string[];
|
|
4005
5221
|
}
|
|
4006
5222
|
/**
|
|
4007
5223
|
* Injected port: the runtime asks the host to plan (and, when `apply`, execute)
|
|
@@ -4011,6 +5227,161 @@ interface WorktreeGcRunInput {
|
|
|
4011
5227
|
*/
|
|
4012
5228
|
type WorktreeGcRunner = (input: WorktreeGcRunInput) => Promise<WorktreeGcResult>;
|
|
4013
5229
|
|
|
5230
|
+
/**
|
|
5231
|
+
* Shared kernel for the daemon's branch-`gc` surface — the sibling of
|
|
5232
|
+
* `worktree-gc.ts`. The engine (`planBranchGc` / `applyBranchGc` /
|
|
5233
|
+
* `recordBranchVerdict`) lives in `@agentproto/worktree`, a dependency the
|
|
5234
|
+
* runtime deliberately does NOT take, so — exactly as the worktree gc surface
|
|
5235
|
+
* does — the runtime only defines runtime-local structural result types and
|
|
5236
|
+
* the injected ports. The CLI host wires the real runners. Repo-root
|
|
5237
|
+
* resolution is shared with the worktree surfaces via
|
|
5238
|
+
* `resolveWorktreeQueryRoot` in `worktree-status.ts`.
|
|
5239
|
+
*/
|
|
5240
|
+
type BranchGcKind = "local" | "remote" | "orphan";
|
|
5241
|
+
type BranchGcClass = "reclaim" | "review" | "hold";
|
|
5242
|
+
type BranchGcStatus = "pr-merged" | "merged" | "squash-merged" | "patch-merged" | "content-merged" | "unmerged";
|
|
5243
|
+
type BranchGcReclaimReason = Exclude<BranchGcStatus, "unmerged"> | "reviewed";
|
|
5244
|
+
type BranchGcHoldReason = "protected" | "worktree" | "open-pr" | "pr-check-unavailable" | "young";
|
|
5245
|
+
type BranchGcTriageVerdict = "obsolete" | "superseded" | "salvage" | "in-progress" | "unclear";
|
|
5246
|
+
interface BranchGcCoverageView {
|
|
5247
|
+
residual: number;
|
|
5248
|
+
covered: number;
|
|
5249
|
+
ignored: number;
|
|
5250
|
+
evolved: number;
|
|
5251
|
+
unique: number;
|
|
5252
|
+
deletes: number;
|
|
5253
|
+
}
|
|
5254
|
+
/** One classified ref — mirrors `BranchGcPlanEntry` structurally. */
|
|
5255
|
+
interface BranchGcPlanEntryView {
|
|
5256
|
+
kind: BranchGcKind;
|
|
5257
|
+
name: string;
|
|
5258
|
+
ref: string;
|
|
5259
|
+
sha: string;
|
|
5260
|
+
date: string;
|
|
5261
|
+
author: string;
|
|
5262
|
+
subject: string;
|
|
5263
|
+
remote?: string;
|
|
5264
|
+
status: BranchGcStatus;
|
|
5265
|
+
history: "current" | "pre-rewrite" | "unrelated";
|
|
5266
|
+
ahead: number | null;
|
|
5267
|
+
behind: number | null;
|
|
5268
|
+
conflicts?: boolean;
|
|
5269
|
+
compareBase?: string;
|
|
5270
|
+
mergeBase?: string;
|
|
5271
|
+
mergedTree?: string | null;
|
|
5272
|
+
coverage?: BranchGcCoverageView;
|
|
5273
|
+
residualFiles?: string[];
|
|
5274
|
+
residualFileCount?: number;
|
|
5275
|
+
ageDays: number;
|
|
5276
|
+
class: BranchGcClass;
|
|
5277
|
+
reclaimReason?: BranchGcReclaimReason;
|
|
5278
|
+
mergedPr?: number;
|
|
5279
|
+
holdReason?: BranchGcHoldReason;
|
|
5280
|
+
holdDetail?: string;
|
|
5281
|
+
pushed?: string;
|
|
5282
|
+
verdict?: {
|
|
5283
|
+
triage: BranchGcTriageVerdict;
|
|
5284
|
+
agree: boolean | null;
|
|
5285
|
+
reviewer: string;
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
interface BranchGcPlanView {
|
|
5289
|
+
repoRoot: string;
|
|
5290
|
+
repoName: string;
|
|
5291
|
+
base: string;
|
|
5292
|
+
baseSha: string;
|
|
5293
|
+
baseTree: string;
|
|
5294
|
+
remote: string | null;
|
|
5295
|
+
anchor: string | null;
|
|
5296
|
+
prCheck: {
|
|
5297
|
+
available: boolean;
|
|
5298
|
+
reason?: string;
|
|
5299
|
+
};
|
|
5300
|
+
scopes: BranchGcKind[];
|
|
5301
|
+
minAgeDays: number;
|
|
5302
|
+
includeReviewed: boolean;
|
|
5303
|
+
generatedAt: string;
|
|
5304
|
+
otherRemoteRefs: number;
|
|
5305
|
+
/** true = base-remote `fetch --prune` succeeded; false = failed; null = skipped / no remote. */
|
|
5306
|
+
fetched: boolean | null;
|
|
5307
|
+
entries: BranchGcPlanEntryView[];
|
|
5308
|
+
}
|
|
5309
|
+
/** kind → class → count, and kind → (status | "protected") → count. */
|
|
5310
|
+
interface BranchGcSummaryView {
|
|
5311
|
+
byClass: Record<BranchGcKind, Record<BranchGcClass, number>>;
|
|
5312
|
+
byStatus: Record<BranchGcKind, Record<string, number>>;
|
|
5313
|
+
}
|
|
5314
|
+
interface BranchGcOutcomeView {
|
|
5315
|
+
kind: BranchGcKind;
|
|
5316
|
+
name: string;
|
|
5317
|
+
sha: string;
|
|
5318
|
+
result: "deleted" | "held" | "skipped-review" | "aborted-moved" | "aborted-vanished" | "aborted-reclassified" | "failed";
|
|
5319
|
+
reclaimReason?: BranchGcReclaimReason;
|
|
5320
|
+
holdReason?: BranchGcHoldReason;
|
|
5321
|
+
currentSha?: string;
|
|
5322
|
+
from?: BranchGcClass;
|
|
5323
|
+
to?: BranchGcClass;
|
|
5324
|
+
message?: string;
|
|
5325
|
+
}
|
|
5326
|
+
type BranchGcResult = {
|
|
5327
|
+
mode: "plan";
|
|
5328
|
+
plan: BranchGcPlanView;
|
|
5329
|
+
summary: BranchGcSummaryView;
|
|
5330
|
+
} | {
|
|
5331
|
+
mode: "apply";
|
|
5332
|
+
plan: BranchGcPlanView;
|
|
5333
|
+
summary: BranchGcSummaryView;
|
|
5334
|
+
outcomes: BranchGcOutcomeView[];
|
|
5335
|
+
/** Restore log written before the first delete; `null` when nothing was deleted. */
|
|
5336
|
+
restoreLog: string | null;
|
|
5337
|
+
};
|
|
5338
|
+
/** Input to the injected runner. `apply` defaults to false at the tool/route
|
|
5339
|
+
* boundary — a bare call is a dry run — and an apply requires `scopes`. */
|
|
5340
|
+
interface BranchGcRunInput {
|
|
5341
|
+
repoRoot: string;
|
|
5342
|
+
apply: boolean;
|
|
5343
|
+
base?: string;
|
|
5344
|
+
scopes?: BranchGcKind[];
|
|
5345
|
+
minAgeDays?: number;
|
|
5346
|
+
includeReviewed: boolean;
|
|
5347
|
+
anchor?: string;
|
|
5348
|
+
}
|
|
5349
|
+
type BranchGcRunner = (input: BranchGcRunInput) => Promise<BranchGcResult>;
|
|
5350
|
+
interface BranchGcVerdictInput {
|
|
5351
|
+
repoRoot: string;
|
|
5352
|
+
/** Validated by the engine (`branchVerdictSchema`) — the runtime only shapes it. */
|
|
5353
|
+
verdict: unknown;
|
|
5354
|
+
}
|
|
5355
|
+
interface BranchGcVerdictRecordView {
|
|
5356
|
+
repo: string;
|
|
5357
|
+
name: string;
|
|
5358
|
+
sha: string;
|
|
5359
|
+
recordedAt: string;
|
|
5360
|
+
triage: {
|
|
5361
|
+
verdict: BranchGcTriageVerdict;
|
|
5362
|
+
confidence: number;
|
|
5363
|
+
reason: string;
|
|
5364
|
+
salvage?: string;
|
|
5365
|
+
};
|
|
5366
|
+
gate?: {
|
|
5367
|
+
agree: boolean;
|
|
5368
|
+
verdict: BranchGcTriageVerdict;
|
|
5369
|
+
reason: string;
|
|
5370
|
+
evidence: string[];
|
|
5371
|
+
};
|
|
5372
|
+
reviewer: string;
|
|
5373
|
+
}
|
|
5374
|
+
/** Injected port: validate + store one reviewer verdict, keyed by repo + tip sha. Throws on an invalid verdict. */
|
|
5375
|
+
type BranchGcVerdictRecorder = (input: BranchGcVerdictInput) => Promise<BranchGcVerdictRecordView>;
|
|
5376
|
+
interface BranchGcVerdictLookupInput {
|
|
5377
|
+
repoRoot: string;
|
|
5378
|
+
/** Full tip sha — the store's key, together with the repo. */
|
|
5379
|
+
sha: string;
|
|
5380
|
+
}
|
|
5381
|
+
/** Injected port: the stored verdict for this exact tip, or `null` — a read-only
|
|
5382
|
+
* lookup, so a caller can check one tip without re-running a whole `branch_gc` plan. */
|
|
5383
|
+
type BranchGcVerdictReader = (input: BranchGcVerdictLookupInput) => Promise<BranchGcVerdictRecordView | null>;
|
|
5384
|
+
|
|
4014
5385
|
/**
|
|
4015
5386
|
* Pluggable adapter resolver — keeps the runtime package free of any
|
|
4016
5387
|
* @agentproto/cli dep. The host (cli `serve`, playground, embedding
|
|
@@ -4136,12 +5507,10 @@ type AgentAdapterResolver = (slug: string) => Promise<{
|
|
|
4136
5507
|
}): Promise<AgentSessionLike>;
|
|
4137
5508
|
/** Display label for the descriptor's `command` field. */
|
|
4138
5509
|
commandPreview?: string;
|
|
4139
|
-
/** Best-effort per-session usage reader (adapter-specific, e.g. hermes
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
tokensOut?: number;
|
|
4144
|
-
} | null>;
|
|
5510
|
+
/** Best-effort per-session usage reader (adapter-specific, e.g. hermes
|
|
5511
|
+
* state.db, the claude-code transcript JSONL). `ctx` carries the spawn's
|
|
5512
|
+
* cwd + isolated config dir for readers that locate their store by them. */
|
|
5513
|
+
readUsage?: (adapterSessionId: string, ctx?: UsageReadContext) => Promise<UsageReadResult | null>;
|
|
4145
5514
|
/** AIP-45 `options[]` this adapter's manifest declares (id + type only —
|
|
4146
5515
|
* no spawn internals). Lets `session-spawn.ts` fold a config-level
|
|
4147
5516
|
* `defaults.skills` list into `options.skills` using the shape the
|
|
@@ -4319,6 +5688,142 @@ interface AuthOptions {
|
|
|
4319
5688
|
token?: string;
|
|
4320
5689
|
}
|
|
4321
5690
|
|
|
5691
|
+
/** One background task as mirrored onto `SessionDescriptor.backgroundTasks`. */
|
|
5692
|
+
interface SessionBackgroundTask {
|
|
5693
|
+
taskId: string;
|
|
5694
|
+
/** Friendly task type (`"shell"`, `"monitor"`, ...), when the agent says. */
|
|
5695
|
+
taskKind?: string;
|
|
5696
|
+
description?: string;
|
|
5697
|
+
/** Where the task writes its output, when the agent says. */
|
|
5698
|
+
outputFile?: string;
|
|
5699
|
+
status: "running" | "paused" | "completed" | "failed" | "stopped";
|
|
5700
|
+
summary?: string;
|
|
5701
|
+
toolCallId?: string;
|
|
5702
|
+
/** ISO 8601 — when the daemon first heard of the task. */
|
|
5703
|
+
startedAt: string;
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
/**
|
|
5707
|
+
* Typed inter-session message envelope (AIP-46 §Session messages).
|
|
5708
|
+
*
|
|
5709
|
+
* A PROMPT is an instruction from whoever drives a session; a MESSAGE is a
|
|
5710
|
+
* report between sessions in the same tree whose sender the DAEMON attests.
|
|
5711
|
+
* Everything in `from` is computed here from the caller's verified identity
|
|
5712
|
+
* and the session forest — a send request can never set it.
|
|
5713
|
+
*
|
|
5714
|
+
* Pure module: no registry, no I/O. The registry (sessions.ts) carries a
|
|
5715
|
+
* `SessionMessage` on a queued prompt (`QueuedPrompt.envelope`), renders it
|
|
5716
|
+
* into the model's context with `renderSessionMessages`, and records it as
|
|
5717
|
+
* its own `session-message` transcript record — never a `user-prompt`.
|
|
5718
|
+
*/
|
|
5719
|
+
/** Sender's position in the tree, relative to the recipient. */
|
|
5720
|
+
type MessageRelation = "child" | "parent" | "sibling" | "human" | "system";
|
|
5721
|
+
type MessageKind = "report" | "question" | "blocker" | "done" | "notice";
|
|
5722
|
+
type MessageUrgency = "fyi" | "next-turn" | "steer" | "interrupt";
|
|
5723
|
+
type MessageDeliveryVia = "wait" | "steer" | "turn" | "interrupt" | "inbox";
|
|
5724
|
+
declare const MESSAGE_KINDS: readonly MessageKind[];
|
|
5725
|
+
declare const MESSAGE_URGENCIES: readonly MessageUrgency[];
|
|
5726
|
+
interface SessionMessageFrom {
|
|
5727
|
+
/** Absent only for relation `human` | `system`. */
|
|
5728
|
+
sessionId?: string;
|
|
5729
|
+
/** Registry label at send time — display only, never identity. */
|
|
5730
|
+
label?: string;
|
|
5731
|
+
role?: string;
|
|
5732
|
+
adapter?: string;
|
|
5733
|
+
relation: MessageRelation;
|
|
5734
|
+
}
|
|
5735
|
+
interface SessionMessage {
|
|
5736
|
+
/** `msg_<uuid8>`, daemon-minted. */
|
|
5737
|
+
id: string;
|
|
5738
|
+
ts: string;
|
|
5739
|
+
/** Recipient sessionId. */
|
|
5740
|
+
to: string;
|
|
5741
|
+
from: SessionMessageFrom;
|
|
5742
|
+
kind: MessageKind;
|
|
5743
|
+
/** Requested urgency. */
|
|
5744
|
+
urgency: MessageUrgency;
|
|
5745
|
+
/** Stamped on delivery. */
|
|
5746
|
+
delivered?: {
|
|
5747
|
+
via: MessageDeliveryVia;
|
|
5748
|
+
at: string;
|
|
5749
|
+
turnSeq?: number;
|
|
5750
|
+
};
|
|
5751
|
+
/** Thread id; defaults to the first message's id. */
|
|
5752
|
+
correlationId?: string;
|
|
5753
|
+
/** A `SessionMessage.id` this answers. */
|
|
5754
|
+
replyTo?: string;
|
|
5755
|
+
text: string;
|
|
5756
|
+
/** Optional structured payload (≤ `MAX_MESSAGE_DATA_BYTES`). */
|
|
5757
|
+
data?: Record<string, unknown>;
|
|
5758
|
+
ackedAt?: string;
|
|
5759
|
+
}
|
|
5760
|
+
declare const MAX_MESSAGE_DATA_BYTES: number;
|
|
5761
|
+
/** The slice of a session descriptor relation/ACL resolution needs. */
|
|
5762
|
+
interface MessageTreeNode {
|
|
5763
|
+
id: string;
|
|
5764
|
+
parentSessionId?: string;
|
|
5765
|
+
label?: string;
|
|
5766
|
+
role?: string;
|
|
5767
|
+
adapterSlug?: string;
|
|
5768
|
+
}
|
|
5769
|
+
/**
|
|
5770
|
+
* Relation of `sender` to `recipient` in the session forest, or `undefined`
|
|
5771
|
+
* when they're not tree neighbours (cousins, strangers, self). `sender`
|
|
5772
|
+
* absent ⇒ `human` (an HTTP/CLI operator); daemon-originated notices pass
|
|
5773
|
+
* `"system"` explicitly instead of calling this.
|
|
5774
|
+
*/
|
|
5775
|
+
declare function resolveRelation(sender: MessageTreeNode | undefined, recipient: MessageTreeNode): Exclude<MessageRelation, "system"> | undefined;
|
|
5776
|
+
/**
|
|
5777
|
+
* ACL: child→parent, parent→direct child, human/system→any. Sibling↔sibling
|
|
5778
|
+
* only when `allowSiblings` (config-gated, default off). Anything else — a
|
|
5779
|
+
* cousin, a stranger, yourself — is refused.
|
|
5780
|
+
*/
|
|
5781
|
+
declare function isMessageAllowed(relation: MessageRelation | undefined, opts?: {
|
|
5782
|
+
allowSiblings?: boolean;
|
|
5783
|
+
}): relation is MessageRelation;
|
|
5784
|
+
/** Build a `from` block from the verified sender + the resolved relation. */
|
|
5785
|
+
declare function messageFrom(sender: MessageTreeNode | undefined, relation: MessageRelation): SessionMessageFrom;
|
|
5786
|
+
declare function mintMessageId(): string;
|
|
5787
|
+
declare function createSessionMessage(input: {
|
|
5788
|
+
to: string;
|
|
5789
|
+
from: SessionMessageFrom;
|
|
5790
|
+
text: string;
|
|
5791
|
+
kind?: MessageKind;
|
|
5792
|
+
urgency?: MessageUrgency;
|
|
5793
|
+
replyTo?: string;
|
|
5794
|
+
correlationId?: string;
|
|
5795
|
+
data?: Record<string, unknown>;
|
|
5796
|
+
}): SessionMessage;
|
|
5797
|
+
interface MessageFilter {
|
|
5798
|
+
/** Sender session ids, or `"children"` for any message whose relation is
|
|
5799
|
+
* `child` (i.e. from one of the recipient's own children). */
|
|
5800
|
+
from?: readonly string[] | "children";
|
|
5801
|
+
kind?: readonly MessageKind[];
|
|
5802
|
+
correlationId?: string;
|
|
5803
|
+
}
|
|
5804
|
+
declare function matchesMessageFilter(msg: SessionMessage, filter: MessageFilter): boolean;
|
|
5805
|
+
declare const MESSAGE_TAG = "agentproto-message";
|
|
5806
|
+
/** Child-settable label → attribute-safe: `[A-Za-z0-9._-]`, ≤ 48 chars. */
|
|
5807
|
+
declare function sanitizeLabel(label: string | undefined): string | undefined;
|
|
5808
|
+
/** Escape every envelope/body sentinel inside a message body, so a sender
|
|
5809
|
+
* can't close the tag early and forge a second header. */
|
|
5810
|
+
declare function escapeMessageBody(text: string): string;
|
|
5811
|
+
/** A human prompt is NOT wrapped (humans keep today's UX), but a line that
|
|
5812
|
+
* starts with the envelope sentinel is escaped, so the invariant "text
|
|
5813
|
+
* outside an `<agentproto-message>` tag is the human" holds. Nothing else
|
|
5814
|
+
* in human text changes. */
|
|
5815
|
+
declare function escapeHumanPrompt(text: string): string;
|
|
5816
|
+
declare function renderSessionMessage(msg: SessionMessage): string;
|
|
5817
|
+
/** A coalesced batch → sibling tags, one per message, in order. */
|
|
5818
|
+
declare function renderSessionMessages(msgs: readonly SessionMessage[]): string;
|
|
5819
|
+
/** One-line typed digest of `fyi` messages that never woke the session —
|
|
5820
|
+
* prepended (as a `system-prompt` slice) to the next turn it runs anyway. */
|
|
5821
|
+
declare function renderInboxDigest(msgs: readonly SessionMessage[]): string;
|
|
5822
|
+
/** Taught once, the first time a session RECEIVES a message (recorded as a
|
|
5823
|
+
* `system-prompt`). Children also get the same invariant in their lineage
|
|
5824
|
+
* line at spawn. */
|
|
5825
|
+
declare const MESSAGE_PREAMBLE: string;
|
|
5826
|
+
|
|
4322
5827
|
/**
|
|
4323
5828
|
* A per-session observer — the pluggable seam behind the runtime's transcript
|
|
4324
5829
|
* tap. `sessions.ts` drives one observer per turn (prompt in, each stream event,
|
|
@@ -4330,15 +5835,33 @@ interface AuthOptions {
|
|
|
4330
5835
|
* calls, so `TranscriptWriter` IS a `SessionObserver` (see transcript-writer.ts).
|
|
4331
5836
|
*/
|
|
4332
5837
|
|
|
5838
|
+
/** The sender-side trace of a typed message (`session-message-sent`). */
|
|
5839
|
+
interface SessionMessageSentRecord {
|
|
5840
|
+
messageId: string;
|
|
5841
|
+
to: string;
|
|
5842
|
+
kind: MessageKind;
|
|
5843
|
+
urgency: MessageUrgency;
|
|
5844
|
+
}
|
|
4333
5845
|
interface SessionObserver {
|
|
4334
5846
|
/** Record the outgoing message that opens a new turn. `opts.source`, when
|
|
4335
5847
|
* set, is the prompt's provenance — `agent:<sessionId>` for a prompt
|
|
4336
5848
|
* injected by another session (`agent_prompt` from a supervisor), absent
|
|
4337
5849
|
* for a human operator — so a transcript view can attribute the turn to
|
|
4338
|
-
* its real author instead of "you".
|
|
5850
|
+
* its real author instead of "you". `opts.messages`, when set, marks a
|
|
5851
|
+
* typed-message turn: the writer records one `session-message` per
|
|
5852
|
+
* message instead of a `user-prompt`. */
|
|
4339
5853
|
recordPrompt(sessionId: string, message: unknown, opts?: {
|
|
4340
5854
|
source?: string;
|
|
5855
|
+
system?: string;
|
|
5856
|
+
messages?: readonly SessionMessage[];
|
|
4341
5857
|
}): void;
|
|
5858
|
+
/** Record, in the SENDER's transcript, that it sent a typed message.
|
|
5859
|
+
* Optional — observers without a transcript of their own skip it. */
|
|
5860
|
+
recordSessionMessageSent?(sessionId: string, record: SessionMessageSentRecord): void;
|
|
5861
|
+
/** Record a typed message delivered into `sessionId`'s context OUTSIDE a
|
|
5862
|
+
* turn prompt (e.g. as an `inbox_wait` result) — a standalone
|
|
5863
|
+
* `session-message` record. Optional, like `recordSessionMessageSent`. */
|
|
5864
|
+
recordSessionMessage?(sessionId: string, message: SessionMessage): void;
|
|
4342
5865
|
/** Record one structured stream event (text-delta, tool-call, usage_update, …). */
|
|
4343
5866
|
recordEvent(sessionId: string, evt: AgentStreamEvent): void;
|
|
4344
5867
|
/** Record the durable turn-boundary / exit usage snapshot. */
|
|
@@ -4361,6 +5884,106 @@ interface SessionObserver {
|
|
|
4361
5884
|
*/
|
|
4362
5885
|
declare function composeSessionObservers(observers: readonly SessionObserver[]): SessionObserver;
|
|
4363
5886
|
|
|
5887
|
+
/** Where a session's `contextSize` came from — see the module doc. */
|
|
5888
|
+
type ContextSizeSource = "adapter" | "catalog" | "reported";
|
|
5889
|
+
|
|
5890
|
+
/**
|
|
5891
|
+
* Derived session outcome (Level 1) — what an ended agent-cli session
|
|
5892
|
+
* PRODUCED, recorded by the daemon with zero agent cooperation.
|
|
5893
|
+
*
|
|
5894
|
+
* Two axes, never conflated:
|
|
5895
|
+
* - termination — HOW the session stopped (the descriptor's existing
|
|
5896
|
+
* `status` + `endedReason`), copied verbatim into `outcome.termination`;
|
|
5897
|
+
* - outcome — WHAT it produced: its last assistant message, the PRs it
|
|
5898
|
+
* opened, what it cost, and which run / parent it belongs to.
|
|
5899
|
+
*
|
|
5900
|
+
* So a killed or idle-reaped session still shows what it said and did,
|
|
5901
|
+
* instead of a bare "killed".
|
|
5902
|
+
*
|
|
5903
|
+
* Pure by construction (plus one bounded sync tail-read helper for boot
|
|
5904
|
+
* reconcile): no LLM call, no full transcript scan. The registry calls
|
|
5905
|
+
* {@link deriveSessionOutcome} from its single exit funnel (`emitExited`)
|
|
5906
|
+
* and from boot reconcile (`loadHistorySnapshot`).
|
|
5907
|
+
*/
|
|
5908
|
+
|
|
5909
|
+
interface SessionOutcomeArtifact {
|
|
5910
|
+
type: "pr" | "commit" | "url";
|
|
5911
|
+
/** The artifact's canonical reference — a PR url, a commit sha, a url. */
|
|
5912
|
+
ref: string;
|
|
5913
|
+
title?: string;
|
|
5914
|
+
}
|
|
5915
|
+
interface SessionOutcomeLink {
|
|
5916
|
+
/** `run` — the workflow run this session was a step of (`ref` = run id,
|
|
5917
|
+
* `title` = `<workflowId>/<stepId>`); `parent` — the orchestrator session
|
|
5918
|
+
* that spawned it; `review` — reserved for the review ledger (Level 2). */
|
|
5919
|
+
rel: "run" | "parent" | "review";
|
|
5920
|
+
ref: string;
|
|
5921
|
+
title?: string;
|
|
5922
|
+
}
|
|
5923
|
+
interface SessionOutcome {
|
|
5924
|
+
/** Level 1 only ever writes `"derived"`; a declared outcome (Level 2) is a
|
|
5925
|
+
* later addition. */
|
|
5926
|
+
source: "derived";
|
|
5927
|
+
/** `produced` — the session said something or left an artifact;
|
|
5928
|
+
* `empty` — no assistant text and no artifacts. */
|
|
5929
|
+
status: "produced" | "empty";
|
|
5930
|
+
/** Last assistant message of the session, trimmed to
|
|
5931
|
+
* {@link OUTCOME_SUMMARY_MAX} chars (the TAIL is kept — the conclusion,
|
|
5932
|
+
* not the preamble). Absent when the session never said anything. */
|
|
5933
|
+
summary?: string;
|
|
5934
|
+
/** Copy of the descriptor's end state at the moment the outcome was
|
|
5935
|
+
* recorded — the termination axis, never mixed into `status`. */
|
|
5936
|
+
termination: {
|
|
5937
|
+
status: string;
|
|
5938
|
+
/** Copy of `SessionDescriptor.endedReason` — see {@link SessionEndReason}
|
|
5939
|
+
* for the full, single-source enum of values (operator kill, cost cap,
|
|
5940
|
+
* provider limit, …). Absent for a plain natural exit or an ordinary
|
|
5941
|
+
* turn error this file doesn't tag. A value outside the known enum is
|
|
5942
|
+
* still valid (a newer/older daemon) — never gate rendering on it being
|
|
5943
|
+
* a member; see `isKnownSessionEndReason`. */
|
|
5944
|
+
reason?: SessionEndReason;
|
|
5945
|
+
/** The reason this row carried BEFORE an operator's "mark as completed"
|
|
5946
|
+
* (`kill(id, signal, "operator-completed")` on an already-terminal
|
|
5947
|
+
* session — the UI's affordance for relabeling a finished-but-not-
|
|
5948
|
+
* operator-tagged row) overwrote `reason` with `"operator-completed"`.
|
|
5949
|
+
* Set ONLY by that relabel, so the true original mechanism (a natural
|
|
5950
|
+
* exit with no reason, `"crashed"`, `"idle-reaped"`, …) is never lost
|
|
5951
|
+
* under the operator's override. Absent otherwise. */
|
|
5952
|
+
previousReason?: SessionEndReason;
|
|
5953
|
+
exitCode?: number;
|
|
5954
|
+
/** True when the session was killed with a turn in flight. */
|
|
5955
|
+
midTurn?: boolean;
|
|
5956
|
+
};
|
|
5957
|
+
cost?: {
|
|
5958
|
+
usd?: number;
|
|
5959
|
+
tokensIn?: number;
|
|
5960
|
+
tokensOut?: number;
|
|
5961
|
+
durationMs?: number;
|
|
5962
|
+
};
|
|
5963
|
+
artifacts?: SessionOutcomeArtifact[];
|
|
5964
|
+
links?: SessionOutcomeLink[];
|
|
5965
|
+
recordedAt: string;
|
|
5966
|
+
}
|
|
5967
|
+
/** The compact list projection of an outcome (`session_list` default). */
|
|
5968
|
+
interface SessionOutcomeCompact {
|
|
5969
|
+
status: SessionOutcome["status"];
|
|
5970
|
+
summary?: string;
|
|
5971
|
+
}
|
|
5972
|
+
interface DeriveSessionOutcomeInput {
|
|
5973
|
+
/** The last assistant message the registry observed (raw, untrimmed). */
|
|
5974
|
+
lastAssistantText?: string;
|
|
5975
|
+
now?: Date;
|
|
5976
|
+
}
|
|
5977
|
+
/**
|
|
5978
|
+
* Build the derived outcome for a session that has just reached a terminal
|
|
5979
|
+
* status. Reads only the descriptor + the caller-supplied last assistant
|
|
5980
|
+
* text; never touches disk.
|
|
5981
|
+
*/
|
|
5982
|
+
declare function deriveSessionOutcome(desc: SessionDescriptor, input?: DeriveSessionOutcomeInput): SessionOutcome;
|
|
5983
|
+
/** The compact projection: status + the first
|
|
5984
|
+
* {@link OUTCOME_COMPACT_SUMMARY_MAX} chars of the summary. */
|
|
5985
|
+
declare function compactOutcome(o: SessionOutcome | undefined): SessionOutcomeCompact | undefined;
|
|
5986
|
+
|
|
4364
5987
|
/**
|
|
4365
5988
|
* Minimal shape we need from a driver-agent-cli session — kept as a
|
|
4366
5989
|
* structural type so the runtime package doesn't take a hard
|
|
@@ -4384,6 +6007,12 @@ interface AgentSessionLike {
|
|
|
4384
6007
|
* drivers that don't model held permissions (sandbox proxy, future
|
|
4385
6008
|
* transports). */
|
|
4386
6009
|
respondPermission?(requestId: string, resolution: AcpPermissionResolution): boolean | Promise<boolean>;
|
|
6010
|
+
/** Subscribe to events the agent emits while no turn is in flight — its
|
|
6011
|
+
* own wake-up when a background task settles, and the task lifecycle
|
|
6012
|
+
* itself. Mirrors `@agentproto/driver-agent-cli`'s
|
|
6013
|
+
* `AgentCliRuntimeSession.onOutOfTurnEvent`. Returns an unsubscribe
|
|
6014
|
+
* function. Absent for drivers that can't produce such events. */
|
|
6015
|
+
onOutOfTurnEvent?(listener: (event: AgentStreamEvent) => void): () => void;
|
|
4387
6016
|
/**
|
|
4388
6017
|
* Switch the active model on this LIVE session — mirrors
|
|
4389
6018
|
* `@agentproto/driver-agent-cli`'s `AgentCliRuntimeSession.setModel`
|
|
@@ -4420,6 +6049,15 @@ interface AgentSessionLike {
|
|
|
4420
6049
|
* routes to restart.
|
|
4421
6050
|
*/
|
|
4422
6051
|
readonly availableModes?: readonly SessionMode[];
|
|
6052
|
+
/** Whether the agent accepts steering — mirrors
|
|
6053
|
+
* `@agentproto/driver-agent-cli`'s `AgentCliRuntimeSession.steeringSupported`
|
|
6054
|
+
* (ACP `_session/steering`, advertised at initialize). */
|
|
6055
|
+
readonly steeringSupported?: boolean;
|
|
6056
|
+
/** Inject content into the turn in flight — mirrors
|
|
6057
|
+
* `AgentCliRuntimeSession.steer`: `"steered"` (injected), `"promptRequired"`
|
|
6058
|
+
* (no host turn in flight — deliver as a normal prompt), `"unsupported"`.
|
|
6059
|
+
* Never throws. Absent for sessions that can't steer. */
|
|
6060
|
+
steer?(content: unknown): Promise<"steered" | "promptRequired" | "unsupported">;
|
|
4423
6061
|
close(): Promise<void>;
|
|
4424
6062
|
}
|
|
4425
6063
|
/** Result of `SessionsRegistry.setModel` — see that method's doc comment. */
|
|
@@ -4586,6 +6224,16 @@ interface AgentStreamEvent {
|
|
|
4586
6224
|
* but no `cost`). */
|
|
4587
6225
|
tokensIn?: number;
|
|
4588
6226
|
tokensOut?: number;
|
|
6227
|
+
/** "usage_update" model the usage belongs to, when the adapter reports it
|
|
6228
|
+
* (claude-agent-acp's `_meta["_claude/model"]`) — see `context-window.ts`. */
|
|
6229
|
+
model?: string;
|
|
6230
|
+
/** "usage_update" `size` is the adapter's own guess, corrected later by a
|
|
6231
|
+
* cost-bearing frame (claude-agent-acp) — see `context-window.ts`. */
|
|
6232
|
+
sizeInferred?: boolean;
|
|
6233
|
+
/** "usage_update" size as the adapter reported it, recorded only when the
|
|
6234
|
+
* daemon corrected `size` (an inferred window superseded by the catalog or
|
|
6235
|
+
* an earlier authoritative frame) — see `context-window.ts`. */
|
|
6236
|
+
reportedSize?: number;
|
|
4589
6237
|
/** "permission-resolved" outcome for the "agent-prompt" it answers (same
|
|
4590
6238
|
* `toolCallId`) — mirrors `session:permission-resolved`'s `decision` so
|
|
4591
6239
|
* the durable transcript can tell an answered ask from a still-pending
|
|
@@ -4612,6 +6260,24 @@ interface AgentStreamEvent {
|
|
|
4612
6260
|
qualifiedName?: string;
|
|
4613
6261
|
};
|
|
4614
6262
|
}>;
|
|
6263
|
+
/** "background-task" lifecycle edge — see @agentproto/acp's `StreamEvent`'s
|
|
6264
|
+
* `background-task` kind. */
|
|
6265
|
+
phase?: "started" | "updated" | "settled";
|
|
6266
|
+
/** "background-task" task snapshot — see @agentproto/acp's
|
|
6267
|
+
* `BackgroundTaskInfo`. Only `taskId` is guaranteed. */
|
|
6268
|
+
task?: {
|
|
6269
|
+
taskId: string;
|
|
6270
|
+
taskKind?: string;
|
|
6271
|
+
description?: string;
|
|
6272
|
+
outputFile?: string;
|
|
6273
|
+
status?: SessionBackgroundTask["status"];
|
|
6274
|
+
summary?: string;
|
|
6275
|
+
toolCallId?: string;
|
|
6276
|
+
};
|
|
6277
|
+
/** "usage_update" origin of the cycle it closes (claude-agent-acp's
|
|
6278
|
+
* `_meta["_claude/origin"].kind`, e.g. `"task-notification"` for an
|
|
6279
|
+
* autonomous wake) — see @agentproto/acp's `StreamEvent`. */
|
|
6280
|
+
origin?: string;
|
|
4615
6281
|
}
|
|
4616
6282
|
/**
|
|
4617
6283
|
* Env vars the registry injects into every process it spawns on a session's
|
|
@@ -4732,6 +6398,13 @@ interface QueuedPrompt {
|
|
|
4732
6398
|
* daemon restart. Absent for legacy queued items — `promptOriginLabel`
|
|
4733
6399
|
* falls back to `source`, then `"user"`. */
|
|
4734
6400
|
origin?: string;
|
|
6401
|
+
/** Set when this item is a typed inter-session message (`message_parent`,
|
|
6402
|
+
* …) rather than a prompt: the daemon-attested envelope. `message` then
|
|
6403
|
+
* only carries `envelope.text` (for `previewPrompt`); the turn is built
|
|
6404
|
+
* from the envelope — rendered as an `<agentproto-message>` tag, recorded
|
|
6405
|
+
* as a `session-message` transcript record, and coalesced with any
|
|
6406
|
+
* envelope items queued right behind it (never with a plain prompt). */
|
|
6407
|
+
envelope?: SessionMessage;
|
|
4735
6408
|
}
|
|
4736
6409
|
/** What `enqueuePrompt` resolved to — lets a caller (e.g. MCP `agent_prompt`)
|
|
4737
6410
|
* tell the user whether the prompt was delivered/started now or PARKED behind
|
|
@@ -4743,6 +6416,22 @@ interface QueuedPrompt {
|
|
|
4743
6416
|
interface EnqueuePromptResult {
|
|
4744
6417
|
queued: boolean;
|
|
4745
6418
|
}
|
|
6419
|
+
/** What `sendMessage` did with a message — truthful, per AIP-46. */
|
|
6420
|
+
interface SendMessageResult {
|
|
6421
|
+
messageId: string;
|
|
6422
|
+
/** Set when the message already reached the recipient's context (a
|
|
6423
|
+
* waiter, a dispatched turn, an interrupt) or was parked as `fyi`
|
|
6424
|
+
* (`via: "inbox"`); `null` while it's queued behind a busy turn. */
|
|
6425
|
+
delivered: {
|
|
6426
|
+
via: MessageDeliveryVia;
|
|
6427
|
+
} | null;
|
|
6428
|
+
queued: boolean;
|
|
6429
|
+
urgencyApplied: MessageUrgency;
|
|
6430
|
+
}
|
|
6431
|
+
interface WaitForMessagesResult {
|
|
6432
|
+
messages: SessionMessage[];
|
|
6433
|
+
timedOut: boolean;
|
|
6434
|
+
}
|
|
4746
6435
|
/** One entry in the after-the-fact queue listing (`listQueuedPrompts` /
|
|
4747
6436
|
* `session_queue_list` / `GET /sessions/:id/queue`). `position` is the
|
|
4748
6437
|
* array index (0 = next to dispatch). */
|
|
@@ -4756,6 +6445,17 @@ interface QueuedPromptView {
|
|
|
4756
6445
|
queuedAt: string;
|
|
4757
6446
|
position: number;
|
|
4758
6447
|
}
|
|
6448
|
+
/** Provenance stamped onto a `session_continue_fresh` target by
|
|
6449
|
+
* {@link SessionDescriptor.handoff} — which harness the checkpoint moved
|
|
6450
|
+
* from/to and when. `fromHarness === toHarness` for a same-harness
|
|
6451
|
+
* continuation; they differ for a genuine cross-harness handoff
|
|
6452
|
+
* (claude-code -> opencode, etc). */
|
|
6453
|
+
interface SessionHandoff {
|
|
6454
|
+
fromHarness: string;
|
|
6455
|
+
toHarness: string;
|
|
6456
|
+
/** ISO 8601 timestamp the handoff spawn completed. */
|
|
6457
|
+
at: string;
|
|
6458
|
+
}
|
|
4759
6459
|
interface SessionDescriptor {
|
|
4760
6460
|
id: string;
|
|
4761
6461
|
kind: SessionKind;
|
|
@@ -4778,30 +6478,36 @@ interface SessionDescriptor {
|
|
|
4778
6478
|
startedAt: string;
|
|
4779
6479
|
endedAt?: string;
|
|
4780
6480
|
exitCode?: number;
|
|
4781
|
-
/**
|
|
4782
|
-
*
|
|
4783
|
-
*
|
|
4784
|
-
*
|
|
4785
|
-
*
|
|
4786
|
-
*
|
|
4787
|
-
*
|
|
4788
|
-
*
|
|
4789
|
-
*
|
|
4790
|
-
* lazy-resumable
|
|
4791
|
-
*
|
|
4792
|
-
* `
|
|
4793
|
-
*
|
|
4794
|
-
*
|
|
4795
|
-
*
|
|
4796
|
-
*
|
|
4797
|
-
*
|
|
4798
|
-
*
|
|
4799
|
-
*
|
|
4800
|
-
*
|
|
4801
|
-
*
|
|
4802
|
-
*
|
|
4803
|
-
*
|
|
4804
|
-
|
|
6481
|
+
/** WHY the session ended — every stable value + its meaning is declared
|
|
6482
|
+
* once, in {@link SessionEndReason} (`session-end-reason.ts`), so a UI can
|
|
6483
|
+
* render a label for each without chasing the call site that set it.
|
|
6484
|
+
* Three families:
|
|
6485
|
+
* - automatic daemon-lifecycle teardown (unchanged, pre-existing):
|
|
6486
|
+
* `"daemon-restart"` (the daemon process it lived in went away out from
|
|
6487
|
+
* under it — a hard crash discovered at next boot, or a graceful
|
|
6488
|
+
* shutdown/restart that force-kills whatever's still busy),
|
|
6489
|
+
* `"idle-reaped"` (the idle-session reaper retired a long-idle row —
|
|
6490
|
+
* deliberately kept lazy-resumable but NEVER eager-resumed on boot,
|
|
6491
|
+
* since the eager pass gates on `endedReason === "daemon-restart"`),
|
|
6492
|
+
* `"crashed"` (the crash-detect sweep found the adapter's OS process
|
|
6493
|
+
* gone between turns with no exit event ever emitted for it — `status`
|
|
6494
|
+
* is `"error"`, not `"killed"`, since nothing targeted it);
|
|
6495
|
+
* - operator-issued kill: `"operator-completed"` / `"operator-stopped"`,
|
|
6496
|
+
* set ONLY by an explicit `POST /sessions/:id/kill` or the `agent_kill`
|
|
6497
|
+
* MCP tool (never by an internal automatic teardown), so it reads as
|
|
6498
|
+
* deliberate rather than random;
|
|
6499
|
+
* - other internal/automatic reasons: `"cost-cap-exceeded"` (the
|
|
6500
|
+
* turn-granular `maxCostUsd` cap tripped), `"policy-cleanup"` (a
|
|
6501
|
+
* supervisor gate tore down its own ephemeral judge session),
|
|
6502
|
+
* `"parent-exited"` (an orchestrator's dying subtree reap),
|
|
6503
|
+
* `"provider-limit"` (a driver-reported subscription/usage-cap error,
|
|
6504
|
+
* e.g. Claude Code's "hit your session limit"), `"forgotten"` (a live
|
|
6505
|
+
* session killed as part of an operator `DELETE`).
|
|
6506
|
+
* Absent for every terminal path this file doesn't tag (a plain natural
|
|
6507
|
+
* exit, an ordinary turn error) — the session's own fault, or at least
|
|
6508
|
+
* not something worth a special label. A string outside this list is
|
|
6509
|
+
* still valid (a newer/older daemon) — see `isKnownSessionEndReason`. */
|
|
6510
|
+
endedReason?: SessionEndReason;
|
|
4805
6511
|
/** Whether a turn was actually in flight the INSTANT `status` flipped to
|
|
4806
6512
|
* "killed" — captured before anything else runs, because `busy` itself
|
|
4807
6513
|
* cannot be trusted after the fact: `runAgentTurn`'s `finally` is what
|
|
@@ -4819,6 +6525,16 @@ interface SessionDescriptor {
|
|
|
4819
6525
|
* `kill()`, `shutdownImpl`'s force-kill, and `loadHistorySnapshot`'s
|
|
4820
6526
|
* wasAlive reclassification; absent for every other terminal path. */
|
|
4821
6527
|
killedMidTurn?: boolean;
|
|
6528
|
+
/** What this session PRODUCED — the derived outcome (Level 1) the daemon
|
|
6529
|
+
* records once it reaches a terminal status, alongside (never instead of)
|
|
6530
|
+
* the termination fields above: last assistant message, opened PRs, cost,
|
|
6531
|
+
* run/parent links. agent-cli only. Recorded from the one exit funnel
|
|
6532
|
+
* (`emitExited`) and from boot reconcile for rows that died with the
|
|
6533
|
+
* daemon; first write wins unless a later one is for a new death or is
|
|
6534
|
+
* richer (see `shouldReplaceOutcome`). Dropped when an in-place resume
|
|
6535
|
+
* revives the row. Persisted, so it survives restarts and archiving. See
|
|
6536
|
+
* `session-outcome.ts`. */
|
|
6537
|
+
outcome?: SessionOutcome;
|
|
4822
6538
|
/** Last time anything was written to stdout/stderr. Lets the UI
|
|
4823
6539
|
* spot stuck sessions ("running for 2h, last output 12min ago"). */
|
|
4824
6540
|
lastOutputAt?: string;
|
|
@@ -4926,10 +6642,23 @@ interface SessionDescriptor {
|
|
|
4926
6642
|
* session sits `busy:false`, `awaitingInput:false`, background tasks
|
|
4927
6643
|
* pending — a silent dead end. Detection + signal ONLY (same doctrine
|
|
4928
6644
|
* as `stalledSinceMs`): nothing here re-prompts or wakes the session.
|
|
6645
|
+
* Not stamped for an agent that reports its background-task lifecycle
|
|
6646
|
+
* (see `backgroundTasks`) — that one is tracked and woken for real.
|
|
4929
6647
|
* Cleared (deleted, never a stale value) on the next turn start and on
|
|
4930
6648
|
* session exit. Emits `session:bg-tasks-parked` /
|
|
4931
6649
|
* `session:bg-tasks-cleared`. */
|
|
4932
6650
|
pendingBgTasks?: number;
|
|
6651
|
+
/** The agent's background tasks that are still running (a backgrounded
|
|
6652
|
+
* Bash command, a monitor, ...), as the agent itself reports them — over
|
|
6653
|
+
* ACP, the AIR `asyncTasks` lifecycle. Unlike `pendingBgTasks` (a
|
|
6654
|
+
* turn-end heuristic) this is the real set: a task is added when it
|
|
6655
|
+
* starts and removed when it settles, so an idle session with a
|
|
6656
|
+
* non-empty list is "waiting on N background tasks", not parked. When
|
|
6657
|
+
* one settles while the session is idle the registry makes sure the
|
|
6658
|
+
* agent is woken (see `SessionsRegistryOptions.backgroundTaskWake`).
|
|
6659
|
+
* Absent when none are running; dropped on exit and on respawn (a new
|
|
6660
|
+
* process owns none of the old one's tasks). */
|
|
6661
|
+
backgroundTasks?: SessionBackgroundTask[];
|
|
4933
6662
|
/** ISO 8601 timestamp of the last turn that ended because the ADAPTER
|
|
4934
6663
|
* ITSELF reported a failure — `runAgentTurn` observed a `turn-end` event
|
|
4935
6664
|
* with `reason:"error"` (session-event-bus.ts's `SessionTurnEndEvent`,
|
|
@@ -4938,16 +6667,32 @@ interface SessionDescriptor {
|
|
|
4938
6667
|
* `status` to `"error"` directly (a genuinely terminal row the existing
|
|
4939
6668
|
* crash/error handling already catches). This is the twin gap for a
|
|
4940
6669
|
* turn that fails IN-BAND while the process stays alive: `status` stays
|
|
4941
|
-
* `"running"`, `lastError` stays unset (reserved for `markCrashed`
|
|
4942
|
-
*
|
|
4943
|
-
*
|
|
4944
|
-
*
|
|
4945
|
-
*
|
|
4946
|
-
*
|
|
4947
|
-
*
|
|
4948
|
-
*
|
|
6670
|
+
* `"running"`, `lastError` stays unset (still reserved for `markCrashed`
|
|
6671
|
+
* — see `lastTurnErrorMessage` below for the message text this field's
|
|
6672
|
+
* own turn carries), and nothing else distinguishes the session from
|
|
6673
|
+
* one that simply finished a clean turn and is idle. Folded into the
|
|
6674
|
+
* same "stalled" activity/UI treatment a mid-turn stall gets (see
|
|
6675
|
+
* sessionsTree.logic.ts's `activityFor`) rather than a new state, on
|
|
6676
|
+
* the theory that both mean "the last thing this session did needs a
|
|
6677
|
+
* look, not a re-prompt on faith". Stamped at turn-end when
|
|
6678
|
+
* `turnEndReason === "error"` — including the case where the adapter's
|
|
6679
|
+
* stream generator simply returned after emitting an in-band `error`
|
|
6680
|
+
* event with no explicit turn-end and no thrown exception, which
|
|
6681
|
+
* `runAgentTurn`'s P5 synthetic-reason fallback now classifies as
|
|
6682
|
+
* `"error"` rather than defaulting to `"exited"`. Cleared the moment a
|
|
6683
|
+
* LATER turn completes without one (same reset shape as
|
|
4949
6684
|
* `resumeAttempts`/`restartAttempts`). Detection + signal only. */
|
|
4950
6685
|
lastTurnErroredAt?: string;
|
|
6686
|
+
/** The captured error text for the turn `lastTurnErroredAt` timestamps —
|
|
6687
|
+
* `evt.error.message` off the in-band stream `error` event observed
|
|
6688
|
+
* during that turn (see `runAgentTurn`'s `turnErrorMessage`). Distinct
|
|
6689
|
+
* from `lastError` (reserved for `markCrashed`) and from
|
|
6690
|
+
* `rt.lastErrorMessage` (an internal, cross-turn, never-cleared runtime
|
|
6691
|
+
* field — this one is the public, turn-scoped descriptor projection of
|
|
6692
|
+
* it). Absent when the adapter reported `reason:"error"` on its own
|
|
6693
|
+
* turn-end without a preceding `error`-kind stream event. Stamped and
|
|
6694
|
+
* cleared together with `lastTurnErroredAt`. */
|
|
6695
|
+
lastTurnErrorMessage?: string;
|
|
4951
6696
|
/** True when the LAST completed turn produced zero assistant output and
|
|
4952
6697
|
* zero tool calls (mirrors `SessionTurnEndEvent.empty` — see that
|
|
4953
6698
|
* field's doc). Persisted so `monitorSessionWait`'s synchronous
|
|
@@ -4977,6 +6722,29 @@ interface SessionDescriptor {
|
|
|
4977
6722
|
* the underlying `killedMidTurn`/`endedReason`). The in-place resume path
|
|
4978
6723
|
* never auto-retries the interrupted prompt. */
|
|
4979
6724
|
interrupted?: boolean;
|
|
6725
|
+
/** The `SessionsRegistry.bootId` of the FIRST daemon boot that found this
|
|
6726
|
+
* row interrupted — i.e. which restart cut its turn off. Stamped by the
|
|
6727
|
+
* boot reload (`loadHistorySnapshot`) on an interrupted row that doesn't
|
|
6728
|
+
* carry one yet; dropped whenever the row is interrupted AGAIN (so the next
|
|
6729
|
+
* boot restamps it) and on the next successful turn-end (with the markers it
|
|
6730
|
+
* qualifies). `interruptedAtBoot === registry.bootId` is how the
|
|
6731
|
+
* continue-interrupted verb and boot pass pick "interrupted by the LAST
|
|
6732
|
+
* restart" apart from a stale interruption nobody picked back up. PERSISTED
|
|
6733
|
+
* (unlike the derived `interrupted`) — the boot that stamped it may not be
|
|
6734
|
+
* the one reading it. */
|
|
6735
|
+
interruptedAtBoot?: string;
|
|
6736
|
+
/** How many automatic continue prompts the daemon has sent this row
|
|
6737
|
+
* (`daemon.continueInterruptedOnBoot`) since its last successful turn-end.
|
|
6738
|
+
* Incremented BEFORE the prompt is sent, so a continue turn that itself
|
|
6739
|
+
* gets cut off by another restart still counts; reset on the next
|
|
6740
|
+
* successful turn-end. The boot pass stops at
|
|
6741
|
+
* `MAX_AUTO_CONTINUE_ATTEMPTS` — the no-loop bound for a turn that keeps
|
|
6742
|
+
* taking the daemon down with it. PERSISTED; absent until the first one. */
|
|
6743
|
+
autoContinueAttempts?: number;
|
|
6744
|
+
/** `bootId` of the boot whose pass last auto-continued this row — the
|
|
6745
|
+
* at-most-one-auto-continue-per-restart guard. Reset with
|
|
6746
|
+
* `autoContinueAttempts`. */
|
|
6747
|
+
lastAutoContinueBoot?: string;
|
|
4980
6748
|
/** How many in-place resume attempts have FAILED in a row for this session
|
|
4981
6749
|
* (§5 cap/backoff). Incremented on every failed `maybeResumeAgent` attempt —
|
|
4982
6750
|
* the resume throws, the adapter rejects the id, or the spawn returns null —
|
|
@@ -5083,6 +6851,9 @@ interface SessionDescriptor {
|
|
|
5083
6851
|
* field is. Absent (not `false`) for every descriptor from before this
|
|
5084
6852
|
* field existed — treated the same as `false` everywhere it's read. */
|
|
5085
6853
|
archived?: boolean;
|
|
6854
|
+
/** ISO-8601 instant `archiveSession` set `archived: true`; cleared by
|
|
6855
|
+
* `unarchiveSession`. Absent on rows archived before this field existed. */
|
|
6856
|
+
archivedAt?: string;
|
|
5086
6857
|
/** When `true`, the idle-reaper (`isReapable`, `idle-reaper.ts`) never
|
|
5087
6858
|
* retires this session regardless of how long it's sat idle. For a
|
|
5088
6859
|
* supervisor that legitimately parks — waiting on a child, waiting on a
|
|
@@ -5137,6 +6908,12 @@ interface SessionDescriptor {
|
|
|
5137
6908
|
* `worktreePath` without an id identifies a PATH, which a later worktree
|
|
5138
6909
|
* may reuse; the pair identifies one specific worktree. */
|
|
5139
6910
|
worktreeId?: string;
|
|
6911
|
+
/** Absolute path of the PRIMARY checkout `worktreePath` was cut from (the
|
|
6912
|
+
* repo root a `worktree_status` query targets), read at spawn from the
|
|
6913
|
+
* worktree admin dir's `commondir` file (`resolveWorktreeIdentity`).
|
|
6914
|
+
* Absent whenever `worktreePath` is, when `commondir` was unreadable, and
|
|
6915
|
+
* for every session persisted before this field existed. */
|
|
6916
|
+
mainRepoPath?: string;
|
|
5140
6917
|
/** `true` only when `worktreePath` was provisioned by the `worktrees.isolation`
|
|
5141
6918
|
* policy WITHOUT an explicit `worktree` request from the caller (see
|
|
5142
6919
|
* `decideWorktreeIsolation`'s `WorktreeDecision.provision.implicit` in
|
|
@@ -5326,15 +7103,34 @@ interface SessionDescriptor {
|
|
|
5326
7103
|
/** Cumulative input / output token counts (same source + cadence as costUsd). */
|
|
5327
7104
|
tokensIn?: number;
|
|
5328
7105
|
tokensOut?: number;
|
|
7106
|
+
/** Cumulative prompt-cache read / cache-creation / reasoning tokens, from
|
|
7107
|
+
* the adapter's usage reader (claude-code transcript, hermes state.db,
|
|
7108
|
+
* opencode.db). Absent when the adapter doesn't expose them — never
|
|
7109
|
+
* zero-filled. See `UsageDetail` in usage.ts. */
|
|
7110
|
+
cacheReadTokens?: number;
|
|
7111
|
+
cacheWriteTokens?: number;
|
|
7112
|
+
reasoningTokens?: number;
|
|
7113
|
+
/** Tool calls the daemon observed across completed turns (cumulative).
|
|
7114
|
+
* Stamped at the first turn end, so absent = no turn has finished yet. */
|
|
7115
|
+
toolCalls?: number;
|
|
7116
|
+
/** Wall time spent inside turns, cumulative ms. Same cadence as
|
|
7117
|
+
* `toolCalls`. */
|
|
7118
|
+
durationMs?: number;
|
|
5329
7119
|
/** Context-window size + tokens-in-context from the latest `usage_update`
|
|
5330
7120
|
* event (ACP adapters that report a context window). Refreshed live as
|
|
5331
7121
|
* usage_update events arrive, not just at turn-end. */
|
|
5332
7122
|
contextSize?: number;
|
|
5333
7123
|
contextUsed?: number;
|
|
7124
|
+
/** Where `contextSize` came from — `"adapter"` (a cost-bearing, authoritative
|
|
7125
|
+
* usage_update; sticky), `"catalog"` (model-catalog window for the model),
|
|
7126
|
+
* or `"reported"` (the adapter's own, possibly inferred, frame size). See
|
|
7127
|
+
* `context-window.ts` for the precedence. */
|
|
7128
|
+
contextSizeSource?: ContextSizeSource;
|
|
5334
7129
|
/** Where `costUsd` came from — `"adapter"` (adapter's own reader or a
|
|
5335
7130
|
* usage_update cost block), `"computed"` (tokens × in-repo catalog price),
|
|
5336
7131
|
* `"no-pricing"` (tokens present but the model isn't in the catalog — cost
|
|
5337
|
-
* deliberately left undefined), or `"none"`. Stamped
|
|
7132
|
+
* deliberately left undefined), or `"none"`. Stamped live on each
|
|
7133
|
+
* cost-bearing usage_update / reader read, and re-derived at turn-end. */
|
|
5338
7134
|
usageSource?: UsageSource;
|
|
5339
7135
|
/** Latest known `available_commands_update` payload (see @agentproto/acp's
|
|
5340
7136
|
* `StreamEvent`'s `available-commands` kind) — the slash-commands/skills
|
|
@@ -5456,7 +7252,7 @@ interface SessionDescriptor {
|
|
|
5456
7252
|
* next assistant `text-delta` (the model has the floor, so nothing is
|
|
5457
7253
|
* pending), at turn start, and in the turn's finally. A claim about the
|
|
5458
7254
|
* present tense — anything that disproves it must clear it. */
|
|
5459
|
-
blockedOn?: "subagent" | "command";
|
|
7255
|
+
blockedOn?: "subagent" | "command" | "inbox";
|
|
5460
7256
|
/** toolCallId of the tool-call that set `blockedOn`. A tool-result only
|
|
5461
7257
|
* clears `blockedOn` when its toolCallId matches — so a nested or
|
|
5462
7258
|
* interleaved tool finishing first can't clear the flag early. */
|
|
@@ -5472,9 +7268,9 @@ interface SessionDescriptor {
|
|
|
5472
7268
|
* (`markCrashed` → `session:exited` with `status:"error"`/
|
|
5473
7269
|
* `reason:"crashed"`), the supervisor-notify subscriber
|
|
5474
7270
|
* (`supervisor-notify.ts`) delivers a `[child-crashed]` notice to its
|
|
5475
|
-
* `parentSessionId`, if any —
|
|
5476
|
-
*
|
|
5477
|
-
*
|
|
7271
|
+
* `parentSessionId`, if any — dispatched now when the parent is alive
|
|
7272
|
+
* and idle, or parked in its `promptQueue` and drained as its own turn
|
|
7273
|
+
* when the parent's current turn ends. The free external
|
|
5478
7274
|
* webhook path (`notifyUrl`) already fires regardless of this flag; this
|
|
5479
7275
|
* only gates the additional IN-BAND signal into the parent's own
|
|
5480
7276
|
* session, which a caller that isn't a delegating supervisor doesn't
|
|
@@ -5483,14 +7279,42 @@ interface SessionDescriptor {
|
|
|
5483
7279
|
notifyParentOnCrash?: boolean;
|
|
5484
7280
|
/** True when the session was spawned in permission-hold mode. */
|
|
5485
7281
|
permissionHold?: boolean;
|
|
5486
|
-
/**
|
|
5487
|
-
*
|
|
5488
|
-
*
|
|
5489
|
-
*
|
|
5490
|
-
*
|
|
5491
|
-
*
|
|
5492
|
-
*
|
|
7282
|
+
/** Effective OS-level confinement of the adapter's own process
|
|
7283
|
+
* (`agent_start.commandSandbox`, else the workspace's
|
|
7284
|
+
* `.agentproto/command-sandbox.json` `adapterSpawn.mode`), resolved at
|
|
7285
|
+
* spawn with the same precedence the driver applies
|
|
7286
|
+
* (`wrapAgentCliSpawn`). Absent when neither engaged the axis (the spawn
|
|
7287
|
+
* ran unconfined without anyone choosing a mode), for a `sandbox` spawn
|
|
7288
|
+
* (confinement is the box's business), and for sessions persisted before
|
|
7289
|
+
* this field existed. Set at spawn time, immutable thereafter. */
|
|
7290
|
+
commandSandbox?: "off" | "workspace" | "strict";
|
|
7291
|
+
/** @deprecated Retired — child reports and `[child-crashed]` notices for a
|
|
7292
|
+
* busy session are now queued as their own `promptQueue` items
|
|
7293
|
+
* (`source:"child:<id>"`) instead of being string-prepended onto the next
|
|
7294
|
+
* prompt. Only read once, at boot, by `migratePendingChildNotices`, which
|
|
7295
|
+
* moves any notices a pre-upgrade daemon persisted into `promptQueue` and
|
|
7296
|
+
* deletes this field. Never written. */
|
|
5493
7297
|
pendingChildCrashNotices?: string[];
|
|
7298
|
+
/** True once this session has been taught the `<agentproto-message>`
|
|
7299
|
+
* invariant (`MESSAGE_PREAMBLE`) — sent as a `system-prompt` slice ahead
|
|
7300
|
+
* of the FIRST message turn it receives, never again. */
|
|
7301
|
+
messagePreambleSent?: boolean;
|
|
7302
|
+
/** Durable, bounded inbox of typed messages NOT yet consumed (AIP-46
|
|
7303
|
+
* §Session messages): every accepted message lands here until it's
|
|
7304
|
+
* delivered into a turn (auto-acked), returned by `inbox_wait` with
|
|
7305
|
+
* `ack`, or acked via `inbox_ack`. `fyi` messages only ever live here
|
|
7306
|
+
* (surfaced as a digest). Capped at `INBOX_CAP`, oldest dropped first
|
|
7307
|
+
* (`[inbox] dropped`). Full history lives in the transcript, not here.
|
|
7308
|
+
* New array on every mutation, like `promptQueue`. */
|
|
7309
|
+
inbox?: SessionMessage[];
|
|
7310
|
+
/** What the live agent session can do, read from its driver at attach
|
|
7311
|
+
* time (spawn / resume) — shown in `session_list` so a sender can predict
|
|
7312
|
+
* a message's delivery tier. `steering`: the agent accepts ACP steering
|
|
7313
|
+
* (`_session/steering`) — a `steer` message can be injected into its
|
|
7314
|
+
* running turn instead of waiting for it to end. */
|
|
7315
|
+
capabilities?: {
|
|
7316
|
+
steering: boolean;
|
|
7317
|
+
};
|
|
5494
7318
|
/** FIFO of prompts that arrived while this session was mid-turn and
|
|
5495
7319
|
* asked to be QUEUED rather than rejected (`enqueuePrompt`'s
|
|
5496
7320
|
* `opts.queue` arm — see its doc comment). Index 0 is next to
|
|
@@ -5505,8 +7329,7 @@ interface SessionDescriptor {
|
|
|
5505
7329
|
* `sessionDescriptorsEqual` diff is a shallow `!==` per field, so an
|
|
5506
7330
|
* in-place `.shift()` here would silently stop propagating queue
|
|
5507
7331
|
* changes to the transcript panel. `[]` (not absent) once anything
|
|
5508
|
-
* has ever been queued
|
|
5509
|
-
* convention above. */
|
|
7332
|
+
* has ever been queued. */
|
|
5510
7333
|
promptQueue?: QueuedPrompt[];
|
|
5511
7334
|
/** Best-effort context-handoff digest (Fix D), stashed on a descriptor
|
|
5512
7335
|
* whose resume degraded to a BLANK spawn — the adapter's own
|
|
@@ -5514,8 +7337,7 @@ interface SessionDescriptor {
|
|
|
5514
7337
|
* false` — built by `buildResumeContextDigest` from the daemon's own
|
|
5515
7338
|
* `events.jsonl` transcript, which survives both cases. Flushed and
|
|
5516
7339
|
* cleared by prepending it onto the first dispatched turn's message
|
|
5517
|
-
* (`runAgentTurn`), exactly once
|
|
5518
|
-
* `pendingChildCrashNotices` above. Gated strictly on the blank-fallback
|
|
7340
|
+
* (`runAgentTurn`), exactly once. Gated strictly on the blank-fallback
|
|
5519
7341
|
* flags at the two sites that set it (`session-restart-core.ts`,
|
|
5520
7342
|
* `maybeResumeAgent` here) — never set for a resume that actually
|
|
5521
7343
|
* restored context, so a real continuation is never double-fed its own
|
|
@@ -5547,6 +7369,12 @@ interface SessionDescriptor {
|
|
|
5547
7369
|
/** When this session was continued fresh into a new session, the target
|
|
5548
7370
|
* session id. */
|
|
5549
7371
|
continuedTo?: string;
|
|
7372
|
+
/** Set alongside `continuedFrom` on the NEW session — the harness the
|
|
7373
|
+
* checkpoint moved from/to and when. Present on every `continue_fresh`
|
|
7374
|
+
* spawn, not just a cross-harness one, so a same-harness continuation
|
|
7375
|
+
* and a genuine handoff are both traceable the same way. See
|
|
7376
|
+
* {@link SessionHandoff}. */
|
|
7377
|
+
handoff?: SessionHandoff;
|
|
5550
7378
|
/** Source label — the channel/harness this session was spawned from
|
|
5551
7379
|
* ("codex", "cowork", "vscode", "cron", …). Descriptor-only; groups the
|
|
5552
7380
|
* session under a source node in the tree. */
|
|
@@ -5678,6 +7506,9 @@ interface SessionSummary {
|
|
|
5678
7506
|
endedAt?: string;
|
|
5679
7507
|
exitCode?: number;
|
|
5680
7508
|
killedMidTurn?: boolean;
|
|
7509
|
+
/** Compact derived outcome (status + 120-char summary preview) — see
|
|
7510
|
+
* `SessionDescriptor.outcome`; the full record is on GET /sessions/:id. */
|
|
7511
|
+
outcome?: SessionOutcomeCompact;
|
|
5681
7512
|
lastOutputAt?: string;
|
|
5682
7513
|
lastActivityAt?: string;
|
|
5683
7514
|
currentPhase?: SessionCurrentPhase;
|
|
@@ -5697,6 +7528,9 @@ interface SessionSummary {
|
|
|
5697
7528
|
renamedByUser?: boolean;
|
|
5698
7529
|
activitySummary?: SessionActivitySummary;
|
|
5699
7530
|
archived?: boolean;
|
|
7531
|
+
/** ISO-8601 instant `archiveSession` set `archived: true`; cleared by
|
|
7532
|
+
* `unarchiveSession`. Absent on rows archived before this field existed. */
|
|
7533
|
+
archivedAt?: string;
|
|
5700
7534
|
keepAlive?: boolean;
|
|
5701
7535
|
pinned?: boolean;
|
|
5702
7536
|
pty?: boolean;
|
|
@@ -5705,6 +7539,7 @@ interface SessionSummary {
|
|
|
5705
7539
|
cwd?: string;
|
|
5706
7540
|
worktreePath?: string;
|
|
5707
7541
|
worktreeId?: string;
|
|
7542
|
+
mainRepoPath?: string;
|
|
5708
7543
|
/** Resolved AGENTS.md path — see `SessionDescriptor.agentsMd`. */
|
|
5709
7544
|
agentsMd?: string;
|
|
5710
7545
|
/** Injection mode — see `SessionDescriptor.agentsMdMode`. */
|
|
@@ -5719,18 +7554,21 @@ interface SessionSummary {
|
|
|
5719
7554
|
tokensOut?: number;
|
|
5720
7555
|
contextSize?: number;
|
|
5721
7556
|
contextUsed?: number;
|
|
7557
|
+
contextSizeSource?: ContextSizeSource;
|
|
5722
7558
|
usageSource?: UsageSource;
|
|
5723
7559
|
awaitingInput?: boolean;
|
|
5724
7560
|
awaitingQuestion?: SessionAwaitingQuestion;
|
|
5725
7561
|
awaitingPermission?: boolean;
|
|
5726
7562
|
turnsCompleted?: number;
|
|
5727
7563
|
busy?: boolean;
|
|
5728
|
-
blockedOn?: "subagent" | "command";
|
|
7564
|
+
blockedOn?: "subagent" | "command" | "inbox";
|
|
5729
7565
|
stalledSinceMs?: number;
|
|
5730
7566
|
/** Parked-with-background-tasks marker — see
|
|
5731
7567
|
* `SessionDescriptor.pendingBgTasks`. Stamped at turn-end, cleared on the
|
|
5732
7568
|
* next turn start / exit. */
|
|
5733
7569
|
pendingBgTasks?: number;
|
|
7570
|
+
/** Running background tasks — see `SessionDescriptor.backgroundTasks`. */
|
|
7571
|
+
backgroundTasks?: SessionBackgroundTask[];
|
|
5734
7572
|
/** Adapter-reported turn-error marker — see
|
|
5735
7573
|
* `SessionDescriptor.lastTurnErroredAt`. Stamped at turn-end, cleared on
|
|
5736
7574
|
* the next turn that completes without one. */
|
|
@@ -5762,6 +7600,8 @@ interface SessionSummary {
|
|
|
5762
7600
|
sandboxTeardown?: "kill" | "pause";
|
|
5763
7601
|
sandboxPorts?: Record<number, string>;
|
|
5764
7602
|
appServe?: SessionAppServeInfo;
|
|
7603
|
+
/** Effective adapter confinement — see `SessionDescriptor.commandSandbox`. */
|
|
7604
|
+
commandSandbox?: "off" | "workspace" | "strict";
|
|
5765
7605
|
/** Read-time projection of the box's ledger liveness verdict — see
|
|
5766
7606
|
* `SessionDescriptor.sandboxAlive`. */
|
|
5767
7607
|
sandboxAlive?: boolean;
|
|
@@ -6096,6 +7936,13 @@ interface SessionsRegistry {
|
|
|
6096
7936
|
queue?: boolean;
|
|
6097
7937
|
force?: boolean;
|
|
6098
7938
|
queueId?: string;
|
|
7939
|
+
/** A typed inter-session message (see `QueuedPrompt.envelope`). When
|
|
7940
|
+
* set, the turn is built from the envelope and `message` is ignored:
|
|
7941
|
+
* rendered as an `<agentproto-message>` tag, recorded as
|
|
7942
|
+
* `session-message` (never `user-prompt`), never matched as a
|
|
7943
|
+
* structured-question answer, and noted in the SENDER's transcript
|
|
7944
|
+
* as `session-message-sent`. */
|
|
7945
|
+
envelope?: SessionMessage;
|
|
6099
7946
|
}): Promise<EnqueuePromptResult>;
|
|
6100
7947
|
/** Cancel one not-yet-dispatched item in `SessionDescriptor.promptQueue`
|
|
6101
7948
|
* by id — the composer's per-item "remove" action. Idempotent: an
|
|
@@ -6106,6 +7953,50 @@ interface SessionsRegistry {
|
|
|
6106
7953
|
removeQueuedPrompt(id: string, queueId: string): {
|
|
6107
7954
|
removed: boolean;
|
|
6108
7955
|
};
|
|
7956
|
+
/** Deliver a daemon-attested typed message (`msg.to` is the recipient;
|
|
7957
|
+
* `from` must already be computed from the verified caller), routed by
|
|
7958
|
+
* `msg.urgency` (AIP-46 §Delivery tiers):
|
|
7959
|
+
* - a matching pending `waitForMessages` wins over every tier (waiter
|
|
7960
|
+
* first — returned as that call's result, never also injected);
|
|
7961
|
+
* - `fyi` — durable inbox only, no wake;
|
|
7962
|
+
* - idle recipient — any other tier starts its own turn now;
|
|
7963
|
+
* - busy + `next-turn` — queued behind the current turn;
|
|
7964
|
+
* - busy + `steer` — injected into the running turn when the daemon's
|
|
7965
|
+
* OWN prompt is in flight (never an autonomous cycle), the agent can
|
|
7966
|
+
* steer, and the per-recipient rate limit allows; else `next-turn`;
|
|
7967
|
+
* - busy + `interrupt` — cancels the turn and delivers — only when
|
|
7968
|
+
* `allowInterrupt` (the caller's grant); otherwise downgraded to
|
|
7969
|
+
* `steer`.
|
|
7970
|
+
* `urgencyApplied` reports the tier actually used. Throws when the
|
|
7971
|
+
* recipient is missing or not alive. */
|
|
7972
|
+
sendMessage(msg: SessionMessage, opts?: {
|
|
7973
|
+
source?: string;
|
|
7974
|
+
origin?: string;
|
|
7975
|
+
allowInterrupt?: boolean;
|
|
7976
|
+
}): Promise<SendMessageResult>;
|
|
7977
|
+
/** Block until a message matching `filter` is in `id`'s inbox (returns at
|
|
7978
|
+
* once when one already is), or `timeoutMs` elapses. Matched messages are
|
|
7979
|
+
* consumed: stamped `delivered.via:"wait"`, removed from the prompt queue
|
|
7980
|
+
* (so they're never also injected as a turn), and — when `ack` (default)
|
|
7981
|
+
* — removed from the inbox. Sets `blockedOn:"inbox"` while parked. */
|
|
7982
|
+
waitForMessages(id: string, filter: MessageFilter, opts: {
|
|
7983
|
+
timeoutMs: number;
|
|
7984
|
+
ack?: boolean;
|
|
7985
|
+
signal?: AbortSignal;
|
|
7986
|
+
}): Promise<WaitForMessagesResult>;
|
|
7987
|
+
/** Read `id`'s inbox (un-consumed messages), newest last. */
|
|
7988
|
+
listInbox(id: string, filter?: MessageFilter & {
|
|
7989
|
+
limit?: number;
|
|
7990
|
+
}): SessionMessage[] | null;
|
|
7991
|
+
/** Find a message `id` RECEIVED (still in its inbox, or already consumed
|
|
7992
|
+
* and only in its transcript) — how `message_reply` routes to the
|
|
7993
|
+
* original sender. `undefined` when `id` never received `messageId`. */
|
|
7994
|
+
findReceivedMessage(id: string, messageId: string): SessionMessage | undefined;
|
|
7995
|
+
/** Ack (remove) messages from `id`'s inbox — and from its prompt queue, so
|
|
7996
|
+
* an acked-but-still-queued message is never delivered afterwards. */
|
|
7997
|
+
ackInbox(id: string, ids: readonly string[] | "all"): {
|
|
7998
|
+
acked: string[];
|
|
7999
|
+
};
|
|
6109
8000
|
/** Snapshot a session's prompt queue for inspection — the after-the-fact
|
|
6110
8001
|
* view of what's sitting in `SessionDescriptor.promptQueue` right now.
|
|
6111
8002
|
* Runs the shared preview + origin-label derivation so every consumer
|
|
@@ -6134,7 +8025,8 @@ interface SessionsRegistry {
|
|
|
6134
8025
|
*
|
|
6135
8026
|
* Implemented as promote-to-front + interrupt: the cancelled turn's
|
|
6136
8027
|
* own `dispatchQueuedPrompt` (in its finally) then drains the promoted
|
|
6137
|
-
* item
|
|
8028
|
+
* item — and only that item; the rest wait for a natural turn-end —
|
|
8029
|
+
* into a fresh turn. On an idle session (nothing to interrupt) the
|
|
6138
8030
|
* promoted item is dispatched directly. Returns:
|
|
6139
8031
|
* `{ delivered: false, reason }` — `"no-session"` / `"not-in-queue"`;
|
|
6140
8032
|
* `{ delivered: true, interrupted: boolean }` otherwise. */
|
|
@@ -6143,6 +8035,21 @@ interface SessionsRegistry {
|
|
|
6143
8035
|
reason?: string;
|
|
6144
8036
|
interrupted?: boolean;
|
|
6145
8037
|
}>;
|
|
8038
|
+
/** A currently-RUNNING background task's info plus a tail of its output
|
|
8039
|
+
* file, for the UI's "click a live background-task row to peek at its
|
|
8040
|
+
* output" affordance. Scoped to `SessionDescriptor.backgroundTasks` on
|
|
8041
|
+
* purpose — a settled task is dropped from that list moments after it
|
|
8042
|
+
* reports terminal status (see `noteBackgroundTask`), so this never
|
|
8043
|
+
* becomes an arbitrary-file-read: the caller supplies a `taskId`, never a
|
|
8044
|
+
* path, and only a path the agent itself already reported for a task
|
|
8045
|
+
* still tracked as running is ever read. Returns `null` when the session
|
|
8046
|
+
* or the task is unknown (the caller surfaces 404); `tail` is `null` when
|
|
8047
|
+
* the task has no `outputFile` or the file can't be read (not yet
|
|
8048
|
+
* created, already rotated away, ...). */
|
|
8049
|
+
readBackgroundTaskTail(id: string, taskId: string): {
|
|
8050
|
+
task: SessionBackgroundTask;
|
|
8051
|
+
tail: string | null;
|
|
8052
|
+
} | null;
|
|
6146
8053
|
/** Eagerly resume ONE dead-but-resumable agent-cli session IN PLACE,
|
|
6147
8054
|
* WITHOUT a prompt — the boot-time counterpart to the lazy resume that
|
|
6148
8055
|
* `sendPrompt`/`enqueuePrompt` trigger on the first prompt after a restart
|
|
@@ -6165,6 +8072,17 @@ interface SessionsRegistry {
|
|
|
6165
8072
|
* touching the adapter, so the bounded boot pass can call it per row and
|
|
6166
8073
|
* tally the results. */
|
|
6167
8074
|
resumeOnBoot(id: string): Promise<EagerResumeOutcome>;
|
|
8075
|
+
/** Opaque id unique to this registry instance — one per daemon boot.
|
|
8076
|
+
* Stamped as `interruptedAtBoot` on rows the boot reload finds interrupted,
|
|
8077
|
+
* so `interruptedAtBoot === bootId` means "interrupted by the restart that
|
|
8078
|
+
* preceded THIS boot" (continue-interrupted). */
|
|
8079
|
+
readonly bootId: string;
|
|
8080
|
+
/** Book one automatic continue prompt against `id` (increment
|
|
8081
|
+
* `autoContinueAttempts`, stamp `lastAutoContinueBoot = bootId`) and
|
|
8082
|
+
* persist. Called by the continue-on-boot pass BEFORE it sends, so the
|
|
8083
|
+
* no-loop bound holds even when the continue turn is itself cut off by the
|
|
8084
|
+
* next restart. No-op for an unknown id. */
|
|
8085
|
+
recordAutoContinue(id: string): void;
|
|
6168
8086
|
/** Cancel the in-flight turn on a live agent-cli session and leave the
|
|
6169
8087
|
* session itself alive and idle — the bare "interrupt, no next prompt"
|
|
6170
8088
|
* primitive `sendPrompt`/`enqueuePrompt`'s `opts.interrupt` arm lacks
|
|
@@ -6390,6 +8308,15 @@ interface SessionsRegistry {
|
|
|
6390
8308
|
* Initial backfill: synchronously invokes `onLine` once for each
|
|
6391
8309
|
* line currently in the ring buffer so attaches show context. */
|
|
6392
8310
|
attach(id: string, onLine: (line: string, stream: "stdout" | "stderr") => void): (() => void) | null;
|
|
8311
|
+
/** Append a `kind: "mcp_app_tool_call"` record (an MCP App iframe's
|
|
8312
|
+
* `tools/call`, proxied by `mcp_app_tool_call`) to the session's
|
|
8313
|
+
* events.jsonl through the same seq-numbered writer the session's own
|
|
8314
|
+
* events use. No-op-safe for an unknown id (the writer creates the dir). */
|
|
8315
|
+
recordMcpAppToolCall(sessionId: string, record: McpAppToolCallRecord): void;
|
|
8316
|
+
/** Tool name the transcript recorded for `toolCallId` (the latest
|
|
8317
|
+
* non-empty `toolName` across its `tool-call` records), or undefined
|
|
8318
|
+
* when the id isn't in the session's events.jsonl. */
|
|
8319
|
+
findToolCallName(sessionId: string, toolCallId: string): Promise<string | undefined>;
|
|
6393
8320
|
/** Subscribe to a session's structured events.jsonl records as they're
|
|
6394
8321
|
* written — the live-push half of `GET /sessions/:id/events/stream`'s
|
|
6395
8322
|
* replay-then-subscribe handoff. Thin passthrough to the transcript
|
|
@@ -6433,7 +8360,26 @@ interface SessionsRegistry {
|
|
|
6433
8360
|
* bytes at the end; `lastBytes` caps the returned size from the
|
|
6434
8361
|
* tail. Returns null when the session is missing or not a PTY. */
|
|
6435
8362
|
readTerminalOutput(id: string, lastBytes?: number): Buffer | null;
|
|
6436
|
-
|
|
8363
|
+
/** SIGTERM the underlying child + close the agent protocol session.
|
|
8364
|
+
* `reason`, when given, is stamped onto `SessionDescriptor.endedReason`
|
|
8365
|
+
* (see {@link SessionEndReason}) — pass it from every call site that
|
|
8366
|
+
* knows WHY it's killing this session (an operator's explicit stop, a
|
|
8367
|
+
* supervisor tearing down its own judge session, …) rather than leaving
|
|
8368
|
+
* the row's cause of death unlabeled. Omitted preserves today's
|
|
8369
|
+
* behaviour: no `endedReason` is stamped by this call (the row may
|
|
8370
|
+
* already carry one from elsewhere, e.g. a prior `reapIdle`/`markCrashed`
|
|
8371
|
+
* — moot in practice since both already refuse a non-"running" row before
|
|
8372
|
+
* `kill()` would ever run on it).
|
|
8373
|
+
*
|
|
8374
|
+
* A row that's ALREADY terminal (exited/killed/error) normally makes this
|
|
8375
|
+
* a no-op refusal (`false`) — EXCEPT `reason: "operator-completed"`,
|
|
8376
|
+
* which is instead treated as "mark as completed": the persisted
|
|
8377
|
+
* outcome's `termination.reason` is idempotently relabeled to
|
|
8378
|
+
* `"operator-completed"` (the true original mechanism preserved in
|
|
8379
|
+
* `termination.previousReason`) and this returns `true`. Every other
|
|
8380
|
+
* reason (including `"operator-stopped"` or none) on a terminal row
|
|
8381
|
+
* stays the plain no-op. */
|
|
8382
|
+
kill(id: string, signal?: NodeJS.Signals, reason?: SessionEndReason): boolean;
|
|
6437
8383
|
/** Retire a long-idle agent-cli session to free its adapter process — the
|
|
6438
8384
|
* primitive the idle-session reaper (`runIdleReapPass`, PR-6) drives on a
|
|
6439
8385
|
* periodic sweep. Terminates the underlying adapter/child the SAME graceful
|
|
@@ -6541,17 +8487,6 @@ interface SessionsRegistry {
|
|
|
6541
8487
|
* window keeps aging out naturally rather than being wiped by the
|
|
6542
8488
|
* give-up itself. Returns false (no-op) for an unknown id. */
|
|
6543
8489
|
giveUpRestart(id: string, message: string): boolean;
|
|
6544
|
-
/** Queue a `[child-crashed] …` notice on a BUSY parent's descriptor
|
|
6545
|
-
* (`SessionDescriptor.pendingChildCrashNotices`) for delivery at its next
|
|
6546
|
-
* turn (`runAgentTurn`'s flush) — the never-interrupt path
|
|
6547
|
-
* `supervisor-notify.ts` takes when the parent can't be prompted
|
|
6548
|
-
* directly right now (it's mid-turn). Idempotent: the exact same notice
|
|
6549
|
-
* string is never queued twice, so a duplicate event for the same crash
|
|
6550
|
-
* can't double-deliver. No-op (returns false) on an unknown id, a
|
|
6551
|
-
* non-agent-cli row, or a row that isn't alive (`running`/`starting`) —
|
|
6552
|
-
* nothing to flush a notice INTO. Returns true iff the notice was newly
|
|
6553
|
-
* queued. */
|
|
6554
|
-
stampPendingChildCrashNotice(id: string, notice: string): boolean;
|
|
6555
8490
|
/** List permission requests currently parked in the pending-permissions
|
|
6556
8491
|
* inbox across all permission-hold sessions, newest last. Optionally
|
|
6557
8492
|
* filtered to one session. */
|
|
@@ -6563,8 +8498,9 @@ interface SessionsRegistry {
|
|
|
6563
8498
|
* clears the session's awaiting-permission state, and emits
|
|
6564
8499
|
* `session:permission-resolved`. */
|
|
6565
8500
|
respondPermission(id: string, input: PermissionRespondInput): Promise<PermissionRespondResult>;
|
|
6566
|
-
/** Stop tracking a session
|
|
6567
|
-
*
|
|
8501
|
+
/** Stop tracking a session. A still-live session is torn down through
|
|
8502
|
+
* `kill` first (same teardown as `agent_kill`), so forgetting never
|
|
8503
|
+
* orphans a running process tree. */
|
|
6568
8504
|
forget(id: string): boolean;
|
|
6569
8505
|
/** Await every best-effort fire-and-forget per-session write currently in
|
|
6570
8506
|
* flight — today the `CommandLogEntry` → `ToolCallRecord` chain
|
|
@@ -6656,8 +8592,10 @@ interface SpawnAgentInput {
|
|
|
6656
8592
|
/** Optional initial prompt to dispatch immediately. The promise
|
|
6657
8593
|
* the registry returns resolves AFTER the spawn — the prompt
|
|
6658
8594
|
* runs in the background, projecting events into the ring
|
|
6659
|
-
* buffer. Skip to spawn idle.
|
|
6660
|
-
|
|
8595
|
+
* buffer. Skip to spawn idle. A plain string, or a content-block
|
|
8596
|
+
* prompt (attachment-bearing first message) — see
|
|
8597
|
+
* `SpawnAgentSessionInput.prompt`'s doc. */
|
|
8598
|
+
initialPrompt?: string | Record<string, unknown> | unknown[];
|
|
6661
8599
|
/** The daemon-composed SYSTEM slice of {@link initialPrompt} — the part
|
|
6662
8600
|
* it synthesized ahead of the CALLER's ask (role disposition, lineage
|
|
6663
8601
|
* line, AGENTS.md, posture preamble). The adapter still receives the
|
|
@@ -6749,12 +8687,7 @@ interface SpawnAgentInput {
|
|
|
6749
8687
|
* cost/token fields on the descriptor. Adapter-specific (e.g. hermes
|
|
6750
8688
|
* reads its state.db keyed by the adapter session id). Omit for adapters
|
|
6751
8689
|
* with no usage source. */
|
|
6752
|
-
readUsage?: () => Promise<
|
|
6753
|
-
model?: string;
|
|
6754
|
-
costUsd?: number;
|
|
6755
|
-
tokensIn?: number;
|
|
6756
|
-
tokensOut?: number;
|
|
6757
|
-
} | null>;
|
|
8690
|
+
readUsage?: () => Promise<UsageReadResult | null>;
|
|
6758
8691
|
/** Opt this session into Langfuse tracing (prompt/completion + tool spans +
|
|
6759
8692
|
* tokens/cost). Effective opt-in is `trace ?? opts.langfuseTracingDefault ?? false`. */
|
|
6760
8693
|
trace?: boolean;
|
|
@@ -6770,6 +8703,9 @@ interface SpawnAgentInput {
|
|
|
6770
8703
|
/** What session close does to the box, when `remote` is true — see
|
|
6771
8704
|
* `SessionDescriptor.sandboxTeardown`. */
|
|
6772
8705
|
sandboxTeardown?: "kill" | "pause";
|
|
8706
|
+
/** Effective adapter confinement, resolved by the caller — see
|
|
8707
|
+
* `SessionDescriptor.commandSandbox`. */
|
|
8708
|
+
commandSandbox?: "off" | "workspace" | "strict";
|
|
6773
8709
|
/** Port-to-URL map from the booted sandbox — see
|
|
6774
8710
|
* `SessionDescriptor.sandboxPorts`. */
|
|
6775
8711
|
sandboxPorts?: Record<number, string>;
|
|
@@ -6808,15 +8744,12 @@ type PendingAgentOutcome = {
|
|
|
6808
8744
|
commandPreview?: string;
|
|
6809
8745
|
resumable?: boolean;
|
|
6810
8746
|
nativeTerminalResume?: boolean;
|
|
6811
|
-
readUsage?: () => Promise<
|
|
6812
|
-
model?: string;
|
|
6813
|
-
costUsd?: number;
|
|
6814
|
-
tokensIn?: number;
|
|
6815
|
-
tokensOut?: number;
|
|
6816
|
-
} | null>;
|
|
8747
|
+
readUsage?: () => Promise<UsageReadResult | null>;
|
|
6817
8748
|
/** Dispatched now that the tree + driver session both exist — never
|
|
6818
|
-
* passed to `spawnAgentPending`, which would race the tree.
|
|
6819
|
-
|
|
8749
|
+
* passed to `spawnAgentPending`, which would race the tree. A plain
|
|
8750
|
+
* string, or a content-block prompt (attachment-bearing first
|
|
8751
|
+
* message) — see `SpawnAgentSessionInput.prompt`'s doc. */
|
|
8752
|
+
initialPrompt?: string | Record<string, unknown> | unknown[];
|
|
6820
8753
|
/** The daemon-composed SYSTEM slice of `initialPrompt` (see
|
|
6821
8754
|
* `SpawnAgentInput.initialPromptSystem`) — threaded through the
|
|
6822
8755
|
* placeholder so the deferred dispatch records it as a system turn. */
|
|
@@ -7092,6 +9025,163 @@ declare function runEagerResumePass(opts: {
|
|
|
7092
9025
|
isServed?: (desc: SessionDescriptor) => boolean;
|
|
7093
9026
|
}): Promise<EagerResumeSummary>;
|
|
7094
9027
|
|
|
9028
|
+
/**
|
|
9029
|
+
* Continue-interrupted: pick back up the sessions a daemon restart cut off
|
|
9030
|
+
* mid-turn, by sending each an explicit "continue" prompt.
|
|
9031
|
+
*
|
|
9032
|
+
* Everything below the prompt already exists — the persisted registry is the
|
|
9033
|
+
* snapshot, `maybeResumeAgent` resumes a dead row in place (lazily on its first
|
|
9034
|
+
* prompt, or eagerly at boot via `eager-resume.ts`), and the row keeps the
|
|
9035
|
+
* derived `interrupted` marker until its next successful turn-end. What a
|
|
9036
|
+
* restart does NOT do, by design, is re-run the interrupted prompt (prompts
|
|
9037
|
+
* aren't idempotent). This module is the explicit, opt-in replacement for that:
|
|
9038
|
+
* a NEW prompt that tells the agent its last turn was cut off and to check the
|
|
9039
|
+
* state on disk before carrying on.
|
|
9040
|
+
*
|
|
9041
|
+
* Two callers share one eligibility rule + one sender:
|
|
9042
|
+
* - the manual verb (`session_continue_interrupted` MCP tool,
|
|
9043
|
+
* `POST /sessions/continue-interrupted`, `agentproto sessions
|
|
9044
|
+
* continue-interrupted`) — `mode: "manual"`, dry-run by default;
|
|
9045
|
+
* - the opt-in boot pass (`daemon.continueInterruptedOnBoot`) —
|
|
9046
|
+
* `mode: "boot"`, run by serve.ts right after the eager resume pass.
|
|
9047
|
+
*
|
|
9048
|
+
* Eligibility (both modes): an agent-cli row that is `interrupted` BY THE LAST
|
|
9049
|
+
* RESTART (`interruptedAtBoot === registry.bootId` — a stale interruption from
|
|
9050
|
+
* an older restart that nobody picked up is left alone), resumable under the
|
|
9051
|
+
* attempt cap (`canResume`), and not already busy. The prompt goes through
|
|
9052
|
+
* `enqueuePrompt`, the normal prompt path, so a dead-but-resumable row
|
|
9053
|
+
* lazy-resumes with its billing auth re-resolved exactly as a human prompt
|
|
9054
|
+
* would.
|
|
9055
|
+
*
|
|
9056
|
+
* No-loop rules (boot mode only — a human pressing the button is intent):
|
|
9057
|
+
* - never auto-continue a row whose resume has failed (`resumeAttempts > 0`:
|
|
9058
|
+
* the eager pass, or an earlier attempt, could not bring it back);
|
|
9059
|
+
* - at most one auto-continue per restart (`lastAutoContinueBoot`);
|
|
9060
|
+
* - at most `MAX_AUTO_CONTINUE_ATTEMPTS` auto-continues without a successful
|
|
9061
|
+
* turn-end in between (`autoContinueAttempts`, booked BEFORE sending), so a
|
|
9062
|
+
* continue turn that keeps getting cut off — or keeps taking the daemon
|
|
9063
|
+
* down with it — stops being retried.
|
|
9064
|
+
*
|
|
9065
|
+
* Ordering (boot mode): MUST run after the supervisor is re-armed, same
|
|
9066
|
+
* constraint as `eager-resume.ts` — the continue turn's `turn-end` is exactly
|
|
9067
|
+
* what a re-armed completion policy is waiting for — and after the eager pass,
|
|
9068
|
+
* so a row whose eager resume failed is recognisable (`resumeAttempts`) and
|
|
9069
|
+
* skipped rather than lazily retried.
|
|
9070
|
+
*/
|
|
9071
|
+
|
|
9072
|
+
/** The prompt sent when the caller doesn't supply one. */
|
|
9073
|
+
declare const DEFAULT_CONTINUE_PROMPT: string;
|
|
9074
|
+
/** Transcript provenance for the continue prompt (`opts.source`). */
|
|
9075
|
+
declare const CONTINUE_PROMPT_SOURCE = "daemon:continue-interrupted";
|
|
9076
|
+
/** Cap on automatic continue prompts sent to one row without a successful
|
|
9077
|
+
* turn-end in between (boot mode). 2 = the original interruption gets one
|
|
9078
|
+
* auto-continue, and if a second restart cuts THAT turn off it gets one more;
|
|
9079
|
+
* after that only a human (manual verb) re-sends. */
|
|
9080
|
+
declare const MAX_AUTO_CONTINUE_ATTEMPTS = 2;
|
|
9081
|
+
type ContinueInterruptedSkipReason =
|
|
9082
|
+
/** Unknown id (explicit `ids` filter only). */
|
|
9083
|
+
"unknown"
|
|
9084
|
+
/** Not interrupted at all — no turn was cut off. */
|
|
9085
|
+
| "not-interrupted"
|
|
9086
|
+
/** Interrupted by an EARLIER restart, not the last one. */
|
|
9087
|
+
| "stale-interrupt"
|
|
9088
|
+
/** Not in-place resumable (not agent-cli / missing adapter session / archived). */
|
|
9089
|
+
| "not-resumable"
|
|
9090
|
+
/** Already burned through `MAX_RESUME_ATTEMPTS` failed resumes. */
|
|
9091
|
+
| "resume-cap-exhausted"
|
|
9092
|
+
/** Mid-turn already — someone got there first. */
|
|
9093
|
+
| "busy"
|
|
9094
|
+
/** Boot mode: a resume attempt has failed on this row. */
|
|
9095
|
+
| "resume-failed"
|
|
9096
|
+
/** Boot mode: already auto-continued by this boot. */
|
|
9097
|
+
| "already-auto-continued"
|
|
9098
|
+
/** Boot mode: `MAX_AUTO_CONTINUE_ATTEMPTS` reached without recovering. */
|
|
9099
|
+
| "auto-continue-cap"
|
|
9100
|
+
/** Excluded by the cross-process `isServed` gate (another daemon's row). */
|
|
9101
|
+
| "not-served";
|
|
9102
|
+
type ContinueInterruptedOutcome = {
|
|
9103
|
+
id: string;
|
|
9104
|
+
name?: string;
|
|
9105
|
+
status: "eligible";
|
|
9106
|
+
} | {
|
|
9107
|
+
id: string;
|
|
9108
|
+
name?: string;
|
|
9109
|
+
status: "sent";
|
|
9110
|
+
} | {
|
|
9111
|
+
id: string;
|
|
9112
|
+
name?: string;
|
|
9113
|
+
status: "skipped";
|
|
9114
|
+
reason: ContinueInterruptedSkipReason;
|
|
9115
|
+
} | {
|
|
9116
|
+
id: string;
|
|
9117
|
+
name?: string;
|
|
9118
|
+
status: "failed";
|
|
9119
|
+
error: string;
|
|
9120
|
+
};
|
|
9121
|
+
interface ContinueInterruptedResult {
|
|
9122
|
+
dryRun: boolean;
|
|
9123
|
+
/** The prompt that was (or, on a dry run, would be) sent. */
|
|
9124
|
+
prompt: string;
|
|
9125
|
+
/** The boot the candidates were interrupted before (`registry.bootId`). */
|
|
9126
|
+
bootId: string;
|
|
9127
|
+
/** One entry per considered row — every interrupted row when `ids` is
|
|
9128
|
+
* omitted, every requested id otherwise. Eligible-first ordering matches
|
|
9129
|
+
* the send order (newest activity first). */
|
|
9130
|
+
sessions: ContinueInterruptedOutcome[];
|
|
9131
|
+
/** Counts by outcome status. */
|
|
9132
|
+
eligible: number;
|
|
9133
|
+
sent: number;
|
|
9134
|
+
skipped: number;
|
|
9135
|
+
failed: number;
|
|
9136
|
+
}
|
|
9137
|
+
type ContinueInterruptedMode = "manual" | "boot";
|
|
9138
|
+
/** Decide one row. `undefined` ⇒ eligible. Pure — reads the descriptor only. */
|
|
9139
|
+
declare function continueSkipReason(desc: SessionDescriptor, bootId: string, mode: ContinueInterruptedMode): ContinueInterruptedSkipReason | undefined;
|
|
9140
|
+
/**
|
|
9141
|
+
* List — and unless `dryRun` — continue the sessions interrupted by the last
|
|
9142
|
+
* restart. See the module docblock for eligibility, the no-loop rules, and the
|
|
9143
|
+
* ordering contract. No-throw per row: a send that fails admission (row went
|
|
9144
|
+
* busy, resume refused) is reported as `failed` and the pass carries on.
|
|
9145
|
+
*/
|
|
9146
|
+
declare function continueInterruptedSessions(opts: {
|
|
9147
|
+
registry: Pick<SessionsRegistry, "list" | "get" | "enqueuePrompt" | "bootId" | "recordAutoContinue">;
|
|
9148
|
+
mode: ContinueInterruptedMode;
|
|
9149
|
+
/** Default true: report what would be sent, send nothing. */
|
|
9150
|
+
dryRun?: boolean;
|
|
9151
|
+
/** Restrict to these session ids. Omitted ⇒ every interrupted row. */
|
|
9152
|
+
ids?: readonly string[];
|
|
9153
|
+
/** Custom prompt. Omitted/blank ⇒ `DEFAULT_CONTINUE_PROMPT`. */
|
|
9154
|
+
prompt?: string;
|
|
9155
|
+
/** Max sends in flight at once (each may lazily resume an adapter). Clamped
|
|
9156
|
+
* to ≥1. Default 4, same as the eager pass. */
|
|
9157
|
+
concurrency?: number;
|
|
9158
|
+
/** Cross-process gate — see `runEagerResumePass`. Omitted ⇒ all served. */
|
|
9159
|
+
isServed?: (desc: SessionDescriptor) => boolean;
|
|
9160
|
+
/** Extra visibility filter (a scoped orchestrator's subtree). Rows it
|
|
9161
|
+
* rejects are dropped silently — never reported, never sent. */
|
|
9162
|
+
visible?: (desc: SessionDescriptor) => boolean;
|
|
9163
|
+
}): Promise<ContinueInterruptedResult>;
|
|
9164
|
+
/** Tally of the boot-time continue pass, for the boot-banner line. */
|
|
9165
|
+
interface ContinueOnBootSummary {
|
|
9166
|
+
/** False when `daemon.continueInterruptedOnBoot` is off (nothing ran). */
|
|
9167
|
+
enabled: boolean;
|
|
9168
|
+
/** Rows passing eligibility at selection time — the banner's denominator. */
|
|
9169
|
+
eligible: number;
|
|
9170
|
+
sent: number;
|
|
9171
|
+
skipped: number;
|
|
9172
|
+
failed: number;
|
|
9173
|
+
}
|
|
9174
|
+
/**
|
|
9175
|
+
* The boot pass: `continueInterruptedSessions` in `mode: "boot"`, not a dry
|
|
9176
|
+
* run, reduced to the banner tally. Caller (the gateway handle, invoked from
|
|
9177
|
+
* serve.ts) is responsible for the ordering contract in the module docblock.
|
|
9178
|
+
*/
|
|
9179
|
+
declare function runContinueOnBootPass(opts: {
|
|
9180
|
+
registry: Parameters<typeof continueInterruptedSessions>[0]["registry"];
|
|
9181
|
+
concurrency: number;
|
|
9182
|
+
isServed?: (desc: SessionDescriptor) => boolean;
|
|
9183
|
+
}): Promise<ContinueOnBootSummary>;
|
|
9184
|
+
|
|
7095
9185
|
/**
|
|
7096
9186
|
* Daemon-lane PR provenance STAMPER.
|
|
7097
9187
|
*
|
|
@@ -7118,7 +9208,7 @@ declare function runEagerResumePass(opts: {
|
|
|
7118
9208
|
|
|
7119
9209
|
/** A minimal `gh` runner — resolves with the captured exit code + stdout.
|
|
7120
9210
|
* Injected so tests drive stamping without spawning a real `gh`. */
|
|
7121
|
-
type GhRunner = (args: readonly string[], cwd: string) => Promise<{
|
|
9211
|
+
type GhRunner$1 = (args: readonly string[], cwd: string) => Promise<{
|
|
7122
9212
|
exitCode: number;
|
|
7123
9213
|
stdout: string;
|
|
7124
9214
|
}>;
|
|
@@ -7206,7 +9296,7 @@ declare function createPrProvenanceReconciler(opts: {
|
|
|
7206
9296
|
* Defaults to reading the session's own events.jsonl via
|
|
7207
9297
|
* {@link readToolCallRecords}; injectable for tests. */
|
|
7208
9298
|
listToolCalls?: (sessionId: string) => Promise<ToolCallRecord[]>;
|
|
7209
|
-
run?: GhRunner;
|
|
9299
|
+
run?: GhRunner$1;
|
|
7210
9300
|
host?: string;
|
|
7211
9301
|
}): PrProvenanceReconciler;
|
|
7212
9302
|
|
|
@@ -7297,6 +9387,87 @@ interface InboundWatcher {
|
|
|
7297
9387
|
shutdown(): void;
|
|
7298
9388
|
}
|
|
7299
9389
|
|
|
9390
|
+
/**
|
|
9391
|
+
* Dynamic MCP-app panels for installed `@agentproto/app-kit` apps that ship
|
|
9392
|
+
* a `ui` block (`defineApp({ ui: { html, title?, tools?, csp? } })`,
|
|
9393
|
+
* `app-tools.ts`'s `performInstall`). Bridges `AppRegistry` → `AgnoMcpApp[]`
|
|
9394
|
+
* so `registerMcpApps` (mcp-apps-adapter.ts) can mount them next to the
|
|
9395
|
+
* built-in panels (agentproto_sessions, agents_overview, ...).
|
|
9396
|
+
*
|
|
9397
|
+
* `mcpServerFactory` (index.ts) rebuilds a fresh McpServer per `/mcp`
|
|
9398
|
+
* request, so this module never throws — a bad app record must not take
|
|
9399
|
+
* down every other request. A collided tool id is still skipped with a
|
|
9400
|
+
* `console.warn` (there's nowhere else for a second panel to live). Every
|
|
9401
|
+
* OTHER failure — a removed app dir, a missing/unreadable `ui.path`, a
|
|
9402
|
+
* failed or in-flight `ui.build` — still mounts the panel, serving a
|
|
9403
|
+
* readable HTML placeholder/error page in place of the real bundle
|
|
9404
|
+
* (app-ui-placeholder.ts) instead of skipping it outright: a `ui://`
|
|
9405
|
+
* resource that silently doesn't exist just shows as a JSON-RPC "resource
|
|
9406
|
+
* not found" in a host, which is what motivated this file's placeholder
|
|
9407
|
+
* page in the first place — a HOST-VISIBLE page, even a stand-in one, beats
|
|
9408
|
+
* a resource the host has to explain away as missing.
|
|
9409
|
+
*/
|
|
9410
|
+
|
|
9411
|
+
/**
|
|
9412
|
+
* The MCP Apps wire (spec `2026-01-26`) over `postMessage` JSON-RPC, exposed
|
|
9413
|
+
* as `window.McpApp.connect() -> Promise<{ callTool, sendMessage,
|
|
9414
|
+
* updateModelContext, openLink, onTeardown }>`. No host in this stack injects
|
|
9415
|
+
* an SDK global into the app iframe, so every UI panel that talks through
|
|
9416
|
+
* `window.McpApp` (all of them — see each app's `ui.ts` under
|
|
9417
|
+
* `packages/apps/src`) would otherwise throw `Cannot read properties of
|
|
9418
|
+
* undefined (reading 'connect')` at boot. Injected here, at serve time, so
|
|
9419
|
+
* every installed app's panel is covered without each app shipping its own
|
|
9420
|
+
* copy of the bridge.
|
|
9421
|
+
*
|
|
9422
|
+
* Bridge surface:
|
|
9423
|
+
* - `callTool(name, args)` — JSON-RPC `tools/call`
|
|
9424
|
+
* - `sendMessage(content)` — JSON-RPC `ui/message`
|
|
9425
|
+
* - `updateModelContext({content?, structuredContent?})` — JSON-RPC
|
|
9426
|
+
* `ui/update-model-context`; each call replaces the previous context.
|
|
9427
|
+
* - `openLink(url)` — JSON-RPC `ui/open-link` {url}
|
|
9428
|
+
* - `onTeardown(cb)` — register a cleanup callback invoked on
|
|
9429
|
+
* `ui/resource-teardown` from the host, at which point the bridge also
|
|
9430
|
+
* replies `{result:{}}` automatically.
|
|
9431
|
+
* - `getHostContext()` / `onHostContext(cb)` — the host context captured
|
|
9432
|
+
* from the `ui/initialize` result and merged on every
|
|
9433
|
+
* `ui/notifications/host-context-changed` (the notification carries only
|
|
9434
|
+
* the changed keys, per the ext-apps spec).
|
|
9435
|
+
* - `getToolInput()` / `onToolInput(cb)` — the arguments the panel's own tool
|
|
9436
|
+
* (`app_ui_<slug>`) was invoked with, from `ui/notifications/tool-input`,
|
|
9437
|
+
* falling back to the tool's own result (`ui/notifications/tool-result`,
|
|
9438
|
+
* which `registerMcpApps` fills with `execute()`'s return value) for a host
|
|
9439
|
+
* that forwards results but not inputs. A late subscriber is replayed the
|
|
9440
|
+
* last value, same as `onHostContext`. Lets an MCP-hosted panel resolve a
|
|
9441
|
+
* deep link (e.g. `{view: "wallets/my-profile"}`) the way the browser-served
|
|
9442
|
+
* panel resolves `location.hash`.
|
|
9443
|
+
* - `displayMode` — `{get, available, request, onChange, mountToggle}`, the
|
|
9444
|
+
* shared controller from `@agentproto/app-client/display-mode` (also
|
|
9445
|
+
* published as `window.McpApp.displayMode` once connected). It is what
|
|
9446
|
+
* mounts the floating ⤢ toggle; an app that wants the control in its own
|
|
9447
|
+
* header opts the floating one out (`<meta name="agentproto-display-toggle"
|
|
9448
|
+
* content="none">`, or `connect({displayToggle: "none"})`) and calls
|
|
9449
|
+
* `displayMode.mountToggle(el)` instead.
|
|
9450
|
+
*/
|
|
9451
|
+
declare const MCP_APP_BRIDGE_SCRIPT = "<script>\n(function () {\n window.__AGENTPROTO_UI_TRANSPORT__ = \"mcp\";\n if (window.McpApp) return;\n var nextId = 1, pending = {}, teardownCbs = [];\n var hostContext = null, hostContextCbs = [];\n var toolInput = null, toolInputCbs = [];\n function post(m) { window.parent.postMessage(m, \"*\"); }\n function getHostContext() { return hostContext; }\n function onHostContext(cb) {\n hostContextCbs.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (hostContext) { try { cb(hostContext); } catch (_) {} }\n }\n function setHostContext(ctx) {\n if (!ctx || typeof ctx !== \"object\") return;\n // host-context-changed carries only the changed keys \u2014 merge, matching\n // the official ext-apps App behaviour.\n hostContext = Object.assign({}, hostContext || {}, ctx);\n for (var i = 0; i < hostContextCbs.length; i++) {\n try { hostContextCbs[i](hostContext); } catch (_) {}\n }\n }\n function getToolInput() { return toolInput; }\n function onToolInput(cb) {\n toolInputCbs.push(cb);\n // Replay the last known input so a late subscriber isn't stuck blind \u2014\n // same convention as onHostContext.\n if (toolInput) { try { cb(toolInput); } catch (_) {} }\n }\n function setToolInput(input) {\n if (!input || typeof input !== \"object\") return;\n toolInput = input;\n for (var i = 0; i < toolInputCbs.length; i++) {\n try { toolInputCbs[i](toolInput); } catch (_) {}\n }\n }\n // A ui/notifications/tool-result carries the full CallToolResult of the\n // tool call that opened this view (spec 2026-01-26) \u2014 the same envelope\n // registerMcpApps wraps execute()'s return value in (JSON text content, or\n // structuredContent). Read as a fallback source of the tool's arguments\n // for a host that forwards results but not tool-input.\n function extractToolResultInput(result) {\n try {\n if (result && result.structuredContent && typeof result.structuredContent === \"object\") {\n return result.structuredContent;\n }\n var c = result && Array.isArray(result.content) ? result.content[0] : null;\n if (c && c.type === \"text\" && typeof c.text === \"string\") return JSON.parse(c.text);\n } catch (_) {}\n return null;\n }\n window.addEventListener(\"message\", function (e) {\n var m = e.data;\n if (typeof m === \"string\") { try { m = JSON.parse(m); } catch (_) { return; } }\n if (!m || m.jsonrpc !== \"2.0\") return;\n if (m.method === \"ui/resource-teardown\") {\n for (var i = 0; i < teardownCbs.length; i++) teardownCbs[i]();\n post({ jsonrpc: \"2.0\", id: m.id, result: {} });\n return;\n }\n if (m.method === \"ui/notifications/host-context-changed\") { setHostContext(m.params || {}); return; }\n if (m.method === \"ui/notifications/tool-input\") { setToolInput((m.params || {}).arguments); return; }\n if (m.method === \"ui/notifications/tool-result\") { setToolInput(extractToolResultInput(m.params)); return; }\n if (m.id != null && pending[m.id]) { var cb = pending[m.id]; delete pending[m.id]; cb(m.result, m.error); }\n });\n function request(method, params) {\n return new Promise(function (resolve, reject) {\n var id = nextId++;\n pending[id] = function (r, err) { if (err) reject(new Error((err && err.message) || String(err))); else resolve(r); };\n post({ jsonrpc: \"2.0\", id: id, method: method, params: params });\n });\n }\n function requestDisplayMode(mode) { return request(\"ui/request-display-mode\", { mode: mode }); }\n // The shared toggle (@agentproto/app-client/display-mode), injected right\n // after this script. Absent only when the app ships its own\n // window.AgentprotoUI namespace, which suppresses that injection \u2014 the\n // fallback keeps window.McpApp.displayMode callable either way.\n function installDisplayMode(opts) {\n var api = { getHostContext: getHostContext, onHostContext: onHostContext, requestDisplayMode: requestDisplayMode };\n if (window.AgentprotoUI && window.AgentprotoUI.installDisplayMode) {\n return window.AgentprotoUI.installDisplayMode(api, opts);\n }\n return {\n get: function () { return (hostContext && hostContext.displayMode) || \"inline\"; },\n available: function () { return (hostContext && hostContext.availableDisplayModes) || []; },\n request: requestDisplayMode,\n onChange: function (cb) { onHostContext(function (c) { cb((c && c.displayMode) || \"inline\", c); }); return function () {}; },\n mountToggle: function () { return null; }\n };\n }\n window.McpApp = {\n connect: function (opts) {\n if (window.parent === window) return Promise.reject(new Error(\"no host (standalone)\"));\n return request(\"ui/initialize\", {\n appInfo: { name: \"agentproto-app\", version: \"1.0\" },\n appCapabilities: { availableDisplayModes: [\"inline\", \"fullscreen\"] },\n protocolVersion: \"2026-01-26\"\n }).then(function (result) {\n // The initialize result carries the initial hostContext (displayMode,\n // availableDisplayModes, theme, safeAreaInsets) \u2014 capture it before\n // notifying the host, so the toggle renders in the right place on\n // its first paint rather than after a host-context-changed.\n if (result && result.hostContext) setHostContext(result.hostContext);\n post({ jsonrpc: \"2.0\", method: \"ui/notifications/initialized\", params: {} });\n var displayMode = installDisplayMode((opts && opts.displayToggle) ? { toggle: opts.displayToggle } : null);\n window.McpApp.displayMode = displayMode;\n return {\n callTool: function (name, args) { return request(\"tools/call\", { name: name, arguments: args || {} }); },\n sendMessage: function (p) {\n var params = typeof p === \"string\" ? { content: p } : (p || {});\n var content = typeof params.content === \"string\" ? [{ type: \"text\", text: params.content }] : (params.content || []);\n return request(\"ui/message\", { role: \"user\", content: content });\n },\n updateModelContext: function (p) { return request(\"ui/update-model-context\", p || {}); },\n openLink: function (url) { return request(\"ui/open-link\", { url: url }); },\n onTeardown: function (cb) { teardownCbs.push(cb); },\n getHostContext: getHostContext,\n onHostContext: onHostContext,\n getToolInput: getToolInput,\n onToolInput: onToolInput,\n displayMode: displayMode\n };\n });\n }\n };\n})();\n</script>\n";
|
|
9452
|
+
/** Inject `MCP_APP_BRIDGE_SCRIPT` followed by the two
|
|
9453
|
+
* `window.AgentprotoUI` scripts (`DISPLAY_MODE_SCRIPT`, then
|
|
9454
|
+
* `RUNNER_SELECT_SCRIPT`) — every installed app UI gets the shared
|
|
9455
|
+
* display-mode toggle and `mountRunnerSelect` for free, same as it gets
|
|
9456
|
+
* `window.McpApp` (see `injectAfterStructuralTag` for the placement rule).
|
|
9457
|
+
* The halves are independently idempotent, so a single
|
|
9458
|
+
* `injectAfterStructuralTag` call inserts only whichever ones the document
|
|
9459
|
+
* doesn't already define — inserting them one call each would place the
|
|
9460
|
+
* second BEFORE the first (all target "right after the same structural
|
|
9461
|
+
* tag"), which is why they're concatenated first instead. `window.
|
|
9462
|
+
* AgentprotoUI` gates BOTH of its scripts as one unit: a document that
|
|
9463
|
+
* already defines that namespace brings its own, and the bridge's
|
|
9464
|
+
* `installDisplayMode` falls back to a controller with no button rather
|
|
9465
|
+
* than fighting it. The bridge guard matches an assignment only — every
|
|
9466
|
+
* app panel CONSUMES `window.McpApp.connect()`, so matching any mention
|
|
9467
|
+
* would skip injection for exactly the documents that need it, leaving
|
|
9468
|
+
* `window.McpApp` undefined in the host iframe. */
|
|
9469
|
+
declare function injectMcpAppBridge(html: string): string;
|
|
9470
|
+
|
|
7300
9471
|
/**
|
|
7301
9472
|
* MCP tools that expose the per-workspace brain to agents.
|
|
7302
9473
|
*
|
|
@@ -7420,6 +9591,326 @@ interface WorkspaceBrainSubscriber {
|
|
|
7420
9591
|
}
|
|
7421
9592
|
declare function createWorkspaceBrainSubscriber(opts: WorkspaceBrainSubscriberOptions): WorkspaceBrainSubscriber;
|
|
7422
9593
|
|
|
9594
|
+
/**
|
|
9595
|
+
* Review ledger — the daemon-side record of every review attestation.
|
|
9596
|
+
*
|
|
9597
|
+
* Lives under the daemon state dir, NEVER in the reviewed repo's working
|
|
9598
|
+
* tree: `~/.agentproto/reviews/<repo-slug>/<manifestSha>/<binding>/<rangeSha>.json`
|
|
9599
|
+
* — the path IS the ledger key `(repoRemote, manifestSha, binding, rangeSha)`
|
|
9600
|
+
* (the repo slug is a filesystem-safe form of the normalized remote). One
|
|
9601
|
+
* file per key; a re-run of the same key overwrites it with the newer
|
|
9602
|
+
* attestation.
|
|
9603
|
+
*
|
|
9604
|
+
* Every attestation is recorded — including `incomplete` and dirty-tree ones,
|
|
9605
|
+
* so `review_ledger` shows the history honestly — but only a clean `pass` /
|
|
9606
|
+
* `block` is ever served back as a cache hit (see `lookupCached`).
|
|
9607
|
+
*
|
|
9608
|
+
* Each file is an {@link LedgerEntry}: the self-contained attestation (what
|
|
9609
|
+
* `review_export` writes out) plus host-local metadata that is NOT part of
|
|
9610
|
+
* what gets attested (the checkout path, the manifest path, the manifest's
|
|
9611
|
+
* `verdict.exportDir`).
|
|
9612
|
+
*
|
|
9613
|
+
* Beside each entry sits an optional `<rangeSha>.annotations.json`
|
|
9614
|
+
* ({@link LedgerAnnotations}): the MUTABLE follow-up layer — a PR link found
|
|
9615
|
+
* after the fact, and `review_pr` status snapshots appended over time. It is
|
|
9616
|
+
* never hashed into (or exported with) the attestation, so annotating an
|
|
9617
|
+
* entry can't disturb a signature over it.
|
|
9618
|
+
*
|
|
9619
|
+
* Same persistence primitives as the sibling JSON stores (`node:fs/promises`,
|
|
9620
|
+
* write-tmp + rename). The root is injectable so tests never touch
|
|
9621
|
+
* `~/.agentproto`.
|
|
9622
|
+
*/
|
|
9623
|
+
|
|
9624
|
+
/** Host-local metadata stored beside an attestation (never exported). */
|
|
9625
|
+
interface LedgerHostMeta {
|
|
9626
|
+
/** Absolute path of the checkout the review ran in. */
|
|
9627
|
+
repoRoot: string;
|
|
9628
|
+
/** Absolute path of the REVIEW.md that was used. */
|
|
9629
|
+
manifestPath: string;
|
|
9630
|
+
/** The manifest's `verdict.exportDir`, resolved against `repoRoot`. */
|
|
9631
|
+
exportDir?: string;
|
|
9632
|
+
}
|
|
9633
|
+
interface LedgerEntry {
|
|
9634
|
+
attestation: Attestation;
|
|
9635
|
+
host: LedgerHostMeta;
|
|
9636
|
+
}
|
|
9637
|
+
/** One `review_pr` status fetch. */
|
|
9638
|
+
interface PrStatusSnapshot {
|
|
9639
|
+
/** ISO timestamp of the fetch. */
|
|
9640
|
+
fetchedAt: string;
|
|
9641
|
+
state: "open" | "merged" | "closed";
|
|
9642
|
+
reviews: Array<{
|
|
9643
|
+
login: string;
|
|
9644
|
+
state: string;
|
|
9645
|
+
submittedAt: string;
|
|
9646
|
+
}>;
|
|
9647
|
+
checks?: Array<{
|
|
9648
|
+
name: string;
|
|
9649
|
+
conclusion: string | null;
|
|
9650
|
+
}>;
|
|
9651
|
+
}
|
|
9652
|
+
/** The mutable annotations sidecar of a ledger entry. */
|
|
9653
|
+
interface LedgerAnnotations {
|
|
9654
|
+
pr?: ReviewPrRef;
|
|
9655
|
+
/** Oldest first — each `review_pr` call appends one. */
|
|
9656
|
+
prStatus?: PrStatusSnapshot[];
|
|
9657
|
+
}
|
|
9658
|
+
interface ReviewLedgerFilter {
|
|
9659
|
+
repoRemote?: string;
|
|
9660
|
+
rangeSha?: string;
|
|
9661
|
+
binding?: string;
|
|
9662
|
+
manifestSha?: string;
|
|
9663
|
+
}
|
|
9664
|
+
interface ReviewLedger {
|
|
9665
|
+
/** Absolute root directory of the ledger. */
|
|
9666
|
+
readonly root: string;
|
|
9667
|
+
put(entry: LedgerEntry): Promise<string>;
|
|
9668
|
+
get(key: LedgerKey): Promise<LedgerEntry | undefined>;
|
|
9669
|
+
/** A reusable verdict for `key`: a clean (not dirty) `pass`/`block` whose
|
|
9670
|
+
* rubric digests still match. `incomplete` is never reused. */
|
|
9671
|
+
lookupCached(key: LedgerKey, rubrics: readonly RubricDigest[]): Promise<LedgerEntry | undefined>;
|
|
9672
|
+
findByRunId(runId: string): Promise<LedgerEntry | undefined>;
|
|
9673
|
+
/** Entries matching `filter`, newest first. */
|
|
9674
|
+
list(filter?: ReviewLedgerFilter): Promise<LedgerEntry[]>;
|
|
9675
|
+
/** The annotations sidecar of `key` (`{}` when there is none). */
|
|
9676
|
+
getAnnotations(key: LedgerKey): Promise<LedgerAnnotations>;
|
|
9677
|
+
/** Read-modify-write the annotations sidecar of `key`; returns the result. */
|
|
9678
|
+
updateAnnotations(key: LedgerKey, update: (current: LedgerAnnotations) => LedgerAnnotations): Promise<LedgerAnnotations>;
|
|
9679
|
+
}
|
|
9680
|
+
/** Record `pr` as the entry's PR link (replacing any previous link). */
|
|
9681
|
+
declare const withPr: (pr: ReviewPrRef) => (a: LedgerAnnotations) => LedgerAnnotations;
|
|
9682
|
+
/** Append one status snapshot. */
|
|
9683
|
+
declare const withPrStatus: (snapshot: PrStatusSnapshot) => (a: LedgerAnnotations) => LedgerAnnotations;
|
|
9684
|
+
declare const defaultReviewLedgerRoot: () => string;
|
|
9685
|
+
/** Filesystem-safe slug of a normalized repo remote
|
|
9686
|
+
* (`github.com/agentproto/ts` → `github.com_agentproto_ts`). */
|
|
9687
|
+
declare function repoSlug(repoRemote: string): string;
|
|
9688
|
+
declare function createReviewLedger(opts?: {
|
|
9689
|
+
root?: string;
|
|
9690
|
+
}): ReviewLedger;
|
|
9691
|
+
|
|
9692
|
+
/**
|
|
9693
|
+
* Review runner — the daemon host for `@agentproto/review`.
|
|
9694
|
+
*
|
|
9695
|
+
* The pure package parses REVIEW.md and compiles a binding into an ordinary
|
|
9696
|
+
* AIP-15 workflow; this module supplies everything that touches the world:
|
|
9697
|
+
*
|
|
9698
|
+
* 1. resolve the repo + range — `merge-base(<target.base>, HEAD)..HEAD`
|
|
9699
|
+
* unless the caller pins `base`/`head`;
|
|
9700
|
+
* 2. ledger pre-check — a clean `pass`/`block` already recorded for the
|
|
9701
|
+
* same `(repoRemote, manifestSha, binding, rangeSha)` (and the same
|
|
9702
|
+
* rubric digests) is returned with `cached: true`, nothing runs;
|
|
9703
|
+
* 3. execute the compiled workflow through `@agentproto/workflow-runtime`'s
|
|
9704
|
+
* `compileWorkflow` + `runWorkflow` — prepare steps (the engine's own
|
|
9705
|
+
* gate runner), then the FREEZE (head resolved after prepare), then the
|
|
9706
|
+
* lanes in parallel through {@link createReviewLaneExecutor}, then the
|
|
9707
|
+
* fan-in verdict;
|
|
9708
|
+
* 4. build the attestation and write it to the ledger. A CANCELLED run
|
|
9709
|
+
* (`cancel()`, or superseded by a newer head — see `supersede`) writes
|
|
9710
|
+
* nothing: nobody asked for its verdict any more.
|
|
9711
|
+
*
|
|
9712
|
+
* Lanes: command lanes run `sh -c <run>` in their own process group with a
|
|
9713
|
+
* hard timeout; agent lanes spawn a CHILD REVIEWER SESSION through
|
|
9714
|
+
* {@link ReviewerSessionHost} — in the daemon, {@link createDaemonReviewerHost},
|
|
9715
|
+
* which uses the same `spawnAgentSession` core `agent_start` uses and kills
|
|
9716
|
+
* the session through the registry on timeout/cancel (a child session, never
|
|
9717
|
+
* an orphan pid). The reviewer gets a pointer-style prompt (range + rubric
|
|
9718
|
+
* path) and writes a structured verdict file under the ledger's run dir —
|
|
9719
|
+
* outside the reviewed tree.
|
|
9720
|
+
*
|
|
9721
|
+
* Async pattern mirrors `WorkflowRunner`: `start()` returns a run record
|
|
9722
|
+
* immediately and executes in the background; `status()` polls; `wait()`
|
|
9723
|
+
* blocks. Runs live in memory for the daemon's lifetime; the ATTESTATION is
|
|
9724
|
+
* what persists (the ledger), and `status()` falls back to it for a run that
|
|
9725
|
+
* finished before a daemon restart.
|
|
9726
|
+
*/
|
|
9727
|
+
|
|
9728
|
+
/**
|
|
9729
|
+
* Normalize a git remote URL so SSH and HTTPS clones of the same repo agree:
|
|
9730
|
+
* `git@github.com:agentproto/ts.git` and `https://user@github.com/agentproto/ts`
|
|
9731
|
+
* both become `github.com/agentproto/ts`. Credentials are always stripped.
|
|
9732
|
+
*/
|
|
9733
|
+
declare function normalizeRemote(url: string): string;
|
|
9734
|
+
/** Run one command lane: `sh -c <command>` in its own process group, output
|
|
9735
|
+
* captured (tail-bounded), hard-killed on timeout or cancel. */
|
|
9736
|
+
declare function runShellLane(input: {
|
|
9737
|
+
command: string;
|
|
9738
|
+
cwd: string;
|
|
9739
|
+
timeoutMs: number;
|
|
9740
|
+
signal?: AbortSignal;
|
|
9741
|
+
}): Promise<LaneOutcome>;
|
|
9742
|
+
/** Outcome of one reviewer session's run. */
|
|
9743
|
+
type ReviewerRunResult = {
|
|
9744
|
+
status: "ended";
|
|
9745
|
+
sessionId: string;
|
|
9746
|
+
preset: string;
|
|
9747
|
+
model?: string;
|
|
9748
|
+
} | {
|
|
9749
|
+
status: "timeout";
|
|
9750
|
+
sessionId: string;
|
|
9751
|
+
preset: string;
|
|
9752
|
+
model?: string;
|
|
9753
|
+
} | {
|
|
9754
|
+
status: "failed";
|
|
9755
|
+
error: string;
|
|
9756
|
+
sessionId?: string;
|
|
9757
|
+
preset?: string;
|
|
9758
|
+
model?: string;
|
|
9759
|
+
};
|
|
9760
|
+
/**
|
|
9761
|
+
* The agent-lane executor seam: spawn a reviewer session under a harness
|
|
9762
|
+
* preset, run ONE turn with `prompt`, and stop it — killing it through the
|
|
9763
|
+
* session lifecycle on timeout or cancel. The daemon wires
|
|
9764
|
+
* {@link createDaemonReviewerHost}; tests wire a fake.
|
|
9765
|
+
*/
|
|
9766
|
+
interface ReviewerSessionHost {
|
|
9767
|
+
run(input: {
|
|
9768
|
+
preset: string;
|
|
9769
|
+
cwd: string;
|
|
9770
|
+
prompt: string;
|
|
9771
|
+
label: string;
|
|
9772
|
+
timeoutMs: number;
|
|
9773
|
+
parentSessionId?: string;
|
|
9774
|
+
signal?: AbortSignal;
|
|
9775
|
+
}): Promise<ReviewerRunResult>;
|
|
9776
|
+
}
|
|
9777
|
+
interface ReviewLaneExecutorContext {
|
|
9778
|
+
runId: string;
|
|
9779
|
+
reviewId: string;
|
|
9780
|
+
repoRoot: string;
|
|
9781
|
+
manifestPath: string;
|
|
9782
|
+
/** Directory the agent lanes' verdict files are written under. */
|
|
9783
|
+
runDir: string;
|
|
9784
|
+
reviewers?: ReviewerSessionHost;
|
|
9785
|
+
parentSessionId?: string;
|
|
9786
|
+
}
|
|
9787
|
+
/** The daemon's {@link ReviewLaneExecutor}: subprocess command lanes, child
|
|
9788
|
+
* reviewer sessions for agent lanes. */
|
|
9789
|
+
declare function createReviewLaneExecutor(ctx: ReviewLaneExecutorContext): ReviewLaneExecutor;
|
|
9790
|
+
interface ReviewRunInput {
|
|
9791
|
+
/** Any directory inside the repo to review. */
|
|
9792
|
+
cwd: string;
|
|
9793
|
+
/** REVIEW.md path — absolute, or relative to `cwd`. Default: `<repo root>/REVIEW.md`. */
|
|
9794
|
+
manifestPath?: string;
|
|
9795
|
+
/** Binding name. Default: the sole binding, or `default`. */
|
|
9796
|
+
binding?: string;
|
|
9797
|
+
/** Range base (ref or sha). Default: `merge-base(<target.base>, HEAD)`. */
|
|
9798
|
+
base?: string;
|
|
9799
|
+
/** Range head (ref or sha). Default: `HEAD`, resolved AFTER prepare. */
|
|
9800
|
+
head?: string;
|
|
9801
|
+
/** Ignore a cached ledger verdict and re-run. */
|
|
9802
|
+
nocache?: boolean;
|
|
9803
|
+
/** Session the agent lanes' reviewer sessions nest under. */
|
|
9804
|
+
parentSessionId?: string;
|
|
9805
|
+
/** Session that requested the review — recorded as
|
|
9806
|
+
* `attestation.requester.sessionId`. Default: `parentSessionId`. */
|
|
9807
|
+
requesterSessionId?: string;
|
|
9808
|
+
/** The PR the range is being reviewed for, when the caller knows it —
|
|
9809
|
+
* recorded as `attestation.pr` and as the entry's annotation link. */
|
|
9810
|
+
pr?: ReviewPrRef;
|
|
9811
|
+
/** Before running, cancel in-flight runs for the same repo + binding + base
|
|
9812
|
+
* whose head DIFFERS (an older push nobody will read the verdict of) — as
|
|
9813
|
+
* long as that run is the same line of work: the same checkout, or a head
|
|
9814
|
+
* that is an ancestor of this one. A sibling branch reviewed from another
|
|
9815
|
+
* worktree off the same base is never cancelled. Gates should pass
|
|
9816
|
+
* `true`; the default is `false`. */
|
|
9817
|
+
supersede?: boolean;
|
|
9818
|
+
}
|
|
9819
|
+
type ReviewRunStatus = "running" | "done" | "failed" | "cancelled";
|
|
9820
|
+
interface ReviewRun {
|
|
9821
|
+
runId: string;
|
|
9822
|
+
status: ReviewRunStatus;
|
|
9823
|
+
startedAt: string;
|
|
9824
|
+
endedAt?: string;
|
|
9825
|
+
reviewId?: string;
|
|
9826
|
+
binding?: string;
|
|
9827
|
+
repoRemote?: string;
|
|
9828
|
+
/** The resolved range base, once known. */
|
|
9829
|
+
baseSha?: string;
|
|
9830
|
+
/** The range head: the pre-prepare head once resolved, then the frozen head. */
|
|
9831
|
+
headSha?: string;
|
|
9832
|
+
/** The checkout the run reviews, once resolved. */
|
|
9833
|
+
repoRoot?: string;
|
|
9834
|
+
/** Set on a run cancelled because a newer head superseded it. */
|
|
9835
|
+
supersededBy?: string;
|
|
9836
|
+
/** Lanes settled so far (live progress while `running`). */
|
|
9837
|
+
lanes: LaneResult[];
|
|
9838
|
+
/** Set once `done`. */
|
|
9839
|
+
attestation?: Attestation;
|
|
9840
|
+
/** True when `attestation` was served from the ledger — nothing ran. */
|
|
9841
|
+
cached?: boolean;
|
|
9842
|
+
/** Where the attestation was written in the ledger. */
|
|
9843
|
+
ledgerPath?: string;
|
|
9844
|
+
error?: string;
|
|
9845
|
+
}
|
|
9846
|
+
interface ReviewRunner {
|
|
9847
|
+
start(input: ReviewRunInput): ReviewRun;
|
|
9848
|
+
status(runId: string): Promise<ReviewRun | undefined>;
|
|
9849
|
+
wait(runId: string): Promise<ReviewRun | undefined>;
|
|
9850
|
+
cancel(runId: string): boolean;
|
|
9851
|
+
readonly ledger: ReviewLedger;
|
|
9852
|
+
}
|
|
9853
|
+
interface CreateReviewRunnerOptions {
|
|
9854
|
+
ledger: ReviewLedger;
|
|
9855
|
+
/** Agent-lane executor. Omitted ⇒ agent lanes are `skipped` (and the
|
|
9856
|
+
* verdict therefore `incomplete`), never silently passed. */
|
|
9857
|
+
reviewers?: ReviewerSessionHost;
|
|
9858
|
+
/** Attestor identity. Default: `agentproto-runtime@<hostname>`. */
|
|
9859
|
+
daemonId?: string;
|
|
9860
|
+
}
|
|
9861
|
+
/** Placeholder values the daemon binds for every review. `{changed}` is a
|
|
9862
|
+
* shell-quoted turbo filter for the packages changed since the range base
|
|
9863
|
+
* (compose dependents with `...{changed}`). */
|
|
9864
|
+
declare function hostPlaceholders(baseSha: string): Record<string, string>;
|
|
9865
|
+
declare function createReviewRunner(opts: CreateReviewRunnerOptions): ReviewRunner;
|
|
9866
|
+
|
|
9867
|
+
/**
|
|
9868
|
+
* PR follow-up for review attestations — the host side of `review_pr`.
|
|
9869
|
+
*
|
|
9870
|
+
* A review attests a git range; whether that range became a PR, and what
|
|
9871
|
+
* happened to the PR afterwards, is only knowable later. This module asks
|
|
9872
|
+
* GitHub through the daemon host's authed `gh` CLI (shelled out — no HTTP
|
|
9873
|
+
* client, no token handling of our own):
|
|
9874
|
+
*
|
|
9875
|
+
* - {@link findPrForCommit}: `headSha → PR` via
|
|
9876
|
+
* `gh api repos/{owner}/{repo}/commits/{sha}/pulls`;
|
|
9877
|
+
* - {@link fetchPrStatus}: the PR's state + reviews (+ check runs on the
|
|
9878
|
+
* PR head, best-effort) as one {@link PrStatusSnapshot}.
|
|
9879
|
+
*
|
|
9880
|
+
* Every failure is a {@link PrLookupError} with a stable `code` — `gh`
|
|
9881
|
+
* missing, `gh` failing (auth, network, 404), a non-GitHub remote, no PR for
|
|
9882
|
+
* the commit — so the tool can return it as structured data instead of
|
|
9883
|
+
* throwing.
|
|
9884
|
+
*/
|
|
9885
|
+
|
|
9886
|
+
type PrLookupErrorCode = "gh_unavailable" | "gh_failed" | "unsupported_remote" | "no_pr";
|
|
9887
|
+
declare class PrLookupError extends Error {
|
|
9888
|
+
readonly code: PrLookupErrorCode;
|
|
9889
|
+
constructor(code: PrLookupErrorCode, message: string);
|
|
9890
|
+
}
|
|
9891
|
+
/** Runs `gh <args>` and resolves its stdout. */
|
|
9892
|
+
type GhRunner = (args: readonly string[]) => Promise<string>;
|
|
9893
|
+
/** The real runner: `gh` from the daemon's PATH. A missing binary is
|
|
9894
|
+
* `gh_unavailable`; any non-zero exit is `gh_failed` with gh's stderr. */
|
|
9895
|
+
declare const execGh: GhRunner;
|
|
9896
|
+
/** `github.com/owner/name` → `owner/name`; `undefined` for any other host.
|
|
9897
|
+
* Also accepts the multi-account SSH host-alias convention
|
|
9898
|
+
* (`git@github.com-work:owner/name` normalizes to `github.com-work/owner/name`). */
|
|
9899
|
+
declare function githubRepoOf(repoRemote: string): string | undefined;
|
|
9900
|
+
/** Parse `https://github.com/owner/name/pull/42` (trailing path/query ok). */
|
|
9901
|
+
declare function parsePrUrl(url: string): {
|
|
9902
|
+
repo: string;
|
|
9903
|
+
number: number;
|
|
9904
|
+
} | undefined;
|
|
9905
|
+
/** The PR `headSha` belongs to, or `undefined` when GitHub knows none.
|
|
9906
|
+
* Prefers a PR whose head IS `headSha`, then an open one, then the first. */
|
|
9907
|
+
declare function findPrForCommit(gh: GhRunner, repo: string, headSha: string): Promise<ReviewPrRef | undefined>;
|
|
9908
|
+
/** The PR's head sha (for matching a `prUrl` to ledger entries). */
|
|
9909
|
+
declare function prHeadSha(gh: GhRunner, repo: string, number: number): Promise<string | undefined>;
|
|
9910
|
+
/** One status snapshot of `pr`: state, reviews, and (best-effort — omitted
|
|
9911
|
+
* when that call fails) the check runs on the PR's current head. */
|
|
9912
|
+
declare function fetchPrStatus(gh: GhRunner, pr: ReviewPrRef, now?: () => Date): Promise<PrStatusSnapshot>;
|
|
9913
|
+
|
|
7423
9914
|
/**
|
|
7424
9915
|
* `~/.agentproto/harness-presets.json` — the persisted harness→profile binding
|
|
7425
9916
|
* (mode 0600). A {@link HarnessPreset} names, per adapter harness, WHICH auth
|
|
@@ -7527,6 +10018,77 @@ declare function removeHarnessPreset(id: string): Promise<boolean>;
|
|
|
7527
10018
|
*/
|
|
7528
10019
|
declare function setDefaultPreset(harnessSlug: string, presetId: string): Promise<HarnessPreset>;
|
|
7529
10020
|
|
|
10021
|
+
/**
|
|
10022
|
+
* The daemon's {@link ReviewerSessionHost}: agent review lanes as CHILD
|
|
10023
|
+
* SESSIONS, spawned through the same `spawnAgentSession` core `agent_start`
|
|
10024
|
+
* uses, waited on with the same `monitorSessionWait` core `session_monitor` /
|
|
10025
|
+
* `sessions wait` use, and stopped through `registry.kill` — the normal
|
|
10026
|
+
* session lifecycle, so a timed-out or cancelled reviewer is a killed session
|
|
10027
|
+
* in `session_list`, never an orphan pid.
|
|
10028
|
+
*
|
|
10029
|
+
* A lane's `preset` names the daemon's existing preset surface — no parallel
|
|
10030
|
+
* model/auth config: first a harness preset (`harness-presets.json`: adapter
|
|
10031
|
+
* harness + auth profile + default model), then a user preset
|
|
10032
|
+
* (`presets.json`, handed to `spawnAgentSession` as-is so it expands the
|
|
10033
|
+
* preset exactly like `agent_start.presetId` does).
|
|
10034
|
+
*
|
|
10035
|
+
* Reviewers spawn with role `executor` (no re-delegation), `worktree: false`
|
|
10036
|
+
* (they read the reviewed checkout's history; they never edit), and
|
|
10037
|
+
* `dedupe: false` (two lanes/runs with the same label are distinct
|
|
10038
|
+
* reviewers, never merged).
|
|
10039
|
+
*/
|
|
10040
|
+
|
|
10041
|
+
interface DaemonReviewerHostDeps {
|
|
10042
|
+
registry: SessionsRegistry;
|
|
10043
|
+
sessionEvents: SessionEventBus;
|
|
10044
|
+
eventRing: EventRing;
|
|
10045
|
+
resolveAgentAdapter: AgentAdapterResolver;
|
|
10046
|
+
/** Extra spawn deps forwarded to `spawnAgentSession` (sandbox resolver,
|
|
10047
|
+
* catalog models, …). `registry`/`resolveAgentAdapter` always win. */
|
|
10048
|
+
spawnDeps?: Partial<SpawnAgentSessionDeps>;
|
|
10049
|
+
/** Preset lookups — injectable for tests; default to the real stores. */
|
|
10050
|
+
getHarnessPreset?: (id: string) => Promise<HarnessPreset | undefined>;
|
|
10051
|
+
getUserPreset?: (id: string) => Promise<UserPreset | undefined>;
|
|
10052
|
+
}
|
|
10053
|
+
/** Resolve a lane's `preset` to spawn fields — harness preset first, then a
|
|
10054
|
+
* user preset. `undefined` when neither store knows the id. */
|
|
10055
|
+
declare function resolveReviewerPreset(preset: string, lookups?: Pick<DaemonReviewerHostDeps, "getHarnessPreset" | "getUserPreset">): Promise<Pick<SpawnAgentSessionInput, "adapter" | "model" | "access" | "preset"> | undefined>;
|
|
10056
|
+
declare function createDaemonReviewerHost(deps: DaemonReviewerHostDeps): ReviewerSessionHost;
|
|
10057
|
+
|
|
10058
|
+
/**
|
|
10059
|
+
* MCP tools for the review primitive (`@agentproto/review`):
|
|
10060
|
+
*
|
|
10061
|
+
* review_run run a REVIEW.md binding over a git range → attestation
|
|
10062
|
+
* (ledger hit ⇒ the cached verdict, `cached: true`)
|
|
10063
|
+
* review_status poll a run started with `wait: false`
|
|
10064
|
+
* review_cancel cancel a running review (lane sessions are killed)
|
|
10065
|
+
* review_ledger list recorded attestations
|
|
10066
|
+
* review_export write one attestation out as a standalone JSON file
|
|
10067
|
+
* review_pr follow a recorded review to its GitHub PR (via `gh`):
|
|
10068
|
+
* link it, snapshot the PR's state/reviews/checks into the
|
|
10069
|
+
* entry's annotations, return the combined view
|
|
10070
|
+
*
|
|
10071
|
+
* Async pattern mirrors `workflow_start`/`workflow_status`: `review_run`
|
|
10072
|
+
* blocks by default (`wait: true`) — pass `wait: false` for long reviews and
|
|
10073
|
+
* poll `review_status` with the returned `runId`.
|
|
10074
|
+
*
|
|
10075
|
+
* Trust note: `review_run` executes the command checks the repo's own
|
|
10076
|
+
* REVIEW.md declares (`sh -c`), the same trust `workflow_run_file` extends to
|
|
10077
|
+
* a WORKFLOW.md's gate steps — not the `command_execute` allowlist, which
|
|
10078
|
+
* governs ad-hoc commands a remote caller composes.
|
|
10079
|
+
*/
|
|
10080
|
+
|
|
10081
|
+
interface RegisterReviewToolsOptions {
|
|
10082
|
+
runner: ReviewRunner;
|
|
10083
|
+
/** The calling session (from `?callerSessionId=`) — agent-lane reviewer
|
|
10084
|
+
* sessions nest under it, and it is the default requester. */
|
|
10085
|
+
callerSessionId?: string;
|
|
10086
|
+
/** `gh` runner for `review_pr` — injectable for tests. Default: the real
|
|
10087
|
+
* `gh` on the daemon's PATH. */
|
|
10088
|
+
gh?: GhRunner;
|
|
10089
|
+
}
|
|
10090
|
+
declare function registerReviewTools(server: McpServer, opts: RegisterReviewToolsOptions): void;
|
|
10091
|
+
|
|
7530
10092
|
/**
|
|
7531
10093
|
* Built-in custom-route registration (PR-5).
|
|
7532
10094
|
*
|
|
@@ -7987,6 +10549,76 @@ declare function reapGcEntry(entry: SandboxLedgerEntry, opts: {
|
|
|
7987
10549
|
/** States that still name a box the gc might act on. */
|
|
7988
10550
|
declare const GC_REAPABLE_STATES: readonly SandboxLedgerState[];
|
|
7989
10551
|
|
|
10552
|
+
/**
|
|
10553
|
+
* Sandbox ledger reconcile — closes the gap between what
|
|
10554
|
+
* `~/.agentproto/sandboxes.json` believes is still booted/paused/connected
|
|
10555
|
+
* and what the PROVIDER actually still has running. A failed teardown, a
|
|
10556
|
+
* daemon that crashed mid-close, or a provider-side idle-reap the daemon
|
|
10557
|
+
* never heard about all leave a ledger row claiming a box is alive long
|
|
10558
|
+
* after the provider reaped it — real-world evidence: one daemon's ledger
|
|
10559
|
+
* carried ~50 rows in "paused" while the provider had far fewer boxes left.
|
|
10560
|
+
*
|
|
10561
|
+
* This is read-only against the PROVIDER (`SandboxProvider.probe`, the same
|
|
10562
|
+
* primitive `GET /sandboxes/:id/alive` and `agentproto sandbox list` already
|
|
10563
|
+
* use per-row) and never tears a box down — it only ever flips a ledger
|
|
10564
|
+
* row's `state` to `"gone"` via `recordSandboxLiveness` when the provider
|
|
10565
|
+
* confirms the box no longer exists. A box a caller still wants stays
|
|
10566
|
+
* completely untouched; this module has no `stop()`/`pause()`/`connect()`
|
|
10567
|
+
* call anywhere in it.
|
|
10568
|
+
*/
|
|
10569
|
+
|
|
10570
|
+
/** The slice of `SandboxProviderHandle` reconcile needs — just the liveness
|
|
10571
|
+
* probe, so tests (and the `local` provider, which has none) can hand back
|
|
10572
|
+
* a handle without a `probe` at all. */
|
|
10573
|
+
interface SandboxReconcileProviderHandle {
|
|
10574
|
+
provider: {
|
|
10575
|
+
probe?(sandboxId: string): Promise<{
|
|
10576
|
+
alive: boolean;
|
|
10577
|
+
state?: string;
|
|
10578
|
+
}>;
|
|
10579
|
+
};
|
|
10580
|
+
}
|
|
10581
|
+
interface SandboxReconcileDeps {
|
|
10582
|
+
/** Resolve a ledger row's provider slug to a handle. Mirrors
|
|
10583
|
+
* `SandboxGcReapDeps.resolveProvider` (`sandbox-gc.ts`) — same DI shape,
|
|
10584
|
+
* narrower need (probe only, never connect/boot). */
|
|
10585
|
+
resolveProvider: (slug: string) => Promise<SandboxReconcileProviderHandle | null>;
|
|
10586
|
+
/** Defaults to `readSandboxLedger` — injectable so callers that already
|
|
10587
|
+
* hold a mocked/scoped ledger reader (tests, a scoped CLI mock) can reuse
|
|
10588
|
+
* it instead of this module reaching for the real `~/.agentproto/
|
|
10589
|
+
* sandboxes.json` on disk. */
|
|
10590
|
+
readLedger?: (path?: string) => SandboxLedgerEntry[];
|
|
10591
|
+
/** Defaults to `recordSandboxLiveness` — same injectability reasoning. */
|
|
10592
|
+
recordLiveness?: (sandboxId: string, alive: boolean, path?: string) => void;
|
|
10593
|
+
ledgerPath?: string;
|
|
10594
|
+
}
|
|
10595
|
+
/** Verdict for one reconciled row. `"skipped"` means the provider has no
|
|
10596
|
+
* `probe()` (e.g. Box today) — never treated as evidence of death. */
|
|
10597
|
+
type SandboxReconcileVerdict = "alive" | "gone" | "unknown" | "skipped";
|
|
10598
|
+
interface SandboxReconcileRow {
|
|
10599
|
+
sandboxId: string;
|
|
10600
|
+
provider: string;
|
|
10601
|
+
verdict: SandboxReconcileVerdict;
|
|
10602
|
+
/** Present when `verdict` is "unknown" — the resolve/probe failure message. */
|
|
10603
|
+
error?: string;
|
|
10604
|
+
}
|
|
10605
|
+
interface SandboxReconcileResult {
|
|
10606
|
+
checked: number;
|
|
10607
|
+
alive: number;
|
|
10608
|
+
gone: number;
|
|
10609
|
+
unknown: number;
|
|
10610
|
+
skipped: number;
|
|
10611
|
+
rows: SandboxReconcileRow[];
|
|
10612
|
+
}
|
|
10613
|
+
/**
|
|
10614
|
+
* Probe every ledger row still claiming to be alive against its provider,
|
|
10615
|
+
* and stamp the verdict back into the ledger (`recordSandboxLiveness` flips
|
|
10616
|
+
* a dead row to `"gone"`; a live or unprobeable row is left as-is). One
|
|
10617
|
+
* broken row's provider resolution or probe call never aborts the pass for
|
|
10618
|
+
* the rest — each row's outcome is independent and best-effort.
|
|
10619
|
+
*/
|
|
10620
|
+
declare function reconcileSandboxLedger(deps: SandboxReconcileDeps): Promise<SandboxReconcileResult>;
|
|
10621
|
+
|
|
7990
10622
|
/**
|
|
7991
10623
|
* Idle agent-session reaper (PR-6).
|
|
7992
10624
|
*
|
|
@@ -8247,6 +10879,108 @@ declare function formatToolCall(toolName: string, args: unknown): string;
|
|
|
8247
10879
|
*/
|
|
8248
10880
|
declare function formatToolResult(toolName: string | undefined, result: unknown, isError: boolean): string | null;
|
|
8249
10881
|
|
|
10882
|
+
/**
|
|
10883
|
+
* Pure sizing helper for a `tools/list` response — shared by
|
|
10884
|
+
* `scripts/measure-mcp-tools.mjs` (human-readable before/after report) and
|
|
10885
|
+
* the budget-guard test (`__tests__/tool-schema-budget.test.ts`) so both
|
|
10886
|
+
* measure the exact same thing: the JSON bytes an MCP client actually pays
|
|
10887
|
+
* for per tool (`description` + `inputSchema`), sorted biggest first.
|
|
10888
|
+
*/
|
|
10889
|
+
interface MeasuredToolEntry {
|
|
10890
|
+
name: string;
|
|
10891
|
+
bytes: number;
|
|
10892
|
+
}
|
|
10893
|
+
interface ToolListMeasurement {
|
|
10894
|
+
entries: MeasuredToolEntry[];
|
|
10895
|
+
totalBytes: number;
|
|
10896
|
+
}
|
|
10897
|
+
interface MeasurableTool {
|
|
10898
|
+
name: string;
|
|
10899
|
+
description?: string;
|
|
10900
|
+
inputSchema?: unknown;
|
|
10901
|
+
}
|
|
10902
|
+
/** UTF-8 byte size of the `{description, inputSchema}` JSON a client
|
|
10903
|
+
* receives for one tool — the same two fields `tools/list` sends over the
|
|
10904
|
+
* wire, so this tracks prompt-token cost rather than just character count. */
|
|
10905
|
+
declare function measureTool(tool: MeasurableTool): number;
|
|
10906
|
+
declare function measureToolList(tools: readonly MeasurableTool[]): ToolListMeasurement;
|
|
10907
|
+
|
|
10908
|
+
/**
|
|
10909
|
+
* Long-form MCP tool documentation, read from `docs/mcp-tools/<name>.md`.
|
|
10910
|
+
*
|
|
10911
|
+
* The schema `description` on a tool/field is a contract: what it does, and
|
|
10912
|
+
* the one rule that prevents a wrong call — kept short so `tools/list` stays
|
|
10913
|
+
* cheap. Everything else (edge cases, interactions with other fields,
|
|
10914
|
+
* examples, history) lives here instead, fetched on demand by the `tool_help`
|
|
10915
|
+
* MCP tool (`tool-help-mcp.ts`) and the `agentproto help <tool>` CLI verb
|
|
10916
|
+
* (`packages/cli/src/commands/help.ts`) — both call the two functions below,
|
|
10917
|
+
* so the markdown file is the single source both surfaces read.
|
|
10918
|
+
*
|
|
10919
|
+
* `docs/mcp-tools/` sits at the PACKAGE root (a sibling of `src`/`dist`, not
|
|
10920
|
+
* the repo-level `docs/`) so it ships inside the published npm tarball —
|
|
10921
|
+
* see the `files` array in `package.json`. Resolved relative to this
|
|
10922
|
+
* module's own directory rather than by package name: whether this code is
|
|
10923
|
+
* running from `src` (vitest, no build) or `dist` (built/published), the
|
|
10924
|
+
* module's directory is always a direct child of the package root, so one
|
|
10925
|
+
* `..` reaches it either way — no `require.resolve` needed.
|
|
10926
|
+
*/
|
|
10927
|
+
/** Full markdown help text for `name`, or `undefined` when no doc exists. */
|
|
10928
|
+
declare function getToolHelp(name: string): string | undefined;
|
|
10929
|
+
/** Every tool name with a help doc available — for a "did you mean" list. */
|
|
10930
|
+
declare function listToolHelpTopics(): string[];
|
|
10931
|
+
/** Extract a `## <topic>` section (case-insensitive) up to the next `##`
|
|
10932
|
+
* heading — shared by the `tool_help` MCP tool and the `agentproto help`
|
|
10933
|
+
* CLI verb so both slice a doc identically. `undefined` when no section
|
|
10934
|
+
* with that heading exists. */
|
|
10935
|
+
declare function extractHelpSection(doc: string, topic: string): string | undefined;
|
|
10936
|
+
|
|
10937
|
+
/**
|
|
10938
|
+
* Subtree usage rollup: one session's own usage plus the summed usage of
|
|
10939
|
+
* every session it (transitively) spawned — the shape `session_usage
|
|
10940
|
+
* { includeSubtree: true }` and `GET /sessions/:id/usage?includeSubtree=1`
|
|
10941
|
+
* return.
|
|
10942
|
+
*
|
|
10943
|
+
* Pure over a descriptor list (walked by `parentSessionId`, the same graph
|
|
10944
|
+
* `collectSubtree` uses for orchestrator scoping), so it's unit-testable
|
|
10945
|
+
* without a registry.
|
|
10946
|
+
*
|
|
10947
|
+
* Absent stays absent: a total is the sum of the sessions that REPORT the
|
|
10948
|
+
* field, and is omitted entirely when none of them do — a subtree of
|
|
10949
|
+
* cost-less sessions never reads as a measured $0.
|
|
10950
|
+
*/
|
|
10951
|
+
|
|
10952
|
+
/** Summed usage across the root and all its descendants. */
|
|
10953
|
+
interface SubtreeUsageTotals {
|
|
10954
|
+
/** Sessions counted, root included. */
|
|
10955
|
+
sessions: number;
|
|
10956
|
+
costUsd?: number;
|
|
10957
|
+
tokensIn?: number;
|
|
10958
|
+
tokensOut?: number;
|
|
10959
|
+
cacheReadTokens?: number;
|
|
10960
|
+
cacheWriteTokens?: number;
|
|
10961
|
+
}
|
|
10962
|
+
/** One direct child of the root, with that child's OWN usage (its own
|
|
10963
|
+
* descendants roll up into `subtree`, not here). */
|
|
10964
|
+
interface SubtreeChildUsage {
|
|
10965
|
+
id: string;
|
|
10966
|
+
label?: string;
|
|
10967
|
+
status: SessionDescriptor["status"];
|
|
10968
|
+
costUsd?: number;
|
|
10969
|
+
tokensIn?: number;
|
|
10970
|
+
tokensOut?: number;
|
|
10971
|
+
}
|
|
10972
|
+
interface SessionUsageWithSubtree {
|
|
10973
|
+
self: SessionUsage;
|
|
10974
|
+
subtree: SubtreeUsageTotals;
|
|
10975
|
+
children: SubtreeChildUsage[];
|
|
10976
|
+
}
|
|
10977
|
+
/**
|
|
10978
|
+
* Roll `root`'s usage up over its subtree. `all` should be the FULL
|
|
10979
|
+
* descriptor list (archived included) so an archived intermediate session
|
|
10980
|
+
* doesn't sever the parent→child chain.
|
|
10981
|
+
*/
|
|
10982
|
+
declare function rollupSessionSubtree(root: SessionDescriptor, all: readonly SessionDescriptor[]): SessionUsageWithSubtree;
|
|
10983
|
+
|
|
8250
10984
|
/**
|
|
8251
10985
|
* Dependency-injection surface for MCP credential resolution.
|
|
8252
10986
|
*
|
|
@@ -8351,6 +11085,26 @@ declare function enrichRollupWithProviderQuota(rollup: UsageRollup, window: stri
|
|
|
8351
11085
|
* such lines; skips any malformed or non-`usage_snapshot` line. Never throws. */
|
|
8352
11086
|
declare function readUsageSnapshots(sessionId: string, baseDir?: string): Promise<UsageSnapshotRecord[]>;
|
|
8353
11087
|
|
|
11088
|
+
/**
|
|
11089
|
+
* Smart default for `features.llmEndpoint` (daemon-managed-gateway): an
|
|
11090
|
+
* operator who already configured a named endpoint, an upstream credential
|
|
11091
|
+
* link, or an explicit `llm-endpoint` custom route is clearly USING the
|
|
11092
|
+
* proxy already — booting with the sidecar off (today's default) would
|
|
11093
|
+
* silently break their next `route:{gateway:"llm-endpoint"}` spawn on a
|
|
11094
|
+
* fresh install. An operator who never touched any of that gets the
|
|
11095
|
+
* unchanged, conservative default (off).
|
|
11096
|
+
*
|
|
11097
|
+
* An explicit `configured` value (`config.json`'s `features.llmEndpoint`,
|
|
11098
|
+
* profile-overlaid) always wins, in either direction — this resolver only
|
|
11099
|
+
* fills in the gap when the operator left the flag unset.
|
|
11100
|
+
*/
|
|
11101
|
+
/**
|
|
11102
|
+
* Resolve the EFFECTIVE `features.llmEndpoint` flag. `configured` is the
|
|
11103
|
+
* operator's merged config value (`undefined` when never set in either
|
|
11104
|
+
* `config.json` or the active profile).
|
|
11105
|
+
*/
|
|
11106
|
+
declare function resolveEffectiveLlmEndpointFlag(configured: boolean | undefined): Promise<boolean>;
|
|
11107
|
+
|
|
8354
11108
|
/**
|
|
8355
11109
|
* MCP tools that drive the pairing registry — the daemon's "let a client pair
|
|
8356
11110
|
* with me over an untrusted rendezvous" surface (design: DESIGN §6).
|
|
@@ -8372,6 +11126,90 @@ interface RegisterPairingToolsOptions {
|
|
|
8372
11126
|
}
|
|
8373
11127
|
declare function registerPairingTools(server: McpServer, opts: RegisterPairingToolsOptions): void;
|
|
8374
11128
|
|
|
11129
|
+
/**
|
|
11130
|
+
* Device registry — a read view over `PairingRegistry` (+, when wired,
|
|
11131
|
+
* `HostRegistry`) that presents each pairing/host as a "device" (DEVICES-PLAN
|
|
11132
|
+
* PR-A, hosts added PR-C). A paired CLIENT (today's `pair_offer`/`pair
|
|
11133
|
+
* accept`) gets `role: "client"`; a registered HOST (`pair offer --host` +
|
|
11134
|
+
* `devices add`, host-registry.ts) gets `role: "host"`.
|
|
11135
|
+
*
|
|
11136
|
+
* This is deliberately a thin computed view, not a second store: renaming
|
|
11137
|
+
* and revoking a client device mutate the same `pairings.json`
|
|
11138
|
+
* `PairingRegistry` already owns; a host device mutates the same
|
|
11139
|
+
* `hosts.json` `HostRegistry` — so `pair_*`/`/pairings` and
|
|
11140
|
+
* `device_*`/`/devices` are two surfaces over the same two sources of truth.
|
|
11141
|
+
*/
|
|
11142
|
+
|
|
11143
|
+
type DeviceRole = "client" | "host";
|
|
11144
|
+
type DeviceKind = "browser" | "cli" | "daemon";
|
|
11145
|
+
interface Device {
|
|
11146
|
+
fingerprint: string;
|
|
11147
|
+
name: string;
|
|
11148
|
+
role: DeviceRole;
|
|
11149
|
+
kind: DeviceKind;
|
|
11150
|
+
rendezvous: string;
|
|
11151
|
+
createdAt: string;
|
|
11152
|
+
lastSeen: string;
|
|
11153
|
+
/** A channel (offer or reconnect) is served for this device right now
|
|
11154
|
+
* (client) or a `forwardHttp` call is in flight for it right now (host).
|
|
11155
|
+
* For a host this is NOT a live heartbeat — see host-registry.ts's
|
|
11156
|
+
* "Online tracking" — it only reflects actual recent/current traffic. */
|
|
11157
|
+
online: boolean;
|
|
11158
|
+
/** A pair/v1 pairing: listed and revocable, but can't connect until
|
|
11159
|
+
* re-paired — see `PairingRecord.legacy`. */
|
|
11160
|
+
legacy?: true;
|
|
11161
|
+
/** Set when this device's pairing/host record was granted under a
|
|
11162
|
+
* HOST-scoped offer (`agentproto pair offer --host`) — see
|
|
11163
|
+
* offer-url.ts's "Offer scope". Surfaced from either side of a pairing so
|
|
11164
|
+
* a user can see, from EITHER daemon, which of their pairings/hosts grant
|
|
11165
|
+
* host control. */
|
|
11166
|
+
scope?: "host";
|
|
11167
|
+
}
|
|
11168
|
+
interface DeviceRegistry {
|
|
11169
|
+
/** Every known device. Read-only. */
|
|
11170
|
+
list(): Promise<Device[]>;
|
|
11171
|
+
/** Rename a device (fingerprint or current name) to a new label. Tries the
|
|
11172
|
+
* pairing registry first, then the host registry. Returns false when
|
|
11173
|
+
* nothing matched. */
|
|
11174
|
+
rename(idOrName: string, newName: string): Promise<boolean>;
|
|
11175
|
+
/** Drop a device by fingerprint or name — same effect as `pair revoke` for
|
|
11176
|
+
* a client device, `devices revoke`-of-a-host for a host device. Tries
|
|
11177
|
+
* the pairing registry first, then the host registry. Returns false when
|
|
11178
|
+
* nothing matched. */
|
|
11179
|
+
revoke(idOrName: string): Promise<boolean>;
|
|
11180
|
+
/** Register a host from an offer URL (delegates to `HostRegistry.add`).
|
|
11181
|
+
* Rejects if no `HostRegistry` was wired into this device registry. */
|
|
11182
|
+
add(offerUrl: string, name?: string): Promise<{
|
|
11183
|
+
fingerprint: string;
|
|
11184
|
+
name: string;
|
|
11185
|
+
rendezvousUrl: string;
|
|
11186
|
+
}>;
|
|
11187
|
+
}
|
|
11188
|
+
declare function createDeviceRegistry(pairing: PairingRegistry, hosts?: HostRegistry): DeviceRegistry;
|
|
11189
|
+
|
|
11190
|
+
/**
|
|
11191
|
+
* MCP tools over the device registry (DEVICES-PLAN PR-A/PR-C) — `device_list`
|
|
11192
|
+
* / `device_rename` / `device_revoke` / `device_add`. A view on top of the
|
|
11193
|
+
* same registries `pair_offer`/`pair_list`/`pair_revoke` (pairing-tools.ts)
|
|
11194
|
+
* and host-registry.ts drive; those keep working unchanged. `device_revoke`
|
|
11195
|
+
* has the exact effect of `pair_revoke` for a client device.
|
|
11196
|
+
*
|
|
11197
|
+
* `device_add` (PR-C) registers a HOST from a `--host`-scoped offer URL —
|
|
11198
|
+
* see `HostRegistry.add`'s scope gate (host-registry.ts). It requires the
|
|
11199
|
+
* gateway to have wired a `HostRegistry` into the `DeviceRegistry`; without
|
|
11200
|
+
* one the call rejects with a clear message rather than a missing-tool error
|
|
11201
|
+
* (this repo always wires one when `pairingRegistry` is wired, so that's
|
|
11202
|
+
* only reachable via a deliberate host misconfiguration).
|
|
11203
|
+
*
|
|
11204
|
+
* Registered beside `pair_*` — same closure-rebind pattern; the registry
|
|
11205
|
+
* singleton lives on the gateway.
|
|
11206
|
+
*/
|
|
11207
|
+
|
|
11208
|
+
interface RegisterDeviceToolsOptions {
|
|
11209
|
+
registry: DeviceRegistry;
|
|
11210
|
+
}
|
|
11211
|
+
declare function registerDeviceTools(server: McpServer, opts: RegisterDeviceToolsOptions): void;
|
|
11212
|
+
|
|
8375
11213
|
/**
|
|
8376
11214
|
* Rate-limited failure logging for reconnect loops.
|
|
8377
11215
|
*
|
|
@@ -9209,6 +12047,15 @@ interface SessionWaitResult {
|
|
|
9209
12047
|
* reported one. `"error"` means the adapter reported a failed turn.
|
|
9210
12048
|
* Same three-branch coverage as `empty`. */
|
|
9211
12049
|
reason?: string;
|
|
12050
|
+
/** The matched turn-end's captured error text — `SessionTurnEndEvent.error`
|
|
12051
|
+
* on the ring-replay/bus branches, `SessionDescriptor.lastTurnErrorMessage`
|
|
12052
|
+
* on the sync fast-path (no event object to read there). Present only
|
|
12053
|
+
* when `reason` is `"error"` AND the adapter emitted an in-band `error`
|
|
12054
|
+
* stream event during the turn. Same three-branch coverage as `empty`/
|
|
12055
|
+
* `reason` — this is the field a waiter (a supervisor session, `sessions
|
|
12056
|
+
* wait`, a webhook) needed to actually learn WHY a turn errored instead
|
|
12057
|
+
* of just that it did. */
|
|
12058
|
+
error?: string;
|
|
9212
12059
|
}
|
|
9213
12060
|
/**
|
|
9214
12061
|
* Block until one of the listed sessions fires a matching lifecycle event
|
|
@@ -9358,6 +12205,12 @@ interface CreateGatewayOptions {
|
|
|
9358
12205
|
* to an `enabled: false` summary. Also surfaced in `daemon_health` /
|
|
9359
12206
|
* `GET /health` so an operator can see the effective value. */
|
|
9360
12207
|
resumeSessionsOnBoot?: boolean;
|
|
12208
|
+
/** Opt-in continue-on-boot. Mirrors the `daemon.continueInterruptedOnBoot`
|
|
12209
|
+
* config knob; when true, the `continueInterruptedOnBoot()` handle method
|
|
12210
|
+
* runs the boot continue pass (see `continue-interrupted.ts`); when
|
|
12211
|
+
* false/omitted it short-circuits to an `enabled: false` summary. Surfaced
|
|
12212
|
+
* in `daemon_health` / `GET /health`. */
|
|
12213
|
+
continueInterruptedOnBoot?: boolean;
|
|
9361
12214
|
/** Idle agent-session reaper threshold in ms (PR-6). Mirrors the
|
|
9362
12215
|
* `daemon.idleReapAfterMs` config knob; the CLI resolves it (env >
|
|
9363
12216
|
* config > off) and passes it here. When positive, the gateway starts a
|
|
@@ -9558,6 +12411,16 @@ interface CreateGatewayOptions {
|
|
|
9558
12411
|
* `worktree_gc` returns a clear "not enabled" error.
|
|
9559
12412
|
*/
|
|
9560
12413
|
runWorktreeGc?: WorktreeGcRunner;
|
|
12414
|
+
/**
|
|
12415
|
+
* Optional branch-`gc` runner powering `branch_gc` (+ `POST /branches/gc`).
|
|
12416
|
+
* Injected for the same reason as `runWorktreeGc`. Omitted → `branch_gc`
|
|
12417
|
+
* returns a clear "not enabled" error.
|
|
12418
|
+
*/
|
|
12419
|
+
runBranchGc?: BranchGcRunner;
|
|
12420
|
+
/** Optional verdict recorder powering `branch_gc_verdict` (+ `POST /branches/gc/verdict`). */
|
|
12421
|
+
recordBranchGcVerdict?: BranchGcVerdictRecorder;
|
|
12422
|
+
/** Optional verdict reader powering `branch_gc_verdict_get`. */
|
|
12423
|
+
readBranchGcVerdict?: BranchGcVerdictReader;
|
|
9561
12424
|
/**
|
|
9562
12425
|
* Optional best-effort exit-time reclaim of ONE policy-provisioned
|
|
9563
12426
|
* (implicit) session's own worktree — powers `SessionDescriptor.
|
|
@@ -9601,12 +12464,41 @@ interface CreateGatewayOptions {
|
|
|
9601
12464
|
* returns) so the injected `serve` can capture the finished gateway.
|
|
9602
12465
|
*/
|
|
9603
12466
|
pairingRegistry?: PairingRegistry;
|
|
12467
|
+
/**
|
|
12468
|
+
* Optional HOST registry (see `createHostRegistry`, DEVICES-PLAN PR-C) —
|
|
12469
|
+
* the reverse-pairing "client" half of pair/v2, living daemon-side. When
|
|
12470
|
+
* wired alongside `pairingRegistry`, the gateway layers registered hosts
|
|
12471
|
+
* into `device_list`/`GET /devices`, and enables the `device_add` MCP tool
|
|
12472
|
+
* plus `POST /devices/add` + `POST /devices/:id/exec`. Unlike
|
|
12473
|
+
* `pairingRegistry`, this registry keeps no standing connections — every
|
|
12474
|
+
* `add`/`forwardHttp` dials fresh — so there is nothing for the gateway to
|
|
12475
|
+
* autoconnect or tear down on shutdown.
|
|
12476
|
+
*/
|
|
12477
|
+
hostRegistry?: HostRegistry;
|
|
9604
12478
|
/** Enable the local LLM Endpoint proxy sidecar (route registration,
|
|
9605
12479
|
* MCP tools, child-process lifecycle). Default false — the endpoint is
|
|
9606
12480
|
* an opt-in feature; when off, the `llm-endpoint` custom route is not
|
|
9607
12481
|
* registered and the `llm_endpoint_*` MCP tools are not exposed. */
|
|
9608
12482
|
llmEndpoint?: boolean;
|
|
9609
12483
|
}
|
|
12484
|
+
/**
|
|
12485
|
+
* Default always-on set when `deferredTools` is enabled without an
|
|
12486
|
+
* explicit `alwaysOn` override: the core spawn/drive/observe/report loop
|
|
12487
|
+
* most sessions need immediately — including an EXECUTOR (the role
|
|
12488
|
+
* `deferredTools` defaults on for, see `role.ts`), which cannot spawn
|
|
12489
|
+
* (`agent_start`/`agent_prompt` are stripped for it anyway by
|
|
12490
|
+
* `toolPolicy.delegation: "deny"`, so those two names are simply absent
|
|
12491
|
+
* from a deny-role's registry and cost nothing) but still needs to report
|
|
12492
|
+
* back to its parent (`message_parent`), coordinate over the task ledger
|
|
12493
|
+
* (`task_*`), and observe its own session/turn state
|
|
12494
|
+
* (`session_context_status` alongside the pre-existing `session_list` /
|
|
12495
|
+
* `session_monitor` / `session_events_poll`). Everything else (fs_*,
|
|
12496
|
+
* directory_*, command_*, remote_*, browser_*, terminal_*, mcp_*,
|
|
12497
|
+
* routine_*, workflow_*, policy_*, inbound_watcher_*, session_tree,
|
|
12498
|
+
* agent_export, adapter_list, every `app_ui_*` panel tool, ...) starts
|
|
12499
|
+
* deferred — reachable via `tool_search`, never removed from `tools/call`.
|
|
12500
|
+
*/
|
|
12501
|
+
declare const DEFAULT_ALWAYS_ON_TOOLS: readonly string[];
|
|
9610
12502
|
interface GatewayHandle {
|
|
9611
12503
|
url: string;
|
|
9612
12504
|
workspace: string;
|
|
@@ -9624,6 +12516,10 @@ interface GatewayHandle {
|
|
|
9624
12516
|
* `CreateGatewayOptions.pairingRegistry`. Undefined otherwise. Exposed so
|
|
9625
12517
|
* the CLI can `startAutoconnect()` after boot and `shutdown()` it. */
|
|
9626
12518
|
pairing?: PairingRegistry;
|
|
12519
|
+
/** HOST registry, when one was wired via `CreateGatewayOptions.hostRegistry`
|
|
12520
|
+
* (DEVICES-PLAN PR-C). Undefined otherwise. No autoconnect/shutdown to
|
|
12521
|
+
* call on it — see that option's doc comment. */
|
|
12522
|
+
hosts?: HostRegistry;
|
|
9627
12523
|
/** Per-boot bearer token required on mutating /sessions/* routes
|
|
9628
12524
|
* + WS PTY upgrades. Exposed so an embedding host (e.g. the CLI
|
|
9629
12525
|
* shell that hosts the gateway in-process) can pass it to child
|
|
@@ -9648,6 +12544,16 @@ interface GatewayHandle {
|
|
|
9648
12544
|
resumeSessionsOnBoot(opts?: {
|
|
9649
12545
|
isServed?: (desc: SessionDescriptor) => boolean;
|
|
9650
12546
|
}): Promise<EagerResumeSummary>;
|
|
12547
|
+
/** Run the opt-in continue-on-boot pass and return its tally — a no-op
|
|
12548
|
+
* returning `{ enabled: false, ... }` when the `continueInterruptedOnBoot`
|
|
12549
|
+
* knob is off. serve.ts invokes this right AFTER `resumeSessionsOnBoot()`
|
|
12550
|
+
* (so a row whose eager resume failed is recognisable and skipped) — and
|
|
12551
|
+
* therefore, like it, after the supervisor was re-armed, so a re-armed
|
|
12552
|
+
* completion policy sees the continue turn's turn-end. Same `isServed`
|
|
12553
|
+
* cross-process gate. */
|
|
12554
|
+
continueInterruptedOnBoot(opts?: {
|
|
12555
|
+
isServed?: (desc: SessionDescriptor) => boolean;
|
|
12556
|
+
}): Promise<ContinueOnBootSummary>;
|
|
9651
12557
|
stop(): Promise<void>;
|
|
9652
12558
|
}
|
|
9653
12559
|
/**
|
|
@@ -9664,4 +12570,4 @@ interface GatewayHandle {
|
|
|
9664
12570
|
*/
|
|
9665
12571
|
declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
|
|
9666
12572
|
|
|
9667
|
-
export { type ActivityKind, type ActivityListFilter, type ActivityPolicyLister, type ActivityProjector, type ActivityProjectorRegistry, type ActivityProjectorSession, type ActivityRecord, type ActivitySource, type ActivityState, type ActivityWaitingOn, type ActivityWorkflowLister, AdapterAuthDescriptor, type AdapterCapabilitiesLister, type AdapterInstallResult, type AdapterListEntry, type AgentAdapterInstaller, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AnthropicReaderConfig, AnthropicRemainingQuotaReader, type AttachPolicyInput, type AttachSandboxOpts, type AttachSandboxResult, AuthMethod, BUCKETS_ROOT, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, CanonicalPosture, type CatalogModelsLister, CatalogModelsQuery, CatalogModelsResponse, type CatalogProviderModel, type CatalogProviderModelsQuery, type CatalogProviderModelsResponse, type CatalogProviderPricing, type ClaudeNativeRef, type CommitSpec, type CompletionPolicySupervisor, ContextProfile, type ConversationIndexRecord, type ConversationNativeRef, type CostBudgetDecision, type CrashDetectSummary, type CrashReaperRegistry, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEAD_SESSION_STATUSES, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DeclaredAdapterOption, type DeclaredAdapterPreset, type EagerResumeFailReason, type EagerResumeOutcome, type EagerResumeSkipReason, type EagerResumeSummary, EffortLevel, GC_REAPABLE_STATES, type GateSpec, type GatewayHandle, type HarnessPreset, type HarnessPresetValidationDeps, HarnessPresetValidationError, type HarnessPresetsFile, type HermesNativeRef, INBOUND_PROVIDERS, type IdleReapSummary, type IdleReaperRegistry, type InboundEndpoint, type InboundEndpointStore, type InboundEndpointStoreOptions, type InboundEnqueuePrompt, type InboundIsSessionAlive, type InboundMessage, type InboundProvider, type InboundRestartSession, type InboundRouteMode, type InboundRouterDeps, type InboundRouterLog, type InboundSpawnForContact, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type LocatedConversation, type LocatedConversationByPath, type McpCredentialDeps, type MigrationMarker, type NormalizeInboundResult, type OnFailSpec, type OpenPrResolver, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type OrphanReaperRegistry, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, Posture, type PostureResolution, type PrResolvedState, type PrStateResolver, type PresetInfo, type PricingResolver, type QuotaReadableProfile, type QuotaStoreFile, type QuotaStoreOptions, type ReconnectLogGate, type RegisterBrainToolsOptions, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, type RegisterWebSearchToolsOptions, type RemainingQuota, type RemainingQuotaReader, type ResolveNativeLinkInput, ResolvedAuthSpec, ResumeDisabledError, type ReuseResolution, type RouteAwareLaunchConfig, type RouteAwareLaunchConfigInput, RouteSpec, type RuntimeMeta, SESSION_ID_ENV, type SandboxAdapterInfo, type SandboxConnectionDescriptor, type SandboxGcCandidate, type SandboxGcProviderHandle, type SandboxGcReapDeps, type SandboxGcReapResult, type SandboxLedgerEntry, type SandboxLedgerState, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, SessionConfig, type SessionCurrentPhase, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionSnapshots, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type StallWatchdogRegistry, type StallWatchdogSummary, type StoredProfileQuota, TASK_STATUSES, type TaskCaller, type TaskChangedEvent, type TaskClaimInput, type TaskCreateInput, type TaskGateOutcome, type TaskGateRunner, type TaskLedger, type TaskLedgerRegistry, type TaskLedgerSessionSlice, type TaskListFilter, type TaskRecord, type TaskStatus, type TaskUpdateInput, type TaskVerification, type TaskVerifySupervisor, type TaskWriteResult, type TokenPricing, type TransmitterBinding, type TransmitterBindingStore, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageBucket, type UsageComputeInput, type UsageRollup, type UsageSnapshotRecord, type UsageSource, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, type WatcherDescriptor, type WatcherStartInput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WorkspaceBrainSubscriber, type WorkspaceBrainSubscriberOptions, type WorkspaceBrains, WorkspaceFs, type WorktreeAutoReclaimer, type WorktreeDecision, type WorktreeField, type WorktreeGcClass, type WorktreeGcOutcomeView, type WorktreeGcPlanEntryView, type WorktreeGcReclaimReason, type WorktreeGcResult, type WorktreeGcRunInput, type WorktreeGcRunner, WorktreeIsolationMode, type WorktreeProvisionOutcome, type WorktreeProvisionRequest, type WorktreeProvisioner, type WorktreeRequest, type WorktreeStatusLister, type WorktreeStatusView, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, composeSessionObservers, conversationIndexPath, createActivityProjector, createFileStepCache, createGateway, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createScopeTokenRegistry, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, defineBraveSearchHttpDriver, defineSerperHttpDriver, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, evaluateCostBudget, filterActivities, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, harnessPresetsPath, isAgentCliAuthConfigured, isClosedTaskStatus, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, loadHarnessPresets, loadQuotaStore, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, policyToActivities, policyWatchesSession, prToActivities, projectSessionUsage, readConversationIndex, readDaemonRegistry, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerPairingTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, removeSandboxLedgerEntry, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveReuseFromLedger, rollupUsage, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runStallWatchdogPass, sandboxLedgerPath, setDefaultPreset, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, verifyInboundSignature, webSearchTool, workflowToActivities, writeDaemonRegistryEntry };
|
|
12573
|
+
export { type ActivityKind, type ActivityListFilter, type ActivityPolicyLister, type ActivityProjector, type ActivityProjectorRegistry, type ActivityProjectorSession, type ActivityRecord, type ActivitySource, type ActivityState, type ActivityWaitingOn, type ActivityWorkflowLister, AdapterAuthDescriptor, type AdapterCapabilitiesLister, type AdapterInstallResult, type AdapterListEntry, type AgentAdapterInstaller, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AnthropicReaderConfig, AnthropicRemainingQuotaReader, type AttachPolicyInput, type AttachSandboxOpts, type AttachSandboxResult, AuthMethod, BUCKETS_ROOT, type BranchGcClass, type BranchGcCoverageView, type BranchGcHoldReason, type BranchGcKind, type BranchGcOutcomeView, type BranchGcPlanEntryView, type BranchGcPlanView, type BranchGcReclaimReason, type BranchGcResult, type BranchGcRunInput, type BranchGcRunner, type BranchGcStatus, type BranchGcSummaryView, type BranchGcTriageVerdict, type BranchGcVerdictInput, type BranchGcVerdictLookupInput, type BranchGcVerdictReader, type BranchGcVerdictRecordView, type BranchGcVerdictRecorder, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, CONTINUE_PROMPT_SOURCE, CanonicalPosture, type CatalogModelsLister, CatalogModelsQuery, CatalogModelsResponse, type CatalogProviderModel, type CatalogProviderModelsQuery, type CatalogProviderModelsResponse, type CatalogProviderPricing, type ClaudeNativeRef, type CommitSpec, type CompletionPolicySupervisor, ContextProfile, type ContinueInterruptedMode, type ContinueInterruptedOutcome, type ContinueInterruptedResult, type ContinueInterruptedSkipReason, type ContinueOnBootSummary, type ConversationIndexRecord, type ConversationNativeRef, type CostBudgetDecision, type CrashDetectSummary, type CrashReaperRegistry, type CreateGatewayOptions, type CreateOfferInput, type CreateReviewRunnerOptions, type CreatedOffer, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, type DaemonReviewerHostDeps, DeclaredAdapterOption, type DeclaredAdapterPreset, DefaultsAdapterAuthConfig, type Device, type DeviceKind, type DeviceRegistry, type DeviceRole, type EagerResumeFailReason, type EagerResumeOutcome, type EagerResumeSkipReason, type EagerResumeSummary, EffortLevel, type EnableInput, type EnableResult, type ForwardHttpRequest, type ForwardHttpResponse, GC_REAPABLE_STATES, type GateSpec, type GatewayHandle, type GhRunner, HOSTS_VERSION, type HarnessPreset, type HarnessPresetValidationDeps, HarnessPresetValidationError, type HarnessPresetsFile, type HermesNativeRef, type HostRecord, type HostRegistry, type HostRegistryDeps, INBOUND_PROVIDERS, type IdleReapSummary, type IdleReaperRegistry, type InboundEndpoint, type InboundEndpointStore, type InboundEndpointStoreOptions, type InboundEnqueuePrompt, type InboundIsSessionAlive, type InboundMessage, type InboundProvider, type InboundRestartSession, type InboundRouteMode, type InboundRouterDeps, type InboundRouterLog, type InboundSpawnForContact, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type LedgerAnnotations, type LedgerEntry, type LedgerHostMeta, type LlmEndpointDescriptor, type LlmEndpointStatus, type LlmEndpointStatusReport, type LocatedConversation, type LocatedConversationByPath, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, type McpCredentialDeps, type MeasurableTool, type MeasuredToolEntry, type MessageDeliveryVia, type MessageFilter, type MessageKind, type MessageRelation, type MessageTreeNode, type MessageUrgency, type MigrationMarker, type NormalizeInboundResult, type OnFailSpec, type OpenPrResolver, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, type OrphanReaperRegistry, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, Posture, type PostureResolution, PrLookupError, type PrLookupErrorCode, type PrResolvedState, type PrStateResolver, type PrStatusSnapshot, type PresetInfo, type PricingResolver, type QuotaReadableProfile, type QuotaStoreFile, type QuotaStoreOptions, type ReconnectLogGate, type RegisterBrainToolsOptions, type RegisterBrowserInput, type RegisterDeviceToolsOptions, type RegisterPairingToolsOptions, type RegisterReviewToolsOptions, type RegisterSessionInput, type RegisterWebSearchToolsOptions, type RemainingQuota, type RemainingQuotaReader, type RemoteStatus, type ResolveNativeLinkInput, ResolvedAuthSpec, ResumeDisabledError, type ReuseResolution, type ReviewLedger, type ReviewLedgerFilter, type ReviewRun, type ReviewRunInput, type ReviewRunStatus, type ReviewRunner, type ReviewerRunResult, type ReviewerSessionHost, type RouteAwareLaunchConfig, type RouteAwareLaunchConfigInput, RouteSpec, type RuntimeMeta, SESSION_END_REASONS, SESSION_ID_ENV, type SandboxAdapterInfo, type SandboxConnectionDescriptor, type SandboxGcCandidate, type SandboxGcProviderHandle, type SandboxGcReapDeps, type SandboxGcReapResult, type SandboxLedgerEntry, type SandboxLedgerState, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type SandboxReconcileDeps, type SandboxReconcileProviderHandle, type SandboxReconcileResult, type SandboxReconcileRow, type SandboxReconcileVerdict, type ScopeTokenRegistry, SessionConfig, type SessionCurrentPhase, type SessionDescriptor, type SessionEndReason, type SessionKind, type SessionMessage, type SessionMessageFrom, type SessionMessageSentRecord, type SessionObserver, type SessionOutcome, type SessionOutcomeArtifact, type SessionOutcomeCompact, type SessionOutcomeLink, type SessionSnapshots, type SessionStatus, type SessionUsage, type SessionUsageWithSubtree, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, SpawnDefaultsConfig, type SpawnSessionInput, type StallWatchdogRegistry, type StallWatchdogSummary, type StoredProfileQuota, type SubtreeChildUsage, type SubtreeUsageTotals, TASK_STATUSES, type TaskCaller, type TaskChangedEvent, type TaskClaimInput, type TaskCreateInput, type TaskGateOutcome, type TaskGateRunner, type TaskLedger, type TaskLedgerRegistry, type TaskLedgerSessionSlice, type TaskListFilter, type TaskRecord, type TaskStatus, type TaskUpdateInput, type TaskVerification, type TaskVerifySupervisor, type TaskWriteResult, type TokenPricing, type ToolListMeasurement, type TransmitterBinding, type TransmitterBindingStore, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageBucket, type UsageComputeInput, type UsageDetail, type UsageReadContext, type UsageReadResult, type UsageRollup, type UsageSnapshotRecord, type UsageSource, UserPreset, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, type WatcherDescriptor, type WatcherStartInput, type WebSearchInput, type WebSearchOutput, type WebSearchResult, type WorkspaceBrainSubscriber, type WorkspaceBrainSubscriberOptions, type WorkspaceBrains, WorkspaceFs, type WorktreeAutoReclaimer, type WorktreeDecision, type WorktreeField, type WorktreeGcClass, type WorktreeGcOutcomeView, type WorktreeGcPlanEntryView, type WorktreeGcReclaimReason, type WorktreeGcResult, type WorktreeGcRunInput, type WorktreeGcRunner, WorktreeIsolationMode, type WorktreeProvisionOutcome, type WorktreeProvisionRequest, type WorktreeProvisioner, type WorktreeRequest, type WorktreeStatusLister, type WorktreeStatusView, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, compactOutcome, composeSessionObservers, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDeviceRegistry, createFileStepCache, createGateway, createHostRegistry, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createReviewLaneExecutor, createReviewLedger, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, defaultReviewLedgerRoot, defineBraveSearchHttpDriver, defineSerperHttpDriver, deriveSessionOutcome, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, filterActivities, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, getToolHelp, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, listToolHelpTopics, loadHarnessPresets, loadQuotaStore, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeRemote, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectSessionUsage, readConversationIndex, readDaemonRegistry, readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerDeviceTools, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resolveBucketSlug, resolveEffectiveLlmEndpointFlag, resolveNativeLink, resolvePosture, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, setDefaultPreset, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, verifyInboundSignature, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
|