@springbrand/agent-runtime 0.3.0-alpha.2 → 0.3.0-alpha.21

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.
Files changed (131) hide show
  1. package/package.json +11 -9
  2. package/src/adapter/cloudflare/agent.ts +233 -179
  3. package/src/adapter/cloudflare/define-agent.ts +252 -104
  4. package/src/adapter/cloudflare/index.ts +18 -0
  5. package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
  6. package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
  7. package/src/adapter/cloudflare/schema.ts +21 -2
  8. package/src/adapter/cloudflare/subagent/definition.ts +25 -13
  9. package/src/adapter/cloudflare/subagent/runner.ts +43 -50
  10. package/src/adapter/cloudflare/subagent/tools.ts +4 -4
  11. package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
  12. package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
  13. package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
  14. package/src/config/activation.ts +13 -15
  15. package/src/config/agent-context.ts +1 -1
  16. package/src/config/assembler.ts +48 -31
  17. package/src/config/assembly-snapshot.ts +1 -1
  18. package/src/config/definition.ts +1 -1
  19. package/src/config/harness-runtime.ts +77 -49
  20. package/src/config/index.ts +1 -1
  21. package/src/config/model-guidance.ts +20 -0
  22. package/src/config/ports.ts +57 -7
  23. package/src/config/{pinning.ts → prepared-runtime.ts} +59 -123
  24. package/src/config/public-contracts.ts +2 -2
  25. package/src/config/runtime-config.ts +2 -1
  26. package/src/context/assembly.ts +1 -1
  27. package/src/context/extension/assembly.ts +2 -2
  28. package/src/context/memory/budget/gate.ts +19 -3
  29. package/src/conversation/harness-resume.ts +89 -238
  30. package/src/conversation/harness-stream.ts +87 -71
  31. package/src/conversation/history.ts +154 -152
  32. package/src/conversation/message/contract.ts +28 -4
  33. package/src/conversation/message/conversion.ts +46 -5
  34. package/src/conversation/message/projection.ts +61 -21
  35. package/src/conversation/session-entry.ts +29 -0
  36. package/src/conversation/state.ts +23 -2
  37. package/src/harness/governance.ts +206 -0
  38. package/src/harness/index.ts +3 -0
  39. package/src/harness/session-harness.ts +269 -445
  40. package/src/harness/submission-operation.ts +15 -40
  41. package/src/index.ts +18 -8
  42. package/src/lib/artifacts.ts +18 -0
  43. package/src/lib/execution-level.ts +13 -0
  44. package/src/lib/prompt.ts +9 -10
  45. package/src/lib/token-estimate.ts +8 -0
  46. package/src/model-protocol/openrouter.ts +41 -0
  47. package/src/models/index.ts +200 -35
  48. package/src/models/openrouter/messages.ts +13 -8
  49. package/src/models/openrouter/openai.ts +200 -0
  50. package/src/models/openrouter/protocol-response.ts +85 -0
  51. package/src/models/provider-streams.ts +45 -0
  52. package/src/session/cloudflare/migration.ts +382 -864
  53. package/src/session/cloudflare/storage.ts +13 -536
  54. package/src/session/index.ts +22 -35
  55. package/src/skills/springbrand-worker-website/index.ts +1 -1
  56. package/src/submission/admission.ts +20 -30
  57. package/src/submission/contracts.ts +42 -2
  58. package/src/submission/delivery.ts +1 -1
  59. package/src/submission/finalization.ts +36 -15
  60. package/src/submission/lifecycle.ts +28 -15
  61. package/src/submission/repository.ts +97 -49
  62. package/src/submission/steer-delivery.ts +32 -76
  63. package/src/submission/unattended.ts +40 -0
  64. package/src/telemetry/contract.ts +0 -1
  65. package/src/telemetry/coordinator.ts +1 -1
  66. package/src/telemetry/runtime-telemetry.ts +0 -4
  67. package/src/telemetry/subagent-lifecycle.ts +1 -1
  68. package/src/telemetry/turn-events.ts +35 -25
  69. package/src/tools/README.md +44 -0
  70. package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
  71. package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
  72. package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +4 -4
  73. package/src/tools/assistant/ask-user.ts +319 -0
  74. package/src/tools/assistant/candidate.ts +15 -0
  75. package/src/tools/assistant/followups.ts +34 -0
  76. package/src/tools/assistant/index.ts +19 -0
  77. package/src/tools/assistant/memory.ts +73 -0
  78. package/src/tools/assistant/plan.ts +98 -0
  79. package/src/tools/{time.ts → assistant/time.ts} +1 -1
  80. package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -53
  81. package/src/tools/{core.ts → codemode/index.ts} +36 -92
  82. package/src/tools/codemode/workspace-connector.ts +198 -0
  83. package/src/tools/extension/index.ts +42 -0
  84. package/src/tools/index.ts +29 -26
  85. package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
  86. package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
  87. package/src/tools/runtime/activation.ts +40 -0
  88. package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
  89. package/src/tools/runtime/compiler.ts +246 -0
  90. package/src/tools/runtime/contracts.ts +140 -0
  91. package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +149 -39
  92. package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
  93. package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
  94. package/src/tools/runtime/result.ts +22 -0
  95. package/src/tools/runtime/unattended-error.ts +4 -0
  96. package/src/tools/sandbox/index.ts +228 -0
  97. package/src/tools/schedule/index.ts +301 -0
  98. package/src/tools/skill/contracts.ts +20 -0
  99. package/src/tools/skill/index.ts +99 -0
  100. package/src/tools/skill/resources.ts +322 -0
  101. package/src/tools/skill/scripts.ts +168 -0
  102. package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
  103. package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
  104. package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
  105. package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
  106. package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
  107. package/src/tools/{temporary-agent → subagent/temporary}/core.ts +2 -3
  108. package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
  109. package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
  110. package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
  111. package/src/tools/web/index.ts +2 -0
  112. package/src/tools/web/search/api.ts +1322 -0
  113. package/src/tools/web/search/index.ts +2 -0
  114. package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
  115. package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
  116. package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
  117. package/src/tools/base.ts +0 -534
  118. package/src/tools/compiler.ts +0 -327
  119. package/src/tools/schedule.ts +0 -261
  120. package/src/tools/skill.ts +0 -565
  121. package/src/tools/web-search/api.ts +0 -1318
  122. package/src/tools/web-search/index.ts +0 -2
  123. package/src/tools/workspace-state-connector.ts +0 -146
  124. package/src/tools/{approval → runtime/approval}/index.ts +0 -0
  125. package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
  126. package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
  127. package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
  128. package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
  129. package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
  130. package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
  131. /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
