@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.
- package/package.json +11 -9
- package/src/adapter/cloudflare/agent.ts +233 -179
- package/src/adapter/cloudflare/define-agent.ts +252 -104
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/adapter/cloudflare/schema.ts +21 -2
- package/src/adapter/cloudflare/subagent/definition.ts +25 -13
- package/src/adapter/cloudflare/subagent/runner.ts +43 -50
- package/src/adapter/cloudflare/subagent/tools.ts +4 -4
- package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
- package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
- package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
- package/src/config/activation.ts +13 -15
- package/src/config/agent-context.ts +1 -1
- package/src/config/assembler.ts +48 -31
- package/src/config/assembly-snapshot.ts +1 -1
- package/src/config/definition.ts +1 -1
- package/src/config/harness-runtime.ts +77 -49
- package/src/config/index.ts +1 -1
- package/src/config/model-guidance.ts +20 -0
- package/src/config/ports.ts +57 -7
- package/src/config/{pinning.ts → prepared-runtime.ts} +59 -123
- package/src/config/public-contracts.ts +2 -2
- package/src/config/runtime-config.ts +2 -1
- package/src/context/assembly.ts +1 -1
- package/src/context/extension/assembly.ts +2 -2
- package/src/context/memory/budget/gate.ts +19 -3
- package/src/conversation/harness-resume.ts +89 -238
- package/src/conversation/harness-stream.ts +87 -71
- package/src/conversation/history.ts +154 -152
- package/src/conversation/message/contract.ts +28 -4
- package/src/conversation/message/conversion.ts +46 -5
- package/src/conversation/message/projection.ts +61 -21
- package/src/conversation/session-entry.ts +29 -0
- package/src/conversation/state.ts +23 -2
- package/src/harness/governance.ts +206 -0
- package/src/harness/index.ts +3 -0
- package/src/harness/session-harness.ts +269 -445
- package/src/harness/submission-operation.ts +15 -40
- package/src/index.ts +18 -8
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/prompt.ts +9 -10
- package/src/lib/token-estimate.ts +8 -0
- package/src/model-protocol/openrouter.ts +41 -0
- package/src/models/index.ts +200 -35
- package/src/models/openrouter/messages.ts +13 -8
- package/src/models/openrouter/openai.ts +200 -0
- package/src/models/openrouter/protocol-response.ts +85 -0
- package/src/models/provider-streams.ts +45 -0
- package/src/session/cloudflare/migration.ts +382 -864
- package/src/session/cloudflare/storage.ts +13 -536
- package/src/session/index.ts +22 -35
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +20 -30
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +36 -15
- package/src/submission/lifecycle.ts +28 -15
- package/src/submission/repository.ts +97 -49
- package/src/submission/steer-delivery.ts +32 -76
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/contract.ts +0 -1
- package/src/telemetry/coordinator.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +0 -4
- package/src/telemetry/subagent-lifecycle.ts +1 -1
- package/src/telemetry/turn-events.ts +35 -25
- package/src/tools/README.md +44 -0
- package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
- package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
- package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +4 -4
- package/src/tools/assistant/ask-user.ts +319 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/tools/{time.ts → assistant/time.ts} +1 -1
- package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -53
- package/src/tools/{core.ts → codemode/index.ts} +36 -92
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +29 -26
- package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
- package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
- package/src/tools/runtime/activation.ts +40 -0
- package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
- package/src/tools/runtime/compiler.ts +246 -0
- package/src/tools/runtime/contracts.ts +140 -0
- package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +149 -39
- package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
- package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
- package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
- package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
- package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
- package/src/tools/{temporary-agent → subagent/temporary}/core.ts +2 -3
- package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
- package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1322 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
- package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
- package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
- package/src/tools/base.ts +0 -534
- package/src/tools/compiler.ts +0 -327
- package/src/tools/schedule.ts +0 -261
- package/src/tools/skill.ts +0 -565
- package/src/tools/web-search/api.ts +0 -1318
- package/src/tools/web-search/index.ts +0 -2
- package/src/tools/workspace-state-connector.ts +0 -146
- package/src/tools/{approval → runtime/approval}/index.ts +0 -0
- package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
- package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
- /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
package/src/tools/compiler.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentHarnessTool,
|
|
3
|
-
AgentTool,
|
|
4
|
-
AgentToolResult,
|
|
5
|
-
} from "@earendil-works/pi-agent-core";
|
|
6
|
-
import type { ApprovalReceipt } from "../submission/contracts";
|
|
7
|
-
import {
|
|
8
|
-
boundDurableToolOutput,
|
|
9
|
-
type BudgetPolicy,
|
|
10
|
-
spillDurableToolOutput,
|
|
11
|
-
} from "../context/memory/budget/gate";
|
|
12
|
-
import {
|
|
13
|
-
type ArtifactRef,
|
|
14
|
-
serializeOutput,
|
|
15
|
-
type SpillWorkspace,
|
|
16
|
-
} from "../lib/artifacts";
|
|
17
|
-
import {
|
|
18
|
-
type ExecutionLevel,
|
|
19
|
-
} from "../lib/execution-level";
|
|
20
|
-
|
|
21
|
-
// 本文件沿用 `../../index.ts` 入口定义的 Tool Candidate 和 Tool Settlement 术语。
|
|
22
|
-
|
|
23
|
-
// #region Public contracts
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 声明一个 Tool 的结果由客户端投递,而不是由 `execute()` 产出。
|
|
27
|
-
*
|
|
28
|
-
* 带这个字段的 Tool 会在执行链里 park 住,等宿主经 `respondToolInteraction` 把响应送进来,
|
|
29
|
-
* 再用 `settle` 把响应映射成这次 `toolCallId` 的权威 ToolResult。它的 `execute` 不会被调用。
|
|
30
|
-
*
|
|
31
|
-
* 这是 AI SDK 里「没有 execute 的 user-interaction tool」的等价物;差别是我们 park 住整个 Turn,
|
|
32
|
-
* 而不是让 tool part 悬空到下一次请求 —— 我们的 transcript 是服务端权威的,悬空的 tool_use
|
|
33
|
-
* 会让下一次模型调用非法。
|
|
34
|
-
*/
|
|
35
|
-
export interface PiToolInteractionSpec {
|
|
36
|
-
/** 结合 Tool 输入校验客户端响应体;不通过则拒绝投递,park 保持不变。 */
|
|
37
|
-
readonly validateResponse: (input: unknown, response: unknown) => boolean;
|
|
38
|
-
/** 把通过校验的响应映射成 ToolResult;缺省是原样回传。 */
|
|
39
|
-
readonly settle?: (
|
|
40
|
-
input: unknown,
|
|
41
|
-
response: unknown,
|
|
42
|
-
) => Omit<AgentToolResult<unknown>, "terminate">;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
|
|
46
|
-
export type ToolExposureMode = "direct" | "codemode" | "both";
|
|
47
|
-
|
|
48
|
-
export interface PiToolCandidate {
|
|
49
|
-
readonly owner: string;
|
|
50
|
-
readonly tool: AgentTool<any, any>;
|
|
51
|
-
/** @internal Send the complete schema but hide it until provider Tool Search finds it. */
|
|
52
|
-
readonly deferLoading?: true;
|
|
53
|
-
/** Omitted means visible both directly and through Code Mode. */
|
|
54
|
-
readonly exposureMode?: ToolExposureMode;
|
|
55
|
-
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
56
|
-
readonly requiredExecutionLevel: ExecutionLevel;
|
|
57
|
-
/** Trusted parameter-level policy, evaluated before approval or dispatch. */
|
|
58
|
-
readonly requiredExecutionLevelForInput?: (
|
|
59
|
-
input: unknown,
|
|
60
|
-
) => ExecutionLevel | Promise<ExecutionLevel>;
|
|
61
|
-
/**
|
|
62
|
-
* 无条件需要人来决定:不看执行档位矩阵,每次调用都 park 出审批。
|
|
63
|
-
*
|
|
64
|
-
* 档位差是「够不够格自己跑」,不是「该不该问人」。花用户钱、改变账户
|
|
65
|
-
* 状态这类工具,一旦 Agent 档位被 `allow_level` 永久拉高就会静默执行,
|
|
66
|
-
* 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
|
|
67
|
-
*/
|
|
68
|
-
readonly alwaysRequiresApproval?: boolean;
|
|
69
|
-
/** Recovery may replay this Tool Call only when the candidate guarantees convergence. Omitted means non-idempotent. */
|
|
70
|
-
readonly retry?: "idempotent" | "non-idempotent";
|
|
71
|
-
readonly summary?: string;
|
|
72
|
-
readonly source?: ApprovalReceipt["source"];
|
|
73
|
-
readonly interaction?: PiToolInteractionSpec;
|
|
74
|
-
/** Large payloads spill by default; protocol-shaped results opt into structure. */
|
|
75
|
-
readonly outputBudget?: BudgetPolicy;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface CompileHarnessToolsOptions {
|
|
79
|
-
readonly workspace?: SpillWorkspace;
|
|
80
|
-
/** Recheck live authorization at the effect boundary, including safe replay. */
|
|
81
|
-
readonly authorize?: (
|
|
82
|
-
candidate: PiToolCandidate,
|
|
83
|
-
args: unknown,
|
|
84
|
-
invocation: { readonly toolCallId: string; readonly signal: AbortSignal },
|
|
85
|
-
) => void | Promise<void>;
|
|
86
|
-
/** Return an already decided interaction result without invoking the handler. */
|
|
87
|
-
readonly resolveReplay?: (
|
|
88
|
-
candidate: PiToolCandidate,
|
|
89
|
-
toolCallId: string,
|
|
90
|
-
args: unknown,
|
|
91
|
-
) => AgentToolResult<unknown> | undefined | Promise<AgentToolResult<unknown> | undefined>;
|
|
92
|
-
/** Optional business projection; Pi remains the authoritative Tool result. */
|
|
93
|
-
readonly onSettlement?: (
|
|
94
|
-
call: SettledPiToolCall,
|
|
95
|
-
) => void | Promise<void>;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** 描述一次已进入持久化结算边界的 Pi 工具调用。 */
|
|
99
|
-
export interface SettledPiToolCall {
|
|
100
|
-
readonly toolCallId: string;
|
|
101
|
-
readonly toolName: string;
|
|
102
|
-
readonly args: unknown;
|
|
103
|
-
readonly result: AgentToolResult<unknown>;
|
|
104
|
-
readonly isError: boolean;
|
|
105
|
-
/** Diagnostic-only original error; callers must not persist or expose it. */
|
|
106
|
-
readonly cause?: unknown;
|
|
107
|
-
readonly summary?: string;
|
|
108
|
-
readonly source?: ApprovalReceipt["source"];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// DO SQLite rows also contain arguments and transcript/recovery envelopes.
|
|
112
|
-
// Keep each serialized result below half the 2 MiB row limit.
|
|
113
|
-
const DURABLE_TOOL_RESULT_MAX_BYTES = 1024 * 1024;
|
|
114
|
-
const MEDIA_CAPTION_MAX_CHARS = 4 * 1024;
|
|
115
|
-
|
|
116
|
-
// #endregion
|
|
117
|
-
|
|
118
|
-
// #region Governance helpers
|
|
119
|
-
|
|
120
|
-
// 把任意失败值收敛为可安全显示的简短消息。
|
|
121
|
-
// 受治理工具在工具执行或结算抛错时调用它。
|
|
122
|
-
// 读取错误自身也可能抛错,因此最外层必须保留固定回退文本。
|
|
123
|
-
function errorMessage(error: unknown): string {
|
|
124
|
-
try {
|
|
125
|
-
if (error instanceof Error) {
|
|
126
|
-
return error.message || error.name;
|
|
127
|
-
}
|
|
128
|
-
return String(error);
|
|
129
|
-
} catch {
|
|
130
|
-
return "Tool execution failed";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Most Pi adapters keep the original Tool value in details and its serialized
|
|
135
|
-
// model form in one text block. Spill that original once instead of duplicating
|
|
136
|
-
// it inside an AgentToolResult JSON envelope.
|
|
137
|
-
function spillPayload(result: AgentToolResult<unknown>): unknown {
|
|
138
|
-
const text = result.content.length === 1 && result.content[0]?.type === "text"
|
|
139
|
-
? result.content[0].text
|
|
140
|
-
: undefined;
|
|
141
|
-
if (text !== undefined && serializeOutput(result.details).text === text) {
|
|
142
|
-
return result.details;
|
|
143
|
-
}
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function artifactResult(
|
|
148
|
-
result: AgentToolResult<unknown>,
|
|
149
|
-
ref: ArtifactRef,
|
|
150
|
-
): AgentToolResult<unknown> {
|
|
151
|
-
const presentation = result.details !== null && typeof result.details === "object"
|
|
152
|
-
? (result.details as Record<string, unknown>).presentation
|
|
153
|
-
: undefined;
|
|
154
|
-
let captionRemaining = MEDIA_CAPTION_MAX_CHARS;
|
|
155
|
-
const mediaContent = result.content.some((part) => part.type !== "text")
|
|
156
|
-
? result.content.flatMap((part): AgentToolResult<unknown>["content"] => {
|
|
157
|
-
if (part.type !== "text") return [part];
|
|
158
|
-
const text = part.text.slice(0, captionRemaining);
|
|
159
|
-
captionRemaining -= text.length;
|
|
160
|
-
return text ? [{ ...part, text }] : [];
|
|
161
|
-
})
|
|
162
|
-
: [];
|
|
163
|
-
return {
|
|
164
|
-
...result,
|
|
165
|
-
content: [{
|
|
166
|
-
type: "text",
|
|
167
|
-
text: [
|
|
168
|
-
ref.note,
|
|
169
|
-
`Path: ${ref.path}`,
|
|
170
|
-
`Bytes: ${ref.bytes}`,
|
|
171
|
-
`Preview:\n${ref.preview}`,
|
|
172
|
-
].join("\n"),
|
|
173
|
-
}, ...mediaContent],
|
|
174
|
-
details: presentation === undefined ? ref : { ...ref, presentation },
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function durableResult(
|
|
179
|
-
result: AgentToolResult<unknown>,
|
|
180
|
-
candidate: PiToolCandidate,
|
|
181
|
-
workspace: SpillWorkspace | undefined,
|
|
182
|
-
): Promise<AgentToolResult<unknown>> {
|
|
183
|
-
const spill = (candidate.outputBudget ?? { kind: "spill" }).kind === "spill";
|
|
184
|
-
let ref = spill
|
|
185
|
-
? await spillDurableToolOutput(spillPayload(result), { workspace })
|
|
186
|
-
: null;
|
|
187
|
-
const bounded = ref
|
|
188
|
-
? artifactResult(result, ref)
|
|
189
|
-
: boundDurableToolOutput(result) as AgentToolResult<unknown>;
|
|
190
|
-
const bytes = new TextEncoder().encode(JSON.stringify(bounded)).byteLength;
|
|
191
|
-
if (bytes <= DURABLE_TOOL_RESULT_MAX_BYTES) return bounded;
|
|
192
|
-
|
|
193
|
-
// Binary blocks cannot be truncated, and this boundary has no image resizer.
|
|
194
|
-
// Preserve the original where possible and fail before any SQLite write.
|
|
195
|
-
if (!spill) ref = await spillDurableToolOutput(spillPayload(result), { workspace });
|
|
196
|
-
throw new Error(
|
|
197
|
-
`Tool output exceeds the 1 MiB durable result limit (${bytes} bytes). ` +
|
|
198
|
-
"Return a smaller result, such as lower-resolution images or fewer media items; the original cannot be included inline. " +
|
|
199
|
-
(ref ? `Original output saved to Workspace: ${ref.path}` : "The original output could not be saved to Workspace."),
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function untilAborted<T>(
|
|
204
|
-
signal: AbortSignal | undefined,
|
|
205
|
-
run: () => T | Promise<T>,
|
|
206
|
-
): Promise<T> {
|
|
207
|
-
// ponytail: 这会立即释放 Turn,但无法杀死不配合的进程内副作用;需要物理终止的 Tool 必须放进可销毁的隔离执行器。
|
|
208
|
-
signal?.throwIfAborted();
|
|
209
|
-
if (!signal) return run();
|
|
210
|
-
let onAbort!: () => void;
|
|
211
|
-
const aborted = new Promise<never>((_resolve, reject) => {
|
|
212
|
-
onAbort = () => reject(
|
|
213
|
-
signal.reason ?? new DOMException("Aborted", "AbortError"),
|
|
214
|
-
);
|
|
215
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
216
|
-
});
|
|
217
|
-
try {
|
|
218
|
-
return await Promise.race([run(), aborted]);
|
|
219
|
-
} finally {
|
|
220
|
-
signal.removeEventListener("abort", onAbort);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// #endregion
|
|
225
|
-
|
|
226
|
-
// #region Governance and compilation
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Convert the configured candidate surface once at the AgentHarness boundary.
|
|
230
|
-
* Schema, argument preparation, progress, cancellation, replay classification,
|
|
231
|
-
* and output bounds stay attached to the original Tool declaration.
|
|
232
|
-
*/
|
|
233
|
-
export function compileHarnessTools(
|
|
234
|
-
candidates: readonly PiToolCandidate[],
|
|
235
|
-
options: CompileHarnessToolsOptions = {},
|
|
236
|
-
): AgentHarnessTool<object | undefined>[] {
|
|
237
|
-
return candidates.map((candidate) => {
|
|
238
|
-
const source = candidate.tool;
|
|
239
|
-
const tool = source.constrainedSampling === false
|
|
240
|
-
? source
|
|
241
|
-
: {
|
|
242
|
-
...source,
|
|
243
|
-
constrainedSampling: source.constrainedSampling ?? {
|
|
244
|
-
type: "json_schema" as const,
|
|
245
|
-
strict: "prefer" as const,
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
return {
|
|
249
|
-
...tool,
|
|
250
|
-
replay: candidate.retry === "idempotent" &&
|
|
251
|
-
candidate.requiredExecutionLevel === "safe" &&
|
|
252
|
-
!candidate.alwaysRequiresApproval &&
|
|
253
|
-
!candidate.requiredExecutionLevelForInput &&
|
|
254
|
-
!candidate.interaction
|
|
255
|
-
? "safe" as const
|
|
256
|
-
: "never" as const,
|
|
257
|
-
...(candidate.deferLoading ? { deferLoading: true as const } : {}),
|
|
258
|
-
async execute(
|
|
259
|
-
toolCallId,
|
|
260
|
-
args,
|
|
261
|
-
onUpdate,
|
|
262
|
-
_toolContext,
|
|
263
|
-
_invocation,
|
|
264
|
-
context,
|
|
265
|
-
) {
|
|
266
|
-
const signal = context.abortSignal ?? new AbortController().signal;
|
|
267
|
-
await options.authorize?.(candidate, args, {
|
|
268
|
-
toolCallId,
|
|
269
|
-
signal,
|
|
270
|
-
});
|
|
271
|
-
const replay = await options.resolveReplay?.(
|
|
272
|
-
candidate,
|
|
273
|
-
toolCallId,
|
|
274
|
-
args,
|
|
275
|
-
);
|
|
276
|
-
if (replay) return durableResult(replay, candidate, options.workspace);
|
|
277
|
-
let result: AgentToolResult<unknown>;
|
|
278
|
-
try {
|
|
279
|
-
result = await untilAborted(
|
|
280
|
-
signal,
|
|
281
|
-
async () => durableResult(
|
|
282
|
-
await source.execute(
|
|
283
|
-
toolCallId,
|
|
284
|
-
args,
|
|
285
|
-
signal,
|
|
286
|
-
(update) => onUpdate(update),
|
|
287
|
-
),
|
|
288
|
-
candidate,
|
|
289
|
-
options.workspace,
|
|
290
|
-
),
|
|
291
|
-
);
|
|
292
|
-
} catch (cause) {
|
|
293
|
-
const message = errorMessage(cause);
|
|
294
|
-
const failed = boundDurableToolOutput({
|
|
295
|
-
content: [{ type: "text", text: message }],
|
|
296
|
-
details: {},
|
|
297
|
-
}) as AgentToolResult<unknown>;
|
|
298
|
-
await options.onSettlement?.({
|
|
299
|
-
toolCallId,
|
|
300
|
-
toolName: source.name,
|
|
301
|
-
args,
|
|
302
|
-
result: failed,
|
|
303
|
-
isError: true,
|
|
304
|
-
cause,
|
|
305
|
-
summary: candidate.summary,
|
|
306
|
-
source: candidate.source,
|
|
307
|
-
});
|
|
308
|
-
throw cause instanceof Error && cause.message === message
|
|
309
|
-
? cause
|
|
310
|
-
: new Error(message, { cause });
|
|
311
|
-
}
|
|
312
|
-
await options.onSettlement?.({
|
|
313
|
-
toolCallId,
|
|
314
|
-
toolName: source.name,
|
|
315
|
-
args,
|
|
316
|
-
result,
|
|
317
|
-
isError: false,
|
|
318
|
-
summary: candidate.summary,
|
|
319
|
-
source: candidate.source,
|
|
320
|
-
});
|
|
321
|
-
return result;
|
|
322
|
-
},
|
|
323
|
-
} satisfies AgentHarnessTool<object | undefined>;
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// #endregion
|
package/src/tools/schedule.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentTool,
|
|
3
|
-
AgentToolResult,
|
|
4
|
-
} from "@earendil-works/pi-agent-core";
|
|
5
|
-
import { Type, type TSchema } from "@earendil-works/pi-ai";
|
|
6
|
-
import type {
|
|
7
|
-
RuntimeSchedulePort,
|
|
8
|
-
RuntimeScheduleUpdate,
|
|
9
|
-
} from "../config/ports";
|
|
10
|
-
import type { ScheduleSpec } from "../submission/contracts";
|
|
11
|
-
import { serializeOutput } from "../lib/artifacts";
|
|
12
|
-
import type { PiToolCandidate } from "./compiler";
|
|
13
|
-
|
|
14
|
-
const scheduleTriggerParameters = Type.Union([
|
|
15
|
-
Type.Object({
|
|
16
|
-
kind: Type.Literal("once-in"),
|
|
17
|
-
seconds: Type.Integer({
|
|
18
|
-
minimum: 1,
|
|
19
|
-
description: "Fire once, this many seconds from now.",
|
|
20
|
-
}),
|
|
21
|
-
}, { additionalProperties: false }),
|
|
22
|
-
Type.Object({
|
|
23
|
-
kind: Type.Literal("once-at"),
|
|
24
|
-
at: Type.String({
|
|
25
|
-
description:
|
|
26
|
-
"Fire once at this absolute UTC ISO time, e.g. 2026-07-20T09:00:00Z.",
|
|
27
|
-
}),
|
|
28
|
-
}, { additionalProperties: false }),
|
|
29
|
-
Type.Object({
|
|
30
|
-
kind: Type.Literal("cron"),
|
|
31
|
-
cron: Type.String({
|
|
32
|
-
description:
|
|
33
|
-
"Repeat on a standard UTC cron, e.g. '0 9 * * *' = daily 09:00 UTC.",
|
|
34
|
-
}),
|
|
35
|
-
}, { additionalProperties: false }),
|
|
36
|
-
Type.Object({
|
|
37
|
-
kind: Type.Literal("interval"),
|
|
38
|
-
seconds: Type.Integer({
|
|
39
|
-
minimum: 1,
|
|
40
|
-
description: "Repeat every this many seconds.",
|
|
41
|
-
}),
|
|
42
|
-
}, { additionalProperties: false }),
|
|
43
|
-
], { description: "When to fire." });
|
|
44
|
-
|
|
45
|
-
const scheduleParameters = Type.Object({
|
|
46
|
-
prompt: Type.String({
|
|
47
|
-
description:
|
|
48
|
-
"The standalone instruction to run when the schedule fires. The future turn has no context from this chat, so make it self-contained.",
|
|
49
|
-
}),
|
|
50
|
-
trigger: scheduleTriggerParameters,
|
|
51
|
-
label: Type.String({
|
|
52
|
-
description:
|
|
53
|
-
"A short human-readable name for this task (e.g. '每日周报提醒'). Required — it is how the user recognises the task in their schedule list and how they will refer to it when asking you to cancel it later.",
|
|
54
|
-
}),
|
|
55
|
-
timezone: Type.Optional(Type.String({
|
|
56
|
-
description:
|
|
57
|
-
"IANA timezone the clock times in `trigger` should be interpreted in, e.g. 'Asia/Shanghai'. Pass the user's own timezone whenever they state a wall-clock time. Omit only for delays and intervals. Defaults to UTC.",
|
|
58
|
-
})),
|
|
59
|
-
}, { additionalProperties: false });
|
|
60
|
-
|
|
61
|
-
const noParameters = Type.Object({}, { additionalProperties: false });
|
|
62
|
-
const scheduleIdParameters = Type.Object({
|
|
63
|
-
id: Type.String({
|
|
64
|
-
description: "The schedule id, as returned by `list_schedules`.",
|
|
65
|
-
}),
|
|
66
|
-
}, { additionalProperties: false });
|
|
67
|
-
const updateScheduleParameters = Type.Object({
|
|
68
|
-
id: Type.String({ description: "The exact id returned by `list_schedules`." }),
|
|
69
|
-
prompt: Type.Optional(Type.String()),
|
|
70
|
-
trigger: Type.Optional(scheduleTriggerParameters),
|
|
71
|
-
label: Type.Optional(Type.String()),
|
|
72
|
-
timezone: Type.Optional(Type.String()),
|
|
73
|
-
}, { additionalProperties: false });
|
|
74
|
-
const changeScheduleAgentParameters = Type.Object({
|
|
75
|
-
id: Type.String({
|
|
76
|
-
description: "The exact schedule id returned by `list_schedules`.",
|
|
77
|
-
}),
|
|
78
|
-
userAgentId: Type.String({
|
|
79
|
-
description: "The exact User Agent id selected by the user.",
|
|
80
|
-
}),
|
|
81
|
-
}, { additionalProperties: false });
|
|
82
|
-
|
|
83
|
-
function result<T>(details: T): AgentToolResult<T> {
|
|
84
|
-
return {
|
|
85
|
-
content: [{ type: "text", text: serializeOutput(details).text }],
|
|
86
|
-
details,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function candidate<T extends TSchema>(
|
|
91
|
-
tool: AgentTool<T>,
|
|
92
|
-
options: Partial<
|
|
93
|
-
Pick<
|
|
94
|
-
PiToolCandidate,
|
|
95
|
-
"alwaysRequiresApproval" | "exposureMode" | "owner" | "requiredExecutionLevel" | "retry" | "summary"
|
|
96
|
-
>
|
|
97
|
-
> = {},
|
|
98
|
-
): PiToolCandidate {
|
|
99
|
-
return {
|
|
100
|
-
owner: options.owner ?? "runtime-base",
|
|
101
|
-
exposureMode: options.exposureMode ?? "direct",
|
|
102
|
-
requiredExecutionLevel: options.requiredExecutionLevel ?? "safe",
|
|
103
|
-
source: "action",
|
|
104
|
-
tool,
|
|
105
|
-
...(options.alwaysRequiresApproval
|
|
106
|
-
? { alwaysRequiresApproval: true }
|
|
107
|
-
: {}),
|
|
108
|
-
...(options.summary ? { summary: options.summary } : {}),
|
|
109
|
-
...(options.retry ? { retry: options.retry } : {}),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function schedulePiToolCandidates(
|
|
114
|
-
schedule: RuntimeSchedulePort,
|
|
115
|
-
): PiToolCandidate[] {
|
|
116
|
-
return [
|
|
117
|
-
candidate(
|
|
118
|
-
{
|
|
119
|
-
name: "schedule",
|
|
120
|
-
label: "Schedule prompt",
|
|
121
|
-
description:
|
|
122
|
-
"Schedule a prompt to run LATER, autonomously. Use when the user asks to be reminded, or to run something after a delay / at a time / on a recurring basis. The user always reviews the proposed schedule before it is created. The scheduled prompt runs in its own dedicated session at each trigger with NO memory of this conversation, so phrase `prompt` as a fully standalone instruction. After scheduling, tell the user what you set.",
|
|
123
|
-
parameters: scheduleParameters,
|
|
124
|
-
async execute(_toolCallId, input, signal) {
|
|
125
|
-
signal?.throwIfAborted();
|
|
126
|
-
const spec: ScheduleSpec = {
|
|
127
|
-
trigger: input.trigger,
|
|
128
|
-
job: { kind: "prompt", prompt: input.prompt },
|
|
129
|
-
label: input.label,
|
|
130
|
-
...(input.timezone ? { tz: input.timezone } : {}),
|
|
131
|
-
};
|
|
132
|
-
const { id } = await schedule.create(spec);
|
|
133
|
-
return result({ scheduled: true, id });
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
alwaysRequiresApproval: true,
|
|
138
|
-
exposureMode: "direct",
|
|
139
|
-
summary: "Create a scheduled task",
|
|
140
|
-
},
|
|
141
|
-
),
|
|
142
|
-
candidate(
|
|
143
|
-
{
|
|
144
|
-
name: "list_schedules",
|
|
145
|
-
label: "List schedules",
|
|
146
|
-
description:
|
|
147
|
-
"List the user's existing scheduled tasks. Call this when the user asks what they have scheduled, or before cancelling one — you need the id, and the user will refer to a task by what it does, not by its id.",
|
|
148
|
-
parameters: noParameters,
|
|
149
|
-
async execute(_toolCallId, _input, signal) {
|
|
150
|
-
signal?.throwIfAborted();
|
|
151
|
-
return result({ schedules: await schedule.list() });
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{ retry: "idempotent" },
|
|
155
|
-
),
|
|
156
|
-
candidate({
|
|
157
|
-
name: "update_schedule",
|
|
158
|
-
label: "Update schedule",
|
|
159
|
-
description:
|
|
160
|
-
"Update one scheduled task without changing its id or history. Call `list_schedules` first and use an exact id. If multiple tasks plausibly match the user's wording, ask which one instead of guessing.",
|
|
161
|
-
parameters: updateScheduleParameters,
|
|
162
|
-
async execute(_toolCallId, input, signal) {
|
|
163
|
-
signal?.throwIfAborted();
|
|
164
|
-
const changes: RuntimeScheduleUpdate = {
|
|
165
|
-
...(input.prompt !== undefined ? { prompt: input.prompt } : {}),
|
|
166
|
-
...(input.trigger !== undefined ? { trigger: input.trigger } : {}),
|
|
167
|
-
...(input.label !== undefined ? { label: input.label } : {}),
|
|
168
|
-
...(input.timezone !== undefined ? { tz: input.timezone } : {}),
|
|
169
|
-
};
|
|
170
|
-
const { ok } = await schedule.update(input.id, changes);
|
|
171
|
-
return result({ updated: ok, id: input.id });
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
candidate({
|
|
175
|
-
name: "pause_schedule",
|
|
176
|
-
label: "Pause schedule",
|
|
177
|
-
description:
|
|
178
|
-
"Pause one scheduled task. Call `list_schedules` first and use an exact id; ask the user if the target is ambiguous.",
|
|
179
|
-
parameters: scheduleIdParameters,
|
|
180
|
-
async execute(_toolCallId, input, signal) {
|
|
181
|
-
signal?.throwIfAborted();
|
|
182
|
-
const { ok } = await schedule.pause(input.id);
|
|
183
|
-
return result({ paused: ok, id: input.id });
|
|
184
|
-
},
|
|
185
|
-
}),
|
|
186
|
-
candidate({
|
|
187
|
-
name: "resume_schedule",
|
|
188
|
-
label: "Resume schedule",
|
|
189
|
-
description:
|
|
190
|
-
"Resume one paused scheduled task. Call `list_schedules` first and use an exact id; ask the user if the target is ambiguous.",
|
|
191
|
-
parameters: scheduleIdParameters,
|
|
192
|
-
async execute(_toolCallId, input, signal) {
|
|
193
|
-
signal?.throwIfAborted();
|
|
194
|
-
const { ok } = await schedule.resume(input.id);
|
|
195
|
-
return result({ resumed: ok, id: input.id });
|
|
196
|
-
},
|
|
197
|
-
}),
|
|
198
|
-
candidate(
|
|
199
|
-
{
|
|
200
|
-
name: "cancel_schedule",
|
|
201
|
-
label: "Cancel schedule",
|
|
202
|
-
description:
|
|
203
|
-
"Cancel a scheduled task by id. Call `list_schedules` first to find the id that matches what the user described. If more than one plausibly matches, ask the user which one instead of guessing — cancelling is not reversible.",
|
|
204
|
-
parameters: scheduleIdParameters,
|
|
205
|
-
async execute(_toolCallId, input, signal) {
|
|
206
|
-
signal?.throwIfAborted();
|
|
207
|
-
const { ok } = await schedule.cancel(input.id);
|
|
208
|
-
return result(ok
|
|
209
|
-
? { cancelled: true, id: input.id }
|
|
210
|
-
: {
|
|
211
|
-
cancelled: false,
|
|
212
|
-
id: input.id,
|
|
213
|
-
reason: "no schedule with that id",
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
requiredExecutionLevel: "high",
|
|
219
|
-
summary: "Cancel a scheduled task",
|
|
220
|
-
},
|
|
221
|
-
),
|
|
222
|
-
candidate(
|
|
223
|
-
{
|
|
224
|
-
name: "list_user_agents",
|
|
225
|
-
label: "List User Agents",
|
|
226
|
-
description:
|
|
227
|
-
"List the enabled User Agents available for scheduled tasks. " +
|
|
228
|
-
"Use this before changing a schedule Agent.",
|
|
229
|
-
parameters: noParameters,
|
|
230
|
-
async execute(_toolCallId, _input, signal) {
|
|
231
|
-
signal?.throwIfAborted();
|
|
232
|
-
return result({ agents: await schedule.listAgents() });
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
{ owner: "schedule-host" },
|
|
236
|
-
),
|
|
237
|
-
candidate(
|
|
238
|
-
{
|
|
239
|
-
name: "change_schedule_agent",
|
|
240
|
-
label: "Change schedule Agent",
|
|
241
|
-
description:
|
|
242
|
-
"Change which User Agent executes one scheduled task without " +
|
|
243
|
-
"changing its id or history. Call `list_schedules` and " +
|
|
244
|
-
"`list_user_agents` first and use exact ids; ask the user when " +
|
|
245
|
-
"the target is ambiguous.",
|
|
246
|
-
parameters: changeScheduleAgentParameters,
|
|
247
|
-
async execute(_toolCallId, input, signal) {
|
|
248
|
-
signal?.throwIfAborted();
|
|
249
|
-
const { ok } = await schedule.changeAgent(
|
|
250
|
-
input.id,
|
|
251
|
-
input.userAgentId,
|
|
252
|
-
);
|
|
253
|
-
return result({ updated: ok, id: input.id });
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{ owner: "schedule-host" },
|
|
257
|
-
),
|
|
258
|
-
];
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** 从 {@link RuntimeSchedulePort} 生成定时任务 Tool 集。 */
|