@agentproto/runtime 0.6.0 → 0.7.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/dist/index.d.ts CHANGED
@@ -495,6 +495,23 @@ interface SessionDescriptor {
495
495
  * the daemon's. Lets the UI show "crashed with the daemon" instead of a
496
496
  * bare "killed" that reads as deliberate. */
497
497
  endedReason?: "daemon-restart";
498
+ /** Whether a turn was actually in flight the INSTANT `status` flipped to
499
+ * "killed" — captured before anything else runs, because `busy` itself
500
+ * cannot be trusted after the fact: `runAgentTurn`'s `finally` is what
501
+ * clears `busy`, and that `finally` never fires for a generator that's
502
+ * never resumed (a killed child mid-tool-call, or a dead daemon), so a
503
+ * post-hoc read of `busy` on a killed session may just be showing you
504
+ * whatever it froze at. This field exists because `status: "killed"`
505
+ * alone can't tell a human's Stop mid-turn apart from a supervisor
506
+ * reaping a child that had already finished — both leave `exitCode:
507
+ * null`, and `turnsCompleted` alone is too weak (a session killed
508
+ * mid-SECOND-turn also has `turnsCompleted: 1`). `killedMidTurn: true`
509
+ * paired with any `turnsCompleted` means interrupted; `false`/absent
510
+ * alongside `turnsCompleted > 0` means the work was done before the
511
+ * kill — see activityFor in the vscode package for the read. Set by
512
+ * `kill()`, `shutdownImpl`'s force-kill, and `loadHistorySnapshot`'s
513
+ * wasAlive reclassification; absent for every other terminal path. */
514
+ killedMidTurn?: boolean;
498
515
  /** Last time anything was written to stdout/stderr. Lets the UI
499
516
  * spot stuck sessions ("running for 2h, last output 12min ago"). */
500
517
  lastOutputAt?: string;
@@ -514,6 +531,10 @@ interface SessionDescriptor {
514
531
  /** Free-text label the spawner can attach (e.g. conversation id,
515
532
  * operator name) so the UI can group/filter. */
516
533
  label?: string;
534
+ /** Derived from the session's FIRST prompt — what this conversation is
535
+ * about, for a UI that would otherwise show the adapter's argv. Distinct
536
+ * from `label`, which the spawner supplies and which always wins. */
537
+ title?: string;
517
538
  /** True when the session was spawned under a real PTY (node-pty)
518
539
  * instead of `child_process.spawn`. PTY sessions carry raw ANSI
519
540
  * bytes (alt-screen, key bindings, colors); attach goes through
@@ -529,6 +550,25 @@ interface SessionDescriptor {
529
550
  argv?: readonly string[];
530
551
  /** Working directory the session was spawned in. Cloned by restart. */
531
552
  cwd?: string;
553
+ /** Root of the git worktree the session was spawned in — the session→
554
+ * worktree edge, resolved from `cwd` at spawn time
555
+ * (`resolveWorktreeIdentity`). Distinct from `cwd`, which may be a
556
+ * subdirectory of it. Absent when `cwd` isn't inside a linked worktree (a
557
+ * plain checkout, a non-repo dir) and for every session persisted before
558
+ * this field existed.
559
+ *
560
+ * Recorded rather than computed at read time because it can't be
561
+ * recovered later: a worktree removed after the session ran leaves nothing
562
+ * on disk to re-resolve. */
563
+ worktreePath?: string;
564
+ /** Generation id of that worktree, read at spawn from the provision marker
565
+ * (`agentproto-worktree.json` in the worktree's private gitdir, written by
566
+ * `worktree.provision`). Absent whenever `worktreePath` is, and also for a
567
+ * worktree created by a bare `git worktree add` — nothing writes a marker
568
+ * there. The marker is what distinguishes generations, so a
569
+ * `worktreePath` without an id identifies a PATH, which a later worktree
570
+ * may reuse; the pair identifies one specific worktree. */
571
+ worktreeId?: string;
532
572
  /** Adapter slug for agent-cli sessions — restart uses this with
533
573
  * `/sessions/agent` to spin up a fresh ACP runtime. Undefined for
534
574
  * pty/command kinds. */
@@ -818,6 +858,22 @@ interface SessionsRegistry {
818
858
  enqueuePrompt(id: string, message: unknown, opts?: {
819
859
  interrupt?: boolean;
820
860
  }): Promise<void>;
861
+ /** Cancel the in-flight turn on a live agent-cli session and leave the
862
+ * session itself alive and idle — the bare "interrupt, no next prompt"
863
+ * primitive `sendPrompt`/`enqueuePrompt`'s `opts.interrupt` arm lacks
864
+ * on its own, since that arm always exists to redirect onto a NEW
865
+ * prompt. Reuses the same `interruptInFlightTurn` helper those two
866
+ * share.
867
+ *
868
+ * Idempotent by design: idle, unknown-alive (starting/running only —
869
+ * same liveness `validateAgentTurn` checks), or already-terminal
870
+ * (exited/killed/error) all resolve `{ wasBusy: false }` rather than
871
+ * throwing — a no-op interrupt is not an error. Throws only when the
872
+ * id is unknown, or when `cancel()` itself rejects ("does not support
873
+ * interrupt", propagated from `interruptInFlightTurn`). */
874
+ interruptSession(id: string): Promise<{
875
+ wasBusy: boolean;
876
+ }>;
821
877
  /** Stamp `lastActivityAt` on a live agent-cli session's descriptor
822
878
  * and schedule a debounced persist. Called from the `onActivity`
823
879
  * callback threaded down through the driver → ACP client, which
@@ -1789,6 +1845,19 @@ interface PolicyRunState {
1789
1845
  nextPolicyId?: string;
1790
1846
  error?: string;
1791
1847
  }
1848
+ /**
1849
+ * Does `policy` watch `sessionId`? True when the id is the representative
1850
+ * `sessionId` or appears anywhere in the fan-in `sessionIds` group — the two
1851
+ * ways `AttachPolicyInput` can name a session.
1852
+ *
1853
+ * This is the reverse of the policy→session link, and it is deliberately
1854
+ * computed on demand over `list()` rather than persisted: a session→policy
1855
+ * index in policies.json (or a `policies` field on the session record) would
1856
+ * be a second source of truth to keep in sync across attach, fan-in nudges,
1857
+ * `next` chaining, and snapshot reload. A filtered pass is O(policies) on a
1858
+ * set the daemon already holds in memory.
1859
+ */
1860
+ declare function policyWatchesSession(policy: PolicyRunState, sessionId: string): boolean;
1792
1861
  interface CompletionPolicySupervisor {
1793
1862
  /**
1794
1863
  * Attach a completion policy to an already-running session.
@@ -2566,6 +2635,158 @@ declare function declaredPresetToProviderPreset(decl: DeclaredAdapterPreset): Pr
2566
2635
  */
2567
2636
  declare function listPresets(env?: Record<string, string | undefined>, adapterPresets?: readonly DeclaredAdapterPreset[]): AdapterEntry<PresetInfo>[];
2568
2637
 
2638
+ /**
2639
+ * Per-workspace state buckets — AIP-46 §State partitioning.
2640
+ *
2641
+ * AIP-46's premise is "a single host serve many bound directories", and
2642
+ * §Workspaces delivered the addressing half of it: a slug resolves to a
2643
+ * path. The state those directories generate stayed pooled in one global
2644
+ * `~/.agentproto/sessions.json` with `workspaceSlug` demoted to a column
2645
+ * on each row — a label, not a boundary.
2646
+ *
2647
+ * What that costs is measurable rather than theoretical. `HISTORY_CAP`
2648
+ * bounds retention across the *union* of every workspace, so the cap is
2649
+ * spent by whoever was busiest. A store pooling a few hundred rows from
2650
+ * several workspaces sits at that ceiling in ordinary use, and the
2651
+ * workspace contributing a handful of them is one busy afternoon in a
2652
+ * NEIGHBOUR away from losing all of them — having done nothing itself.
2653
+ * The eviction is silent and lands on the quietest workspace.
2654
+ *
2655
+ * This module is the slug→bucket rule and nothing more:
2656
+ *
2657
+ * ~/.agentproto/
2658
+ * ├── workspaces.json # the registry (workspaces-config.ts)
2659
+ * └── workspaces/ # one bucket per workspace (here)
2660
+ * ├── agentik-studio/sessions.json
2661
+ * └── default/sessions.json
2662
+ *
2663
+ * The registry (a file) and the state (a directory) are deliberate
2664
+ * siblings: one names the workspaces, the other holds what each
2665
+ * accumulated.
2666
+ *
2667
+ * ## Membership is the validation
2668
+ *
2669
+ * `workspaceSlug` arrives on a spawn request — it is caller input
2670
+ * (`session-spawn.ts` passes `input.workspaceSlug` straight through when
2671
+ * an explicit `cwd` accompanies it, so nothing sanitises it on the way
2672
+ * to the descriptor). A bucket is a directory name. Joining the two
2673
+ * without a check hands the caller `../../` under the daemon's own state
2674
+ * root, as the daemon's UID.
2675
+ *
2676
+ * So a slug does not become a directory name by being cleaned up — it
2677
+ * becomes one by coming *back from* the registry lookup. The registry's
2678
+ * slugs were sanitised by `sanitizeSlug` on the way in, so the only
2679
+ * strings that ever reach the filesystem are ones the host itself
2680
+ * minted. An unregistered slug isn't rejected or escaped; it fails to
2681
+ * match and lands in `default` like any other unregistered work. This is
2682
+ * why the rule is membership rather than sanitisation: you cannot forget
2683
+ * to validate a value you never chose.
2684
+ *
2685
+ * ## What this does NOT buy
2686
+ *
2687
+ * Partitioning bounds what a workspace's state *costs* other workspaces.
2688
+ * It does not bound who may read it — every bucket is still served to
2689
+ * every authorised caller, because no workspace-scoped credential
2690
+ * exists to serve them differently (pairings carry no workspace concept
2691
+ * at all; the orchestrator scope-token's `session_list` is daemon-wide
2692
+ * by documented debt, `orchestrator-gateway.ts`). The files moved; the
2693
+ * access did not. AIP-46 §Security Considerations says this in the
2694
+ * negative — do not read this module as an isolation control.
2695
+ */
2696
+ /** The bucket every record lands in when its slug doesn't resolve —
2697
+ * absent, empty, or naming a workspace that isn't registered.
2698
+ *
2699
+ * A real bucket, not an error state. Refusing to persist an
2700
+ * unregistered session would make this a breaking change for every
2701
+ * one-off `cwd` spawn, and the obvious workaround (auto-register)
2702
+ * turns a registry of the user's intent into a registry of everything
2703
+ * that ever ran. The cost is stated plainly in the AIP: `default`'s
2704
+ * occupants get no separation from *each other* — registering a
2705
+ * workspace is what buys that. */
2706
+ declare const DEFAULT_BUCKET = "default";
2707
+ /** Root of the per-workspace state buckets. Sibling of the
2708
+ * `workspaces.json` registry that names them. */
2709
+ declare const BUCKETS_ROOT: () => string;
2710
+ /** The pre-partition global snapshot. Still read (to migrate from) and
2711
+ * never written after the split — see `migrateLegacySessionsFile`. */
2712
+ declare const LEGACY_SESSIONS_FILE: () => string;
2713
+ /** Marker recording that the legacy split already ran. Its presence —
2714
+ * not the legacy file's absence — is what makes migration
2715
+ * once-and-only-once, so a user who deletes rows from a bucket doesn't
2716
+ * get them resurrected on the next boot. */
2717
+ declare const migrationMarkerPath: (root: string) => string;
2718
+ declare const bucketDir: (root: string, slug: string) => string;
2719
+ declare const bucketSessionsFile: (root: string, slug: string) => string;
2720
+ /** Per-bucket transcript directory (AIP-46 §Layout).
2721
+ *
2722
+ * Not yet wired: the registry still writes transcripts to the shared
2723
+ * `~/.agentproto/sessions/`. Moving them needs the read side to move
2724
+ * too, and several readers currently ignore the configured base dir
2725
+ * entirely (`http-server.ts` and `transcript-export.ts` call
2726
+ * `sessionEventsPath(id)` with no `baseDir`, so they resolve off
2727
+ * `homedir()` regardless) — a pre-existing bug that a partial move
2728
+ * would turn into missing transcripts. Exported so the follow-up has
2729
+ * the path rule in one place. The AIP makes transcripts a SHOULD, not
2730
+ * a MUST, for exactly this reason. */
2731
+ declare const bucketTranscriptDir: (root: string, slug: string) => string;
2732
+ declare const isSafeBucketSlug: (slug: string) => boolean;
2733
+ /**
2734
+ * The slug→bucket rule (AIP-46 §Bucket resolution).
2735
+ *
2736
+ * 1. non-empty AND registered → that workspace's bucket
2737
+ * 2. otherwise → `default`
2738
+ *
2739
+ * `registered` must come from the workspaces registry. Passing a set
2740
+ * built from caller input defeats the entire point of this function.
2741
+ */
2742
+ declare function resolveBucketSlug(workspaceSlug: string | undefined | null, registered: ReadonlySet<string>): string;
2743
+ /** Registered slugs, read fresh. Cheap (the registry is <1KB) and read
2744
+ * per persist rather than cached at boot, so a workspace registered
2745
+ * while the daemon is up starts bucketing immediately instead of
2746
+ * silently pooling into `default` until restart. Never throws: a
2747
+ * missing/corrupt registry degrades to "nothing is registered", i.e.
2748
+ * everything lands in `default` — today's pooled behaviour, which is
2749
+ * the right failure direction. */
2750
+ declare function readRegisteredSlugs(configPath?: string): ReadonlySet<string>;
2751
+ /** Bucket directories that exist on disk. Order is not meaningful. */
2752
+ declare function listBuckets(root: string): string[];
2753
+ interface MigrationMarker {
2754
+ version: 1;
2755
+ migratedAt: string;
2756
+ /** Absolute path of the legacy artifact this split read from. Left
2757
+ * untouched on disk — recorded so the provenance survives. */
2758
+ from: string;
2759
+ /** Total records read out of the legacy artifact. */
2760
+ rows: number;
2761
+ /** Records landed, per bucket. Sums to `rows` — the migration never
2762
+ * drops a record it cannot place; it sends it to `default`. */
2763
+ byBucket: Record<string, number>;
2764
+ }
2765
+ /**
2766
+ * Split the legacy global snapshot into buckets (AIP-46 §Migration).
2767
+ *
2768
+ * **Additive by mandate.** The legacy file is opened read-only and left
2769
+ * exactly where it is — never deleted, truncated, or rewritten. It is
2770
+ * the user's data and their only rollback; the buckets are a derivative
2771
+ * until they decide otherwise. Reclaiming it is a separate, human-gated
2772
+ * call, and this module deliberately ships no GC: a real store is backed
2773
+ * by a transcript directory that can reach hundreds of megabytes and may
2774
+ * be someone's only copy of months of work — a deleter that guesses
2775
+ * wrong there is unrecoverable.
2776
+ *
2777
+ * Idempotent via the marker, not via the legacy file's absence — the
2778
+ * file stays, so absence would never fire and every boot would re-import
2779
+ * rows the user had since deleted from a bucket.
2780
+ *
2781
+ * Returns the marker it wrote, or `null` when there was nothing to do
2782
+ * (already migrated, or no legacy file).
2783
+ */
2784
+ declare function migrateLegacySessionsFile(opts: {
2785
+ root: string;
2786
+ legacyFile: string;
2787
+ registered: ReadonlySet<string>;
2788
+ }): MigrationMarker | null;
2789
+
2569
2790
  /** A journal-file StepCache scoped to one cacheKey. Best-effort: read/parse
2570
2791
  * failures degrade to "no cache" (a miss), never throw into the run. */
2571
2792
  declare function createFileStepCache(cacheKey: string, opts?: {
@@ -2844,6 +3065,30 @@ interface CreateGatewayOptions {
2844
3065
  * tool family, and the `/sessions/:id/pty` WebSocket. Without it,
2845
3066
  * those routes return 501 / the MCP tools aren't registered. */
2846
3067
  spawnPty?: PtyFactory;
3068
+ /** Enable filesystem persistence for every store this gateway owns —
3069
+ * the sessions registry plus the supervisor / routine / cron / workflow
3070
+ * / inbound-watcher run stores. Defaults to `true`, i.e. production is
3071
+ * unchanged.
3072
+ *
3073
+ * Tests pass `false`. Without it, a test gateway writes its fake rows
3074
+ * into the *developer's real* `~/.agentproto/sessions.json` (and the
3075
+ * sibling `policies.json` / `routine-runs.json` / `cron-jobs.json` /
3076
+ * `workflow-runs.json`): those paths resolve off `homedir()`, and the
3077
+ * gateway opted each store into persistence unconditionally. Since
3078
+ * `loadHistorySnapshot` re-reads sessions.json at boot, the real daemon
3079
+ * then restores the fakes and shows them in the dashboard — and against
3080
+ * `HISTORY_CAP` (200) they evict genuine history.
3081
+ *
3082
+ * The sibling stores already default to persist-off when constructed
3083
+ * directly (see `createRoutineRunner`); this knob is what lets a
3084
+ * gateway stop overriding that on their behalf. */
3085
+ persist?: boolean;
3086
+ /** Override the sessions-registry persistence path — tests pin a tmpdir
3087
+ * so an assertion can look at the file the gateway would have written
3088
+ * instead of the developer's real one. Defaults to
3089
+ * `~/.agentproto/sessions.json`. The structured-transcript dir follows
3090
+ * this path's parent, so pinning it isolates transcripts too. */
3091
+ persistPath?: string;
2847
3092
  /** Override the per-boot bearer token. Default: `randomUUID()`.
2848
3093
  * Tests can pin a known value; production should always let
2849
3094
  * the gateway generate fresh. The token is written into
@@ -2938,4 +3183,4 @@ interface GatewayHandle {
2938
3183
  */
2939
3184
  declare function createGateway(opts: CreateGatewayOptions): Promise<GatewayHandle>;
2940
3185
 
2941
- export { AdapterAuthDescriptor, type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEFAULT_ORCHESTRATOR_TOOLS, DeclaredAdapterOption, type DeclaredAdapterPreset, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, type McpCredentialDeps, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, PAIRINGS_VERSION, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, ResolvedAuthSpec, type RuntimeMeta, type SandboxAdapterInfo, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TokenPricing, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageComputeInput, type UsageSource, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, listPresets, makeBrowserAdapterLister, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, projectSessionUsage, readDaemonRegistry, readRuntimeMeta, registerPairingTools, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
3186
+ export { AdapterAuthDescriptor, type AdapterListEntry, type AgentAdapterLister, type AgentAdapterResolver, type AgentSessionLike, type AgentStreamEvent, type AttachPolicyInput, BUCKETS_ROOT, type BrowserAdapterHandle, type BrowserAdapterInfo, type BrowserAdapterLister, type BrowserAdapterResolver, BuildHeartbeatAgent, type CommitSpec, type CompletionPolicySupervisor, type CreateGatewayOptions, type CreateOfferInput, type CreatedOffer, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DeclaredAdapterOption, type DeclaredAdapterPreset, type GateSpec, type GatewayHandle, type InboundWatcher, type JudgeGateSpec, LEGACY_SESSIONS_FILE, type McpCredentialDeps, type MigrationMarker, type OnFailSpec, type OrchestratorGatewayDeps, type OrchestratorInjection, type OrchestratorInjector, type OrchestratorInjectorDeps, type OrchestratorMcpServerFactory, type OrchestratorScope, PAIRINGS_VERSION, type PairingChannelContext, type PairingChannelHandle, type PairingChannelMode, type PairingRecord, type PairingRegistry, type PairingRegistryDeps, type PendingPermission, type PermissionRespondInput, type PermissionRespondResult, type PolicyRunState, type PolicyRunStatus, type PresetInfo, type PricingResolver, type RegisterBrowserInput, type RegisterPairingToolsOptions, type RegisterSessionInput, ResolvedAuthSpec, type RuntimeMeta, type SandboxAdapterInfo, type SandboxProviderCapabilities, type SandboxProviderHandle, type SandboxProviderLister, type SandboxProviderResolver, type ScopeTokenRegistry, type SessionDescriptor, type SessionKind, type SessionObserver, type SessionStatus, type SessionUsage, type SessionWaitEvent, type SessionWaitResult, type SessionsRegistry, type ShellGateSpec, type SpawnAgentInput, type SpawnSessionInput, type TokenPricing, type TunnelDescriptor, type TunnelProvider, type TunnelStatus, type UsageComputeInput, type UsageSource, type WatcherDescriptor, type WatcherStartInput, WorkspaceFs, bucketDir, bucketSessionsFile, bucketTranscriptDir, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, formatToolCall, formatToolResult, getMcpCredentialDeps, isSafeBucketSlug, listBuckets, listPresets, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, policyWatchesSession, projectSessionUsage, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, resolveBucketSlug, setMcpCredentialDeps, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };