@foldrun/core 2.4.0 → 2.6.0
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/dist/src/agent-activity.d.ts +5 -0
- package/dist/src/agent-activity.js +31 -0
- package/dist/src/agent-import.js +2 -2
- package/dist/src/agent-tools.js +2 -2
- package/dist/src/agents-md.d.ts +5 -1
- package/dist/src/agents-md.js +42 -18
- package/dist/src/alerts.d.ts +60 -0
- package/dist/src/alerts.js +161 -0
- package/dist/src/browser-pod.js +4 -0
- package/dist/src/capture.d.ts +23 -0
- package/dist/src/capture.js +80 -0
- package/dist/src/compare.d.ts +57 -0
- package/dist/src/compare.js +116 -0
- package/dist/src/completions.js +26 -3
- package/dist/src/deploy.d.ts +36 -1
- package/dist/src/deploy.js +237 -13
- package/dist/src/egress.d.ts +16 -1
- package/dist/src/evals.d.ts +27 -2
- package/dist/src/evals.js +62 -19
- package/dist/src/flow-lint.d.ts +14 -0
- package/dist/src/flow-lint.js +35 -0
- package/dist/src/flow-patterns.js +29 -14
- package/dist/src/frontmatter-fields.d.ts +27 -0
- package/dist/src/frontmatter-fields.js +60 -3
- package/dist/src/frontmatter-guard.d.ts +19 -0
- package/dist/src/frontmatter-guard.js +67 -0
- package/dist/src/frontmatter-schema.d.ts +2 -2
- package/dist/src/frontmatter-schema.js +33 -2
- package/dist/src/frontmatter-text.d.ts +31 -0
- package/dist/src/frontmatter-text.js +102 -0
- package/dist/src/history.d.ts +4 -0
- package/dist/src/history.js +15 -0
- package/dist/src/hooks.js +1 -1
- package/dist/src/insights.d.ts +30 -0
- package/dist/src/insights.js +152 -0
- package/dist/src/issues.d.ts +140 -0
- package/dist/src/issues.js +302 -0
- package/dist/src/judge-verdict.d.ts +19 -0
- package/dist/src/judge-verdict.js +54 -0
- package/dist/src/library.js +1 -1
- package/dist/src/limits.d.ts +8 -0
- package/dist/src/limits.js +36 -5
- package/dist/src/linkable.js +1 -1
- package/dist/src/matter.d.ts +42 -0
- package/dist/src/matter.js +132 -0
- package/dist/src/model-credential.d.ts +14 -0
- package/dist/src/model-credential.js +40 -0
- package/dist/src/network.d.ts +12 -1
- package/dist/src/network.js +35 -8
- package/dist/src/notify.d.ts +16 -0
- package/dist/src/notify.js +17 -7
- package/dist/src/okf.js +1 -1
- package/dist/src/online-checks.d.ts +76 -0
- package/dist/src/online-checks.js +432 -0
- package/dist/src/operator.d.ts +277 -0
- package/dist/src/operator.js +176 -2
- package/dist/src/otel-config.d.ts +18 -0
- package/dist/src/otel-config.js +69 -0
- package/dist/src/otel.d.ts +38 -0
- package/dist/src/otel.js +85 -0
- package/dist/src/outcome.d.ts +40 -0
- package/dist/src/outcome.js +77 -0
- package/dist/src/package.js +9 -2
- package/dist/src/platform.d.ts +1 -1
- package/dist/src/prompt-layers.d.ts +30 -0
- package/dist/src/prompt-layers.js +77 -0
- package/dist/src/provider-health.js +2 -2
- package/dist/src/providers.d.ts +5 -0
- package/dist/src/providers.js +2 -2
- package/dist/src/run-container.d.ts +25 -2
- package/dist/src/run-container.js +61 -6
- package/dist/src/runner.d.ts +53 -2
- package/dist/src/runner.js +471 -102
- package/dist/src/runs-filter.d.ts +48 -0
- package/dist/src/runs-filter.js +73 -0
- package/dist/src/scan.d.ts +30 -1
- package/dist/src/scan.js +145 -14
- package/dist/src/starter.js +27 -2
- package/dist/src/step-exec.d.ts +39 -3
- package/dist/src/step-exec.js +118 -15
- package/dist/src/store.d.ts +144 -3
- package/dist/src/store.js +150 -7
- package/dist/src/tokens.d.ts +38 -0
- package/dist/src/tokens.js +57 -0
- package/dist/src/tool-names.js +1 -1
- package/dist/src/tool-programs.js +1 -1
- package/dist/src/trace-tree.d.ts +40 -0
- package/dist/src/trace-tree.js +156 -0
- package/dist/src/translator.d.ts +8 -0
- package/dist/src/translator.js +7 -2
- package/dist/src/trigger-log.d.ts +1 -1
- package/dist/src/workspace-check.d.ts +18 -0
- package/dist/src/workspace-check.js +34 -0
- package/package.json +2 -2
- package/src/agent-activity.ts +32 -0
- package/src/agent-import.ts +2 -2
- package/src/agent-tools.ts +2 -2
- package/src/agents-md.ts +39 -14
- package/src/alerts.ts +190 -0
- package/src/browser-pod.ts +3 -0
- package/src/capture.ts +88 -0
- package/src/compare.ts +146 -0
- package/src/completions.ts +27 -3
- package/src/deploy.ts +238 -12
- package/src/egress.ts +12 -2
- package/src/evals.ts +64 -19
- package/src/flow-lint.ts +36 -0
- package/src/flow-patterns.ts +26 -13
- package/src/frontmatter-fields.ts +83 -3
- package/src/frontmatter-guard.ts +70 -0
- package/src/frontmatter-schema.ts +33 -4
- package/src/frontmatter-text.ts +109 -0
- package/src/history.ts +13 -0
- package/src/hooks.ts +1 -1
- package/src/insights.ts +174 -0
- package/src/issues.ts +396 -0
- package/src/judge-verdict.ts +52 -0
- package/src/library.ts +1 -1
- package/src/limits.ts +38 -5
- package/src/linkable.ts +1 -1
- package/src/matter.ts +145 -0
- package/src/model-credential.ts +45 -0
- package/src/network.ts +35 -7
- package/src/notify.ts +22 -7
- package/src/okf.ts +1 -1
- package/src/online-checks.ts +450 -0
- package/src/operator.ts +213 -3
- package/src/otel-config.ts +67 -0
- package/src/otel.ts +100 -0
- package/src/outcome.ts +90 -0
- package/src/package.ts +8 -2
- package/src/platform.ts +1 -1
- package/src/prompt-layers.ts +90 -0
- package/src/provider-health.ts +2 -2
- package/src/providers.ts +7 -2
- package/src/run-container.ts +77 -8
- package/src/runner.ts +477 -79
- package/src/runs-filter.ts +88 -0
- package/src/scan.ts +153 -14
- package/src/starter.ts +27 -2
- package/src/step-exec.ts +168 -17
- package/src/store.ts +268 -9
- package/src/tokens.ts +68 -0
- package/src/tool-names.ts +1 -1
- package/src/tool-programs.ts +1 -1
- package/src/trace-tree.ts +187 -0
- package/src/translator.ts +13 -2
- package/src/trigger-log.ts +1 -0
- package/src/workspace-check.ts +41 -0
package/src/step-exec.ts
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
// results. That is what lets the same function run somewhere the vault, the
|
|
9
9
|
// library and the account do not exist.
|
|
10
10
|
|
|
11
|
+
import { judgeVerdict, JUDGE_VERDICT_INSTRUCTION } from "./judge-verdict.ts";
|
|
11
12
|
import { runHook, runToolCallHooks, matchesTool, hookLine, resolveHooks, type HookDef, type HookRecord } from "./hooks.ts";
|
|
12
13
|
import fs from "node:fs";
|
|
13
14
|
import os from "node:os";
|
|
14
15
|
import path from "node:path";
|
|
15
16
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
16
17
|
import type { HookInput, McpServerConfig, SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
17
|
-
import type
|
|
18
|
+
import { CAPTURE_CAP, type Effort, type ModelCall } from "./store.ts";
|
|
18
19
|
import { costOf, type TokenPrice } from "./catalog.ts";
|
|
19
20
|
import type { TestEffect } from "./test-mode.ts";
|
|
20
21
|
import type { OperatorEvent } from "./operator.ts";
|
|
@@ -23,7 +24,7 @@ import { checkPaths, checkBash, isFilesystemTool, linkWorkspace, workspaceLinkEn
|
|
|
23
24
|
import { DELEGATE_TOOLS, subagentGuard, toAgentDefinitions, type SubagentSpec } from "./subagents.ts";
|
|
24
25
|
import { hostSafeEnv } from "./host-env.ts";
|
|
25
26
|
import { validateSchema, describeSchemaErrors, looksLikeSchema } from "./json-schema.ts";
|
|
26
|
-
import { CallCounter,
|
|
27
|
+
import { CallCounter, callKeysFor, type Limits } from "./limits.ts";
|
|
27
28
|
import { classifyCall, podAt, readPodEvents, type BrowserPodOutcome, type CallKind, type ClassifyContext } from "./browser-pod.ts";
|
|
28
29
|
|
|
29
30
|
export interface ExecUsage {
|
|
@@ -86,6 +87,11 @@ export interface ExecOutcome {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export interface ExecOptions {
|
|
90
|
+
/** What the step reports beyond its events (FOL-54 step 4): `metadata`
|
|
91
|
+
* sends each model call (`turn`); `full` also sends each tool call's input
|
|
92
|
+
* and output (`io`). Unset sends neither — the consult, the probe and any
|
|
93
|
+
* caller that does not store them. */
|
|
94
|
+
capture?: "metadata" | "full";
|
|
89
95
|
/** `hooks:` resolved host-side (hooks.ts): run at this step's own dispatch,
|
|
90
96
|
* inside its sandbox. `hookBase` is what every hook event carries. */
|
|
91
97
|
hooks?: HookDef[];
|
|
@@ -128,6 +134,11 @@ export interface ExecOptions {
|
|
|
128
134
|
* `matches: re`, `file: path`, `judge: sentence`) — see checkVerify. */
|
|
129
135
|
verify?: string;
|
|
130
136
|
verifyEnv?: Record<string, string>;
|
|
137
|
+
/** The model `verify: judge:` grades with — the workspace's `judge:` from
|
|
138
|
+
* AGENTS.md, resolved; absent, the fast tier. */
|
|
139
|
+
judgeModel?: string;
|
|
140
|
+
/** And how long it thinks — `judge: {effort}`; absent, the SDK's default. */
|
|
141
|
+
judgeEffort?: Effort;
|
|
131
142
|
/** `output: json` — the reply must carry one JSON value; extracting it is
|
|
132
143
|
* part of finishing the step, and failing to is failing the step. */
|
|
133
144
|
output?: "json";
|
|
@@ -195,7 +206,50 @@ export type QueryLike = AsyncIterable<unknown> & { interrupt(): Promise<unknown>
|
|
|
195
206
|
export type QueryFn = (args: { prompt: string; options: Record<string, unknown> }) => QueryLike;
|
|
196
207
|
|
|
197
208
|
/** The pairing fields on a tool event — see RunEvent in store.ts. */
|
|
198
|
-
export type EventExtra = {
|
|
209
|
+
export type EventExtra = {
|
|
210
|
+
call?: string;
|
|
211
|
+
ms?: number;
|
|
212
|
+
err?: boolean;
|
|
213
|
+
bytes?: number;
|
|
214
|
+
effect?: TestEffect;
|
|
215
|
+
operator?: OperatorEvent;
|
|
216
|
+
subagent?: string;
|
|
217
|
+
check?: boolean;
|
|
218
|
+
hook?: HookRecord;
|
|
219
|
+
/** A tool call's content, on its way to the runner (FOL-54 step 4): the
|
|
220
|
+
* runner masks it, stores it beside the run and keeps only `captured` on
|
|
221
|
+
* the event. Sent only under `capture: "full"`. */
|
|
222
|
+
io?: CallIo;
|
|
223
|
+
/** One model call, on its way to the runner's `step.calls` — not an event. */
|
|
224
|
+
turn?: ModelCall;
|
|
225
|
+
/** The model's first response of the step has begun streaming — the
|
|
226
|
+
* moment the prompt cache entry its request wrote becomes readable. Sent
|
|
227
|
+
* once; the runner's fan-out gate reads it. Not an event. */
|
|
228
|
+
responding?: true;
|
|
229
|
+
/** Set by the runner once it stored `io` (RunEvent.captured). */
|
|
230
|
+
captured?: boolean;
|
|
231
|
+
/** On a tool call: the model call (its message id) that asked for it. */
|
|
232
|
+
inCall?: string;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** A tool call's input (on the call) or output (on its completion), capped. */
|
|
236
|
+
export interface CallIo {
|
|
237
|
+
input?: string;
|
|
238
|
+
output?: string;
|
|
239
|
+
/** The whole input's / output's length, in characters, before any cut. */
|
|
240
|
+
inputChars?: number;
|
|
241
|
+
outputChars?: number;
|
|
242
|
+
truncated?: boolean;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// The cap plus slack: the runner masks, then cuts to the cap (capture.ts), so
|
|
246
|
+
// a secret on the edge is masked whole.
|
|
247
|
+
const SEND_CAP = CAPTURE_CAP + 4096;
|
|
248
|
+
|
|
249
|
+
function capped(text: string): { text: string; chars: number; truncated: boolean } {
|
|
250
|
+
if (text.length <= SEND_CAP) return { text, chars: text.length, truncated: false };
|
|
251
|
+
return { text: text.slice(0, SEND_CAP), chars: text.length, truncated: true };
|
|
252
|
+
}
|
|
199
253
|
|
|
200
254
|
/** The tag on every event a failed check writes — `verify:`, `output: json`,
|
|
201
255
|
* a schema — headline and detail alike. The runner's no-retry rule for
|
|
@@ -391,6 +445,10 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
391
445
|
...(opts.effort ? { effort: opts.effort } : {}),
|
|
392
446
|
systemPrompt: opts.systemPrompt,
|
|
393
447
|
...(opts.maxTurns ? { maxTurns: opts.maxTurns } : {}),
|
|
448
|
+
// The raw stream, only when model calls are recorded: an assistant
|
|
449
|
+
// message carries usage from the call's start (output a few tokens) and
|
|
450
|
+
// no stop reason; the call's message_delta has both (FOL-72).
|
|
451
|
+
...(opts.capture ? { includePartialMessages: true } : {}),
|
|
394
452
|
// Sub-agents: the SDK's own delegation. Every definition carries an
|
|
395
453
|
// explicit tool list (an absent one inherits all of ours).
|
|
396
454
|
...(subagents ? { agents: toAgentDefinitions(subagents) } : {}),
|
|
@@ -474,7 +532,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
474
532
|
// refused before it runs: the tool, and any paid API behind it,
|
|
475
533
|
// never sees it.
|
|
476
534
|
if (counter.active) {
|
|
477
|
-
const refusal = counter.take(
|
|
535
|
+
const refusal = counter.take(callKeysFor(hookInput.tool_name, hookInput.tool_input as Record<string, unknown>, opts.toolOwners));
|
|
478
536
|
if (refusal) {
|
|
479
537
|
emit("info", `${refusal.message} (${hookInput.tool_name} refused)`, via ? { subagent: via } : undefined);
|
|
480
538
|
return { hookSpecificOutput: { hookEventName: "PreToolUse" as const, permissionDecision: "deny" as const, permissionDecisionReason: refusal.message } };
|
|
@@ -489,7 +547,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
489
547
|
// model differently from its delegates can tell them apart.
|
|
490
548
|
const v = await runToolCallHooks(toolHooks, { tool: hookInput.tool_name, input, base: via ? { ...hookBase, subagent: via } : hookBase }, callHook);
|
|
491
549
|
for (const r of v.records) record(r, via);
|
|
492
|
-
const refundCall = () => { if (counter.active) counter.refund(
|
|
550
|
+
const refundCall = () => { if (counter.active) counter.refund(callKeysFor(hookInput.tool_name, hookInput.tool_input as Record<string, unknown>, opts.toolOwners)); };
|
|
493
551
|
if (v.decision === "deny") {
|
|
494
552
|
refundCall();
|
|
495
553
|
return { hookSpecificOutput: { hookEventName: "PreToolUse" as const, permissionDecision: "deny" as const, permissionDecisionReason: v.reason! } };
|
|
@@ -558,7 +616,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
558
616
|
// anything outside it is a denial with a reason the model can act on.
|
|
559
617
|
const fromGrantedServer = opts.mcpNames.some((n) => toolName.startsWith(`mcp__${n}__`));
|
|
560
618
|
// A call the hook counted and this refuses never ran: give it back.
|
|
561
|
-
const refund = () => { if (counter.active) counter.refund(
|
|
619
|
+
const refund = () => { if (counter.active) counter.refund(callKeysFor(toolName, input, opts.toolOwners)); };
|
|
562
620
|
if (!opts.allowed.includes(toolName) && !fromGrantedServer) {
|
|
563
621
|
refund();
|
|
564
622
|
return {
|
|
@@ -630,6 +688,29 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
630
688
|
// ceiling and on the bill.
|
|
631
689
|
const turns = new Map<string, { u: Record<string, number | undefined>; price: TokenPrice | null }>();
|
|
632
690
|
let anonymousTurns = 0;
|
|
691
|
+
// Each model call as a span (FOL-54 step 4), keyed like `turns`: a turn's
|
|
692
|
+
// blocks arrive as several messages under one id, so it is updated in place
|
|
693
|
+
// and sent again; the runner keeps the latest by id.
|
|
694
|
+
const spans = new Map<string, ModelCall>();
|
|
695
|
+
// From the raw stream: which call each agent is in (message_start), and each
|
|
696
|
+
// call's final output tokens and stop reason (message_delta).
|
|
697
|
+
const streaming = new Map<string, string>();
|
|
698
|
+
let saidResponding = false;
|
|
699
|
+
const finals = new Map<string, { output?: number; stop?: string }>();
|
|
700
|
+
const withFinal = (span: ModelCall): ModelCall => {
|
|
701
|
+
const f = finals.get(span.id);
|
|
702
|
+
return {
|
|
703
|
+
...span,
|
|
704
|
+
...(f?.output !== undefined ? { tokens: { ...span.tokens, output: f.output } } : {}),
|
|
705
|
+
// Without the stream (an older CLI), a call that used tools stopped to use them.
|
|
706
|
+
...(f?.stop ? { stop: f.stop } : !span.stop && span.tools > 0 ? { stop: "tool_use" } : {}),
|
|
707
|
+
};
|
|
708
|
+
};
|
|
709
|
+
// When each agent's next request went out — the step's own, and each
|
|
710
|
+
// sub-agent's apart, so one's tool results do not restart another's clock.
|
|
711
|
+
const lastInputAt = new Map<string, number>();
|
|
712
|
+
const sentAt = (via: string | null | undefined) => lastInputAt.get(via ?? "") ?? loopStartedAt;
|
|
713
|
+
let loopStartedAt = Date.now();
|
|
633
714
|
const specByName = new Map((subagents ?? []).map((s) => [s.name, s]));
|
|
634
715
|
const priceOf = (message: unknown): TokenPrice | null => {
|
|
635
716
|
const parent = (message as { parent_tool_use_id?: string | null }).parent_tool_use_id;
|
|
@@ -705,11 +786,62 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
705
786
|
|
|
706
787
|
try {
|
|
707
788
|
for await (const message of q as AsyncIterable<SDKMessage>) {
|
|
789
|
+
// The session is up: the first model call is timed from here, not from
|
|
790
|
+
// before the SDK and its MCP servers started.
|
|
791
|
+
if (message.type === "system" && (message as { subtype?: string }).subtype === "init") loopStartedAt = Date.now();
|
|
792
|
+
if (message.type === "stream_event") {
|
|
793
|
+
const ev = (message as { event?: { type?: string; message?: { id?: string }; usage?: { output_tokens?: number }; delta?: { stop_reason?: string | null } } }).event;
|
|
794
|
+
const agentKey = (message as { parent_tool_use_id?: string | null }).parent_tool_use_id ?? "";
|
|
795
|
+
if (ev?.type === "message_start" && ev.message?.id) {
|
|
796
|
+
streaming.set(agentKey, ev.message.id);
|
|
797
|
+
// The step's own first response has begun: its prompt is in the
|
|
798
|
+
// cache now, so siblings held back to read it may start.
|
|
799
|
+
if (!agentKey && !saidResponding) {
|
|
800
|
+
saidResponding = true;
|
|
801
|
+
emit("info", "", { responding: true });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (ev?.type === "message_delta") {
|
|
805
|
+
const id = streaming.get(agentKey);
|
|
806
|
+
if (id) {
|
|
807
|
+
finals.set(id, {
|
|
808
|
+
...(typeof ev.usage?.output_tokens === "number" ? { output: ev.usage.output_tokens } : {}),
|
|
809
|
+
...(ev.delta?.stop_reason ? { stop: ev.delta.stop_reason } : {}),
|
|
810
|
+
});
|
|
811
|
+
const span = spans.get(id);
|
|
812
|
+
if (span) {
|
|
813
|
+
const final = withFinal(span);
|
|
814
|
+
spans.set(id, final);
|
|
815
|
+
emit("info", "", { turn: final });
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
708
821
|
if (ended) break;
|
|
709
822
|
if (message.type === "assistant") {
|
|
710
|
-
const m = message.message as unknown as { id?: string; usage?: Record<string, number | undefined
|
|
823
|
+
const m = message.message as unknown as { id?: string; model?: string; stop_reason?: string | null; usage?: Record<string, number | undefined>; content?: { type?: string }[] };
|
|
711
824
|
const u = m.usage;
|
|
712
|
-
|
|
825
|
+
const turnKey = m.id ?? `turn-${anonymousTurns++}`;
|
|
826
|
+
if (u) turns.set(turnKey, { u, price: priceOf(message) });
|
|
827
|
+
if (opts.capture && u) {
|
|
828
|
+
const t = usageFromSdk(u);
|
|
829
|
+
const prior = spans.get(turnKey);
|
|
830
|
+
const span: ModelCall = {
|
|
831
|
+
id: turnKey,
|
|
832
|
+
n: prior?.n ?? spans.size + 1,
|
|
833
|
+
at: prior?.at ?? new Date().toISOString(),
|
|
834
|
+
ms: prior?.ms ?? Date.now() - sentAt(subagentOf(message)),
|
|
835
|
+
...(m.model ? { model: m.model } : prior?.model ? { model: prior.model } : {}),
|
|
836
|
+
...(m.stop_reason ? { stop: m.stop_reason } : prior?.stop ? { stop: prior.stop } : {}),
|
|
837
|
+
tokens: { input: t.inputTokens, output: t.outputTokens, cacheRead: t.cacheReadTokens, cacheWrite: t.cacheWriteTokens },
|
|
838
|
+
tools: (prior?.tools ?? 0) + (m.content ?? []).filter((b) => b?.type === "tool_use").length,
|
|
839
|
+
...(subagentOf(message) ? { subagent: subagentOf(message)! } : {}),
|
|
840
|
+
};
|
|
841
|
+
const final = withFinal(span);
|
|
842
|
+
spans.set(turnKey, final);
|
|
843
|
+
emit("info", "", { turn: final });
|
|
844
|
+
}
|
|
713
845
|
if (ceiling && u) {
|
|
714
846
|
spentUsd = turnsTotal();
|
|
715
847
|
if (spentUsd >= ceiling) {
|
|
@@ -731,13 +863,21 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
731
863
|
const input = block.input as { subagent_type?: unknown; description?: unknown; prompt?: unknown } | undefined;
|
|
732
864
|
const to = String(input?.subagent_type ?? "subagent");
|
|
733
865
|
delegations.set(block.id, to);
|
|
866
|
+
// That sub-agent's first model call is timed from its job going out.
|
|
867
|
+
lastInputAt.set(to, Date.now());
|
|
734
868
|
// One line a person can read: who got the job, and what it was.
|
|
735
869
|
const job = String(input?.description ?? input?.prompt ?? "").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
736
870
|
emit("info", `delegating to ${to}${job ? `: ${job}` : ""}`, { subagent: to });
|
|
737
871
|
}
|
|
738
872
|
openCalls.set(block.id, { name: block.name, at: Date.now(), ...(via ? { subagent: via } : {}) });
|
|
739
873
|
if (pod) ledger.set(block.id, classifyCall(block.name, block.input as Record<string, unknown> | undefined, pod));
|
|
740
|
-
|
|
874
|
+
const input = opts.capture === "full" ? capped(JSON.stringify(block.input ?? {})) : null;
|
|
875
|
+
emit("tool", block.name, {
|
|
876
|
+
call: block.id,
|
|
877
|
+
...(opts.capture ? { inCall: turnKey } : {}),
|
|
878
|
+
...(via ? { subagent: via } : {}),
|
|
879
|
+
...(input ? { io: { input: input.text, inputChars: input.chars, ...(input.truncated ? { truncated: true } : {}) } } : {}),
|
|
880
|
+
});
|
|
741
881
|
}
|
|
742
882
|
}
|
|
743
883
|
} else if (message.type === "user") {
|
|
@@ -750,6 +890,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
750
890
|
const open = openCalls.get(block.tool_use_id);
|
|
751
891
|
if (!open) continue;
|
|
752
892
|
openCalls.delete(block.tool_use_id);
|
|
893
|
+
const output = opts.capture === "full" ? capped(toolResultText(block.content)) : null;
|
|
753
894
|
emit("tool", open.name, {
|
|
754
895
|
call: block.tool_use_id,
|
|
755
896
|
ms: Date.now() - open.at,
|
|
@@ -758,6 +899,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
758
899
|
bytes: toolResultText(block.content).length,
|
|
759
900
|
...(block.is_error ? { err: true } : {}),
|
|
760
901
|
...(open.subagent ? { subagent: open.subagent } : {}),
|
|
902
|
+
...(output ? { io: { output: output.text, outputChars: output.chars, ...(output.truncated ? { truncated: true } : {}) } } : {}),
|
|
761
903
|
});
|
|
762
904
|
// Why it failed, on the trail. The span above carried only `err`,
|
|
763
905
|
// so a refused call left the agent's own guess as the only account
|
|
@@ -769,6 +911,8 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
769
911
|
}
|
|
770
912
|
readPod(block.tool_use_id);
|
|
771
913
|
}
|
|
914
|
+
// That agent's next model call is timed from here: the results went back.
|
|
915
|
+
lastInputAt.set(subagentOf(message) ?? "", Date.now());
|
|
772
916
|
}
|
|
773
917
|
} else if (message.type === "result") {
|
|
774
918
|
status = message.subtype === "success" ? "completed" : "failed";
|
|
@@ -928,6 +1072,8 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
|
|
|
928
1072
|
data,
|
|
929
1073
|
modelEnv: opts.env,
|
|
930
1074
|
signal: halt.signal,
|
|
1075
|
+
...(opts.judgeModel ? { judgeModel: opts.judgeModel } : {}),
|
|
1076
|
+
...(opts.judgeEffort ? { judgeEffort: opts.judgeEffort } : {}),
|
|
931
1077
|
});
|
|
932
1078
|
} finally {
|
|
933
1079
|
for (const t of clocks) clearTimeout(t);
|
|
@@ -1032,6 +1178,9 @@ export async function checkVerify(
|
|
|
1032
1178
|
/** The step's own model environment, for `judge:` — it grades on the
|
|
1033
1179
|
* fast tier through the same credential the step rode. */
|
|
1034
1180
|
modelEnv?: Record<string, string | undefined>;
|
|
1181
|
+
/** The judge's model and effort (the workspace's `judge:`). */
|
|
1182
|
+
judgeModel?: string;
|
|
1183
|
+
judgeEffort?: Effort;
|
|
1035
1184
|
/** Ends a shell check or a judge mid-way: the step's clock ran out, or a
|
|
1036
1185
|
* person asked the run to stop. */
|
|
1037
1186
|
signal?: AbortSignal;
|
|
@@ -1095,9 +1244,9 @@ export async function checkVerify(
|
|
|
1095
1244
|
// GOOD` on a reply that did (fix-desk, run-muwevg72-7mza): the joined
|
|
1096
1245
|
// text opened with the narration before it.
|
|
1097
1246
|
const reply = ctx.conclusion?.trim() ? ctx.conclusion : output;
|
|
1098
|
-
const
|
|
1099
|
-
const ok =
|
|
1100
|
-
return { ok, headline: ok ? "PASS" : "FAIL", detail
|
|
1247
|
+
const said = await judgeReply(value, reply, output, ctx.modelEnv ?? {}, ctx.signal, ctx.judgeModel, ctx.judgeEffort);
|
|
1248
|
+
const { passed: ok, detail } = judgeVerdict(said);
|
|
1249
|
+
return { ok, headline: ok ? "PASS" : "FAIL", detail };
|
|
1101
1250
|
}
|
|
1102
1251
|
}
|
|
1103
1252
|
return { ok: false, headline: `unknown check "${kind}"`, detail: "" };
|
|
@@ -1105,8 +1254,8 @@ export async function checkVerify(
|
|
|
1105
1254
|
|
|
1106
1255
|
/**
|
|
1107
1256
|
* A toolless, fast-tier grading call: does the reply satisfy the sentence?
|
|
1108
|
-
* Same shape as the eval judge
|
|
1109
|
-
*
|
|
1257
|
+
* Same shape as the eval judge: reasoning first, then the verdict alone on
|
|
1258
|
+
* the last line (judge-verdict.ts says why).
|
|
1110
1259
|
*/
|
|
1111
1260
|
export function judgePrompt(rubric: string, reply: string, work: string): string {
|
|
1112
1261
|
const shown = reply.slice(0, 20_000);
|
|
@@ -1115,8 +1264,7 @@ export function judgePrompt(rubric: string, reply: string, work: string): string
|
|
|
1115
1264
|
`never this:\n<work>\n${work.slice(-20_000)}\n</work>\n\n`
|
|
1116
1265
|
: "";
|
|
1117
1266
|
return (
|
|
1118
|
-
`You are grading a reply against one requirement.
|
|
1119
|
-
`first word, then one sentence of reason.\n\nRequirement: ${rubric}\n\n` +
|
|
1267
|
+
`You are grading a reply against one requirement. ${JUDGE_VERDICT_INSTRUCTION}\n\nRequirement: ${rubric}\n\n` +
|
|
1120
1268
|
context +
|
|
1121
1269
|
`<reply>\n${shown}\n</reply>`
|
|
1122
1270
|
);
|
|
@@ -1128,6 +1276,8 @@ async function judgeReply(
|
|
|
1128
1276
|
work: string,
|
|
1129
1277
|
env: Record<string, string | undefined>,
|
|
1130
1278
|
signal?: AbortSignal,
|
|
1279
|
+
model = "haiku",
|
|
1280
|
+
effort?: Effort,
|
|
1131
1281
|
): Promise<string> {
|
|
1132
1282
|
const texts: string[] = [];
|
|
1133
1283
|
const abortController = new AbortController();
|
|
@@ -1137,7 +1287,8 @@ async function judgeReply(
|
|
|
1137
1287
|
const q = query({
|
|
1138
1288
|
prompt: judgePrompt(rubric, reply, work),
|
|
1139
1289
|
options: {
|
|
1140
|
-
model
|
|
1290
|
+
model,
|
|
1291
|
+
...(effort ? { effort } : {}),
|
|
1141
1292
|
systemPrompt: "You grade text against a stated requirement. Be strict and literal.",
|
|
1142
1293
|
tools: [],
|
|
1143
1294
|
settingSources: [],
|