@arnilo/prism 0.0.16 → 0.0.18
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 +33 -0
- package/README.md +9 -2
- package/dist/agent-run-state.js +13 -1
- package/dist/agents.js +42 -10
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +12 -0
- package/dist/cli-runner.d.ts +1 -5
- package/dist/cli-runner.js +5 -28
- package/dist/context-budget.js +10 -7
- package/dist/contracts.d.ts +13 -0
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/credentials.d.ts +7 -1
- package/dist/credentials.js +6 -2
- package/dist/event-multiplexer.js +17 -1
- package/dist/extensions.d.ts +7 -1
- package/dist/extensions.js +64 -6
- package/dist/feedback.js +1 -1
- package/dist/guardrails.js +9 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/input.js +12 -5
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/providers/openai-compatible.d.ts +42 -1
- package/dist/providers/openai-compatible.js +109 -47
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.js +21 -7
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +3 -9
- package/dist/session-stores.js +15 -11
- package/docs/0.1.0-readiness.md +35 -21
- package/docs/agent-events.md +2 -1
- package/docs/agent-session-runtime.md +3 -3
- package/docs/cli-rpc.md +1 -5
- package/docs/coding-agent-tools.md +10 -6
- package/docs/compaction-and-retry.md +3 -1
- package/docs/contribution-registries.md +1 -0
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/extensions.md +1 -1
- package/docs/guardrails.md +13 -2
- package/docs/index.md +4 -4
- package/docs/input-and-prompt-assembly.md +6 -8
- package/docs/mcp-tools.md +3 -3
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +24 -1
- package/docs/provider-caching.md +1 -1
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-packages.md +1 -1
- package/docs/providers/ai-sdk.md +2 -1
- package/docs/providers/openai-compatible.md +28 -1
- package/docs/public-contracts.md +2 -2
- package/docs/release-and-install.md +59 -17
- package/docs/session-stores.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.18] - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Default `inputLayout` is `cache_aware` (unset `AgentConfig` / `RunOptions` use cache-stable message order); set `inputLayout: "legacy"` to restore prior ordering.
|
|
7
|
+
- `applyContextBudget` evicts oldest history messages first under pressure (was newest-first).
|
|
8
|
+
- `@arnilo/prism-mcp` pins `@modelcontextprotocol/sdk` **1.30.0** (clears moderate `@hono/node-server` path-traversal advisory on the MCP HTTP stack).
|
|
9
|
+
|
|
10
|
+
### Breaking (minor, pre-1.0)
|
|
11
|
+
- `@arnilo/prism-coding-agent` `repo_search` is literal-only: `mode: "regex"` removed; `compileSearchPattern` drops the `mode` argument (ReDoS mitigation).
|
|
12
|
+
- Default local `write` / `edit` operations use same-directory temp + `rename` for crash-safe replacement.
|
|
13
|
+
|
|
14
|
+
See [docs/migration.md](docs/migration.md) for the full 0.0.17 → 0.0.18 notes.
|
|
15
|
+
|
|
16
|
+
## [0.0.17] - 2026-07-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Extension lifecycle: `ExtensionKernel.load()` returns `LoadedExtension[]` dispose handles; contribution/provider/model registries gain `unregister(...)`; a failed `setup` unwinds its partial registrations.
|
|
20
|
+
- `MemoryCredentialStoreOptions.allowProviderFallback` for strict provider-scoped credential resolution; `createMemoryCheckpointStore` `maxRecords`/`maxValueBytes` bounds; `ShellToolOptions.envAllowlist` (coding-agent); `ErrorInfo.retryAfterMs` plus `retryAfterMs`-aware `createDefaultRetryPolicy` with `jitter`/`random` options; guardrail `steer_rejected` event; `httpStatusError` provider transport helper wired into anthropic, google, kimi, openai, opencode-go, and the shared OpenAI-compatible transport.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Durable runs: run-state load now bounds against the 1 MiB hard cap (states saved with a raised `maxStateBytes` resume correctly); agent fingerprint also covers instructions, system-prompt contributions, and skills; resume-after-interrupt is explicit implicit-approval.
|
|
24
|
+
- Retry/backpressure: HTTP provider errors carry numeric codes and `Retry-After` hints; default retry policy applies ±25% jitter.
|
|
25
|
+
- `input_assembly` middleware runs unconditionally (both plain and context-budget paths, any `InputBuilder`); memory session store rejects cross-session `expectedParentId`; context-budget eviction is O(n) instead of O(n²).
|
|
26
|
+
- Guardrails: `interrupt` errors name the stage; `guardrail_failed` records carry the underlying error message in `metadata.error`; steer `block`/`tripwire` drops the message and emits `steer_rejected` instead of failing the run.
|
|
27
|
+
- Default prompt builder omits the `Available tools:` text for tool-capable models (`capabilities.tools === true`).
|
|
28
|
+
- Middleware registry throws on double `next()` and diagnoses conflicting `next(v)` + return; event multiplexer keeps sorted delivery while a consumer is parked; batched run-ledger dead counters removed.
|
|
29
|
+
|
|
30
|
+
### Breaking (minor, pre-1.0)
|
|
31
|
+
- CLI: `--config`, `--resource`, `--extension`, `--tool` are rejected (`<flag> is not supported in this build`); the dead `CliOptions.config/resources/extensions/tools` fields are removed.
|
|
32
|
+
- `ExtensionKernel.load()` resolves to `LoadedExtension[]` instead of `void`.
|
|
33
|
+
|
|
34
|
+
See [docs/migration.md](docs/migration.md) for the full 0.0.16 → 0.0.17 notes.
|
|
35
|
+
|
|
3
36
|
## [0.0.16] - 2026-07-26
|
|
4
37
|
|
|
5
38
|
### Added
|
package/README.md
CHANGED
|
@@ -157,6 +157,13 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
157
157
|
| `@arnilo/prism-provider-neuralwatt` | NeuralWatt provider with implicit vLLM prefix caching |
|
|
158
158
|
| `@arnilo/prism-provider-alibaba` | Alibaba Cloud (Model Studio / DashScope + Coding Plan) provider with dynamic discovery and explicit/implicit caching |
|
|
159
159
|
| `@arnilo/prism-provider-ollama` | Ollama Cloud / local provider with dynamic discovery and implicit-only caching |
|
|
160
|
+
| `@arnilo/prism-provider-anthropic` | Anthropic Messages provider |
|
|
161
|
+
| `@arnilo/prism-provider-google` | Google Gemini provider |
|
|
162
|
+
| `@arnilo/prism-provider-azure` | Azure OpenAI provider |
|
|
163
|
+
| `@arnilo/prism-provider-bedrock` | AWS Bedrock provider |
|
|
164
|
+
| `@arnilo/prism-provider-vertex` | Google Vertex provider |
|
|
165
|
+
| `@arnilo/prism-provider-ai-sdk` | AI SDK interoperability adapter |
|
|
166
|
+
| `@arnilo/prism-browser` | optional host-wired Playwright browser automation (not core; not auto-activated) |
|
|
160
167
|
| `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
|
|
161
168
|
| `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
|
|
162
169
|
| `@arnilo/prism-coding-agent` | bounded shell/read/write/edit tools |
|
|
@@ -170,7 +177,7 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
170
177
|
| `@arnilo/prism-credentials-node` | encrypted-file and keychain credentials |
|
|
171
178
|
| `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases/owned run feedback |
|
|
172
179
|
| `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases/owned run feedback |
|
|
173
|
-
| `@arnilo/prism-providers` | family: all
|
|
180
|
+
| `@arnilo/prism-providers` | family: all 14 first-party provider adapters, including AI SDK interoperability |
|
|
174
181
|
| `@arnilo/prism-compaction` | family: both compaction strategies |
|
|
175
182
|
| `@arnilo/prism-base` | profile: core + compaction + JSON Schema validation |
|
|
176
183
|
| `@arnilo/prism-code` | profile: base + coding tools/security + MCP |
|
|
@@ -189,6 +196,6 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
189
196
|
## Non-goals (v1)
|
|
190
197
|
|
|
191
198
|
- Privileged tools, MCP servers, telemetry, credentials, or databases activated by install — hosts explicitly configure and register every capability.
|
|
192
|
-
- Browser automation or interactive terminal UI —
|
|
199
|
+
- Browser automation or interactive terminal UI in core — hosts may opt into `@arnilo/prism-browser` with their own Playwright lifecycle; Prism does not auto-start browsers or ship a TUI.
|
|
193
200
|
- Provider, credential, extension, or package auto-discovery.
|
|
194
201
|
- Core-owned database drivers, secret persistence, sandbox, or application policy — optional packages implement adapters over host-owned boundaries.
|
package/dist/agent-run-state.js
CHANGED
|
@@ -9,16 +9,25 @@ const MAX_PROPERTIES = 256;
|
|
|
9
9
|
export function agentFingerprint(agent, revision) {
|
|
10
10
|
const config = agent.config;
|
|
11
11
|
const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
|
|
12
|
+
const skills = !config.skills ? [] : "list" in config.skills ? config.skills.list() : config.skills;
|
|
12
13
|
const guardrails = [
|
|
13
14
|
...(config.guardrails?.input ?? []),
|
|
14
15
|
...(config.guardrails?.output ?? []),
|
|
15
16
|
...(config.guardrails?.toolInput ?? []),
|
|
16
17
|
...(config.guardrails?.toolOutput ?? []),
|
|
17
18
|
];
|
|
19
|
+
const systemPrompt = config.systemPrompt === false || config.systemPrompt === undefined
|
|
20
|
+
? (config.systemPrompt ?? null)
|
|
21
|
+
: (Array.isArray(config.systemPrompt) ? config.systemPrompt : [config.systemPrompt]).map((c) => ({ id: c.id, text: c.text }));
|
|
18
22
|
const value = JSON.stringify({
|
|
19
23
|
id: config.id ?? config.name ?? "agent",
|
|
20
24
|
revision,
|
|
21
25
|
model: config.model,
|
|
26
|
+
// Instructions/prompt text shapes agent behavior as much as the tool set; a change
|
|
27
|
+
// without a definitionRevision bump must not resume stale durable runs silently.
|
|
28
|
+
instructions: config.instructions ?? null,
|
|
29
|
+
systemPrompt,
|
|
30
|
+
skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
|
|
22
31
|
tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
|
|
23
32
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
33
|
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
@@ -111,7 +120,10 @@ export function parseAgentRunState(value, version) {
|
|
|
111
120
|
!state.deadlineAt) {
|
|
112
121
|
throw new AgentRunStateError("Malformed agent run state");
|
|
113
122
|
}
|
|
114
|
-
|
|
123
|
+
// Load bounds against the hard cap, not the default: the configured maxStateBytes is a
|
|
124
|
+
// save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
|
|
125
|
+
// with a raised maxStateBytes must remain resumable.
|
|
126
|
+
return boundState({ ...state, version }, HARD_MAX_AGENT_RUN_STATE_BYTES);
|
|
115
127
|
}
|
|
116
128
|
function boundState(state, maxBytes) {
|
|
117
129
|
checkShape(state, 0);
|
package/dist/agents.js
CHANGED
|
@@ -346,15 +346,17 @@ class RuntimeAgentSession {
|
|
|
346
346
|
redactor: this.activeRedactor,
|
|
347
347
|
emit: (event) => this.emit(event),
|
|
348
348
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
// Input-guardrail decision table:
|
|
350
|
+
// - interrupt + durable + fresh run → suspend for approval.
|
|
351
|
+
// - interrupt + durable + resumed run → proceed: resuming IS the operator approval.
|
|
352
|
+
// - interrupt without durable, or block/tripwire → fail via assertGuardrailsAllowed.
|
|
353
|
+
const approvedByResume = resumed !== undefined && inputGuardrails.terminal?.action === "interrupt" && this.activeDurable !== undefined;
|
|
354
|
+
if (inputGuardrails.terminal?.action === "interrupt" && this.activeDurable && !approvedByResume) {
|
|
355
|
+
const interruption = { kind: "input_guardrail", reason: inputGuardrails.terminal.reason ?? "Input requires approval" };
|
|
356
|
+
throw new AgentRunSuspended(await this.suspendDurable({ runId, model, limits, interruption, messages: inputMessages }), interruption);
|
|
354
357
|
}
|
|
355
|
-
|
|
358
|
+
if (inputGuardrails.terminal && !approvedByResume)
|
|
356
359
|
assertGuardrailsAllowed(inputGuardrails);
|
|
357
|
-
}
|
|
358
360
|
for (const message of inputMessages)
|
|
359
361
|
await this.appendMessage(message, runId);
|
|
360
362
|
await this.autoCompact(runId, options, controller.signal, inputMessages);
|
|
@@ -1049,7 +1051,22 @@ class RuntimeAgentSession {
|
|
|
1049
1051
|
redactor: this.activeRedactor,
|
|
1050
1052
|
emit: (event) => this.emit(event),
|
|
1051
1053
|
});
|
|
1052
|
-
|
|
1054
|
+
// Mid-run steer: a terminal decision drops the message (never enters history or
|
|
1055
|
+
// the session store) and the run continues. Run-start input blocking still fails
|
|
1056
|
+
// the run — only the blast radius of steered input is narrowed.
|
|
1057
|
+
const terminal = inputGuardrails.terminal;
|
|
1058
|
+
if (terminal) {
|
|
1059
|
+
if (terminal.action === "interrupt")
|
|
1060
|
+
throw new GuardrailError(terminal);
|
|
1061
|
+
this.emit({
|
|
1062
|
+
type: "steer_rejected",
|
|
1063
|
+
sessionId: this.id,
|
|
1064
|
+
runId,
|
|
1065
|
+
message: this.activeRedactor ? this.activeRedactor.redact(message) : message,
|
|
1066
|
+
record: terminal,
|
|
1067
|
+
});
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1053
1070
|
this.history.push(message);
|
|
1054
1071
|
await this.appendMessage(message, runId);
|
|
1055
1072
|
}
|
|
@@ -1324,11 +1341,25 @@ function withoutTrailingInput(messages, input) {
|
|
|
1324
1341
|
const next = [...messages];
|
|
1325
1342
|
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
1326
1343
|
const last = next.at(-1);
|
|
1327
|
-
if (last &&
|
|
1344
|
+
if (last && stableMessageKey(last) === stableMessageKey(input[i]))
|
|
1328
1345
|
next.pop();
|
|
1329
1346
|
}
|
|
1330
1347
|
return next;
|
|
1331
1348
|
}
|
|
1349
|
+
// Key-order-insensitive comparison: a redacted-then-reassembled message with reordered
|
|
1350
|
+
// keys must still dedupe against the trailing input, or auto-compaction duplicates it.
|
|
1351
|
+
function stableMessageKey(value) {
|
|
1352
|
+
if (Array.isArray(value))
|
|
1353
|
+
return `[${value.map(stableMessageKey).join(",")}]`;
|
|
1354
|
+
if (value !== null && typeof value === "object") {
|
|
1355
|
+
const record = value;
|
|
1356
|
+
return `{${Object.keys(record)
|
|
1357
|
+
.sort()
|
|
1358
|
+
.map((key) => `${JSON.stringify(key)}:${stableMessageKey(record[key])}`)
|
|
1359
|
+
.join(",")}}`;
|
|
1360
|
+
}
|
|
1361
|
+
return JSON.stringify(value) ?? "null";
|
|
1362
|
+
}
|
|
1332
1363
|
function bridgeAbort(signal, controller) {
|
|
1333
1364
|
if (!signal)
|
|
1334
1365
|
return () => undefined;
|
|
@@ -1346,9 +1377,10 @@ function throwIfAbortedSignal(signal) {
|
|
|
1346
1377
|
if (signal?.aborted)
|
|
1347
1378
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
1348
1379
|
}
|
|
1380
|
+
const jsonTextEncoder = new TextEncoder();
|
|
1349
1381
|
function jsonBytes(value) {
|
|
1350
1382
|
try {
|
|
1351
|
-
return
|
|
1383
|
+
return jsonTextEncoder.encode(JSON.stringify(value)).byteLength;
|
|
1352
1384
|
}
|
|
1353
1385
|
catch {
|
|
1354
1386
|
throw new TypeError("Provider request or event must be JSON-serializable for run limits");
|
package/dist/checkpoints.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare class CheckpointConflictError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export interface MemoryCheckpointStoreOptions {
|
|
8
8
|
readonly maxPageSize?: number;
|
|
9
|
+
/** Capacity bound: least-recently-saved records are evicted past this count (default 10_000). */
|
|
10
|
+
readonly maxRecords?: number;
|
|
11
|
+
/** Per-record bound on the JSON-encoded value in bytes (default 1 MiB); oversized values are rejected. */
|
|
12
|
+
readonly maxValueBytes?: number;
|
|
9
13
|
}
|
|
10
14
|
/** In-process reference implementation of the generic checkpoint contract. */
|
|
11
15
|
export declare function createMemoryCheckpointStore(options?: MemoryCheckpointStoreOptions): CheckpointStore;
|
package/dist/checkpoints.js
CHANGED
|
@@ -6,10 +6,14 @@ export class CheckpointConflictError extends Error {
|
|
|
6
6
|
this.name = "CheckpointConflictError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
const DEFAULT_MAX_CHECKPOINT_RECORDS = 10_000;
|
|
10
|
+
const DEFAULT_MAX_CHECKPOINT_VALUE_BYTES = 1024 * 1024;
|
|
9
11
|
/** In-process reference implementation of the generic checkpoint contract. */
|
|
10
12
|
export function createMemoryCheckpointStore(options = {}) {
|
|
11
13
|
const records = new Map();
|
|
12
14
|
const maxPageSize = Math.max(1, options.maxPageSize ?? 500);
|
|
15
|
+
const maxRecords = Math.max(1, options.maxRecords ?? DEFAULT_MAX_CHECKPOINT_RECORDS);
|
|
16
|
+
const maxValueBytes = Math.max(1, options.maxValueBytes ?? DEFAULT_MAX_CHECKPOINT_VALUE_BYTES);
|
|
13
17
|
return {
|
|
14
18
|
async saveCheckpoint(input) {
|
|
15
19
|
throwIfAborted(input.signal);
|
|
@@ -32,6 +36,9 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
32
36
|
}
|
|
33
37
|
const now = new Date().toISOString();
|
|
34
38
|
const value = cloneJson(input.value, "Checkpoint value");
|
|
39
|
+
if (new TextEncoder().encode(JSON.stringify(value)).length > maxValueBytes) {
|
|
40
|
+
throw new RangeError(`Checkpoint value exceeds maxValueBytes (${maxValueBytes})`);
|
|
41
|
+
}
|
|
35
42
|
const metadata = input.metadata === undefined ? undefined : cloneJson(input.metadata, "Checkpoint metadata");
|
|
36
43
|
const record = {
|
|
37
44
|
namespace: input.namespace,
|
|
@@ -45,7 +52,12 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
45
52
|
updatedAt: now,
|
|
46
53
|
...(metadata === undefined ? {} : { metadata }),
|
|
47
54
|
};
|
|
55
|
+
// delete+set moves an updated record to the end, keeping Map order = least- to
|
|
56
|
+
// most-recently-saved, so capacity eviction is O(1) off the front.
|
|
57
|
+
records.delete(id);
|
|
48
58
|
records.set(id, record);
|
|
59
|
+
while (records.size > maxRecords)
|
|
60
|
+
records.delete(records.keys().next().value);
|
|
49
61
|
return record;
|
|
50
62
|
},
|
|
51
63
|
async loadCheckpoint(input) {
|
package/dist/cli-runner.d.ts
CHANGED
|
@@ -9,10 +9,6 @@ export interface CliOptions {
|
|
|
9
9
|
readonly provider?: string;
|
|
10
10
|
readonly model?: string;
|
|
11
11
|
readonly session?: string;
|
|
12
|
-
readonly config: readonly string[];
|
|
13
|
-
readonly resources: readonly string[];
|
|
14
|
-
readonly extensions: readonly string[];
|
|
15
|
-
readonly tools: readonly string[];
|
|
16
12
|
readonly system?: string;
|
|
17
13
|
readonly context: readonly string[];
|
|
18
14
|
readonly compact?: number;
|
|
@@ -67,7 +63,7 @@ export interface CliRuntime {
|
|
|
67
63
|
/** Working directory for relative `prism init` destinations (tests). */
|
|
68
64
|
readonly cwd?: string;
|
|
69
65
|
}
|
|
70
|
-
export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --
|
|
66
|
+
export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
|
|
71
67
|
export declare function parseCliArgs(argv: readonly string[]): CliOptions;
|
|
72
68
|
export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
|
|
73
69
|
export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
|
package/dist/cli-runner.js
CHANGED
|
@@ -19,10 +19,6 @@ Options:
|
|
|
19
19
|
--provider <name> Explicit provider id (mock is built in for smoke tests)
|
|
20
20
|
--model <name> Explicit model name
|
|
21
21
|
--session <id> Session id
|
|
22
|
-
--config <path> Explicit config path (recorded, not auto-loaded)
|
|
23
|
-
--resource <uri> Explicit resource URI (recorded, not auto-loaded)
|
|
24
|
-
--extension <name> Explicit extension name (recorded, not auto-loaded)
|
|
25
|
-
--tool <name> Explicit tool name (recorded, not auto-enabled)
|
|
26
22
|
--system <text> System instructions
|
|
27
23
|
--context <text> Context text
|
|
28
24
|
--compact <entries> Auto-compaction threshold
|
|
@@ -43,10 +39,6 @@ const valueFlags = new Set([
|
|
|
43
39
|
"--provider",
|
|
44
40
|
"--model",
|
|
45
41
|
"--session",
|
|
46
|
-
"--config",
|
|
47
|
-
"--resource",
|
|
48
|
-
"--extension",
|
|
49
|
-
"--tool",
|
|
50
42
|
"--system",
|
|
51
43
|
"--context",
|
|
52
44
|
"--compact",
|
|
@@ -59,6 +51,9 @@ const valueFlags = new Set([
|
|
|
59
51
|
"--agents-config",
|
|
60
52
|
]);
|
|
61
53
|
const boolFlags = new Set(["--discover", "--no-discovery", "--no-agents-md", "--no-system-md"]);
|
|
54
|
+
// Known-but-inert flags: parsed by earlier builds, never wired to any behavior.
|
|
55
|
+
// Rejected loudly (rather than silently ignored) until a CLI-harness plan wires them.
|
|
56
|
+
const unsupportedFlags = new Set(["--config", "--resource", "--extension", "--tool"]);
|
|
62
57
|
const ALL_KINDS = ["skill", "tool", "context", "instructions"];
|
|
63
58
|
export function parseCliArgs(argv) {
|
|
64
59
|
let mode = "print";
|
|
@@ -78,10 +73,6 @@ export function parseCliArgs(argv) {
|
|
|
78
73
|
let agentsMdFile;
|
|
79
74
|
let systemMdFile;
|
|
80
75
|
let agentsConfig;
|
|
81
|
-
const config = [];
|
|
82
|
-
const resources = [];
|
|
83
|
-
const extensions = [];
|
|
84
|
-
const tools = [];
|
|
85
76
|
const context = [];
|
|
86
77
|
const instructions = [];
|
|
87
78
|
const injectorFiles = [];
|
|
@@ -92,6 +83,8 @@ export function parseCliArgs(argv) {
|
|
|
92
83
|
continue;
|
|
93
84
|
}
|
|
94
85
|
const name = flag === "-p" ? "--prompt" : flag;
|
|
86
|
+
if (unsupportedFlags.has(name))
|
|
87
|
+
throw new CliUsageError(`${name} is not supported in this build`);
|
|
95
88
|
if (boolFlags.has(name)) {
|
|
96
89
|
switch (name) {
|
|
97
90
|
case "--discover":
|
|
@@ -133,18 +126,6 @@ export function parseCliArgs(argv) {
|
|
|
133
126
|
case "--session":
|
|
134
127
|
session = value;
|
|
135
128
|
break;
|
|
136
|
-
case "--config":
|
|
137
|
-
config.push(value);
|
|
138
|
-
break;
|
|
139
|
-
case "--resource":
|
|
140
|
-
resources.push(value);
|
|
141
|
-
break;
|
|
142
|
-
case "--extension":
|
|
143
|
-
extensions.push(value);
|
|
144
|
-
break;
|
|
145
|
-
case "--tool":
|
|
146
|
-
tools.push(value);
|
|
147
|
-
break;
|
|
148
129
|
case "--system":
|
|
149
130
|
system = value;
|
|
150
131
|
break;
|
|
@@ -183,10 +164,6 @@ export function parseCliArgs(argv) {
|
|
|
183
164
|
provider,
|
|
184
165
|
model,
|
|
185
166
|
session,
|
|
186
|
-
config,
|
|
187
|
-
resources,
|
|
188
|
-
extensions,
|
|
189
|
-
tools,
|
|
190
167
|
system,
|
|
191
168
|
context,
|
|
192
169
|
compact,
|
package/dist/context-budget.js
CHANGED
|
@@ -48,7 +48,7 @@ export function getContextBudgetReport(request) {
|
|
|
48
48
|
}
|
|
49
49
|
export function applyContextBudget(options) {
|
|
50
50
|
const budget = resolveContextBudget(options.budget);
|
|
51
|
-
const layout = options.layout ?? "
|
|
51
|
+
const layout = options.layout ?? "cache_aware";
|
|
52
52
|
const groups = {
|
|
53
53
|
instructions: [...options.groups.instructions],
|
|
54
54
|
summaries: [...options.groups.summaries],
|
|
@@ -61,16 +61,19 @@ export function applyContextBudget(options) {
|
|
|
61
61
|
const skills = [...(options.skills ?? [])].filter((skill) => skill.instructions);
|
|
62
62
|
const tools = options.tools ? [...options.tools] : undefined;
|
|
63
63
|
const omitted = [];
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Measure once, then subtract each dropped item's own estimate (dropNext computes it
|
|
65
|
+
// with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
|
|
66
|
+
const kept = measureAll(groups, context, skills, tools);
|
|
67
|
+
while (overBudget(kept, budget)) {
|
|
66
68
|
const drop = dropNext(groups, context, skills, layout);
|
|
67
69
|
if (!drop) {
|
|
68
70
|
throw new ContextBudgetError();
|
|
69
71
|
}
|
|
72
|
+
kept.tokens -= drop.tokenEstimate;
|
|
73
|
+
kept.bytes -= drop.byteLength;
|
|
70
74
|
if (omitted.length < HARD_MAX_CONTEXT_BUDGET_OMISSIONS)
|
|
71
75
|
omitted.push(drop);
|
|
72
76
|
}
|
|
73
|
-
const kept = cost();
|
|
74
77
|
const reportOmissions = omitted.slice(0, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS);
|
|
75
78
|
return {
|
|
76
79
|
groups,
|
|
@@ -88,8 +91,8 @@ export function applyContextBudget(options) {
|
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
function dropNext(groups, context, skills, layout) {
|
|
91
|
-
// ponytail: drop
|
|
92
|
-
// attachments longer so stable prefix stays intact while budget still allows it.
|
|
94
|
+
// ponytail: drop droppable groups in layout order; within history, drop oldest first (shift).
|
|
95
|
+
// cache_aware keeps attachments longer so stable prefix stays intact while budget still allows it.
|
|
93
96
|
const order = layout === "cache_aware"
|
|
94
97
|
? ["tool_results", "history", "summaries", "context", "skills", "attachments"]
|
|
95
98
|
: ["tool_results", "history", "summaries", "attachments", "context", "skills"];
|
|
@@ -99,7 +102,7 @@ function dropNext(groups, context, skills, layout) {
|
|
|
99
102
|
return omission("tool_results", message.id ?? toolResultId(message), message);
|
|
100
103
|
}
|
|
101
104
|
if (kind === "history" && groups.history.length > 0) {
|
|
102
|
-
const message = groups.history.
|
|
105
|
+
const message = groups.history.shift();
|
|
103
106
|
return omission("history", message.id, message);
|
|
104
107
|
}
|
|
105
108
|
if (kind === "summaries" && groups.summaries.length > 0) {
|
package/dist/contracts.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface ErrorInfo {
|
|
|
15
15
|
readonly name?: string;
|
|
16
16
|
readonly message: string;
|
|
17
17
|
readonly code?: string | number;
|
|
18
|
+
/** Provider backpressure hint (e.g. from a `Retry-After` header); retry policies
|
|
19
|
+
* honor it capped at their own `maxDelayMs`. */
|
|
20
|
+
readonly retryAfterMs?: number;
|
|
18
21
|
readonly cause?: unknown;
|
|
19
22
|
}
|
|
20
23
|
export type { AudioContent, DocumentContent, FileContent } from "./content.js";
|
|
@@ -749,6 +752,13 @@ export type AgentEvent = {
|
|
|
749
752
|
readonly sessionId: string;
|
|
750
753
|
readonly runId: string;
|
|
751
754
|
readonly size: number;
|
|
755
|
+
} | {
|
|
756
|
+
/** A steered message was dropped by a terminal input guardrail; the run continues without it. */
|
|
757
|
+
readonly type: "steer_rejected";
|
|
758
|
+
readonly sessionId: string;
|
|
759
|
+
readonly runId: string;
|
|
760
|
+
readonly message: Message;
|
|
761
|
+
readonly record: GuardrailRecord;
|
|
752
762
|
} | {
|
|
753
763
|
readonly type: "event_subscriber_overflow";
|
|
754
764
|
readonly sessionId: string;
|
|
@@ -937,6 +947,9 @@ export interface PromptBuildRequest {
|
|
|
937
947
|
readonly context?: readonly ContextBlock[];
|
|
938
948
|
readonly skills?: readonly Skill[];
|
|
939
949
|
readonly tools?: readonly ToolDefinition[];
|
|
950
|
+
/** Model being prompted; lets builders adapt composition to declared capabilities
|
|
951
|
+
* (e.g. the default builder omits the `Available tools:` text for tool-capable models). */
|
|
952
|
+
readonly model?: ModelConfig;
|
|
940
953
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
941
954
|
readonly signal?: AbortSignal;
|
|
942
955
|
}
|
package/dist/contributions.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type ProviderRegistry } from "./providers.js";
|
|
|
4
4
|
import { type DuplicateRegistrationOptions } from "./registry-options.js";
|
|
5
5
|
export interface ContributionRegistry<T> {
|
|
6
6
|
register(key: string, contribution: T): void;
|
|
7
|
+
/** Remove a contribution; returns false when the key was not registered. */
|
|
8
|
+
unregister(key: string): boolean;
|
|
7
9
|
get(key: string): T | undefined;
|
|
8
10
|
resolve(key: string): T;
|
|
9
11
|
list(): readonly T[];
|
package/dist/contributions.js
CHANGED
|
@@ -9,6 +9,9 @@ export function createContributionRegistry(options = {}) {
|
|
|
9
9
|
assertCanRegister(byKey, key, label, key, options.duplicate);
|
|
10
10
|
byKey.set(key, contribution);
|
|
11
11
|
},
|
|
12
|
+
unregister(key) {
|
|
13
|
+
return byKey.delete(key);
|
|
14
|
+
},
|
|
12
15
|
get(key) {
|
|
13
16
|
return byKey.get(key);
|
|
14
17
|
},
|
package/dist/credentials.d.ts
CHANGED
|
@@ -10,7 +10,13 @@ export interface MemoryCredentialStore extends CredentialResolver {
|
|
|
10
10
|
delete(request: Pick<CredentialRequest, "name" | "provider">): boolean;
|
|
11
11
|
clear(): void;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export interface MemoryCredentialStoreOptions {
|
|
14
|
+
/** When true (default, backward compatible), a provider-scoped request falls back to a
|
|
15
|
+
* providerless record of the same name — that record is then served to EVERY provider.
|
|
16
|
+
* Set false for exact-match-only resolution (strict provider scoping). */
|
|
17
|
+
readonly allowProviderFallback?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function createMemoryCredentialStore(initial?: readonly CredentialRecord[], options?: MemoryCredentialStoreOptions): MemoryCredentialStore;
|
|
14
20
|
export declare function createChainedCredentialResolver(resolvers: readonly CredentialResolver[]): CredentialResolver;
|
|
15
21
|
export declare function createExplicitCredentialResolver(sources: readonly CredentialResolverSource[]): CredentialResolver;
|
|
16
22
|
export declare function createEnvCredentialResolver(env: Readonly<Record<string, string | undefined>>, map: Readonly<Record<string, string>>): CredentialResolver;
|
package/dist/credentials.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export function createMemoryCredentialStore(initial = []) {
|
|
1
|
+
export function createMemoryCredentialStore(initial = [], options = {}) {
|
|
2
2
|
const records = new Map();
|
|
3
|
+
const allowProviderFallback = options.allowProviderFallback ?? true;
|
|
3
4
|
const key = (name, provider) => `${provider ?? ""}\u0000${name}`;
|
|
4
5
|
const store = {
|
|
5
6
|
set(record) {
|
|
@@ -12,7 +13,10 @@ export function createMemoryCredentialStore(initial = []) {
|
|
|
12
13
|
records.clear();
|
|
13
14
|
},
|
|
14
15
|
resolve(request) {
|
|
15
|
-
|
|
16
|
+
const exact = records.get(key(request.name, request.provider));
|
|
17
|
+
if (exact !== undefined || !allowProviderFallback || request.provider === undefined)
|
|
18
|
+
return exact;
|
|
19
|
+
return records.get(key(request.name));
|
|
16
20
|
},
|
|
17
21
|
};
|
|
18
22
|
for (const record of initial)
|
|
@@ -13,10 +13,21 @@ export function createEventMultiplexer(options = {}) {
|
|
|
13
13
|
isClosed = true;
|
|
14
14
|
else
|
|
15
15
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
16
|
+
// With compare set, publish always enqueues and the parked consumer is woken with
|
|
17
|
+
// this token instead of a value, so every event drains through the sorted queue —
|
|
18
|
+
// delivery order follows the comparator even when the consumer is caught up.
|
|
19
|
+
const WAKE = Symbol("wake");
|
|
20
|
+
const wakeWaiter = () => {
|
|
21
|
+
if (!waiter)
|
|
22
|
+
return;
|
|
23
|
+
const resolve = waiter;
|
|
24
|
+
waiter = undefined;
|
|
25
|
+
resolve({ value: WAKE, done: false });
|
|
26
|
+
};
|
|
16
27
|
function publish(event) {
|
|
17
28
|
if (isClosed)
|
|
18
29
|
return;
|
|
19
|
-
if (waiter) {
|
|
30
|
+
if (!options.compare && waiter) {
|
|
20
31
|
const resolve = waiter;
|
|
21
32
|
waiter = undefined;
|
|
22
33
|
resolve({ value: event, done: false });
|
|
@@ -24,6 +35,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
24
35
|
}
|
|
25
36
|
if (queue.length < maxQueuedEvents) {
|
|
26
37
|
queue.push(event);
|
|
38
|
+
wakeWaiter();
|
|
27
39
|
return;
|
|
28
40
|
}
|
|
29
41
|
droppedEvents += 1;
|
|
@@ -41,6 +53,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
41
53
|
if (overflow === "drop_newest") {
|
|
42
54
|
if (notice !== undefined)
|
|
43
55
|
queue[queue.length - 1] = notice;
|
|
56
|
+
wakeWaiter();
|
|
44
57
|
return;
|
|
45
58
|
}
|
|
46
59
|
queue.shift();
|
|
@@ -50,6 +63,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
50
63
|
queue.shift();
|
|
51
64
|
}
|
|
52
65
|
queue.push(event);
|
|
66
|
+
wakeWaiter();
|
|
53
67
|
}
|
|
54
68
|
function observe(source, map) {
|
|
55
69
|
if (isClosed)
|
|
@@ -115,6 +129,8 @@ export function createEventMultiplexer(options = {}) {
|
|
|
115
129
|
});
|
|
116
130
|
if (next.done)
|
|
117
131
|
return;
|
|
132
|
+
if (next.value === WAKE)
|
|
133
|
+
continue;
|
|
118
134
|
yield next.value;
|
|
119
135
|
}
|
|
120
136
|
}
|
package/dist/extensions.d.ts
CHANGED
|
@@ -26,11 +26,17 @@ export interface ExtensionEventBus {
|
|
|
26
26
|
on(type: ExtensionLifecycleEventName | string, handler: ExtensionEventHandler): () => void;
|
|
27
27
|
emit(event: ExtensionEvent): Promise<void>;
|
|
28
28
|
}
|
|
29
|
+
export interface LoadedExtension {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** Remove this extension's registry contributions and middleware/event subscriptions.
|
|
32
|
+
* Best-effort and idempotent; side effects outside the registries are NOT unwound. */
|
|
33
|
+
dispose(): void;
|
|
34
|
+
}
|
|
29
35
|
export interface ExtensionKernel {
|
|
30
36
|
readonly registries: ContributionRegistries;
|
|
31
37
|
readonly middleware: MiddlewareRegistry;
|
|
32
38
|
readonly events: ExtensionEventBus;
|
|
33
|
-
load(extensions: readonly Extension[]): Promise<
|
|
39
|
+
load(extensions: readonly Extension[]): Promise<LoadedExtension[]>;
|
|
34
40
|
}
|
|
35
41
|
export declare function createExtensionEventBus(options?: Pick<ExtensionKernelOptions, "errorPolicy" | "secrets">): ExtensionEventBus;
|
|
36
42
|
export declare function createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel;
|