@agentproto/workflow-runtime 0.9.0 → 0.11.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/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { DriverHandle, ResolverContext } from '@agentproto/driver';
2
2
  import { ToolContext, ToolHandle } from '@agentproto/tool';
3
3
  import { ZodType } from 'zod';
4
4
  import { WorkflowHandle } from '@agentproto/workflow';
5
+ import { FsPort, FsStat, FsLockHandle } from '@agentproto/corpus';
5
6
 
6
7
  /**
7
8
  * Typed step algebra for AIP-15 WORKFLOW execution.
@@ -155,6 +156,11 @@ interface ApprovalStep {
155
156
  id: string;
156
157
  prompt: Selector<string>;
157
158
  approvers?: readonly string[];
159
+ /** Artifacts attached to the approval request (e.g. paths to review). */
160
+ artifacts?: readonly string[];
161
+ /** Give up waiting after this long — the step resolves as REJECTED with
162
+ * `who: "timeout"` (the host decides; the runtime enforces it). */
163
+ timeoutMs?: number;
158
164
  onApprove?: readonly RunStep[];
159
165
  onReject?: readonly RunStep[];
160
166
  }
@@ -184,6 +190,73 @@ interface SubworkflowStep {
184
190
  /** Input for the child (default: the parent's workflow input). */
185
191
  input?: Selector<unknown>;
186
192
  }
193
+ /**
194
+ * Harness pinning for an {@link AgentStep}'s spawn (AIP-15 P2) — mirrors
195
+ * `@agentproto/workflow`'s manifest `Harness` block, compiled onto the
196
+ * runtime step. Precedence when a field is also set elsewhere: this block
197
+ * (highest) > the resolved AGENT.md's own frontmatter > `app_run` caller
198
+ * args > the adapter's own default (lowest).
199
+ */
200
+ interface AgentHarness {
201
+ /** Model id override for this spawn. */
202
+ model?: string;
203
+ /** Reasoning-effort override for this spawn (adapter-defined vocabulary). */
204
+ effort?: string;
205
+ /** Spawn-time role — governs the child's tool-policy disposition. */
206
+ role?: string;
207
+ /** Per-spawn tool allowlist. Applied where the resolved adapter supports a
208
+ * generic per-spawn allowlist mechanism; where it doesn't, the host MUST
209
+ * record `toolsApplied: false` on the step's output and emit a warning
210
+ * rather than silently ignoring the field. */
211
+ tools?: readonly string[];
212
+ /** Skill ids auto-mounted into the spawn. */
213
+ skills?: readonly string[];
214
+ /** Working directory override for this step's spawn — takes precedence
215
+ * over {@link AgentStep.cwd} and the run-level `ctx.cwd`. */
216
+ cwd?: string;
217
+ /** sha256 (hex) of the `harness.promptFile` this step's prompt was read
218
+ * from, computed by `@agentproto/workflow-loader` at load time. Absent
219
+ * when the step's prompt was authored inline. */
220
+ promptSha?: string;
221
+ /** AIP-10 corpus knowledge materialized into the step's cwd (under
222
+ * `.knowledge/`) before the session runs. See
223
+ * {@link HarnessKnowledgeSelector}. */
224
+ knowledge?: readonly HarnessKnowledgeSelector[];
225
+ }
226
+ /**
227
+ * One AIP-10 corpus workspace attachment on an {@link AgentStep}'s
228
+ * `harness.knowledge[]` — mirrors `@agentproto/workflow`'s manifest
229
+ * `KnowledgeSelector`, with the `workspace` path already resolved to an
230
+ * absolute path by `@agentproto/workflow-loader` (or authored absolute).
231
+ */
232
+ interface HarnessKnowledgeSelector {
233
+ /** Absolute path to an AIP-10 corpus workspace root. */
234
+ workspace: string;
235
+ /** Tags with OR semantics (maps to `resolveKnowledge`'s `query.tags`). */
236
+ anyOf?: readonly string[];
237
+ /** Tags that must ALL be present (post-filter after `anyOf`). */
238
+ allOf?: readonly string[];
239
+ /** Refined-kind filter. */
240
+ kinds?: readonly string[];
241
+ /** Cap on materialized entries after slug-ascending sort. Default 50. */
242
+ maxEntries?: number;
243
+ /** Materialization mode — v1 supports only `"files"`. */
244
+ mode?: "files";
245
+ /** Internal — set by `@agentproto/workflow-loader` when a selector string
246
+ * carries `$…` run-time references that must be resolved against the run
247
+ * bindings before materialization. Not user-authored; the loader rejects
248
+ * an authored `deferred`, and resolution strips the flag. */
249
+ deferred?: boolean;
250
+ }
251
+ /** Per-selector materialization record on an agent step's run output. */
252
+ interface KnowledgeAppliedRecord {
253
+ /** The (absolute) corpus workspace root the selector resolved against. */
254
+ workspace: string;
255
+ /** Entries matching the selector after allOf + kind filtering. */
256
+ matched: number;
257
+ /** Entries actually written (≤ matched, after the `maxEntries` cap). */
258
+ written: number;
259
+ }
187
260
  /**
188
261
  * Where an {@link AgentStep}'s session runs when not on the host: a sandbox
189
262
  * provider slug (e.g. `"local"`, `"e2b"`) or an inline AIP-36 SandboxSpec-like
@@ -207,6 +280,13 @@ interface AgentStep {
207
280
  adapter?: Selector<string> | string;
208
281
  /** Reuse an earlier AgentStep's spawned session, by that step's id. */
