@springbrand/agent-runtime 0.2.0-alpha.36 → 0.2.0-alpha.38
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 +3 -1
- package/src/adapter/cloudflare/dynamic-worker-observability.ts +84 -0
- package/src/adapter/cloudflare/index.ts +11 -1
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +114 -0
- package/src/index.ts +2 -3
- package/src/pi/tool/index.ts +1 -0
- package/src/pi/tool/time.ts +31 -0
- package/src/runtime-agent.ts +30 -36
- package/src/runtime-assembler.ts +11 -103
- package/src/runtime-definition.ts +5 -89
- package/src/runtime.ts +57 -44
- package/src/adapter/cloudflare/universal-agent/hooks.ts +0 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/agent-runtime",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"@cloudflare/think": "0.15.0",
|
|
24
24
|
"@earendil-works/pi-agent-core": "0.83.0",
|
|
25
25
|
"@earendil-works/pi-ai": "0.83.0",
|
|
26
|
+
"@types/lodash-es": "^4.17.12",
|
|
27
|
+
"cron-schedule": "6.0.0",
|
|
26
28
|
"isomorphic-git": "1.38.6",
|
|
27
29
|
"lodash-es": "^4.18.1",
|
|
28
30
|
"zod": "^4.4.3"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { RuntimePlatformPort } from "../../kernel/bindings";
|
|
2
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
|
|
4
|
+
export type DynamicWorkerObservation = {
|
|
5
|
+
source: string;
|
|
6
|
+
attributes?: Readonly<Record<string, string>>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type TailProps = DynamicWorkerObservation & {
|
|
10
|
+
workerId: string;
|
|
11
|
+
mainModule: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ExportContext = Pick<DurableObjectState, "exports">;
|
|
15
|
+
|
|
16
|
+
function attachTail(
|
|
17
|
+
context: ExportContext,
|
|
18
|
+
code: WorkerLoaderWorkerCode,
|
|
19
|
+
observation: DynamicWorkerObservation,
|
|
20
|
+
workerId: string,
|
|
21
|
+
): WorkerLoaderWorkerCode {
|
|
22
|
+
const entrypoint = (context.exports as unknown as {
|
|
23
|
+
DynamicWorkerTail(options: { props: TailProps }): Fetcher;
|
|
24
|
+
}).DynamicWorkerTail;
|
|
25
|
+
const tail = entrypoint({
|
|
26
|
+
props: { ...observation, workerId, mainModule: code.mainModule },
|
|
27
|
+
});
|
|
28
|
+
return { ...code, tails: [...(code.tails ?? []), tail] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function observeDynamicWorkers(
|
|
32
|
+
context: ExportContext,
|
|
33
|
+
loader: WorkerLoader,
|
|
34
|
+
observation: DynamicWorkerObservation,
|
|
35
|
+
): WorkerLoader {
|
|
36
|
+
return {
|
|
37
|
+
load: (code) =>
|
|
38
|
+
loader.load(attachTail(context, code, observation, crypto.randomUUID())),
|
|
39
|
+
get: (name, getCode) => {
|
|
40
|
+
const workerId = name ?? crypto.randomUUID();
|
|
41
|
+
return loader.get(name, async () =>
|
|
42
|
+
attachTail(context, await getCode(), observation, workerId)
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function observeDynamicWorkerPlatform(
|
|
49
|
+
context: ExportContext,
|
|
50
|
+
load: () => Promise<RuntimePlatformPort>,
|
|
51
|
+
observation: DynamicWorkerObservation,
|
|
52
|
+
): () => Promise<RuntimePlatformPort> {
|
|
53
|
+
return async () => {
|
|
54
|
+
const platform = await load();
|
|
55
|
+
return {
|
|
56
|
+
...platform,
|
|
57
|
+
loader: observeDynamicWorkers(context, platform.loader, observation),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class DynamicWorkerTail extends WorkerEntrypoint<Cloudflare.Env, TailProps> {
|
|
63
|
+
tail(events: TraceItem[]): void {
|
|
64
|
+
for (const event of events) {
|
|
65
|
+
console.log({
|
|
66
|
+
...this.ctx.props.attributes,
|
|
67
|
+
source: "dynamic-worker-tail",
|
|
68
|
+
dynamicWorkerSource: this.ctx.props.source,
|
|
69
|
+
dynamicWorkerId: this.ctx.props.workerId,
|
|
70
|
+
durableObjectId: event.durableObjectId ?? null,
|
|
71
|
+
mainModule: this.ctx.props.mainModule,
|
|
72
|
+
eventTimestamp: event.eventTimestamp,
|
|
73
|
+
scriptName: event.scriptName,
|
|
74
|
+
entrypoint: event.entrypoint,
|
|
75
|
+
outcome: event.outcome,
|
|
76
|
+
truncated: event.truncated,
|
|
77
|
+
cpuTime: event.cpuTime,
|
|
78
|
+
wallTime: event.wallTime,
|
|
79
|
+
logs: event.logs,
|
|
80
|
+
exceptions: event.exceptions,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -14,7 +14,17 @@ export {
|
|
|
14
14
|
export {
|
|
15
15
|
assembleUniversalAgentTools,
|
|
16
16
|
} from "./universal-agent/tools";
|
|
17
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
DynamicWorkerTail,
|
|
19
|
+
observeDynamicWorkerPlatform,
|
|
20
|
+
observeDynamicWorkers,
|
|
21
|
+
type DynamicWorkerObservation,
|
|
22
|
+
} from "./dynamic-worker-observability";
|
|
23
|
+
export {
|
|
24
|
+
isValidTimeZone,
|
|
25
|
+
nextCronOccurrence,
|
|
26
|
+
zoneOffsetMs,
|
|
27
|
+
} from "./scheduling/zoned-cron";
|
|
18
28
|
|
|
19
29
|
export {
|
|
20
30
|
CloudflareSandboxAdapter,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { parseCronExpression } from "cron-schedule";
|
|
2
|
+
|
|
3
|
+
export function zoneOffsetMs(instant: number, timeZone: string): number {
|
|
4
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
5
|
+
timeZone,
|
|
6
|
+
hour12: false,
|
|
7
|
+
year: "numeric",
|
|
8
|
+
month: "2-digit",
|
|
9
|
+
day: "2-digit",
|
|
10
|
+
hour: "2-digit",
|
|
11
|
+
minute: "2-digit",
|
|
12
|
+
second: "2-digit",
|
|
13
|
+
}).formatToParts(new Date(instant));
|
|
14
|
+
const field = (type: Intl.DateTimeFormatPartTypes) =>
|
|
15
|
+
Number(parts.find((part) => part.type === type)?.value ?? 0);
|
|
16
|
+
const wallAsUtc = Date.UTC(
|
|
17
|
+
field("year"),
|
|
18
|
+
field("month") - 1,
|
|
19
|
+
field("day"),
|
|
20
|
+
field("hour") % 24,
|
|
21
|
+
field("minute"),
|
|
22
|
+
field("second"),
|
|
23
|
+
);
|
|
24
|
+
return wallAsUtc - Math.floor(instant / 1_000) * 1_000;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function nextCronOccurrence(
|
|
28
|
+
cron: string,
|
|
29
|
+
timeZone: string,
|
|
30
|
+
after = Date.now(),
|
|
31
|
+
): number {
|
|
32
|
+
const parsed = parseCronExpression(cron);
|
|
33
|
+
const secondPrecision = parsed.seconds.length !== 1 || parsed.seconds[0] !== 0;
|
|
34
|
+
const step = secondPrecision ? 1_000 : 60_000;
|
|
35
|
+
for (
|
|
36
|
+
let instant = Math.floor(after / step) * step + step;
|
|
37
|
+
instant <= after + 3 * 3_600_000;
|
|
38
|
+
instant += step
|
|
39
|
+
) {
|
|
40
|
+
if (matches(parsed, instant + zoneOffsetMs(instant, timeZone))) return instant;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const wallAfter = after + zoneOffsetMs(after, timeZone);
|
|
44
|
+
const wallDate = new Date(wallAfter);
|
|
45
|
+
const firstDay = Date.UTC(
|
|
46
|
+
wallDate.getUTCFullYear(),
|
|
47
|
+
wallDate.getUTCMonth(),
|
|
48
|
+
wallDate.getUTCDate(),
|
|
49
|
+
);
|
|
50
|
+
for (let dayOffset = 0; dayOffset <= 366 * 5; dayOffset += 1) {
|
|
51
|
+
const day = firstDay + dayOffset * 86_400_000;
|
|
52
|
+
if (!matchesDate(parsed, new Date(day))) continue;
|
|
53
|
+
for (const hour of parsed.hours) {
|
|
54
|
+
for (const minute of parsed.minutes) {
|
|
55
|
+
for (const second of parsed.seconds) {
|
|
56
|
+
const wall = day + hour * 3_600_000 + minute * 60_000 + second * 1_000;
|
|
57
|
+
if (wall <= wallAfter) continue;
|
|
58
|
+
const resolved = wallTimeCandidates(wall, timeZone, after)[0];
|
|
59
|
+
if (resolved !== undefined) return resolved;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Cron has no occurrence in the next five years");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isValidTimeZone(timeZone: string): boolean {
|
|
68
|
+
try {
|
|
69
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function wallTimeCandidates(wallUtc: number, timeZone: string, after: number): number[] {
|
|
77
|
+
const offsets = new Set([
|
|
78
|
+
zoneOffsetMs(after, timeZone),
|
|
79
|
+
zoneOffsetMs(wallUtc - 2 * 86_400_000, timeZone),
|
|
80
|
+
zoneOffsetMs(wallUtc, timeZone),
|
|
81
|
+
zoneOffsetMs(wallUtc + 2 * 86_400_000, timeZone),
|
|
82
|
+
]);
|
|
83
|
+
return [...offsets]
|
|
84
|
+
.map((offset) => wallUtc - offset)
|
|
85
|
+
.filter((instant) =>
|
|
86
|
+
instant > after && instant + zoneOffsetMs(instant, timeZone) === wallUtc
|
|
87
|
+
)
|
|
88
|
+
.sort((left, right) => left - right);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function matches(
|
|
92
|
+
cron: ReturnType<typeof parseCronExpression>,
|
|
93
|
+
wallUtc: number,
|
|
94
|
+
): boolean {
|
|
95
|
+
const wall = new Date(wallUtc);
|
|
96
|
+
return cron.seconds.includes(wall.getUTCSeconds()) &&
|
|
97
|
+
cron.minutes.includes(wall.getUTCMinutes()) &&
|
|
98
|
+
cron.hours.includes(wall.getUTCHours()) &&
|
|
99
|
+
matchesDate(cron, wall);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function matchesDate(
|
|
103
|
+
cron: ReturnType<typeof parseCronExpression>,
|
|
104
|
+
wall: Date,
|
|
105
|
+
): boolean {
|
|
106
|
+
if (!cron.months.includes(wall.getUTCMonth())) return false;
|
|
107
|
+
const daysRestricted = cron.days.length !== 31;
|
|
108
|
+
const weekdaysRestricted = cron.weekdays.length !== 7;
|
|
109
|
+
const dayMatches = cron.days.includes(wall.getUTCDate());
|
|
110
|
+
const weekdayMatches = cron.weekdays.includes(wall.getUTCDay());
|
|
111
|
+
return daysRestricted && weekdaysRestricted
|
|
112
|
+
? dayMatches || weekdayMatches
|
|
113
|
+
: dayMatches && weekdayMatches;
|
|
114
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* - `admission` 是提示进入 Turn 前的接纳、去重和状态登记流程。
|
|
25
25
|
* - `defineRuntimeAgent.load` 返回 config 和已加载 Resource。
|
|
26
26
|
* - `defineRuntimeAgent.tools` 返回 Host 的 Pi Tool Candidate 装配结果。
|
|
27
|
-
* - `assembleRuntimeSnapshot`
|
|
27
|
+
* - `assembleRuntimeSnapshot` 消费已加载 Resource 与 Tool,产出候选 Snapshot。
|
|
28
28
|
* - `Port` 是 Runtime 调用外部能力时依赖的最小接口(Definition 作者不直接装配)。
|
|
29
29
|
* - `RuntimeBindings` 是本次装配选中的 Port 和可执行对象集合。
|
|
30
30
|
* - `RuntimeBuilder` 是包内实现,用来校验贡献并生成候选结果。
|
|
@@ -63,11 +63,9 @@ export type {
|
|
|
63
63
|
} from "./runtime-assembler";
|
|
64
64
|
export { assembleRuntimeSnapshot } from "./runtime-assembler";
|
|
65
65
|
export type {
|
|
66
|
-
ProfileOverrides,
|
|
67
66
|
ResolvedConnectors,
|
|
68
67
|
ResolvedResources,
|
|
69
68
|
RuntimeAgentContext,
|
|
70
|
-
RuntimeAgentHooks,
|
|
71
69
|
RuntimeSettings,
|
|
72
70
|
RuntimeToolBindings,
|
|
73
71
|
ToolAssemblyResult,
|
|
@@ -104,6 +102,7 @@ export type {
|
|
|
104
102
|
PiDeclaredToolPolicy,
|
|
105
103
|
} from "./pi/tool";
|
|
106
104
|
export { memoryPiToolCandidate } from "./pi/tool";
|
|
105
|
+
export { GET_TIME_TOOL_NAME, getTimePiToolCandidate } from "./pi/tool";
|
|
107
106
|
export { schedulePiToolCandidates } from "./pi/tool";
|
|
108
107
|
export {
|
|
109
108
|
sandboxPiToolCandidates,
|
package/src/pi/tool/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { PiToolCandidate } from "./compiler";
|
|
4
|
+
|
|
5
|
+
export const GET_TIME_TOOL_NAME = "get_time";
|
|
6
|
+
|
|
7
|
+
const parameters = Type.Object({});
|
|
8
|
+
|
|
9
|
+
/** Product-neutral current-time Tool for Host registries. */
|
|
10
|
+
export function getTimePiToolCandidate(owner = "utilities"): PiToolCandidate {
|
|
11
|
+
return {
|
|
12
|
+
owner,
|
|
13
|
+
requiredExecutionLevel: "safe",
|
|
14
|
+
source: "action",
|
|
15
|
+
summary: "Get the current server time as an ISO string.",
|
|
16
|
+
tool: {
|
|
17
|
+
name: GET_TIME_TOOL_NAME,
|
|
18
|
+
label: "Get Time",
|
|
19
|
+
parameters,
|
|
20
|
+
description: "Get the current server time as an ISO string.",
|
|
21
|
+
async execute(_toolCallId, _input, signal) {
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
const details = { now: new Date().toISOString() };
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: JSON.stringify(details) }],
|
|
26
|
+
details,
|
|
27
|
+
} satisfies AgentToolResult<typeof details>;
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/runtime-agent.ts
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
} from "./runtime-assembler";
|
|
36
36
|
import type {
|
|
37
37
|
ResolvedResources,
|
|
38
|
-
RuntimeAgentHooks,
|
|
39
38
|
ToolAssemblyResult,
|
|
40
39
|
} from "./runtime-definition";
|
|
41
40
|
import type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
|
|
@@ -78,6 +77,7 @@ export interface LoadedRuntime<Config> {
|
|
|
78
77
|
readonly runtimeKey: string;
|
|
79
78
|
readonly config: Config;
|
|
80
79
|
readonly resources: ResolvedResources;
|
|
80
|
+
readonly commitGuard?: () => Promise<void>;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface RuntimeConfigUpdate<Change> {
|
|
@@ -106,11 +106,6 @@ interface RuntimeAgentDefinitionBase<
|
|
|
106
106
|
config: Config,
|
|
107
107
|
) => ToolAssemblyResult | Promise<ToolAssemblyResult>;
|
|
108
108
|
|
|
109
|
-
readonly hooks?:
|
|
110
|
-
| RuntimeAgentHooks<Env, Config, Command, Change>
|
|
111
|
-
| ((context: RuntimeAgentPlanningContext<Env, Command, Change>) =>
|
|
112
|
-
RuntimeAgentHooks<Env, Config, Command, Change>);
|
|
113
|
-
|
|
114
109
|
/** Prepare the optional telemetry consumer for this concrete Agent facet. */
|
|
115
110
|
readonly telemetry?: (
|
|
116
111
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
@@ -407,18 +402,6 @@ export function defineRuntimeAgent<
|
|
|
407
402
|
| MutableRuntimeAgentDefinition<Env, Config, Command, Change>
|
|
408
403
|
| ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
409
404
|
): RuntimeAgentClass<Env, Command, Change> {
|
|
410
|
-
const resolveDefinitionHooks = (
|
|
411
|
-
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
412
|
-
): RuntimeAgentHooks<Env, Config, Command, Change> | undefined => {
|
|
413
|
-
const hooks = definition.hooks;
|
|
414
|
-
if (!hooks) return undefined;
|
|
415
|
-
return typeof hooks === "function"
|
|
416
|
-
? (hooks as (
|
|
417
|
-
ctx: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
418
|
-
) => RuntimeAgentHooks<Env, Config, Command, Change>)(context)
|
|
419
|
-
: hooks as RuntimeAgentHooks<Env, Config, Command, Change>;
|
|
420
|
-
};
|
|
421
|
-
|
|
422
405
|
const resolveDefinitionTelemetry = (
|
|
423
406
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
424
407
|
): AgentTelemetryBinding | undefined => {
|
|
@@ -634,7 +617,8 @@ export function defineRuntimeAgent<
|
|
|
634
617
|
)(context, loaded.config),
|
|
635
618
|
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
636
619
|
);
|
|
637
|
-
let
|
|
620
|
+
let resources = loaded.resources;
|
|
621
|
+
let commitGuard = loaded.commitGuard;
|
|
638
622
|
if (!this.telemetryResolved) {
|
|
639
623
|
this.resolvedTelemetry = resolveDefinitionTelemetry(context);
|
|
640
624
|
this.telemetryResolved = true;
|
|
@@ -643,14 +627,14 @@ export function defineRuntimeAgent<
|
|
|
643
627
|
if (this.role === "temporary") {
|
|
644
628
|
this.temporaryDispose = tools.dispose;
|
|
645
629
|
tools = this.applyTemporaryToolPolicy(tools);
|
|
646
|
-
|
|
630
|
+
resources = await this.applyTemporaryResources(resources);
|
|
631
|
+
commitGuard = undefined;
|
|
647
632
|
}
|
|
648
633
|
const candidate = await assembleRuntimeSnapshot({
|
|
649
634
|
ctx: context,
|
|
650
|
-
config: loaded.config,
|
|
651
635
|
tools,
|
|
652
|
-
resources
|
|
653
|
-
|
|
636
|
+
resources,
|
|
637
|
+
...(commitGuard ? { commitGuard } : {}),
|
|
654
638
|
...(telemetry ? { telemetry } : {}),
|
|
655
639
|
});
|
|
656
640
|
|
|
@@ -709,9 +693,9 @@ export function defineRuntimeAgent<
|
|
|
709
693
|
};
|
|
710
694
|
}
|
|
711
695
|
|
|
712
|
-
private async
|
|
713
|
-
|
|
714
|
-
> {
|
|
696
|
+
private async applyTemporaryResources(
|
|
697
|
+
resources: ResolvedResources,
|
|
698
|
+
): Promise<ResolvedResources> {
|
|
715
699
|
const launch = this.temporaryLaunch ??
|
|
716
700
|
await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
|
|
717
701
|
TEMPORARY_AGENT_LAUNCH_KEY,
|
|
@@ -719,17 +703,27 @@ export function defineRuntimeAgent<
|
|
|
719
703
|
if (!launch) throw new Error("Temporary Agent is not initialized");
|
|
720
704
|
this.temporaryLaunch = launch;
|
|
721
705
|
return {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
706
|
+
...resources,
|
|
707
|
+
settings: {
|
|
708
|
+
...resources.settings,
|
|
709
|
+
profile: {
|
|
710
|
+
...resources.settings.profile,
|
|
711
|
+
systemPrompt: launch.instructions,
|
|
712
|
+
executionLevel: "high",
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
platform: {
|
|
716
|
+
...resources.platform,
|
|
717
|
+
gateTool: async (request) => {
|
|
718
|
+
if (!requiresExecutionApproval(
|
|
719
|
+
launch.executionLevel,
|
|
720
|
+
request.requiredExecutionLevel,
|
|
721
|
+
)) return;
|
|
722
|
+
await this.requestParentTemporaryApproval(launch, request);
|
|
723
|
+
},
|
|
732
724
|
},
|
|
725
|
+
turnEvents: undefined,
|
|
726
|
+
admission: undefined,
|
|
733
727
|
};
|
|
734
728
|
}
|
|
735
729
|
|
package/src/runtime-assembler.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
RuntimeSandboxPort,
|
|
8
8
|
RuntimeSchedulePort,
|
|
9
9
|
RuntimeSkillSourceBinding,
|
|
10
|
+
RuntimeSubmissionAdmissionHook,
|
|
10
11
|
RuntimeSubagentPort,
|
|
11
12
|
RuntimeTurnEventsPort,
|
|
12
13
|
RuntimeBindings,
|
|
@@ -37,7 +38,6 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
|
37
38
|
import type { PiToolCandidate } from "./pi/tool/compiler";
|
|
38
39
|
import type {
|
|
39
40
|
AssembleRuntimeSnapshotInput,
|
|
40
|
-
RuntimeAgentHooks,
|
|
41
41
|
RuntimeSettings,
|
|
42
42
|
RuntimeExtensionContribution,
|
|
43
43
|
RuntimeProfileContribution,
|
|
@@ -45,7 +45,6 @@ import type {
|
|
|
45
45
|
RuntimeToolSurfacePolicy,
|
|
46
46
|
} from "./runtime-definition";
|
|
47
47
|
import type { RuntimeCodeExecutionFactory } from "./kernel/bindings";
|
|
48
|
-
import { withRuntimeLoadTimeout } from "./kernel/runtime-load";
|
|
49
48
|
import {
|
|
50
49
|
basePiToolCandidates,
|
|
51
50
|
memoryPiToolCandidate,
|
|
@@ -122,8 +121,7 @@ export interface RuntimeSnapshot {
|
|
|
122
121
|
export interface RuntimeCandidate {
|
|
123
122
|
readonly snapshot: RuntimeSnapshot;
|
|
124
123
|
readonly commitGuards: readonly (() => Promise<void>)[];
|
|
125
|
-
readonly
|
|
126
|
-
readonly turnEvents?: RuntimeTurnEventsPort;
|
|
124
|
+
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
127
125
|
readonly telemetry?: AgentTelemetryBinding;
|
|
128
126
|
}
|
|
129
127
|
|
|
@@ -690,110 +688,34 @@ export async function assembleRuntime(
|
|
|
690
688
|
commit(candidate.snapshot);
|
|
691
689
|
}
|
|
692
690
|
|
|
693
|
-
function hooksToTurnEventsPort<
|
|
694
|
-
Env extends Cloudflare.Env,
|
|
695
|
-
Config,
|
|
696
|
-
Command,
|
|
697
|
-
Change,
|
|
698
|
-
>(
|
|
699
|
-
ctx: AssembleRuntimeSnapshotInput<Env, Config, Command, Change>["ctx"],
|
|
700
|
-
hooks?: RuntimeAgentHooks<Env, Config, Command, Change>,
|
|
701
|
-
): RuntimeTurnEventsPort | undefined {
|
|
702
|
-
if (!hooks) return undefined;
|
|
703
|
-
if (
|
|
704
|
-
!hooks.onTurnEnd &&
|
|
705
|
-
!hooks.onModelUsage &&
|
|
706
|
-
!hooks.onSubagentUsage &&
|
|
707
|
-
!hooks.onLifecycleFact &&
|
|
708
|
-
!hooks.onToolStart &&
|
|
709
|
-
!hooks.onToolSettled &&
|
|
710
|
-
!hooks.onApproval &&
|
|
711
|
-
!hooks.onActivityChanged &&
|
|
712
|
-
!hooks.onSubmissionTerminal
|
|
713
|
-
) {
|
|
714
|
-
return undefined;
|
|
715
|
-
}
|
|
716
|
-
return {
|
|
717
|
-
onResponse: hooks.onTurnEnd
|
|
718
|
-
? (messages) => hooks.onTurnEnd!(ctx, messages)
|
|
719
|
-
: async () => undefined,
|
|
720
|
-
...(hooks.onModelUsage ? { onModelUsage: hooks.onModelUsage } : {}),
|
|
721
|
-
...(hooks.onSubagentUsage ? { onSubagentUsage: hooks.onSubagentUsage } : {}),
|
|
722
|
-
...(hooks.onLifecycleFact ? { onLifecycleFact: hooks.onLifecycleFact } : {}),
|
|
723
|
-
...(hooks.onToolStart ? { onToolStart: hooks.onToolStart } : {}),
|
|
724
|
-
...(hooks.onToolSettled ? { onToolSettled: hooks.onToolSettled } : {}),
|
|
725
|
-
...(hooks.onApproval ? { onApproval: hooks.onApproval } : {}),
|
|
726
|
-
...(hooks.onActivityChanged
|
|
727
|
-
? { onActivityChanged: hooks.onActivityChanged }
|
|
728
|
-
: {}),
|
|
729
|
-
...(hooks.onSubmissionTerminal
|
|
730
|
-
? { onSubmissionTerminal: hooks.onSubmissionTerminal }
|
|
731
|
-
: {}),
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
|
|
735
691
|
/**
|
|
736
|
-
* 从
|
|
692
|
+
* 从 Tool 和已加载 Resource 装配 Runtime 候选 Snapshot。
|
|
737
693
|
*
|
|
738
694
|
* @internal
|
|
739
695
|
*/
|
|
740
696
|
export async function assembleRuntimeSnapshot<
|
|
741
697
|
Env extends Cloudflare.Env,
|
|
742
|
-
Config,
|
|
743
698
|
Command = never,
|
|
744
699
|
Change = never,
|
|
745
700
|
>(
|
|
746
|
-
input: AssembleRuntimeSnapshotInput<Env,
|
|
701
|
+
input: AssembleRuntimeSnapshotInput<Env, Command, Change>,
|
|
747
702
|
): Promise<RuntimeCandidate> {
|
|
748
703
|
const resources = input.resources;
|
|
749
704
|
const toolAssembly = input.tools;
|
|
750
705
|
const settings = resources.settings;
|
|
751
706
|
const provider = resources.provider;
|
|
752
|
-
|
|
753
|
-
if (input.hooks?.profileOverrides) {
|
|
754
|
-
const overrides = await withRuntimeLoadTimeout(
|
|
755
|
-
"profile-overrides",
|
|
756
|
-
() => input.hooks!.profileOverrides!(
|
|
757
|
-
input.ctx,
|
|
758
|
-
input.config,
|
|
759
|
-
),
|
|
760
|
-
);
|
|
761
|
-
if (overrides.systemPrompt !== undefined) {
|
|
762
|
-
profile = { ...profile, systemPrompt: overrides.systemPrompt };
|
|
763
|
-
}
|
|
764
|
-
if (overrides.executionLevel !== undefined) {
|
|
765
|
-
profile = { ...profile, executionLevel: overrides.executionLevel };
|
|
766
|
-
}
|
|
767
|
-
}
|
|
707
|
+
const profile = settings.profile;
|
|
768
708
|
|
|
769
709
|
const hostTools = toolAssembly.tools;
|
|
770
710
|
|
|
771
711
|
const commitGuards: Array<() => Promise<void>> = [];
|
|
772
712
|
if (toolAssembly.commitGuard) commitGuards.push(toolAssembly.commitGuard);
|
|
773
|
-
if (input.
|
|
774
|
-
for (const guard of input.hooks.beforeCommit) {
|
|
775
|
-
commitGuards.push(() => guard(input.ctx, input.config));
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const platform: RuntimePlatformPort = input.hooks?.gateTool
|
|
780
|
-
? Object.freeze({
|
|
781
|
-
...resources.platform,
|
|
782
|
-
gateTool: (request: {
|
|
783
|
-
toolCallId: string;
|
|
784
|
-
toolName: string;
|
|
785
|
-
input: unknown;
|
|
786
|
-
requiredExecutionLevel: import("./lib/execution-level").ExecutionLevel;
|
|
787
|
-
signal: AbortSignal;
|
|
788
|
-
}) => input.hooks!.gateTool!(request),
|
|
789
|
-
})
|
|
790
|
-
: resources.platform;
|
|
713
|
+
if (input.commitGuard) commitGuards.push(input.commitGuard);
|
|
791
714
|
|
|
792
715
|
const memoryProfile =
|
|
793
716
|
toolAssembly.memoryProfile ?? settings.memoryProfile;
|
|
794
717
|
const enabledSubagents =
|
|
795
718
|
toolAssembly.enabledSubagents ?? settings.enabledSubagents;
|
|
796
|
-
const turnEvents = hooksToTurnEventsPort(input.ctx, input.hooks);
|
|
797
719
|
|
|
798
720
|
const toolPolicy: RuntimeToolSurfacePolicy | undefined =
|
|
799
721
|
settings.toolPolicy || toolAssembly.surfacePolicy
|
|
@@ -828,7 +750,7 @@ export async function assembleRuntimeSnapshot<
|
|
|
828
750
|
...(toolPolicy ? { toolPolicy } : {}),
|
|
829
751
|
enabledSubagents: [...enabledSubagents],
|
|
830
752
|
provider,
|
|
831
|
-
platform,
|
|
753
|
+
platform: resources.platform,
|
|
832
754
|
...(toolAssembly.bindings?.workspace
|
|
833
755
|
? { workspace: toolAssembly.bindings.workspace }
|
|
834
756
|
: {}),
|
|
@@ -851,9 +773,9 @@ export async function assembleRuntimeSnapshot<
|
|
|
851
773
|
? { gateway: resources.connectors.gateway }
|
|
852
774
|
: {}),
|
|
853
775
|
extensions: resources.extensions,
|
|
854
|
-
...(turnEvents ? { turnEvents } : {}),
|
|
776
|
+
...(resources.turnEvents ? { turnEvents: resources.turnEvents } : {}),
|
|
855
777
|
...(input.telemetry ? { telemetry: input.telemetry } : {}),
|
|
856
|
-
commitGuards
|
|
778
|
+
commitGuards,
|
|
857
779
|
degradations: [
|
|
858
780
|
...(toolAssembly.degradations ?? []),
|
|
859
781
|
...(resources.degradations ?? []),
|
|
@@ -863,22 +785,8 @@ export async function assembleRuntimeSnapshot<
|
|
|
863
785
|
|
|
864
786
|
const candidate = await prepareRuntimeCandidate(assemblyInput);
|
|
865
787
|
return Object.freeze({
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
...candidate.commitGuards,
|
|
869
|
-
...commitGuards,
|
|
870
|
-
]),
|
|
871
|
-
...(input.hooks
|
|
872
|
-
? {
|
|
873
|
-
hooks: input.hooks as unknown as RuntimeAgentHooks<
|
|
874
|
-
Cloudflare.Env,
|
|
875
|
-
unknown,
|
|
876
|
-
never,
|
|
877
|
-
never
|
|
878
|
-
>,
|
|
879
|
-
}
|
|
880
|
-
: {}),
|
|
881
|
-
...(turnEvents ? { turnEvents } : {}),
|
|
788
|
+
...candidate,
|
|
789
|
+
...(resources.admission ? { admission: resources.admission } : {}),
|
|
882
790
|
...(candidate.telemetry ? { telemetry: candidate.telemetry } : {}),
|
|
883
791
|
});
|
|
884
792
|
}
|
|
@@ -2,16 +2,11 @@ import type { SkillSource } from "agents/skills";
|
|
|
2
2
|
import type { ExecutionLevel } from "./lib/execution-level";
|
|
3
3
|
import type {
|
|
4
4
|
RuntimeGatewayPort,
|
|
5
|
-
RuntimeModelUsageEvent,
|
|
6
|
-
RuntimeSubmissionAdmissionHook,
|
|
7
|
-
RuntimeSubagentUsageEvent,
|
|
8
|
-
RuntimeLifecycleFact,
|
|
9
|
-
RuntimeEventConfirmation,
|
|
10
5
|
RuntimePlatformPort,
|
|
11
6
|
RuntimeProviderPort,
|
|
12
7
|
RuntimeSkillScriptPolicy,
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
RuntimeSubmissionAdmissionHook,
|
|
9
|
+
RuntimeTurnEventsPort,
|
|
15
10
|
} from "./kernel/bindings";
|
|
16
11
|
import type {
|
|
17
12
|
RuntimeDenyPolicy,
|
|
@@ -20,12 +15,10 @@ import type {
|
|
|
20
15
|
ThinkingEffort,
|
|
21
16
|
} from "./kernel/profile";
|
|
22
17
|
import type { RuntimeExtensionConfig } from "./kernel/extensions";
|
|
23
|
-
import type { RuntimeActivityProjection } from "./kernel/state";
|
|
24
18
|
import type { RuntimeDegradation } from "./kernel/degradation";
|
|
25
19
|
import type { PiToolCandidate } from "./pi/tool/compiler";
|
|
26
20
|
import type {
|
|
27
21
|
RuntimeAgentContext,
|
|
28
|
-
RuntimeAssemblyContext,
|
|
29
22
|
RuntimeAgentPlanningContext,
|
|
30
23
|
} from "./runtime-agent-context";
|
|
31
24
|
import type { AgentTelemetryBinding } from "./telemetry/contract";
|
|
@@ -83,14 +76,6 @@ export interface RuntimeToolSurfacePolicy extends ToolSurfaceSelectionPolicy {
|
|
|
83
76
|
|
|
84
77
|
export type { RuntimeAgentContext, RuntimeAssemblyContext } from "./runtime-agent-context";
|
|
85
78
|
|
|
86
|
-
export interface GateToolRequest {
|
|
87
|
-
readonly toolCallId: string;
|
|
88
|
-
readonly toolName: string;
|
|
89
|
-
readonly input: unknown;
|
|
90
|
-
readonly requiredExecutionLevel: ExecutionLevel;
|
|
91
|
-
readonly signal: AbortSignal;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
79
|
export interface ResolvedConnectors {
|
|
95
80
|
readonly servers: readonly RuntimeMcpServer[];
|
|
96
81
|
readonly gateway?: RuntimeGatewayPort;
|
|
@@ -101,81 +86,14 @@ export interface ResolvedResources {
|
|
|
101
86
|
readonly settings: RuntimeSettings;
|
|
102
87
|
readonly provider: RuntimeProviderPort;
|
|
103
88
|
readonly platform: RuntimePlatformPort;
|
|
89
|
+
readonly turnEvents?: RuntimeTurnEventsPort;
|
|
90
|
+
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
104
91
|
readonly skills: readonly RuntimeSkillContribution[];
|
|
105
92
|
readonly extensions: readonly RuntimeExtensionContribution[];
|
|
106
93
|
readonly connectors: ResolvedConnectors;
|
|
107
94
|
readonly degradations?: readonly RuntimeDegradation[];
|
|
108
95
|
}
|
|
109
96
|
|
|
110
|
-
export interface ProfileOverrides {
|
|
111
|
-
readonly systemPrompt?: string;
|
|
112
|
-
readonly executionLevel?: ExecutionLevel;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface ApprovalHookInput {
|
|
116
|
-
readonly submissionId: string;
|
|
117
|
-
readonly approvalExecutionId: string;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface SubmissionTerminalHookInput {
|
|
121
|
-
readonly submissionId: string;
|
|
122
|
-
readonly status: "completed" | "aborted" | "skipped" | "error";
|
|
123
|
-
readonly error?: string;
|
|
124
|
-
readonly resultMessageId?: string;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface RuntimeToolSettlementEvent {
|
|
128
|
-
readonly eventId: string;
|
|
129
|
-
readonly submissionId: string;
|
|
130
|
-
readonly toolCallId: string;
|
|
131
|
-
readonly toolName: string;
|
|
132
|
-
readonly status: "success" | "error";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface RuntimeAgentHooks<
|
|
136
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
137
|
-
Config = unknown,
|
|
138
|
-
Command = never,
|
|
139
|
-
Change = never,
|
|
140
|
-
> {
|
|
141
|
-
readonly beforeCommit?: ReadonlyArray<
|
|
142
|
-
(
|
|
143
|
-
ctx: RuntimeAssemblyContext<Env, Command, Change>,
|
|
144
|
-
config: Config,
|
|
145
|
-
) => Promise<void>
|
|
146
|
-
>;
|
|
147
|
-
|
|
148
|
-
readonly onTurnEnd?: (
|
|
149
|
-
ctx: RuntimeAssemblyContext<Env, Command, Change>,
|
|
150
|
-
messages: readonly RuntimeTurnMessage[],
|
|
151
|
-
) => Promise<void>;
|
|
152
|
-
readonly onModelUsage?: (event: RuntimeModelUsageEvent) => Promise<void>;
|
|
153
|
-
readonly onSubagentUsage?: (
|
|
154
|
-
event: RuntimeSubagentUsageEvent,
|
|
155
|
-
) => Promise<RuntimeEventConfirmation | void>;
|
|
156
|
-
readonly onLifecycleFact?: (
|
|
157
|
-
event: RuntimeLifecycleFact,
|
|
158
|
-
) => Promise<RuntimeEventConfirmation | void>;
|
|
159
|
-
/** Single admission seam shared by chat, RPC, regenerate and scheduled submissions. */
|
|
160
|
-
readonly onSubmissionAdmission?: RuntimeSubmissionAdmissionHook;
|
|
161
|
-
readonly onToolStart?: (event: RuntimeToolStartEvent) => Promise<void>;
|
|
162
|
-
readonly onToolSettled?: (event: RuntimeToolSettlementEvent) => Promise<void>;
|
|
163
|
-
readonly onApproval?: (input: ApprovalHookInput) => Promise<void>;
|
|
164
|
-
readonly onActivityChanged?: (
|
|
165
|
-
projection: RuntimeActivityProjection,
|
|
166
|
-
) => Promise<void>;
|
|
167
|
-
readonly onSubmissionTerminal?: (
|
|
168
|
-
input: SubmissionTerminalHookInput,
|
|
169
|
-
) => Promise<void>;
|
|
170
|
-
|
|
171
|
-
readonly gateTool?: (request: GateToolRequest) => Promise<void>;
|
|
172
|
-
|
|
173
|
-
readonly profileOverrides?: (
|
|
174
|
-
ctx: RuntimeAssemblyContext<Env, Command, Change>,
|
|
175
|
-
config: Config,
|
|
176
|
-
) => ProfileOverrides | Promise<ProfileOverrides>;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
97
|
export interface RuntimeSettings {
|
|
180
98
|
readonly profile: RuntimeProfileContribution;
|
|
181
99
|
readonly memoryProfile: RuntimeMemoryProfile;
|
|
@@ -185,14 +103,12 @@ export interface RuntimeSettings {
|
|
|
185
103
|
|
|
186
104
|
export interface AssembleRuntimeSnapshotInput<
|
|
187
105
|
Env extends Cloudflare.Env,
|
|
188
|
-
Config,
|
|
189
106
|
Command = never,
|
|
190
107
|
Change = never,
|
|
191
108
|
> {
|
|
192
109
|
readonly ctx: RuntimeAgentPlanningContext<Env, Command, Change>;
|
|
193
|
-
readonly config: Config;
|
|
194
110
|
readonly tools: ToolAssemblyResult;
|
|
195
111
|
readonly resources: ResolvedResources;
|
|
196
|
-
readonly
|
|
112
|
+
readonly commitGuard?: () => Promise<void>;
|
|
197
113
|
readonly telemetry?: AgentTelemetryBinding;
|
|
198
114
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -36,7 +36,11 @@ import {
|
|
|
36
36
|
type SubmissionReceipt,
|
|
37
37
|
} from "./kernel/receipts";
|
|
38
38
|
import type { ExecutionLevel } from "./lib/execution-level";
|
|
39
|
-
import type {
|
|
39
|
+
import type {
|
|
40
|
+
RuntimeGatewaySession,
|
|
41
|
+
RuntimeSubmissionAdmissionHook,
|
|
42
|
+
RuntimeTurnEventsPort,
|
|
43
|
+
} from "./kernel/bindings";
|
|
40
44
|
import type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
|
|
41
45
|
import { projectRuntimeAssembly } from "./kernel/runtime-assembly";
|
|
42
46
|
import type {
|
|
@@ -57,8 +61,6 @@ import {
|
|
|
57
61
|
type RuntimeCandidate,
|
|
58
62
|
type RuntimeSnapshot,
|
|
59
63
|
} from "./runtime-assembler";
|
|
60
|
-
import type { RuntimeAgentHooks } from "./runtime-definition";
|
|
61
|
-
import type { RuntimeTurnEventsPort } from "./kernel/bindings";
|
|
62
64
|
import {
|
|
63
65
|
RuntimeTelemetryCoordinator,
|
|
64
66
|
} from "./telemetry";
|
|
@@ -199,6 +201,14 @@ interface ActiveTurn {
|
|
|
199
201
|
agent: PreparedPiTurnAdapter;
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
interface ActiveRuntime {
|
|
205
|
+
readonly snapshot: RuntimeSnapshot;
|
|
206
|
+
readonly revision: string;
|
|
207
|
+
readonly pi: PreparedPiRuntime;
|
|
208
|
+
readonly gatewaySession?: RuntimeGatewaySession;
|
|
209
|
+
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
210
|
+
}
|
|
211
|
+
|
|
202
212
|
interface PlannedContinuationData {
|
|
203
213
|
readonly submissionId: string;
|
|
204
214
|
readonly requestId: string;
|
|
@@ -414,12 +424,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
414
424
|
|
|
415
425
|
protected abstract ensureRuntimeReady(): Promise<void>;
|
|
416
426
|
|
|
417
|
-
private
|
|
418
|
-
private runtimeRevision?: string;
|
|
419
|
-
private runtimePi?: PreparedPiRuntime;
|
|
420
|
-
private runtimeGatewaySession?: RuntimeGatewaySession;
|
|
421
|
-
private runtimeTurnEvents?: RuntimeTurnEventsPort;
|
|
422
|
-
private runtimeHooks?: RuntimeAgentHooks;
|
|
427
|
+
private activeRuntime?: ActiveRuntime;
|
|
423
428
|
private telemetryCoordinator?: RuntimeTelemetryCoordinator;
|
|
424
429
|
private piAdapter?: PiRuntimeAdapter;
|
|
425
430
|
private readonly transcript: PiRuntimeTranscript;
|
|
@@ -464,7 +469,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
464
469
|
return this.runtimeLoadTracker ??= new RuntimeLoadTracker({
|
|
465
470
|
read: () => this.state.runtimeLoad,
|
|
466
471
|
publish: (runtimeLoad) => this.setState({ ...this.state, runtimeLoad }),
|
|
467
|
-
isAvailable: () => Boolean(this.
|
|
472
|
+
isAvailable: () => Boolean(this.activeRuntime),
|
|
468
473
|
});
|
|
469
474
|
}
|
|
470
475
|
|
|
@@ -715,14 +720,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
715
720
|
}
|
|
716
721
|
|
|
717
722
|
private turnEventsPort(): RuntimeTurnEventsPort | undefined {
|
|
718
|
-
return this.
|
|
723
|
+
return this.activeRuntime?.snapshot.bindings.turnEvents;
|
|
719
724
|
}
|
|
720
725
|
|
|
721
726
|
protected async initCandidate(candidate: RuntimeCandidate): Promise<void> {
|
|
722
|
-
this.
|
|
723
|
-
candidate.turnEvents ?? candidate.snapshot.bindings.turnEvents;
|
|
724
|
-
this.runtimeHooks = candidate.hooks;
|
|
725
|
-
const previous = this.runtimeSnapshot;
|
|
727
|
+
const previous = this.activeRuntime;
|
|
726
728
|
const candidateSnapshot = candidate.snapshot;
|
|
727
729
|
this.runtimeLoad.advance("assembly");
|
|
728
730
|
this.runtimeLoad.advance("mcp");
|
|
@@ -776,7 +778,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
776
778
|
// 没有任何工具在执行 —— 换装配对它是安全的,而且这正是「决定时才装上
|
|
777
779
|
// 新能力,同一轮接着跑」所依赖的那一步。真正在执行的 Turn 仍然被挡住。
|
|
778
780
|
const contested = Boolean(previous) &&
|
|
779
|
-
revision !==
|
|
781
|
+
revision !== previous?.revision &&
|
|
780
782
|
this.submissions.isBusy();
|
|
781
783
|
const parked = contested && this.db.everyUnfinishedSubmissionParked();
|
|
782
784
|
if (contested && !parked) {
|
|
@@ -791,8 +793,17 @@ export abstract class AgentRuntimeKernel<
|
|
|
791
793
|
() => this.prepareParkedSubmissionRepin(prepared, revision),
|
|
792
794
|
)
|
|
793
795
|
: undefined;
|
|
796
|
+
const next = Object.freeze({
|
|
797
|
+
snapshot: candidateSnapshot,
|
|
798
|
+
revision,
|
|
799
|
+
pi: prepared,
|
|
800
|
+
...(gatewaySession ? { gatewaySession } : {}),
|
|
801
|
+
...(candidate.admission
|
|
802
|
+
? { admission: candidate.admission }
|
|
803
|
+
: {}),
|
|
804
|
+
}) satisfies ActiveRuntime;
|
|
794
805
|
let activated = false;
|
|
795
|
-
const previousGatewaySession =
|
|
806
|
+
const previousGatewaySession = previous?.gatewaySession;
|
|
796
807
|
try {
|
|
797
808
|
for (const [index, guard] of candidate.commitGuards.entries()) {
|
|
798
809
|
await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
|
|
@@ -800,14 +811,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
800
811
|
this.pi.activate(candidateSnapshot);
|
|
801
812
|
activated = true;
|
|
802
813
|
repin?.commit();
|
|
803
|
-
this.runtimeSnapshot = candidateSnapshot;
|
|
804
|
-
this.runtimeRevision = revision;
|
|
805
|
-
this.runtimePi = prepared;
|
|
806
|
-
this.runtimeGatewaySession = gatewaySession;
|
|
807
814
|
this.telemetry.configure(candidate.telemetry);
|
|
815
|
+
this.activeRuntime = next;
|
|
808
816
|
} catch (error) {
|
|
809
817
|
repin?.abort();
|
|
810
|
-
if (activated && previous) this.pi.activate(previous);
|
|
818
|
+
if (activated && previous) this.pi.activate(previous.snapshot);
|
|
811
819
|
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.aborted");
|
|
812
820
|
throw error;
|
|
813
821
|
}
|
|
@@ -1101,22 +1109,22 @@ export abstract class AgentRuntimeKernel<
|
|
|
1101
1109
|
try {
|
|
1102
1110
|
const payload = JSON.parse(row.body) as RuntimeEventOutboxPayload;
|
|
1103
1111
|
if (payload.type === "model-usage") {
|
|
1104
|
-
|
|
1105
|
-
await turnEvents.onModelUsage(payload.event);
|
|
1112
|
+
await turnEvents.onModelUsage?.(payload.event);
|
|
1106
1113
|
} else if (payload.type === "tool-settlement") {
|
|
1107
|
-
|
|
1108
|
-
await turnEvents.onToolSettled(payload.event);
|
|
1114
|
+
await turnEvents.onToolSettled?.(payload.event);
|
|
1109
1115
|
} else if (payload.type === "subagent-usage") {
|
|
1110
|
-
if (
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1116
|
+
if (turnEvents.onSubagentUsage) {
|
|
1117
|
+
const confirmation = await turnEvents.onSubagentUsage(payload.event);
|
|
1118
|
+
if (confirmation?.confirmed !== true) {
|
|
1119
|
+
throw new Error("Runtime event was not confirmed");
|
|
1120
|
+
}
|
|
1114
1121
|
}
|
|
1115
1122
|
} else {
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1123
|
+
if (turnEvents.onLifecycleFact) {
|
|
1124
|
+
const confirmation = await turnEvents.onLifecycleFact(payload.event);
|
|
1125
|
+
if (confirmation?.confirmed !== true) {
|
|
1126
|
+
throw new Error("Runtime event was not confirmed");
|
|
1127
|
+
}
|
|
1120
1128
|
}
|
|
1121
1129
|
}
|
|
1122
1130
|
this.db.transaction(() =>
|
|
@@ -1320,12 +1328,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1320
1328
|
// 调用:所有需要模型、工作区、扩展或投影绑定的运行时路径调用。
|
|
1321
1329
|
// 原因:未初始化时立即失败,比在深层路径触发空值更容易定位。
|
|
1322
1330
|
private assembly(): RuntimeSnapshot {
|
|
1323
|
-
if (!this.
|
|
1331
|
+
if (!this.activeRuntime) {
|
|
1324
1332
|
throw new Error(
|
|
1325
1333
|
"Runtime must be assembled before use",
|
|
1326
1334
|
);
|
|
1327
1335
|
}
|
|
1328
|
-
return this.
|
|
1336
|
+
return this.activeRuntime.snapshot;
|
|
1329
1337
|
}
|
|
1330
1338
|
|
|
1331
1339
|
private async normalizeUIUserInput(
|
|
@@ -1377,10 +1385,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
1377
1385
|
// 调用:Session facet 的 `getRuntimeAssembly` RPC。
|
|
1378
1386
|
// 原因:UI 需要读取真实装配结果,而不是上层配置声明。
|
|
1379
1387
|
protected readRuntimeAssembly(): RuntimeAssemblyView {
|
|
1388
|
+
const runtime = this.activeRuntime;
|
|
1389
|
+
if (!runtime) {
|
|
1390
|
+
throw new Error("Runtime must be assembled before use");
|
|
1391
|
+
}
|
|
1380
1392
|
return projectRuntimeAssembly(
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1393
|
+
runtime.snapshot,
|
|
1394
|
+
runtime.revision,
|
|
1395
|
+
runtime.pi,
|
|
1384
1396
|
);
|
|
1385
1397
|
}
|
|
1386
1398
|
|
|
@@ -1442,12 +1454,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1442
1454
|
}
|
|
1443
1455
|
|
|
1444
1456
|
private preparedPi(): PreparedPiRuntime {
|
|
1445
|
-
if (!this.
|
|
1457
|
+
if (!this.activeRuntime) {
|
|
1446
1458
|
throw new Error(
|
|
1447
1459
|
"SpringBrand Runtime must be prepared before starting a Turn",
|
|
1448
1460
|
);
|
|
1449
1461
|
}
|
|
1450
|
-
return this.
|
|
1462
|
+
return this.activeRuntime.pi;
|
|
1451
1463
|
}
|
|
1452
1464
|
|
|
1453
1465
|
// #endregion
|
|
@@ -1474,10 +1486,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
1474
1486
|
descriptor: string;
|
|
1475
1487
|
}> {
|
|
1476
1488
|
await this.drainRuntimeEvents();
|
|
1477
|
-
|
|
1489
|
+
const runtime = this.activeRuntime;
|
|
1490
|
+
if (!runtime) {
|
|
1478
1491
|
throw new Error("Runtime revision is not initialized");
|
|
1479
1492
|
}
|
|
1480
|
-
return this.pinAssembly(
|
|
1493
|
+
return this.pinAssembly(runtime.pi, runtime.revision);
|
|
1481
1494
|
}
|
|
1482
1495
|
|
|
1483
1496
|
private async pinAssembly(
|
|
@@ -1731,7 +1744,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1731
1744
|
"Regenerate request must match an existing user message",
|
|
1732
1745
|
);
|
|
1733
1746
|
}
|
|
1734
|
-
const hook = this.
|
|
1747
|
+
const hook = this.activeRuntime?.admission;
|
|
1735
1748
|
let existing: StoredSubmissionAdmission | null = null;
|
|
1736
1749
|
if (hook) {
|
|
1737
1750
|
const attemptedAt = Date.now();
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RuntimeSubmissionAdmissionHook,
|
|
3
|
-
RuntimeTurnEventsPort,
|
|
4
|
-
} from "../../../kernel/bindings";
|
|
5
|
-
import type { RuntimeAgentHooks } from "../../../runtime-definition";
|
|
6
|
-
|
|
7
|
-
export function createUniversalAgentHooks<
|
|
8
|
-
Env extends Cloudflare.Env,
|
|
9
|
-
Config,
|
|
10
|
-
Command = never,
|
|
11
|
-
Change = never,
|
|
12
|
-
>(adapter: {
|
|
13
|
-
guard(config: Config): Promise<void>;
|
|
14
|
-
turnEvents(): RuntimeTurnEventsPort | Promise<RuntimeTurnEventsPort>;
|
|
15
|
-
onSubmissionAdmission?: RuntimeSubmissionAdmissionHook;
|
|
16
|
-
}): RuntimeAgentHooks<Env, Config, Command, Change> {
|
|
17
|
-
const turnEvents = () => Promise.resolve(adapter.turnEvents());
|
|
18
|
-
return {
|
|
19
|
-
beforeCommit: [async (_context, config) => adapter.guard(config)],
|
|
20
|
-
onTurnEnd: async (_context, messages) => {
|
|
21
|
-
await (await turnEvents()).onResponse(messages);
|
|
22
|
-
},
|
|
23
|
-
onModelUsage: async (event) => {
|
|
24
|
-
await (await turnEvents()).onModelUsage?.(event);
|
|
25
|
-
},
|
|
26
|
-
...(adapter.onSubmissionAdmission
|
|
27
|
-
? { onSubmissionAdmission: adapter.onSubmissionAdmission }
|
|
28
|
-
: {}),
|
|
29
|
-
// 一个不订阅这两类事实的 Host 不能把 outbox 堵住:Runtime 只有拿到确认才
|
|
30
|
-
// 会标记投递,而 `undefined` 表示"没投出去"。因此没有 Host 实现时这里就地确认,
|
|
31
|
-
// 语义等同于旧版本"Host 不关心就丢弃",而不是让整条队列在队头无限重试。
|
|
32
|
-
onSubagentUsage: async (event) => {
|
|
33
|
-
const port = await turnEvents();
|
|
34
|
-
return port.onSubagentUsage
|
|
35
|
-
? port.onSubagentUsage(event)
|
|
36
|
-
: { confirmed: true };
|
|
37
|
-
},
|
|
38
|
-
onLifecycleFact: async (event) => {
|
|
39
|
-
const port = await turnEvents();
|
|
40
|
-
return port.onLifecycleFact
|
|
41
|
-
? port.onLifecycleFact(event)
|
|
42
|
-
: { confirmed: true };
|
|
43
|
-
},
|
|
44
|
-
onToolSettled: async (event) => {
|
|
45
|
-
await (await turnEvents()).onToolSettled?.(event);
|
|
46
|
-
},
|
|
47
|
-
onApproval: async (input) => {
|
|
48
|
-
await (await turnEvents()).onApproval?.(input);
|
|
49
|
-
},
|
|
50
|
-
onActivityChanged: async (projection) => {
|
|
51
|
-
await (await turnEvents()).onActivityChanged?.(projection);
|
|
52
|
-
},
|
|
53
|
-
onSubmissionTerminal: async (input) => {
|
|
54
|
-
await (await turnEvents()).onSubmissionTerminal?.(input);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|