@arnilo/prism 0.5.6 → 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/CHANGELOG.md +81 -5
- package/README.md +10 -10
- package/dist/agent-approval.js +7 -6
- package/dist/agent-definitions.js +1 -0
- package/dist/agent-loops.js +51 -12
- package/dist/agent-run-lifecycle.js +11 -0
- package/dist/agent-run-state.d.ts +6 -0
- package/dist/agent-run-state.js +29 -9
- package/dist/agent-session/session/assemble.js +33 -2
- package/dist/agent-session/session/persist.js +6 -2
- package/dist/agent-session/session/tool-round.js +1 -0
- package/dist/agent-session/session/types.d.ts +10 -0
- package/dist/agent-session/session.d.ts +15 -0
- package/dist/agent-session/session.js +59 -4
- package/dist/agent-tool-dispatch.js +5 -4
- package/dist/artifacts.d.ts +39 -1
- package/dist/artifacts.js +73 -0
- package/dist/attention-compiler.d.ts +121 -0
- package/dist/attention-compiler.js +479 -0
- package/dist/cli-init.js +20 -6
- package/dist/content.d.ts +3 -16
- package/dist/content.js +9 -99
- package/dist/context-budget.d.ts +32 -2
- package/dist/context-budget.js +51 -19
- package/dist/contracts-core/agent.d.ts +18 -0
- package/dist/contracts-core/agent.js +4 -1
- package/dist/contracts-core/attention.d.ts +66 -0
- package/dist/contracts-core/attention.js +2 -0
- package/dist/contracts-core/compaction.d.ts +59 -0
- package/dist/contracts-core/compaction.js +77 -1
- package/dist/contracts-core/provider.d.ts +4 -0
- package/dist/contracts-core.d.ts +1 -0
- package/dist/contracts-core.js +1 -0
- package/dist/contracts-protocol.d.ts +29 -0
- package/dist/contracts-run-state.d.ts +6 -0
- package/dist/host-composition.d.ts +78 -0
- package/dist/host-composition.js +248 -0
- package/dist/index.d.ts +11 -8
- package/dist/index.js +6 -5
- package/dist/input.d.ts +19 -1
- package/dist/input.js +52 -2
- package/dist/media-types.d.ts +34 -0
- package/dist/media-types.js +158 -0
- package/dist/pinned-fetch.d.ts +2 -2
- package/dist/pinned-fetch.js +11 -12
- package/dist/redaction.js +74 -1
- package/dist/secure-agent.d.ts +2 -0
- package/dist/secure-agent.js +6 -1
- package/dist/session-stores.d.ts +11 -0
- package/dist/session-stores.js +23 -8
- package/dist/tool-result-fold.d.ts +12 -0
- package/dist/tool-result-fold.js +13 -6
- package/dist/tools.d.ts +10 -0
- package/dist/tools.js +41 -0
- package/docs/acp-agent.md +42 -11
- package/docs/acp.md +3 -2
- package/docs/ag-ui.md +9 -5
- package/docs/agent-definitions.md +9 -1
- package/docs/agent-events.md +6 -1
- package/docs/agent-loops.md +1 -1
- package/docs/agent-session-runtime.md +9 -7
- package/docs/attention-compiler.md +272 -0
- package/docs/browser-automation.md +5 -2
- package/docs/cli-rpc.md +4 -2
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +5 -3
- package/docs/coding-tools.md +1 -1
- package/docs/coding-workspaces.md +22 -0
- package/docs/compaction-and-retry.md +36 -4
- package/docs/compaction-observational-memory.md +62 -9
- package/docs/context-and-skills.md +4 -2
- package/docs/contributing.md +37 -0
- package/docs/conversations.md +1 -1
- package/docs/core.md +2 -0
- package/docs/dev-inspector.md +4 -0
- package/docs/device-adapters.md +1 -0
- package/docs/document-reader.md +12 -2
- package/docs/documents.md +11 -3
- package/docs/enterprise-postgres-state.md +2 -2
- package/docs/evaluations.md +168 -4
- package/docs/execution-timeline.md +180 -0
- package/docs/graft.md +3 -1
- package/docs/history/0.7.0-primitive-review.md +254 -0
- package/docs/history/migration-0.0.md +2 -2
- package/docs/history/release-handoffs.md +70 -1
- package/docs/host-compositions.md +147 -0
- package/docs/host-security.md +2 -2
- package/docs/hosted-sandboxes.md +94 -0
- package/docs/index.md +73 -41
- package/docs/input-and-prompt-assembly.md +5 -4
- package/docs/knowledge-sync.md +84 -0
- package/docs/language-intelligence.md +2 -2
- package/docs/live-testing.md +4 -1
- package/docs/mcp-tools.md +2 -1
- package/docs/memory-fabric.md +416 -0
- package/docs/migrate-to-0.5.md +8 -3
- package/docs/migrate-to-0.6.md +90 -0
- package/docs/migrate-to-0.7.md +345 -0
- package/docs/migration.md +43 -1
- package/docs/model-registry.md +1 -1
- package/docs/model-routing.md +79 -4
- package/docs/multi-agent-patterns.md +20 -6
- package/docs/multimodal-content.md +1 -1
- package/docs/obscura.md +3 -1
- package/docs/observability.md +52 -1
- package/docs/operations.md +13 -1
- package/docs/options-index.md +298 -0
- package/docs/peer-dependencies.md +96 -0
- package/docs/performance.md +34 -2
- package/docs/ponytail.md +2 -0
- package/docs/postgres-persistence.md +3 -1
- package/docs/process-sessions.md +3 -1
- package/docs/prompt-registry.md +1 -1
- package/docs/provider-caching.md +4 -2
- package/docs/provider-conformance.md +2 -2
- package/docs/provider-packages.md +23 -23
- package/docs/provider-primitives.md +2 -1
- package/docs/providers/ai-sdk.md +5 -2
- package/docs/providers/bedrock.md +71 -7
- package/docs/providers/openai.md +1 -1
- package/docs/public-contracts.md +2 -2
- package/docs/rag.md +24 -8
- package/docs/realtime-voice.md +87 -0
- package/docs/release-and-install.md +78 -56
- package/docs/runs-and-usage.md +3 -2
- package/docs/server.md +6 -4
- package/docs/session-stores.md +3 -1
- package/docs/speech.md +2 -0
- package/docs/sqlite-persistence.md +2 -0
- package/docs/supervisors.md +33 -5
- package/docs/testing.md +38 -0
- package/docs/thinking-and-reasoning.md +3 -1
- package/docs/tools.md +7 -6
- package/docs/web-tools.md +2 -1
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +14 -4
- package/docs/work-connectors.md +3 -1
- package/docs/work-tools.md +14 -4
- package/docs/workflows.md +69 -1
- package/docs/working-and-semantic-memory.md +25 -14
- package/package.json +5 -5
- package/templates/README.md +2 -0
- package/templates/business-worker/README.md.tmpl +19 -0
- package/templates/business-worker/env.example.tmpl +1 -0
- package/templates/business-worker/gitignore.tmpl +11 -0
- package/templates/business-worker/manifest.json +11 -0
- package/templates/business-worker/package.json.tmpl +23 -0
- package/templates/business-worker/src/agent.ts.tmpl +92 -0
- package/templates/business-worker/src/index.ts.tmpl +13 -0
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
- package/templates/business-worker/tsconfig.json.tmpl +15 -0
- package/templates/personal-assistant/README.md.tmpl +18 -0
- package/templates/personal-assistant/env.example.tmpl +1 -0
- package/templates/personal-assistant/gitignore.tmpl +11 -0
- package/templates/personal-assistant/manifest.json +11 -0
- package/templates/personal-assistant/package.json.tmpl +23 -0
- package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
- package/templates/personal-assistant/src/index.ts.tmpl +13 -0
- package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
- package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
+
## [0.7.0] - 2026-09-15 (extended line: plans 072, 073, 074, 075, 077, 078)
|
|
2
|
+
|
|
3
|
+
> **Channels are not in this cut.** Plan 079 (Telegram/Signal adapters) was reassigned to **0.8.0** so the 0.7.0 cut stops waiting on it; nothing in this release mentions or ships a channel adapter.
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Execution timeline (plan 072).** `@arnilo/prism-core/governance/observability` ships one frozen, JSON-serializable view-model for what a run did: `projectAgentTimeline` (live `AgentEvent[]`), `projectTraceTimeline` (persisted `EvaluationTrace`), `projectWorkflowTimeline` (workflow events plus optional checkpoint), incremental `createTimelineFolder` / `createWorkflowTimelineFolder` folders for SSE/cockpit updates, and `summarizeTimeline` / `summarizeSession` rollups (tool counts capped at 64, no double counting across runs). See [docs/execution-timeline.md](docs/execution-timeline.md).
|
|
7
|
+
- **Workflow graph view-model (plan 072).** `serializeWorkflowGraph`, `workflowGraphToMermaid`, `workflowGraphToDot`, `projectWorkflowGraphRun`, and `createWorkflowGraphRunFolder` render a workflow DAG and overlay live or checkpoint run state (`WorkflowGraphRunView`) without executing it.
|
|
8
|
+
- **Trajectory and outcome evals (plan 072).** Scorers (`defineScorer`, pairwise preferences, model-judge budgets), `runScenario`, `runExperiment`, `runWorkflowExperiment`, dataset items with expected trajectories, trials, manifests, comparisons, and thresholds — with `runComparison` / `datasetFromRuns` curation over recorded runs.
|
|
9
|
+
- **Eval primitives match their contracts (plan 073 Tasks 30–31).** Injection/timeline holes closed, deterministic `mulberry32` sampling, `collectWhileRunning`, and host-activity eval packs (coding, browser, memory, voice invariants) over the true primitives.
|
|
10
|
+
- **Attention compiler (plan 074, opt-in).** `createAttentionCompiler` / `resolveInputCap` / `compileAttention` / `createAttentionTruncationTrigger`: a per-turn gate that measures the assembled input against a host ratio of the model input cap and, only past the ratio, mutates a **history clone** oldest thinking blocks first, then fold-eligible tool results — keeping cache prefixes, the session store, and the observational-memory ledger untouched, and raising `AttentionBudgetError` rather than silently dropping context. Wired through `AgentConfig`, `AgentDefinition`, and `RunOptions` (run overlay narrows), plus `attention_compiled` telemetry and an `attention` timeline step; sticky frontier persists through `persistSessionState`. See [docs/attention-compiler.md](docs/attention-compiler.md).
|
|
11
|
+
- **Memory fabric subpath (plan 075, opt-in).** `@arnilo/prism-memory/fabric` adds typed notes (`fact`, `procedure`, `file`, `working`, `episode`) with links, validity windows, time/tool recall, conversation search, and opt-in consolidation/linker/evolution workers over the stores a host already configured. Inert until `fabric.attach(session)`; no new package, provider, database, or mandatory dependency. See [docs/memory-fabric.md](docs/memory-fabric.md).
|
|
12
|
+
- **Work-scope memory index (plan 077, opt-in).** `createWorkScopeController` appends `om.scope.*` entries to one observational-memory ledger; `foldWorkScopeMap`, `projectWorkMemory`, and `withWorkScope` project the outline to a host-selected working set (leaf `self+ancestors` by default), auto-bind new observations to the leaf, and skip the observation dropper while any host scope exists. Caps fail closed (256 scopes, depth 8, 4,096 binds, 512-char labels). See [docs/compaction-observational-memory.md](docs/compaction-observational-memory.md).
|
|
13
|
+
- **Host-owned subagent spawn (plan 078, opt-in).** `createSpawnAgentTool` turns the supervisor's host-owned child catalog into a non-exclusive `spawn_agent` whose closed schema exposes only allow-listed child ids, input, an optional thread id, and `mode: "sync" | "async"`; `createWaitAgentTool` / `createCancelAgentTool` join or abort async handles from `delegateAsync()`. Child identity narrows from the parent, results and errors are redacted, child slots are reserved atomically (after a before-hook narrows limits), and parent-run abort cancels running children. `createWorktreeChildFactory` gives each child its own linked git worktree and cleans it up on every terminal outcome (including a suspended child that later resumes), while `observeSupervisorLifecycle` bridges `delegation_*` events to redacted coding `subagent_started` / `subagent_stopped` lifecycle events. See [docs/supervisors.md](docs/supervisors.md).
|
|
14
|
+
- **Governed host completeness (plan 073).** Governed provider invocation with aggregate task/tenant accounting across every paid work kind (enterprise migration `006_aggregate_budgets`); durable business-action drafts with editable approvals; Docker process sessions and coherent workspace recovery; incremental Drive knowledge synchronization; snapshot/reconnect lifecycle with a hosted E2B sandbox; fair worker admission and operator routes; cross-layer memory lineage with correction and revocation; semantic artifact review with evidence-backed citations; import-fidelity reports with optional OCR; monotonic per-run tool narrowing with remote invalidation; native Bedrock `Converse`/`ConverseStream`; and governed realtime voice orchestration. New pages: [docs/execution-timeline.md](docs/execution-timeline.md), [docs/host-compositions.md](docs/host-compositions.md), [docs/hosted-sandboxes.md](docs/hosted-sandboxes.md), [docs/knowledge-sync.md](docs/knowledge-sync.md), [docs/realtime-voice.md](docs/realtime-voice.md), [docs/attention-compiler.md](docs/attention-compiler.md), [docs/memory-fabric.md](docs/memory-fabric.md).
|
|
15
|
+
- **Examples.** Runnable demos for the new surfaces: `examples/execution-timeline.ts`, `examples/behavior-evaluation.ts`, `examples/coding-browser-evaluation.ts`, `examples/attention-compiler.ts`, `examples/memory-fabric.ts`, `examples/work-scopes-coding-loop.ts`, `examples/spawn-agent-tool.ts`, `examples/governed-provider.ts`, `examples/docker-process-session.ts`, `examples/drive-rag-sync.ts`, `examples/hosted-sandbox.ts`, `examples/scanned-document-rag.ts`, and `examples/realtime-voice-host.ts`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Lockstep `0.6.0` → `0.7.0`.** All ten publishable manifests move together with `^0.7.0` internal ranges; the lockfile, the `src/index.ts` version constant, the docs index banner, the release-workflow tag lists, and the generated package-truth artifact agree (enforced by `scripts/version-literal-gate.test.mjs`).
|
|
19
|
+
- **Compat baselines regenerated** (`--update-baseline`): **469 added declarations, zero removals**. The additions are the new subpath APIs above plus members added to existing declaration groups; no export was renamed or dropped.
|
|
20
|
+
- **Release budgets rebaselined with recorded reasons**: root packed/unpacked/fileCount moved for the new dist modules, templates, and docs pages, and per-package export ceilings carry the 0.7.0 addition list. Startup and timing ceilings are unchanged.
|
|
21
|
+
- **Migration guide for 0.6.0 hosts**: [docs/migrate-to-0.7.md](docs/migrate-to-0.7.md) (per-item actions for the ACP/model-router refusals, every tightening, the opt-in activation steps, and rollback), indexed from [docs/migration.md](docs/migration.md) and [docs/index.md](docs/index.md).
|
|
22
|
+
- **Options index and peer matrix** cover the new surfaces: [docs/options-index.md](docs/options-index.md) routes `AttentionCompilerOptions`/`AttentionInputCapOptions`/`AttentionCompileOptions`/`AttentionTruncationTriggerOptions`, the fabric and work-scope option objects, and the supervisor/spawn/worktree/lifecycle options to their owning pages (gated by `scripts/live-doc-check.test.mjs`).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **Task-scoped enterprise budgets failed on their first insert.** Migration `006_aggregate_budgets`' insert bound one JavaScript `Date` to both a `timestamptz` column and interval arithmetic, so PostgreSQL refused the statement with `42P08 inconsistent types deduced for parameter $8`; the parameter is now explicitly `::timestamptz` (found by the protected PostgreSQL leg, not by hermetic doubles).
|
|
26
|
+
- **A serialization failure inside the budget upsert was swallowed.** The read-then-insert path caught *every* error from the `SELECT … FOR UPDATE` probe and then issued SQL against an aborted transaction (`25P02`), which defeated the retry loop; only a genuinely missing row is recoverable by inserting now.
|
|
27
|
+
- **Serializable retry policy was too small for concurrent writers.** Budget/rate rewrites on one row now retry up to 12 times with full-jitter exponential backoff (capped at 250 ms), so a 16-client burst converges instead of exhausting three near-instant attempts with `ERR_PRISM_ENTERPRISE_POSTGRES_RETRYABLE`.
|
|
28
|
+
- **Integration tests were stale against migration 006** (expected five migrations) and `scripts/phase27-release.test.mjs` still asserted that no `006_` migration existed; both now check the append-only list including `006_aggregate_budgets`.
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
- **ACP MCP destination matching (Trap A).** `mcp.allow` entries now match by WHATWG origin plus path-segment subtree: origin lookalikes (`mcp.example.com.attacker.invalid`) and sibling path prefixes (`/mcp-other`) no longer match, and allow entries carrying userinfo, query, fragment, or ambiguous encoded path forms fail `ConfigError` at parse time.
|
|
32
|
+
- **Model-router facade fails closed (Trap B).** `router.providerSource(model)` throws `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` / `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` instead of handing back a provider that bypasses budgets, rate limits, circuits, fallbacks, selection policies, or durable state; `isProviderSourceEligible` lets a host check first.
|
|
33
|
+
- **ACP launcher requires a real provider (Trap C/R05).** No silent `createMockProvider()` fallback; mock mode is an explicit opt-in.
|
|
34
|
+
- **Per-run tool narrowing is monotonic (R11)** and remote invalidation is honored; child agents cannot widen the tenant, account, user, or scopes of the parent identity (`narrowIdentity` + `assertIdentityPropagation`).
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
- **Protected PostgreSQL leg now green on 0.7.0 code**: `PRISM_TEST_POSTGRES_URL=… npm run test:postgres` passes core (72), memory (457), and the phase conformance legs (11) against `pgvector/pgvector:pg16`, the same image the release workflow uses. The three fixes above are what that leg caught.
|
|
38
|
+
- **Node floor is unchanged** at `>=22` (Node 22/24 supported).
|
|
39
|
+
|
|
40
|
+
## [0.6.0] - 2026-09-12 (plans 070, 071)
|
|
41
|
+
|
|
42
|
+
> **0.5.7 was never published.** This release folds that cut's content (durable concurrent tool rounds, strict-provider tool results, host-tunable knobs, peer/options truth, the dependency refresh, and the module splits) together with the 0.6.0 changes below, so a host on 0.5.6 upgrades once. See [docs/migrate-to-0.6.md](docs/migrate-to-0.6.md).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **Concurrent tool dispatch dropped sibling results.** With `toolConcurrency > 1` a failed or aborted call threw before the round's results were appended, so every successful sibling result was lost and the next provider request carried unanswered `tool_use` blocks. Results are now persisted before the round fails: successes as-is, the failing call as a real redacted `tool_execution_failed` error, and calls that never started as `tool_call_not_dispatched`. Run-level control errors (`ERR_PRISM_AGENT_RUN_SUSPENDED`, `ERR_PRISM_DELEGATION_SUSPENDED`, `ERR_PRISM_LOOP_*`) still skip synthetic results so durable recovery re-dispatches instead of double-appending.
|
|
46
|
+
- **Content-less tool results serialized as an empty payload.** A `ToolResult` with no `content`/`result` now folds to `EMPTY_TOOL_RESULT_TEXT` (`(tool completed with no output)`) through the shared `serializeToolResultJson` seam (OpenAI-compatible, Alibaba, DeepSeek, Kimi/Moonshot, NeuralWatt, OpenCode-Go), so strict providers stop rejecting zero-length tool payloads.
|
|
47
|
+
- **A failing coverage child hid its own error.** `npm run test:coverage` reported a crashed or failed workspace suite as a bare `no coverage data (suite failed)`, with the child's output discarded (the diagnostic gap that made a one-off `@arnilo/prism-memory` flake unreadable). The summary now prints the child's output tail under the failing row and records `status`/`exitCode`/`tail` on its artifact row — redacted through the public `createSecretRedactor` (repo root and home become placeholders, credential-shaped env values become `[REDACTED]`) and char-bounded (last 40 lines, 8 KiB) so one pathological line cannot bloat the artifact. Passing rows are byte-identical to before.
|
|
48
|
+
- **Memory patch merge aliased caller objects.** `packages/memory`'s `mergeJsonObjects` deep-merged in place; it now delegates to core `mergeConfigLayers` (deep copy, strict JSON validation — `undefined`/`Date`/function values fail closed — with the `MemoryValidationError` taxonomy preserved).
|
|
49
|
+
- **`redactSecrets` was O(n·m) in needles.** A guarded single-pass alternation fast path handles large inputs (≥16 KiB, 2–32 non-overlapping needles) with byte-identical output (~13× faster on 1 MiB transcripts); the ordered loop remains the fallback for overlapping needle sets and small inputs.
|
|
50
|
+
- **Peer manifest resolution broke for packages that do not export `./package.json`** (e.g. `@dietrichgebert/ponytail`): upstream resolvers now resolve the package entry point and walk up to the manifest.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- **Host-tunable context assembly:** `AssembleProviderInputOptions.tokenEstimator?: (text: string) => number` replaces the built-in UTF-16/4 heuristic for eviction accounting (byte caps stay estimator-independent).
|
|
54
|
+
- **Host-tunable session snapshot cache:** `AgentSessionConfig.snapshotCacheTtlMs` — `DEFAULT_SNAPSHOT_CACHE_TTL_MS` (1000), `HARD_MAX_SNAPSHOT_CACHE_TTL_MS` (30000), `0` disables the branch-rebuild cache.
|
|
55
|
+
- **Host-tunable memory-session search caps:** `createMemorySessionStore(entries, { search: { maxLinearSessions, maxLinearEntries, maxLinearBytes } })`, validated against the same hard bounds as the defaults.
|
|
56
|
+
- **SSRF allow-list:** `SsrfPolicy.allowedCidrs` accepts IPv4 and IPv6 CIDR entries so a host can explicitly reach a private range; hostname denials (metadata endpoints), credential checks, and the fail-closed unparseable-CIDR behavior are unchanged.
|
|
57
|
+
- **Browser run lifetime:** `BrowserLimitOptions.idleRunTtlMs` (default `0`, `HARD_IDLE_RUN_TTL_MS` 30 min) reaps runs with nothing queued; any interaction resets the idle clock.
|
|
58
|
+
- **Host onboarding docs:** [`docs/peer-dependencies.md`](docs/peer-dependencies.md) (every third-party peer declaration with range, optionality, subpath, install line, and network footprint) and [`docs/options-index.md`](docs/options-index.md) (123 public option/limits/config surfaces routed to the page that documents them), both gated by `live-doc-check` against the manifests and the source.
|
|
59
|
+
- **Real-peer and ACP round-trip coverage:** contract smoke tests for the `@dietrichgebert/ponytail` and `@nanonets/graft` layouts, and stdio round-trip tests driving a spawned ACP agent with the real `@agentclientprotocol/sdk` client (spawn→close, mode negotiation, config options, refused/cancelled permissions).
|
|
60
|
+
- **Release-truth gates:** `scripts/version-literal-gate.test.mjs` asserts every release-claim surface (manifests, internal ranges, `package-lock.json`, the `src/index.ts` version constant, the `docs/index.md` current line, `release.yml` tag lists, `scripts/package-truth.json`) equals the root manifest, so a half-finished cut fails the suite instead of shipping; `scripts/workflow-liveness.test.mjs` resolves every workflow script/workspace/`uses:` reference and requires 40-hex SHA pins for actions; `scripts/wiki-scratch-isolation.test.mjs` proves the memory wiki suites leave tracked fixtures and the repository root untouched; `scripts/run-all-tests.mjs` runs every test stage without short-circuiting, and the startup import budget now asserts a machine-relative ratio (absolute 250 ms ceiling only off-load) so a busy machine no longer reports a false regression.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- **Node floor raised to `>=22`** in all ten publishable packages (`@types/node` `^20.19.0` → `^22.20.0`; the `node20-compat` release leg becomes `node22-compat` on Node 22). Node 20 is upstream EOL since 2026-04-30; Node 24 stays the CI default. This is the host-breaking support-matrix change that the unreleased 0.5.7 deliberately deferred to a minor.
|
|
64
|
+
- **Dependency refresh:** `pg` 8.23, `playwright-core` 1.63.0, `zod` ^4.6.2, `@ai-sdk/provider` 4.0.13 (`@ai-sdk/openai` 4.0.65 with a new supported-version matrix entry), `@office-open/*` 0.14.5 (adapters use the `*Sync` parse variants), `@biomejs/biome` 2.5.13, `@agentclientprotocol/sdk` exact-pinned 1.4.0, and the `@arnilo/prism-memory` `@nanonets/graft` peer range widened to `^0.16.0 || ^0.18.0`.
|
|
65
|
+
- **Examples are Node-20-floor safe:** four runnable examples used `import.meta.main` (Node ≥22.18/≥24.2) on a Node-20 floor; they now use the house `import.meta.url` + `process.argv[1]` guard.
|
|
66
|
+
- **Release gate:** lockstep cuts now require every internal `@arnilo/*` range to be the cut version exactly (`0.6.0` or `^0.6.0`) instead of merely satisfying it, so one release line cannot resolve two first-party minors.
|
|
67
|
+
- **Test/coverage tooling:** the npm test chain runs every stage through `scripts/run-all-tests.mjs` (no short-circuit, one summary), coverage discovery finds nested `dist/**/__tests__` in all 9 workspace packages, `coverage-thresholds.json` may no longer name retired packages, and protected legs use one blocked-gate shape (`scripts/blocked-gate.mjs`) with env **names** only in the release evidence.
|
|
68
|
+
- **Internal structure, additive-only surface:** `runtime/server/artifacts.ts` split into four modules, `enterprise/postgres/model-router.ts` split into a directory (`util`/`circuit`/`capacity`/`reservations`/`expiry`/`state-store`), MCP OAuth discovery extracted to `oauth-metadata.ts`, one abort-aware `Semaphore` shared across `@arnilo/prism-coding-tools` (sandbox error types preserved), shared upstream persona primitives in `@arnilo/prism-coding-tools/src/upstream/`, shared provider HTTP retry primitives (`@arnilo/prism-providers/src/shared/retry-http.ts`), and the `content.ts` ↔ `pinned-fetch.ts` ESM cycle broken via the leaf module `src/media-types.ts`. Compat baselines were regenerated (`--update-baseline`): **69 added declarations, zero removals** — the five new host-tunable surfaces plus the helpers the moved modules now export from their new files.
|
|
69
|
+
|
|
70
|
+
### Removed
|
|
71
|
+
- **`@arnilo/prism-office` optional `playwright-core` peer** (test-only: `/diagrams` drives a host-supplied iframe). Hosts no longer install a browser for office; see [`docs/peer-dependencies.md`](docs/peer-dependencies.md).
|
|
72
|
+
|
|
73
|
+
### Notes
|
|
74
|
+
- **Lockstep `0.5.6` → `0.6.0` bump:** all 10 publishable manifests move to `0.6.0` with internal ranges `^0.6.0`; the predecessor published release is **0.5.6** (0.5.7 was never published and is superseded by this cut).
|
|
75
|
+
- **Retired-gate and test-truth fixes** landed in the same window (plans 070/071): frozen-era version markers derive from the root manifest, retired doc paths resolve through the frozen lineage, the packed-consumer enterprise journey no longer inherits a Postgres env var (the `pg` peer is not installed in a packed consumer), the `sandbox-browser` workflow references live packages again, the secret scan is operator-independent (tracked files only), the LSP restart-budget test awaits the exhaustion transition instead of racing a write, and the release workflow tag lists carry every published tag.
|
|
76
|
+
|
|
1
77
|
## [0.5.6] - 2026-09-09 (plan 069)
|
|
2
78
|
|
|
3
79
|
### Added
|
|
@@ -92,27 +168,27 @@
|
|
|
92
168
|
### Added
|
|
93
169
|
- **OKF adoption (`@arnilo/prism-wiki`)**: wiki-init/refresh/lint emit and validate
|
|
94
170
|
OKF v0.2 bundles (Karpathy prompt retained). See `docs/wiki.md`.
|
|
95
|
-
- **DOCS-1 (
|
|
171
|
+
- **DOCS-1 (integration findings)**: three integrator contracts, in place on
|
|
96
172
|
the pages that own them — resume-aware workflow nodes (`ctx.resume` or silent
|
|
97
173
|
re-suspend) in `docs/workflows.md`; supervisor child factories return `Agent`
|
|
98
174
|
not `AgentSession` (`SupervisorError: child "<id>" factory must return an
|
|
99
175
|
Agent, got <type>`) plus durable-store nested approvals in `docs/supervisors.md`;
|
|
100
176
|
task-boundary `session.compact()` fails closed during an active run in
|
|
101
177
|
`docs/compaction-and-retry.md`. Each block links `examples/autonomous-coding-loop.ts`.
|
|
102
|
-
- **FEATURE-2 (
|
|
178
|
+
- **FEATURE-2 (integration findings)**: documented bounded iterate-until-done
|
|
103
179
|
host-loop pattern in `docs/workflows.md` — one `runWorkflow` per iteration,
|
|
104
180
|
iteration state in workflow inputs, explicit termination predicate and budgets,
|
|
105
181
|
typed `BudgetExhaustedError` (fail-closed, never a hang), `replayWorkflow` per
|
|
106
182
|
iteration run id. Seeded by `examples/autonomous-coding-loop.ts`. Plan 045 `loop`
|
|
107
183
|
node remains the future in-graph primitive; this intake ships the docs+example
|
|
108
184
|
minimum only.
|
|
109
|
-
- **FEATURE-6 (
|
|
185
|
+
- **FEATURE-6 (integration findings)**: composite `examples/autonomous-coding-loop.ts`
|
|
110
186
|
conformance reference — goal → roadmap → per-task supervisor children (per-child
|
|
111
187
|
models) → `runCodingGoalVerify`-style validation → observational-memory attach +
|
|
112
188
|
task-boundary compact + recall → human gate with simulated restart → host-side
|
|
113
189
|
bounded iterate-until-done with deterministic budget exhaustion. Mock providers
|
|
114
190
|
only; no credentials or network.
|
|
115
|
-
- **FEATURE-3 (
|
|
191
|
+
- **FEATURE-3 (integration findings)**: host-opt-in command driver hooks.
|
|
116
192
|
`CommandExecutionContext` gains an optional `drivers?: CommandDrivers`
|
|
117
193
|
(`startRun` / `startWorkflow` / `steer` — typed minimal handles returning
|
|
118
194
|
`AgentRunResult`-shaped results / workflow run id + status) so a contributed
|
|
@@ -122,7 +198,7 @@
|
|
|
122
198
|
the context shape unchanged (no key, no allocation). Drivers are
|
|
123
199
|
host-injected capabilities, never package-supplied.
|
|
124
200
|
### Fixed
|
|
125
|
-
- **FEATURE-1 (
|
|
201
|
+
- **FEATURE-1 (integration findings)**: `resolveAgentDefinition` no longer
|
|
126
202
|
throws `Agent "<name>" has no model` when the declarative definition omits
|
|
127
203
|
`model` but `context.overrides.model` supplies one — the fallback is a
|
|
128
204
|
single `??` at `buildBaseConfig`, the `create()` path is unchanged, and a
|
package/README.md
CHANGED
|
@@ -162,16 +162,16 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
162
162
|
|
|
163
163
|
| package | version | notes |
|
|
164
164
|
| --- | --- | --- |
|
|
165
|
-
| `@arnilo/prism` | 0.
|
|
166
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
167
|
-
| `@arnilo/prism-core` | 0.
|
|
168
|
-
| `@arnilo/prism-providers` | 0.
|
|
169
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
170
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
171
|
-
| `@arnilo/prism-mcp` | 0.
|
|
172
|
-
| `@arnilo/prism-memory` | 0.
|
|
173
|
-
| `@arnilo/prism-office` | 0.
|
|
174
|
-
| `@arnilo/prism-web-tools` | 0.
|
|
165
|
+
| `@arnilo/prism` | 0.7.0 | core — runtime, CLI/RPC, templates, docs |
|
|
166
|
+
| `@arnilo/prism-coding-tools` | 0.7.0 | family — /agent, /security, /document-reader, /openapi, /computer-use-linux, /dev, /caveman, /ponytail, /impeccable subpaths |
|
|
167
|
+
| `@arnilo/prism-core` | 0.7.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /work, /validation subpaths |
|
|
168
|
+
| `@arnilo/prism-providers` | 0.7.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
169
|
+
| `@arnilo/prism-acp-agent` | 0.7.0 | capability — ACP adapter |
|
|
170
|
+
| `@arnilo/prism-ag-ui` | 0.7.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
171
|
+
| `@arnilo/prism-mcp` | 0.7.0 | capability — MCP client/server/OAuth interop |
|
|
172
|
+
| `@arnilo/prism-memory` | 0.7.0 | capability — memory plus /rag, /compaction/*, /fabric, /graft, /wiki subpaths |
|
|
173
|
+
| `@arnilo/prism-office` | 0.7.0 | capability — /documents, /sheets, /diagrams subpaths |
|
|
174
|
+
| `@arnilo/prism-web-tools` | 0.7.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
175
175
|
<!-- generated:package-truth:inventory end -->
|
|
176
176
|
|
|
177
177
|
## Scripts
|
package/dist/agent-approval.js
CHANGED
|
@@ -37,8 +37,9 @@ export function assertValidAgentRunResume(resume) {
|
|
|
37
37
|
if (!Number.isSafeInteger(resume.expectedVersion) || resume.expectedVersion <= 0) {
|
|
38
38
|
throw invalid("Resume expectedVersion must be a positive safe integer");
|
|
39
39
|
}
|
|
40
|
+
const decisions = resume.decisions;
|
|
40
41
|
const hasDecision = resume.decision !== undefined;
|
|
41
|
-
const hasDecisions =
|
|
42
|
+
const hasDecisions = decisions !== undefined;
|
|
42
43
|
if (hasDecision && hasDecisions)
|
|
43
44
|
throw invalid("Resume accepts exactly one of decision or decisions");
|
|
44
45
|
if (!hasDecision && !hasDecisions)
|
|
@@ -49,7 +50,6 @@ export function assertValidAgentRunResume(resume) {
|
|
|
49
50
|
}
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
|
-
const decisions = resume.decisions;
|
|
53
53
|
if (!Array.isArray(decisions))
|
|
54
54
|
throw invalid("Decision batch must be an array");
|
|
55
55
|
if (decisions.length === 0)
|
|
@@ -242,10 +242,11 @@ export function decisionScopesEqual(a, b) {
|
|
|
242
242
|
return false;
|
|
243
243
|
if (a.actionConstraints === undefined || b.actionConstraints === undefined)
|
|
244
244
|
return a.actionConstraints === b.actionConstraints;
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
const aConstraints = a.actionConstraints;
|
|
246
|
+
const bConstraints = b.actionConstraints;
|
|
247
|
+
const keys = Object.keys(aConstraints);
|
|
248
|
+
return (keys.length === Object.keys(bConstraints).length &&
|
|
249
|
+
keys.every((key) => key in bConstraints && canonicalToolEffectJson(aConstraints[key]) === canonicalToolEffectJson(bConstraints[key])));
|
|
249
250
|
}
|
|
250
251
|
export function nestedOutcomeToolResult(outcome, toolCallId, name) {
|
|
251
252
|
return outcome.status === "completed"
|
|
@@ -34,6 +34,7 @@ function buildBaseConfig(def, context) {
|
|
|
34
34
|
...(def.context !== undefined && { context: resolveContextProviders(def.name, def.context, context) }),
|
|
35
35
|
...(def.systemPrompt !== undefined && { systemPrompt: def.systemPrompt }),
|
|
36
36
|
...(def.instructions !== undefined && { instructions: def.instructions }),
|
|
37
|
+
...(def.attentionCompiler !== undefined && { attentionCompiler: def.attentionCompiler }),
|
|
37
38
|
...(def.loop !== undefined && { loop: def.loop }),
|
|
38
39
|
...(def.metadata !== undefined && { metadata: def.metadata }),
|
|
39
40
|
};
|
package/dist/agent-loops.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AgentLoopStateError } from "./contracts.js";
|
|
2
2
|
import { createId } from "./ids.js";
|
|
3
3
|
import { inputMessages, toToolResultMessage } from "./input.js";
|
|
4
|
+
import { errorToErrorInfo } from "./redaction.js";
|
|
4
5
|
import { artifactStructuredOutputRequest, withoutStructuredOutput } from "./structured-output.js";
|
|
5
6
|
function throwIfAborted(signal) {
|
|
6
7
|
if (signal.aborted)
|
|
@@ -207,12 +208,13 @@ export function generateValidateReviseLoop(opts) {
|
|
|
207
208
|
: { ok: true, value: text };
|
|
208
209
|
// Parse failure consumes revision budget like a validation failure; the
|
|
209
210
|
// repairer receives `undefined` value plus a synthetic parse issue.
|
|
210
|
-
const
|
|
211
|
-
? { ok: false, errors: [{ path: "$", message: parsed.error ?? "artifact parse failed" }], metadata: { reason: "parse_error" } }
|
|
212
|
-
: undefined;
|
|
211
|
+
const candidate = parsed.ok && parsed.value !== undefined ? { ok: true, value: parsed.value } : undefined;
|
|
213
212
|
const attempt = ++attempts;
|
|
214
213
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
215
|
-
const result =
|
|
214
|
+
const result = candidate
|
|
215
|
+
? await opts.validator(candidate.value, artifactCtx)
|
|
216
|
+
: { ok: false, errors: [{ path: "$", message: parsed.error ?? "artifact parse failed" }], metadata: { reason: "parse_error" } };
|
|
217
|
+
const parseFailure = candidate ? undefined : result;
|
|
216
218
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
217
219
|
if (result.ok) {
|
|
218
220
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -271,39 +273,76 @@ export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
|
271
273
|
let nextIndex = 0;
|
|
272
274
|
let stopped = false;
|
|
273
275
|
let firstFailure;
|
|
274
|
-
|
|
276
|
+
let failureIndex = -1;
|
|
277
|
+
const recordFailure = (error, index) => {
|
|
275
278
|
if (stopped)
|
|
276
279
|
return;
|
|
277
280
|
stopped = true;
|
|
278
281
|
firstFailure = error;
|
|
282
|
+
failureIndex = index;
|
|
279
283
|
};
|
|
280
284
|
const workers = Array.from({ length: concurrency }, async () => {
|
|
281
285
|
for (;;) {
|
|
282
286
|
if (stopped)
|
|
283
287
|
return;
|
|
288
|
+
let index = -1;
|
|
284
289
|
try {
|
|
285
290
|
throwIfAborted(ctx.signal);
|
|
286
|
-
|
|
291
|
+
index = nextIndex;
|
|
287
292
|
nextIndex += 1;
|
|
288
293
|
if (index >= calls.length)
|
|
289
294
|
return;
|
|
290
295
|
results[index] = await ctx.dispatchToolCall(calls[index]);
|
|
291
296
|
}
|
|
292
297
|
catch (error) {
|
|
293
|
-
recordFailure(error);
|
|
298
|
+
recordFailure(error, index >= 0 && index < calls.length ? index : -1);
|
|
294
299
|
return;
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
});
|
|
298
303
|
await Promise.allSettled(workers);
|
|
304
|
+
// Persist before rethrowing: a stopped batch must not leave the branch with `tool_call`
|
|
305
|
+
// ids that never got a `tool_result` (providers reject that history on the next turn).
|
|
306
|
+
// ponytail: synthetic rows carry `errorToErrorInfo` output only — no cause chain; the
|
|
307
|
+
// store redacts entries again at `appendEntry`, and `ctx.history` mirrors the run error.
|
|
308
|
+
const claimed = Math.min(nextIndex, calls.length);
|
|
309
|
+
for (let index = 0; index < calls.length; index += 1) {
|
|
310
|
+
const result = results[index];
|
|
311
|
+
if (result) {
|
|
312
|
+
await appendToolResultMessage(result, ctx);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
// Suspension/loop-state errors are not tool outcomes: their machinery appends the real
|
|
316
|
+
// result on resume, so a synthetic row here would duplicate it.
|
|
317
|
+
if (index === failureIndex && isRunControlError(firstFailure))
|
|
318
|
+
continue;
|
|
319
|
+
const call = calls[index];
|
|
320
|
+
if (call === undefined)
|
|
321
|
+
continue;
|
|
322
|
+
await appendToolResultMessage(syntheticFailureResult(call, index < claimed ? firstFailure : undefined), ctx);
|
|
323
|
+
}
|
|
299
324
|
if (stopped)
|
|
300
325
|
throw firstFailure;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
326
|
+
}
|
|
327
|
+
/** Run-level control errors rethrown by `dispatchToolCall` (mirrors `src/tools.ts`). */
|
|
328
|
+
function isRunControlError(error) {
|
|
329
|
+
const code = error?.code;
|
|
330
|
+
return (typeof code === "string" &&
|
|
331
|
+
(code === "ERR_PRISM_AGENT_RUN_SUSPENDED" || code === "ERR_PRISM_DELEGATION_SUSPENDED" || code.startsWith("ERR_PRISM_LOOP_")));
|
|
332
|
+
}
|
|
333
|
+
function syntheticFailureResult(call, failure) {
|
|
334
|
+
if (failure === undefined) {
|
|
335
|
+
return {
|
|
336
|
+
toolCallId: call.id,
|
|
337
|
+
name: call.name,
|
|
338
|
+
error: {
|
|
339
|
+
code: "tool_call_not_dispatched",
|
|
340
|
+
message: "Tool call was not dispatched: the batch stopped after an earlier call failed or the run was aborted.",
|
|
341
|
+
},
|
|
342
|
+
};
|
|
306
343
|
}
|
|
344
|
+
const info = errorToErrorInfo(failure);
|
|
345
|
+
return { toolCallId: call.id, name: call.name, error: { name: info.name, message: info.message, code: info.code } };
|
|
307
346
|
}
|
|
308
347
|
async function appendToolResultMessage(result, ctx) {
|
|
309
348
|
// Approval-gated calls return a marker instead of a real result; the transcript must not
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertValidAgentRunResume, pendingDecisionsOf, resolveRunDecisions } from "./agent-approval.js";
|
|
2
|
+
import { parseAttentionStickyFrontier } from "./attention-compiler.js";
|
|
2
3
|
import { agentFingerprint, loadAgentRunState, publicState, saveAgentRunState } from "./agent-run-state.js";
|
|
3
4
|
import { RuntimeAgentSession, throwIfAbortedSignal } from "./agent-session.js";
|
|
4
5
|
import { AgentRunStateError } from "./contracts.js";
|
|
@@ -96,6 +97,9 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
96
97
|
throw new AgentRunStateError("Stale or non-suspended agent run resume");
|
|
97
98
|
}
|
|
98
99
|
const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
|
|
100
|
+
// Plan 078 Task 7: hand the reconstructed session to an observer (supervisor child-event pump)
|
|
101
|
+
// before any event flows. Called for every resume outcome; a throw fails closed.
|
|
102
|
+
options.onSession?.(session);
|
|
99
103
|
// Opt-in session-state restore (plan 015 Task 4): names only; bodies re-resolve from
|
|
100
104
|
// the live registry the next time the model (re)loads them via load_skill.
|
|
101
105
|
if (options.persistSessionState && state.sessionState?.loadedSkillNames) {
|
|
@@ -105,6 +109,13 @@ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
|
105
109
|
if (options.persistSessionState && state.sessionState?.activatedToolNames) {
|
|
106
110
|
session.restoreActivatedTools(state.sessionState.activatedToolNames);
|
|
107
111
|
}
|
|
112
|
+
// Plan 074 P3: restore sticky attention mutations (already validated at load) so the first
|
|
113
|
+
// turn after a resume keeps its stubs instead of re-deciding them from the ratio.
|
|
114
|
+
if (options.persistSessionState && state.sessionState?.attentionSticky) {
|
|
115
|
+
const frontier = parseAttentionStickyFrontier(state.sessionState.attentionSticky);
|
|
116
|
+
if (frontier)
|
|
117
|
+
session.restoreAttentionSticky(frontier);
|
|
118
|
+
}
|
|
108
119
|
// Plan 018 Task 6 (closeout `checkpoint-bodies`): restore exact instructions so the
|
|
109
120
|
// resumed session renders them registry-independently (no load_skill round-trip).
|
|
110
121
|
if (options.persistSessionState && options.includeSkillBodies && state.sessionState?.loadedSkillBodies) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
|
|
2
|
+
import { type PersistedAttentionStickyFrontier } from "./attention-compiler.js";
|
|
2
3
|
import type { SecretRedactor } from "./redaction.js";
|
|
3
4
|
import { type LoadedSkillBodiesEntry } from "./skill-load.js";
|
|
4
5
|
export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
@@ -46,7 +47,12 @@ export interface StoredAgentRunState extends AgentRunState {
|
|
|
46
47
|
readonly loadedSkillBodies?: readonly LoadedSkillBodiesEntry[];
|
|
47
48
|
/** Plan 041: tools activated via `search_tools` (names only; inert for absent tools on restore). */
|
|
48
49
|
readonly activatedToolNames?: readonly string[];
|
|
50
|
+
/** Plan 074 P3: sticky attention mutations (thinking hashes + tool-call ids), so a durable
|
|
51
|
+
* resume keeps its stubs instead of re-deciding on the first turn. Validated on load. */
|
|
52
|
+
readonly attentionSticky?: PersistedAttentionStickyFrontier;
|
|
49
53
|
};
|
|
54
|
+
/** Per-run allow-list (Task 21). Absent = full registered set (legacy checkpoints). */
|
|
55
|
+
readonly toolNames?: readonly string[];
|
|
50
56
|
}
|
|
51
57
|
/** Session-state caps (plan 015 Task 4): bounded names charged against the run-state byte budget. */
|
|
52
58
|
export declare const MAX_PERSISTED_SKILL_NAMES = 64;
|
package/dist/agent-run-state.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
|
|
3
|
+
import { parseAttentionStickyFrontier } from "./attention-compiler.js";
|
|
3
4
|
import { validateLoadedSkillBodies } from "./skill-load.js";
|
|
5
|
+
import { HARD_RUN_TOOL_NAMES } from "./tools.js";
|
|
4
6
|
export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
5
7
|
export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
6
8
|
export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
|
|
@@ -128,7 +130,7 @@ export async function saveAgentRunState(input) {
|
|
|
128
130
|
return { record, state: { ...bounded, version: record.version } };
|
|
129
131
|
}
|
|
130
132
|
export function publicState(state) {
|
|
131
|
-
const { input: _input, pending: _pending, pendingCalls: _pendingCalls, nestedRuns: _nestedRuns, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
|
|
133
|
+
const { input: _input, pending: _pending, pendingCalls: _pendingCalls, nestedRuns: _nestedRuns, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, toolNames: _toolNames, ...publicValue } = state;
|
|
132
134
|
return publicValue;
|
|
133
135
|
}
|
|
134
136
|
export function initialAgentRunState(input) {
|
|
@@ -203,6 +205,16 @@ export function parseAgentRunState(value, version) {
|
|
|
203
205
|
!("snapshot" in state.loopState))) {
|
|
204
206
|
throw new AgentRunStateError("Malformed agent run loop state");
|
|
205
207
|
}
|
|
208
|
+
if (state.toolNames !== undefined) {
|
|
209
|
+
if (!Array.isArray(state.toolNames) || state.toolNames.length > HARD_RUN_TOOL_NAMES) {
|
|
210
|
+
throw new AgentRunStateError(`Run toolNames exceed ${HARD_RUN_TOOL_NAMES} entries`);
|
|
211
|
+
}
|
|
212
|
+
for (const name of state.toolNames) {
|
|
213
|
+
if (typeof name !== "string" || name.length === 0 || name.length > MAX_PERSISTED_SKILL_NAME_CHARS) {
|
|
214
|
+
throw new AgentRunStateError("Malformed agent run toolNames");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
206
218
|
// Load bounds against the hard cap, not the default: the configured maxStateBytes is a
|
|
207
219
|
// save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
|
|
208
220
|
// with a raised maxStateBytes must remain resumable.
|
|
@@ -261,15 +273,23 @@ function validateSessionState(sessionState) {
|
|
|
261
273
|
}
|
|
262
274
|
}
|
|
263
275
|
const activated = sessionState.activatedToolNames;
|
|
264
|
-
if (activated
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
throw new AgentRunStateError(`Activated-tool names exceed ${MAX_PERSISTED_ACTIVATED_TOOL_NAMES} entries`);
|
|
268
|
-
}
|
|
269
|
-
for (const name of activated) {
|
|
270
|
-
if (typeof name !== "string" || name.length > MAX_PERSISTED_SKILL_NAME_CHARS) {
|
|
271
|
-
throw new AgentRunStateError(`Activated-tool name exceeds ${MAX_PERSISTED_SKILL_NAME_CHARS} chars`);
|
|
276
|
+
if (activated !== undefined) {
|
|
277
|
+
if (!Array.isArray(activated) || activated.length > MAX_PERSISTED_ACTIVATED_TOOL_NAMES) {
|
|
278
|
+
throw new AgentRunStateError(`Activated-tool names exceed ${MAX_PERSISTED_ACTIVATED_TOOL_NAMES} entries`);
|
|
272
279
|
}
|
|
280
|
+
for (const name of activated) {
|
|
281
|
+
if (typeof name !== "string" || name.length > MAX_PERSISTED_SKILL_NAME_CHARS) {
|
|
282
|
+
throw new AgentRunStateError(`Activated-tool name exceeds ${MAX_PERSISTED_SKILL_NAME_CHARS} chars`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const attention = sessionState.attentionSticky;
|
|
287
|
+
if (attention === undefined)
|
|
288
|
+
return;
|
|
289
|
+
// Both arrays are capped by the parser, and a malformed frontier is dropped rather than
|
|
290
|
+
// failing the resume: re-deciding a mutation is safe, refusing to resume is not.
|
|
291
|
+
if (parseAttentionStickyFrontier(attention) === undefined) {
|
|
292
|
+
throw new AgentRunStateError("Malformed agent run attention frontier");
|
|
273
293
|
}
|
|
274
294
|
}
|
|
275
295
|
//# sourceMappingURL=agent-run-state.js.map
|
|
@@ -3,6 +3,7 @@ import { AgentRunSuspended } from "../../agent-approval.js";
|
|
|
3
3
|
import { resolveLoop, resolveToolConcurrency } from "../../agent-loops.js";
|
|
4
4
|
import { validateRunStateOptions } from "../../agent-run-state.js";
|
|
5
5
|
import { activeTools } from "../../agent-tool-dispatch.js";
|
|
6
|
+
import { resolveRunAttentionCompiler } from "../../attention-compiler.js";
|
|
6
7
|
import { AgentLoopStateError, AgentRunError, AgentRunStateError } from "../../contracts.js";
|
|
7
8
|
import { assertGuardrailsAllowed, runGuardrails } from "../../guardrails.js";
|
|
8
9
|
import { identityTelemetryAttributes, ownershipFromIdentity, resolveRunIdentity } from "../../identity.js";
|
|
@@ -16,7 +17,7 @@ import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } fro
|
|
|
16
17
|
import { composeSystemPrompt, mergeSystemPromptConfig } from "../../system-prompts.js";
|
|
17
18
|
import { resolveToolResultFold } from "../../tool-result-fold.js";
|
|
18
19
|
import { createSearchToolsTool, createToolSearchState, resolveToolsDisclosure } from "../../tool-search.js";
|
|
19
|
-
import { createToolRegistry } from "../../tools.js";
|
|
20
|
+
import { createToolRegistry, selectRunTools } from "../../tools.js";
|
|
20
21
|
import { bridgeAbort, createUsageAccumulator, inputToMessages, isDurableLoop, isSteerSoftInterrupt, mergeGuardrails, throwIfAborted, } from "../helpers.js";
|
|
21
22
|
import { cleanupRun, persistDurable, persistSucceeded, suspendDurable } from "./persist.js";
|
|
22
23
|
import { generateWithRetry, recordProviderUsage } from "./provider-round.js";
|
|
@@ -61,7 +62,12 @@ async function assembleRoundContext(params) {
|
|
|
61
62
|
};
|
|
62
63
|
await session.activeLedger?.appendRun(redactRunLedgerRecord(startRecord, session.activeRedactor));
|
|
63
64
|
await session.rebuildHistory();
|
|
64
|
-
const {
|
|
65
|
+
const { tools: listed } = activeTools(session.agent.config.tools);
|
|
66
|
+
const selected = selectRunTools(listed, options.toolNames, resumed?.state?.toolNames);
|
|
67
|
+
session.activeToolNames = selected.grant;
|
|
68
|
+
// Run-local snapshot: concurrent runs and MCP refresh must not mutate this registry.
|
|
69
|
+
const activeToolList = selected.tools;
|
|
70
|
+
const baseRegistry = createToolRegistry(activeToolList);
|
|
65
71
|
const toolsDisclosure = resolveToolsDisclosure(options.toolsDisclosure, session.agent.config.toolsDisclosure);
|
|
66
72
|
const toolSearch = toolsDisclosure === "search" && activeToolList.length > 0
|
|
67
73
|
? {
|
|
@@ -115,6 +121,26 @@ async function assembleRoundContext(params) {
|
|
|
115
121
|
const providerOptions = resolveRunProviderOptions(options, session.agent.config);
|
|
116
122
|
assertStructuredOutputRequestSupported(options.model ?? session.agent.config.model, providerOptions);
|
|
117
123
|
const validate = options.validate ?? session.agent.config.validator;
|
|
124
|
+
// Resolved once per run, before any provider turn: a bad setting or a widening run overlay
|
|
125
|
+
// fails here rather than on the turn that happens to cross the ratio (plan 074 C12).
|
|
126
|
+
const attentionCompiler = resolveRunAttentionCompiler(session.agent.config.attentionCompiler, options.attentionCompiler, options.model ?? session.agent.config.model);
|
|
127
|
+
// Telemetry seam (plan 074 T6): one `attention_compiled` per mutated turn, counts and the
|
|
128
|
+
// measured ratio inputs only. Under-ratio turns and compiler-off runs emit nothing.
|
|
129
|
+
const onAttentionReport = attentionCompiler
|
|
130
|
+
? (report) => session.emit({
|
|
131
|
+
type: "attention_compiled",
|
|
132
|
+
sessionId: session.id,
|
|
133
|
+
runId,
|
|
134
|
+
used: report.used,
|
|
135
|
+
usedAfter: report.usedAfter,
|
|
136
|
+
inputCap: report.inputCap,
|
|
137
|
+
triggerRatio: report.triggerRatio,
|
|
138
|
+
droppedThinkingTurns: report.droppedThinkingTurns,
|
|
139
|
+
stubbedToolResults: report.stubbedToolResults,
|
|
140
|
+
stubbedBytes: report.stubbedBytes,
|
|
141
|
+
truncated: report.truncated,
|
|
142
|
+
})
|
|
143
|
+
: undefined;
|
|
118
144
|
const instructionInjectors = options.instructionInjectors ?? session.agent.config.instructionInjectors ?? [];
|
|
119
145
|
const inputLayout = options.inputLayout ?? session.agent.config.inputLayout;
|
|
120
146
|
const loop = resolveLoop(options, session.agent.config);
|
|
@@ -183,6 +209,11 @@ async function assembleRoundContext(params) {
|
|
|
183
209
|
toolsSearch: session.agent.config.toolsSearch,
|
|
184
210
|
activatedTools: session.activatedTools,
|
|
185
211
|
toolResultFold: resolveToolResultFold(options.toolResultFold, session.agent.config.toolResultFold),
|
|
212
|
+
attentionCompiler,
|
|
213
|
+
// Session-owned: a stub made earlier stays applied even on a later under-ratio turn, so
|
|
214
|
+
// the prompt-cache prefix is not rewritten (C10). Undefined when the compiler is off.
|
|
215
|
+
attentionSticky: attentionCompiler ? session.attentionStickyFor() : undefined,
|
|
216
|
+
onAttentionReport,
|
|
186
217
|
loadedSkills: session.loadedSkills,
|
|
187
218
|
tools,
|
|
188
219
|
resourceLoader: session.agent.config.resourceLoader,
|
|
@@ -9,11 +9,14 @@ export async function persistDurable(session, state) {
|
|
|
9
9
|
const durable = session.activeDurable;
|
|
10
10
|
if (!durable)
|
|
11
11
|
throw new AgentRunStateError("Durable run state is not configured");
|
|
12
|
+
const withGrant = session.activeToolNames !== undefined ? { ...state, toolNames: session.activeToolNames } : state;
|
|
13
|
+
const attentionSticky = session.serializedAttentionSticky();
|
|
12
14
|
const persisted = durable.options.persistSessionState
|
|
13
15
|
? {
|
|
14
|
-
...
|
|
16
|
+
...withGrant,
|
|
15
17
|
sessionState: {
|
|
16
18
|
loadedSkillNames: session.loadedSkills.list(),
|
|
19
|
+
...(attentionSticky ? { attentionSticky } : {}),
|
|
17
20
|
...(session.activatedTools.list().length ? { activatedToolNames: session.activatedTools.list() } : {}),
|
|
18
21
|
...(durable.options.includeSkillBodies
|
|
19
22
|
? {
|
|
@@ -24,7 +27,7 @@ export async function persistDurable(session, state) {
|
|
|
24
27
|
: {}),
|
|
25
28
|
},
|
|
26
29
|
}
|
|
27
|
-
:
|
|
30
|
+
: withGrant;
|
|
28
31
|
const saved = await saveAgentRunState({
|
|
29
32
|
checkpoints: durable.options.checkpoints,
|
|
30
33
|
state: persisted,
|
|
@@ -155,6 +158,7 @@ export async function cleanupRun(input) {
|
|
|
155
158
|
session.activeMetadata = undefined;
|
|
156
159
|
session.activePromptVersion = undefined;
|
|
157
160
|
session.activeLimits?.dispose();
|
|
161
|
+
session.activeToolNames = undefined;
|
|
158
162
|
session.activeLimits = undefined;
|
|
159
163
|
session.activeLimitOutputBuffer = false;
|
|
160
164
|
session.activeRedactor = undefined;
|
|
@@ -278,6 +278,7 @@ export function bindDispatchToolCall(ctx) {
|
|
|
278
278
|
ownership: ctx.session.activeOwnership,
|
|
279
279
|
identity: ctx.session.activeIdentity,
|
|
280
280
|
guardrails: ctx.session.activeGuardrails,
|
|
281
|
+
...(ctx.tools.length > 0 ? { filter: { allow: ctx.tools.map((tool) => tool.name) } } : {}),
|
|
281
282
|
limitTracker: ctx.limits,
|
|
282
283
|
beforeExecute: async (mediatedCall) => {
|
|
283
284
|
const durable = ctx.session.activeDurable;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Shared host/round types for runInternal phase split (plan 059). Internal only. */
|
|
2
2
|
import type { ActiveDurableRun } from "../../agent-approval.js";
|
|
3
3
|
import type { PendingToolCall } from "../../agent-run-state.js";
|
|
4
|
+
import type { AttentionStickyFrontier, PersistedAttentionStickyFrontier } from "../../attention-compiler.js";
|
|
4
5
|
import type { Agent, AgentEvent, AgentLoopStrategy, AgentRunResult, AIProvider, ErrorInfo, Guardrails, LoopContext, Message, ModelConfig, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunLedger, RunOptions, SessionEntry, SessionStore, Skill, ToolDefinition, ToolEffectStore, ToolRegistry, Usage } from "../../contracts.js";
|
|
5
6
|
import type { AgentIdentity } from "../../identity.js";
|
|
6
7
|
import type { AgentInput } from "../../input.js";
|
|
@@ -47,6 +48,15 @@ export type SessionHost = {
|
|
|
47
48
|
readonly activatedTools: ActiveToolSet;
|
|
48
49
|
restoredSkillBodies: readonly LoadedSkillBodiesEntry[];
|
|
49
50
|
activeRunSkills: readonly Skill[];
|
|
51
|
+
/** Names-only grant for this run; undefined means the full registered set. */
|
|
52
|
+
activeToolNames?: readonly string[];
|
|
53
|
+
/** Sticky mutation frontier for this session (plan 074 C10); session-owned so it survives
|
|
54
|
+
* across turns, runs, and provider rounds. Lazily created on first use. */
|
|
55
|
+
attentionStickyFor(): AttentionStickyFrontier;
|
|
56
|
+
/** Plan 074 P3: bounded frontier snapshot for durable checkpoints (undefined before any mutation). */
|
|
57
|
+
serializedAttentionSticky(): PersistedAttentionStickyFrontier | undefined;
|
|
58
|
+
/** Plan 074 P3: restore a frontier that was validated when the checkpoint was loaded. */
|
|
59
|
+
restoreAttentionSticky(persisted: PersistedAttentionStickyFrontier): void;
|
|
50
60
|
invalidateSnapshot(): void;
|
|
51
61
|
resolveRunProvider(options: RunOptions): void;
|
|
52
62
|
emit(event: AgentEvent): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** session (0.2.5 plan 025 Task 1 split). Moved verbatim from agent-session.ts; public surface unchanged behind the barrel. */
|
|
2
2
|
import { ActiveDurableRun } from "../agent-approval.js";
|
|
3
3
|
import type { PendingToolCall, StoredAgentRunState } from "../agent-run-state.js";
|
|
4
|
+
import { type AttentionStickyFrontier, type PersistedAttentionStickyFrontier } from "../attention-compiler.js";
|
|
4
5
|
import type { Agent, AgentEvent, AgentRunResult, AgentRunState, AgentRunStateOptions, AgentSession, AgentSessionConfig, AIProvider, CompactionOptions, CompactionResult, ErrorInfo, Message, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunDecision, RunOptions, SessionEntry, Skill, SteerOptions, SubscribeOptions, ToolDefinition, ToolEffectStore, Usage } from "../contracts.js";
|
|
5
6
|
import type { AgentIdentity } from "../identity.js";
|
|
6
7
|
import type { AgentInput } from "../input.js";
|
|
@@ -48,6 +49,19 @@ export declare class RuntimeAgentSession implements AgentSession {
|
|
|
48
49
|
restoredSkillBodies: readonly LoadedSkillBodiesEntry[];
|
|
49
50
|
/** Skills of the current run (for the bodies snapshot); replaced at each run start. */
|
|
50
51
|
activeRunSkills: readonly import("../contracts.js").Skill[];
|
|
52
|
+
/** Per-run tool allow-list (Task 21); undefined means the full registered set. */
|
|
53
|
+
activeToolNames?: readonly string[];
|
|
54
|
+
/** Sticky frontier for this session (plan 074 C10); created on first use, so a session whose
|
|
55
|
+
* agents never enable the compiler allocates nothing. Mutations stay applied once made, so a
|
|
56
|
+
* later under-ratio turn re-applies them instead of rewriting the prompt-cache prefix. */
|
|
57
|
+
private attentionSticky?;
|
|
58
|
+
attentionStickyFor(): AttentionStickyFrontier;
|
|
59
|
+
/** Plan 074 P3: bounded snapshot for a durable checkpoint; `undefined` when the session never
|
|
60
|
+
* mutated anything, so a compiler-off (or never-over-ratio) session persists nothing extra. */
|
|
61
|
+
serializedAttentionSticky(): PersistedAttentionStickyFrontier | undefined;
|
|
62
|
+
/** Plan 074 P3: restore a frontier validated at checkpoint load, so a resumed run keeps its
|
|
63
|
+
* stubs instead of re-deciding its first turn from the ratio. */
|
|
64
|
+
restoreAttentionSticky(persisted: PersistedAttentionStickyFrontier): void;
|
|
51
65
|
/** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
|
|
52
66
|
restoreLoadedSkills(names: readonly string[]): void;
|
|
53
67
|
/** Plan 041: re-add persisted activated-tool names (names only; inert for absent tools). */
|
|
@@ -60,6 +74,7 @@ export declare class RuntimeAgentSession implements AgentSession {
|
|
|
60
74
|
private ledgerFailure;
|
|
61
75
|
private snapshotGeneration;
|
|
62
76
|
private snapshotCache?;
|
|
77
|
+
private readonly snapshotCacheTtlMs;
|
|
63
78
|
constructor(config: AgentSessionConfig & {
|
|
64
79
|
readonly agent: Agent;
|
|
65
80
|
});
|