@arnilo/prism 0.0.15 → 0.0.16
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 +11 -0
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-loops.js +12 -7
- package/dist/agent-run-lifecycle.d.ts +1 -2
- package/dist/agent-run-lifecycle.js +1 -1
- package/dist/agent-run-state.js +16 -3
- package/dist/agents.d.ts +1 -1
- package/dist/agents.js +121 -51
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.js +5 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +1 -1
- package/dist/cli-runner.js +74 -13
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.js +12 -8
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.js +4 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/conversations.js +2 -1
- package/dist/credentials.d.ts +1 -1
- package/dist/credentials.js +3 -1
- package/dist/event-multiplexer.js +1 -3
- package/dist/feedback.js +11 -9
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +17 -14
- package/dist/identity.js +10 -2
- package/dist/index.d.ts +82 -83
- package/dist/index.js +42 -42
- package/dist/input.d.ts +2 -2
- package/dist/input.js +40 -24
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +10 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.js +1 -3
- package/dist/provider-events.js +3 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.js +3 -4
- package/dist/providers/openai-primitives.js +7 -7
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +5 -2
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/rpc.js +42 -9
- package/dist/run-ledger.js +13 -4
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +1 -1
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +1 -1
- package/dist/session-stores.js +13 -13
- package/dist/structured-output.js +2 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.js +206 -37
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.js +1 -1
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/thinking.js +4 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +24 -5
- package/docs/0.1.0-readiness.md +139 -0
- package/docs/index.md +3 -1
- package/docs/migration.md +29 -0
- package/docs/performance.md +33 -0
- package/docs/public-contracts.md +1 -1
- package/docs/release-and-install.md +106 -17
- package/package.json +14 -6
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
- package/docs/review-coverage-2026-07-21-phase-5.md +0 -172
- package/docs/review-coverage-2026-07-22-phase-6.md +0 -209
- package/docs/review-coverage-2026-07-22-phase-7.md +0 -173
- package/docs/review-coverage-2026-07-23-phase-8.md +0 -245
- package/docs/review-coverage-2026-07-25-phase-9.md +0 -256
- package/docs/review-coverage-2026-07-26-phase-10.md +0 -132
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, SessionSearchUnsupportedError, } from "../contracts.js";
|
|
4
4
|
import { isNodeErrorCode } from "./config.js";
|
|
5
5
|
export function createJsonlSessionStore(pathOrOptions) {
|
|
6
6
|
const options = typeof pathOrOptions === "string" ? { path: pathOrOptions, createDirectory: true } : pathOrOptions;
|
|
@@ -93,7 +93,10 @@ function parseEntry(line, lineNumber) {
|
|
|
93
93
|
}
|
|
94
94
|
function validateSessionEntry(value, lineNumber, raw) {
|
|
95
95
|
if (!isBasicSessionEntry(value)) {
|
|
96
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: { line: lineNumber, message: "Invalid session entry: expected object with id, sessionId, timestamp, and kind", raw },
|
|
99
|
+
};
|
|
97
100
|
}
|
|
98
101
|
const entry = value;
|
|
99
102
|
if (entry.parentId !== undefined && typeof entry.parentId !== "string") {
|
|
@@ -149,10 +152,10 @@ function isBasicSessionEntry(value) {
|
|
|
149
152
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
150
153
|
return false;
|
|
151
154
|
const entry = value;
|
|
152
|
-
return typeof entry.id === "string"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
return (typeof entry.id === "string" &&
|
|
156
|
+
typeof entry.sessionId === "string" &&
|
|
157
|
+
typeof entry.timestamp === "string" &&
|
|
158
|
+
typeof entry.kind === "string");
|
|
156
159
|
}
|
|
157
160
|
function isMessage(value) {
|
|
158
161
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
package/dist/node/settings.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ConfigLayer } from "../config.js";
|
|
2
|
-
import { type NodeConfigFile } from "./config.js";
|
|
3
2
|
import type { SettingsProvider } from "../contracts.js";
|
|
3
|
+
import { type NodeConfigFile } from "./config.js";
|
|
4
4
|
export type NodeSettingsFile = NodeConfigFile;
|
|
5
5
|
export declare function defaultUserSettingsPath(appName?: string): string;
|
|
6
6
|
export declare function readSettingsFile(path: string): Promise<ConfigLayer["config"]>;
|
package/dist/node/settings.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { mergeConfigLayers } from "../config.js";
|
|
4
|
-
import { readConfigFile, isNodeErrorCode } from "./config.js";
|
|
5
4
|
import { createStaticSettingsProvider } from "../settings.js";
|
|
5
|
+
import { isNodeErrorCode, readConfigFile } from "./config.js";
|
|
6
6
|
export function defaultUserSettingsPath(appName = "prism") {
|
|
7
7
|
return join(homedir(), ".config", appName, "settings.json");
|
|
8
8
|
}
|
|
@@ -14,10 +14,8 @@ import { readOptionalFile } from "./contribution-discovery.js";
|
|
|
14
14
|
* singletons don't fit `discoverContributions`' named-subdir scanner, so this is a
|
|
15
15
|
* sibling loader mirroring `src/node/instruction-injectors.ts`'s per-concern adapter shape. */
|
|
16
16
|
export async function loadSystemPromptFiles(options) {
|
|
17
|
-
const agentsPath = options.agentsMdPath
|
|
18
|
-
|
|
19
|
-
const systemPath = options.systemMdPath
|
|
20
|
-
?? (options.globalRoot !== undefined ? join(options.globalRoot, ".prism", "agent", "SYSTEM.md") : undefined);
|
|
17
|
+
const agentsPath = options.agentsMdPath ?? (options.workspaceRoot !== undefined ? join(options.workspaceRoot, "AGENTS.md") : undefined);
|
|
18
|
+
const systemPath = options.systemMdPath ?? (options.globalRoot !== undefined ? join(options.globalRoot, ".prism", "agent", "SYSTEM.md") : undefined);
|
|
21
19
|
const out = [];
|
|
22
20
|
if (systemPath !== undefined) {
|
|
23
21
|
const layer = await readSystemFile(systemPath, options.permission);
|
package/dist/node/trust.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isAbsolute, relative, resolve, dirname, basename, join } from "node:path";
|
|
2
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { isNodeErrorCode } from "./config.js";
|
|
4
4
|
export function isPathInside(root, target) {
|
|
5
5
|
const from = resolve(root);
|
|
@@ -64,9 +64,7 @@ export function createMemoryPersistenceLifecycle() {
|
|
|
64
64
|
async applyRetention(input) {
|
|
65
65
|
throwIfAborted(input.signal);
|
|
66
66
|
assertOwnership(input);
|
|
67
|
-
const held = new Set([...holds.values()]
|
|
68
|
-
.filter((hold) => sameOwnership(input, hold) && hold.resourceKind === "session")
|
|
69
|
-
.map((hold) => hold.resourceId));
|
|
67
|
+
const held = new Set([...holds.values()].filter((hold) => sameOwnership(input, hold) && hold.resourceKind === "session").map((hold) => hold.resourceId));
|
|
70
68
|
const candidates = input.candidates ?? [];
|
|
71
69
|
const deletedIds = [];
|
|
72
70
|
const skippedHeld = [];
|
package/dist/provider-events.js
CHANGED
|
@@ -35,7 +35,9 @@ export function reconstructToolCallDeltas(events) {
|
|
|
35
35
|
partial.argumentsText += event.argumentsText;
|
|
36
36
|
partials.set(event.index, partial);
|
|
37
37
|
}
|
|
38
|
-
return [...partials.entries()]
|
|
38
|
+
return [...partials.entries()]
|
|
39
|
+
.sort(([a], [b]) => a - b)
|
|
40
|
+
.map(([index, partial]) => {
|
|
39
41
|
if (!partial.id || !partial.name) {
|
|
40
42
|
throw new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${index}`);
|
|
41
43
|
}
|
|
@@ -50,10 +50,9 @@ export function mergeProviderRequestOptions(base, patch) {
|
|
|
50
50
|
cache: {
|
|
51
51
|
...base.cache,
|
|
52
52
|
...patch.cache,
|
|
53
|
-
breakpoints: base.cache?.breakpoints || patch.cache?.breakpoints
|
|
54
|
-
...(base.cache?.breakpoints ?? []),
|
|
55
|
-
|
|
56
|
-
] : undefined,
|
|
53
|
+
breakpoints: base.cache?.breakpoints || patch.cache?.breakpoints
|
|
54
|
+
? [...(base.cache?.breakpoints ?? []), ...(patch.cache?.breakpoints ?? [])]
|
|
55
|
+
: undefined,
|
|
57
56
|
},
|
|
58
57
|
};
|
|
59
58
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type MediaContentBlock, type ModelInputCapability, type ResolvedMediaContent, type ResolveMediaContentOptions } from "../content.js";
|
|
1
2
|
import type { AudioContent, ContentBlock, DocumentContent, FileContent, JsonObject, Message, ModelCapabilities, ModelConfig } from "../contracts.js";
|
|
2
|
-
import { type MediaContentBlock, type ModelInputCapability, type ResolveMediaContentOptions, type ResolvedMediaContent } from "../content.js";
|
|
3
3
|
/** Default upload-cache entry cap per provider media session. */
|
|
4
4
|
export declare const DEFAULT_PROVIDER_UPLOAD_CACHE_ENTRIES = 32;
|
|
5
5
|
/** Inline OpenAI file_data ceiling before preferring Files API upload. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolveCredentialValue } from "../credentials.js";
|
|
2
2
|
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallFromArgumentsText, } from "../provider-events.js";
|
|
3
|
-
import { assertOpenAIChatMessage, applyOpenAIChatStructuredOutput, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
4
|
-
import { ProviderTransportError, readBoundedResponseText, readSseData, } from "./transport.js";
|
|
5
3
|
import { assertStructuredOutputRequestSupported } from "../structured-output.js";
|
|
4
|
+
import { applyOpenAIChatStructuredOutput, assertOpenAIChatMessage, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
5
|
+
import { ProviderTransportError, readBoundedResponseText, readSseData } from "./transport.js";
|
|
6
6
|
export function createOpenAICompatibleProvider(options) {
|
|
7
7
|
const providerId = options.id ?? "openai-compatible";
|
|
8
8
|
return {
|
|
@@ -18,8 +18,7 @@ export function createOpenAICompatibleProvider(options) {
|
|
|
18
18
|
try {
|
|
19
19
|
const url = typeof options.chatCompletionsUrl === "function"
|
|
20
20
|
? options.chatCompletionsUrl(request)
|
|
21
|
-
: options.chatCompletionsUrl
|
|
22
|
-
?? `${options.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
21
|
+
: (options.chatCompletionsUrl ?? `${options.baseUrl.replace(/\/$/, "")}/chat/completions`);
|
|
23
22
|
const authStyle = options.authStyle ?? "bearer";
|
|
24
23
|
const headers = {
|
|
25
24
|
...Object.fromEntries(Object.entries(request.options?.headers ?? {}).filter((entry) => typeof entry[1] === "string")),
|
|
@@ -110,13 +110,13 @@ export function mapOpenAIChatUsage(usage) {
|
|
|
110
110
|
if (!usage || typeof usage !== "object" || Array.isArray(usage))
|
|
111
111
|
return undefined;
|
|
112
112
|
const wire = usage;
|
|
113
|
-
if (wire.prompt_tokens === undefined
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
if (wire.prompt_tokens === undefined &&
|
|
114
|
+
wire.completion_tokens === undefined &&
|
|
115
|
+
wire.total_tokens === undefined &&
|
|
116
|
+
wire.prompt_cache_hit_tokens === undefined &&
|
|
117
|
+
wire.prompt_tokens_details?.cached_tokens === undefined &&
|
|
118
|
+
wire.prompt_tokens_details?.cache_write_tokens === undefined &&
|
|
119
|
+
wire.prompt_tokens_details?.cache_creation_input_tokens === undefined) {
|
|
120
120
|
return undefined;
|
|
121
121
|
}
|
|
122
122
|
return {
|
package/dist/redaction.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export declare function redactAgentEvent(event: AgentEvent, redactor?: SecretRed
|
|
|
8
8
|
export declare function redactSessionEntry(entry: SessionEntry, redactor?: SecretRedactor): SessionEntry;
|
|
9
9
|
export declare function redactProviderRequest(request: ProviderRequest, redactor?: SecretRedactor): ProviderRequest;
|
|
10
10
|
export declare function redactRunLedgerRecord<T extends RunLedgerRecord>(record: T, redactor?: SecretRedactor): T;
|
|
11
|
+
export declare function resolveRedactor(redactor?: SecretRedactor, secrets?: readonly (string | undefined)[]): SecretRedactor | undefined;
|
|
11
12
|
export declare function redactSecrets<T>(value: T, secrets: readonly (string | undefined)[]): T;
|
|
12
13
|
export declare function errorToErrorInfo(error: unknown, secrets?: readonly (string | undefined)[]): ErrorInfo;
|
package/dist/redaction.js
CHANGED
|
@@ -17,6 +17,9 @@ export function redactProviderRequest(request, redactor) {
|
|
|
17
17
|
export function redactRunLedgerRecord(record, redactor) {
|
|
18
18
|
return redactor?.redact(record) ?? record;
|
|
19
19
|
}
|
|
20
|
+
export function resolveRedactor(redactor, secrets) {
|
|
21
|
+
return redactor ?? (secrets?.some((secret) => Boolean(secret)) ? createSecretRedactor(secrets) : undefined);
|
|
22
|
+
}
|
|
20
23
|
export function redactSecrets(value, secrets) {
|
|
21
24
|
const needles = secrets.filter((secret) => Boolean(secret));
|
|
22
25
|
if (needles.length === 0)
|
|
@@ -28,12 +31,12 @@ export function redactSecrets(value, secrets) {
|
|
|
28
31
|
return String(key);
|
|
29
32
|
};
|
|
30
33
|
const assignKey = (target, key, value) => {
|
|
31
|
-
if (!Object.
|
|
34
|
+
if (!Object.hasOwn(target, key)) {
|
|
32
35
|
target[key] = value;
|
|
33
36
|
return;
|
|
34
37
|
}
|
|
35
38
|
let suffix = 2;
|
|
36
|
-
while (Object.
|
|
39
|
+
while (Object.hasOwn(target, `${key}__${suffix}`))
|
|
37
40
|
suffix += 1;
|
|
38
41
|
target[`${key}__${suffix}`] = value;
|
|
39
42
|
};
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type PrismManifest } from "./manifests.js";
|
|
2
|
-
import type { JsonObject, ResourceLoader, ResourceLoadContext } from "./contracts.js";
|
|
3
1
|
import { type MediaContentBounds } from "./content.js";
|
|
2
|
+
import type { JsonObject, ResourceLoadContext, ResourceLoader } from "./contracts.js";
|
|
3
|
+
import { type PrismManifest } from "./manifests.js";
|
|
4
4
|
export declare function loadTextResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<string>;
|
|
5
5
|
export declare function loadJsonResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<JsonObject>;
|
|
6
6
|
export declare function loadManifestResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<PrismManifest>;
|
package/dist/resources.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { parsePrismManifest } from "./manifests.js";
|
|
2
1
|
import { assertJsonObject } from "./config.js";
|
|
3
|
-
import { DEFAULT_MAX_MEDIA_ITEM_BYTES, loadBoundedBinaryResource
|
|
2
|
+
import { DEFAULT_MAX_MEDIA_ITEM_BYTES, loadBoundedBinaryResource } from "./content.js";
|
|
3
|
+
import { parsePrismManifest } from "./manifests.js";
|
|
4
4
|
import { assertPermission, assertTrusted } from "./security.js";
|
|
5
5
|
export async function loadTextResource(loader, uri, context) {
|
|
6
6
|
await assertTrusted(context?.trust, { kind: "resource", target: uri, capability: "load", metadata: context?.metadata });
|
package/dist/rpc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
|
-
import { errorToErrorInfo } from "./redaction.js";
|
|
3
2
|
import { resolveInstructionInjectors } from "./instruction-injection.js";
|
|
3
|
+
import { errorToErrorInfo } from "./redaction.js";
|
|
4
4
|
export async function runRpcServer(options) {
|
|
5
5
|
const first = options.createSession();
|
|
6
6
|
const state = {
|
|
@@ -58,7 +58,11 @@ async function handleRequest(request, state, stdout, activeRuns) {
|
|
|
58
58
|
const session = state.current;
|
|
59
59
|
const existing = activeRuns.get(session.id);
|
|
60
60
|
if (existing) {
|
|
61
|
-
write(stdout, {
|
|
61
|
+
write(stdout, {
|
|
62
|
+
id: request.id,
|
|
63
|
+
ok: false,
|
|
64
|
+
error: errorToErrorInfo(new Error(`Session ${session.id} already has an active run for request ${existing.requestId}`)),
|
|
65
|
+
});
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
// ponytail: Phase 30 — resolve run options (incl. instructionInjectors names) BEFORE opening the
|
|
@@ -74,9 +78,11 @@ async function handleRequest(request, state, stdout, activeRuns) {
|
|
|
74
78
|
await events;
|
|
75
79
|
}
|
|
76
80
|
write(stdout, { id: request.id, ok: true, result: { sessionId: session.id } });
|
|
77
|
-
})()
|
|
81
|
+
})()
|
|
82
|
+
.catch((error) => {
|
|
78
83
|
write(stdout, { id: request.id, ok: false, error: errorToErrorInfo(error) });
|
|
79
|
-
})
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
80
86
|
activeRuns.delete(session.id);
|
|
81
87
|
});
|
|
82
88
|
activeRuns.set(session.id, { requestId: request.id, promise });
|
|
@@ -100,16 +106,24 @@ async function handleRequest(request, state, stdout, activeRuns) {
|
|
|
100
106
|
write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id } });
|
|
101
107
|
break;
|
|
102
108
|
case "state":
|
|
103
|
-
write(stdout, {
|
|
109
|
+
write(stdout, {
|
|
110
|
+
id: request.id,
|
|
111
|
+
ok: true,
|
|
112
|
+
result: {
|
|
104
113
|
sessionId: state.current.id,
|
|
105
114
|
leafId: state.current.leafId,
|
|
106
115
|
handleId: state.currentHandleId,
|
|
107
116
|
// ponytail: `sessions` kept as a backward-compatible handle-id string list (== sessionId
|
|
108
117
|
// for the initial session and clones); `handles` is the branch-handle detail view.
|
|
109
118
|
sessions: [...state.sessions.keys()],
|
|
110
|
-
handles: [...state.sessions.entries()].map(([handleId, session]) => ({
|
|
119
|
+
handles: [...state.sessions.entries()].map(([handleId, session]) => ({
|
|
120
|
+
handleId,
|
|
121
|
+
sessionId: session.id,
|
|
122
|
+
leafId: session.leafId,
|
|
123
|
+
})),
|
|
111
124
|
model: state.model,
|
|
112
|
-
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
113
127
|
break;
|
|
114
128
|
case "messages":
|
|
115
129
|
write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id, entries: await state.current.entries() } });
|
|
@@ -152,7 +166,11 @@ async function handleRequest(request, state, stdout, activeRuns) {
|
|
|
152
166
|
if (!leafId)
|
|
153
167
|
throw new Error("checkout requires params.leafId");
|
|
154
168
|
await state.current.checkout(leafId);
|
|
155
|
-
write(stdout, {
|
|
169
|
+
write(stdout, {
|
|
170
|
+
id: request.id,
|
|
171
|
+
ok: true,
|
|
172
|
+
result: { sessionId: state.current.id, leafId: state.current.leafId, handleId: state.currentHandleId },
|
|
173
|
+
});
|
|
156
174
|
break;
|
|
157
175
|
}
|
|
158
176
|
case "command": {
|
|
@@ -245,7 +263,22 @@ function isObject(value) {
|
|
|
245
263
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
246
264
|
}
|
|
247
265
|
function isCommand(value) {
|
|
248
|
-
return typeof value === "string" &&
|
|
266
|
+
return (typeof value === "string" &&
|
|
267
|
+
[
|
|
268
|
+
"prompt",
|
|
269
|
+
"steer",
|
|
270
|
+
"followUp",
|
|
271
|
+
"abort",
|
|
272
|
+
"state",
|
|
273
|
+
"messages",
|
|
274
|
+
"setModel",
|
|
275
|
+
"compact",
|
|
276
|
+
"switchSession",
|
|
277
|
+
"forkSession",
|
|
278
|
+
"cloneSession",
|
|
279
|
+
"checkout",
|
|
280
|
+
"command",
|
|
281
|
+
].includes(value));
|
|
249
282
|
}
|
|
250
283
|
function readRequestId(value) {
|
|
251
284
|
return isObject(value) && (typeof value.id === "string" || typeof value.id === "number") ? value.id : null;
|
package/dist/run-ledger.js
CHANGED
|
@@ -32,12 +32,18 @@ export function createBatchedRunLedger(target, options = {}) {
|
|
|
32
32
|
let flushChain = Promise.resolve();
|
|
33
33
|
let disposed = false;
|
|
34
34
|
const status = () => ({ accepted, flushed, buffered: queue.length });
|
|
35
|
-
const cancelTimer = () => {
|
|
36
|
-
|
|
35
|
+
const cancelTimer = () => {
|
|
36
|
+
if (timer)
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
timer = undefined;
|
|
39
|
+
};
|
|
37
40
|
const schedule = () => {
|
|
38
41
|
if (timer || disposed || queue.length === 0)
|
|
39
42
|
return;
|
|
40
|
-
timer = setTimeout(() => {
|
|
43
|
+
timer = setTimeout(() => {
|
|
44
|
+
timer = undefined;
|
|
45
|
+
void flush().catch(() => undefined);
|
|
46
|
+
}, maxDelayMs);
|
|
41
47
|
timer.unref?.();
|
|
42
48
|
};
|
|
43
49
|
const write = (item) => {
|
|
@@ -83,7 +89,10 @@ export function createBatchedRunLedger(target, options = {}) {
|
|
|
83
89
|
queue.push({ ...item, bytes });
|
|
84
90
|
bufferedBytes += bytes;
|
|
85
91
|
accepted += 1;
|
|
86
|
-
if (durability === "write_through" ||
|
|
92
|
+
if (durability === "write_through" ||
|
|
93
|
+
queue.length >= maxBatchEntries ||
|
|
94
|
+
bufferedBytes >= maxBatchBytes ||
|
|
95
|
+
(item.kind === "run" && terminal(item.record) && durability === "flush_on_terminal"))
|
|
87
96
|
await flush();
|
|
88
97
|
else
|
|
89
98
|
schedule();
|
package/dist/run-limits.js
CHANGED
|
@@ -58,9 +58,23 @@ export function resolveRunLimits(agent, run) {
|
|
|
58
58
|
resolved[name] = base ? Math.min(resolved[name], override[name]) : override[name];
|
|
59
59
|
}
|
|
60
60
|
const maxCost = override?.maxCost ?? base?.maxCost;
|
|
61
|
-
return Object.freeze({
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
...resolved,
|
|
63
|
+
...(maxCost
|
|
64
|
+
? {
|
|
65
|
+
maxCost: base?.maxCost && override?.maxCost
|
|
66
|
+
? {
|
|
67
|
+
amount: Math.min(base.maxCost.amount, override.maxCost.amount),
|
|
68
|
+
currency: base.maxCost.currency === override.maxCost.currency ? base.maxCost.currency : failCurrency(),
|
|
69
|
+
}
|
|
70
|
+
: maxCost,
|
|
71
|
+
}
|
|
72
|
+
: {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function failCurrency() {
|
|
76
|
+
throw new TypeError("Run limit currencies must match when narrowed");
|
|
62
77
|
}
|
|
63
|
-
function failCurrency() { throw new TypeError("Run limit currencies must match when narrowed"); }
|
|
64
78
|
function validateLimits(input) {
|
|
65
79
|
for (const name of LIMIT_NAMES) {
|
|
66
80
|
const value = input[name];
|
|
@@ -88,7 +102,20 @@ export class RunLimitTracker {
|
|
|
88
102
|
constructor(limits, options = {}) {
|
|
89
103
|
this.options = options;
|
|
90
104
|
this.limits = limits;
|
|
91
|
-
this.counters = {
|
|
105
|
+
this.counters = {
|
|
106
|
+
turns: 0,
|
|
107
|
+
providerAttempts: 0,
|
|
108
|
+
toolRounds: 0,
|
|
109
|
+
toolCalls: 0,
|
|
110
|
+
wallTimeMs: 0,
|
|
111
|
+
requestBytes: 0,
|
|
112
|
+
responseBytes: 0,
|
|
113
|
+
inputTokens: 0,
|
|
114
|
+
outputTokens: 0,
|
|
115
|
+
totalTokens: 0,
|
|
116
|
+
cost: 0,
|
|
117
|
+
...options.snapshot,
|
|
118
|
+
};
|
|
92
119
|
for (const [key, value] of Object.entries(this.counters)) {
|
|
93
120
|
if (!Number.isFinite(value) || value < 0 || (key !== "cost" && !Number.isSafeInteger(value))) {
|
|
94
121
|
throw new TypeError("Run limit snapshot must contain finite non-negative counters");
|
|
@@ -104,10 +131,17 @@ export class RunLimitTracker {
|
|
|
104
131
|
if (remaining === 0)
|
|
105
132
|
this.exceed("maxWallTimeMs", limits.maxWallTimeMs);
|
|
106
133
|
}
|
|
107
|
-
get breach() {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
get breach() {
|
|
135
|
+
return this.exceeded;
|
|
136
|
+
}
|
|
137
|
+
snapshot() {
|
|
138
|
+
return { ...this.counters, wallTimeMs: Math.min(this.limits.maxWallTimeMs, Math.ceil(performance.now() - this.startedAt)) };
|
|
139
|
+
}
|
|
140
|
+
dispose() {
|
|
141
|
+
if (this.timer)
|
|
142
|
+
clearTimeout(this.timer);
|
|
143
|
+
this.timer = undefined;
|
|
144
|
+
}
|
|
111
145
|
charge(limit, delta = 1) {
|
|
112
146
|
if (!Number.isSafeInteger(delta) || delta < 0)
|
|
113
147
|
throw new TypeError("Run limit delta must be a non-negative safe integer");
|
|
@@ -130,7 +164,7 @@ export class RunLimitTracker {
|
|
|
130
164
|
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
|
|
131
165
|
throw new TypeError(`Provider usage ${key} must be a non-negative safe integer`);
|
|
132
166
|
}
|
|
133
|
-
const total = usage.totalTokens ?? (
|
|
167
|
+
const total = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
134
168
|
if (!Number.isSafeInteger(total))
|
|
135
169
|
throw new TypeError("Provider usage totalTokens is invalid");
|
|
136
170
|
this.charge("maxInputTokens", usage.inputTokens ?? 0);
|
|
@@ -149,8 +183,13 @@ export class RunLimitTracker {
|
|
|
149
183
|
}
|
|
150
184
|
exceed(limit, observed) {
|
|
151
185
|
if (!this.exceeded) {
|
|
152
|
-
const maximum = limit === "maxCost" ? this.limits.maxCost?.amount ?? 0 : this.limits[limit];
|
|
153
|
-
this.exceeded = {
|
|
186
|
+
const maximum = limit === "maxCost" ? (this.limits.maxCost?.amount ?? 0) : this.limits[limit];
|
|
187
|
+
this.exceeded = {
|
|
188
|
+
limit,
|
|
189
|
+
maximum,
|
|
190
|
+
observed,
|
|
191
|
+
...(limit === "maxCost" && this.limits.maxCost ? { currency: this.limits.maxCost.currency } : {}),
|
|
192
|
+
};
|
|
154
193
|
this.options.onExceeded?.(this.exceeded);
|
|
155
194
|
}
|
|
156
195
|
if (limit !== "maxWallTimeMs")
|
package/dist/secure-agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createAgent } from "./agents.js";
|
|
2
1
|
import { validateRunStateOptions } from "./agent-run-state.js";
|
|
2
|
+
import { createAgent } from "./agents.js";
|
|
3
3
|
import { assertIdentityActive, assertIdentityMatchesOwnership } from "./identity.js";
|
|
4
4
|
import { resolveRunLimits } from "./run-limits.js";
|
|
5
5
|
import { createToolParameterValidator, createToolRegistry } from "./tools.js";
|
package/dist/security.js
CHANGED
|
@@ -50,11 +50,16 @@ export function createStaticPermissionPolicy(options) {
|
|
|
50
50
|
const key = `${request.kind}:${request.target}:${request.action}`;
|
|
51
51
|
if (deny.has(key))
|
|
52
52
|
return { allowed: false, reason: `Permission denied: ${key}` };
|
|
53
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
allowed: allow.size === 0 || allow.has(key),
|
|
55
|
+
reason: allow.size && !allow.has(key) ? `Permission not allowed: ${key}` : undefined,
|
|
56
|
+
};
|
|
54
57
|
},
|
|
55
58
|
};
|
|
56
59
|
}
|
|
57
60
|
export function denialToErrorInfo(error) {
|
|
58
|
-
return error instanceof Error
|
|
61
|
+
return error instanceof Error
|
|
62
|
+
? { name: error.name, message: error.message, code: error.code }
|
|
63
|
+
: { message: String(error) };
|
|
59
64
|
}
|
|
60
65
|
//# sourceMappingURL=security.js.map
|
package/dist/session-stores.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type BranchReader, type Message, type SessionBranchRead, type SessionEntry, type SessionStore } from "./contracts.js";
|
|
2
2
|
export interface CreateSessionEntryOptions extends Omit<SessionEntry, "id" | "timestamp"> {
|
|
3
3
|
readonly id?: string;
|
|
4
4
|
readonly timestamp?: string;
|
package/dist/session-stores.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, SESSION_APPEND_CONFLICT_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError,
|
|
1
|
+
import { DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
|
|
2
2
|
import { createId } from "./ids.js";
|
|
3
3
|
export function createSessionEntry(options) {
|
|
4
4
|
const { createId, now, ...entry } = options;
|
|
@@ -13,7 +13,9 @@ export function createSessionEntry(options) {
|
|
|
13
13
|
// this if a legitimate branch exceeds it.
|
|
14
14
|
const MAX_BRANCH_PAGES = 64;
|
|
15
15
|
export function getSessionBranchEntries(input, options = {}) {
|
|
16
|
-
return typeof input === "function"
|
|
16
|
+
return typeof input === "function"
|
|
17
|
+
? readBranchFromReader(input, options)
|
|
18
|
+
: getSessionBranchEntriesCore(input, options);
|
|
17
19
|
}
|
|
18
20
|
async function readBranchFromReader(reader, query) {
|
|
19
21
|
const items = [];
|
|
@@ -66,13 +68,15 @@ async function rebuildSessionContextFromReader(reader, query) {
|
|
|
66
68
|
}
|
|
67
69
|
function rebuildSessionContextCore(entries, options = {}) {
|
|
68
70
|
const branch = getSessionBranchEntriesCore(entries, options);
|
|
69
|
-
const compaction = [...branch]
|
|
71
|
+
const compaction = [...branch]
|
|
72
|
+
.reverse()
|
|
73
|
+
.find((entry) => entry.kind === "compaction" && entry.summary && isCompactionEntryData(entry.data));
|
|
70
74
|
if (!compaction || !isCompactionEntryData(compaction.data)) {
|
|
71
75
|
return {
|
|
72
76
|
leafId: branch.at(-1)?.id,
|
|
73
77
|
entries: branch,
|
|
74
|
-
messages: branch.flatMap((entry) => entry.kind === "message" && entry.message ? [entry.message] : []),
|
|
75
|
-
summaries: branch.flatMap((entry) => entry.kind === "summary" && entry.summary ? [entry.summary] : []),
|
|
78
|
+
messages: branch.flatMap((entry) => (entry.kind === "message" && entry.message ? [entry.message] : [])),
|
|
79
|
+
summaries: branch.flatMap((entry) => (entry.kind === "summary" && entry.summary ? [entry.summary] : [])),
|
|
76
80
|
};
|
|
77
81
|
}
|
|
78
82
|
const keepIds = new Set(compaction.data.keepEntryIds ?? []);
|
|
@@ -257,16 +261,12 @@ function searchMemorySessionsLinear(bySession, leafBySession, query) {
|
|
|
257
261
|
}
|
|
258
262
|
matches.sort((a, b) => compareSearchHit(a, b, q.order));
|
|
259
263
|
const afterCursor = q.cursor ? decodeSearchCursor(q.cursor) : undefined;
|
|
260
|
-
const filtered = afterCursor
|
|
261
|
-
? matches.filter((hit) => isAfterSearchCursor(hit, afterCursor, q.order))
|
|
262
|
-
: matches;
|
|
264
|
+
const filtered = afterCursor ? matches.filter((hit) => isAfterSearchCursor(hit, afterCursor, q.order)) : matches;
|
|
263
265
|
const page = filtered.slice(0, q.limit);
|
|
264
266
|
const last = page.at(-1);
|
|
265
267
|
return {
|
|
266
268
|
items: page,
|
|
267
|
-
nextCursor: filtered.length > q.limit && last?.updatedAt
|
|
268
|
-
? encodeSearchCursor(last.updatedAt, last.sessionId)
|
|
269
|
-
: undefined,
|
|
269
|
+
nextCursor: filtered.length > q.limit && last?.updatedAt ? encodeSearchCursor(last.updatedAt, last.sessionId) : undefined,
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
272
|
function entrySearchText(entry) {
|
|
@@ -323,8 +323,8 @@ function isCompactionEntryData(value) {
|
|
|
323
323
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
324
324
|
return false;
|
|
325
325
|
const data = value;
|
|
326
|
-
return (data.throughEntryId === undefined || typeof data.throughEntryId === "string")
|
|
327
|
-
|
|
326
|
+
return ((data.throughEntryId === undefined || typeof data.throughEntryId === "string") &&
|
|
327
|
+
(data.keepEntryIds === undefined || (Array.isArray(data.keepEntryIds) && data.keepEntryIds.every((item) => typeof item === "string"))));
|
|
328
328
|
}
|
|
329
329
|
function indexEntries(entries) {
|
|
330
330
|
const byId = new Map();
|
|
@@ -38,8 +38,8 @@ export function assertStructuredOutputRequestSupported(model, options) {
|
|
|
38
38
|
return;
|
|
39
39
|
validateStructuredOutputOptions(options.structuredOutput);
|
|
40
40
|
if (!modelSupportsStructuredOutput(model.capabilities)) {
|
|
41
|
-
throw new StructuredOutputError("unsupported_model", `Model ${model.provider}/${model.model} does not declare structuredOutput capability; `
|
|
42
|
-
|
|
41
|
+
throw new StructuredOutputError("unsupported_model", `Model ${model.provider}/${model.model} does not declare structuredOutput capability; ` +
|
|
42
|
+
`set loop.structuredOutputMode to "artifact-loop" or choose a capable model`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
function isGenerateValidateReviseLoopOptions(loop) {
|
package/dist/system-prompts.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// ponytail: Phase 31 — `source: "user"` is the global base layer; `source: "app"` sits above package.
|
|
2
2
|
// Behavioral change from Phase 14: `source: "user"` is now the global base (rank 0), not a high-priority caller override.
|
|
3
3
|
// Unknown custom sources sort after package but before app/run so they cannot override host/run layers.
|
|
4
|
-
const sourceRank = new Map([
|
|
4
|
+
const sourceRank = new Map([
|
|
5
|
+
["user", 0],
|
|
6
|
+
["package", 1],
|
|
7
|
+
["app", 2],
|
|
8
|
+
["run", 3],
|
|
9
|
+
]);
|
|
5
10
|
const unknownSourceRank = 1.5;
|
|
6
11
|
export function composeSystemPrompt(contributions = [], options = {}) {
|
|
7
12
|
const parts = baseParts(options.base);
|
|
@@ -40,7 +45,7 @@ function isContributionArray(value) {
|
|
|
40
45
|
return Array.isArray(value);
|
|
41
46
|
}
|
|
42
47
|
function baseParts(base) {
|
|
43
|
-
return (typeof base === "string" ? [base] : base ?? []).filter((text) => text.length > 0);
|
|
48
|
+
return (typeof base === "string" ? [base] : (base ?? [])).filter((text) => text.length > 0);
|
|
44
49
|
}
|
|
45
50
|
function rank(layer) {
|
|
46
51
|
return sourceRank.get(layer.source ?? "") ?? unknownSourceRank;
|
|
@@ -16,7 +16,11 @@ import { createSessionEntry } from "../session-stores.js";
|
|
|
16
16
|
export async function assertCompactionStrategyConforms(strategy, options = {}) {
|
|
17
17
|
const secrets = options.secrets ?? [];
|
|
18
18
|
const entries = [
|
|
19
|
-
createSessionEntry({
|
|
19
|
+
createSessionEntry({
|
|
20
|
+
sessionId: "s",
|
|
21
|
+
kind: "message",
|
|
22
|
+
message: { role: "user", content: [{ type: "text", text: `old ${secrets[0] ?? "history"}` }] },
|
|
23
|
+
}),
|
|
20
24
|
createSessionEntry({ sessionId: "s", kind: "message", message: { role: "assistant", content: [{ type: "text", text: "recent" }] } }),
|
|
21
25
|
];
|
|
22
26
|
const context = {
|