209
282
  sessionRef?: string;
283
+ /** Model id override for this spawn — same semantics as `agent_start.model`
284
+ * (a literal string or a per-run selector resolving to one; `undefined` ⇒
285
+ * unchanged behaviour, the adapter keeps its default). Forwarded to the
286
+ * spawn through the harness slot; an explicit `harness.model` pinning
287
+ * (AIP-15 P2) still wins. Only meaningful with `adapter`; ignored on a
288
+ * `sessionRef` reuse. */
289
+ model?: Selector<string> | string;
210
290
  /** Working directory for this spawn. Omit to fall back to the run-level `ctx.cwd`. */
211
291
  cwd?: Selector<string>;
212
292
  /** Run this step's session inside a sandbox instead of on the host — a
@@ -233,7 +313,7 @@ interface AgentStep {
233
313
  /** Re-prompt-and-retry attempts on schema mismatch before failing. Default 2. */
234
314
  maxRetries?: number;
235
315
  /** Cache this step's output under the run's cacheKey; the resolved prompt +
236
- * adapter + sessionRef are hashed. Default false — most agent steps have
316
+ * adapter + model + sessionRef are hashed. Default false — most agent steps have
237
317
  * side effects. */
238
318
  cacheable?: boolean;
239
319
  /** Manifest-declared adapter option id → value, forwarded to a NEW spawn's
@@ -242,6 +322,81 @@ interface AgentStep {
242
322
  * declarative `agent.ref` names an app-scoped agent. Only meaningful with
243
323
  * `adapter`; ignored on a `sessionRef` reuse. */
244
324
  options?: Record<string, boolean | number | string>;