@@ -12,12 +12,14 @@ 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 {
18
20
  TemporaryAgentApprovalDecision,
19
21
  TemporaryAgentApprovalRequest,
20
- TemporaryAgentLaunch,
22
+ TemporaryAgentLaunchSelection,
21
23
  } from "../../tools";
22
24
  import { TEMPORARY_AGENT_LAUNCH_KEY } from "../../tools";
23
25
  import {
@@ -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> {
@@ -83,7 +88,7 @@ export interface LoadedRuntime<Config> {
83
88
  export interface RuntimeConfigUpdate<Change> {
84
89
  readonly changed: boolean;
85
90
  readonly change: Change;
86
- /** Restore any committed config state when the replacement Runtime cannot load. */
91
+ /** Restore committed state if an immediate replacement fails. */
87
92
  readonly rollback?: () => Promise<void>;
88
93
  }
89
94
 
@@ -96,16 +101,27 @@ interface RuntimeAgentDefinitionBase<
96
101
  /** 必须等于 Worker 导出名(Wrangler `class_name`)。 */
97
102
  readonly name: string;
98
103
 
99
- /** Refresh Host-owned inputs before a new Submission is pinned. Never runs on continuation/recovery. */
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<boolean>;
107
+ ) => Promise<void>;
108
+
109
+ /** Prepare per-Submission Host state after admission and before first execution. */
110
+ readonly afterSubmission?: (
111
+ context: RuntimeAgentPlanningContext<Env, Command, Change>,
112
+ ) => Promise<void>;
103
113
 
104
114
  readonly load: (
105
115
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
106
116
  requestedRuntimeKey?: string,
107
117
  ) => Promise<LoadedRuntime<Config>>;
108
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
+
109
125
  readonly tools: (
110
126
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
111
127
  config: Config,
@@ -114,7 +130,7 @@ interface RuntimeAgentDefinitionBase<
114
130
  /** Prepare the optional telemetry consumer for this concrete Agent facet. */
115
131
  readonly telemetry?: (
116
132
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
117
- ) => AgentTelemetryBinding | undefined;
133
+ ) => AgentTelemetryBinding | undefined | Promise<AgentTelemetryBinding | undefined>;
118
134
  }
119
135
 
120
136
  export interface ReadonlyRuntimeAgentDefinition<
@@ -262,7 +278,12 @@ export interface RuntimeAgentControls {
262
278
  */
263
279
  submitScheduledPrompt(
264
280
  prompt: string,
265
- opts?: { idempotencyKey?: string },
281
+ opts?: {
282
+ idempotencyKey?: string;
283
+ scheduledMaxExecutionLevel?: import("../../lib/execution-level").ExecutionLevel;
284
+ /** @deprecated Retained for rolling upgrades; ignored by Runtime. */
285
+ unattendedExecutionGrant?: import("../../submission").RuntimeUnattendedExecutionGrant;
286
+ },
266
287
  ): Promise<SubmissionReceipt>;
267
288
  /**
268
289
  * 按提交编号查询当前处理状态。
@@ -311,6 +332,7 @@ declare class RuntimeAgentHostSeam {
311
332
  protected prepareToolInteractionResponse(
312
333
  input: import("./agent").ToolInteractionResponsePreparation,
313
334
  ): Promise<unknown | null>;
335
+ protected hasUnfinishedRuntimeWork(): boolean;
314
336
  }
315
337
 
316
338
  /**
@@ -354,10 +376,9 @@ const TERMINAL_AGENT_TOOL_STATUSES = [
354
376
  "interrupted",
355
377
  ] as const;
356
378
  const AGENT_TOOL_RETENTION_MS = 24 * 60 * 60 * 1_000;
379
+ const STABLE_RUNTIME_KEY_MIGRATION = "agent-runtime:stable-runtime-key-migration-v1";
357
380
 
358
- function parseTemporaryAgentLaunch<Config>(
359
- input: unknown,
360
- ): TemporaryAgentLaunch<Config> {
381
+ function parseTemporaryAgentLaunch(input: unknown): TemporaryAgentLaunchSelection {
361
382
  const record = input != null && typeof input === "object"
362
383
  ? input as Record<string, unknown>
363
384
  : {};
@@ -375,17 +396,15 @@ function parseTemporaryAgentLaunch<Config>(
375
396
  ) {
376
397
  throw new Error("invalid temporary agent executionLevel");
377
398
  }
378
- if (!("config" in record)) {
379
- throw new Error("invalid temporary agent config");
380
- }
381
- return {
399
+ const selection: TemporaryAgentLaunchSelection = {
382
400
  subagentName: read("subagentName", 128),
383
401
  instructions: read("instructions", 65_536),
384
402
  task: read("task", 65_536),
385
403
  runtimeKey: read("runtimeKey", 512),
386
- config: record.config as Config,
387
404
  executionLevel: executionLevel as ExecutionLevel,
388
405
  };
406
+ if ("config" in record) throw new Error("unexpected temporary agent config");
407
+ return selection;
389
408
  }
390
409
 
391
410
  /**
@@ -406,15 +425,17 @@ export function defineRuntimeAgent<
406
425
  | MutableRuntimeAgentDefinition<Env, Config, Command, Change>
407
426
  | ReadonlyRuntimeAgentDefinition<Env, Config>,
408
427
  ): RuntimeAgentClass<Env, Command, Change> {
409
- const resolveDefinitionTelemetry = (
428
+ const resolveDefinitionTelemetry = async (
410
429
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
411
- ): AgentTelemetryBinding | undefined => {
430
+ ): Promise<AgentTelemetryBinding | undefined> => {
412
431
  const telemetry = definition.telemetry;
413
432
  if (!telemetry) return undefined;
414
- return (telemetry as (
433
+ return await (telemetry as (
415
434
  context: RuntimeAgentPlanningContext<Env, Command, Change>,
416
- ) => AgentTelemetryBinding | undefined)(context);
435
+ ) => AgentTelemetryBinding | undefined | Promise<AgentTelemetryBinding | undefined>)(context);
417
436
  };
437
+ const migrateSavedKeys = definition.migrateStoredRuntimeKeys as
438
+ RuntimeAgentDefinitionBase<Env, Config, Command, Change>["migrateStoredRuntimeKeys"];
418
439
 
419
440
  const GeneratedRuntimeAgent = {
420
441
  [definition.name]: class extends AgentToolRuntimeKernel<Env> {
@@ -423,9 +444,71 @@ export function defineRuntimeAgent<
423
444
  private loading?: Promise<void>;
424
445
  private telemetryResolved = false;
425
446
  private resolvedTelemetry?: AgentTelemetryBinding;
426
- private temporaryLaunch?: TemporaryAgentLaunch<Config>;
447
+ private temporaryLaunch?: TemporaryAgentLaunchSelection;
427
448
  private temporaryDispose?: () => Promise<void>;
428
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
+ }
429
512
 
430
513
  protected get role(): RuntimeAgentRole {
431
514
  return this.parentPath.at(-1)?.className === definition.name
@@ -440,15 +523,27 @@ export function defineRuntimeAgent<
440
523
  return this.ensureConfig();
441
524
  }
442
525
 
443
- protected override ensureRuntimeVersion(runtimeKey: string): Promise<void> {
444
- return this.ensureConfig(runtimeKey);
526
+ protected override ensureRuntimeVersion(runtimeKey: string, force = false): Promise<void> {
527
+ return this.ensureConfig(runtimeKey, force);
445
528
  }
446
529
 
447
- protected async prepareRuntimeForSubmission(submissionId: string): Promise<void> {
530
+ protected async prepareRuntimeForAdmission(): Promise<{
531
+ runtimeKey: string;
532
+ admission?: RuntimeSubmissionAdmissionHook;
533
+ }> {
448
534
  const beforeSubmission = definition.beforeSubmission as RuntimeAgentDefinitionBase<Env, Config, Command, Change>["beforeSubmission"];
535
+ await beforeSubmission?.(this.createDefinitionContext());
536
+ const requestedKey = this.role === "temporary"
537
+ ? this.loadedRuntimeKey ?? this.temporaryLaunch?.runtimeKey
538
+ : undefined;
539
+ return this.inspectCandidate(requestedKey);
540
+ }
541
+
542
+ protected async prepareRuntimeForSubmission(submissionId: string): Promise<void> {
543
+ const afterSubmission = definition.afterSubmission as RuntimeAgentDefinitionBase<Env, Config, Command, Change>["afterSubmission"];
449
544
  try {
450
- const changed = await beforeSubmission?.(this.createDefinitionContext(undefined, submissionId));
451
- await this.ensureConfig(undefined, changed === true);
545
+ await this.ensureRuntimeReady();
546
+ await afterSubmission?.(this.createDefinitionContext(undefined, submissionId));
452
547
  } catch (error) {
453
548
  this.hasLoadedRuntime = false;
454
549
  throw error;
@@ -462,7 +557,7 @@ export function defineRuntimeAgent<
462
557
  runtimeKey?: string,
463
558
  options?: { force?: boolean },
464
559
  ): Promise<void> {
465
- await this.ensureConfig(runtimeKey, options?.force === true);
560
+ await this.runConfigControl(() => this.ensureConfig(runtimeKey, options?.force === true));
466
561
  }
467
562
 
468
563
  async updateConfig(
@@ -475,26 +570,36 @@ export function defineRuntimeAgent<
475
570
  if (!update) {
476
571
  return { change: undefined as Change, runtime: "unchanged" };
477
572
  }
478
- const result = await update(this.createDefinitionContext(), command);
479
- if (!result.changed) {
480
- return { change: result.change, runtime: "unchanged" };
481
- }
482
- try {
483
- await this.ensureConfig(undefined, true);
484
- return { change: result.change, runtime: "reloaded" };
485
- } catch (reloadError) {
486
- if (result.rollback) {
487
- try {
488
- await result.rollback();
489
- } catch (rollbackError) {
490
- throw new AggregateError(
491
- [reloadError, rollbackError],
492
- "Runtime Config rollback failed",
493
- );
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" };
494
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
+ }
596
+ }
597
+ return { change: result.change, runtime: "reload-failed" };
495
598
  }
496
- return { change: result.change, runtime: "reload-failed" };
497
- }
599
+ };
600
+ return deferActivation
601
+ ? apply()
602
+ : this.runConfigControl(apply);
498
603
  }
499
604
 
500
605
  async getRuntimeAssembly(): Promise<RuntimeAssemblyView> {
@@ -546,13 +651,105 @@ export function defineRuntimeAgent<
546
651
  if (this.role !== "temporary") {
547
652
  throw new Error("Agent Tool runs require a temporary child facet");
548
653
  }
549
- const launch = parseTemporaryAgentLaunch<Config>(input);
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 };
550
661
  this.temporaryLaunch = launch;
551
662
  await this.ctx.storage.put(TEMPORARY_AGENT_LAUNCH_KEY, launch);
552
663
  await this.reloadRuntime(launch.runtimeKey, { force: true });
553
664
  return launch.task;
554
665
  }
555
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
+
556
753
  // 作用:把首次加载和按 key 重载合并成一个串行入口。
557
754
  // 调用:Runtime readiness、Session 配置和重载都会调用。
558
755
  // 原因:共享同一个 Promise 可合并并发首加载,显式切 key 则等待前序完成后再判断。
@@ -601,64 +798,16 @@ export function defineRuntimeAgent<
601
798
  // 先创建局部 Promise,再赋给 `loading`,失败时也能在 finally 中放开重试。
602
799
  const pending = (async () => {
603
800
  this.runtimeLoad.begin();
604
- const loadContext = this.createDefinitionContext();
605
- const loaded = await withRuntimeLoadTimeout(
606
- "definition.load",
607
- () => (
608
- definition.load as (
609
- context: RuntimeAgentPlanningContext<Env, Command, Change>,
610
- requestedRuntimeKey?: string,
611
- ) => Promise<LoadedRuntime<Config>>
612
- )(
613
- loadContext,
614
- requestedRuntimeKey,
615
- ),
616
- { timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
617
- );
618
- const resolvedRuntimeKey = loaded.runtimeKey.trim();
619
- if (!resolvedRuntimeKey) {
620
- throw new Error("Resolved Runtime key must not be empty");
621
- }
622
- const context = this.createDefinitionContext({
623
- value: loaded.config,
624
- });
625
- let tools = await withRuntimeLoadTimeout(
626
- "definition.tools",
627
- () => (
628
- definition.tools as (
629
- context: RuntimeAgentPlanningContext<Env, Command, Change>,
630
- config: Config,
631
- ) => ToolAssemblyResult | Promise<ToolAssemblyResult>
632
- )(context, loaded.config),
633
- { timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
634
- );
635
- let resources = loaded.resources;
636
- let commitGuard = loaded.commitGuard;
637
- if (!this.telemetryResolved) {
638
- this.resolvedTelemetry = resolveDefinitionTelemetry(context);
639
- this.telemetryResolved = true;
640
- }
641
- const telemetry = this.resolvedTelemetry;
642
- if (this.role === "temporary") {
643
- this.temporaryDispose = tools.dispose;
644
- tools = this.applyTemporaryToolPolicy(tools);
645
- resources = await this.applyTemporaryResources(resources);
646
- 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;
647
807
  }
648
- const candidate = await assembleRuntimeSnapshot({
649
- ctx: context,
650
- tools,
651
- resources,
652
- ...(commitGuard ? { commitGuard } : {}),
653
- ...(telemetry ? { telemetry } : {}),
654
- });
655
-
656
- await this.initCandidate({
657
- ...candidate,
658
- runtimeKey: resolvedRuntimeKey,
659
- });
808
+ if (this.role === "temporary") this.temporaryDispose = prepared.dispose;
660
809
  this.hasLoadedRuntime = true;
661
- this.loadedRuntimeKey = resolvedRuntimeKey;
810
+ this.loadedRuntimeKey = prepared.runtimeKey;
662
811
  this.runtimeLoad.complete();
663
812
  // 提交之前 turnEvents 绑定还不存在,此前每一次广播都到不了 Host。
664
813
  // Host 的会话列表把装配后的第一次投影当作「facet 已就绪」的信号。
@@ -718,7 +867,7 @@ export function defineRuntimeAgent<
718
867
  resources: ResolvedResources,
719
868
  ): Promise<ResolvedResources> {
720
869
  const launch = this.temporaryLaunch ??
721
- await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
870
+ await this.ctx.storage.get<TemporaryAgentLaunchSelection>(
722
871
  TEMPORARY_AGENT_LAUNCH_KEY,
723
872
  );
724
873
  if (!launch) throw new Error("Temporary Agent is not initialized");
@@ -749,7 +898,7 @@ export function defineRuntimeAgent<
749
898
  }
750
899
 
751
900
  private async requestParentTemporaryApproval(
752
- launch: TemporaryAgentLaunch<Config>,
901
+ launch: TemporaryAgentLaunchSelection,
753
902
  request: {
754
903
  toolCallId: string;
755
904
  toolName: string;
@@ -815,7 +964,7 @@ export function defineRuntimeAgent<
815
964
  this.temporaryCleanup = (async () => {
816
965
  if (!this.temporaryDispose) {
817
966
  const launch = this.temporaryLaunch ??
818
- await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
967
+ await this.ctx.storage.get<TemporaryAgentLaunchSelection>(
819
968
  TEMPORARY_AGENT_LAUNCH_KEY,
820
969
  );
821
970
  if (launch) await this.ensureConfig(launch.runtimeKey);
@@ -885,7 +1034,6 @@ export function defineRuntimeAgent<
885
1034
  input: {
886
1035
  ...normalized,
887
1036
  runtimeKey: agent.loadedRuntimeKey,
888
- config: runtimeConfig.value,
889
1037
  executionLevel: agent.executionLevel(),
890
1038
  },
891
1039
  runId: `temporary-agent:${runContext.toolCallId}`,
@@ -23,8 +23,26 @@ export {
23
23
  export {
24
24
  isValidTimeZone,
25
25
  nextCronOccurrence,
26
+ wallTimeInstants,
26
27
  zoneOffsetMs,
27
28
  } from "./scheduling/zoned-cron";
29
+ export {
30
+ DEFAULT_LATENESS_WINDOW_MS,
31
+ isWithinLatenessWindow,
32
+ nextScheduledOccurrence,
33
+ resolveScheduleOccurrences,
34
+ toScheduleRuleSpec,
35
+ validateScheduleRuleSpec,
36
+ type LocalScheduleTrigger,
37
+ type ScheduledOccurrence,
38
+ type ScheduleOccurrence,
39
+ type ScheduleOccurrenceResolution,
40
+ type ScheduleRuleError,
41
+ type ScheduleRuleErrorCode,
42
+ type ScheduleRuleSpec,
43
+ type ScheduleSkipReason,
44
+ type SkippedOccurrence,
45
+ } from "./scheduling/schedule-rule";
28
46
 
29
47
  export {
30
48
  CloudflareSandboxAdapter,