@springbrand/agent-runtime 0.3.0-alpha.20 → 0.3.0-alpha.22
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 +5 -5
- package/src/adapter/cloudflare/agent.ts +79 -80
- package/src/adapter/cloudflare/define-agent.ts +220 -93
- package/src/adapter/cloudflare/schema.ts +6 -2
- package/src/config/activation.ts +13 -15
- package/src/config/harness-runtime.ts +26 -36
- package/src/config/index.ts +1 -1
- package/src/config/{pinning.ts → prepared-runtime.ts} +20 -118
- package/src/config/runtime-config.ts +2 -1
- package/src/conversation/message/contract.ts +16 -4
- package/src/conversation/message/conversion.ts +43 -2
- package/src/harness/session-harness.ts +21 -6
- package/src/submission/admission.ts +8 -29
- package/src/submission/lifecycle.ts +38 -16
- package/src/submission/repository.ts +39 -30
- package/src/telemetry/contract.ts +0 -1
- package/src/telemetry/coordinator.ts +13 -3
- package/src/telemetry/runtime-telemetry.ts +0 -4
- package/src/tools/assistant/ask-user.ts +3 -1
- package/src/tools/extension/index.ts +2 -2
- package/src/tools/web/search/api.ts +1 -1
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
MessageDelivery,
|
|
13
13
|
MessageDispatchReceipt,
|
|
14
14
|
SubmissionReceipt,
|
|
15
|
+
StoredRuntimeKey,
|
|
16
|
+
RuntimeKeyRewrite,
|
|
15
17
|
} from "../../submission";
|
|
16
18
|
import type { RuntimeState } from "../../conversation";
|
|
17
19
|
import type {
|
|
@@ -31,7 +33,9 @@ import {
|
|
|
31
33
|
} from "../../lib/execution-level";
|
|
32
34
|
import {
|
|
33
35
|
assembleRuntimeSnapshot,
|
|
36
|
+
verifyRuntimeCandidateGuards,
|
|
34
37
|
type RuntimeCandidate,
|
|
38
|
+
type RuntimeSubmissionAdmissionHook,
|
|
35
39
|
} from "../../config";
|
|
36
40
|
import type {
|
|
37
41
|
ResolvedResources,
|
|
@@ -71,6 +75,7 @@ export type {
|
|
|
71
75
|
RuntimeAgentToolResult,
|
|
72
76
|
RuntimeAssemblyContext,
|
|
73
77
|
} from "../../config";
|
|
78
|
+
export type { RuntimeKeyRewrite, StoredRuntimeKey } from "../../submission";
|
|
74
79
|
|
|
75
80
|
/** Host 一次性准备的完整 Runtime 加载结果。 */
|
|
76
81
|
export interface LoadedRuntime<Config> {
|
|
@@ -99,9 +104,9 @@ interface RuntimeAgentDefinitionBase<
|
|
|
99
104
|
/** Refresh Host-owned inputs before a new Submission records its Runtime key. */
|
|
100
105
|
readonly beforeSubmission?: (
|
|
101
106
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
102
|
-
) => Promise<
|
|
107
|
+
) => Promise<void>;
|
|
103
108
|
|
|
104
|
-
/** Prepare per-Submission Host state after admission and before
|
|
109
|
+
/** Prepare per-Submission Host state after admission and before first execution. */
|
|
105
110
|
readonly afterSubmission?: (
|
|
106
111
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
107
112
|
) => Promise<void>;
|
|
@@ -111,6 +116,12 @@ interface RuntimeAgentDefinitionBase<
|
|
|
111
116
|
requestedRuntimeKey?: string,
|
|
112
117
|
) => Promise<LoadedRuntime<Config>>;
|
|
113
118
|
|
|
119
|
+
/** Resolve saved identities; unfinished work must still belong to the selected Agent. */
|
|
120
|
+
readonly migrateStoredRuntimeKeys?: (
|
|
121
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
122
|
+
keys: readonly StoredRuntimeKey[],
|
|
123
|
+
) => Promise<readonly RuntimeKeyRewrite[]>;
|
|
124
|
+
|
|
114
125
|
readonly tools: (
|
|
115
126
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
116
127
|
config: Config,
|
|
@@ -321,6 +332,7 @@ declare class RuntimeAgentHostSeam {
|
|
|
321
332
|
protected prepareToolInteractionResponse(
|
|
322
333
|
input: import("./agent").ToolInteractionResponsePreparation,
|
|
323
334
|
): Promise<unknown | null>;
|
|
335
|
+
protected hasUnfinishedRuntimeWork(): boolean;
|
|
324
336
|
}
|
|
325
337
|
|
|
326
338
|
/**
|
|
@@ -364,6 +376,7 @@ const TERMINAL_AGENT_TOOL_STATUSES = [
|
|
|
364
376
|
"interrupted",
|
|
365
377
|
] as const;
|
|
366
378
|
const AGENT_TOOL_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
379
|
+
export const STABLE_RUNTIME_KEY_MIGRATION = "agent-runtime:stable-runtime-key-migration-v1";
|
|
367
380
|
|
|
368
381
|
function parseTemporaryAgentLaunch(input: unknown): TemporaryAgentLaunchSelection {
|
|
369
382
|
const record = input != null && typeof input === "object"
|
|
@@ -421,6 +434,8 @@ export function defineRuntimeAgent<
|
|
|
421
434
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
422
435
|
) => AgentTelemetryBinding | undefined | Promise<AgentTelemetryBinding | undefined>)(context);
|
|
423
436
|
};
|
|
437
|
+
const migrateSavedKeys = definition.migrateStoredRuntimeKeys as
|
|
438
|
+
RuntimeAgentDefinitionBase<Env, Config, Command, Change>["migrateStoredRuntimeKeys"];
|
|
424
439
|
|
|
425
440
|
const GeneratedRuntimeAgent = {
|
|
426
441
|
[definition.name]: class extends AgentToolRuntimeKernel<Env> {
|
|
@@ -432,6 +447,68 @@ export function defineRuntimeAgent<
|
|
|
432
447
|
private temporaryLaunch?: TemporaryAgentLaunchSelection;
|
|
433
448
|
private temporaryDispose?: () => Promise<void>;
|
|
434
449
|
private temporaryCleanup?: Promise<void>;
|
|
450
|
+
private storedKeyMigration?: Promise<void>;
|
|
451
|
+
|
|
452
|
+
async onStart(): Promise<void> {
|
|
453
|
+
await this.migrateStoredRuntimeKeys();
|
|
454
|
+
await super.onStart();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private async resolveSavedKeyRewrites(
|
|
458
|
+
keys: readonly StoredRuntimeKey[],
|
|
459
|
+
): Promise<readonly RuntimeKeyRewrite[]> {
|
|
460
|
+
if (!migrateSavedKeys) {
|
|
461
|
+
return keys.map(({ runtimeKey }) => ({ from: runtimeKey, to: runtimeKey }));
|
|
462
|
+
}
|
|
463
|
+
const rewrites = await migrateSavedKeys(this.createDefinitionContext(), keys);
|
|
464
|
+
if (
|
|
465
|
+
rewrites.length !== keys.length ||
|
|
466
|
+
rewrites.some(({ from, to }, index) =>
|
|
467
|
+
from !== keys[index]?.runtimeKey || !to.trim())
|
|
468
|
+
) {
|
|
469
|
+
throw new Error("Saved Runtime key migration returned an invalid rewrite plan");
|
|
470
|
+
}
|
|
471
|
+
return rewrites;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private migrateStoredRuntimeKeys(): Promise<void> {
|
|
475
|
+
if (!migrateSavedKeys) return Promise.resolve();
|
|
476
|
+
if (this.storedKeyMigration) return this.storedKeyMigration;
|
|
477
|
+
const migration = (async () => {
|
|
478
|
+
if (await this.ctx.storage.get<boolean>(STABLE_RUNTIME_KEY_MIGRATION)) return;
|
|
479
|
+
const savedLaunch = await this.ctx.storage.get<unknown>(TEMPORARY_AGENT_LAUNCH_KEY);
|
|
480
|
+
const launch = savedLaunch === undefined
|
|
481
|
+
? undefined
|
|
482
|
+
: parseTemporaryAgentLaunch(savedLaunch);
|
|
483
|
+
const keysByValue = new Map(
|
|
484
|
+
this.savedRuntimeKeys().map((key) => [key.runtimeKey, key]),
|
|
485
|
+
);
|
|
486
|
+
if (launch) {
|
|
487
|
+
keysByValue.set(launch.runtimeKey, {
|
|
488
|
+
runtimeKey: launch.runtimeKey,
|
|
489
|
+
unfinished: keysByValue.get(launch.runtimeKey)?.unfinished ?? true,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
const keys = [...keysByValue.values()];
|
|
493
|
+
if (keys.length > 0) {
|
|
494
|
+
const rewrites = await this.resolveSavedKeyRewrites(keys);
|
|
495
|
+
this.rewriteSavedRuntimeKeys(rewrites);
|
|
496
|
+
if (launch) {
|
|
497
|
+
const runtimeKey = rewrites.find(({ from }) => from === launch.runtimeKey)?.to;
|
|
498
|
+
if (!runtimeKey) throw new Error("Saved temporary Agent key was not migrated");
|
|
499
|
+
const currentLaunch = { ...launch, runtimeKey };
|
|
500
|
+
await this.ctx.storage.put(TEMPORARY_AGENT_LAUNCH_KEY, currentLaunch);
|
|
501
|
+
this.temporaryLaunch = currentLaunch;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
await this.ctx.storage.put(STABLE_RUNTIME_KEY_MIGRATION, true);
|
|
505
|
+
})().catch((error) => {
|
|
506
|
+
this.storedKeyMigration = undefined;
|
|
507
|
+
throw error;
|
|
508
|
+
});
|
|
509
|
+
this.storedKeyMigration = migration;
|
|
510
|
+
return migration;
|
|
511
|
+
}
|
|
435
512
|
|
|
436
513
|
protected get role(): RuntimeAgentRole {
|
|
437
514
|
return this.parentPath.at(-1)?.className === definition.name
|
|
@@ -446,19 +523,20 @@ export function defineRuntimeAgent<
|
|
|
446
523
|
return this.ensureConfig();
|
|
447
524
|
}
|
|
448
525
|
|
|
449
|
-
protected override ensureRuntimeVersion(runtimeKey: string): Promise<void> {
|
|
450
|
-
return this.ensureConfig(runtimeKey);
|
|
526
|
+
protected override ensureRuntimeVersion(runtimeKey: string, force = false): Promise<void> {
|
|
527
|
+
return this.ensureConfig(runtimeKey, force);
|
|
451
528
|
}
|
|
452
529
|
|
|
453
|
-
protected async prepareRuntimeForAdmission(): Promise<
|
|
530
|
+
protected async prepareRuntimeForAdmission(): Promise<{
|
|
531
|
+
runtimeKey: string;
|
|
532
|
+
admission?: RuntimeSubmissionAdmissionHook;
|
|
533
|
+
}> {
|
|
454
534
|
const beforeSubmission = definition.beforeSubmission as RuntimeAgentDefinitionBase<Env, Config, Command, Change>["beforeSubmission"];
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
throw error;
|
|
461
|
-
}
|
|
535
|
+
await beforeSubmission?.(this.createDefinitionContext());
|
|
536
|
+
const requestedKey = this.role === "temporary"
|
|
537
|
+
? this.loadedRuntimeKey ?? this.temporaryLaunch?.runtimeKey
|
|
538
|
+
: undefined;
|
|
539
|
+
return this.inspectCandidate(requestedKey);
|
|
462
540
|
}
|
|
463
541
|
|
|
464
542
|
protected async prepareRuntimeForSubmission(submissionId: string): Promise<void> {
|
|
@@ -479,7 +557,7 @@ export function defineRuntimeAgent<
|
|
|
479
557
|
runtimeKey?: string,
|
|
480
558
|
options?: { force?: boolean },
|
|
481
559
|
): Promise<void> {
|
|
482
|
-
await this.ensureConfig(runtimeKey, options?.force === true);
|
|
560
|
+
await this.runConfigControl(() => this.ensureConfig(runtimeKey, options?.force === true));
|
|
483
561
|
}
|
|
484
562
|
|
|
485
563
|
async updateConfig(
|
|
@@ -492,26 +570,36 @@ export function defineRuntimeAgent<
|
|
|
492
570
|
if (!update) {
|
|
493
571
|
return { change: undefined as Change, runtime: "unchanged" };
|
|
494
572
|
}
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
573
|
+
const deferActivation = this.isSubmissionExecuting();
|
|
574
|
+
const apply = async (): Promise<RuntimeConfigUpdateResult<Change>> => {
|
|
575
|
+
const result = await update(this.createDefinitionContext(), command);
|
|
576
|
+
if (!result.changed) {
|
|
577
|
+
return { change: result.change, runtime: "unchanged" };
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
if (deferActivation) {
|
|
581
|
+
await this.inspectCandidate();
|
|
582
|
+
return { change: result.change, runtime: "deferred" };
|
|
583
|
+
}
|
|
584
|
+
await this.ensureConfig(undefined, true);
|
|
585
|
+
return { change: result.change, runtime: "reloaded" };
|
|
586
|
+
} catch (reloadError) {
|
|
587
|
+
if (result.rollback) {
|
|
588
|
+
try {
|
|
589
|
+
await result.rollback();
|
|
590
|
+
} catch (rollbackError) {
|
|
591
|
+
throw new AggregateError(
|
|
592
|
+
[reloadError, rollbackError],
|
|
593
|
+
"Runtime Config rollback failed",
|
|
594
|
+
);
|
|
595
|
+
}
|
|
511
596
|
}
|
|
597
|
+
return { change: result.change, runtime: "reload-failed" };
|
|
512
598
|
}
|
|
513
|
-
|
|
514
|
-
|
|
599
|
+
};
|
|
600
|
+
return deferActivation
|
|
601
|
+
? apply()
|
|
602
|
+
: this.runConfigControl(apply);
|
|
515
603
|
}
|
|
516
604
|
|
|
517
605
|
async getRuntimeAssembly(): Promise<RuntimeAssemblyView> {
|
|
@@ -563,13 +651,105 @@ export function defineRuntimeAgent<
|
|
|
563
651
|
if (this.role !== "temporary") {
|
|
564
652
|
throw new Error("Agent Tool runs require a temporary child facet");
|
|
565
653
|
}
|
|
566
|
-
const
|
|
654
|
+
const parsed = parseTemporaryAgentLaunch(input);
|
|
655
|
+
const [rewrite] = await this.resolveSavedKeyRewrites([
|
|
656
|
+
{ runtimeKey: parsed.runtimeKey, unfinished: true },
|
|
657
|
+
]);
|
|
658
|
+
const runtimeKey = rewrite?.to;
|
|
659
|
+
if (!runtimeKey) throw new Error("Temporary Agent runtime key must not be empty");
|
|
660
|
+
const launch = { ...parsed, runtimeKey };
|
|
567
661
|
this.temporaryLaunch = launch;
|
|
568
662
|
await this.ctx.storage.put(TEMPORARY_AGENT_LAUNCH_KEY, launch);
|
|
569
663
|
await this.reloadRuntime(launch.runtimeKey, { force: true });
|
|
570
664
|
return launch.task;
|
|
571
665
|
}
|
|
572
666
|
|
|
667
|
+
/** One definition load and assembly path for admission and execution. */
|
|
668
|
+
private async prepareCandidate(requestedRuntimeKey?: string): Promise<{
|
|
669
|
+
candidate: RuntimeCandidate;
|
|
670
|
+
runtimeKey: string;
|
|
671
|
+
dispose?: () => Promise<void>;
|
|
672
|
+
}> {
|
|
673
|
+
const loadContext = this.createDefinitionContext();
|
|
674
|
+
const loaded = await withRuntimeLoadTimeout(
|
|
675
|
+
"definition.load",
|
|
676
|
+
() => (
|
|
677
|
+
definition.load as (
|
|
678
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
679
|
+
requestedRuntimeKey?: string,
|
|
680
|
+
) => Promise<LoadedRuntime<Config>>
|
|
681
|
+
)(loadContext, requestedRuntimeKey),
|
|
682
|
+
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
683
|
+
);
|
|
684
|
+
const runtimeKey = loaded.runtimeKey.trim();
|
|
685
|
+
if (!runtimeKey) {
|
|
686
|
+
throw new Error("Resolved Runtime key must not be empty");
|
|
687
|
+
}
|
|
688
|
+
if (requestedRuntimeKey && runtimeKey !== requestedRuntimeKey) {
|
|
689
|
+
throw new Error(
|
|
690
|
+
`Requested Runtime key ${requestedRuntimeKey} resolved to ${runtimeKey}`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
const context = this.createDefinitionContext({ value: loaded.config });
|
|
694
|
+
let tools = await withRuntimeLoadTimeout(
|
|
695
|
+
"definition.tools",
|
|
696
|
+
() => (
|
|
697
|
+
definition.tools as (
|
|
698
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
699
|
+
config: Config,
|
|
700
|
+
) => ToolAssemblyResult | Promise<ToolAssemblyResult>
|
|
701
|
+
)(context, loaded.config),
|
|
702
|
+
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
703
|
+
);
|
|
704
|
+
const dispose = tools.dispose;
|
|
705
|
+
try {
|
|
706
|
+
let resources = loaded.resources;
|
|
707
|
+
let commitGuard = loaded.commitGuard;
|
|
708
|
+
if (!this.telemetryResolved) {
|
|
709
|
+
this.resolvedTelemetry = await resolveDefinitionTelemetry(context);
|
|
710
|
+
this.telemetryResolved = true;
|
|
711
|
+
}
|
|
712
|
+
const telemetry = this.resolvedTelemetry;
|
|
713
|
+
if (this.role === "temporary") {
|
|
714
|
+
tools = this.applyTemporaryToolPolicy(tools);
|
|
715
|
+
resources = await this.applyTemporaryResources(resources);
|
|
716
|
+
commitGuard = undefined;
|
|
717
|
+
}
|
|
718
|
+
const candidate = await assembleRuntimeSnapshot({
|
|
719
|
+
ctx: context,
|
|
720
|
+
tools,
|
|
721
|
+
resources,
|
|
722
|
+
...(commitGuard ? { commitGuard } : {}),
|
|
723
|
+
...(telemetry ? { telemetry } : {}),
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
candidate: { ...candidate, runtimeKey },
|
|
727
|
+
runtimeKey,
|
|
728
|
+
...(dispose ? { dispose } : {}),
|
|
729
|
+
};
|
|
730
|
+
} catch (error) {
|
|
731
|
+
await dispose?.();
|
|
732
|
+
throw error;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Validate a candidate without changing the active Runtime or its Gateway. */
|
|
737
|
+
private async inspectCandidate(requestedRuntimeKey?: string): Promise<{
|
|
738
|
+
runtimeKey: string;
|
|
739
|
+
admission?: RuntimeSubmissionAdmissionHook;
|
|
740
|
+
}> {
|
|
741
|
+
const prepared = await this.prepareCandidate(requestedRuntimeKey);
|
|
742
|
+
try {
|
|
743
|
+
await verifyRuntimeCandidateGuards(prepared.candidate, "admission");
|
|
744
|
+
return {
|
|
745
|
+
runtimeKey: prepared.runtimeKey,
|
|
746
|
+
admission: prepared.candidate.admission,
|
|
747
|
+
};
|
|
748
|
+
} finally {
|
|
749
|
+
await prepared.dispose?.();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
573
753
|
// 作用:把首次加载和按 key 重载合并成一个串行入口。
|
|
574
754
|
// 调用:Runtime readiness、Session 配置和重载都会调用。
|
|
575
755
|
// 原因:共享同一个 Promise 可合并并发首加载,显式切 key 则等待前序完成后再判断。
|
|
@@ -618,69 +798,16 @@ export function defineRuntimeAgent<
|
|
|
618
798
|
// 先创建局部 Promise,再赋给 `loading`,失败时也能在 finally 中放开重试。
|
|
619
799
|
const pending = (async () => {
|
|
620
800
|
this.runtimeLoad.begin();
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
requestedRuntimeKey?: string,
|
|
628
|
-
) => Promise<LoadedRuntime<Config>>
|
|
629
|
-
)(
|
|
630
|
-
loadContext,
|
|
631
|
-
requestedRuntimeKey,
|
|
632
|
-
),
|
|
633
|
-
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
634
|
-
);
|
|
635
|
-
const resolvedRuntimeKey = loaded.runtimeKey.trim();
|
|
636
|
-
if (!resolvedRuntimeKey) {
|
|
637
|
-
throw new Error("Resolved Runtime key must not be empty");
|
|
638
|
-
}
|
|
639
|
-
if (requestedRuntimeKey && resolvedRuntimeKey !== requestedRuntimeKey) {
|
|
640
|
-
throw new Error(
|
|
641
|
-
`Requested Runtime key ${requestedRuntimeKey} resolved to ${resolvedRuntimeKey}`,
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
const context = this.createDefinitionContext({
|
|
645
|
-
value: loaded.config,
|
|
646
|
-
});
|
|
647
|
-
let tools = await withRuntimeLoadTimeout(
|
|
648
|
-
"definition.tools",
|
|
649
|
-
() => (
|
|
650
|
-
definition.tools as (
|
|
651
|
-
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
652
|
-
config: Config,
|
|
653
|
-
) => ToolAssemblyResult | Promise<ToolAssemblyResult>
|
|
654
|
-
)(context, loaded.config),
|
|
655
|
-
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
656
|
-
);
|
|
657
|
-
let resources = loaded.resources;
|
|
658
|
-
let commitGuard = loaded.commitGuard;
|
|
659
|
-
if (!this.telemetryResolved) {
|
|
660
|
-
this.resolvedTelemetry = await resolveDefinitionTelemetry(context);
|
|
661
|
-
this.telemetryResolved = true;
|
|
662
|
-
}
|
|
663
|
-
const telemetry = this.resolvedTelemetry;
|
|
664
|
-
if (this.role === "temporary") {
|
|
665
|
-
this.temporaryDispose = tools.dispose;
|
|
666
|
-
tools = this.applyTemporaryToolPolicy(tools);
|
|
667
|
-
resources = await this.applyTemporaryResources(resources);
|
|
668
|
-
commitGuard = undefined;
|
|
801
|
+
const prepared = await this.prepareCandidate(requestedRuntimeKey);
|
|
802
|
+
try {
|
|
803
|
+
await this.initCandidate(prepared.candidate);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
await prepared.dispose?.();
|
|
806
|
+
throw error;
|
|
669
807
|
}
|
|
670
|
-
|
|
671
|
-
ctx: context,
|
|
672
|
-
tools,
|
|
673
|
-
resources,
|
|
674
|
-
...(commitGuard ? { commitGuard } : {}),
|
|
675
|
-
...(telemetry ? { telemetry } : {}),
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
await this.initCandidate({
|
|
679
|
-
...candidate,
|
|
680
|
-
runtimeKey: resolvedRuntimeKey,
|
|
681
|
-
});
|
|
808
|
+
if (this.role === "temporary") this.temporaryDispose = prepared.dispose;
|
|
682
809
|
this.hasLoadedRuntime = true;
|
|
683
|
-
this.loadedRuntimeKey =
|
|
810
|
+
this.loadedRuntimeKey = prepared.runtimeKey;
|
|
684
811
|
this.runtimeLoad.complete();
|
|
685
812
|
// 提交之前 turnEvents 绑定还不存在,此前每一次广播都到不了 Host。
|
|
686
813
|
// Host 的会话列表把装配后的第一次投影当作「facet 已就绪」的信号。
|
|
@@ -19,8 +19,6 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
19
19
|
error TEXT,
|
|
20
20
|
created_at INTEGER NOT NULL,
|
|
21
21
|
completed_at INTEGER,
|
|
22
|
-
assembly_revision TEXT NOT NULL,
|
|
23
|
-
assembly_descriptor TEXT NOT NULL,
|
|
24
22
|
assistant_message_id TEXT NOT NULL,
|
|
25
23
|
abort_reason TEXT
|
|
26
24
|
)`;
|
|
@@ -28,6 +26,12 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
28
26
|
sql<{ name: string }>`PRAGMA table_info(pi_submissions)`
|
|
29
27
|
.map((column) => column.name),
|
|
30
28
|
);
|
|
29
|
+
if (submissionColumns.has("assembly_revision")) {
|
|
30
|
+
sql`ALTER TABLE pi_submissions DROP COLUMN assembly_revision`;
|
|
31
|
+
}
|
|
32
|
+
if (submissionColumns.has("assembly_descriptor")) {
|
|
33
|
+
sql`ALTER TABLE pi_submissions DROP COLUMN assembly_descriptor`;
|
|
34
|
+
}
|
|
31
35
|
if (!submissionColumns.has("runtime_key")) {
|
|
32
36
|
sql`ALTER TABLE pi_submissions ADD COLUMN runtime_key TEXT NOT NULL DEFAULT ''`;
|
|
33
37
|
}
|
package/src/config/activation.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { connectConfiguredMcpServers } from "../lib/mcp";
|
|
|
2
2
|
import { sha256Hex } from "../lib/artifacts";
|
|
3
3
|
import type { RuntimeCandidate, RuntimeSnapshot } from "./assembler";
|
|
4
4
|
import type { HarnessRuntimeConfig } from "./harness-runtime";
|
|
5
|
-
import type { PreparedPiRuntime, PreparePiRuntimeOptions } from "./
|
|
5
|
+
import type { PreparedPiRuntime, PreparePiRuntimeOptions } from "./prepared-runtime";
|
|
6
6
|
import type { RuntimeGatewaySession, RuntimeSubmissionAdmissionHook } from "./ports";
|
|
7
7
|
import { withRuntimeLoadTimeout } from "./runtime-load";
|
|
8
8
|
import type { RuntimeLoadPhase } from "../conversation/state";
|
|
@@ -16,6 +16,16 @@ export interface ActiveRuntime {
|
|
|
16
16
|
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** Check the same candidate guards during admission and final activation. */
|
|
20
|
+
export async function verifyRuntimeCandidateGuards(
|
|
21
|
+
candidate: RuntimeCandidate,
|
|
22
|
+
phase: "admission" | "commit",
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
for (const [index, guard] of candidate.commitGuards.entries()) {
|
|
25
|
+
await withRuntimeLoadTimeout(`${phase}-guard:${index}`, guard);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
async function closeRuntimeGatewaySession(
|
|
20
30
|
session: RuntimeGatewaySession | undefined,
|
|
21
31
|
step: string,
|
|
@@ -33,7 +43,6 @@ interface RuntimeActivationOptions {
|
|
|
33
43
|
readonly sessionId: string;
|
|
34
44
|
readonly createExtensionHostBinding: PreparePiRuntimeOptions["createExtensionHostBinding"];
|
|
35
45
|
advance(phase: RuntimeLoadPhase): void;
|
|
36
|
-
isExecuting(): boolean;
|
|
37
46
|
commit(runtime: ActiveRuntime): void;
|
|
38
47
|
}
|
|
39
48
|
|
|
@@ -92,19 +101,10 @@ export async function activateRuntimeCandidate(
|
|
|
92
101
|
throw error;
|
|
93
102
|
}
|
|
94
103
|
const revision = await sha256Hex(prepared.revisionDescriptor);
|
|
95
|
-
const contested = Boolean(previous) &&
|
|
96
|
-
revision !== previous?.revision &&
|
|
97
|
-
options.isExecuting();
|
|
98
|
-
if (contested) {
|
|
99
|
-
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
|
|
100
|
-
throw new Error(
|
|
101
|
-
"Cannot reload Runtime while a revision-pinned SpringBrand Turn is active",
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
104
|
const next = Object.freeze({
|
|
105
105
|
snapshot: candidateSnapshot,
|
|
106
106
|
revision,
|
|
107
|
-
runtimeKey: candidate.runtimeKey ??
|
|
107
|
+
runtimeKey: candidate.runtimeKey ?? "default",
|
|
108
108
|
pi: prepared,
|
|
109
109
|
...(gatewaySession ? { gatewaySession } : {}),
|
|
110
110
|
...(candidate.admission
|
|
@@ -114,9 +114,7 @@ export async function activateRuntimeCandidate(
|
|
|
114
114
|
let activated = false;
|
|
115
115
|
const previousGatewaySession = previous?.gatewaySession;
|
|
116
116
|
try {
|
|
117
|
-
|
|
118
|
-
await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
|
|
119
|
-
}
|
|
117
|
+
await verifyRuntimeCandidateGuards(candidate, "commit");
|
|
120
118
|
options.pi.activate(candidateSnapshot);
|
|
121
119
|
activated = true;
|
|
122
120
|
options.commit(next);
|
|
@@ -15,19 +15,17 @@ export {
|
|
|
15
15
|
readModelStreamStallDetails,
|
|
16
16
|
} from "../models";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
assembleCurrentPiContext,
|
|
19
19
|
preparePiRuntime,
|
|
20
|
-
readPinnedPiRuntime,
|
|
21
20
|
readPreparedPiRuntime,
|
|
22
|
-
type
|
|
23
|
-
type PinnedPiRuntime,
|
|
21
|
+
type CurrentPiContextOptions,
|
|
24
22
|
type PreparePiRuntimeOptions,
|
|
25
23
|
type PreparedPiRuntime,
|
|
26
|
-
} from "./
|
|
24
|
+
} from "./prepared-runtime";
|
|
27
25
|
import { refreshModelGuidance } from "./model-guidance";
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
|
-
* Runtime
|
|
28
|
+
* Runtime 通过这个配置入口准备并读取当前 Harness 的模型、提示和工具输入。
|
|
31
29
|
*
|
|
32
30
|
* @remarks
|
|
33
31
|
* Runtime、Pi、Turn、Tool Candidate 和 canonical transcript 等核心术语见包入口 `../../index.ts`。
|
|
@@ -37,9 +35,7 @@ import { refreshModelGuidance } from "./model-guidance";
|
|
|
37
35
|
* 它包含已加载 Extension、Tool candidate 和快照,但 Runtime 只能读取 revision descriptor 与 degradation。
|
|
38
36
|
* - revision descriptor 是影响执行行为的已排序 JSON 描述。
|
|
39
37
|
* Runtime 会对它取哈希,用来识别当前 Prepared Runtime 的基础版本。
|
|
40
|
-
*
|
|
41
|
-
* 它在 Prepared Runtime 基础上加入动态 system context、approval mode 和 base revision。
|
|
42
|
-
* 调用方:`AgentRuntimeKernel` 在配置加载、请求准入与打开 SessionHarness 前调用这些粗粒度能力。
|
|
38
|
+
* 调用方:`AgentRuntimeKernel` 在配置加载与打开 SessionHarness 前调用这些粗粒度能力。
|
|
43
39
|
*
|
|
44
40
|
* 执行位置仍由 `SessionHarness` 拥有,消息读写和投影由 `ConversationHistory` 拥有。
|
|
45
41
|
*/
|
|
@@ -86,23 +82,31 @@ export class HarnessRuntimeConfig {
|
|
|
86
82
|
);
|
|
87
83
|
}
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
interaction(prepared: PreparedPiRuntime, name: string) {
|
|
86
|
+
return readPreparedPiRuntime(prepared, this.owner).candidates.find(
|
|
87
|
+
(candidate) => candidate.tool.name === name,
|
|
88
|
+
)?.interaction;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Read all executable inputs from the current Prepared Runtime. */
|
|
92
|
+
async harnessInputs(
|
|
91
93
|
prepared: PreparedPiRuntime,
|
|
92
|
-
pinnedDescriptor: string,
|
|
93
94
|
request: {
|
|
94
95
|
readonly modelSessionId?: string | (() => string);
|
|
95
96
|
readonly onGeneration?: PiGenerationLifecycleObserver;
|
|
97
|
+
readonly includeMemory?: boolean;
|
|
98
|
+
readonly readExtensionContext?: CurrentPiContextOptions["readExtensionContext"];
|
|
96
99
|
readonly assertRequestAllowed?: () => void;
|
|
97
100
|
readonly onCompactionGeneration?: PiGenerationLifecycleObserver;
|
|
98
101
|
} = {},
|
|
99
102
|
) {
|
|
100
|
-
const
|
|
103
|
+
const state = readPreparedPiRuntime(prepared, this.owner);
|
|
104
|
+
const context = await assembleCurrentPiContext({
|
|
101
105
|
prepared,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
const
|
|
106
|
+
includeMemory: request.includeMemory,
|
|
107
|
+
readExtensionContext: request.readExtensionContext,
|
|
108
|
+
}, this.owner);
|
|
109
|
+
const currentModel = state.snapshot.pi.model;
|
|
106
110
|
let models!: MutableModels;
|
|
107
111
|
const requestStream = (observer = request.onGeneration) => {
|
|
108
112
|
const stream = withProviderBoundary(
|
|
@@ -121,8 +125,8 @@ export class HarnessRuntimeConfig {
|
|
|
121
125
|
get: (target, property) => {
|
|
122
126
|
if (property === "getModel") {
|
|
123
127
|
return (provider: string, modelId: string) =>
|
|
124
|
-
provider ===
|
|
125
|
-
?
|
|
128
|
+
provider === currentModel.provider && modelId === currentModel.id
|
|
129
|
+
? currentModel
|
|
126
130
|
: target.getModel(provider, modelId);
|
|
127
131
|
}
|
|
128
132
|
if (property === "streamSimple") return requestStream();
|
|
@@ -147,7 +151,8 @@ export class HarnessRuntimeConfig {
|
|
|
147
151
|
models,
|
|
148
152
|
model: state.snapshot.pi.model,
|
|
149
153
|
thinkingLevel: state.snapshot.pi.thinkingLevel,
|
|
150
|
-
systemPrompt:
|
|
154
|
+
systemPrompt: context.systemPrompt,
|
|
155
|
+
degradations: context.degradations,
|
|
151
156
|
candidates: state.candidates,
|
|
152
157
|
workspace: state.snapshot.bindings.workspace,
|
|
153
158
|
platform: state.snapshot.bindings.platform,
|
|
@@ -179,27 +184,12 @@ export class HarnessRuntimeConfig {
|
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
/**
|
|
183
|
-
* 在接受 Submission 前固定本次执行使用的动态上下文和审批模式。
|
|
184
|
-
*
|
|
185
|
-
* @remarks
|
|
186
|
-
* 调用方:Runtime 的准入流程为每个新 Submission 调用,并把 descriptor 与其哈希一起持久化。
|
|
187
|
-
*
|
|
188
|
-
* 实现理由:memory、workspace 和 Extension context 可能随时间变化,所以把准入时的 Turn 字段保存在 descriptor 中。
|
|
189
|
-
* 不要把 pin 推迟到 Turn 启动后,否则已持久化的 Submission 将失去可验证的执行基线。
|
|
190
|
-
*/
|
|
191
|
-
pin(options: PinPiRuntimeOptions): Promise<PinnedPiRuntime> {
|
|
192
|
-
return pinPiRuntime(options, this.owner);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
187
|
}
|
|
196
188
|
|
|
197
189
|
export type {
|
|
198
|
-
PinPiRuntimeOptions,
|
|
199
|
-
PinnedPiRuntime,
|
|
200
190
|
PreparePiRuntimeOptions,
|
|
201
191
|
PreparedPiRuntime,
|
|
202
|
-
} from "./
|
|
192
|
+
} from "./prepared-runtime";
|
|
203
193
|
|
|
204
194
|
/**
|
|
205
195
|
* Input tokens above which a Turn compacts its history.
|
package/src/config/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from "./assembly-view";
|
|
|
7
7
|
export * from "./definition";
|
|
8
8
|
export * from "./degradation";
|
|
9
9
|
export * from "./harness-runtime";
|
|
10
|
-
export * from "./
|
|
10
|
+
export * from "./prepared-runtime";
|
|
11
11
|
export * from "./ports";
|
|
12
12
|
export * from "./profile";
|
|
13
13
|
export * from "./runtime-config";
|