325
+ /** Harness pinning for this step's spawn. See {@link AgentHarness}. */
326
+ harness?: AgentHarness;
327
+ }
328
+ /**
329
+ * `kind: "gate"` — run a shell command through the host's subprocess runner
330
+ * as a deterministic pass/fail check (AIP-15 P3 / AIP-17). Exit code 0 is a
331
+ * pass; any other code is a failure. Bound output is `{ ok, exitCode, report
332
+ * }`; `report` is the process' stdout (if it parses as JSON) or the file at
333
+ * `reportPath` (relative to `cwd`), parsed as JSON.
334
+ */
335
+ interface GateStep {
336
+ kind: "gate";
337
+ id: string;
338
+ command: string;
339
+ /** Command arguments. Each element (selector or string) resolves per-run
340
+ * against the bindings: a LEADING `$input|$item|$steps.<id>|$index` token
341
+ * (AIP-16 prefix grammar) resolves and any trailing text is appended
342
+ * verbatim (`"$input.bookDir/knowledge"` → `"<resolved>/knowledge"`); a
343
+ * string that is exactly a ref resolves to that value's string form;
344
+ * `$$…` stays a literal `$`; a ref that resolves to nothing (or a `$…`
345
+ * string that opens no known ref token) throws naming the step and the
346
+ * arg index. */
347
+ args?: readonly (Selector<string> | string)[];
348
+ /** Working directory. Selector form resolves per-run. A string may carry
349
+ * the same leading-`$…` ref rule as `args` (leading token + trailing
350
+ * text, `$$` escape, unresolvable ref throws). The RESOLVED cwd is made
351
+ * absolute: absolute stays absolute; relative (incl. `.`) resolves
352
+ * against the run's own `ctx.cwd` — never the daemon process cwd.
353
+ * Omit for the run's `ctx.cwd`. */
354
+ cwd?: Selector<string> | string;
355
+ /** Path (relative to `cwd`), of a JSON report file, consulted when stdout
356
+ * doesn't itself parse as JSON. */
357
+ reportPath?: string;
358
+ /** Hard wall-clock cap for a single command invocation, in ms. */
359
+ timeoutMs?: number;
360
+ /** Re-run on a failing exit code, up to `maxAttempts` (default 1 = no
361
+ * retry — a single attempt, fail immediately on a non-zero exit). */
362
+ retry?: {
363
+ maxAttempts: number;
364
+ backoff: "fixed" | "exponential";
365
+ initialMs?: number;
366
+ };
367
+ /** Re-prompt-and-rerun on a failing attempt, BEFORE each retry after the
368
+ * first: send the named prior {@link AgentStep}'s session (resolved via
369
+ * {@link AgentSessionHost.resolveByLabel}, the same lookup a `sessionRef`
370
+ * reuse uses) a prompt with this gate's last report injected, wait for
371
+ * its turn, then re-run the gate command. */
372
+ onFail?: {
373
+ reprompt: string;
374
+ with?: Record<string, unknown>;
375
+ };
376
+ }
377
+ /** One gate command invocation's raw result — before report resolution. */
378
+ interface GateCommandResult {
379
+ exitCode: number;
380
+ stdout: string;
381
+ stderr: string;
382
+ timedOut?: boolean;
383
+ }
384
+ /** Host-injectable subprocess runner for {@link GateStep}. Undefined ⇒ the
385
+ * runtime's own `node:child_process`-backed default. */
386
+ type GateCommandRunner = (spec: {
387
+ command: string;
388
+ args: readonly string[];
389
+ cwd: string;
390
+ timeoutMs?: number;
391
+ }) => Promise<GateCommandResult>;
392
+ /** One `kind: "gate"` command attempt's outcome, reported as it happens
393
+ * (every attempt, not just the last) — the `gate-report` lifecycle event. */
394
+ interface GateReportEvent {
395
+ stepId: string;
396
+ ok: boolean;
397
+ exitCode: number;
398
+ report: unknown;
399
+ attempt: number;
245
400
  }
246
401
  /** What a declarative agent-step's `agent.ref` resolves to — the adapter to
247
402
  * spawn under plus any adapter options that pick which concrete agent runs
@@ -262,7 +417,7 @@ interface AgentRefResolution {
262
417
  * issue #21534); tightening the union to `ToolStep<unknown, …>` rejects a
263
418
  * concrete `ToolStep<MarketSearchInput, …>` on the zod `ZodType<T>` variance.
264
419
  */
