@springbrand/agent-runtime 0.3.0-alpha.2 → 0.3.0-alpha.20
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/package.json +11 -9
- package/src/adapter/cloudflare/agent.ts +177 -122
- package/src/adapter/cloudflare/define-agent.ts +47 -26
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/adapter/cloudflare/schema.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +25 -13
- package/src/adapter/cloudflare/subagent/runner.ts +43 -50
- package/src/adapter/cloudflare/subagent/tools.ts +4 -4
- package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
- package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
- package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
- package/src/config/agent-context.ts +1 -1
- package/src/config/assembler.ts +48 -31
- package/src/config/assembly-snapshot.ts +1 -1
- package/src/config/definition.ts +1 -1
- package/src/config/harness-runtime.ts +51 -13
- package/src/config/model-guidance.ts +20 -0
- package/src/config/pinning.ts +39 -5
- package/src/config/ports.ts +57 -7
- package/src/config/public-contracts.ts +2 -2
- package/src/context/assembly.ts +1 -1
- package/src/context/extension/assembly.ts +2 -2
- package/src/context/memory/budget/gate.ts +19 -3
- package/src/conversation/harness-resume.ts +89 -238
- package/src/conversation/harness-stream.ts +87 -71
- package/src/conversation/history.ts +154 -152
- package/src/conversation/message/contract.ts +12 -0
- package/src/conversation/message/conversion.ts +3 -3
- package/src/conversation/message/projection.ts +61 -21
- package/src/conversation/session-entry.ts +29 -0
- package/src/conversation/state.ts +23 -2
- package/src/harness/governance.ts +206 -0
- package/src/harness/index.ts +3 -0
- package/src/harness/session-harness.ts +255 -446
- package/src/harness/submission-operation.ts +15 -40
- package/src/index.ts +18 -8
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/prompt.ts +9 -10
- package/src/lib/token-estimate.ts +8 -0
- package/src/model-protocol/openrouter.ts +41 -0
- package/src/models/index.ts +200 -35
- package/src/models/openrouter/messages.ts +13 -8
- package/src/models/openrouter/openai.ts +200 -0
- package/src/models/openrouter/protocol-response.ts +85 -0
- package/src/models/provider-streams.ts +45 -0
- package/src/session/cloudflare/migration.ts +382 -864
- package/src/session/cloudflare/storage.ts +13 -536
- package/src/session/index.ts +22 -35
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +12 -1
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +36 -15
- package/src/submission/repository.ts +64 -25
- package/src/submission/steer-delivery.ts +32 -76
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/coordinator.ts +1 -1
- package/src/telemetry/subagent-lifecycle.ts +1 -1
- package/src/telemetry/turn-events.ts +35 -25
- package/src/tools/README.md +44 -0
- package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
- package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
- package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +4 -4
- package/src/tools/assistant/ask-user.ts +317 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/tools/{time.ts → assistant/time.ts} +1 -1
- package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -53
- package/src/tools/{core.ts → codemode/index.ts} +36 -92
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +29 -26
- package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
- package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
- package/src/tools/runtime/activation.ts +40 -0
- package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
- package/src/tools/runtime/compiler.ts +246 -0
- package/src/tools/runtime/contracts.ts +140 -0
- package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +149 -39
- package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
- package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
- package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
- package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
- package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
- package/src/tools/{temporary-agent → subagent/temporary}/core.ts +2 -3
- package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
- package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1322 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
- package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
- package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
- package/src/tools/base.ts +0 -534
- package/src/tools/compiler.ts +0 -327
- package/src/tools/schedule.ts +0 -261
- package/src/tools/skill.ts +0 -565
- package/src/tools/web-search/api.ts +0 -1318
- package/src/tools/web-search/index.ts +0 -2
- package/src/tools/workspace-state-connector.ts +0 -146
- package/src/tools/{approval → runtime/approval}/index.ts +0 -0
- package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
- package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
- /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApprovalReceipt } from "../submission/contracts";
|
|
2
2
|
import type { StoredSubmission } from "../submission/repository";
|
|
3
|
-
import type { PendingApprovalSummary } from "../tools/approval/repository";
|
|
3
|
+
import type { PendingApprovalSummary } from "../tools/runtime/approval/repository";
|
|
4
4
|
import type { PiCanonicalUserInput } from "./message/contract";
|
|
5
5
|
|
|
6
6
|
export type RuntimeLoadPhase = "config" | "assembly" | "mcp" | "pi";
|
|
@@ -68,6 +68,17 @@ export interface RuntimeTurnState {
|
|
|
68
68
|
queued: RuntimeQueuedSubmission[];
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* 本 Session 当前是否拒绝普通用户输入。
|
|
73
|
+
*
|
|
74
|
+
* 定时 Run 未结束时它独占输入:宿主据此禁用输入框并显示原因。
|
|
75
|
+
* 服务端准入用同一事实拒绝消息,所以客户端状态与后端判定不会漂移。
|
|
76
|
+
*/
|
|
77
|
+
export interface RuntimeInputLock {
|
|
78
|
+
/** 稳定原因码,由宿主/UI 决定文案。 */
|
|
79
|
+
reason: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
/**
|
|
72
83
|
* 保存 Kernel 在当前会话里自己维护的审批状态。
|
|
73
84
|
*
|
|
@@ -85,6 +96,8 @@ export interface RuntimeState {
|
|
|
85
96
|
activity?: RuntimeActivityProjection;
|
|
86
97
|
approvals?: ApprovalReceipt[];
|
|
87
98
|
turn?: RuntimeTurnState;
|
|
99
|
+
/** 有值时普通输入被锁定,直到锁定的 Submission 进入终态。 */
|
|
100
|
+
inputLock?: RuntimeInputLock;
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
type SubmissionProjection = Pick<
|
|
@@ -103,6 +116,8 @@ export function projectRuntimeState(input: {
|
|
|
103
116
|
readonly hasPendingInteraction: (submissionId: string) => boolean;
|
|
104
117
|
readonly hasPendingSteer: (submissionId: string) => boolean;
|
|
105
118
|
readonly hasRunningAgentTools: boolean;
|
|
119
|
+
/** A non-terminal scheduled Submission owns ordinary input until it ends. */
|
|
120
|
+
readonly hasUnfinishedScheduled?: boolean;
|
|
106
121
|
readonly previousState: RuntimeState;
|
|
107
122
|
}): { state: RuntimeState; changed: boolean } {
|
|
108
123
|
const approvals: ApprovalReceipt[] = [
|
|
@@ -164,12 +179,18 @@ export function projectRuntimeState(input: {
|
|
|
164
179
|
backgroundWork: input.hasRunningAgentTools,
|
|
165
180
|
revision: (previousActivity?.revision ?? 0) + 1,
|
|
166
181
|
};
|
|
182
|
+
const inputLock: RuntimeInputLock | undefined = input.hasUnfinishedScheduled
|
|
183
|
+
? { reason: "scheduled-run" }
|
|
184
|
+
: undefined;
|
|
167
185
|
const changed = input.previousState.approvals === undefined ||
|
|
168
186
|
JSON.stringify(input.previousState.approvals) !== JSON.stringify(approvals) ||
|
|
169
187
|
JSON.stringify(input.previousState.turn) !== JSON.stringify(turn) ||
|
|
188
|
+
JSON.stringify(input.previousState.inputLock) !== JSON.stringify(inputLock) ||
|
|
170
189
|
nextActivity !== previousActivity;
|
|
171
190
|
return {
|
|
172
|
-
state: changed
|
|
191
|
+
state: changed
|
|
192
|
+
? { ...input.previousState, activity: nextActivity, approvals, turn, inputLock }
|
|
193
|
+
: input.previousState,
|
|
173
194
|
changed,
|
|
174
195
|
};
|
|
175
196
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { AssistantMessage, Message, ToolCall, ToolResultMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { isRetryableAssistantError } from "@earendil-works/pi-ai/utils/retry";
|
|
3
|
+
import type { EntryRecord } from "@earendil-works/pi-durable";
|
|
4
|
+
import type { ArtifactRef } from "../lib/artifacts";
|
|
5
|
+
import {
|
|
6
|
+
isModelStreamStallMessage,
|
|
7
|
+
isOpenRouterServerToolTimeout,
|
|
8
|
+
isRecoverableAssistantError,
|
|
9
|
+
} from "../models";
|
|
10
|
+
|
|
11
|
+
export const MAX_TOOL_RETRIES = 3;
|
|
12
|
+
export const TURN_FAILURE_CEILING = 8;
|
|
13
|
+
export const DEFAULT_MAX_WRAP_UP_MODEL_TURNS = 2;
|
|
14
|
+
export const TOOL_FAILURE_WRAP_UP_INSTRUCTION =
|
|
15
|
+
"This turn reached its tool failure limit. Stop calling tools now. Reply with a final message that explains what failed and the exact next step the user should take. Do not start new work.";
|
|
16
|
+
const RECOVERY_MAX_FAILURES = 5;
|
|
17
|
+
const STALL_MAX_FAILURES = 3;
|
|
18
|
+
const SERVER_TOOL_TIMEOUT_MAX_FAILURES = 2;
|
|
19
|
+
const ARTIFACT_MANIFEST_MAX_CHARS = 12 * 1024;
|
|
20
|
+
/** Harness diagnostics for calls that never crossed the Tool effect boundary. */
|
|
21
|
+
const UNEXECUTED_CODES = new Set(["blocked", "tool_unavailable", "invalid_arguments", "interrupted", "aborted"]);
|
|
22
|
+
|
|
23
|
+
function canonicalJson(value: unknown): string {
|
|
24
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
25
|
+
if (value !== null && typeof value === "object") {
|
|
26
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
27
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
28
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
|
|
29
|
+
.join(",")}}`;
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify(value) ?? "null";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function toolCallKey(toolName: string, args: unknown): string {
|
|
35
|
+
return `${toolName}\0${canonicalJson(args)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resultCodes(entry: EntryRecord): string[] {
|
|
39
|
+
const diagnostics = (entry.data as { diagnostics?: { code?: unknown }[] } | undefined)?.diagnostics;
|
|
40
|
+
return Array.isArray(diagnostics)
|
|
41
|
+
? diagnostics.flatMap((item) => typeof item?.code === "string" ? [item.code] : [])
|
|
42
|
+
: [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Per-Submission Tool failure budget: an exact call that keeps failing is blocked, and too many failures in one
|
|
47
|
+
* Submission switch it to a text-only wrap-up. Counts are rebuilt from canonical entries after a restart.
|
|
48
|
+
*/
|
|
49
|
+
export class ToolGovernance {
|
|
50
|
+
private readonly failures = new Map<string, number>();
|
|
51
|
+
private total = 0;
|
|
52
|
+
wrapUp = false;
|
|
53
|
+
/** Completed model turns when the wrap-up began; the wrap-up then has a bounded number of text turns. */
|
|
54
|
+
wrapUpAt: number | undefined;
|
|
55
|
+
|
|
56
|
+
restore(entries: readonly EntryRecord[]): void {
|
|
57
|
+
this.failures.clear();
|
|
58
|
+
this.total = 0;
|
|
59
|
+
this.wrapUp = false;
|
|
60
|
+
this.wrapUpAt = undefined;
|
|
61
|
+
const calls = new Map<string, ToolCall>();
|
|
62
|
+
let turns = 0;
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const message = entry.model?.[0];
|
|
65
|
+
if (message?.role === "assistant") {
|
|
66
|
+
turns += 1;
|
|
67
|
+
for (const part of message.content) if (part.type === "toolCall") calls.set(part.id, part);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (message?.role !== "toolResult") continue;
|
|
71
|
+
const call = calls.get(message.toolCallId);
|
|
72
|
+
if (!call) continue;
|
|
73
|
+
const codes = resultCodes(entry);
|
|
74
|
+
if (codes.some((code) => UNEXECUTED_CODES.has(code))) {
|
|
75
|
+
if (codes.includes("blocked") && this.exhausted(call)) this.startWrapUp(turns);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
this.record(call, message.isError, turns);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exhausted(call: Pick<ToolCall, "name" | "arguments">): boolean {
|
|
83
|
+
return (this.failures.get(toolCallKey(call.name, call.arguments)) ?? 0) >= MAX_TOOL_RETRIES;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Returns a block reason, or undefined when the call may run. */
|
|
87
|
+
admit(call: Pick<ToolCall, "name" | "arguments">, turns: number): string | undefined {
|
|
88
|
+
if (this.wrapUp) return "This turn reached its tool failure limit. Stop calling tools now.";
|
|
89
|
+
if (!this.exhausted(call)) return undefined;
|
|
90
|
+
this.startWrapUp(turns);
|
|
91
|
+
return `This exact ${call.name} call has already failed ${MAX_TOOL_RETRIES} times — change the input or approach instead of retrying it verbatim.`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
record(call: Pick<ToolCall, "name" | "arguments">, isError: boolean, turns: number): void {
|
|
95
|
+
const key = toolCallKey(call.name, call.arguments);
|
|
96
|
+
if (!isError) {
|
|
97
|
+
this.failures.delete(key);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.failures.set(key, (this.failures.get(key) ?? 0) + 1);
|
|
101
|
+
this.total += 1;
|
|
102
|
+
if (this.total >= TURN_FAILURE_CEILING) this.startWrapUp(turns);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private startWrapUp(turns: number): void {
|
|
106
|
+
this.wrapUp = true;
|
|
107
|
+
this.wrapUpAt ??= turns;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Provider-error recovery the durable Harness cannot express by itself: per-class budgets, refusing deterministic
|
|
113
|
+
* failures, and a fresh provider Session after an OpenRouter server Tool timeout so retries leave a failing route.
|
|
114
|
+
* Pi only retries messages its own classifier accepts, so recoverable failures it does not recognize are labelled.
|
|
115
|
+
*/
|
|
116
|
+
export class ModelRecovery {
|
|
117
|
+
private failures = 0;
|
|
118
|
+
private stalls = 0;
|
|
119
|
+
private serverToolTimeouts = 0;
|
|
120
|
+
private allowed = true;
|
|
121
|
+
|
|
122
|
+
constructor(private readonly baseSessionId: string) {}
|
|
123
|
+
|
|
124
|
+
/** Provider Session identity for the next request. */
|
|
125
|
+
sessionId(): string {
|
|
126
|
+
return this.serverToolTimeouts === 0
|
|
127
|
+
? this.baseSessionId
|
|
128
|
+
: `${this.baseSessionId}:server-tool-recovery:${this.serverToolTimeouts}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Whether the Harness may retry the response last passed to {@link classify}. */
|
|
132
|
+
get retryAllowed(): boolean {
|
|
133
|
+
return this.allowed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Classify one terminal provider response before the Harness decides whether to retry it. */
|
|
137
|
+
classify(message: AssistantMessage): void {
|
|
138
|
+
if (message.stopReason !== "error") {
|
|
139
|
+
this.allowed = true;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.failures += 1;
|
|
143
|
+
const stalled = isModelStreamStallMessage(message.errorMessage);
|
|
144
|
+
const serverToolTimeout = isOpenRouterServerToolTimeout(message);
|
|
145
|
+
if (stalled) this.stalls += 1;
|
|
146
|
+
if (serverToolTimeout) this.serverToolTimeouts += 1;
|
|
147
|
+
const recoverable = stalled || serverToolTimeout || isRecoverableAssistantError(message);
|
|
148
|
+
this.allowed = recoverable && this.failures < RECOVERY_MAX_FAILURES &&
|
|
149
|
+
(!stalled || this.stalls < STALL_MAX_FAILURES) &&
|
|
150
|
+
(!serverToolTimeout || this.serverToolTimeouts < SERVER_TOOL_TIMEOUT_MAX_FAILURES);
|
|
151
|
+
if (this.allowed && !isRetryableAssistantError(message)) {
|
|
152
|
+
// Pi's classifier does not know our stall watchdog or OpenRouter's HTTP/2 upstream failure. Only a retried
|
|
153
|
+
// attempt carries the label; a final failure keeps the provider text.
|
|
154
|
+
message.errorMessage = `network error: ${message.errorMessage ?? "recoverable provider failure"}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function artifactRefFromMessage(message: Message | Pick<ToolResultMessage, "role" | "details">): ArtifactRef | undefined {
|
|
160
|
+
if (message.role !== "toolResult" || message.details === null ||
|
|
161
|
+
typeof message.details !== "object" ||
|
|
162
|
+
(message.details as { kind?: unknown }).kind !== "artifact_ref") {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const ref = message.details as Partial<ArtifactRef>;
|
|
166
|
+
if (typeof ref.path !== "string" || typeof ref.hash !== "string" ||
|
|
167
|
+
typeof ref.bytes !== "number" || typeof ref.preview !== "string" ||
|
|
168
|
+
typeof ref.note !== "string") return undefined;
|
|
169
|
+
return {
|
|
170
|
+
kind: "artifact_ref",
|
|
171
|
+
artifactId: typeof ref.artifactId === "string" ? ref.artifactId : `artifact_${ref.hash.slice(0, 24)}`,
|
|
172
|
+
path: ref.path,
|
|
173
|
+
bytes: ref.bytes,
|
|
174
|
+
hash: ref.hash,
|
|
175
|
+
workspaceRevision: typeof ref.workspaceRevision === "string" ? ref.workspaceRevision : "unknown",
|
|
176
|
+
availability: ref.availability === "unavailable" ? "unavailable" : "available",
|
|
177
|
+
preview: ref.preview,
|
|
178
|
+
note: ref.note,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Lightweight pointers to spilled Tool output, so the model can reread evidence after compaction. */
|
|
183
|
+
export function artifactManifestMessage(refs: ReadonlyMap<string, ArtifactRef>): Message | undefined {
|
|
184
|
+
if (refs.size === 0) return undefined;
|
|
185
|
+
const lines = [
|
|
186
|
+
"<workspace_artifact_manifest>",
|
|
187
|
+
"These are lightweight references to prior Tool output. Do not treat previews as complete. Read an available artifact with the Workspace readFile/readFilePage tool only when the current request needs its evidence.",
|
|
188
|
+
];
|
|
189
|
+
let omitted = 0;
|
|
190
|
+
for (const ref of refs.values()) {
|
|
191
|
+
const preview = ref.preview.replace(/[\r\n]+/g, " ").slice(0, 160);
|
|
192
|
+
const line = [
|
|
193
|
+
`path=${ref.path}`,
|
|
194
|
+
...(ref.availability === "unavailable" ? ["unavailable"] : []),
|
|
195
|
+
`preview=${preview}`,
|
|
196
|
+
].join(" ");
|
|
197
|
+
if ([...lines, line, "</workspace_artifact_manifest>"].join("\n").length > ARTIFACT_MANIFEST_MAX_CHARS) {
|
|
198
|
+
omitted += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
lines.push(line);
|
|
202
|
+
}
|
|
203
|
+
if (omitted > 0) lines.push(`omitted=${omitted} additional artifact references due to manifest budget`);
|
|
204
|
+
lines.push("</workspace_artifact_manifest>");
|
|
205
|
+
return { role: "user", content: lines.join("\n"), timestamp: 1 };
|
|
206
|
+
}
|
package/src/harness/index.ts
CHANGED
|
@@ -3,5 +3,8 @@ export type {
|
|
|
3
3
|
SessionHarnessLimits,
|
|
4
4
|
SessionHarnessOptions,
|
|
5
5
|
SessionHarnessRunResult,
|
|
6
|
+
SessionRunOutcome,
|
|
7
|
+
SessionRunRequest,
|
|
6
8
|
} from "./session-harness";
|
|
7
9
|
export { prepareSubmissionOperation } from "./submission-operation";
|
|
10
|
+
export { ModelRecovery } from "./governance";
|