@arnilo/prism 0.2.4 → 0.2.6

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agent-session/create-agent.d.ts +6 -0
  3. package/dist/agent-session/create-agent.js +13 -0
  4. package/dist/agent-session/event-subscriber.d.ts +17 -0
  5. package/dist/agent-session/event-subscriber.js +68 -0
  6. package/dist/agent-session/helpers.d.ts +44 -0
  7. package/dist/agent-session/helpers.js +194 -0
  8. package/dist/agent-session/session.d.ts +101 -0
  9. package/dist/agent-session/session.js +1568 -0
  10. package/dist/agent-session.d.ts +6 -105
  11. package/dist/agent-session.js +6 -1833
  12. package/dist/contracts-core/agent.d.ts +257 -0
  13. package/dist/contracts-core/agent.js +5 -0
  14. package/dist/contracts-core/compaction.d.ts +72 -0
  15. package/dist/contracts-core/compaction.js +2 -0
  16. package/dist/contracts-core/content.d.ts +116 -0
  17. package/dist/contracts-core/content.js +2 -0
  18. package/dist/contracts-core/extensions.d.ts +163 -0
  19. package/dist/contracts-core/extensions.js +2 -0
  20. package/dist/contracts-core/loop.d.ts +98 -0
  21. package/dist/contracts-core/loop.js +2 -0
  22. package/dist/contracts-core/persistence.d.ts +366 -0
  23. package/dist/contracts-core/persistence.js +9 -0
  24. package/dist/contracts-core/provider.d.ts +97 -0
  25. package/dist/contracts-core/provider.js +2 -0
  26. package/dist/contracts-core/resources.d.ts +44 -0
  27. package/dist/contracts-core/resources.js +7 -0
  28. package/dist/contracts-core/run-limits.d.ts +81 -0
  29. package/dist/contracts-core/run-limits.js +2 -0
  30. package/dist/contracts-core/session.d.ts +187 -0
  31. package/dist/contracts-core/session.js +131 -0
  32. package/dist/contracts-core.d.ts +13 -1425
  33. package/dist/contracts-core.js +10 -138
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/docs/0.1.0-readiness.md +10 -10
  37. package/docs/acp.md +2 -0
  38. package/docs/browser-automation.md +1 -1
  39. package/docs/coding-agent-tools.md +5 -4
  40. package/docs/coding-review-and-diagnostics.md +76 -0
  41. package/docs/coding-security.md +2 -0
  42. package/docs/coding-workspaces.md +69 -0
  43. package/docs/forge-integration.md +6 -0
  44. package/docs/index.md +5 -5
  45. package/docs/indexed-code-search.md +82 -0
  46. package/docs/language-intelligence.md +15 -0
  47. package/docs/migration.md +20 -0
  48. package/docs/process-sessions.md +58 -3
  49. package/docs/release-and-install.md +71 -3
  50. package/docs/work-artifacts-and-review.md +4 -0
  51. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.6] - 2026-08-16