265
- type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep;
420
+ type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep | GateStep;
266
421
  interface RuntimeWorkflow {
267
422
  id: string;
268
423
  description?: string;
@@ -270,10 +425,21 @@ interface RuntimeWorkflow {
270
425
  /** Pick the run's final output (default: the last top-level step's output). */
271
426
  output?: Selector<unknown>;
272
427
  }
428
+ /** A human/host decision on one approval request. `who` records WHO decided
429
+ * ("human", "timeout", "cancelled", …); `note` is optional free text. */
430
+ interface ApprovalDecision {
431
+ approved: boolean;
432
+ who: string;
433
+ note?: string;
434
+ }
273
435
  interface ApprovalRequest {
274
436
  stepId: string;
275
437
  prompt: string;
276
438
  approvers: readonly string[];
439
+ artifacts?: readonly string[];
440
+ /** The step's `timeoutMs` when set — the host may enforce it itself; the
441
+ * runtime's default when the host doesn't is to wait forever. */
442
+ timeoutMs?: number;
277
443
  }
278
444
  interface ResumeRequest {
279
445
  stepId: string;
@@ -291,6 +457,8 @@ interface AgentSessionHost {
291
457
  sandbox?: AgentSandboxRef;
292
458
  /** Adapter option id → value for this spawn (see {@link AgentStep.options}). */
293
459
  options?: Record<string, boolean | number | string>;
460
+ /** Harness pinning for this spawn (see {@link AgentStep.harness}). */
461
+ harness?: AgentHarness;
294
462
  }): Promise<string>;
295
463
  /** Send a prompt to an existing session and wait for its turn to end. */
296
464
  sendPromptAndWait(sessionId: string, prompt: string): Promise<void>;
@@ -302,6 +470,16 @@ interface AgentSessionHost {
302
470
  readFinalMessage?(sessionId: string): Promise<string>;
303
471
  /** Current cumulative cost (USD) of a session, for run-level budgeting. */
304
472
  readCostUsd?(sessionId: string): Promise<number>;
473
+ /** Emit a harness-warning for a session — the pass-through channel behind
474
+ * the AIP-15 "never silently ignore" `session:harness-warning` event (used
475
+ * by the runtime for `harness.knowledge` empty matches, reason
476
+ * `knowledge-empty`). Optional: hosts without an event bus omit it and
477
+ * the runtime degrades to the run-record entry alone. */
478
+ emitHarnessWarning?(input: {
479
+ sessionId: string;
480
+ warnings: readonly string[];
481
+ label?: string;
482
+ }): void;
305
483
  }
306
484
  /** One journal entry: a cached step output plus the hash of the inputs that produced it. */
307
485
  interface StepCacheEntry {
@@ -319,8 +497,10 @@ interface RunWorkflowArgs {
319
497
  workflow: RuntimeWorkflow;
320
498
  input?: unknown;
321
499
  signal?: AbortSignal;
322
- /** Decide an {@link ApprovalStep}. Default: auto-approve. */
323
- approve?: (req: ApprovalRequest) => boolean | Promise<boolean>;
500
+ /** Decide an {@link ApprovalStep}. Default: auto-approve. May return a bare
501
+ * boolean (equivalent to `{approved, who: "host"}`) or a full
502
+ * {@link ApprovalDecision} recording who decided. */
503
+ approve?: (req: ApprovalRequest) => boolean | ApprovalDecision | Promise<boolean | ApprovalDecision>;
324
504
  /** Supply a {@link SuspendStep}'s resume payload. Default: throw + suspend. */
325
505
  resume?: (req: ResumeRequest) => unknown | Promise<unknown>;
326
506
  /** Host-injected agent session runtime. Undefined ⇒ {@link AgentStep} throws. */
@@ -341,6 +521,12 @@ interface RunWorkflowArgs {
341
521
  onStepStart?: (stepId: string) => void;
342
522
  /** Called when a step completes execution, with its output. */
343
523
  onStepComplete?: (stepId: string, output: unknown) => void;
524
+ /** Host-injectable subprocess runner for `kind: "gate"` steps. Undefined ⇒
525
+ * the runtime's own `node:child_process`-backed default. */
526
+ runGateCommand?: GateCommandRunner;
527
+ /** Called once per `kind: "gate"` command attempt (every attempt, not just
528
+ * the last) — the `gate-report` lifecycle event. */
529
+ onGateReport?: (ev: GateReportEvent) => void;
344
530
  }
345
531
  interface WorkflowRunResult {
346
532
  output: unknown;
@@ -414,6 +600,16 @@ declare class WorkflowCompileError extends Error {
414
600
  }
415
601
  /** Resolve a single `$…` reference string against the run bindings. */
416
602
  declare function resolveRef(ref: string, b: Bindings): unknown;
603
+ /** Resolve the leading ref token of a string (see {@link REF_PREFIX_RE}):
604
+ * if `value` starts with a `$input`/`$item`/`$steps`/`$index` reference
605
+ * token, resolve it against the bindings and return the resolved value plus
606
+ * the literal remainder (`rest`). Throws exactly like {@link resolveRef} for
607
+ * a matched-but-malformed token; returns `undefined` when the string does
608
+ * not START with a ref token at all (caller decides pass-through vs error). */
609
+ declare function resolveRefPrefixed(value: string, b: Bindings): {
610
+ resolved: unknown;
611
+ rest: string;
612
+ } | undefined;
417
613
  /** Recursively resolve a value node: refs in strings, into arrays/objects. */
418
614
  declare function resolveValue(node: unknown, b: Bindings): unknown;
419
615
  /** Evaluate a `while`/`when` predicate string against the bindings. */
@@ -461,6 +657,7 @@ interface AgentStepFields {
461
657
  * steps resolve `$steps.*` refs into one before calling this). */
462
658
  prompt: string | Selector<string>;
463
659
  adapter?: string;
660
+ model?: Selector<string> | string;
464
661
  sessionRef?: string;
465
662
  sandbox?: AgentSandboxRef;
466
663
  cacheable?: boolean;
@@ -468,9 +665,62 @@ interface AgentStepFields {
468
665
  outputSchema?: AgentStep["outputSchema"];
469
666
  maxRetries?: number;
470
667
  options?: Record<string, boolean | number | string>;
668
+ harness?: AgentHarness;
471
669
  }
472
670
  /** Build a runtime {@link AgentStep} from field values, applying the same
473
671
  * defaults everywhere: `policy` defaults to `{ awaiting: "fail" }`. */
474
672
  declare function buildAgentStep(id: string, fields: AgentStepFields): AgentStep;
475
673
 
476
- export { type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, type AgentStep, type AgentStepFields, type ApprovalRequest, type ApprovalStep, type Bindings, type BranchStep, type CompileWorkflowOptions, type FanOutOutcome, type GroupStep, type LoopStep, type MapStep, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type Selector, type StepCache, type StepCacheEntry, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCompileError, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, resolveRef, resolveValue, runWorkflow };
674
+ /**
675
+ * `harness.knowledge` materialization (AIP-15 P2) — resolve each selector
676
+ * against its AIP-10 corpus workspace with the same `resolveKnowledge` the
677
+ * corpus CLI previews with, then write the matched raw entries (frontmatter +
678
+ * body) into the step's cwd under `.knowledge/` BEFORE the step's session
679
+ * runs. Idempotent: every run rewrites the same deterministic file set.
680
+ */
681
+
682
+ /**
683
+ * Resolve every `$…`-bearing string of each `harness.knowledge[]` selector
684
+ * (`workspace`, `anyOf[]`, `allOf[]`, `kinds[]`) against the run bindings,
685
+ * producing selectors whose strings are run-resolved and stripped of the
686
+ * loader's internal `deferred` flag. Selectors without refs pass through
687
+ * unchanged (a copy). See {@link resolveRefString} for the ref rule.
688
+ */
689
+ declare function resolveKnowledgeSelectors(stepId: string, selectors: readonly HarnessKnowledgeSelector[], b: Bindings): HarnessKnowledgeSelector[];
690
+ interface MaterializedKnowledge {
691
+ records: KnowledgeAppliedRecord[];
692
+ /** Total entries written across all selectors — drives the prompt note. */
693
+ written: number;
694
+ /** human-readable warnings for empty matches (`knowledge-empty`). */
695
+ warnings: string[];
696
+ }
697
+ /**
698
+ * Materialize every selector in `selectors` under `<stepCwd>/.knowledge/`.
699
+ * Throws on a non-"files" `mode` or an unreadable workspace — the loader
700
+ * rejects both earlier; this is the direct-TS-authoring path's backstop.
701
+ */
702
+ declare function materializeKnowledge(stepId: string, selectors: readonly HarnessKnowledgeSelector[], stepCwd: string): Promise<MaterializedKnowledge>;
703
+
704
+ /**
705
+ * NodeFsPort — a read-mostly `FsPort` over the host filesystem, rooted at an
706
+ * AIP-10 corpus workspace. The local-FS twin of the adapter `corpus-cli`'s
707
+ * knowledge preview uses, so `harness.knowledge` materialization resolves a
708
+ * workspace with exactly the same port (and therefore the same hidden-segment
709
+ * skipping and path confinement) the CLI does.
710
+ */
711
+
712
+ declare class NodeFsPort implements FsPort {
713
+ private readonly root;
714
+ constructor(root: string);
715
+ private resolve;
716
+ exists(p: string): Promise<boolean>;
717
+ readFile(p: string): Promise<string>;
718
+ writeFile(p: string, content: string): Promise<void>;
719
+ appendFile(p: string, content: string): Promise<void>;
720
+ readdir(p: string): Promise<readonly string[]>;
721
+ walk(p: string): Promise<readonly string[]>;
722
+ stat(p: string): Promise<FsStat | null>;
723
+ lock(): Promise<FsLockHandle>;
724
+ }
725
+
726
+ export { type AgentHarness, type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, type AgentStep, type AgentStepFields, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type Bindings, type BranchStep, type CompileWorkflowOptions, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, NodeFsPort, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type Selector, type StepCache, type StepCacheEntry, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCompileError, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, materializeKnowledge, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow };