@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10
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 +7 -7
- package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
- package/src/adapter/cloudflare/agent.ts +1682 -0
- package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
- package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
- package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
- package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/{db → adapter/cloudflare}/schema.ts +28 -80
- package/src/adapter/cloudflare/subagent/definition.ts +63 -33
- package/src/adapter/cloudflare/subagent/runner.ts +174 -120
- package/src/adapter/cloudflare/subagent/tools.ts +13 -14
- package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
- package/src/adapter/cloudflare/universal-agent/tools.ts +10 -12
- package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
- package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
- package/src/config/activation.ts +140 -0
- package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
- package/src/{runtime-assembler.ts → config/assembler.ts} +45 -46
- package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
- package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
- package/src/{runtime-definition.ts → config/definition.ts} +13 -13
- package/src/config/harness-runtime.ts +190 -0
- package/src/config/index.ts +14 -0
- package/src/config/model-guidance.ts +21 -0
- package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
- package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
- package/src/{kernel → config}/profile.ts +7 -0
- package/src/{kernel → config}/public-contracts.ts +6 -6
- package/src/{kernel → config}/runtime-load.ts +1 -1
- package/src/{pi/assembly/context.ts → context/assembly.ts} +5 -207
- package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
- package/src/context/extension/index.ts +3 -0
- package/src/context/index.ts +6 -0
- package/src/{layers/context → context/memory}/budget/gate.ts +21 -5
- package/src/conversation/harness-resume.ts +288 -0
- package/src/conversation/harness-stream.ts +99 -0
- package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
- package/src/conversation/index.ts +6 -0
- package/src/{pi → conversation}/message/contract.ts +3 -0
- package/src/{pi → conversation}/message/conversion.ts +75 -0
- package/src/{pi → conversation}/message/index.ts +1 -0
- package/src/{pi → conversation}/message/model-input.ts +1 -1
- package/src/{pi → conversation}/message/projection.ts +113 -30
- package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
- package/src/conversation/state.ts +196 -0
- package/src/harness/index.ts +7 -0
- package/src/harness/session-harness.ts +562 -0
- package/src/harness/submission-operation.ts +49 -0
- package/src/index.ts +38 -43
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/mcp.ts +1 -1
- package/src/lib/prompt.ts +7 -6
- package/src/lib/token-estimate.ts +8 -0
- package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
- package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
- package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
- package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
- package/src/session/index.ts +39 -0
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +286 -0
- package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
- package/src/submission/delivery.ts +212 -0
- package/src/submission/finalization.ts +111 -0
- package/src/submission/index.ts +9 -0
- package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
- package/src/{db/submission.repo.ts → submission/repository.ts} +117 -114
- package/src/submission/steer-delivery.ts +167 -0
- package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/contract.ts +12 -0
- package/src/telemetry/coordinator.ts +65 -0
- package/src/telemetry/delivery.ts +2 -2
- package/src/telemetry/index.ts +5 -0
- package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
- package/src/telemetry/recorder.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +12 -0
- package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
- package/src/telemetry/turn-events.ts +279 -0
- package/src/tools/README.md +44 -0
- package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
- package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
- package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
- 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/{pi/tool → tools/assistant}/time.ts +1 -1
- package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
- package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +32 -0
- package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
- package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
- package/src/tools/runtime/approval/index.ts +1 -0
- package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
- package/src/tools/runtime/compiler.ts +264 -0
- package/src/tools/runtime/contracts.ts +117 -0
- package/src/tools/runtime/decision-gate.ts +337 -0
- package/src/tools/runtime/human-decisions.ts +127 -0
- package/src/tools/runtime/interaction/index.ts +1 -0
- package/src/{db/interaction.repo.ts → tools/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/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
- package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
- package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
- package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
- package/src/tools/subagent/temporary/approvals.ts +123 -0
- package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
- package/src/tools/subagent/temporary/core.ts +53 -0
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
- package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1320 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
- package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
- package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
- package/src/db/milestone.repo.ts +0 -104
- package/src/db/settlement.repo.ts +0 -104
- package/src/kernel/approval-lifecycle.ts +0 -638
- package/src/kernel/interaction-lifecycle.ts +0 -433
- package/src/kernel/recoverable-chat-agent.ts +0 -1020
- package/src/kernel/state.ts +0 -91
- package/src/pi/assembly/index.ts +0 -19
- package/src/pi/runtime-adapter/execution.ts +0 -1163
- package/src/pi/runtime-adapter/index.ts +0 -293
- package/src/pi/runtime-adapter/recovery.ts +0 -982
- package/src/pi/session/index.ts +0 -24
- package/src/pi/tool/base.ts +0 -550
- package/src/pi/tool/compiler.ts +0 -483
- package/src/pi/tool/index.ts +0 -38
- package/src/pi/tool/schedule.ts +0 -261
- package/src/pi/tool/skill.ts +0 -565
- package/src/pi/tool/web-search/api.ts +0 -1318
- package/src/pi/tool/web-search/index.ts +0 -2
- package/src/pi/tool/workspace-state-connector.ts +0 -146
- package/src/pi/turn/approval.ts +0 -181
- package/src/pi/turn/index.ts +0 -82
- package/src/pi/turn/interaction.ts +0 -181
- package/src/runtime.ts +0 -4760
- package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
- package/src/{kernel → config}/degradation.ts +0 -0
- package/src/{kernel → config}/runtime-config.ts +0 -0
- package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
- package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
- package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
- package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
- package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
- package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
- package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/contract.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/extract/index.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/fanout/index.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/registry.ts +1 -1
- /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
|
@@ -2,7 +2,7 @@ import { isEqual } from "lodash-es";
|
|
|
2
2
|
import {
|
|
3
3
|
decodePiToolRecoveryMilestone,
|
|
4
4
|
replayPiToolRecovery,
|
|
5
|
-
} from "
|
|
5
|
+
} from "./legacy-tool-recovery";
|
|
6
6
|
/** Versioned SQL data migration, called before the Runtime opens its transcript. */
|
|
7
7
|
import {
|
|
8
8
|
branchTip,
|
|
@@ -12,19 +12,43 @@ import {
|
|
|
12
12
|
setValue,
|
|
13
13
|
deleteValue,
|
|
14
14
|
sessionName,
|
|
15
|
+
laneState,
|
|
16
|
+
operationMeta,
|
|
17
|
+
operationState,
|
|
18
|
+
pendingEntry,
|
|
15
19
|
sessionEntryToContextMessages,
|
|
16
20
|
value,
|
|
17
21
|
type Entry,
|
|
18
22
|
type JsonValue,
|
|
19
23
|
type Write,
|
|
24
|
+
type Value,
|
|
25
|
+
type LaneState,
|
|
26
|
+
type ToolExecution,
|
|
20
27
|
} from "@earendil-works/pi-agent-core/harness/session";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type AgentMessage,
|
|
28
|
+
import type {
|
|
29
|
+
AgentToolResult,
|
|
30
|
+
AgentMessage,
|
|
25
31
|
} from "@earendil-works/pi-agent-core";
|
|
26
32
|
import type { Usage, ToolResultMessage } from "@earendil-works/pi-ai";
|
|
27
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
CloudflareSessionStorage,
|
|
35
|
+
CURRENT_PI_STORAGE_VERSION,
|
|
36
|
+
} from "./storage";
|
|
37
|
+
|
|
38
|
+
const RETIRED_SCHEDULE_CALLBACKS = [
|
|
39
|
+
"_cleanupStreamBuffers",
|
|
40
|
+
"_piPlannedContinuation",
|
|
41
|
+
"_piContinuationWatchdog",
|
|
42
|
+
"_piApprovalContinuation",
|
|
43
|
+
"_chatRecoveryRetry",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
const LEGACY_TERMINAL_SUBMISSION_STATUSES = new Set([
|
|
47
|
+
"completed",
|
|
48
|
+
"failed",
|
|
49
|
+
"aborted",
|
|
50
|
+
"skipped",
|
|
51
|
+
]);
|
|
28
52
|
|
|
29
53
|
type LegacyRecord = Record<string, unknown> & {
|
|
30
54
|
id: string;
|
|
@@ -76,7 +100,7 @@ function message(input: unknown): AgentMessage {
|
|
|
76
100
|
finite(record.timestamp);
|
|
77
101
|
if (record.role === "assistant" && record.stopReason === "pending")
|
|
78
102
|
throw new Error("Unsettled legacy assistant message");
|
|
79
|
-
return record as
|
|
103
|
+
return normalizeLegacyAskUserMessage(record) as AgentMessage;
|
|
80
104
|
}
|
|
81
105
|
function hasTable(sql: SqlStorage, name: string): boolean {
|
|
82
106
|
return (
|
|
@@ -89,6 +113,319 @@ function hasTable(sql: SqlStorage, name: string): boolean {
|
|
|
89
113
|
);
|
|
90
114
|
}
|
|
91
115
|
|
|
116
|
+
const ASK_USER_RESPONSE_TYPES = new Set([
|
|
117
|
+
"text",
|
|
118
|
+
"single_select",
|
|
119
|
+
"multi_select",
|
|
120
|
+
"attachment",
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
function normalizeLegacyAskUserInput(input: unknown): unknown {
|
|
124
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return input;
|
|
125
|
+
const record = input as Record<string, unknown>;
|
|
126
|
+
if (!Array.isArray(record.questions)) return input;
|
|
127
|
+
return {
|
|
128
|
+
...record,
|
|
129
|
+
questions: record.questions.map((value) => {
|
|
130
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
131
|
+
const question = value as Record<string, unknown>;
|
|
132
|
+
const { kind, multiSelect, ...current } = question;
|
|
133
|
+
if (ASK_USER_RESPONSE_TYPES.has(question.responseType as string)) {
|
|
134
|
+
return current;
|
|
135
|
+
}
|
|
136
|
+
if (kind !== undefined && kind !== "attachment") return value;
|
|
137
|
+
const responseType = kind === "attachment"
|
|
138
|
+
? "attachment"
|
|
139
|
+
: Array.isArray(question.options)
|
|
140
|
+
? multiSelect === true ? "multi_select" : "single_select"
|
|
141
|
+
: "text";
|
|
142
|
+
return { ...current, responseType };
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function normalizeLegacyAskUserMessage(value: unknown): unknown {
|
|
148
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
149
|
+
const message = value as Record<string, unknown>;
|
|
150
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) return value;
|
|
151
|
+
return {
|
|
152
|
+
...message,
|
|
153
|
+
content: message.content.map((part) => {
|
|
154
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) return part;
|
|
155
|
+
const block = part as Record<string, unknown>;
|
|
156
|
+
return block.type === "toolCall" && block.name === "ask_user"
|
|
157
|
+
? {
|
|
158
|
+
...block,
|
|
159
|
+
arguments: normalizeLegacyAskUserInput(block.arguments),
|
|
160
|
+
}
|
|
161
|
+
: part;
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeLegacyAskUserEntry(value: unknown): unknown {
|
|
167
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
168
|
+
const entry = value as Record<string, unknown>;
|
|
169
|
+
if (entry.type === "message") {
|
|
170
|
+
return { ...entry, message: normalizeLegacyAskUserMessage(entry.message) };
|
|
171
|
+
}
|
|
172
|
+
if (entry.type === "compaction" && Array.isArray(entry.retainedTail)) {
|
|
173
|
+
return {
|
|
174
|
+
...entry,
|
|
175
|
+
retainedTail: entry.retainedTail.map(normalizeLegacyAskUserMessage),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function migrateLegacyAskUserData(sql: SqlStorage): void {
|
|
182
|
+
if (hasTable(sql, "pi_entries")) {
|
|
183
|
+
for (const row of sql.exec<{ id: string; body: string }>(
|
|
184
|
+
"SELECT id, body FROM pi_entries",
|
|
185
|
+
).toArray()) {
|
|
186
|
+
sql.exec(
|
|
187
|
+
"UPDATE pi_entries SET body = ? WHERE id = ?",
|
|
188
|
+
JSON.stringify(normalizeLegacyAskUserEntry(JSON.parse(row.body))),
|
|
189
|
+
row.id,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (hasTable(sql, "pi_tool_interactions")) {
|
|
194
|
+
for (const row of sql.exec<{ interaction_id: string; input_json: string }>(
|
|
195
|
+
"SELECT interaction_id, input_json FROM pi_tool_interactions WHERE tool_name = 'ask_user'",
|
|
196
|
+
).toArray()) {
|
|
197
|
+
sql.exec(
|
|
198
|
+
"UPDATE pi_tool_interactions SET input_json = ? WHERE interaction_id = ?",
|
|
199
|
+
JSON.stringify(normalizeLegacyAskUserInput(JSON.parse(row.input_json))),
|
|
200
|
+
row.interaction_id,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (hasTable(sql, "pi_values")) {
|
|
205
|
+
for (const row of sql.exec<{ key: string; body: string }>(
|
|
206
|
+
"SELECT key, body FROM pi_values WHERE namespace = 'pi.op.state'",
|
|
207
|
+
).toArray()) {
|
|
208
|
+
const state = JSON.parse(row.body) as {
|
|
209
|
+
batch?: {
|
|
210
|
+
assistantEntryId?: unknown;
|
|
211
|
+
turnId?: unknown;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
const assistantEntryId = state.batch?.assistantEntryId;
|
|
215
|
+
const turnId = state.batch?.turnId;
|
|
216
|
+
if (typeof assistantEntryId !== "string" || typeof turnId !== "string") {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const entryRow = sql.exec<{ body: string }>(
|
|
220
|
+
"SELECT body FROM pi_entries WHERE id = ?",
|
|
221
|
+
assistantEntryId,
|
|
222
|
+
).toArray()[0];
|
|
223
|
+
if (!entryRow) continue;
|
|
224
|
+
const entry = JSON.parse(entryRow.body) as {
|
|
225
|
+
type?: unknown;
|
|
226
|
+
message?: { role?: unknown; content?: unknown };
|
|
227
|
+
};
|
|
228
|
+
if (
|
|
229
|
+
entry.type !== "message" || entry.message?.role !== "assistant" ||
|
|
230
|
+
!Array.isArray(entry.message.content)
|
|
231
|
+
) continue;
|
|
232
|
+
entry.message.content.forEach((part, sourceIndex) => {
|
|
233
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) return;
|
|
234
|
+
const block = part as Record<string, unknown>;
|
|
235
|
+
if (block.type !== "toolCall" || block.name !== "ask_user") return;
|
|
236
|
+
const key = `${row.key}:${turnId}:${sourceIndex}`;
|
|
237
|
+
const args = sql.exec<{ body: string }>(
|
|
238
|
+
"SELECT body FROM pi_values WHERE namespace = 'pi.op.tool_args' AND key = ?",
|
|
239
|
+
key,
|
|
240
|
+
).toArray()[0];
|
|
241
|
+
if (!args) return;
|
|
242
|
+
sql.exec(
|
|
243
|
+
"UPDATE pi_values SET body = ? WHERE namespace = 'pi.op.tool_args' AND key = ?",
|
|
244
|
+
JSON.stringify(normalizeLegacyAskUserInput(JSON.parse(args.body))),
|
|
245
|
+
key,
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function removeRetiredRuntimeArtifacts(sql: SqlStorage): void {
|
|
253
|
+
sql.exec(
|
|
254
|
+
"CREATE TABLE IF NOT EXISTS pi_session_entry_submission (entry_id TEXT PRIMARY KEY, submission_id TEXT NOT NULL)",
|
|
255
|
+
);
|
|
256
|
+
if (hasTable(sql, "pi_session_message_submission")) {
|
|
257
|
+
sql.exec(`
|
|
258
|
+
INSERT OR IGNORE INTO pi_session_entry_submission (entry_id, submission_id)
|
|
259
|
+
SELECT message_id, submission_id FROM pi_session_message_submission;
|
|
260
|
+
DROP TABLE pi_session_message_submission;
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
if (hasTable(sql, "cf_agents_schedules")) {
|
|
264
|
+
const placeholders = RETIRED_SCHEDULE_CALLBACKS.map(() => "?").join(", ");
|
|
265
|
+
sql.exec(
|
|
266
|
+
`DELETE FROM cf_agents_schedules WHERE callback IN (${placeholders})`,
|
|
267
|
+
...RETIRED_SCHEDULE_CALLBACKS,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
migrateLegacyAskUserData(sql);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Older Harness adapters copied the initial user sidecar onto consumed Steers. */
|
|
274
|
+
function repairDuplicatedUserSidecars(sql: SqlStorage): void {
|
|
275
|
+
if (!hasTable(sql, "pi_message_ui") || !hasTable(sql, "pi_submissions")) return;
|
|
276
|
+
const entries = new Map(sql.exec<{
|
|
277
|
+
id: string;
|
|
278
|
+
parent_id: string | null;
|
|
279
|
+
body: string;
|
|
280
|
+
submission_id: string | null;
|
|
281
|
+
}>(`
|
|
282
|
+
SELECT entry.id, entry.parent_id, entry.body, link.submission_id
|
|
283
|
+
FROM pi_entries AS entry
|
|
284
|
+
LEFT JOIN pi_session_entry_submission AS link ON link.entry_id = entry.id
|
|
285
|
+
`).toArray().map((row) => [row.id, { ...row, entry: JSON.parse(row.body) as Entry }]));
|
|
286
|
+
const candidates = sql.exec<{ message_id: string }>(`
|
|
287
|
+
SELECT ui.message_id
|
|
288
|
+
FROM pi_message_ui AS ui
|
|
289
|
+
JOIN pi_session_entry_submission AS link ON link.entry_id = ui.message_id
|
|
290
|
+
JOIN pi_submissions AS submission ON submission.submission_id = link.submission_id
|
|
291
|
+
WHERE json_extract(ui.body, '$.id') = submission.user_message_id
|
|
292
|
+
`).toArray();
|
|
293
|
+
for (const candidate of candidates) {
|
|
294
|
+
const current = entries.get(candidate.message_id);
|
|
295
|
+
if (current?.entry.type !== "message" || current.entry.message.role !== "user") continue;
|
|
296
|
+
let parentId = current.parent_id;
|
|
297
|
+
const visited = new Set<string>();
|
|
298
|
+
while (parentId !== null) {
|
|
299
|
+
if (visited.has(parentId)) throw new Error(`Cyclic Pi ancestry: ${parentId}`);
|
|
300
|
+
visited.add(parentId);
|
|
301
|
+
const parent = entries.get(parentId);
|
|
302
|
+
if (!parent) throw new Error(`Missing Pi ancestor: ${parentId}`);
|
|
303
|
+
if (parent.submission_id === current.submission_id &&
|
|
304
|
+
parent.entry.type === "message" && parent.entry.message.role === "user") {
|
|
305
|
+
// The copied parts are also wrong: canonical Steer content is authoritative.
|
|
306
|
+
sql.exec("DELETE FROM pi_message_ui WHERE message_id = ?", candidate.message_id);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
parentId = parent.parent_id;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function hasToolExecution(value: ToolExecution | undefined): boolean {
|
|
315
|
+
return value?.status === "blocked" ||
|
|
316
|
+
(value?.status === "executed" && value.args !== null &&
|
|
317
|
+
typeof value.args === "object" && !Array.isArray(value.args));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** A terminal Submission cannot resume, so its stale lane pointer is not active work. */
|
|
321
|
+
function clearTerminalOperationPointers(sql: SqlStorage): void {
|
|
322
|
+
if (!hasTable(sql, "pi_submissions")) return;
|
|
323
|
+
const lanes = sql.exec<{ key: string; body: string }>(
|
|
324
|
+
"SELECT key, body FROM pi_values WHERE namespace = ?",
|
|
325
|
+
laneState("").namespace,
|
|
326
|
+
).toArray();
|
|
327
|
+
for (const lane of lanes) {
|
|
328
|
+
const state = JSON.parse(lane.body) as LaneState;
|
|
329
|
+
if (state.currentOperationId === null) continue;
|
|
330
|
+
const terminal = sql.exec(
|
|
331
|
+
`SELECT 1 FROM pi_submissions
|
|
332
|
+
WHERE submission_id = ?
|
|
333
|
+
AND status IN ('completed', 'failed', 'aborted', 'skipped')
|
|
334
|
+
LIMIT 1`,
|
|
335
|
+
state.currentOperationId,
|
|
336
|
+
).toArray().length === 1;
|
|
337
|
+
if (!terminal) continue;
|
|
338
|
+
sql.exec(
|
|
339
|
+
`UPDATE pi_values
|
|
340
|
+
SET body = json_set(body, '$.currentOperationId', json('null'))
|
|
341
|
+
WHERE namespace = ? AND key = ?`,
|
|
342
|
+
laneState("").namespace,
|
|
343
|
+
lane.key,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Old active tool outcomes cannot be reconstructed from error text or approval facts. */
|
|
349
|
+
function assertActiveToolProvenance(sql: SqlStorage): void {
|
|
350
|
+
const read = <T>(address: Value<T>): T | undefined => {
|
|
351
|
+
const row = sql.exec<{ body: string }>(
|
|
352
|
+
"SELECT body FROM pi_values WHERE namespace = ? AND key = ?",
|
|
353
|
+
address.namespace, address.key,
|
|
354
|
+
).toArray()[0];
|
|
355
|
+
return row ? JSON.parse(row.body) as T : undefined;
|
|
356
|
+
};
|
|
357
|
+
const lanes = sql.exec<{ key: string; body: string }>(
|
|
358
|
+
"SELECT key, body FROM pi_values WHERE namespace = ?",
|
|
359
|
+
laneState("").namespace,
|
|
360
|
+
).toArray();
|
|
361
|
+
for (const lane of lanes) {
|
|
362
|
+
const operationId = (JSON.parse(lane.body) as LaneState).currentOperationId;
|
|
363
|
+
if (operationId === null) continue;
|
|
364
|
+
const reject: (reason: string) => never = (reason) => {
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Cannot upgrade Pi storage while operation ${operationId} is active: ${reason}. ` +
|
|
367
|
+
"Complete this operation with the previous runtime version, then retry the upgrade.",
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
const meta = read(operationMeta(operationId));
|
|
371
|
+
const state = read(operationState(operationId));
|
|
372
|
+
if (!meta || !state) reject("its durable operation metadata is incomplete");
|
|
373
|
+
let tip = read(branchTip(lane.key));
|
|
374
|
+
if (tip === undefined) reject("its lane branch tip is missing");
|
|
375
|
+
const visited = new Set<string>();
|
|
376
|
+
while (tip !== meta.sourceTipId) {
|
|
377
|
+
if (tip === null || visited.has(tip)) reject("its operation branch cannot be verified");
|
|
378
|
+
visited.add(tip);
|
|
379
|
+
const row = sql.exec<{ body: string }>("SELECT body FROM pi_entries WHERE id = ?", tip).toArray()[0];
|
|
380
|
+
if (!row) reject(`its canonical entry ${tip} is missing`);
|
|
381
|
+
const entry = JSON.parse(row.body) as Entry;
|
|
382
|
+
if (entry.type === "message" && entry.message.role === "toolResult" &&
|
|
383
|
+
!hasToolExecution(entry.toolExecution)) {
|
|
384
|
+
reject(`ToolResult ${entry.id} has no reliable execution provenance`);
|
|
385
|
+
}
|
|
386
|
+
tip = entry.parentId;
|
|
387
|
+
}
|
|
388
|
+
if (state.at !== "tools") continue;
|
|
389
|
+
for (const call of state.batch.calls) {
|
|
390
|
+
if (call.status === "planned" || call.status === "completed") continue;
|
|
391
|
+
if (call.status === "effect_pending") {
|
|
392
|
+
reject(`Tool invocation ${call.resultEntryId} has an unresolved execution outcome`);
|
|
393
|
+
}
|
|
394
|
+
const pending = read(pendingEntry(call.resultEntryId));
|
|
395
|
+
if (pending?.type !== "message" || pending.payload.role !== "toolResult" ||
|
|
396
|
+
!hasToolExecution(pending.toolExecution)) {
|
|
397
|
+
reject(`pending ToolResult ${call.resultEntryId} has no reliable execution provenance`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Upgrade an already-Harness-backed Session before normal reads begin. */
|
|
404
|
+
export function migrateCurrentPiSession(
|
|
405
|
+
storage: DurableObjectStorage,
|
|
406
|
+
): boolean {
|
|
407
|
+
return storage.transactionSync(() => {
|
|
408
|
+
const sql = storage.sql;
|
|
409
|
+
if (!hasTable(sql, "pi_storage_state")) return false;
|
|
410
|
+
const version = sql.exec<{ storage_version: number }>(
|
|
411
|
+
"SELECT storage_version FROM pi_storage_state WHERE singleton = 1",
|
|
412
|
+
).one().storage_version;
|
|
413
|
+
if (version === CURRENT_PI_STORAGE_VERSION) return false;
|
|
414
|
+
if (version !== 1 && version !== 2) {
|
|
415
|
+
throw new Error(`Unsupported Cloudflare Pi storage version: ${version}`);
|
|
416
|
+
}
|
|
417
|
+
clearTerminalOperationPointers(sql);
|
|
418
|
+
assertActiveToolProvenance(sql);
|
|
419
|
+
if (version === 1) removeRetiredRuntimeArtifacts(sql);
|
|
420
|
+
repairDuplicatedUserSidecars(sql);
|
|
421
|
+
sql.exec(
|
|
422
|
+
"UPDATE pi_storage_state SET storage_version = ? WHERE singleton = 1",
|
|
423
|
+
CURRENT_PI_STORAGE_VERSION,
|
|
424
|
+
);
|
|
425
|
+
return true;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
92
429
|
function assertSameToolResult(
|
|
93
430
|
left: ToolResultMessage,
|
|
94
431
|
right: ToolResultMessage,
|
|
@@ -107,6 +444,28 @@ function assertSameToolResult(
|
|
|
107
444
|
}
|
|
108
445
|
}
|
|
109
446
|
|
|
447
|
+
function legacyToolResultMessage(input: {
|
|
448
|
+
toolCallId: string;
|
|
449
|
+
toolName: string;
|
|
450
|
+
result: AgentToolResult<unknown>;
|
|
451
|
+
isError: boolean;
|
|
452
|
+
timestamp: number;
|
|
453
|
+
}): ToolResultMessage {
|
|
454
|
+
return {
|
|
455
|
+
role: "toolResult",
|
|
456
|
+
toolCallId: input.toolCallId,
|
|
457
|
+
toolName: input.toolName,
|
|
458
|
+
content: input.result.content ?? [],
|
|
459
|
+
details: input.result.details,
|
|
460
|
+
...(input.result.usage ? { usage: input.result.usage } : {}),
|
|
461
|
+
...(input.result.addedToolNames?.length
|
|
462
|
+
? { addedToolNames: input.result.addedToolNames }
|
|
463
|
+
: {}),
|
|
464
|
+
isError: input.isError,
|
|
465
|
+
timestamp: input.timestamp,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
110
469
|
/**
|
|
111
470
|
* Called synchronously during DO startup, before admission or recovery can execute.
|
|
112
471
|
* Durable pending work keeps its IDs and state; no old executor runs across this boundary.
|
|
@@ -122,6 +481,7 @@ export function migrateLegacyPiSession(
|
|
|
122
481
|
try {
|
|
123
482
|
storage.transactionSync(() => {
|
|
124
483
|
if (hasTable(sql, "pi_storage_state")) {
|
|
484
|
+
migrateCurrentPiSession(storage);
|
|
125
485
|
const store = new CloudflareSessionStorage(storage);
|
|
126
486
|
report = {
|
|
127
487
|
sourceEntries: 0,
|
|
@@ -356,6 +716,20 @@ export function migrateLegacyPiSession(
|
|
|
356
716
|
.map((row) => [row.message_id, row.submission_id] as const)
|
|
357
717
|
: [],
|
|
358
718
|
);
|
|
719
|
+
const submissionStatuses = new Map(
|
|
720
|
+
hasTable(sql, "pi_submissions")
|
|
721
|
+
? sql
|
|
722
|
+
.exec<{ submission_id: string; status: string }>(
|
|
723
|
+
"SELECT submission_id, status FROM pi_submissions",
|
|
724
|
+
)
|
|
725
|
+
.toArray()
|
|
726
|
+
.map((row) => [row.submission_id, row.status] as const)
|
|
727
|
+
: [],
|
|
728
|
+
);
|
|
729
|
+
const terminalSubmission = (submissionId: string): boolean =>
|
|
730
|
+
LEGACY_TERMINAL_SUBMISSION_STATUSES.has(
|
|
731
|
+
submissionStatuses.get(submissionId) ?? "",
|
|
732
|
+
);
|
|
359
733
|
const results = new Map<string, ToolResultMessage>();
|
|
360
734
|
for (const entry of converted.values()) {
|
|
361
735
|
if (entry.type !== "message" || entry.message.role !== "toolResult")
|
|
@@ -485,8 +859,12 @@ export function migrateLegacyPiSession(
|
|
|
485
859
|
if (!knownSubmissions.has(row.submission_id))
|
|
486
860
|
throw new Error("Dangling recovery submission");
|
|
487
861
|
const record = decodePiToolRecoveryMilestone(row.body);
|
|
488
|
-
if (!record || record.turnId !== row.submission_id)
|
|
862
|
+
if (!record || record.turnId !== row.submission_id) {
|
|
863
|
+
if (LEGACY_TERMINAL_SUBMISSION_STATUSES.has(
|
|
864
|
+
submissionStatuses.get(row.submission_id) ?? "",
|
|
865
|
+
)) continue;
|
|
489
866
|
throw new Error("Invalid legacy recovery milestone");
|
|
867
|
+
}
|
|
490
868
|
const bodies = bySubmission.get(row.submission_id) ?? [];
|
|
491
869
|
bodies.push(row.body);
|
|
492
870
|
bySubmission.set(row.submission_id, bodies);
|
|
@@ -495,8 +873,12 @@ export function migrateLegacyPiSession(
|
|
|
495
873
|
.canonicalToolResults) {
|
|
496
874
|
const key = `${row.submission_id}:${result.toolCallId}`;
|
|
497
875
|
const previous = results.get(key);
|
|
498
|
-
if (previous
|
|
499
|
-
|
|
876
|
+
if (previous && !terminalSubmission(row.submission_id)) {
|
|
877
|
+
assertSameToolResult(previous, result);
|
|
878
|
+
}
|
|
879
|
+
if (!previous || !terminalSubmission(row.submission_id)) {
|
|
880
|
+
results.set(key, result);
|
|
881
|
+
}
|
|
500
882
|
}
|
|
501
883
|
}
|
|
502
884
|
}
|
|
@@ -508,8 +890,12 @@ export function migrateLegacyPiSession(
|
|
|
508
890
|
if (call.result) {
|
|
509
891
|
const key = `${submissionId}:${call.toolCallId}`;
|
|
510
892
|
const previous = results.get(key);
|
|
511
|
-
if (previous
|
|
512
|
-
|
|
893
|
+
if (previous && !terminalSubmission(submissionId)) {
|
|
894
|
+
assertSameToolResult(previous, call.result);
|
|
895
|
+
}
|
|
896
|
+
if (!previous || !terminalSubmission(submissionId)) {
|
|
897
|
+
results.set(key, call.result);
|
|
898
|
+
}
|
|
513
899
|
}
|
|
514
900
|
}
|
|
515
901
|
}
|
|
@@ -567,7 +953,7 @@ export function migrateLegacyPiSession(
|
|
|
567
953
|
"SELECT submission_id, tool_call_id, tool_name, result, is_error, created_at FROM pi_tool_settlements",
|
|
568
954
|
)
|
|
569
955
|
.toArray()) {
|
|
570
|
-
const expected =
|
|
956
|
+
const expected = legacyToolResultMessage({
|
|
571
957
|
toolCallId: row.tool_call_id,
|
|
572
958
|
toolName: row.tool_name,
|
|
573
959
|
result: JSON.parse(row.result) as AgentToolResult<unknown>,
|
|
@@ -577,7 +963,9 @@ export function migrateLegacyPiSession(
|
|
|
577
963
|
const recorded = results.get(
|
|
578
964
|
`${row.submission_id}:${row.tool_call_id}`,
|
|
579
965
|
);
|
|
580
|
-
if (recorded
|
|
966
|
+
if (recorded && !terminalSubmission(row.submission_id)) {
|
|
967
|
+
assertSameToolResult(recorded, expected);
|
|
968
|
+
}
|
|
581
969
|
}
|
|
582
970
|
}
|
|
583
971
|
// The old table remains an inert backup. Failure rolls back DDL, data and the version together.
|
|
@@ -585,6 +973,7 @@ export function migrateLegacyPiSession(
|
|
|
585
973
|
"ALTER TABLE pi_session_entries RENAME TO pi_legacy_entries_backup",
|
|
586
974
|
);
|
|
587
975
|
const store = new CloudflareSessionStorage(storage);
|
|
976
|
+
removeRetiredRuntimeArtifacts(sql);
|
|
588
977
|
for (const entry of converted.values()) {
|
|
589
978
|
const { seq: _seq, timestamp, ...next } = entry;
|
|
590
979
|
const writes: Write[] = [insertEntry(next)];
|
|
@@ -626,6 +1015,7 @@ export function migrateLegacyPiSession(
|
|
|
626
1015
|
values.push(setValue(branchTip(`history:${entry.id}`), entry.id));
|
|
627
1016
|
}
|
|
628
1017
|
store.commitSync([...values, setValue(branchTip("main"), tip)]);
|
|
1018
|
+
repairDuplicatedUserSidecars(sql);
|
|
629
1019
|
sql.exec(
|
|
630
1020
|
"UPDATE pi_storage_state SET session_id = ?, created_at = ? WHERE singleton = 1",
|
|
631
1021
|
metadata.id,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
setValue,
|
|
9
9
|
insertEntry,
|
|
10
10
|
insertUsage,
|
|
11
|
-
type CompactionEntry,
|
|
12
11
|
type Storage,
|
|
13
12
|
type Write,
|
|
14
13
|
type CommitResult,
|
|
@@ -26,9 +25,10 @@ import {
|
|
|
26
25
|
type SessionStats,
|
|
27
26
|
type SessionMetadata,
|
|
28
27
|
} from "@earendil-works/pi-agent-core/harness/session";
|
|
29
|
-
import type { Usage } from "@earendil-works/pi-ai";
|
|
30
28
|
import type { Context } from "@earendil-works/pi-agent-core/harness/context";
|
|
31
29
|
|
|
30
|
+
export const CURRENT_PI_STORAGE_VERSION = 3;
|
|
31
|
+
|
|
32
32
|
/** Pi's Storage contract, with every physical commit confined to one DO transaction. */
|
|
33
33
|
export class CloudflareSessionStorage implements Storage {
|
|
34
34
|
private closed = false;
|
|
@@ -60,13 +60,13 @@ export class CloudflareSessionStorage implements Storage {
|
|
|
60
60
|
storage_version: number;
|
|
61
61
|
}>("SELECT storage_version FROM pi_storage_state WHERE singleton = 1")
|
|
62
62
|
.one().storage_version;
|
|
63
|
-
if (version !==
|
|
63
|
+
if (version !== CURRENT_PI_STORAGE_VERSION)
|
|
64
64
|
throw new Error(
|
|
65
65
|
`Unsupported Cloudflare Pi storage version: ${version}`,
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
this.sql.exec(`
|
|
69
|
-
CREATE TABLE IF NOT EXISTS
|
|
69
|
+
CREATE TABLE IF NOT EXISTS pi_session_entry_submission (entry_id TEXT PRIMARY KEY, submission_id TEXT NOT NULL);
|
|
70
70
|
CREATE TABLE IF NOT EXISTS pi_entries (
|
|
71
71
|
id TEXT PRIMARY KEY, seq INTEGER NOT NULL UNIQUE, parent_id TEXT,
|
|
72
72
|
type TEXT NOT NULL, custom_type TEXT, timestamp INTEGER NOT NULL, body TEXT NOT NULL
|
|
@@ -83,7 +83,8 @@ export class CloudflareSessionStorage implements Storage {
|
|
|
83
83
|
CREATE TABLE IF NOT EXISTS pi_storage_state (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), next_seq INTEGER NOT NULL, storage_version INTEGER NOT NULL, session_id TEXT NOT NULL, created_at INTEGER NOT NULL, stats TEXT NOT NULL);
|
|
84
84
|
`);
|
|
85
85
|
this.sql.exec(
|
|
86
|
-
"INSERT OR IGNORE INTO pi_storage_state VALUES (1, 1,
|
|
86
|
+
"INSERT OR IGNORE INTO pi_storage_state VALUES (1, 1, ?, ?, ?, ?)",
|
|
87
|
+
CURRENT_PI_STORAGE_VERSION,
|
|
87
88
|
crypto.randomUUID(),
|
|
88
89
|
Date.now(),
|
|
89
90
|
JSON.stringify({
|
|
@@ -495,7 +496,7 @@ export class CloudflareSessionStorage implements Storage {
|
|
|
495
496
|
]);
|
|
496
497
|
if (input.submissionId)
|
|
497
498
|
this.sql.exec(
|
|
498
|
-
"INSERT INTO
|
|
499
|
+
"INSERT INTO pi_session_entry_submission VALUES (?, ?)",
|
|
499
500
|
input.id,
|
|
500
501
|
input.submissionId,
|
|
501
502
|
);
|
|
@@ -503,35 +504,6 @@ export class CloudflareSessionStorage implements Storage {
|
|
|
503
504
|
});
|
|
504
505
|
}
|
|
505
506
|
|
|
506
|
-
moveBranchTip(id: string | null): void {
|
|
507
|
-
if (id !== null && !this.entry(id))
|
|
508
|
-
throw new Error(`Unknown branch target: ${id}`);
|
|
509
|
-
this.commitSync([setValue(branchTip("main"), id)]);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
recordCompaction(
|
|
513
|
-
entry: Omit<CompactionEntry, "id" | "parentId" | "seq" | "timestamp"> | undefined,
|
|
514
|
-
usage: Usage | undefined = entry?.usage,
|
|
515
|
-
): string {
|
|
516
|
-
const id = crypto.randomUUID();
|
|
517
|
-
const writes: Write[] = [];
|
|
518
|
-
if (entry) writes.push(
|
|
519
|
-
insertEntry({
|
|
520
|
-
...entry, id,
|
|
521
|
-
parentId: this.getValueSync(branchTip("main"))?.value ?? null,
|
|
522
|
-
}),
|
|
523
|
-
setValue(branchTip("main"), id),
|
|
524
|
-
);
|
|
525
|
-
if (usage) writes.push(insertUsage({
|
|
526
|
-
id: entry ? crypto.randomUUID() : id,
|
|
527
|
-
...(entry ? { entryId: id } : {}),
|
|
528
|
-
usage,
|
|
529
|
-
adjustment: false,
|
|
530
|
-
}));
|
|
531
|
-
this.commitSync(writes);
|
|
532
|
-
return id;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
507
|
entries(): Entry[] {
|
|
536
508
|
return this.sql
|
|
537
509
|
.exec<{ body: string }>("SELECT body FROM pi_entries ORDER BY seq")
|
|
@@ -542,7 +514,7 @@ export class CloudflareSessionStorage implements Storage {
|
|
|
542
514
|
clear(): void {
|
|
543
515
|
this.storage.transactionSync(() => {
|
|
544
516
|
this.sql.exec(
|
|
545
|
-
"DELETE FROM
|
|
517
|
+
"DELETE FROM pi_session_entry_submission; DELETE FROM pi_entries; DELETE FROM pi_values; DELETE FROM pi_lists; DELETE FROM pi_usage;",
|
|
546
518
|
);
|
|
547
519
|
const usage = {
|
|
548
520
|
input: 0,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StorageBackedSession } from "@earendil-works/pi-agent-core/harness/session";
|
|
2
|
+
import {
|
|
3
|
+
migrateCurrentPiSession,
|
|
4
|
+
migrateLegacyPiSession,
|
|
5
|
+
} from "./cloudflare/migration";
|
|
6
|
+
import { CloudflareSessionStorage } from "./cloudflare/storage";
|
|
7
|
+
|
|
8
|
+
export { migrateLegacyPiSession } from "./cloudflare/migration";
|
|
9
|
+
export { CloudflareSessionStorage } from "./cloudflare/storage";
|
|
10
|
+
|
|
11
|
+
/** Open only after the startup transaction has installed the current database format. */
|
|
12
|
+
export function openPiSessionStorage(
|
|
13
|
+
storage: DurableObjectStorage,
|
|
14
|
+
): CloudflareSessionStorage {
|
|
15
|
+
return storage.transactionSync(() => {
|
|
16
|
+
const tables = new Set(
|
|
17
|
+
storage.sql
|
|
18
|
+
.exec<{ name: string }>(
|
|
19
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('pi_storage_state', 'pi_session_entries')",
|
|
20
|
+
)
|
|
21
|
+
.toArray()
|
|
22
|
+
.map((row) => row.name),
|
|
23
|
+
);
|
|
24
|
+
if (!tables.has("pi_storage_state") && tables.has("pi_session_entries")) {
|
|
25
|
+
migrateLegacyPiSession(storage, { dryRun: false });
|
|
26
|
+
} else if (tables.has("pi_storage_state")) {
|
|
27
|
+
migrateCurrentPiSession(storage);
|
|
28
|
+
}
|
|
29
|
+
return new CloudflareSessionStorage(storage);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Open the single official Pi Session owned by one Cloudflare Agent facet. */
|
|
34
|
+
export function openCloudflareSession(
|
|
35
|
+
storage: DurableObjectStorage,
|
|
36
|
+
): StorageBackedSession {
|
|
37
|
+
const store = openPiSessionStorage(storage);
|
|
38
|
+
return new StorageBackedSession(store.metadata, store);
|
|
39
|
+
}
|
|
@@ -163,7 +163,7 @@ export const SPRINGBRAND_WORKER_WEBSITE_SKILL = {
|
|
|
163
163
|
"5. Call `get_browser_console_logs` for the home page and for every primary internal page. Check navigation responses, missing assets, exceptions, CSP or CORS failures, and client fetch failures.",
|
|
164
164
|
"6. Open the Website with the available browser evidence tools. For a multi-page Website, follow real links from the home page to every primary internal page and verify navigation back to the home page. Check at least the normal desktop viewport and one narrow viewport; capture and inspect a screenshot when visual appearance matters.",
|
|
165
165
|
"7. Fix every failure, redeploy, and repeat the same checks. A clean home page does not prove a multi-page Website works.",
|
|
166
|
-
"8. After browser verification succeeds, call `save_creation` exactly once with the existing entry HTML Working Copy path. For the recommended layout, that is `/creations/<website-name>/public/index.html`.",
|
|
166
|
+
"8. After browser verification succeeds, call `save_creation` exactly once with a user-visible title and the existing entry HTML Working Copy path. For the recommended layout, that path is `/creations/<website-name>/public/index.html`.",
|
|
167
167
|
"9. Do not call the Website complete until `save_creation` returns an `artifactId`. Do not save referenced assets as separate Creations.",
|
|
168
168
|
"",
|
|
169
169
|
"## Publication contract",
|