@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
@@ -1,49 +1,24 @@
1
- import type { OperationRequest, OperationResultRecord } from "@earendil-works/pi-agent-core";
2
- import type { Context } from "@earendil-works/pi-agent-core/harness/context";
1
+ import type { Context } from "@earendil-works/chord";
2
+ import type { EntryId } from "@earendil-works/pi-durable";
3
3
  import type { PiCanonicalUserInput } from "../conversation/message/contract";
4
4
  import type { StoredSubmission } from "../submission/repository";
5
- import type { SessionHarness } from "./session-harness";
5
+ import type { SessionHarness, SessionRunRequest } from "./session-harness";
6
6
 
7
- /** Recover the stable operation before accepting new input or navigating its branch. */
7
+ /** Reacquire Pi's stable Submission before admitting another model run. */
8
8
  export async function prepareSubmissionOperation(
9
- runtime: SessionHarness<object | undefined>,
9
+ runtime: SessionHarness,
10
10
  submission: Pick<StoredSubmission, "submissionId" | "queuedInputJson" | "regenerateMessageId">,
11
- regenerationTarget: (messageId: string) => string | null,
11
+ regenerationTarget: (messageId: string) => Promise<EntryId | null>,
12
12
  context: Context,
13
- ): Promise<{ kind: "run"; request?: OperationRequest } | { kind: "settled"; outcome: OperationResultRecord }> {
14
- const id = submission.submissionId;
15
- const navigationId = `${id}:regenerate`;
16
- if (runtime.openOperation?.operationId === navigationId) {
17
- const navigation = await runtime.run(undefined, context);
18
- if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
19
- throw new Error("Could not recover the regeneration branch navigation");
20
- }
21
- }
22
- if (runtime.openOperation) {
23
- if (runtime.openOperation.operationId !== id) {
24
- throw new Error(`Submission ${id} does not own the open Harness operation`);
25
- }
26
- return { kind: "run" };
27
- }
28
- const watch = await runtime.lane.watch(context);
29
- const previous = watch.snapshot.lastResult;
30
- watch.unsubscribe();
31
- if (previous?.operationId === id) return { kind: "settled", outcome: previous };
32
- if (!submission.queuedInputJson) {
33
- throw new Error(`Submission ${id} has no open Harness operation or queued input`);
34
- }
13
+ ): Promise<SessionRunRequest> {
14
+ const existing = await runtime.submissionFor(submission.submissionId, context);
15
+ if (existing) return { operationId: submission.submissionId };
16
+ if (!submission.queuedInputJson) throw new Error("The admitted Submission has no canonical input");
35
17
  const prompt = JSON.parse(submission.queuedInputJson) as PiCanonicalUserInput;
36
- if (prompt.role !== "user") throw new Error("Queued SpringBrand user message is invalid");
37
- if (submission.regenerateMessageId && previous?.operationId !== navigationId) {
38
- const navigation = await runtime.run({
39
- kind: "navigation",
40
- operationId: navigationId,
41
- targetId: regenerationTarget(submission.regenerateMessageId),
42
- options: { summarize: false },
43
- }, context);
44
- if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
45
- throw new Error("Could not navigate to the regeneration branch");
46
- }
18
+ if (prompt.role !== "user") throw new Error("Invalid canonical user input");
19
+ if (submission.regenerateMessageId) {
20
+ const target = await regenerationTarget(submission.regenerateMessageId);
21
+ await runtime.fork(target, context);
47
22
  }
48
- return { kind: "run", request: { kind: "prompt", operationId: id, prompt } };
23
+ return { operationId: submission.submissionId, prompt };
49
24
  }
package/src/index.ts CHANGED
@@ -51,7 +51,7 @@ export type { RuntimeAssemblyView } from "./config/assembly-view";
51
51
  export * from "./adapter/cloudflare/define-agent";
52
52
  export type { ToolInteractionResponsePreparation } from "./adapter/cloudflare/agent";
53
53
  export * from "./conversation/state";
