@arnilo/prism 0.2.9 → 0.3.1
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 +39 -0
- package/README.md +12 -5
- package/dist/agent-loops.js +45 -8
- package/dist/agent-session/helpers.js +2 -2
- package/dist/cache-helpers.d.ts +11 -0
- package/dist/cache-helpers.js +29 -5
- package/dist/cli-provider-add.js +2 -1
- package/dist/context-budget.js +9 -6
- package/dist/contracts-core/agent.d.ts +2 -0
- package/dist/contracts-core/provider.d.ts +2 -0
- package/dist/contracts-protocol.d.ts +31 -1
- package/dist/delegated-agent-step.d.ts +20 -0
- package/dist/delegated-agent-step.js +99 -0
- package/dist/event-multiplexer.js +0 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -2
- package/dist/input.js +19 -11
- package/dist/node/session-store-jsonl.js +7 -3
- package/dist/providers/openai-compatible.js +2 -1
- package/dist/providers/openai-primitives.js +2 -1
- package/dist/providers/schema.d.ts +7 -0
- package/dist/providers/schema.js +25 -0
- package/dist/testing/provider-conformance.d.ts +10 -0
- package/dist/testing/provider-conformance.js +37 -0
- package/dist/trim-trailing-slashes.d.ts +8 -0
- package/dist/trim-trailing-slashes.js +14 -0
- package/docs/0.1.0-readiness.md +8 -8
- package/docs/acp.md +5 -3
- package/docs/ag-ui.md +6 -2
- package/docs/agent-events.md +8 -1
- package/docs/agent-loops.md +3 -0
- package/docs/agent-session-runtime.md +1 -0
- package/docs/antigravity-agent.md +207 -0
- package/docs/browser-automation.md +1 -0
- package/docs/coding-agent-tools.md +32 -4
- package/docs/computer-use-linux.md +122 -0
- package/docs/database-persistence.md +1 -1
- package/docs/device-adapters.md +4 -3
- package/docs/graft.md +125 -0
- package/docs/host-security.md +3 -1
- package/docs/index.md +21 -10
- package/docs/input-and-prompt-assembly.md +11 -6
- package/docs/instruction-injection.md +1 -1
- package/docs/mcp-tools.md +2 -1
- package/docs/migration.md +25 -2
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/obscura.md +175 -0
- package/docs/observability.md +21 -1
- package/docs/performance.md +58 -4
- package/docs/ponytail.md +1 -1
- package/docs/provider-caching.md +13 -11
- package/docs/provider-conformance.md +6 -0
- package/docs/provider-packages.md +1 -1
- package/docs/provider-primitives.md +15 -2
- package/docs/providers/ai-sdk.md +1 -1
- package/docs/providers/anthropic.md +1 -1
- package/docs/providers/azure.md +1 -0
- package/docs/providers/bedrock.md +1 -0
- package/docs/providers/kimi.md +2 -1
- package/docs/providers/openai.md +19 -7
- package/docs/providers/opencode-go.md +3 -1
- package/docs/providers/openrouter.md +4 -3
- package/docs/providers/vertex.md +1 -0
- package/docs/public-contracts.md +2 -1
- package/docs/rag.md +55 -8
- package/docs/release-and-install.md +105 -25
- package/docs/server.md +1 -0
- package/docs/supervisors.md +3 -2
- package/docs/system-prompts.md +1 -1
- package/docs/tools.md +1 -1
- package/docs/web-tools.md +2 -0
- package/docs/wiki.md +140 -0
- package/docs/workflows.md +4 -3
- package/docs/working-and-semantic-memory.md +20 -0
- package/package.json +14 -5
- package/docs/api-page-template.md +0 -32
- package/docs/release-0.2.7-evidence.md +0 -514
package/dist/input.js
CHANGED
|
@@ -41,16 +41,22 @@ export function createDefaultPromptBuilder() {
|
|
|
41
41
|
// Tool-capable models receive schemas via request.tools; the text list only serves
|
|
42
42
|
// text-only (or unknown-capability) models — duplicating it doubles tool tokens per turn.
|
|
43
43
|
const tools = request.model?.capabilities?.tools === true ? undefined : request.tools;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...request.messages
|
|
53
|
-
|
|
44
|
+
const context = contextMessages(request.context);
|
|
45
|
+
const skills = buildSkillMessages(request.skills, {
|
|
46
|
+
disclosure: request.skillsDisclosure,
|
|
47
|
+
loaded: request.loadedSkills,
|
|
48
|
+
demotedBodies: request.demotedSkillBodies?.length ? new Set(request.demotedSkillBodies) : undefined,
|
|
49
|
+
});
|
|
50
|
+
const declarations = toolMessages(tools);
|
|
51
|
+
if ((request.inputLayout ?? "cache_aware") === "legacy") {
|
|
52
|
+
return [...context, ...skills, ...declarations, ...request.messages];
|
|
53
|
+
}
|
|
54
|
+
// Default input assembly keeps stable instruction messages at the front. Keep that
|
|
55
|
+
// boundary ahead of dynamic context/skills; legacy retains the old whole-prompt order.
|
|
56
|
+
const firstNonSystem = request.messages.findIndex((message) => message.role !== "system");
|
|
57
|
+
const stable = firstNonSystem < 0 ? request.messages : request.messages.slice(0, firstNonSystem);
|
|
58
|
+
const dynamic = firstNonSystem < 0 ? [] : request.messages.slice(firstNonSystem);
|
|
59
|
+
return [...stable, ...context, ...skills, ...declarations, ...dynamic];
|
|
54
60
|
},
|
|
55
61
|
};
|
|
56
62
|
}
|
|
@@ -165,6 +171,7 @@ export async function assembleProviderInput(options) {
|
|
|
165
171
|
const promptRequest = options.middleware
|
|
166
172
|
? await options.middleware.run("prompt_build", {
|
|
167
173
|
messages,
|
|
174
|
+
inputLayout: layout,
|
|
168
175
|
context,
|
|
169
176
|
skills,
|
|
170
177
|
skillsDisclosure: options.skillsDisclosure,
|
|
@@ -176,6 +183,7 @@ export async function assembleProviderInput(options) {
|
|
|
176
183
|
})
|
|
177
184
|
: {
|
|
178
185
|
messages,
|
|
186
|
+
inputLayout: layout,
|
|
179
187
|
context,
|
|
180
188
|
skills,
|
|
181
189
|
skillsDisclosure: options.skillsDisclosure,
|
|
@@ -185,7 +193,7 @@ export async function assembleProviderInput(options) {
|
|
|
185
193
|
metadata: options.metadata,
|
|
186
194
|
signal: options.signal,
|
|
187
195
|
};
|
|
188
|
-
const providerMessages = await promptBuilder.build({ ...promptRequest, tools, model: options.model });
|
|
196
|
+
const providerMessages = await promptBuilder.build({ ...promptRequest, inputLayout: layout, tools, model: options.model });
|
|
189
197
|
assertMessagesSupportModelCapabilities(options.model, providerMessages);
|
|
190
198
|
const metadata = budgetReport ? { ...options.metadata, [CONTEXT_BUDGET_REPORT_METADATA_KEY]: budgetReport } : options.metadata;
|
|
191
199
|
return {
|
|
@@ -14,7 +14,9 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
14
14
|
const idempotencySeen = new Set();
|
|
15
15
|
return {
|
|
16
16
|
append(entry, appendOptions) {
|
|
17
|
-
|
|
17
|
+
const operation = appendChain
|
|
18
|
+
.catch(() => undefined)
|
|
19
|
+
.then(async () => {
|
|
18
20
|
if (options.createDirectory !== false)
|
|
19
21
|
await mkdir(dirname(path), { recursive: true });
|
|
20
22
|
const readResult = await readJsonlSessionEntries(path);
|
|
@@ -29,7 +31,8 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
29
31
|
if (dedupKey !== undefined && idempotencySeen.has(dedupKey)) {
|
|
30
32
|
throw new SessionAppendConflictError({ code: SESSION_APPEND_CONFLICT_CODE, idempotencyDuplicate: true });
|
|
31
33
|
}
|
|
32
|
-
if (appendOptions?.expectedParentId !== undefined &&
|
|
34
|
+
if (appendOptions?.expectedParentId !== undefined &&
|
|
35
|
+
!entries.some((existing) => existing.id === appendOptions.expectedParentId)) {
|
|
33
36
|
throw new SessionAppendConflictError({ code: SESSION_APPEND_CONFLICT_CODE, expectedParentId: appendOptions.expectedParentId });
|
|
34
37
|
}
|
|
35
38
|
if (entries.some((existing) => existing.id === entry.id))
|
|
@@ -38,7 +41,8 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
38
41
|
idempotencySeen.add(dedupKey);
|
|
39
42
|
await appendFile(path, `${JSON.stringify(entry)}\n`, "utf8");
|
|
40
43
|
});
|
|
41
|
-
|
|
44
|
+
appendChain = operation.catch(() => undefined);
|
|
45
|
+
return operation;
|
|
42
46
|
},
|
|
43
47
|
async list(sessionId) {
|
|
44
48
|
return (await readEntries(path)).filter((entry) => entry.sessionId === sessionId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveCredentialValue } from "../credentials.js";
|
|
2
2
|
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallFromArgumentsText, } from "../provider-events.js";
|
|
3
3
|
import { assertStructuredOutputRequestSupported } from "../structured-output.js";
|
|
4
|
+
import { trimTrailingSlashes } from "../trim-trailing-slashes.js";
|
|
4
5
|
import { applyOpenAIChatStructuredOutput, assertOpenAIChatMessage, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
5
6
|
import { httpStatusError, ProviderTransportError, readBoundedResponseText, readSseEvents } from "./transport.js";
|
|
6
7
|
/**
|
|
@@ -100,7 +101,7 @@ export function createOpenAICompatibleProvider(options) {
|
|
|
100
101
|
try {
|
|
101
102
|
const url = typeof options.chatCompletionsUrl === "function"
|
|
102
103
|
? options.chatCompletionsUrl(request)
|
|
103
|
-
: (options.chatCompletionsUrl ?? `${options.baseUrl
|
|
104
|
+
: (options.chatCompletionsUrl ?? `${trimTrailingSlashes(options.baseUrl)}/chat/completions`);
|
|
104
105
|
const authStyle = options.authStyle ?? "bearer";
|
|
105
106
|
const headers = {
|
|
106
107
|
...Object.fromEntries(Object.entries(request.options?.headers ?? {}).filter((entry) => typeof entry[1] === "string")),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalizeJsonSchema } from "./schema.js";
|
|
1
2
|
export function assertOpenAIChatMessage(message, path) {
|
|
2
3
|
if (!message || typeof message !== "object") {
|
|
3
4
|
throw new Error(`Invalid provider message at ${path}: expected object`);
|
|
@@ -46,7 +47,7 @@ export function serializeOpenAITool(tool) {
|
|
|
46
47
|
function: {
|
|
47
48
|
name: tool.name,
|
|
48
49
|
description: tool.description,
|
|
49
|
-
parameters: tool.parameters ?? { type: "object" },
|
|
50
|
+
parameters: canonicalizeJsonSchema(tool.parameters ?? { type: "object" }),
|
|
50
51
|
},
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON Schema clone for tool/function parameters.
|
|
3
|
+
* Sorts object keys and unordered `required` names. Leaves semantic arrays
|
|
4
|
+
* (`prefixItems`, `examples`, `enum`, tuple `items`) in caller order.
|
|
5
|
+
* Does not resolve `$ref`, mutate input, or enforce schema bounds.
|
|
6
|
+
*/
|
|
7
|
+
export declare function canonicalizeJsonSchema(value: unknown): unknown;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON Schema clone for tool/function parameters.
|
|
3
|
+
* Sorts object keys and unordered `required` names. Leaves semantic arrays
|
|
4
|
+
* (`prefixItems`, `examples`, `enum`, tuple `items`) in caller order.
|
|
5
|
+
* Does not resolve `$ref`, mutate input, or enforce schema bounds.
|
|
6
|
+
*/
|
|
7
|
+
export function canonicalizeJsonSchema(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map(canonicalizeJsonSchema);
|
|
10
|
+
if (!value || typeof value !== "object")
|
|
11
|
+
return value;
|
|
12
|
+
return Object.fromEntries(Object.entries(value)
|
|
13
|
+
.sort(([left], [right]) => compareKey(left, right))
|
|
14
|
+
.map(([key, item]) => [key, key === "required" ? canonicalizeRequired(item) : canonicalizeJsonSchema(item)]));
|
|
15
|
+
}
|
|
16
|
+
function canonicalizeRequired(item) {
|
|
17
|
+
if (Array.isArray(item) && item.every((entry) => typeof entry === "string")) {
|
|
18
|
+
return [...item].sort(compareKey);
|
|
19
|
+
}
|
|
20
|
+
return canonicalizeJsonSchema(item);
|
|
21
|
+
}
|
|
22
|
+
function compareKey(left, right) {
|
|
23
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -38,6 +38,16 @@ export declare function assertProviderStreamConforms(options: ProviderStreamConf
|
|
|
38
38
|
export declare function assertAbortIsObserved(options: ProviderAbortConformanceOptions): Promise<void>;
|
|
39
39
|
export declare function assertToolCallDeltasReconstruct(events: readonly ProviderEvent[], expected: readonly ToolCallDeltaExpectation[]): readonly ToolCallContent[];
|
|
40
40
|
export declare function assertSerializedRequestCoversContent(request: ProviderRequest, body: unknown, options?: SerializedContentCoverageOptions): void;
|
|
41
|
+
export declare function assertCanonicalToolParameters(serialized: unknown, original: unknown): void;
|
|
41
42
|
export declare function assertProviderOwnedHeadersWin(captured: Headers, options: ProviderHeaderOwnershipConformanceOptions): void;
|
|
42
43
|
export declare function assertNoSecretLeak(events: readonly ProviderEvent[], secrets: readonly string[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Implicit/none-cache providers must serialize no foreign cache fields: implicit
|
|
46
|
+
* caching works by byte-stable prefix reuse, not request payloads. `allowed` names
|
|
47
|
+
* fields the provider documents for that route (e.g. `cachedContent` via the host
|
|
48
|
+
* `extra.cachedContent` escape hatch on Gemini).
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertNoForeignCacheFields(body: unknown, allowed?: readonly string[]): void;
|
|
51
|
+
/** Provider construction and setup must perform zero network calls; discovery and streams are caller-gated. */
|
|
52
|
+
export declare function assertNoFetches(calls: readonly unknown[]): void;
|
|
43
53
|
export declare function assertUsageAccounting(events: readonly ProviderEvent[], expected: Usage): Usage;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reconstructToolCallDeltas } from "../provider-events.js";
|
|
2
|
+
import { canonicalizeJsonSchema } from "../providers/schema.js";
|
|
2
3
|
export async function collectProviderEvents(provider, request) {
|
|
3
4
|
const events = [];
|
|
4
5
|
for await (const event of provider.generate(request))
|
|
@@ -63,6 +64,12 @@ export function assertSerializedRequestCoversContent(request, body, options = {}
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
export function assertCanonicalToolParameters(serialized, original) {
|
|
68
|
+
const expected = canonicalizeJsonSchema(original ?? { type: "object" });
|
|
69
|
+
if (JSON.stringify(serialized) !== JSON.stringify(expected)) {
|
|
70
|
+
throw new Error("Tool parameters were not canonicalized");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
66
73
|
export function assertProviderOwnedHeadersWin(captured, options) {
|
|
67
74
|
const ownedLower = {};
|
|
68
75
|
for (const [name, expected] of Object.entries(options.owned))
|
|
@@ -89,6 +96,36 @@ export function assertNoSecretLeak(events, secrets) {
|
|
|
89
96
|
throw new Error(`Secret leaked into provider events: ${secret.slice(0, 8)}...`);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
99
|
+
/** Known cache wire fields across protocols; any of these in a request body is an explicit cache control. */
|
|
100
|
+
const CACHE_WIRE_FIELDS = [
|
|
101
|
+
"cache_control",
|
|
102
|
+
"prompt_cache_key",
|
|
103
|
+
"prompt_cache_retention",
|
|
104
|
+
"prompt_cache_options",
|
|
105
|
+
"prompt_cache_breakpoint",
|
|
106
|
+
"cachedContent",
|
|
107
|
+
"cachePoint",
|
|
108
|
+
];
|
|
109
|
+
/**
|
|
110
|
+
* Implicit/none-cache providers must serialize no foreign cache fields: implicit
|
|
111
|
+
* caching works by byte-stable prefix reuse, not request payloads. `allowed` names
|
|
112
|
+
* fields the provider documents for that route (e.g. `cachedContent` via the host
|
|
113
|
+
* `extra.cachedContent` escape hatch on Gemini).
|
|
114
|
+
*/
|
|
115
|
+
export function assertNoForeignCacheFields(body, allowed = []) {
|
|
116
|
+
const bodyText = JSON.stringify(body);
|
|
117
|
+
for (const field of CACHE_WIRE_FIELDS) {
|
|
118
|
+
if (allowed.includes(field))
|
|
119
|
+
continue;
|
|
120
|
+
if (bodyText.includes(field))
|
|
121
|
+
throw new Error(`Serialized request carries foreign cache field "${field}"`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Provider construction and setup must perform zero network calls; discovery and streams are caller-gated. */
|
|
125
|
+
export function assertNoFetches(calls) {
|
|
126
|
+
if (calls.length > 0)
|
|
127
|
+
throw new Error(`Provider fetched ${calls.length} time(s) outside caller-gated discovery/stream`);
|
|
128
|
+
}
|
|
92
129
|
export function assertUsageAccounting(events, expected) {
|
|
93
130
|
const usage = [...events].reverse().find((event) => (event.type === "done" && event.usage) || event.type === "usage");
|
|
94
131
|
const actual = usage?.type === "usage" ? usage.usage : usage?.usage;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trims trailing "/" characters with a linear index scan.
|
|
3
|
+
*
|
|
4
|
+
* Shared replacement for `value.replace(/\/+$/, "")` (CodeQL js/polynomial-redos):
|
|
5
|
+
* no regex is evaluated, so hostile long inputs cannot backtrack. Semantics are
|
|
6
|
+
* identical — only trailing "/" characters (U+002F) are removed; "" and "/" stay "".
|
|
7
|
+
*/
|
|
8
|
+
export declare function trimTrailingSlashes(value: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trims trailing "/" characters with a linear index scan.
|
|
3
|
+
*
|
|
4
|
+
* Shared replacement for `value.replace(/\/+$/, "")` (CodeQL js/polynomial-redos):
|
|
5
|
+
* no regex is evaluated, so hostile long inputs cannot backtrack. Semantics are
|
|
6
|
+
* identical — only trailing "/" characters (U+002F) are removed; "" and "/" stay "".
|
|
7
|
+
*/
|
|
8
|
+
export function trimTrailingSlashes(value) {
|
|
9
|
+
let end = value.length;
|
|
10
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47)
|
|
11
|
+
end -= 1;
|
|
12
|
+
return value.slice(0, end);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=trim-trailing-slashes.js.map
|
package/docs/0.1.0-readiness.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0.1.0 / 1.0 Readiness Gates
|
|
2
2
|
|
|
3
|
-
Status: **0.
|
|
3
|
+
Status: **0.3.0** is the current release line (the final 0.3.x lockstep cut: Linux desktop wrapper, coding/ACP closeouts, and independent package versions with Decision B `^0.3.0` ranges); **0.1.7** was the terminal 0.1.x baseline; **1.0** readiness remains operator-gated, not automatic.
|
|
4
4
|
|
|
5
5
|
This page distills runnable readiness gates into one command-per-gate table.
|
|
6
6
|
The **Last evidence** column records the 0.1.0-tree snapshot (plan 012 Tasks
|
|
@@ -20,16 +20,16 @@ Historical release lines (0.0.16 floor → 0.0.27 Phase 10 ACP interop → 0.1.0
|
|
|
20
20
|
keep their per-phase evidence in the pages above; this page records the 0.2.6
|
|
21
21
|
snapshot (plan 026) with the 0.1.x tables below as the historical record.
|
|
22
22
|
|
|
23
|
-
## Current line (0.
|
|
23
|
+
## Current line (0.3.1)
|
|
24
24
|
|
|
25
25
|
| Item | Status |
|
|
26
26
|
|---|---|
|
|
27
|
-
|
|
|
28
|
-
| Current-line cut | The 0.
|
|
29
|
-
| Upgrade path | `docs/migration.md` `0.2.
|
|
30
|
-
| Compat promise | Additive-only vs the frozen 0.
|
|
31
|
-
| Security policy | `npm audit --audit-level=moderate` 0 at 0.
|
|
32
|
-
| Docs freeze | tripwires green including the
|
|
27
|
+
| Release graph | **56** publishable manifests at exact **0.3.0** (root + 55 workspace packages: 17 provider adapters + 10 `prism-*` family/profile + 28 capability; generated by `node scripts/package-truth.mjs` → `scripts/package-truth.json`; npm publication remains the operator handoff) |
|
|
28
|
+
| Current-line cut | The 0.3.x line starts with the final lockstep cut: optional host-owned Linux desktop control, coding/ACP tool closeouts, and independent package publication after the cut; live canaries and delegated agents remain later demand-gated work |
|
|
29
|
+
| Upgrade path | `docs/migration.md` `0.2.9 → 0.3.0` (additive; no store migration; rollback = restore 0.2.9 manifests/tag); internal first-party ranges become `^0.3.0` |
|
|
30
|
+
| Compat promise | Additive-only vs the frozen 0.2.9 baseline; package peers use Decision B's `^0.3.0` caret window and `scripts/compat-baseline` remains the reviewed declaration gate |
|
|
31
|
+
| Security policy | `npm audit --audit-level=moderate` 0 target at 0.3.0; desktop admission remains deny-by-default, mutators require approval + `ExecutionPolicy`, and package tags publish only changed packages |
|
|
32
|
+
| Docs freeze | tripwires green including the 56-package truth graph, Decision B range policy, the plan 030 freeze/release tests, coding/ACP tool regressions, and the host-owned desktop wrapper's fail-closed package/docs gates |
|
|
33
33
|
| 0.1.x line | **0.1.7** (plan 019) is the terminal 0.1.x baseline; the 0.1.1 table below keeps the plan 013 snapshot; the 0.1.0 table keeps the plan 012 snapshot; the **0.0.16** values remain the historical network-free floor |
|
|
34
34
|
|
|
35
35
|
## Previous line (0.1.1)
|
package/docs/acp.md
CHANGED
|
@@ -46,7 +46,7 @@ In-stream `SessionUpdate`s:
|
|
|
46
46
|
| Assistant text | `agent_message_chunk` |
|
|
47
47
|
| Assistant thinking | `agent_thought_chunk` (same `messageId` scheme as text; through the shared redactor and byte caps) |
|
|
48
48
|
| Tool lifecycle | `tool_call` / `tool_call_update` (title/status/content) — `tool_call.kind` comes from the session's tool registry `kind` metadata when present (B4), else the name heuristic |
|
|
49
|
-
| Tool result, projected | `tool_call_update` with `locations` (≤ `acpLocationsPerUpdate`) and/or a `diff` block (≤ `acpDiffBytes`) — only from `AgUiProjection.toolLocations`/`toolDiff` allow-lists, at `finish()`. `toolResult` may return a string (text content) or `{ type: "image", data, mimeType }` (F8) — the mapper wraps the image as `{ type: "content", content: { type: "image", data, mimeType } }` and drops payloads over `acpImageBytes` (never truncated). Opt-in turnkey: `createCodingToolProjection()` (F7) recognizes first-party `edit` (path + unified patch as `newText`, `firstChangedLine` location) and `
|
|
49
|
+
| Tool result, projected | `tool_call_update` with `locations` (≤ `acpLocationsPerUpdate`) and/or a `diff` block (≤ `acpDiffBytes`) — only from `AgUiProjection.toolLocations`/`toolDiff` allow-lists, at `finish()`. `toolResult` may return a string (text content) or `{ type: "image", data, mimeType }` (F8) — the mapper wraps the image as `{ type: "content", content: { type: "image", data, mimeType } }` and drops payloads over `acpImageBytes` (never truncated). Opt-in turnkey: `createCodingToolProjection()` (F7) recognizes first-party `edit` (path + unified patch as `newText`, `firstChangedLine` location), `write` and `delete` (metadata path locations), and `move` (destination `metadata.to` location) results; moves never emit diffs, and default remains deny-by-default. |
|
|
50
50
|
| Provider usage | `usage_update` (only when the `capabilities.usage.contextWindow` seam reports a valid window — absent/undefined/throw ⇒ the update is omitted, never `size = used`) |
|
|
51
51
|
| Run-level failure | No transcript chunk — the `session/prompt` request rejects with `ERR_PRISM_ACP_RUN` (redacted, byte-capped message). Retryable provider-turn failures stay silent and may recover; only a terminal `error` event fails the request. |
|
|
52
52
|
| Run stop reason | `session/prompt` returns the SDK `StopReason` (F4): `cancelled` when the run was aborted, `max_turn_requests` for the tool-round ceiling (`finishReason: "turn_limit"`), `max_tokens` for `"token_limit"`, `refusal` for `"refusal"`, else `end_turn`. The generic `finishReason` field is set on `agent_finished` by loop strategies (single-shot records `turn_limit` at the `maxToolRounds` ceiling); `token_limit`/`refusal` have no core producer yet — the mapping is ready. |
|
|
@@ -112,7 +112,8 @@ const agent = createPrismAcpAgent({
|
|
|
112
112
|
|
|
113
113
|
- **Seam = capability.** Wiring `sessions.load` advertises `loadSession`; removing it withdraws the method. There is no separate capability flag to keep in sync — the freeze manifest's advertise-when matrix is enforced by construction and asserted by `scripts/phase10-conformance.test.mjs`.
|
|
114
114
|
- **Transcript replay (F2).** When `sessions.transcript` is wired, `session/load` and `session/resume` replay `user_message_chunk`/`agent_message_chunk` text chunks (from `SessionEntry`s with `kind: "message"` and a user/assistant role, text blocks only) before returning `sessionState`. Each chunk passes the shared redactor and is truncated at `maxTextBytes`; replay stops at `maxReplayEvents` chunks and counts against the stream event/byte caps (an oversized transcript fails the load/resume request closed). Absent seam = no replay, behavior unchanged.
|
|
115
|
-
- **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. Host repo operations remain default when the client fs is absent.
|
|
115
|
+
- **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. `createAcpFilesystemOperations` from `@arnilo/prism-coding-agent` maps that filesystem seam onto the coding tools' `read`/`write`/`edit` operations. This editor-buffer mode is intentionally hybrid: `repo_list`, `repo_search`, `glob`, `delete`, and `move` remain disk-backed unless the host supplies separate operations; binary/image/document handling never falls back to local disk. Host repo operations remain default when the client fs is absent.
|
|
116
|
+
- **Spawnable ACP coding registry (Task 6).** `@arnilo/prism-acp-agent` wires `createAcpClientFilesystem` and creates a separate coding tool registry per ACP session when the client advertises `fs/read_text_file` or `fs/write_text_file`. That session's `read`/`write`/`edit` operations use editor buffers; without fs advertisement, the existing disk registry is used. `shell`, repository search/list/glob, `delete`, and `move` remain disk-backed in this hybrid mode. Durable approvals resolve the same per-session agent, so one session cannot resume through another session's buffer adapter.
|
|
116
117
|
- **Modes and config options are a pure host overlay.** The agent stores only a thin per-session registry; `apply`/`onChange` hooks narrow the host's own behavior. Mode switches can narrow or host-authorized widen — never a parallel policy evaluator, never a client-enabled tool.
|
|
117
118
|
- **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
|
|
118
119
|
- **Stream budgets.** Every lifecycle update counts against the same per-run stream event/byte budget as prompt updates; overflowing closes the update, never the run.
|
|
@@ -151,7 +152,7 @@ const agent = createPrismAcpAgent({
|
|
|
151
152
|
- **Deny-closed by default.** Unknown mode ids, unadvertised methods, unprojected lifecycle events, oversize diffs/locations/media, thrown projection hooks, and failed elicitation all fail closed. Raw tool arguments/results are never sent unless a projection allow-list says otherwise.
|
|
152
153
|
- **Slash commands (F9).** `commands.list` is a host-owned slash-command list (not derived from the tool registry). The agent emits `available_commands_update` on session start (`session/new`, `session/load`, `session/resume`). Mid-session refresh is not in this release — re-list by starting a session. Names, descriptions, and input hints pass the shared redactor; the list is sliced at `acpCommandsPerUpdate`. Absent seam or a thrown list ⇒ no update.
|
|
153
154
|
- **Projected images (F8).** `AgUiProjection.toolResult` may return `{ type: "image", data, mimeType }` (return-type widening — existing string returns stay valid). The mapper emits `{ type: "content", content: { type: "image", data, mimeType } }` (SDK v1 `ToolCallContent` has no top-level image variant). `data` is the host-supplied base64; it is not redacted and not truncated — payloads over `acpImageBytes` are dropped. Default (no hook / non-image return) emits no image.
|
|
154
|
-
- **Coding-tool projection (F7).** `createCodingToolProjection({ maxDiffBytes? })` is an opt-in `AgUiProjection` for first-party `@arnilo/prism-coding-agent`
|
|
155
|
+
- **Coding-tool projection (F7).** `createCodingToolProjection({ maxDiffBytes? })` is an opt-in `AgUiProjection` for first-party `@arnilo/prism-coding-agent` results: `edit` → `toolDiff` (`path` + unified `patch` as `newText`) and `toolLocations` (`path` + `firstChangedLine`); `write` and `delete` → `toolLocations` (`path` only); `move` → destination `toolLocations` (`metadata.to`, with `from` fallback). No delete/move diff is fabricated. Pass as `projection: createCodingToolProjection()` on the agent/mapper. Mapper still redacts and enforces `acpDiffBytes` / `acpLocationsPerUpdate`; optional `maxDiffBytes` pre-truncates the patch so a slightly-oversize edit is shortened instead of dropped. Without the factory, behavior is unchanged (deny-by-default).
|
|
155
156
|
- **No secrets.** Updates carry no raw file bodies, terminal output is capped by the Phase 9 chunk budget, and the shared redactor is applied before anything leaves the host. `permission_denied` never includes raw args.
|
|
156
157
|
- **Performance.** The adapter is O(1) per update with no unbounded buffering; p95 targets (fs round trip 250 ms, mode switch 250 ms, terminal chunk ack 1000 ms, prompt first update 2000 ms, prompt end 30 s) are recorded by `scripts/benchmark-0.0.27.mjs` and gated in `scripts/budgets.json` `phase10`.
|
|
157
158
|
|
|
@@ -163,3 +164,4 @@ const agent = createPrismAcpAgent({
|
|
|
163
164
|
- [Host security guide](host-security.md): fail-closed checklist rows for ACP boundaries (authorize, ownership, redaction, untrusted MCP).
|
|
164
165
|
- [Migration guide](migration.md): 0.0.26 → 0.0.27 advertise/surface changes for hosts that parsed the old `initialize`.
|
|
165
166
|
- [AG-UI adoption evaluation](ag-ui-adoption.md): the underlying input/event/capability matrix.
|
|
167
|
+
- [Obscura browser engine](obscura.md): optional binary-backed generic tools behind the session prompt loop.
|
package/docs/ag-ui.md
CHANGED
|
@@ -44,7 +44,9 @@ The handler accepts only `POST` JSON validated with official AG-UI `RunAgentInpu
|
|
|
44
44
|
|
|
45
45
|
## Outputs / response / events
|
|
46
46
|
|
|
47
|
-
The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: `RUN_*`, `STEP_*`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*` are deterministic Prism mappings. Host projectors may additionally prove and emit `STATE_SNAPSHOT`/`STATE_DELTA`, `MESSAGES_SNAPSHOT`, `ACTIVITY_*`, current `REASONING_*`, `RAW`, and named `CUSTOM` values.
|
|
47
|
+
The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: `RUN_*`, `STEP_*`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*` are deterministic Prism mappings. Host projectors may additionally prove and emit `STATE_SNAPSHOT`/`STATE_DELTA`, `MESSAGES_SNAPSHOT`, `ACTIVITY_*`, current `REASONING_*`, `RAW`, and named `CUSTOM` values.
|
|
48
|
+
|
|
49
|
+
`delegated_agent_step` maps by default to bounded `ACTIVITY_SNAPSHOT` metadata with activity type `prism.delegated_agent_step`; `includeCustomEvents: true` also emits `CUSTOM prism.delegated_agent_step`. The safe payload contains adapter/conversation identifiers, step index/state/kind, duration, token counts, tool/subagent names, and opaque detail references only. Normal assistant text remains `TEXT_MESSAGE_*`; delegated events never duplicate transcript text. Raw event bodies, tool arguments/results, paths, URIs, logs, and hidden thought text remain absent unless a host explicitly supplies a projection. All values revalidate against official `EventSchemas`; deprecated `THINKING_*` and convenience chunk events are not produced. Active message/tool/reasoning/step sequences close before error, interruption, or finish.
|
|
48
50
|
|
|
49
51
|
A Prism durable `agent_suspended` returns `RUN_FINISHED` with core interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. `projection.interrupt` may attach bounded expiry/metadata or additional host policy interrupts but must retain that core id. Without `interrupts.resume`, one exact entry is required; `cancelled` means deny. An aggregate policy may validate bounded multiple entries, then returns one current-version core decision. Payloads containing `editedArgs`/`args` always deny: Prism does not mutate persisted tool calls. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
|
|
50
52
|
|
|
@@ -122,7 +124,7 @@ See runnable network-free [`examples/ag-ui-server.ts`](../examples/ag-ui-server.
|
|
|
122
124
|
|
|
123
125
|
All identity, authorization, session/thread mapping, durable checkpoint lookup, persistence selection, replay cursor persistence, transport adaptation, MCP bridge/card configuration, app sandbox DOM, remote A2A task correlation, and optional projection are host-owned. The adapter owns no listener, database, background reconnect loop, credential resolver, or UI state.
|
|
124
126
|
|
|
125
|
-
`AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, arbitrary state/patches/transcripts/activity/reasoning/raw events, paths, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Reasoning signatures do not become AG-UI encrypted values automatically: a host must explicitly provide an already client-encrypted opaque value. `input.project` is also an allow-list: do not merge client state/forwarded props into ownership, identity, tools, permissions, provider options, or media fetch policy.
|
|
127
|
+
`AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, arbitrary state/patches/transcripts/activity/reasoning/raw events, delegated raw event bodies, paths, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Reasoning signatures do not become AG-UI encrypted values automatically: a host must explicitly provide an already client-encrypted opaque value. `input.project` is also an allow-list: do not merge client state/forwarded props into ownership, identity, tools, permissions, provider options, or media fetch policy.
|
|
126
128
|
|
|
127
129
|
### Reasoning encrypted-value helper (FR-3)
|
|
128
130
|
|
|
@@ -219,7 +221,9 @@ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages, 32 / 25
|
|
|
219
221
|
- [A2A interoperability](a2a.md): remote agent-to-agent tasks, not frontend protocol mapping.
|
|
220
222
|
- [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 event/input matrix and shipped explicit MCP/MCP Apps/A2A handshakes.
|
|
221
223
|
- [ACP coding-host interop](acp.md): the full ACP reference — seam-based capability advertisement, session modes/config, MCP select, fs/terminal adapters, lifecycle mapping, elicitation, and caps.
|
|
224
|
+
- [Obscura browser engine](obscura.md): optional binary-backed generic tools selectable through the MCP adapter.
|
|
222
225
|
- [MCP bridge/server](mcp-tools.md): `mcpApps` negotiation, bounded resources, and remote tool trust.
|
|
223
226
|
- [A2A interoperability](a2a.md): verified rich task client and remote task lifecycle.
|
|
224
227
|
- [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
|
|
228
|
+
- [Antigravity delegated agent](antigravity-agent.md): delegated Antigravity CLI execution with timeline step projection.
|
|
225
229
|
- [Work artifacts and review](work-artifacts-and-review.md): durable artifact service that produces the co-work approval/progress/download-link events projected here.
|
package/docs/agent-events.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
`AgentEvent` is the single observable stream every `AgentSession` run emits. Subscribers receive normalized, redacted, in-order events covering agent lifecycle, assistant message streaming, tool execution, queue updates, subscriber overflow, compaction, retry, artifact validation/refinement, and terminal errors. The stream is in-memory, live-only, and bounded per subscriber by `SubscribeOptions`; there is no durable queue, no background work, and no extra dependency.
|
|
5
|
+
`AgentEvent` is the single observable stream every `AgentSession` run emits. Subscribers receive normalized, redacted, in-order events covering agent lifecycle, assistant message streaming, delegated-agent activity, tool execution, queue updates, subscriber overflow, compaction, retry, artifact validation/refinement, and terminal errors. The stream is in-memory, live-only, and bounded per subscriber by `SubscribeOptions`; there is no durable queue, no background work, and no extra dependency.
|
|
6
6
|
|
|
7
7
|
Events are emitted by the runtime and by loops through `LoopContext.emit`, both of which route through `redactAgentEvent(event, activeRedactor)` so every payload is secret-redacted before subscribers observe it.
|
|
8
8
|
|
|
@@ -56,6 +56,7 @@ const subscription = session.subscribe({ maxQueuedEvents: 256, overflow: "close"
|
|
|
56
56
|
for await (const event of subscription) {
|
|
57
57
|
switch (event.type) {
|
|
58
58
|
case "message_delta": // append event.content
|
|
59
|
+
case "delegated_agent_step": // render bounded external activity
|
|
59
60
|
case "tool_execution_started": // …
|
|
60
61
|
case "artifact_failed": // budget exhausted
|
|
61
62
|
break;
|
|
@@ -71,6 +72,7 @@ The `AgentEvent` union (grouped by concern):
|
|
|
71
72
|
| Turns | `turn_started`, `turn_finished` |
|
|
72
73
|
| Provider turns | `provider_turn_started`, `provider_turn_finished` |
|
|
73
74
|
| Assistant messages | `message_started`, `message_delta`, `message_finished` |
|
|
75
|
+
| Delegated agents | `delegated_agent_step` |
|
|
74
76
|
| Tool execution | `tool_execution_started`, `tool_execution_progress`, `tool_execution_finished`, `tool_execution_error`, `tool_execution_blocked` |
|
|
75
77
|
| Guardrails | `guardrail_decision` |
|
|
76
78
|
| Queue/subscribers | `queue_updated`, `event_subscriber_overflow`, `steer_rejected` |
|
|
@@ -95,6 +97,11 @@ Agent / turn / message events:
|
|
|
95
97
|
| `turn_started` / `turn_finished` | `sessionId`, `runId`, `turn: number` |
|
|
96
98
|
| `message_started` / `message_finished` | `sessionId`, `runId`, `message: Message` |
|
|
97
99
|
| `message_delta` | `sessionId`, `runId`, `content: ContentBlock` (`tool_call_delta` fragments may appear here for live UI streaming; stored messages use final `tool_call` blocks) |
|
|
100
|
+
| `delegated_agent_step` | `sessionId`, `runId`, `adapterId`, `externalConversationId` (≤512 UTF-8 bytes), `stepIndex`, `state`, `kind`, optional `durationMs`, token-only `usage`, `toolName`, `subagentType`, and opaque `detail` references |
|
|
101
|
+
|
|
102
|
+
`delegated_agent_step` is a safe timeline event for an adapter-owned loop. `kind` is one of `assistant`, `tool`, `subagent`, `checkpoint`, or `unknown`; unknown external step kinds normalize to `unknown`. It never carries raw arguments, results, paths, URIs, logs, event bodies, or hidden thought text. `thinkingTokens` is a count only. The constructor and existing event-source default cap keep serialized events at 64 KiB.
|
|
103
|
+
|
|
104
|
+
Adapters should call `createDelegatedAgentStep({ sessionId, runId, adapterId, externalConversationId, stepIndex, state, kind, usage })` rather than forwarding external JSON. The constructor allow-lists fields and fails closed on malformed or oversized identifiers/counters.
|
|
98
105
|
|
|
99
106
|
`message_delta.content.type === "tool_call_delta"` carries `{ index, id?, name?, argumentsText? }`. Treat it as a streaming fragment. The runtime reconstructs and persists a final `tool_call` before executing tools. Deltas missing `id`/`name` at stream end fail the provider turn with `ErrorInfo.code: "incomplete_delta"` (typed `ProviderTransportError`); they never throw a bare `Error`. Malformed JSON with id+name present recovers as a blocked tool result (`invalid_json_arguments`) instead.
|
|
100
107
|
|
package/docs/agent-loops.md
CHANGED
|
@@ -128,6 +128,8 @@ Optional steer hooks on `LoopContext` (0.0.11): `hasPendingSteers?()` / `applyPe
|
|
|
128
128
|
|
|
129
129
|
The snapshot is stored as `loopState: { name, revision, snapshot }` on the durable run state and cleared when the run reaches a terminal status. On resume, a name/revision mismatch between the stored `loopState` and the resolved strategy fails closed (`ERR_PRISM_LOOP_REVISION`), and the fingerprint check independently rejects any loop drift. Suspension occurs only before an input provider call or immediately before a tool side effect; completed provider turns remain in `SessionStore` history and are not repeated after `resumeAgentRun()`.
|
|
130
130
|
|
|
131
|
+
A strategy returned by `generateValidateReviseLoop()` is safe to reuse across sequential runs. Its built-in state is scoped to `(sessionId, runId)`; a new non-restored run resets attempts, artifact phase, saved schema, and pending repair messages, while a restored run keeps the checkpointed state. Arbitrary custom strategies are not cloned or reset automatically.
|
|
132
|
+
|
|
131
133
|
## Outputs / response / events
|
|
132
134
|
|
|
133
135
|
`AgentLoopStrategy.run(ctx)` returns `Promise<Usage | undefined>` as a fallback for custom loops. Core runtime independently accumulates every usage-bearing provider turn in O(turns), persists scoped turn/run rows, and emits `agent_finished` with the aggregate.
|
|
@@ -231,6 +233,7 @@ await session.run(input, { loop: twoShotLoop });
|
|
|
231
233
|
- `ArtifactValidation.errors[].message` may echo model text — `artifact_*` event payloads flow through the same `redactAgentEvent` path as other `AgentEvent`s (see [Agent events](agent-events.md)).
|
|
232
234
|
- `generateValidateReviseLoop` makes at most `1 + maxRevisions + maxToolRounds` provider turns when bounded tools are enabled (otherwise `maxRevisions + 1`); it cannot loop forever. Each revision costs one provider turn plus one store append.
|
|
233
235
|
- Bounded artifact tool calls run sequentially through `dispatchToolCall` (permission + validation + execute); their assistant call and result are persisted before the next provider request. `singleShotLoop` retains its bounded parallel worker pool and original call-order transcript behavior.
|
|
236
|
+
- In a parallel single-shot batch, the worker pool stops claiming calls after the first dispatch error or abort, waits for every already-claimed worker with `Promise.allSettled`, appends no buffered tool-result rows for a failed batch, then rethrows the first failure. Already-claimed side effects may finish and are not rolled back; successful batches still append results in original call order. The round-level `chargeToolRound` approval gate runs before workers, so approval suspension starts no worker.
|
|
234
237
|
- The loop is a plain object/factory; no class hierarchy, no background work, no extra dependencies. `LoopContext` is a single object literal of bound arrows built once per run.
|
|
235
238
|
- The host-domain-free boundary is guarded by tests: `src/` imports no host-domain package, and the `Artifact*`/`AgentLoop*`/`LoopContext` contracts contain no `workflow`/`node`/`step` field names. Hosts supply their own schema; no host domain type is imported by `src/`.
|
|
236
239
|
|
|
@@ -221,6 +221,7 @@ Per-run options may narrow `limits` and append `guardrails`; they cannot replace
|
|
|
221
221
|
- [Session stores and branching](session-stores-and-branching.md): `SessionStore`, memory store, branch helpers, and context rebuild.
|
|
222
222
|
- [Compaction and retry policies](compaction-and-retry.md): compaction strategy/config APIs used by `session.compact()` and auto-compaction, plus retry policy/config APIs.
|
|
223
223
|
- [Tools](tools.md): host-owned tool harness used by the bounded runtime tool loop.
|
|
224
|
+
- [Obscura browser engine](obscura.md): optional binary-backed tool array that composes into `createAgent({ tools })` with no host branch.
|
|
224
225
|
- [Middleware hooks](middleware-hooks.md): hooks that configured assembly/runtime can run.
|
|
225
226
|
- [CLI/RPC](cli-rpc.md): terminal and JSONL adapters over this runtime.
|
|
226
227
|
- [Workflows](workflows.md): optional DAG orchestration that calls `AgentSession.run()` for agent nodes.
|