@agentproto/workflow-runtime 0.9.0 → 0.10.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
@@ -242,6 +315,81 @@ interface AgentStep {
242
315
  * declarative `agent.ref` names an app-scoped agent. Only meaningful with
243
316
  * `adapter`; ignored on a `sessionRef` reuse. */
244
317
  options?: Record<string, boolean | number | string>;
318
+ /** Harness pinning for this step's spawn. See {@link AgentHarness}. */
319
+ harness?: AgentHarness;
320
+ }
321
+ /**
322
+ * `kind: "gate"` — run a shell command through the host's subprocess runner
323
+ * as a deterministic pass/fail check (AIP-15 P3 / AIP-17). Exit code 0 is a
324
+ * pass; any other code is a failure. Bound output is `{ ok, exitCode, report
325
+ * }`; `report` is the process' stdout (if it parses as JSON) or the file at
326
+ * `reportPath` (relative to `cwd`), parsed as JSON.
327
+ */
328
+ interface GateStep {
329
+ kind: "gate";
330
+ id: string;
331
+ command: string;
332
+ /** Command arguments. Each element (selector or string) resolves per-run
333
+ * against the bindings: a LEADING `$input|$item|$steps.<id>|$index` token
334
+ * (AIP-16 prefix grammar) resolves and any trailing text is appended
335
+ * verbatim (`"$input.bookDir/knowledge"` → `"<resolved>/knowledge"`); a
336
+ * string that is exactly a ref resolves to that value's string form;
337
+ * `$$…` stays a literal `$`; a ref that resolves to nothing (or a `$…`
338
+ * string that opens no known ref token) throws naming the step and the
339
+ * arg index. */
340
+ args?: readonly (Selector<string> | string)[];
341
+ /** Working directory. Selector form resolves per-run. A string may carry
342
+ * the same leading-`$…` ref rule as `args` (leading token + trailing
343
+ * text, `$$` escape, unresolvable ref throws). The RESOLVED cwd is made
344
+ * absolute: absolute stays absolute; relative (incl. `.`) resolves
345
+ * against the run's own `ctx.cwd` — never the daemon process cwd.
346
+ * Omit for the run's `ctx.cwd`. */
347
+ cwd?: Selector<string> | string;
348
+ /** Path (relative to `cwd`), of a JSON report file, consulted when stdout
349
+ * doesn't itself parse as JSON. */
350
+ reportPath?: string;
351
+ /** Hard wall-clock cap for a single command invocation, in ms. */
352
+ timeoutMs?: number;
353
+ /** Re-run on a failing exit code, up to `maxAttempts` (default 1 = no
354
+ * retry — a single attempt, fail immediately on a non-zero exit). */
355
+ retry?: {
356
+ maxAttempts: number;
357
+ backoff: "fixed" | "exponential";
358
+ initialMs?: number;
359
+ };
360
+ /** Re-prompt-and-rerun on a failing attempt, BEFORE each retry after the
361
+ * first: send the named prior {@link AgentStep}'s session (resolved via
362
+ * {@link AgentSessionHost.resolveByLabel}, the same lookup a `sessionRef`
363
+ * reuse uses) a prompt with this gate's last report injected, wait for
364
+ * its turn, then re-run the gate command. */
365
+ onFail?: {
366
+ reprompt: string;
367
+ with?: Record<string, unknown>;
368
+ };
369
+ }
370
+ /** One gate command invocation's raw result — before report resolution. */
371
+ interface GateCommandResult {
372
+ exitCode: number;
373
+ stdout: string;
374
+ stderr: string;
375
+ timedOut?: boolean;
376
+ }
377
+ /** Host-injectable subprocess runner for {@link GateStep}. Undefined ⇒ the
378
+ * runtime's own `node:child_process`-backed default. */
379
+ type GateCommandRunner = (spec: {
380
+ command: string;
381
+ args: readonly string[];
382
+ cwd: string;
383
+ timeoutMs?: number;
384
+ }) => Promise<GateCommandResult>;
385
+ /** One `kind: "gate"` command attempt's outcome, reported as it happens
386
+ * (every attempt, not just the last) — the `gate-report` lifecycle event. */
387
+ interface GateReportEvent {
388
+ stepId: string;
389
+ ok: boolean;
390
+ exitCode: number;
391
+ report: unknown;
392
+ attempt: number;
245
393
  }
246
394
  /** What a declarative agent-step's `agent.ref` resolves to — the adapter to
247
395
  * spawn under plus any adapter options that pick which concrete agent runs
@@ -262,7 +410,7 @@ interface AgentRefResolution {
262
410
  * issue #21534); tightening the union to `ToolStep<unknown, …>` rejects a
263
411
  * concrete `ToolStep<MarketSearchInput, …>` on the zod `ZodType<T>` variance.
264
412
  */