54
- export * from "./tools/workspace-versioning";
54
+ export * from "./tools/workspace/versioning";
55
55
  export * from "./lib/execution-level";
56
56
  export type {
57
57
  RuntimeAssemblyInput,
@@ -84,16 +84,17 @@ export type {
84
84
  } from "./lib/host-actions";
85
85
  export {
86
86
  AGENT_TYPES,
87
- } from "./tools/subagent-types/registry";
87
+ } from "./tools/subagent/types/registry";
88
88
  export type {
89
89
  AgentType,
90
- } from "./tools/subagent-types/contract";
90
+ } from "./tools/subagent/types/contract";
91
91
  export type {
92
92
  PiToolCandidate,
93
93
  ToolExposureMode,
94
94
  } from "./tools";
95
95
  export { basePiToolCandidates } from "./tools";
96
96
  export { createPiDeclaredToolCandidate } from "./tools";
97
+ export { harnessToolActivation } from "./tools";
97
98
  export type {
98
99
  PiDeclaredTool,
99
100
  PiDeclaredToolCallContext,
@@ -126,20 +127,29 @@ export {
126
127
  export type {
127
128
  TemporaryAgentApprovalDecision,
128
129
  TemporaryAgentApprovalRequest,
129
- TemporaryAgentLaunch,
130
+ TemporaryAgentLaunchSelection,
130
131
  TemporaryAgentRequest,
131
132
  TemporaryAgentRunContext,
132
- } from "./tools/temporary-agent/core";
133
- export { TEMPORARY_AGENT_LAUNCH_KEY } from "./tools/temporary-agent/core";
133
+ } from "./tools/subagent/temporary/core";
134
+ export { TEMPORARY_AGENT_LAUNCH_KEY } from "./tools/subagent/temporary/core";
134
135
  export {
135
136
  temporaryAgentExtensionIsSafe,
136
137
  temporaryAgentToolAllowed,
137
- } from "./tools/temporary-agent/runner";
138
+ } from "./tools/subagent/temporary/runner";
138
139
  export {
139
140
  createTemporaryAgentWorkspace,
140
- } from "./tools/temporary-agent/workspace";
141
+ } from "./tools/subagent/temporary/workspace";
141
142
  export {
142
143
  createPiModels,
143
144
  modelRequestUrl,
144
145
  resolvePiModel,
145
146
  } from "./models";
147
+ export {
148
+ openRouterRequestUrl,
149
+ requestOpenRouter,
150
+ } from "./model-protocol/openrouter";
151
+ export type { OpenRouterRequestOptions } from "./model-protocol/openrouter";
152
+ export type {
153
+ ModelProtocolResponse,
154
+ OpenRouterProtocol,
155
+ } from "./models/openrouter/protocol-response";
@@ -4,9 +4,12 @@
4
4
  /** Stable narrow reference shared by messages, RPC responses and the UI. */
5
5
  export interface ArtifactRef {
6
6
  kind: "artifact_ref";
7
+ artifactId: string;
7
8
  path: string;
8
9
  bytes: number;
9
10
  hash: string;
11
+ workspaceRevision: string;
12
+ availability: "available" | "unavailable";
10
13
  preview: string;
11
14
  note: string;
12
15
  }
