@springbrand/agent-runtime 0.1.3-alpha.1 → 0.1.3-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 +12 -3
- package/src/adapter/cloudflare/index.ts +56 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +1513 -0
- package/src/adapter/cloudflare/sandbox/id.ts +23 -0
- package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +574 -0
- package/src/adapter/cloudflare/subagent/runner.ts +175 -0
- package/src/adapter/cloudflare/subagent/tools.ts +254 -0
- package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
- package/src/adapter/cloudflare/universal-agent/preparation.ts +277 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +80 -0
- package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
- package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
- package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
- package/src/agent-tool-runtime.ts +152 -0
- package/src/db/agent-tool.repo.ts +27 -0
- package/src/db/index.ts +33 -0
- package/src/db/interaction.repo.ts +185 -0
- package/src/db/schema.ts +25 -1
- package/src/db/submission.repo.ts +63 -1
- package/src/index.ts +57 -21
- package/src/kernel/approval-lifecycle.ts +41 -6
- package/src/kernel/bindings.ts +73 -9
- package/src/kernel/interaction-lifecycle.ts +395 -0
- package/src/kernel/public-contracts.ts +2 -0
- package/src/kernel/recoverable-chat-agent.ts +104 -6
- package/src/kernel/runtime-assembly-view.ts +37 -0
- package/src/kernel/runtime-assembly.ts +41 -0
- package/src/kernel/runtime-config.ts +4 -0
- package/src/kernel/runtime-load.ts +191 -0
- package/src/kernel/state.ts +12 -1
- package/src/kernel/submission-lifecycle.ts +33 -2
- package/src/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
- package/src/lib/mcp.ts +7 -3
- package/src/lib/prompt.ts +1 -1
- package/src/lib/telemetry-dev.ts +7 -4
- package/src/pi/assembly/context.ts +3 -3
- package/src/pi/assembly/extensions.ts +11 -22
- package/src/pi/assembly/snapshot.ts +6 -3
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +26 -31
- package/src/pi/runtime-adapter/execution.ts +198 -15
- package/src/pi/runtime-adapter/index.ts +24 -8
- package/src/pi/runtime-adapter/models.ts +382 -35
- package/src/pi/runtime-adapter/recovery.ts +188 -1
- package/src/pi/runtime-adapter/transcript.ts +61 -3
- package/src/pi/tool/ai-adapter.ts +58 -1
- package/src/pi/tool/base.ts +190 -12
- package/src/pi/tool/compiler.ts +34 -1
- package/src/pi/tool/core-host.ts +19 -24
- package/src/pi/tool/core.ts +30 -120
- package/src/pi/tool/gateway.ts +54 -0
- package/src/pi/tool/index.ts +2 -0
- package/src/pi/tool/mcp.ts +96 -68
- package/src/pi/tool/schedule.ts +41 -19
- package/src/pi/tool/skill.ts +126 -420
- package/src/pi/tool/subagent.ts +14 -2
- package/src/pi/tool/web-fetch.ts +281 -0
- package/src/pi/tool/web-search/api.ts +34 -18
- package/src/pi/tool/web-search/web-search.ts +0 -1
- package/src/pi/tool/workspace-revision.ts +64 -0
- package/src/pi/tool/workspace-sandbox.ts +105 -263
- package/src/pi/turn/index.ts +20 -0
- package/src/pi/turn/interaction.ts +181 -0
- package/src/pi/turn/tool-recovery.ts +244 -1
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +568 -321
- package/src/{plugins.ts → runtime-assembler.ts} +372 -398
- package/src/runtime-definition.ts +175 -0
- package/src/runtime.ts +835 -204
- package/src/tool-registry.ts +143 -0
- package/src/workspace-versioning.ts +46 -0
|
@@ -73,15 +73,15 @@ export type RuntimeRecoveryDecision<Data> =
|
|
|
73
73
|
| { readonly kind: "ignore"; readonly reason: string };
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* 记录一次已执行恢复的结果,或表明该恢复已接力给下一次持久调度。
|
|
77
77
|
*
|
|
78
78
|
* @remarks
|
|
79
|
-
* `_chatRecoveryRetry` 在调用端口的 `retry`
|
|
79
|
+
* `_chatRecoveryRetry` 在调用端口的 `retry` 后读取该结果;终态关闭事故,`scheduled` 保持事故活跃。
|
|
80
80
|
*
|
|
81
81
|
* 只有失败时才需要 `error`;结果不承载流内容,避免在调度数据与流缓冲区之间复制状态。
|
|
82
82
|
*/
|
|
83
83
|
export interface RuntimeRecoveryResult {
|
|
84
|
-
readonly status: "completed" | "failed";
|
|
84
|
+
readonly status: "completed" | "scheduled" | "failed";
|
|
85
85
|
readonly error?: string;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -103,7 +103,7 @@ export interface RuntimeRecoveryPort<Data> {
|
|
|
103
103
|
/**
|
|
104
104
|
* 使用已分类的业务数据重放一次对话。
|
|
105
105
|
*
|
|
106
|
-
* @remarks
|
|
106
|
+
* @remarks 持久调度回调会调用它;实现应返回权威终态,或明确说明下一次恢复已经持久调度。
|
|
107
107
|
*/
|
|
108
108
|
retry(data: Data): Promise<RuntimeRecoveryResult>;
|
|
109
109
|
/**
|
|
@@ -353,7 +353,13 @@ export abstract class RecoverableChatAgent<
|
|
|
353
353
|
* 清理延后执行,为断线重连的客户端保留终态流片段。
|
|
354
354
|
*/
|
|
355
355
|
protected completeRecoverableStream(streamId: string): void {
|
|
356
|
+
const completedRequestId = this.resumableStream.activeRequestId;
|
|
356
357
|
this.resumableStream.complete(streamId);
|
|
358
|
+
this.pendingResumeConnections.clear();
|
|
359
|
+
if (completedRequestId === this.continuation.activeRequestId) {
|
|
360
|
+
this.continuation.activeRequestId = null;
|
|
361
|
+
this.continuation.activeConnectionId = null;
|
|
362
|
+
}
|
|
357
363
|
void this.ensureStreamCleanupScheduled();
|
|
358
364
|
}
|
|
359
365
|
|
|
@@ -366,7 +372,13 @@ export abstract class RecoverableChatAgent<
|
|
|
366
372
|
* 流错误标志与业务失败分开,因为续传协议和提交生命周期的责任不同。
|
|
367
373
|
*/
|
|
368
374
|
protected failRecoverableStream(streamId: string): void {
|
|
375
|
+
const erroredRequestId = this.resumableStream.activeRequestId;
|
|
369
376
|
this.resumableStream.markError(streamId);
|
|
377
|
+
this.pendingResumeConnections.clear();
|
|
378
|
+
if (erroredRequestId === this.continuation.activeRequestId) {
|
|
379
|
+
this.continuation.activeRequestId = null;
|
|
380
|
+
this.continuation.activeConnectionId = null;
|
|
381
|
+
}
|
|
370
382
|
void this.ensureStreamCleanupScheduled();
|
|
371
383
|
}
|
|
372
384
|
|
|
@@ -465,13 +477,20 @@ export abstract class RecoverableChatAgent<
|
|
|
465
477
|
* @remarks
|
|
466
478
|
* 子类在发送增量记录、成功终态或失败终态时调用;`done` 表示轮次终态,`error` 只在终态失败时设置。
|
|
467
479
|
*
|
|
480
|
+
* `continuation` 必须与本轮 `runRecoverableChatFiber` 的取值一致。重放帧由
|
|
481
|
+
* `ResumableStream` 从 `is_continuation` 列自动补上这个标记,实时帧只能由调用方给出:
|
|
482
|
+
* 少了它,客户端会为续跑另建一个空的累加器,把已经渲染出来的那半条回答整条换掉。
|
|
483
|
+
*
|
|
468
484
|
* 广播显式排除正在续传握手的连接,避免它们在历史流重放完成前同时收到实时帧。
|
|
469
485
|
*/
|
|
470
486
|
protected sendChatResponse(
|
|
471
487
|
requestId: string,
|
|
472
488
|
body: string,
|
|
473
489
|
done: boolean,
|
|
474
|
-
|
|
490
|
+
options: {
|
|
491
|
+
readonly error?: boolean;
|
|
492
|
+
readonly continuation?: boolean;
|
|
493
|
+
} = {},
|
|
475
494
|
): void {
|
|
476
495
|
this.broadcast(
|
|
477
496
|
json({
|
|
@@ -479,7 +498,8 @@ export abstract class RecoverableChatAgent<
|
|
|
479
498
|
id: requestId,
|
|
480
499
|
body,
|
|
481
500
|
done,
|
|
482
|
-
...(error ? { error: true } : {}),
|
|
501
|
+
...(options.error ? { error: true } : {}),
|
|
502
|
+
...(options.continuation ? { continuation: true } : {}),
|
|
483
503
|
}),
|
|
484
504
|
[...this.pendingResumeConnections],
|
|
485
505
|
);
|
|
@@ -544,6 +564,7 @@ export abstract class RecoverableChatAgent<
|
|
|
544
564
|
this.activeRecoveryRootRequestId = data.requestId;
|
|
545
565
|
try {
|
|
546
566
|
const result = await this.recoveryPort().retry(data);
|
|
567
|
+
if (result.status === "scheduled") return;
|
|
547
568
|
await this.engine().updateIncident(
|
|
548
569
|
data.incidentId,
|
|
549
570
|
result.status === "completed" ? "completed" : "failed",
|
|
@@ -563,6 +584,83 @@ export abstract class RecoverableChatAgent<
|
|
|
563
584
|
}
|
|
564
585
|
}
|
|
565
586
|
|
|
587
|
+
/**
|
|
588
|
+
* 把活跃进程内检测到的模型流停滞交给同一套持久恢复预算。
|
|
589
|
+
*
|
|
590
|
+
* @remarks
|
|
591
|
+
* 子类在确认当前 Turn 可以从 durable checkpoint 重放后调用;`beforeSchedule`
|
|
592
|
+
* 必须先结束旧流并恢复权威消息快照,随后引擎才广播 recovering 状态。
|
|
593
|
+
*/
|
|
594
|
+
protected async scheduleChatRecoveryRetry(
|
|
595
|
+
data: RecoveryData,
|
|
596
|
+
beforeSchedule: () => void | Promise<void>,
|
|
597
|
+
): Promise<"disabled" | "scheduled" | "exhausted"> {
|
|
598
|
+
if (!resolveChatRecoveryConfig(this.chatRecovery).enabled) {
|
|
599
|
+
return "disabled";
|
|
600
|
+
}
|
|
601
|
+
const recoveryRootRequestId =
|
|
602
|
+
this.activeRecoveryRootRequestId ?? data.requestId;
|
|
603
|
+
const { incident, exhausted } = await this.engine().beginIncident({
|
|
604
|
+
requestId: data.requestId,
|
|
605
|
+
recoveryRootRequestId,
|
|
606
|
+
recoveryKind: "retry",
|
|
607
|
+
});
|
|
608
|
+
await beforeSchedule();
|
|
609
|
+
if (exhausted) {
|
|
610
|
+
await this.engine().exhaustRecoveryGiveUp({
|
|
611
|
+
callback: "_chatRecoveryRetry",
|
|
612
|
+
data: {
|
|
613
|
+
incidentId: incident.incidentId,
|
|
614
|
+
originalRequestId: recoveryRootRequestId,
|
|
615
|
+
},
|
|
616
|
+
reason: incident.reason ?? "max_attempts_exceeded",
|
|
617
|
+
});
|
|
618
|
+
return "exhausted";
|
|
619
|
+
}
|
|
620
|
+
await this.engine().scheduleRecovery({
|
|
621
|
+
incident,
|
|
622
|
+
recoveryKind: "retry",
|
|
623
|
+
callback: "_chatRecoveryRetry",
|
|
624
|
+
data: {
|
|
625
|
+
...data,
|
|
626
|
+
incidentId: incident.incidentId,
|
|
627
|
+
},
|
|
628
|
+
reason: this.activeRecoveryRootRequestId
|
|
629
|
+
? "stable_timeout_retry"
|
|
630
|
+
: "initial",
|
|
631
|
+
});
|
|
632
|
+
return "scheduled";
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** 终态化一个 Turn 时取消同请求的恢复调度,并关闭恢复事故与客户端状态。 */
|
|
636
|
+
protected async settleChatRecovery(
|
|
637
|
+
requestId: string,
|
|
638
|
+
status: "completed" | "skipped" | "failed",
|
|
639
|
+
reason?: string,
|
|
640
|
+
): Promise<void> {
|
|
641
|
+
const incidentIds = new Set<string>();
|
|
642
|
+
try {
|
|
643
|
+
for (const schedule of await this.listSchedules()) {
|
|
644
|
+
if (schedule.callback !== "_chatRecoveryRetry") continue;
|
|
645
|
+
const payload = schedule.payload;
|
|
646
|
+
if (!payload || typeof payload !== "object") continue;
|
|
647
|
+
const recovery = payload as Partial<
|
|
648
|
+
RecoveryScheduleData<RecoveryData>
|
|
649
|
+
>;
|
|
650
|
+
if (recovery.requestId !== requestId) continue;
|
|
651
|
+
if (typeof recovery.incidentId === "string") {
|
|
652
|
+
incidentIds.add(recovery.incidentId);
|
|
653
|
+
}
|
|
654
|
+
await this.cancelSchedule(schedule.id);
|
|
655
|
+
}
|
|
656
|
+
for (const incidentId of incidentIds) {
|
|
657
|
+
await this.engine().updateIncident(incidentId, status, reason);
|
|
658
|
+
}
|
|
659
|
+
} finally {
|
|
660
|
+
await this.setRecovering(false, requestId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
566
664
|
/**
|
|
567
665
|
* 把 Agents SDK 发现的孤立 Fiber 交给聊天恢复引擎。
|
|
568
666
|
*
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RuntimeDegradation } from "./degradation";
|
|
2
|
+
import type {
|
|
3
|
+
RuntimeMemoryProfile,
|
|
4
|
+
RuntimeMcpServer,
|
|
5
|
+
ThinkingEffort,
|
|
6
|
+
} from "./profile";
|
|
7
|
+
import type { ExecutionLevel } from "../lib/execution-level";
|
|
8
|
+
|
|
9
|
+
export interface RuntimeAssemblySkillBinding {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly script: {
|
|
13
|
+
readonly network: "none" | "full";
|
|
14
|
+
readonly workspace: "none" | "read" | "read-write";
|
|
15
|
+
readonly tools: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RuntimeAssemblyExtension {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly version: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RuntimeAssemblyView {
|
|
25
|
+
readonly revision: string | null;
|
|
26
|
+
readonly model: string;
|
|
27
|
+
readonly thinking: ThinkingEffort;
|
|
28
|
+
readonly executionLevel: ExecutionLevel;
|
|
29
|
+
readonly systemPrompt?: string;
|
|
30
|
+
readonly memory: RuntimeMemoryProfile;
|
|
31
|
+
readonly toolDeny: readonly string[];
|
|
32
|
+
readonly skills: readonly RuntimeAssemblySkillBinding[];
|
|
33
|
+
readonly extensions: readonly RuntimeAssemblyExtension[];
|
|
34
|
+
readonly subagents: readonly string[];
|
|
35
|
+
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
36
|
+
readonly degradations: readonly RuntimeDegradation[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RuntimeSnapshot } from "../runtime-assembler";
|
|
2
|
+
import type { PreparedPiRuntime } from "../pi/runtime-adapter";
|
|
3
|
+
import type { RuntimeAssemblyView } from "./runtime-assembly-view";
|
|
4
|
+
export type * from "./runtime-assembly-view";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Serializable view of the Runtime Snapshot currently installed on a Session facet.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* UI debug panels and Host tooling call `getRuntimeAssembly` to read what the
|
|
11
|
+
* Agent actually assembled, not what upstream configuration merely declared.
|
|
12
|
+
*/
|
|
13
|
+
export function projectRuntimeAssembly(
|
|
14
|
+
snapshot: RuntimeSnapshot,
|
|
15
|
+
revision: string | null,
|
|
16
|
+
prepared: Pick<PreparedPiRuntime, "degradations" | "extensions">,
|
|
17
|
+
): RuntimeAssemblyView {
|
|
18
|
+
const { profile, bindings } = snapshot;
|
|
19
|
+
return {
|
|
20
|
+
revision,
|
|
21
|
+
model: profile.model,
|
|
22
|
+
thinking: profile.thinking,
|
|
23
|
+
executionLevel: profile.executionLevel,
|
|
24
|
+
...(profile.systemPrompt ? { systemPrompt: profile.systemPrompt } : {}),
|
|
25
|
+
memory: { ...profile.memory },
|
|
26
|
+
toolDeny: [...(profile.denyPolicy?.deny ?? [])],
|
|
27
|
+
skills: bindings.skills.sources.map((binding) => ({
|
|
28
|
+
name: binding.name,
|
|
29
|
+
description: binding.description,
|
|
30
|
+
script: {
|
|
31
|
+
network: binding.script.network,
|
|
32
|
+
workspace: binding.script.workspace,
|
|
33
|
+
tools: [...binding.script.tools],
|
|
34
|
+
},
|
|
35
|
+
})),
|
|
36
|
+
extensions: prepared.extensions.map((extension) => ({ ...extension })),
|
|
37
|
+
subagents: [...profile.enabledSubagents],
|
|
38
|
+
mcpServers: profile.mcpServers.map((server) => ({ ...server })),
|
|
39
|
+
degradations: prepared.degradations.map((degradation) => ({ ...degradation })),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { RuntimeLoadPhase, RuntimeLoadState } from "./state";
|
|
2
|
+
|
|
3
|
+
/** Hard ceiling for one Host-owned Runtime definition phase. */
|
|
4
|
+
export const RUNTIME_LOAD_TIMEOUT_MS = 180_000;
|
|
5
|
+
|
|
6
|
+
/** Hard ceiling for one optional external capability load. */
|
|
7
|
+
export const RUNTIME_CAPABILITY_LOAD_TIMEOUT_MS = 60_000;
|
|
8
|
+
|
|
9
|
+
const reportedFailures = new WeakSet<object>();
|
|
10
|
+
|
|
11
|
+
function errorText(error: unknown): string {
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Emit one stable structured record for a Runtime load failure. */
|
|
16
|
+
export function logRuntimeLoadFailure(
|
|
17
|
+
step: string,
|
|
18
|
+
error: unknown,
|
|
19
|
+
details: {
|
|
20
|
+
reason?: "error" | "timeout";
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
} = {},
|
|
24
|
+
): void {
|
|
25
|
+
if (typeof error === "object" && error !== null) {
|
|
26
|
+
if (reportedFailures.has(error)) return;
|
|
27
|
+
reportedFailures.add(error);
|
|
28
|
+
}
|
|
29
|
+
console.error(
|
|
30
|
+
"[runtime-load:failed]",
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
step,
|
|
33
|
+
reason: details.reason ?? "error",
|
|
34
|
+
...(details.timeoutMs !== undefined
|
|
35
|
+
? { timeoutMs: details.timeoutMs }
|
|
36
|
+
: {}),
|
|
37
|
+
...(details.durationMs !== undefined
|
|
38
|
+
? { durationMs: details.durationMs }
|
|
39
|
+
: {}),
|
|
40
|
+
error: errorText(error),
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Stop waiting for one Runtime load boundary and report any failure once. */
|
|
46
|
+
export async function withRuntimeLoadTimeout<T>(
|
|
47
|
+
step: string,
|
|
48
|
+
load: () => T | PromiseLike<T>,
|
|
49
|
+
options: {
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
onLateResult?: (value: T) => void | Promise<void>;
|
|
52
|
+
} = {},
|
|
53
|
+
): Promise<T> {
|
|
54
|
+
const timeoutMs = options.timeoutMs ?? RUNTIME_CAPABILITY_LOAD_TIMEOUT_MS;
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
57
|
+
let timedOut = false;
|
|
58
|
+
const operation = Promise.resolve().then(load);
|
|
59
|
+
|
|
60
|
+
if (options.onLateResult) {
|
|
61
|
+
void operation.then(async (value) => {
|
|
62
|
+
if (timedOut) await options.onLateResult!(value);
|
|
63
|
+
}, () => undefined).catch((error) => {
|
|
64
|
+
logRuntimeLoadFailure(`${step}:late-cleanup`, error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return await Promise.race([
|
|
70
|
+
operation,
|
|
71
|
+
new Promise<never>((_, reject) => {
|
|
72
|
+
timeout = setTimeout(() => {
|
|
73
|
+
timedOut = true;
|
|
74
|
+
reject(new Error(
|
|
75
|
+
`Runtime load step "${step}" timed out after ${timeoutMs}ms`,
|
|
76
|
+
));
|
|
77
|
+
}, timeoutMs);
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logRuntimeLoadFailure(step, error, {
|
|
82
|
+
reason: timedOut ? "timeout" : "error",
|
|
83
|
+
timeoutMs,
|
|
84
|
+
durationMs: Date.now() - startedAt,
|
|
85
|
+
});
|
|
86
|
+
throw error;
|
|
87
|
+
} finally {
|
|
88
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 装载状态机与宿主之间的全部接触面。
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* 只有这三项:读上一次发布的状态、发布新状态、回答旧 Runtime 是否仍可服务。
|
|
97
|
+
* 追踪器碰不到 Kernel 的 Snapshot、数据库或 Submission,因此它的行为可以脱离
|
|
98
|
+
* Durable Object 单独验证。
|
|
99
|
+
*/
|
|
100
|
+
export interface RuntimeLoadPort {
|
|
101
|
+
read(): RuntimeLoadState | undefined;
|
|
102
|
+
publish(next: RuntimeLoadState): void;
|
|
103
|
+
/** 旧 Runtime 是否仍在服务;强制重载失败时它可能仍为 true。 */
|
|
104
|
+
isAvailable(): boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 维护一次 Runtime 装载尝试的公开状态机。
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* 宿主在装载的各个边界调用 `begin` / `advance` / `complete` / `fail`,
|
|
112
|
+
* 由本类统一发布 `RuntimeLoadState` 的形状与时间戳。
|
|
113
|
+
*
|
|
114
|
+
* `startedAt` 表示一次尝试的开始时刻:`begin` 重置它,其余转移沿用它,
|
|
115
|
+
* 使前端可以按同一基准显示耗时。
|
|
116
|
+
*/
|
|
117
|
+
export class RuntimeLoadTracker {
|
|
118
|
+
constructor(
|
|
119
|
+
private readonly port: RuntimeLoadPort,
|
|
120
|
+
private readonly now: () => number = Date.now,
|
|
121
|
+
) {}
|
|
122
|
+
|
|
123
|
+
// 作用:把状态清回未装载。
|
|
124
|
+
// 调用:Durable Object 启动时调用。
|
|
125
|
+
// 原因:实例刚被唤醒时没有任何在途尝试,旧记录里的 ready 不能继续对外承诺。
|
|
126
|
+
reset(): void {
|
|
127
|
+
this.port.publish({ status: "idle", available: false });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 作用:开始一轮新的装载尝试。
|
|
131
|
+
// 调用:宿主在调用 Definition config.read 前调用。
|
|
132
|
+
// 原因:config 阶段可能包含身份、D1 和 Resource 解析,必须在首个慢请求前可见。
|
|
133
|
+
begin(): void {
|
|
134
|
+
const now = this.now();
|
|
135
|
+
this.port.publish({
|
|
136
|
+
status: "loading",
|
|
137
|
+
phase: "config",
|
|
138
|
+
available: this.port.isAvailable(),
|
|
139
|
+
startedAt: now,
|
|
140
|
+
updatedAt: now,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 作用:推进当前装载尝试的阶段。
|
|
145
|
+
// 调用:宿主在进入 Assembly、MCP 和 Pi 边界时调用。
|
|
146
|
+
// 原因:保持一个稳定的粗粒度协议,不向前端泄漏具体能力准备和并发细节。
|
|
147
|
+
advance(phase: RuntimeLoadPhase): void {
|
|
148
|
+
const now = this.now();
|
|
149
|
+
this.port.publish({
|
|
150
|
+
status: "loading",
|
|
151
|
+
phase,
|
|
152
|
+
available: this.port.isAvailable(),
|
|
153
|
+
startedAt: this.startedAt(now),
|
|
154
|
+
updatedAt: now,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 作用:把成功提交的装载尝试标记为可用。
|
|
159
|
+
// 调用:宿主在配置初始化和 Runtime key 提交完成后调用。
|
|
160
|
+
// 原因:只有完整原子提交后才能向客户端承诺 ready。
|
|
161
|
+
complete(): void {
|
|
162
|
+
const now = this.now();
|
|
163
|
+
this.port.publish({
|
|
164
|
+
status: "ready",
|
|
165
|
+
available: true,
|
|
166
|
+
startedAt: this.startedAt(now),
|
|
167
|
+
completedAt: now,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 作用:记录装载失败,同时保留旧 Runtime 是否仍可用的信息。
|
|
172
|
+
// 调用:宿主收口 Config 读取或 Runtime 装配异常时调用。
|
|
173
|
+
// 原因:前端需要状态但不应接收可能包含存储细节的底层错误文本。
|
|
174
|
+
fail(): void {
|
|
175
|
+
const now = this.now();
|
|
176
|
+
const current = this.port.read();
|
|
177
|
+
this.port.publish({
|
|
178
|
+
status: "error",
|
|
179
|
+
phase: current?.status === "loading" ? current.phase : "config",
|
|
180
|
+
available: this.port.isAvailable(),
|
|
181
|
+
startedAt: this.startedAt(now),
|
|
182
|
+
failedAt: now,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 同一轮尝试内沿用原开始时刻;不在装载中说明这是一次孤立转移,只能以当下为准。
|
|
187
|
+
private startedAt(now: number): number {
|
|
188
|
+
const current = this.port.read();
|
|
189
|
+
return current?.status === "loading" ? current.startedAt : now;
|
|
190
|
+
}
|
|
191
|
+
}
|
package/src/kernel/state.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ApprovalReceipt } from "./receipts";
|
|
2
2
|
|
|
3
|
-
export type RuntimeLoadPhase = "config" | "
|
|
3
|
+
export type RuntimeLoadPhase = "config" | "assembly" | "mcp" | "pi";
|
|
4
4
|
|
|
5
5
|
export type RuntimeActivity = "idle" | "working" | "needs-input";
|
|
6
6
|
|
|
7
7
|
export interface RuntimeActivityProjection {
|
|
8
8
|
activity: RuntimeActivity;
|
|
9
|
+
/**
|
|
10
|
+
* 是否仍有 Agent Tool 子运行在执行。
|
|
11
|
+
*
|
|
12
|
+
* `activity` 三态互斥,表达不了「等人回应的同时后台还在跑」。Host 的列表需要
|
|
13
|
+
* 这两件事各自成立,所以它是一个独立维度,而不是第四种活动值。
|
|
14
|
+
*/
|
|
15
|
+
backgroundWork: boolean;
|
|
9
16
|
revision: number;
|
|
10
17
|
}
|
|
11
18
|
|
|
@@ -51,6 +58,10 @@ export interface RuntimeQueuedSubmission {
|
|
|
51
58
|
|
|
52
59
|
export interface RuntimeTurnState {
|
|
53
60
|
activeSubmissionId?: string;
|
|
61
|
+
activeRequestId?: string;
|
|
62
|
+
recoveryAttempt?: number;
|
|
63
|
+
recoveryMax?: number;
|
|
64
|
+
recoveryReason?: "no_meaningful_model_progress" | "transient_model_error";
|
|
54
65
|
steerable: boolean;
|
|
55
66
|
hasPendingSteer: boolean;
|
|
56
67
|
queued: RuntimeQueuedSubmission[];
|
|
@@ -202,6 +202,13 @@ interface SubmissionLifecycleOptions<
|
|
|
202
202
|
// 调用:`cancel` 命中活动执行器,或 `activate` 发现早到取消时调用。
|
|
203
203
|
// 原因:生命周期不依赖具体执行器类型,只通过这个回调中断。
|
|
204
204
|
abortActive(active: TActive): void;
|
|
205
|
+
/**
|
|
206
|
+
* 中断活动执行器,但让这条提交留给持久续跑接手。
|
|
207
|
+
*
|
|
208
|
+
* `interrupt` 调用。与 `abortActive` 的差别在结局而不在动作:中断不写终态,
|
|
209
|
+
* 也不接受这条执行器退场路上产生的任何工具结算。
|
|
210
|
+
*/
|
|
211
|
+
interruptActive(active: TActive): void;
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
// #endregion
|
|
@@ -528,6 +535,29 @@ export class SubmissionLifecycle<
|
|
|
528
535
|
};
|
|
529
536
|
}
|
|
530
537
|
|
|
538
|
+
/**
|
|
539
|
+
* 中断一条提交的活动执行器,但不写取消原因,也不写终态。
|
|
540
|
+
*
|
|
541
|
+
* @remarks
|
|
542
|
+
* 宿主在 park 期间换掉装配后调用:内存里那条执行器的系统提示和工具表锁在它
|
|
543
|
+
* 自己被创建的那一刻,续跑不可能带上新能力,必须让位给持久续跑路径重建一条。
|
|
544
|
+
*
|
|
545
|
+
* 返回的 Promise 在旧执行器真正退场后才完成,调用方必须等它 —— 否则决策会
|
|
546
|
+
* 看到还没散场的内存等待器,又把这一轮唤回旧装配上。没有活动执行器时是空操作。
|
|
547
|
+
*/
|
|
548
|
+
async interrupt(
|
|
549
|
+
submissionId: string,
|
|
550
|
+
afterAbort?: () => void,
|
|
551
|
+
): Promise<void> {
|
|
552
|
+
const active = this.activeBySubmission.get(submissionId);
|
|
553
|
+
if (!active) return;
|
|
554
|
+
// 先中断再拆等待器:反过来的话,park 住的工具会先醒来、发现自己没有结算,
|
|
555
|
+
// 然后真的把工具跑一遍 —— 而这一轮本来就该让位。
|
|
556
|
+
this.options.interruptActive(active);
|
|
557
|
+
afterAbort?.();
|
|
558
|
+
await this.executions.get(submissionId)?.catch(() => undefined);
|
|
559
|
+
}
|
|
560
|
+
|
|
531
561
|
/**
|
|
532
562
|
* 返回当前唯一活动的执行器。
|
|
533
563
|
*
|
|
@@ -587,13 +617,14 @@ export class SubmissionLifecycle<
|
|
|
587
617
|
return submission ? [submission.submissionId] : [];
|
|
588
618
|
})()
|
|
589
619
|
: (() => {
|
|
590
|
-
const submission = this.options.store.findRunning()
|
|
620
|
+
const submission = this.options.store.findRunning() ??
|
|
621
|
+
this.options.store.findNextPending();
|
|
591
622
|
return submission ? [submission.submissionId] : [];
|
|
592
623
|
})();
|
|
593
624
|
for (const submissionId of submissionIds) {
|
|
594
625
|
await this.cancel(submissionId, reason);
|
|
595
626
|
}
|
|
596
|
-
return { ok:
|
|
627
|
+
return { ok: submissionIds.length > 0 };
|
|
597
628
|
}
|
|
598
629
|
|
|
599
630
|
// #endregion
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import type { ExecutionLevel } from "../../../lib/execution-level";
|
|
2
|
+
|
|
3
|
+
export const TEMPORARY_AGENT_LAUNCH_KEY =
|
|
4
|
+
"universal-agent:temporary-agent-launch";
|
|
5
|
+
|
|
1
6
|
export interface TemporaryAgentRequest {
|
|
2
7
|
subagentName: string;
|
|
3
8
|
instructions: string;
|
|
4
9
|
task: string;
|
|
5
10
|
}
|
|
6
11
|
|
|
12
|
+
export interface TemporaryAgentLaunch<Config = unknown>
|
|
13
|
+
extends TemporaryAgentRequest {
|
|
14
|
+
runtimeKey: string;
|
|
15
|
+
config: Config;
|
|
16
|
+
executionLevel: ExecutionLevel;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
export interface TemporaryAgentRunContext {
|
|
8
20
|
signal: AbortSignal;
|
|
9
21
|
requestId: string;
|
|
@@ -149,4 +161,3 @@ export class TemporaryAgentCoordinator {
|
|
|
149
161
|
});
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
|
-
import type { ExecutionLevel } from "../../../lib/execution-level";
|
|
@@ -10,7 +10,6 @@ const BLOCKED_TOOLS = new Set([
|
|
|
10
10
|
"resume_schedule",
|
|
11
11
|
"change_schedule_agent",
|
|
12
12
|
"cancel_schedule",
|
|
13
|
-
"execute",
|
|
14
13
|
"set_context",
|
|
15
14
|
"load_context",
|
|
16
15
|
"search_context",
|
|
@@ -25,7 +24,7 @@ const BLOCKED_TOOLS = new Set([
|
|
|
25
24
|
* @remarks
|
|
26
25
|
* 宿主在装配临时 Agent 的平台、Workspace、Sandbox 和其他 Tool 时调用;调用方可再加名字或前缀黑名单。
|
|
27
26
|
*
|
|
28
|
-
*
|
|
27
|
+
* 固定黑名单排除再次委派、调度和主 Session 上下文,以保持单层临时 Agent 和已确认的继承范围。
|
|
29
28
|
*
|
|
30
29
|
* Agent、Session、Workspace 和 Tool 的术语见 `src/index.ts`。
|
|
31
30
|
*/
|
package/src/lib/mcp.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RetryOptions } from "agents";
|
|
2
|
+
import { withRuntimeLoadTimeout } from "../kernel/runtime-load";
|
|
2
3
|
|
|
3
4
|
const MCP_RETRY: RetryOptions = {
|
|
4
5
|
maxAttempts: 3,
|
|
@@ -63,9 +64,12 @@ export async function connectConfiguredMcpServers(
|
|
|
63
64
|
const servers = [...pending.values()];
|
|
64
65
|
const settled = await Promise.allSettled(
|
|
65
66
|
servers.map((server) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
withRuntimeLoadTimeout(
|
|
68
|
+
`mcp:${server.name}:connect`,
|
|
69
|
+
() => host.addMcpServer(server.name, server.url, {
|
|
70
|
+
retry: MCP_RETRY,
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
69
73
|
),
|
|
70
74
|
);
|
|
71
75
|
const failed = settled.flatMap((result, index) =>
|
package/src/lib/prompt.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const BEHAVIOR =
|
|
|
46
46
|
export const PLANNING =
|
|
47
47
|
"Planning: For work with 3 or more distinct steps, or any non-trivial / multi-file change, call " +
|
|
48
48
|
"update_plan with the complete plan (it fully replaces the previous one) so the user sees live " +
|
|
49
|
-
"progress. Mark a step in_progress before starting it and
|
|
49
|
+
"progress. Mark a step in_progress before starting it and done the moment it's done; keep only " +
|
|
50
50
|
"one step in_progress at a time and don't batch completions. Do not make a plan for a single trivial " +
|
|
51
51
|
"step or a purely conversational reply — just do it.";
|
|
52
52
|
|
package/src/lib/telemetry-dev.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 观测降级:把 observability 事件打到 console,让 `wrangler dev` / `wrangler tail` 里能实时看到埋点,
|
|
3
|
+
* 并且(生产上开了 `observability.logs` 时)进 Workers Logs 供事后查询。
|
|
4
|
+
* 不装它,事件就 publish 到零订阅 channel = 静默 no-op。
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* 由 onStart 在 env `TELEMETRY_CONSOLE==="1"` 时装载 —— `.dev.vars` 和 `wrangler.jsonc` 的 vars 里都开着。
|
|
7
|
+
* 注意:上游注释宣称"生产上所有 channel 事件自动转发 Tail Worker",那条路径需要 `tail_consumers`,
|
|
8
|
+
* 本仓没有配,所以生产**不能**指望它;这个 console sink 就是生产的唯一消费面(2026-08-06 实测:
|
|
9
|
+
* 未开启前 Observability API 查 universal-agent 24h 事件 count=0)。
|
|
7
10
|
*
|
|
8
11
|
* 用 `agents/observability` 的类型化 `subscribe`(按 channel key 订阅),避免引 `node:diagnostics_channel`
|
|
9
12
|
* 与 `@types/node`(tsconfig 只带 workers-types)。
|
|
@@ -367,7 +367,7 @@ async function readMemory(
|
|
|
367
367
|
return { blocks, degradations };
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
//
|
|
370
|
+
// 作用:把已配置 Skill 的持久化 catalog 整理成提示词。
|
|
371
371
|
// 调用:系统上下文装配在需要告诉模型当前可用 Skill 时调用。
|
|
372
372
|
// 原因:catalog 已在发布和绑定时固定,装配期不得为此访问远端内容来源。
|
|
373
373
|
function renderSkillCatalog(
|
|
@@ -379,8 +379,8 @@ function renderSkillCatalog(
|
|
|
379
379
|
if (bindings.length === 0) return null;
|
|
380
380
|
|
|
381
381
|
return [
|
|
382
|
-
"
|
|
383
|
-
"Use an
|
|
382
|
+
"AVAILABLE SKILLS",
|
|
383
|
+
"Use an available Skill when its description matches. Read its instructions or files only through the Skill tools.",
|
|
384
384
|
"",
|
|
385
385
|
...bindings.map(
|
|
386
386
|
({ name, description }) => `- ${name}: ${description}`,
|