4
+
5
+ ### Changed
6
+ - **Release 0.2.6 (plan 026)** is the fully-featured coding-agent-readiness cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.6: expected deltas are the version literal plus the Task 1-6 additive exports — PTY backend/handle types, the indexed-search seam, the workspace lifecycle, process/ACP recovery, the review manifest + diagnostics; zero removals; no `--allow-break`; freeze manifest `scripts/phase26-freeze-manifest.json` records per-task evidence tokens, state machines, caps, and the demand registry). Seven roadmap items with threat model T1-T8, ownership tests, no implicit activation, package budget, docs entry, and protected end-to-end evidence: (1) **host-selected PTY/interactive terminal backend** (`pty-backend`, plan 026 Task 1) — `createProcessSessions` gains an optional `ptyBackend` seam with explicit `capabilities.resize` (never duck-typed); `pty: true` without a backend fails byte-compatibly with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` before spawn; bounded terminal geometry/TERM/attach-timeout/resize-rate/metadata caps (`ERR_PRISM_PROCESS_PTY_LIMIT`), generic `ERR_PRISM_PROCESS_PTY_BACKEND` errors that never leak backend text, NUL rejected in PTY input as a policy error, terminal output stays untrusted with no parser/emulator, backend loss surfaces as `unknown` with `exitCode: null`. (2) **scalable indexed code-search seam** (`indexed-search`, Task 2) — `createIndexedRepositoryOperations` composes a host-owned incremental index backend (`update/remove/search/status/dispose`, `capabilities.semantic` explicit) with the bounded literal search as the unchanged default; `indexed_literal`/`semantic` modes fail closed on stale/failed/unsupported/untrusted indexes (`ERR_PRISM_INDEX_*`), no silent semantic-to-literal downgrade, results labeled `untrusted_index`, containment/score/snippet caps enforced, 100k-entry benchmark p95 <= 250 ms. (3) **ownership-scoped multi-repository and worktree lifecycle** (`workspace-lifecycle`, Task 3) — `createCodingWorkspaceLifecycle` over CheckpointStore CAS + LeaseStore fencing (`prism.coding-agent.workspace.v1` records, schemaVersion 1): deterministic `ws-` ids, locked worktrees with `prism-workspace:` reasons, credential-free remote fingerprints (sha256 of redacted URL + default branch, never the URL), idempotent create, verify revalidating repository/worktree identity, and a cleanup refusal matrix (dirty/locked/unowned/missing/mismatched/main) with `ERR_PRISM_WORKSPACE_*` codes; `GitOperations` gains worktree `lock`/`unlock` and `fingerprint()`. (4) **forge breadth demand-gated** (Task 4) — GitLab/Bitbucket adapters stay **deferred** in the demand registry (no named consumer/date/use case recorded), so no adapter source ships and the forge barrel contains no provider name; activation requires a recorded named consumer. (5) **durable ACP/live-task and managed-process recovery** (`durable-recovery`, Task 5) — `ProcessSessions` persists bounded intent/metadata before spawn in `prism.coding-agent.process.v1` records (never handles/env/tokens/raw output), serializes per-record transition CAS writes, and `recover()` is attach-if-attested reporting `attached|terminal|unknown` with no fabricated exit code and no PID probing; per-record leases (30 s/300 s) fence two replicas (split-brain conformance green on real Postgres); `PersistedAcpSession` gains an additive optional `activeRun` ref (0.2.5 records stay readable) and `createAcpRunRecovery` re-resolves status against `AgentRunLifecycle` (suspended keeps pending approval ids, unprovable in-flight -> unknown, never a restarted prompt) with durable ownership/version/fence-checked cancellation in `prism.coding-agent.cancel.v1` markers that never replays a pending/dispatched tool; `ERR_PRISM_RECOVERY_*` codes. (6) **bounded patch review and incremental diagnostics** (`review-diagnostics`, Task 6) — `createCodingPatchReviewManifest` builds a digest-bound manifest (patch sha256 + repository/worktree/base/head identity + changed paths/diffstat + checks + diagnostic summaries, never a raw patch body) composed over the server `ArtifactService`; `assertCodingPatchAccepted` derives `pending|accepted|rejected|superseded` bound to the exact artifact revision and digest, refuses stale acceptance, and never applies/commits/pushes/merges; `LanguageIntelligence` gains opt-in full-content `syncDocument` (monotonic versions) and `diagnosticDelta` (push/pull with resultId reuse, stale-version guards) plus `normalizeDiagnostics` with deterministic added/removed/unchanged deltas and host-supplied check parsers; LSP stays strictly opt-in (nothing spawns from tool factories or agent assembly). (7) **protected real coding journey** (`coding-journey`, Task 7) — `scripts/phase26-coding-journey.test.mjs` runs a packed consumer through real host services only (provider call, digest-pinned Docker sandbox, Postgres worktree lifecycle, provider-driven ACP edit with policy approval, named check + `diagnosticDelta`, patch review over the server artifact store, cross-replica process recovery, durable cancellation, real GitHub push/lookup-before-create PR/reconcile/cleanup, host Playwright inspection, host PTY adapter in the frozen profile) under the frozen wall/cleanup ceilings with run-suffix side effects and per-step idempotent cleanup; missing credentials/services or skipped substeps record `blocked`, never a passing skip; the retained `scripts/phase26-coding-journey-report.json` carries timings/states/ids only and gates release evidence (pass/blocked/protected). Release graph stays **50** publishable manifests at exact **0.2.6**; zero new runtime dependency names (core remains dependency-free). Store compatibility with 0.2.5: **compatible, additive** (three new versioned record namespaces + the optional ACP `activeRun` field; rollback = stop 0.2.6 workers, mark active PTY/process/recovery records unknown, restore the 0.2.5 manifests/tag — see `docs/migration.md` `0.2.5 → 0.2.6`). Exit gate green (core + script gates incl. `phase26-freeze` 11, `phase26-index-benchmark`, coding-agent 348 + ag-ui 203 workspace suites, coverage gate, `sdk:ready`, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.6 — version literal + additive exports only, protected Postgres recovery/workspace conformance 8/8, protected PTY leg 4/4, protected real coding journey per the retained report, release-evidence manifest with zero blocked surfaces, evidence in `scripts/phase26-baseline.json`). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.2.6 publish handoff` — signed `v0.2.6` tag + npm OIDC).
7
+
8
+ ## [0.2.5] - 2026-08-15
9
+
10
+ ### Changed
11
+ - **Release 0.2.5 (plan 025)** is the maintainability-and-bounded-performance cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.5: expected deltas are the version literal plus 105 additive internal-helper exports — 84 Task 1 cross-family helpers across core/coding-agent/workflows/server/ag-ui and 21 `prism-session-store-codecs` helpers; zero removals; no `--allow-break`). Five roadmap items, no runtime contract change and no migration: (1) **god-module splits** (`god-module-split`, 0.1.4 precedent) — the six remaining implementation god-modules split along cohesive boundaries into internal family files behind preserved barrels: `src/contracts-core.ts` (1,719 L → 10 families, max 403 L), `src/agent-session.ts` (2,049 L → 4 modules; the 1,686-L `RuntimeAgentSession` class kept intact — a single TS class cannot span files without exporting private methods, recorded reason), `packages/workflows/src/run.ts` (1,227 L → 6 families, max 417 L), `packages/server/src/handler.ts` (1,005 L → 8 families, max 444 L), `packages/coding-agent/src/repository.ts` (974 L → 7 families, max 299 L), `packages/ag-ui/src/acp/agent.ts` (836 L → 8 families, max 386 L); no `exports`-map subpath added; `ponytail:` comments preserved; zero breaking compat deltas (`scripts/phase25-compat-diff.mjs`). (2) **persistence-mechanics dedup** (`session-store-codecs`) — 21 pure helpers (ownership scope/assertion, checkpoint stale/encode/decode, branch cursors, lifecycle quota/reason/page-limit, search metadata/clipping, deepFreeze/string-array/throwIfAborted, feedback row mapping) moved into the dependency-free codecs package (426 → 624 L, stdlib only); the postgres/sqlite adapters shrank 273 lines total; SQL dialect/DDL/query execution stay per-adapter; no persisted-shape change; cross-store conformance green before/after. (3) **quadratic accumulation removed** (`bounded-accumulation`) — the per-push `Buffer.concat` loops in `LspFrameReader` (language framing) and `summarizeTarStream` (tar parsing) became chunk-array readers (two-phase header parse, offset-advance drop, take/drop sliding window); caps + fail-closed overflow byte-identical; framing ~100–200× faster at 4,000 chunks (1,298.4 ms → 11.2 ms), tar linear at 8 MiB; CLI `collectOutput` audited and kept (already linear, plan 020); the 10-test near-limit probe `scripts/phase25-bounded-accumulation.test.mjs` is wired into the npm test gate segment and asserts linear copying by byte-count instrumentation. (4) **dead-code cleanup, internal-only** (`dead-code`) — the 62 `dead-exports.mjs` candidates triaged in `docs/_evidence/phase25-dead-exports-triage.md`: 2 internal removals (`PostgresPersistenceCloseOptions`, `SqlitePersistenceCloseOptions` — never re-exported from their adapter indexes), 37 test-used false positives, 20 dead-but-compat-tracked + 3 public type aliases deferred to the 0.3.0 breaking cut; the named-internal audit (`agent-session`/`cache-telemetry`/`skill-load`) recorded clean at 0.2.4; no public export removed. (5) **coverage close, behavior-backed** (`coverage`) — 76 focused regressions (approval 43, conversations 22, artifacts 6, tool-effect-store conformance 5; compaction via its 17 existing package suites); core coverage 90.53/84.20/90.54 → **91.43/84.80/91.60** lines/branches/functions (gate 60/70/75; all 39 non-protected packages above their evidence thresholds). Release graph stays **50** publishable manifests at exact **0.2.5**; zero new runtime dependency names (core remains dependency-free). Store compatibility with 0.2.4: **compatible, no migration** (no persisted-shape change; rollback = restore the 0.2.4 manifests/tag — downgrade is store-safe and code-safe, the added exports simply disappear). Exit gate green (core + script gates incl. `phase24-truth` 12 + `phase25-bounded-accumulation` 10, coverage gate, `sdk:ready`, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.5 — version literal + additive exports only, protected Postgres durable + cross-store conformance evidence per the release skip manifest, release-evidence manifest with zero blocked surfaces, evidence in `scripts/phase25-baseline.json`). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.2.5 publish handoff` — signed `v0.2.5` tag + npm OIDC).
12
+
3
13
  ## [0.2.4] - 2026-08-14
4
14
 
5
15
  ### Changed
@@ -0,0 +1,6 @@
1
+ /** create-agent (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
2
+ import type { Agent, AgentConfig, AgentSession, AgentSessionConfig } from "../contracts.js";
3
+ export declare function createAgent(config: AgentConfig): Agent;
4
+ export declare function createAgentSession(config: AgentSessionConfig & {
5
+ readonly agent: Agent;
6
+ }): AgentSession;
@@ -0,0 +1,13 @@
1
+ import { RuntimeAgentSession } from "./session.js";
2
+ export function createAgent(config) {
3
+ return {
4
+ config,
5
+ createSession(sessionConfig = {}) {
6
+ return createAgentSession({ ...sessionConfig, agent: this });
7
+ },
8
+ };
9
+ }
10
+ export function createAgentSession(config) {
11
+ return new RuntimeAgentSession(config);
12
+ }
13
+ //# sourceMappingURL=create-agent.js.map
@@ -0,0 +1,17 @@
1
+ /** event-subscriber (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
2
+ import type { AgentEvent, SubscribeOptions } from "../contracts.js";
3
+ export declare class EventSubscriber implements AsyncIterable<AgentEvent>, AsyncIterator<AgentEvent> {
4
+ private readonly sessionId;
5
+ private readonly onClose;
6
+ private readonly queue;
7
+ private readonly waiters;
8
+ private readonly maxQueuedEvents;
9
+ private readonly overflow;
10
+ private closed;
11
+ constructor(sessionId: string, options: SubscribeOptions, onClose: () => void);
12
+ [Symbol.asyncIterator](): AsyncIterator<AgentEvent>;
13
+ next(): Promise<IteratorResult<AgentEvent>>;
14
+ return(): Promise<IteratorResult<AgentEvent>>;
15
+ push(event: AgentEvent): void;
16
+ close(): void;
17
+ }
@@ -0,0 +1,68 @@
1
+ export class EventSubscriber {
2
+ sessionId;
3
+ onClose;
4
+ queue = [];
5
+ waiters = [];
6
+ maxQueuedEvents;
7
+ overflow;
8
+ closed = false;
9
+ constructor(sessionId, options, onClose) {
10
+ this.sessionId = sessionId;
11
+ this.onClose = onClose;
12
+ const maxQueuedEvents = options.maxQueuedEvents ?? 1024;
13
+ this.maxQueuedEvents = Number.isFinite(maxQueuedEvents) ? Math.max(1, Math.floor(maxQueuedEvents)) : 1024;
14
+ this.overflow = options.overflow ?? "close";
15
+ }
16
+ [Symbol.asyncIterator]() {
17
+ return this;
18
+ }
19
+ next() {
20
+ const event = this.queue.shift();
21
+ if (event)
22
+ return Promise.resolve({ value: event, done: false });
23
+ if (this.closed)
24
+ return Promise.resolve({ value: undefined, done: true });
25
+ return new Promise((resolve) => this.waiters.push(resolve));
26
+ }
27
+ return() {
28
+ this.close();
29
+ this.onClose();
30
+ return Promise.resolve({ value: undefined, done: true });
31
+ }
32
+ push(event) {
33
+ const waiter = this.waiters.shift();
34
+ if (waiter) {
35
+ waiter({ value: event, done: false });
36
+ return;
37
+ }
38
+ if (this.closed)
39
+ return;
40
+ if (this.queue.length < this.maxQueuedEvents) {
41
+ this.queue.push(event);
42
+ return;
43
+ }
44
+ if (this.overflow === "drop_oldest") {
45
+ this.queue.shift();
46
+ this.queue.push(event);
47
+ return;
48
+ }
49
+ if (this.overflow === "drop_newest")
50
+ return;
51
+ const droppedEvents = this.queue.length + 1;
52
+ this.queue.splice(0, this.queue.length, {
53
+ type: "event_subscriber_overflow",
54
+ sessionId: this.sessionId,
55
+ droppedEvents,
56
+ maxQueuedEvents: this.maxQueuedEvents,
57
+ overflow: this.overflow,
58
+ });
59
+ this.close();
60
+ this.onClose();
61
+ }
62
+ close() {
63
+ this.closed = true;
64
+ for (const waiter of this.waiters.splice(0))
65
+ waiter({ value: undefined, done: true });
66
+ }
67
+ }
68
+ //# sourceMappingURL=event-subscriber.js.map
@@ -0,0 +1,44 @@
1
+ /** helpers (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
2
+ import type { CompactionOptions, ContentBlock, ErrorInfo, Guardrails, Message, ProviderEvent, RetryOptions, ToolCallContent, Usage } from "../contracts.js";
3
+ import type { AgentInput } from "../input.js";
4
+ import { createId } from "../ids.js";
5
+ export declare function providerContent(event: Extract<ProviderEvent, {
6
+ type: "content_delta" | "tool_call";
7
+ }>): ContentBlock;
8
+ export declare function reconstructMissingToolCalls(deltas: readonly ProviderEvent[], calls: readonly ToolCallContent[]): readonly ToolCallContent[];
9
+ export declare function inputToMessages(input: AgentInput): Message[];
10
+ export declare function messageTextBytes(message: Message): number;
11
+ export declare class SteerSoftInterrupt extends Error {
12
+ readonly code = "steer_soft_interrupt";
13
+ constructor();
14
+ }
15
+ export declare function isSteerSoftInterrupt(error: unknown): boolean;
16
+ export declare function finalAssistantMessage(history: readonly Message[]): {
17
+ readonly message?: Message;
18
+ readonly content: readonly ContentBlock[];
19
+ readonly text: string;
20
+ };
21
+ export declare function errorFromInfo(error: ErrorInfo): Error;
22
+ export declare class ProviderTurnFailure extends Error {
23
+ readonly info: ErrorInfo;
24
+ readonly observable: boolean;
25
+ constructor(info: ErrorInfo, observable: boolean);
26
+ }
27
+ export declare function mergeRetry(agent: false | RetryOptions | undefined, run: false | RetryOptions | undefined): RetryOptions | undefined;
28
+ export declare function mergeCompaction(agent: false | CompactionOptions | undefined, run: false | CompactionOptions | undefined): CompactionOptions | undefined;
29
+ /**
30
+ * Durable-run gate: built-in option forms and the single-shot singleton are durable via the
31
+ * pending-call mechanism; a custom strategy must declare both snapshot and restore hooks.
32
+ */
33
+ export declare function isDurableLoop(loop: import("../contracts.js").AgentLoopStrategy | import("../contracts.js").AgentLoopOptions): boolean;
34
+ export declare function mergeGuardrails(agent: Guardrails | undefined, run: Guardrails | undefined): Guardrails | undefined;
35
+ export declare function withoutTrailingInput(messages: readonly Message[], input: readonly Message[]): Message[];
36
+ export declare function bridgeAbort(signal: AbortSignal | undefined, controller: AbortController): () => void;
37
+ export declare function throwIfAborted(signal: AbortSignal): void;
38
+ export declare function throwIfAbortedSignal(signal: AbortSignal | undefined): void;
39
+ export declare function jsonBytes(value: unknown): number;
40
+ export declare function createUsageAccumulator(): {
41
+ add(usage: Usage): void;
42
+ value(): Usage | undefined;
43
+ };
44
+ export declare const randomId: typeof createId;
@@ -0,0 +1,194 @@
1
+ import { createId } from "../ids.js";
2
+ import { reconstructToolCallDeltas } from "../provider-events.js";
3
+ import { singleShotLoop } from "../agent-loops.js";
4
+ export function providerContent(event) {
5
+ return event.type === "content_delta" ? event.content : event.call;
6
+ }
7
+ export function reconstructMissingToolCalls(deltas, calls) {
8
+ if (deltas.length === 0)
9
+ return [];
10
+ const seen = new Set(calls.map((call) => call.id));
11
+ return reconstructToolCallDeltas(deltas).filter((call) => !seen.has(call.id));
12
+ }
13
+ export function inputToMessages(input) {
14
+ if (typeof input === "string")
15
+ return [{ role: "user", content: [{ type: "text", text: input }] }];
16
+ if ("role" in input)
17
+ return [input];
18
+ return [...input];
19
+ }
20
+ const steerTextEncoder = new TextEncoder();
21
+ export function messageTextBytes(message) {
22
+ let total = 0;
23
+ for (const block of message.content) {
24
+ if (block.type === "text")
25
+ total += steerTextEncoder.encode(block.text).byteLength;
26
+ }
27
+ return total;
28
+ }
29
+ const STEER_SOFT_INTERRUPT_CODE = "steer_soft_interrupt";
30
+ export class SteerSoftInterrupt extends Error {
31
+ code = STEER_SOFT_INTERRUPT_CODE;
32
+ constructor() {
33
+ super("Provider turn soft-interrupted by steer");
34
+ this.name = "SteerSoftInterrupt";
35
+ }
36
+ }
37
+ export function isSteerSoftInterrupt(error) {
38
+ return (error instanceof SteerSoftInterrupt ||
39
+ (typeof error === "object" && error !== null && error.code === STEER_SOFT_INTERRUPT_CODE));
40
+ }
41
+ export function finalAssistantMessage(history) {
42
+ for (let index = history.length - 1; index >= 0; index -= 1) {
43
+ const message = history[index];
44
+ if (message.role !== "assistant")
45
+ continue;
46
+ const text = message.content
47
+ .filter((block) => block.type === "text")
48
+ .map((block) => block.text)
49
+ .join("");
50
+ return { message, content: message.content, text };
51
+ }
52
+ return { content: [], text: "" };
53
+ }
54
+ export function errorFromInfo(error) {
55
+ return Object.assign(new Error(error.message), { name: error.name ?? "Error", cause: error.cause, code: error.code });
56
+ }
57
+ export class ProviderTurnFailure extends Error {
58
+ info;
59
+ observable;
60
+ constructor(info, observable) {
61
+ super(info.message);
62
+ this.info = info;
63
+ this.observable = observable;
64
+ }
65
+ }
66
+ export function mergeRetry(agent, run) {
67
+ if (run === false)
68
+ return undefined;
69
+ if (run)
70
+ return { ...(agent || {}), ...run };
71
+ return agent || undefined;
72
+ }
73
+ export function mergeCompaction(agent, run) {
74
+ if (run === false)
75
+ return undefined;
76
+ if (run)
77
+ return { ...(agent || {}), ...run };
78
+ return agent || undefined;
79
+ }
80
+ /**
81
+ * Durable-run gate: built-in option forms and the single-shot singleton are durable via the
82
+ * pending-call mechanism; a custom strategy must declare both snapshot and restore hooks.
83
+ */
84
+ export function isDurableLoop(loop) {
85
+ if (typeof loop !== "object" || loop === null)
86
+ return true;
87
+ if ("strategy" in loop)
88
+ return true;
89
+ if (loop === singleShotLoop)
90
+ return true;
91
+ return typeof loop.snapshot === "function" && typeof loop.restore === "function";
92
+ }
93
+ export function mergeGuardrails(agent, run) {
94
+ if (!agent && !run)
95
+ return undefined;
96
+ return {
97
+ input: [...(agent?.input ?? []), ...(run?.input ?? [])],
98
+ output: [...(agent?.output ?? []), ...(run?.output ?? [])],
99
+ toolInput: [...(agent?.toolInput ?? []), ...(run?.toolInput ?? [])],
100
+ toolOutput: [...(agent?.toolOutput ?? []), ...(run?.toolOutput ?? [])],
101
+ maxConcurrency: run?.maxConcurrency ?? agent?.maxConcurrency,
102
+ };
103
+ }
104
+ export function withoutTrailingInput(messages, input) {
105
+ const next = [...messages];
106
+ for (let i = input.length - 1; i >= 0; i -= 1) {
107
+ const last = next.at(-1);
108
+ if (last && stableMessageKey(last) === stableMessageKey(input[i]))
109
+ next.pop();
110
+ }
111
+ return next;
112
+ }
113
+ // Key-order-insensitive comparison: a redacted-then-reassembled message with reordered
114
+ // keys must still dedupe against the trailing input, or auto-compaction duplicates it.
115
+ function stableMessageKey(value) {
116
+ if (Array.isArray(value))
117
+ return `[${value.map(stableMessageKey).join(",")}]`;
118
+ if (value !== null && typeof value === "object") {
119
+ const record = value;
120
+ return `{${Object.keys(record)
121
+ .sort()
122
+ .map((key) => `${JSON.stringify(key)}:${stableMessageKey(record[key])}`)
123
+ .join(",")}}`;
124
+ }
125
+ return JSON.stringify(value) ?? "null";
126
+ }
127
+ export function bridgeAbort(signal, controller) {
128
+ if (!signal)
129
+ return () => undefined;
130
+ const abort = () => controller.abort(signal.reason);
131
+ if (signal.aborted)
132
+ abort();
133
+ else
134
+ signal.addEventListener("abort", abort, { once: true });
135
+ return () => signal.removeEventListener("abort", abort);
136
+ }
137
+ export function throwIfAborted(signal) {
138
+ throwIfAbortedSignal(signal);
139
+ }
140
+ export function throwIfAbortedSignal(signal) {
141
+ if (signal?.aborted)
142
+ throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
143
+ }
144
+ const jsonTextEncoder = new TextEncoder();
145
+ export function jsonBytes(value) {
146
+ try {
147
+ return jsonTextEncoder.encode(JSON.stringify(value)).byteLength;
148
+ }
149
+ catch {
150
+ throw new TypeError("Provider request or event must be JSON-serializable for run limits");
151
+ }
152
+ }
153
+ export function createUsageAccumulator() {
154
+ const sums = new Map();
155
+ let costCurrency;
156
+ let costCompatible = true;
157
+ return {
158
+ add(usage) {
159
+ for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
160
+ const value = usage[key];
161
+ if (value !== undefined)
162
+ sums.set(key, (sums.get(key) ?? 0) + value);
163
+ }
164
+ const total = usage.totalTokens ??
165
+ (usage.inputTokens !== undefined || usage.outputTokens !== undefined
166
+ ? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
167
+ : undefined);
168
+ if (total !== undefined)
169
+ sums.set("totalTokens", (sums.get("totalTokens") ?? 0) + total);
170
+ if (usage.cost !== undefined && costCompatible) {
171
+ if (!sums.has("cost"))
172
+ costCurrency = usage.currency;
173
+ else if (usage.currency !== costCurrency)
174
+ costCompatible = false;
175
+ if (costCompatible)
176
+ sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
177
+ }
178
+ },
179
+ value() {
180
+ if (sums.size === 0)
181
+ return undefined;
182
+ const usage = {};
183
+ for (const [key, value] of sums) {
184
+ if (key !== "cost" || costCompatible)
185
+ usage[key] = value;
186
+ }
187
+ if (costCompatible && sums.has("cost") && costCurrency !== undefined)
188
+ usage.currency = costCurrency;
189
+ return Object.keys(usage).length > 0 ? usage : undefined;
190
+ },
191
+ };
192
+ }
193
+ export const randomId = createId;
194
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1,101 @@
1
+ /** session (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
2
+ import type { Agent, AgentEvent, AgentRunResult, AgentRunStateOptions, AgentSession, AgentSessionConfig, CompactionOptions, CompactionResult, OwnershipScope, RunDecision, RunOptions, SessionEntry, SteerOptions, SubscribeOptions } from "../contracts.js";
3
+ import type { AgentInput } from "../input.js";
4
+ import type { LoadedSkillBodiesEntry } from "../skill-load.js";
5
+ import type { StoredAgentRunState } from "../agent-run-state.js";
6
+ export declare class RuntimeAgentSession implements AgentSession {
7
+ readonly id: string;
8
+ private readonly agent;
9
+ private readonly metadata?;
10
+ private readonly store;
11
+ private readonly subscribers;
12
+ private currentLeafId?;
13
+ private history;
14
+ private activeRun?;
15
+ private activeRunId?;
16
+ private activeProviderTurnAbort?;
17
+ private pendingSoftInterrupt;
18
+ private pendingSteers;
19
+ private pendingSteerBytes;
20
+ private activeRedactor?;
21
+ private activeProvider?;
22
+ private activeLedger?;
23
+ private activeEffectStore?;
24
+ private activeOwnership?;
25
+ private activeIdentity?;
26
+ private activeIdempotencyKey?;
27
+ private activeGuardrails?;
28
+ private activeMetadata?;
29
+ private activeLimits?;
30
+ private activeLimitOutputBuffer;
31
+ private activeDurable?;
32
+ private activeLoop?;
33
+ /** Gated calls of the current tool round awaiting one collected suspension. */
34
+ private activeGatedRound?;
35
+ private activeLoopTurn;
36
+ private readonly loadedSkills;
37
+ /** Plan 018 Task 6 (closeout `checkpoint-bodies`): persisted exact instructions, registry-independent. */
38
+ private restoredSkillBodies;
39
+ /** Skills of the current run (for the bodies snapshot); replaced at each run start. */
40
+ private activeRunSkills;
41
+ /** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
42
+ restoreLoadedSkills(names: readonly string[]): void;
43
+ /** Plan 018 Task 6: restore persisted loaded-skill bodies (already validated fail-closed at load). */
44
+ restoreLoadedSkillBodies(bodies: readonly LoadedSkillBodiesEntry[]): void;
45
+ private ledgerChain;
46
+ private ledgerFailure;
47
+ private snapshotGeneration;
48
+ private snapshotCache?;
49
+ constructor(config: AgentSessionConfig & {
50
+ readonly agent: Agent;
51
+ });
52
+ get leafId(): string | undefined;
53
+ subscribe(options?: SubscribeOptions): AsyncIterable<AgentEvent>;
54
+ run(input: AgentInput, options?: RunOptions): Promise<AgentRunResult>;
55
+ steer(input: AgentInput, options?: SteerOptions): void;
56
+ resumeDurable(state: StoredAgentRunState, runState: AgentRunStateOptions, ownership?: OwnershipScope, signal?: AbortSignal, decisions?: ReadonlyMap<string, RunDecision>): Promise<AgentRunResult>;
57
+ recordDurableResumption(runId: string, interruption: import("../contracts.js").AgentRunInterruption, version: number, ownership?: OwnershipScope): Promise<void>;
58
+ recordDurableDenial(runId: string, interruption: import("../contracts.js").AgentRunInterruption, version: number, ownership?: OwnershipScope): Promise<void>;
59
+ private runInternal;
60
+ prompt(input: string, options?: RunOptions): Promise<AgentRunResult>;
61
+ stream(input: AgentInput, options?: RunOptions & SubscribeOptions): AsyncGenerator<AgentEvent>;
62
+ private buildRunResult;
63
+ private suspendDurable;
64
+ /** First attributed sticky whose scope and delegation path exactly match a nested decision. */
65
+ private matchNestedSticky;
66
+ /** First sticky decision whose scope exactly matches this call, if any. */
67
+ private matchStickyDecision;
68
+ /** Redacted pending-decision descriptor for one gated call; never carries raw arguments. */
69
+ private buildPendingDecision;
70
+ private persistDurable;
71
+ compact(options?: CompactionOptions): Promise<CompactionResult>;
72
+ abort(reason?: unknown): void;
73
+ entries(): Promise<readonly SessionEntry[]>;
74
+ checkout(leafId?: string): Promise<void>;
75
+ fork(options?: {
76
+ readonly leafId?: string;
77
+ }): AgentSession;
78
+ clone(options?: {
79
+ readonly id?: string;
80
+ readonly leafId?: string;
81
+ }): Promise<AgentSession>;
82
+ private branchReader;
83
+ private resolveRunProvider;
84
+ private resolveRunSkills;
85
+ private emit;
86
+ private closeSubscribers;
87
+ private drainLedger;
88
+ private generateWithRetry;
89
+ private generateProviderTurn;
90
+ private applyPendingSteers;
91
+ private applyProviderRequestPolicies;
92
+ private appendMessage;
93
+ private autoCompact;
94
+ private compactBranch;
95
+ private appendEntry;
96
+ private invalidateSnapshot;
97
+ private redact;
98
+ private redactProviderRequest;
99
+ private rebuildHistory;
100
+ private snapshot;
101
+ }