265
- type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep;
413
+ type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep | GateStep;
266
414
  interface RuntimeWorkflow {
267
415
  id: string;
268
416
  description?: string;
@@ -270,10 +418,21 @@ interface RuntimeWorkflow {
270
418
  /** Pick the run's final output (default: the last top-level step's output). */
271
419
  output?: Selector<unknown>;
272
420
  }
421
+ /** A human/host decision on one approval request. `who` records WHO decided
422
+ * ("human", "timeout", "cancelled", …); `note` is optional free text. */
423
+ interface ApprovalDecision {
424
+ approved: boolean;
425
+ who: string;
426
+ note?: string;
427
+ }
273
428
  interface ApprovalRequest {
274
429
  stepId: string;
275
430
  prompt: string;
276
431
  approvers: readonly string[];
432
+ artifacts?: readonly string[];
433
+ /** The step's `timeoutMs` when set — the host may enforce it itself; the
434
+ * runtime's default when the host doesn't is to wait forever. */
435
+ timeoutMs?: number;
277
436
  }
278
437
  interface ResumeRequest {
279
438
  stepId: string;
@@ -291,6 +450,8 @@ interface AgentSessionHost {
291
450
  sandbox?: AgentSandboxRef;
292
451
  /** Adapter option id → value for this spawn (see {@link AgentStep.options}). */
293
452
  options?: Record<string, boolean | number | string>;
453
+ /** Harness pinning for this spawn (see {@link AgentStep.harness}). */
454
+ harness?: AgentHarness;
294
455
  }): Promise<string>;
295
456
  /** Send a prompt to an existing session and wait for its turn to end. */
296
457
  sendPromptAndWait(sessionId: string, prompt: string): Promise<void>;
@@ -302,6 +463,16 @@ interface AgentSessionHost {
302
463
  readFinalMessage?(sessionId: string): Promise<string>;
303
464
  /** Current cumulative cost (USD) of a session, for run-level budgeting. */
304
465
  readCostUsd?(sessionId: string): Promise<number>;
466
+ /** Emit a harness-warning for a session — the pass-through channel behind
467
+ * the AIP-15 "never silently ignore" `session:harness-warning` event (used
468
+ * by the runtime for `harness.knowledge` empty matches, reason
469
+ * `knowledge-empty`). Optional: hosts without an event bus omit it and
470
+ * the runtime degrades to the run-record entry alone. */
471
+ emitHarnessWarning?(input: {
472
+ sessionId: string;
473
+ warnings: readonly string[];
474
+ label?: string;
475
+ }): void;
305
476
  }
306
477
  /** One journal entry: a cached step output plus the hash of the inputs that produced it. */
307
478
  interface StepCacheEntry {
@@ -319,8 +490,10 @@ interface RunWorkflowArgs {
319
490
  workflow: RuntimeWorkflow;
320
491
  input?: unknown;
321
492
  signal?: AbortSignal;
322
- /** Decide an {@link ApprovalStep}. Default: auto-approve. */
323
- approve?: (req: ApprovalRequest) => boolean | Promise<boolean>;
493
+ /** Decide an {@link ApprovalStep}. Default: auto-approve. May return a bare
494
+ * boolean (equivalent to `{approved, who: "host"}`) or a full
495
+ * {@link ApprovalDecision} recording who decided. */
496
+ approve?: (req: ApprovalRequest) => boolean | ApprovalDecision | Promise<boolean | ApprovalDecision>;
324
497
  /** Supply a {@link SuspendStep}'s resume payload. Default: throw + suspend. */
325
498
  resume?: (req: ResumeRequest) => unknown | Promise<unknown>;
326
499
  /** Host-injected agent session runtime. Undefined ⇒ {@link AgentStep} throws. */
@@ -341,6 +514,12 @@ interface RunWorkflowArgs {
341
514
  onStepStart?: (stepId: string) => void;
342
515
  /** Called when a step completes execution, with its output. */
343
516
  onStepComplete?: (stepId: string, output: unknown) => void;
517
+ /** Host-injectable subprocess runner for `kind: "gate"` steps. Undefined ⇒
518
+ * the runtime's own `node:child_process`-backed default. */
519
+ runGateCommand?: GateCommandRunner;
520
+ /** Called once per `kind: "gate"` command attempt (every attempt, not just
521
+ * the last) — the `gate-report` lifecycle event. */
522
+ onGateReport?: (ev: GateReportEvent) => void;
344
523
  }
345
524
  interface WorkflowRunResult {
346
525
  output: unknown;
@@ -414,6 +593,16 @@ declare class WorkflowCompileError extends Error {
414
593
  }
415
594
  /** Resolve a single `$…` reference string against the run bindings. */
416
595
  declare function resolveRef(ref: string, b: Bindings): unknown;
596
+ /** Resolve the leading ref token of a string (see {@link REF_PREFIX_RE}):
597
+ * if `value` starts with a `$input`/`$item`/`$steps`/`$index` reference
598
+ * token, resolve it against the bindings and return the resolved value plus
599
+ * the literal remainder (`rest`). Throws exactly like {@link resolveRef} for
600
+ * a matched-but-malformed token; returns `undefined` when the string does
601
+ * not START with a ref token at all (caller decides pass-through vs error). */
602
+ declare function resolveRefPrefixed(value: string, b: Bindings): {
603
+ resolved: unknown;
604
+ rest: string;
605
+ } | undefined;
417
606
  /** Recursively resolve a value node: refs in strings, into arrays/objects. */
418
607
  declare function resolveValue(node: unknown, b: Bindings): unknown;
419
608
  /** Evaluate a `while`/`when` predicate string against the bindings. */
@@ -468,9 +657,62 @@ interface AgentStepFields {
468
657
  outputSchema?: AgentStep["outputSchema"];
469
658
  maxRetries?: number;
470
659
  options?: Record<string, boolean | number | string>;
660
+ harness?: AgentHarness;
471
661
  }
472
662
  /** Build a runtime {@link AgentStep} from field values, applying the same
473
663
  * defaults everywhere: `policy` defaults to `{ awaiting: "fail" }`. */
474
664
  declare function buildAgentStep(id: string, fields: AgentStepFields): AgentStep;
475
665
 
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 };
666
+ /**
667
+ * `harness.knowledge` materialization (AIP-15 P2) — resolve each selector
668
+ * against its AIP-10 corpus workspace with the same `resolveKnowledge` the
669
+ * corpus CLI previews with, then write the matched raw entries (frontmatter +
670
+ * body) into the step's cwd under `.knowledge/` BEFORE the step's session
671
+ * runs. Idempotent: every run rewrites the same deterministic file set.
672
+ */
673
+
674
+ /**
675
+ * Resolve every `$…`-bearing string of each `harness.knowledge[]` selector
676
+ * (`workspace`, `anyOf[]`, `allOf[]`, `kinds[]`) against the run bindings,
677
+ * producing selectors whose strings are run-resolved and stripped of the
678
+ * loader's internal `deferred` flag. Selectors without refs pass through
679
+ * unchanged (a copy). See {@link resolveRefString} for the ref rule.
680
+ */
681
+ declare function resolveKnowledgeSelectors(stepId: string, selectors: readonly HarnessKnowledgeSelector[], b: Bindings): HarnessKnowledgeSelector[];
682
+ interface MaterializedKnowledge {
683
+ records: KnowledgeAppliedRecord[];
684
+ /** Total entries written across all selectors — drives the prompt note. */
685
+ written: number;
686
+ /** human-readable warnings for empty matches (`knowledge-empty`). */
687
+ warnings: string[];
688
+ }
689
+ /**
690
+ * Materialize every selector in `selectors` under `<stepCwd>/.knowledge/`.
691
+ * Throws on a non-"files" `mode` or an unreadable workspace — the loader
692
+ * rejects both earlier; this is the direct-TS-authoring path's backstop.
693
+ */
694
+ declare function materializeKnowledge(stepId: string, selectors: readonly HarnessKnowledgeSelector[], stepCwd: string): Promise<MaterializedKnowledge>;
695
+
696
+ /**
697
+ * NodeFsPort — a read-mostly `FsPort` over the host filesystem, rooted at an
698
+ * AIP-10 corpus workspace. The local-FS twin of the adapter `corpus-cli`'s
699
+ * knowledge preview uses, so `harness.knowledge` materialization resolves a
700
+ * workspace with exactly the same port (and therefore the same hidden-segment
701
+ * skipping and path confinement) the CLI does.
702
+ */
703
+
704
+ declare class NodeFsPort implements FsPort {
705
+ private readonly root;
706
+ constructor(root: string);
707
+ private resolve;
708
+ exists(p: string): Promise<boolean>;
709
+ readFile(p: string): Promise<string>;
710
+ writeFile(p: string, content: string): Promise<void>;
711
+ appendFile(p: string, content: string): Promise<void>;
712
+ readdir(p: string): Promise<readonly string[]>;
713
+ walk(p: string): Promise<readonly string[]>;
714
+ stat(p: string): Promise<FsStat | null>;
715
+ lock(): Promise<FsLockHandle>;
716
+ }
717
+
718
+ 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 };