@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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { CancelQueuedResult, LaneSnapshot } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
import type { ConversationHistory } from "../conversation/history";
|
|
4
|
+
import type { SubmitMessageOptions } from "./admission";
|
|
5
|
+
import type { SubmissionHandle } from "./lifecycle";
|
|
6
|
+
import { submissionAbortReason, type StoredSubmission, type SubmissionRepository } from "./repository";
|
|
7
|
+
import type { PiCanonicalUserInput } from "../conversation/message/contract";
|
|
8
|
+
import type { MessageUiRepository } from "../conversation/repository";
|
|
9
|
+
import type { PendingSteer, SteerRepository } from "./steer-repository";
|
|
10
|
+
|
|
11
|
+
interface PendingSteerDeliveryOptions {
|
|
12
|
+
steers: SteerRepository;
|
|
13
|
+
messageUi: MessageUiRepository;
|
|
14
|
+
transaction<T>(run: () => T): T;
|
|
15
|
+
enqueue(message: PiCanonicalUserInput): Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Reconciles Host steer receipts with Pi queue identities, without owning execution. */
|
|
19
|
+
export class PendingSteerDelivery {
|
|
20
|
+
constructor(private readonly options: PendingSteerDeliveryOptions) {}
|
|
21
|
+
|
|
22
|
+
async queue(steer: PendingSteer): Promise<void> {
|
|
23
|
+
const entryId = await this.options.enqueue(
|
|
24
|
+
JSON.parse(steer.canonicalJson) as PiCanonicalUserInput,
|
|
25
|
+
);
|
|
26
|
+
this.link(steer, entryId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Called after Pi commits the original queued user entry. */
|
|
30
|
+
settle(entryId: string): boolean {
|
|
31
|
+
const steer = this.options.steers.findByHarnessEntryId(entryId);
|
|
32
|
+
if (!steer) return false;
|
|
33
|
+
this.options.transaction(() => {
|
|
34
|
+
this.persistSidecar(steer, entryId);
|
|
35
|
+
this.options.steers.deleteByMessageId(steer.messageId);
|
|
36
|
+
});
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async reconcile(
|
|
41
|
+
submissionId: string,
|
|
42
|
+
snapshot: Pick<LaneSnapshot, "queues" | "transcript">,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const pending = this.options.steers.listForSubmission(submissionId);
|
|
45
|
+
const queuedIds = new Set(snapshot.queues.map(({ entryId }) => entryId));
|
|
46
|
+
const transcriptIds = new Set(snapshot.transcript.map(({ id }) => id));
|
|
47
|
+
const linkedIds = new Set(pending.flatMap(({ harnessEntryId }) =>
|
|
48
|
+
harnessEntryId ? [harnessEntryId] : []
|
|
49
|
+
));
|
|
50
|
+
const unlinked = snapshot.queues.filter((entry) =>
|
|
51
|
+
entry.kind === "steer" && entry.type === "message" &&
|
|
52
|
+
!linkedIds.has(entry.entryId)
|
|
53
|
+
);
|
|
54
|
+
// Both durable queues preserve acceptance order; identical text is not identity.
|
|
55
|
+
for (const steer of pending) {
|
|
56
|
+
if (steer.harnessEntryId && transcriptIds.has(steer.harnessEntryId)) {
|
|
57
|
+
this.settle(steer.harnessEntryId);
|
|
58
|
+
} else if (steer.harnessEntryId && queuedIds.has(steer.harnessEntryId)) {
|
|
59
|
+
this.options.transaction(() =>
|
|
60
|
+
this.persistSidecar(steer, steer.harnessEntryId!)
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
const queued = !steer.harnessEntryId ? unlinked.shift() : undefined;
|
|
64
|
+
if (queued) this.link(steer, queued.entryId);
|
|
65
|
+
else await this.queue(steer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private link(steer: PendingSteer, entryId: string): void {
|
|
71
|
+
this.options.transaction(() => {
|
|
72
|
+
if (steer.harnessEntryId && steer.harnessEntryId !== entryId) {
|
|
73
|
+
this.options.messageUi.delete(steer.harnessEntryId);
|
|
74
|
+
}
|
|
75
|
+
this.options.steers.linkHarnessEntry(steer.messageId, entryId);
|
|
76
|
+
this.persistSidecar(steer, entryId);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private persistSidecar(steer: PendingSteer, entryId: string): void {
|
|
81
|
+
if (steer.uiMessageJson) {
|
|
82
|
+
this.options.messageUi.upsert(entryId, steer.uiMessageJson);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/** Keep accepted steers visible on stop, or transfer unconsumed messages to new Submissions. */
|
|
89
|
+
export async function finishPendingSteers(options: {
|
|
90
|
+
submissionId: string;
|
|
91
|
+
aborted: boolean;
|
|
92
|
+
submissions: SubmissionRepository;
|
|
93
|
+
cancelHandoff(submissionId: string, reason: string): StoredSubmission;
|
|
94
|
+
steers: SteerRepository;
|
|
95
|
+
messageUi: MessageUiRepository;
|
|
96
|
+
transaction<T>(run: () => T): T;
|
|
97
|
+
conversation: Pick<ConversationHistory, "append">;
|
|
98
|
+
cancelQueued(entryId: string): Promise<CancelQueuedResult | undefined>;
|
|
99
|
+
submit(
|
|
100
|
+
message: PiCanonicalUserInput,
|
|
101
|
+
options: SubmitMessageOptions,
|
|
102
|
+
): Promise<SubmissionHandle<StoredSubmission>>;
|
|
103
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
104
|
+
}): Promise<void> {
|
|
105
|
+
for (const steer of options.steers.listForSubmission(options.submissionId)) {
|
|
106
|
+
const message = JSON.parse(steer.canonicalJson) as PiCanonicalUserInput;
|
|
107
|
+
const userMessage = steer.uiMessageJson
|
|
108
|
+
? JSON.parse(steer.uiMessageJson) as UIMessage & { role: "user" }
|
|
109
|
+
: undefined;
|
|
110
|
+
const requestId = `${options.submissionId}:steer:${steer.steerId}`;
|
|
111
|
+
if (options.aborted) {
|
|
112
|
+
options.transaction(() => {
|
|
113
|
+
let handoff = options.submissions.findByRequestId(requestId);
|
|
114
|
+
if (handoff?.status === "pending") {
|
|
115
|
+
const reason = submissionAbortReason(options.submissions.find(options.submissionId)) ?? "Cancelled";
|
|
116
|
+
options.submissions.updateAbortReason(handoff.submissionId, reason);
|
|
117
|
+
handoff = options.cancelHandoff(handoff.submissionId, reason);
|
|
118
|
+
}
|
|
119
|
+
if (steer.harnessEntryId) options.messageUi.delete(steer.harnessEntryId);
|
|
120
|
+
options.conversation.append(steer.messageId, message, {
|
|
121
|
+
submissionId: handoff?.status === "aborted" || handoff?.status === "skipped"
|
|
122
|
+
? handoff.submissionId
|
|
123
|
+
: options.submissionId,
|
|
124
|
+
...(userMessage ? { userMessage } : {}),
|
|
125
|
+
});
|
|
126
|
+
options.steers.deleteByMessageId(steer.messageId);
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (steer.harnessEntryId) {
|
|
131
|
+
const cancelled = await options.cancelQueued(steer.harnessEntryId);
|
|
132
|
+
if (cancelled) {
|
|
133
|
+
if (!cancelled.ok) throw cancelled.error;
|
|
134
|
+
if (cancelled.value.kind === "already_consumed") {
|
|
135
|
+
options.transaction(() => options.steers.deleteByMessageId(steer.messageId));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
options.transaction(() => options.messageUi.delete(steer.harnessEntryId!));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// A queued Submission moved to this Turn already owns messageId and is aborted.
|
|
142
|
+
// Handoff needs its own stable identity while preserving the user's message identity.
|
|
143
|
+
const submitted = await options.submit(message, {
|
|
144
|
+
requestId,
|
|
145
|
+
idempotencyKey: requestId,
|
|
146
|
+
userMessageId: steer.messageId,
|
|
147
|
+
steerHandoff: { submissionId: options.submissionId, steerId: steer.steerId },
|
|
148
|
+
...(userMessage ? { userMessage } : {}),
|
|
149
|
+
});
|
|
150
|
+
options.waitUntil(submitted.completion);
|
|
151
|
+
options.transaction(() => {
|
|
152
|
+
let handoff = options.submissions.find(submitted.receipt.submissionId) ?? submitted.receipt;
|
|
153
|
+
const abortReason = submissionAbortReason(options.submissions.find(options.submissionId));
|
|
154
|
+
if (abortReason && handoff.status === "pending" && handoff.requestId === requestId) {
|
|
155
|
+
options.submissions.updateAbortReason(handoff.submissionId, abortReason);
|
|
156
|
+
handoff = options.cancelHandoff(handoff.submissionId, abortReason);
|
|
157
|
+
}
|
|
158
|
+
if (handoff.status === "skipped" || handoff.status === "aborted") {
|
|
159
|
+
options.conversation.append(steer.messageId, message, {
|
|
160
|
+
submissionId: handoff.submissionId,
|
|
161
|
+
...(userMessage ? { userMessage } : {}),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
options.steers.deleteByMessageId(steer.messageId);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -4,6 +4,7 @@ export interface PendingSteer {
|
|
|
4
4
|
steerId: string;
|
|
5
5
|
submissionId: string;
|
|
6
6
|
messageId: string;
|
|
7
|
+
harnessEntryId: string | null;
|
|
7
8
|
canonicalJson: string;
|
|
8
9
|
uiMessageJson: string | null;
|
|
9
10
|
createdAt: number;
|
|
@@ -13,6 +14,7 @@ type PendingSteerRow = {
|
|
|
13
14
|
steer_id: string;
|
|
14
15
|
submission_id: string;
|
|
15
16
|
message_id: string;
|
|
17
|
+
harness_entry_id: string | null;
|
|
16
18
|
canonical_json: string;
|
|
17
19
|
ui_message_json: string | null;
|
|
18
20
|
created_at: number;
|
|
@@ -23,6 +25,7 @@ function mapRow(row: PendingSteerRow): PendingSteer {
|
|
|
23
25
|
steerId: row.steer_id,
|
|
24
26
|
submissionId: row.submission_id,
|
|
25
27
|
messageId: row.message_id,
|
|
28
|
+
harnessEntryId: row.harness_entry_id,
|
|
26
29
|
canonicalJson: row.canonical_json,
|
|
27
30
|
uiMessageJson: row.ui_message_json,
|
|
28
31
|
createdAt: row.created_at,
|
|
@@ -35,28 +38,29 @@ export class SteerRepository {
|
|
|
35
38
|
insert(steer: PendingSteer): void {
|
|
36
39
|
this.sql`
|
|
37
40
|
INSERT INTO pi_pending_steers (
|
|
38
|
-
steer_id, submission_id, message_id, canonical_json,
|
|
41
|
+
steer_id, submission_id, message_id, harness_entry_id, canonical_json,
|
|
39
42
|
ui_message_json, created_at
|
|
40
43
|
) VALUES (
|
|
41
44
|
${steer.steerId}, ${steer.submissionId}, ${steer.messageId},
|
|
42
|
-
${steer.
|
|
45
|
+
${steer.harnessEntryId}, ${steer.canonicalJson},
|
|
46
|
+
${steer.uiMessageJson}, ${steer.createdAt}
|
|
43
47
|
)
|
|
44
48
|
`;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
listForSubmission(submissionId: string): PendingSteer[] {
|
|
48
52
|
return this.sql<PendingSteerRow>`
|
|
49
|
-
SELECT steer_id, submission_id, message_id, canonical_json,
|
|
53
|
+
SELECT steer_id, submission_id, message_id, harness_entry_id, canonical_json,
|
|
50
54
|
ui_message_json, created_at
|
|
51
55
|
FROM pi_pending_steers
|
|
52
56
|
WHERE submission_id = ${submissionId}
|
|
53
|
-
ORDER BY
|
|
57
|
+
ORDER BY rowid ASC
|
|
54
58
|
`.map(mapRow);
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
findByMessageId(messageId: string): PendingSteer | null {
|
|
58
62
|
const row = this.sql<PendingSteerRow>`
|
|
59
|
-
SELECT steer_id, submission_id, message_id, canonical_json,
|
|
63
|
+
SELECT steer_id, submission_id, message_id, harness_entry_id, canonical_json,
|
|
60
64
|
ui_message_json, created_at
|
|
61
65
|
FROM pi_pending_steers
|
|
62
66
|
WHERE message_id = ${messageId}
|
|
@@ -64,6 +68,24 @@ export class SteerRepository {
|
|
|
64
68
|
return row ? mapRow(row) : null;
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
findByHarnessEntryId(entryId: string): PendingSteer | null {
|
|
72
|
+
const row = this.sql<PendingSteerRow>`
|
|
73
|
+
SELECT steer_id, submission_id, message_id, harness_entry_id, canonical_json,
|
|
74
|
+
ui_message_json, created_at
|
|
75
|
+
FROM pi_pending_steers
|
|
76
|
+
WHERE harness_entry_id = ${entryId}
|
|
77
|
+
`[0];
|
|
78
|
+
return row ? mapRow(row) : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
linkHarnessEntry(messageId: string, entryId: string): void {
|
|
82
|
+
this.sql`
|
|
83
|
+
UPDATE pi_pending_steers
|
|
84
|
+
SET harness_entry_id = ${entryId}
|
|
85
|
+
WHERE message_id = ${messageId}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
deleteByMessageId(messageId: string): void {
|
|
68
90
|
this.sql`
|
|
69
91
|
DELETE FROM pi_pending_steers
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Host-confirmed authority pinned to one scheduled task Requirement version.
|
|
2
|
+
* Credentials never belong in scope; only trusted Host ports consume it.
|
|
3
|
+
*/
|
|
4
|
+
export interface RuntimeUnattendedExecutionGrant {
|
|
5
|
+
readonly taskId: string;
|
|
6
|
+
readonly requirementVersion: string;
|
|
7
|
+
readonly authorizationVersion: string;
|
|
8
|
+
readonly scope: Readonly<Record<string, unknown>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function pinnedUnattendedExecutionGrant(
|
|
12
|
+
value: RuntimeUnattendedExecutionGrant,
|
|
13
|
+
): RuntimeUnattendedExecutionGrant {
|
|
14
|
+
for (const [name, version] of [
|
|
15
|
+
["task ID", value.taskId],
|
|
16
|
+
["Requirement", value.requirementVersion],
|
|
17
|
+
["authorization", value.authorizationVersion],
|
|
18
|
+
] as const) {
|
|
19
|
+
if (typeof version !== "string" || !version.trim()) {
|
|
20
|
+
throw new Error(`Unattended execution ${name} is required`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (!value.scope || typeof value.scope !== "object" || Array.isArray(value.scope)) {
|
|
24
|
+
throw new Error("Unattended execution scope must be an object");
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const scope = JSON.parse(JSON.stringify(value.scope)) as unknown;
|
|
28
|
+
if (!scope || typeof scope !== "object" || Array.isArray(scope)) {
|
|
29
|
+
throw new Error("scope changed shape during serialization");
|
|
30
|
+
}
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
taskId: value.taskId,
|
|
33
|
+
requirementVersion: value.requirementVersion,
|
|
34
|
+
authorizationVersion: value.authorizationVersion,
|
|
35
|
+
scope: scope as Readonly<Record<string, unknown>>,
|
|
36
|
+
});
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error("Unattended execution scope must be JSON-serializable", { cause: error });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -88,6 +88,12 @@ export interface TelemetryPayloadByType {
|
|
|
88
88
|
readonly generationId: string;
|
|
89
89
|
readonly outcome: "completed" | "failed" | "cancelled";
|
|
90
90
|
readonly durationMs?: number;
|
|
91
|
+
readonly responseHeadersMs?: number;
|
|
92
|
+
readonly firstRawEventMs?: number;
|
|
93
|
+
readonly firstMeaningfulEventMs?: number;
|
|
94
|
+
readonly firstTextEventMs?: number;
|
|
95
|
+
readonly sourceWaitMs?: number;
|
|
96
|
+
readonly forwardingOverheadMs?: number;
|
|
91
97
|
readonly inputTokens?: number;
|
|
92
98
|
readonly outputTokens?: number;
|
|
93
99
|
readonly totalTokens?: number;
|
|
@@ -288,6 +294,12 @@ function assertPayload(
|
|
|
288
294
|
}
|
|
289
295
|
for (const field of [
|
|
290
296
|
"durationMs",
|
|
297
|
+
"responseHeadersMs",
|
|
298
|
+
"firstRawEventMs",
|
|
299
|
+
"firstMeaningfulEventMs",
|
|
300
|
+
"firstTextEventMs",
|
|
301
|
+
"sourceWaitMs",
|
|
302
|
+
"forwardingOverheadMs",
|
|
291
303
|
"inputTokens",
|
|
292
304
|
"outputTokens",
|
|
293
305
|
"totalTokens",
|
|
@@ -4,6 +4,8 @@ import type { TelemetryOutboxWriter } from "./recorder";
|
|
|
4
4
|
import { TelemetryRecorder } from "./recorder";
|
|
5
5
|
import { RuntimeTelemetry } from "./runtime-telemetry";
|
|
6
6
|
import type { AgentTelemetryBinding } from "./contract";
|
|
7
|
+
import type { PiGenerationLifecycleEvent } from "../models";
|
|
8
|
+
import type { TemporaryAgentLifecycleEvent } from "../tools/subagent/temporary/coordinator";
|
|
7
9
|
|
|
8
10
|
const TELEMETRY_COLD_BUFFER_LIMIT = 1_000;
|
|
9
11
|
|
|
@@ -93,6 +95,69 @@ export class RuntimeTelemetryCoordinator {
|
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
generation(submissionId: string, event: PiGenerationLifecycleEvent): void {
|
|
99
|
+
if (event.type === "started") {
|
|
100
|
+
this.capture("generationStarted", {
|
|
101
|
+
submissionId,
|
|
102
|
+
generationId: event.generationId,
|
|
103
|
+
provider: event.provider,
|
|
104
|
+
model: event.model,
|
|
105
|
+
input: event.input,
|
|
106
|
+
occurredAt: event.timestamp,
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.capture("generationFinished", {
|
|
111
|
+
submissionId,
|
|
112
|
+
generationId: event.generationId,
|
|
113
|
+
outcome: event.outcome === "success" ? "completed"
|
|
114
|
+
: event.outcome === "cancelled" ? "cancelled" : "failed",
|
|
115
|
+
durationMs: event.durationMs,
|
|
116
|
+
responseHeadersMs: event.responseHeadersMs,
|
|
117
|
+
firstRawEventMs: event.firstRawEventMs,
|
|
118
|
+
firstMeaningfulEventMs: event.firstMeaningfulEventMs,
|
|
119
|
+
firstTextEventMs: event.firstTextEventMs,
|
|
120
|
+
sourceWaitMs: event.sourceWaitMs,
|
|
121
|
+
forwardingOverheadMs: event.forwardingOverheadMs,
|
|
122
|
+
...(event.usage ? {
|
|
123
|
+
inputTokens: event.usage.input,
|
|
124
|
+
outputTokens: event.usage.output,
|
|
125
|
+
totalTokens: event.usage.totalTokens,
|
|
126
|
+
cost: event.usage.cost.total,
|
|
127
|
+
} : {}),
|
|
128
|
+
...(event.errorName ? { error: event.errorName } : {}),
|
|
129
|
+
...(event.output ? { output: event.output } : {}),
|
|
130
|
+
occurredAt: event.timestamp,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
temporaryAgent(
|
|
135
|
+
submissionId: string,
|
|
136
|
+
toolCallId: string,
|
|
137
|
+
event: TemporaryAgentLifecycleEvent,
|
|
138
|
+
): void {
|
|
139
|
+
const identity = { submissionId, toolCallId, subagentId: `temporary:${toolCallId}` };
|
|
140
|
+
if (event.type === "started") {
|
|
141
|
+
this.capture("subagentStarted", {
|
|
142
|
+
...identity,
|
|
143
|
+
agentType: event.request.subagentName,
|
|
144
|
+
prompt: { instructions: event.request.instructions, task: event.request.task },
|
|
145
|
+
occurredAt: event.occurredAt,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.capture("subagentFinished", {
|
|
150
|
+
...identity,
|
|
151
|
+
outcome: event.outcome,
|
|
152
|
+
durationMs: event.durationMs,
|
|
153
|
+
occurredAt: event.occurredAt,
|
|
154
|
+
...(event.output === undefined ? {} : { output: event.output }),
|
|
155
|
+
...(event.error === undefined ? {} : {
|
|
156
|
+
error: event.error instanceof Error ? event.error.message : String(event.error),
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
96
161
|
recover(ensureReady: () => Promise<void>): void {
|
|
97
162
|
if (!this.options.outbox()?.hasPending()) return;
|
|
98
163
|
this.options.waitUntil(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
StoredTelemetryOutboxEvent,
|
|
3
|
-
} from "
|
|
3
|
+
} from "./repository";
|
|
4
4
|
import {
|
|
5
5
|
TELEMETRY_OUTBOX_BATCH_SIZE,
|
|
6
|
-
} from "
|
|
6
|
+
} from "./repository";
|
|
7
7
|
import {
|
|
8
8
|
assertUniversalTelemetryEvent,
|
|
9
9
|
type AgentTelemetryPort,
|
package/src/telemetry/index.ts
CHANGED
|
@@ -5,3 +5,8 @@ export * from "./ids";
|
|
|
5
5
|
export * from "./recorder";
|
|
6
6
|
export * from "./runtime-telemetry";
|
|
7
7
|
export * from "./sanitize";
|
|
8
|
+
export * from "./model-usage";
|
|
9
|
+
export * from "./repository";
|
|
10
|
+
export * from "./runtime-event-repository";
|
|
11
|
+
export * from "./subagent-lifecycle";
|
|
12
|
+
export * from "./turn-events";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
|
|
2
|
-
import type { RuntimeModelUsageEvent } from "
|
|
2
|
+
import type { RuntimeModelUsageEvent } from "../config/ports";
|
|
3
3
|
|
|
4
4
|
type ModelUsage = Pick<RuntimeModelUsageEvent,
|
|
5
5
|
"api" | "provider" | "model" | "responseModel" | "stopReason" | "usage"
|
|
@@ -188,6 +188,12 @@ export class RuntimeTelemetry {
|
|
|
188
188
|
generationId: string;
|
|
189
189
|
outcome: "completed" | "failed" | "cancelled";
|
|
190
190
|
durationMs?: number;
|
|
191
|
+
responseHeadersMs?: number;
|
|
192
|
+
firstRawEventMs?: number;
|
|
193
|
+
firstMeaningfulEventMs?: number;
|
|
194
|
+
firstTextEventMs?: number;
|
|
195
|
+
sourceWaitMs?: number;
|
|
196
|
+
forwardingOverheadMs?: number;
|
|
191
197
|
inputTokens?: number;
|
|
192
198
|
outputTokens?: number;
|
|
193
199
|
totalTokens?: number;
|
|
@@ -204,6 +210,12 @@ export class RuntimeTelemetry {
|
|
|
204
210
|
generationId: input.generationId,
|
|
205
211
|
outcome: input.outcome,
|
|
206
212
|
...(input.durationMs === undefined ? {} : { durationMs: input.durationMs }),
|
|
213
|
+
responseHeadersMs: input.responseHeadersMs,
|
|
214
|
+
firstRawEventMs: input.firstRawEventMs,
|
|
215
|
+
firstMeaningfulEventMs: input.firstMeaningfulEventMs,
|
|
216
|
+
firstTextEventMs: input.firstTextEventMs,
|
|
217
|
+
sourceWaitMs: input.sourceWaitMs,
|
|
218
|
+
forwardingOverheadMs: input.forwardingOverheadMs,
|
|
207
219
|
...(input.inputTokens === undefined ? {} : { inputTokens: input.inputTokens }),
|
|
208
220
|
...(input.outputTokens === undefined ? {} : { outputTokens: input.outputTokens }),
|
|
209
221
|
...(input.totalTokens === undefined ? {} : { totalTokens: input.totalTokens }),
|