@@ -14,6 +17,15 @@ export interface ArtifactRef {
14
17
  export interface SpillWorkspace {
15
18
  /** 把溢出的 Artifact 内容写入指定 Workspace 路径。 */
16
19
  writeFile(path: string, content: string, mimeType?: string): Promise<void>;
20
+ /** 写入后验证物理文件,防止只持久化了一个不存在或被截断的引用。 */
21
+ stat?(path: string): Promise<{
22
+ type: "file" | "directory" | "symlink";
23
+ size: number;
24
+ updatedAt: number;
25
+ contentVersion?: string;
26
+ } | null>;
27
+ /** 写入后验证内容摘要;Artifact 全部是 UTF-8 文本。 */
28
+ readFileBytes?(path: string): Promise<Uint8Array | null>;
17
29
  }
18
30
 
19
31
  /**
@@ -33,6 +45,10 @@ export async function sha256Hex(text: string): Promise<string> {
33
45
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
34
46
  }
35
47
 
48
+ function artifactId(hash: string): string {
49
+ return `artifact_${hash.slice(0, 24)}`;
50
+ }
51
+
36
52
  /**
37
53
  * 把 Tool 结果转成可存储的文本,并选择 `.txt` 或 `.json` 扩展名。
38
54
  *
@@ -52,3 +68,5 @@ export function serializeOutput(output: unknown): { text: string; ext: "txt" | "
52
68
  return { text: String(output), ext: "txt" };
53
69
  }
54
70
  }
71
+
72
+ export { artifactId };
@@ -25,3 +25,16 @@ export function maxExecutionLevel(
25
25
  )
26
26
  ]!;
27
27
  }
28
+
29
+ /** The most permissive level shared by an Agent and a Scheduled Task ceiling. */
30
+ export function minExecutionLevel(
31
+ left: ExecutionLevel,
32
+ right: ExecutionLevel,
33
+ ): ExecutionLevel {
34
+ return EXECUTION_LEVELS[
35
+ Math.min(
36
+ EXECUTION_LEVELS.indexOf(left),
37
+ EXECUTION_LEVELS.indexOf(right),
38
+ )
39
+ ]!;
40
+ }
package/src/lib/prompt.ts CHANGED
@@ -17,9 +17,8 @@ export const PERSONA =
17
17
  // Names the stable runtime environment.
18
18
  export const RUNTIME =
19
19
  "Runtime: this agent runs on Cloudflare Workers. When execute is present, its Code Mode Dynamic Worker is your " +
20
- "instrument — code there runs with outbound network access (fetch), your " +
21
- "workspace filesystem (state.*), and the tools.* methods listed in its own description. Use it for raw or customized HTTP " +
22
- "requests, parsing a payload, hitting several known endpoints, or computing over a file. Write plain " +
20
+ "instrument — code there runs with your workspace filesystem (state.*) and the tools.* methods listed in its own description. " +
21
+ "Use it for tool composition, parsing a payload, or computing over a file. Write plain " +
23
22
  "JavaScript — the sandbox evaluates " +
24
23
  "it directly, so TypeScript type annotations (`: number`, `as Type`) are a syntax error, and there " +
25
24
  "is no Python interpreter or package manager to invoke. Use the JS/Workers equivalent of what you " +
@@ -58,8 +57,7 @@ export const TOOLS =
58
57
  "use state.* for workspace file operations, tools.* for other host capabilities, and sequential calls for repeated or durable operations. " +
59
58
  "Top-level-only Tools remain Direct even when called repeatedly. " +
60
59
  "For web tasks, use web_search for web discovery, current facts, cited research, " +
61
- "and public URL analysis. For web access specifically, use execute Code Mode only for raw or customized network requests, structured " +
62
- "API calls, or when web_search cannot retrieve the required content; do not use execute for ordinary web " +
60
+ "and public URL analysis; do not use execute for ordinary web " +
63
61
  "searches. When execute is absent, use the actually exposed Direct Tools. When sandbox_* tools are present, use that isolated Linux environment for Python/Node, " +
64
62
  "package managers, system commands, builds, tests and background processes; its filesystem is temporary, " +
65
63
  "persistent inputs are copied in automatically on first use, /userspace is read-only, and only explicitly " +
@@ -70,7 +68,10 @@ export const TOOLS =
70
68
  "prioritize verification, saving durable results, and the final response. " +
71
69
  "When related Tool calls can run independently, all are available inside execute, and execute is present, run them inside that execute rather than as parallel top-level calls; " +
72
70
  "only independent top-level-only Direct Tools should be issued together in one turn. When you reference " +
73
- "code, cite it as file_path:line_number.";
71
+ "code, cite it as file_path:line_number. " +
72
+ "When schedule tools are available, prepare a standalone Requirement (at most 1000 Unicode characters), a name (at most 50), a time rule and an explicit timezone. Ask for missing information. " +
73
+ "Use local once/daily/weekly rules for new scheduled tasks. The Host prepares the authorization scope and the user must confirm before saving; never claim a task was created before the Tool succeeds. " +
74
+ "One source Chat may create multiple tasks. Before editing, list tasks and use the exact Task ID; ask when the target is ambiguous. Never attach source history or hidden context to the Requirement.";
74
75
 
75
76
  // A real browser is the only way to observe what a page actually does, and the
76
77
  // shared failure mode is a browser tool that returns nothing and looks clean.
@@ -117,10 +118,8 @@ export const INTERACTION =
117
118
  "without that answer; required defaults to false. At most one question may request attachments. Do not add an accept field or tell the user to upload through the normal chat composer; " +
118
119
  "continue the same turn when its result arrives. " +
119
120
  "Don't use it when a sensible default lets you proceed. " +
120
- "After completing a substantive task (report, analysis, multi-step job), finish all work and other Tool calls, " +
121
- "then call suggest_followups (2-4 directions) as the last Tool call before your final answer. " +
122
- "After it returns, write your complete final answer and make no further Tool calls. Never for small talk or while a " +
123
- "task is still in progress; it is non-blocking, unlike ask_user.";
121
+ "When a substantive task (report, analysis, or multi-step job) is complete, call suggest_followups once before the final answer. " +
122
+ "Never use it for small talk, quick answers, or while a task is still in progress; it is non-blocking, unlike ask_user.";
124
123
 
125
124
  // Separates short always-on memory from indexed long-tail workspace files.
126
125
  export const MEMORY =
@@ -0,0 +1,8 @@
1
+ /** Conservative token estimate for prompt budgeting, not provider billing. */
2
+ export function estimateStringTokens(text: string): number {
3
+ if (!text) return 0;
4
+ return Math.ceil(Math.max(
5
+ text.length / 4,
6
+ text.split(/\s+/u).filter(Boolean).length * 1.3,
7
+ ));
8
+ }
@@ -0,0 +1,41 @@
1
+ import type { RuntimeModelEndpoint } from "../config/ports";
2
+
3
+ export interface OpenRouterRequestOptions {
4
+ readonly headers?: HeadersInit;
5
+ readonly signal?: AbortSignal;
6
+ readonly fetch?: typeof fetch;
7
+ }
8
+
9
+ export function openRouterRequestUrl(
10
+ endpoint: Pick<RuntimeModelEndpoint, "protocol" | "baseURL">,
11
+ ): string {
12
+ const baseURL = endpoint.baseURL.replace(/\/+$/, "");
13
+ switch (endpoint.protocol) {
14
+ case "openrouter-messages":
15
+ return `${baseURL}/v1/messages`;
16
+ case "openrouter-chat":
17
+ return `${baseURL}/chat/completions`;
18
+ case "openrouter-responses":
19
+ return `${baseURL}/responses`;
20
+ default:
21
+ throw new Error(`Not an OpenRouter protocol: ${endpoint.protocol}`);
22
+ }
23
+ }
24
+
25
+ export function requestOpenRouter(
26
+ endpoint: RuntimeModelEndpoint,
27
+ body: Readonly<Record<string, unknown>>,
28
+ options: OpenRouterRequestOptions = {},
29
+ ): Promise<Response> {
30
+ const url = openRouterRequestUrl(endpoint);
31
+ const headers = new Headers(endpoint.headers);
32
+ new Headers(options.headers).forEach((value, name) => headers.set(name, value));
33
+ headers.set("content-type", "application/json");
34
+ headers.set("authorization", `Bearer ${endpoint.apiKey}`);
35
+ return (options.fetch ?? globalThis.fetch)(url, {
36
+ method: "POST",
37
+ headers,
38
+ body: JSON.stringify(body),
39
+ ...(options.signal ? { signal: options.signal } : {}),
40
+ });
41
+ }