@loops-adk/core 0.1.0 → 0.2.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/README.md +120 -13
- package/assets/logo.png +0 -0
- package/bin/loops.mjs +5 -5
- package/dist/{agent-sdk-RF5VJZAT.js → agent-sdk-4QJDWM7N.js} +3 -3
- package/dist/{agent-sdk-RF5VJZAT.js.map → agent-sdk-4QJDWM7N.js.map} +1 -1
- package/dist/api.d.ts +177 -3
- package/dist/api.js +26 -10
- package/dist/api.js.map +1 -1
- package/dist/{chunk-XC46B4FD.js → chunk-MA6NDQMO.js} +2 -2
- package/dist/chunk-MA6NDQMO.js.map +1 -0
- package/dist/{chunk-3BPU34DE.js → chunk-WM5QVHM2.js} +789 -46
- package/dist/chunk-WM5QVHM2.js.map +1 -0
- package/dist/{claude-cli-U7WEVAOL.js → claude-cli-75AOQUKG.js} +3 -3
- package/dist/{claude-cli-U7WEVAOL.js.map → claude-cli-75AOQUKG.js.map} +1 -1
- package/dist/{codex-6I5UZ2HM.js → codex-LYZF52WL.js} +25 -13
- package/dist/codex-LYZF52WL.js.map +1 -0
- package/dist/env/command.d.ts +1 -1
- package/dist/env/docker.d.ts +1 -1
- package/dist/env/sst.d.ts +1 -1
- package/dist/index.js +249 -11
- package/dist/index.js.map +1 -1
- package/dist/{types-B4wGVpqo.d.ts → types-Cv_3ymr9.d.ts} +118 -37
- package/package.json +10 -1
- package/skills/author-loop/SKILL.md +25 -14
- package/skills/design-agent-team/SKILL.md +108 -0
- package/skills/supervise-loop-run/SKILL.md +64 -0
- package/dist/chunk-3BPU34DE.js.map +0 -1
- package/dist/chunk-XC46B4FD.js.map +0 -1
- package/dist/codex-6I5UZ2HM.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="loops" width="320">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Stop prompting agents. Write the loop that prompts them. Make "done" mean <em>converged</em>, not <em>claimed</em>.</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@loops-adk/core"><img src="https://img.shields.io/npm/v/@loops-adk/core" alt="npm"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/status-alpha-orange" alt="status: alpha">
|
|
12
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-3178c6" alt="TypeScript">
|
|
13
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20-3c873a" alt="node >=20">
|
|
14
|
+
<img src="https://img.shields.io/badge/license-MIT-blue" alt="license: MIT">
|
|
15
|
+
</p>
|
|
4
16
|
|
|
5
17
|
`loops` is a small, nestable library for running an agent in a convergence loop. The loop finds the work, hands it to an agent, checks the result, records what it learned, and goes again until a gate _you_ define says the work is finished. You write the loop once and it drives the agent, rather than prompting the agent by hand. Compose loops and DAGs both ways, run them against any model behind a one-method `Engine`, and watch a run in a live terminal UI.
|
|
6
18
|
|
|
@@ -8,10 +20,31 @@ Every iteration runs with a **fresh context**, so a long run never rots. Progres
|
|
|
8
20
|
|
|
9
21
|
Where most "agent memory" recalls a _conversation_, this keeps your _decisions_ consistent across long work. No vector database, no embeddings, no index to sync or let go stale. **Git is the memory.**
|
|
10
22
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
## The fastest proof
|
|
24
|
+
|
|
25
|
+
A downstream agent had to preserve one upstream decision: snapshots must start
|
|
26
|
+
with the exact wire tag `SSv1|`. That decision lived only in a git commit body,
|
|
27
|
+
not in the source files or the downstream task prompt. The commit was not just a
|
|
28
|
+
fact store; it was the thread back through the journey, what was decided, why it
|
|
29
|
+
was decided, and what downstream work had to honour.
|
|
30
|
+
|
|
31
|
+
| Runner | What it could read | Result |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Memoryless graph | files plus task prompt | 0/10 preserved the contract |
|
|
34
|
+
| Loops Ledger | gated commit bodies plus grounding | 9/10 preserved the contract |
|
|
35
|
+
| Raw git dump | full git log pasted into every prompt | 10/10 on a toy log, not a real-repo operating mode |
|
|
36
|
+
|
|
37
|
+
That is the honest shape of the claim. Loops is not just `git log`: it is the
|
|
38
|
+
deterministic enforcement layer that makes agents write useful commit bodies when
|
|
39
|
+
work converges, then the grounding layer that reads those verified reasons back
|
|
40
|
+
into later fresh contexts. The value is not bare recall. A fresh agent can pull
|
|
41
|
+
on one thread and reconstruct how and why the repository got here. Full-log dump
|
|
42
|
+
is a useful sanity check on tiny histories, but on a repo with significant
|
|
43
|
+
history it is context rot and cost.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run bench:compare
|
|
47
|
+
```
|
|
15
48
|
|
|
16
49
|
```ts
|
|
17
50
|
import { loop, agentJob, commandSucceeds, agentCheck } from '@loops-adk/core';
|
|
@@ -151,6 +184,7 @@ loops run \
|
|
|
151
184
|
```bash
|
|
152
185
|
loops validate examples/confidence-gate.loop.ts # offline pre-flight: load + print the shape, no model calls
|
|
153
186
|
loops describe examples/confidence-gate.loop.ts # print the loop's shape (gate, body, nodes) without running
|
|
187
|
+
loops describe examples/confidence-gate.loop.ts --json # machine-readable shape for agents
|
|
154
188
|
loops run examples/confidence-gate.loop.ts # live Ink TUI
|
|
155
189
|
loops run examples/confidence-gate.loop.ts --no-tui # plain streamed logs
|
|
156
190
|
loops run examples/confidence-gate.loop.ts --json # NDJSON event stream
|
|
@@ -160,6 +194,19 @@ loops run examples/confidence-gate.loop.ts --json # NDJSON event stream
|
|
|
160
194
|
|
|
161
195
|
**Authoring is agent-native.** Both commands work from any repo, including one that consumes `loops` as a submodule or dependency (the recipe's folder just needs an ES module scope, which such repos already have). `loops validate <file>` is the cheap, no-model pre-flight an agent runs before `loops run`: it loads the loop, reports a fix-oriented error if anything is wrong, and prints the loop's shape (its gate, body, and dag nodes), all without spending a single agent turn. `loops describe <file>` prints that same shape on its own, so an agent can see exactly what it just authored. The authoring guide an agent reads to compose a loop is [`skills/author-loop/SKILL.md`](skills/author-loop/SKILL.md).
|
|
162
196
|
|
|
197
|
+
The end-to-end agent workflow, from authoring through reading a supervised run's decisions back as structured records rather than a raw event stream:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
loops validate feature.loop.ts --json # pre-flight: loads, no spend
|
|
201
|
+
loops describe feature.loop.ts --json # the shape, incl. each agent node's contract
|
|
202
|
+
loops run feature.loop.ts --no-tui --supervise # run it, registered for observation
|
|
203
|
+
loops list # find the runId
|
|
204
|
+
loops tail <runId> # follow live events
|
|
205
|
+
loops records <runId> --kind revision --path ship/implementation --json # the semantic decision stream, filtered
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Two supervision skills go deeper: [`skills/supervise-loop-run/SKILL.md`](skills/supervise-loop-run/SKILL.md) (monitor a run) and [`skills/design-agent-team/SKILL.md`](skills/design-agent-team/SKILL.md) (compose a specialist team).
|
|
209
|
+
|
|
163
210
|
**Offline demo** (no network, no key; uses the mock engine):
|
|
164
211
|
|
|
165
212
|
```bash
|
|
@@ -276,10 +323,10 @@ The agent launch only ever touches the `Engine` interface, so the loop knows not
|
|
|
276
323
|
|
|
277
324
|
| name | backend | notes |
|
|
278
325
|
| --------------- | -------------------------------- | ----------------------------------------------------------- |
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
326
|
+
| `codex` | `codex exec` subprocess (`execa`) | fresh process per call; read-only unless `bypassPermissions` |
|
|
327
|
+
| `claude-cli` | `claude` subprocess (`execa`) | fresh process per call; uses host Claude auth, no key |
|
|
328
|
+
| `agent-sdk` | `@anthropic-ai/claude-agent-sdk` | fresh `query()` per call; host Claude auth |
|
|
329
|
+
| `anthropic-api` | `@anthropic-ai/sdk` | token-level streaming; cheapest for judges; needs a key |
|
|
283
330
|
| `mock` | scripted, offline | for tests and examples |
|
|
284
331
|
|
|
285
332
|
Select per-run (`--engine`, `RunOptions.engine`) or per-job/condition (`engine:` takes a name **or** a ready-made `Engine`). Bring your own in ~10 lines:
|
|
@@ -314,15 +361,23 @@ const storeEngineer = defineAgent({
|
|
|
314
361
|
system: fromFile(new URL('./agents/store-engineer.md', import.meta.url)), // the persona, as markdown
|
|
315
362
|
model: 'sonnet',
|
|
316
363
|
tools: ['edit', 'bash'],
|
|
364
|
+
tier: 'worker',
|
|
317
365
|
capabilities: ['storage engine', 'id stability'],
|
|
366
|
+
outputs: [{ name: 'patch' }, { name: 'test-report' }],
|
|
367
|
+
requiresSkills: ['contract-first'],
|
|
318
368
|
skills: [tdd], // methodologies fold into the system
|
|
369
|
+
usesSkills: ['small-diff'],
|
|
370
|
+
humanGates: [{ name: 'prod-approval', when: 'deploying production changes' }],
|
|
319
371
|
failureModes: [{ mode: 'tests-flaky', recovery: 'isolate the flake, retry once' }],
|
|
320
372
|
});
|
|
321
373
|
|
|
322
374
|
agentJob({ agent: storeEngineer, prompt: 'Build the store to its tests.', ground: true });
|
|
323
375
|
```
|
|
324
376
|
|
|
325
|
-
|
|
377
|
+
For a small runnable contract plus feedback example, see
|
|
378
|
+
[`examples/contracted-agent.loop.ts`](examples/contracted-agent.loop.ts).
|
|
379
|
+
|
|
380
|
+
`agentJob` resolves the def into the engine request (`system` = persona + skills, plus `model`/`tools`); inline `system`/`model`/`tools` still override it. A **skill** is a methodology (how to work: TDD, writing-plans), not a worker. The extra contract fields are optional metadata for validation, `loops describe`, docs, and future discovery. They do not give an agent dispatch authority. This is what turns a `dag` into a named **team** (`storeEngineer`, `apiEngineer`, `securityReviewer` as small files) orchestrated by the DAG and gated by `quorum(...)`.
|
|
326
381
|
|
|
327
382
|
## Environments: test the running thing
|
|
328
383
|
|
|
@@ -381,6 +436,43 @@ dag({
|
|
|
381
436
|
|
|
382
437
|
`needs` = dependencies; a non-`pass` required dependency blocks its dependents; `optional` nodes never block or fail the DAG; an unmet `when` skips a node (counts green); cycles are detected before any work runs. `sequence(name, ...jobs)` and `parallel(name, jobs, concurrency?)` are sugar over `dag`.
|
|
383
438
|
|
|
439
|
+
### Feedback between nodes
|
|
440
|
+
|
|
441
|
+
Review feedback is a structured revision request. In a loop, a failing `review`
|
|
442
|
+
outcome is threaded into the next body turn as `ctx.lastReview`; with
|
|
443
|
+
`consumeFeedback: true`, `agentJob` appends it to the implementation prompt in a
|
|
444
|
+
standard block.
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
const implement = agentJob({
|
|
448
|
+
label: 'implementation',
|
|
449
|
+
prompt: brief,
|
|
450
|
+
consumeFeedback: true,
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
For several reviewers, use `reviewPanel` to aggregate their verdicts into one
|
|
455
|
+
outcome. Every reviewer is a gate: the panel passes when all of them clear (or
|
|
456
|
+
`pass: N` of them, k-of-n), and each failing reviewer's concern is surfaced as a
|
|
457
|
+
blocking finding threaded into the next pass. An empty panel is a construction
|
|
458
|
+
error, not a vacuous pass.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
const review = reviewPanel({
|
|
462
|
+
// pass: 2, // optional: k-of-n instead of all
|
|
463
|
+
reviewers: [
|
|
464
|
+
{ name: 'security', review: agentCheck({ question: 'Is it safe?', context: reviewContext({ diff: true, ledger: true }) }) },
|
|
465
|
+
{ name: 'correctness', review: agentCheck({ question: 'Is it correct?' }) },
|
|
466
|
+
{ name: 'simplicity', review: agentCheck({ question: 'Is it simple?', context: reviewContext({ files: ['src/**'] }) }) },
|
|
467
|
+
],
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
In a DAG, a targeted `revisionRequest({ target, findings })` reruns the target
|
|
472
|
+
node and its dependents when `maxKickbacks` allows it. `kickback(to, reason)` is
|
|
473
|
+
the terse compatibility helper for the same routed feedback. Agents can opt into
|
|
474
|
+
a small graph-position prompt block with `graphContext: true`.
|
|
475
|
+
|
|
384
476
|
**Worktree isolation: branches as teams.** A concurrent node can run in its own git worktree on a fork branch (`isolation: 'worktree'` on the DAG, or `isolate: true` per node), so parallel writers never collide on files or the index. On pass, its committed work lands back into the line with a `--no-ff` merge; a conflict fails the node honestly (loops does not auto-resolve; that's a separate layer). Each team gets its own branch, its own scratch files, and (with `DagConfig.environment`) its own stage, all born and torn down together.
|
|
385
477
|
|
|
386
478
|
For **dynamic** dispatch (a loop that discovers each unit at runtime and routes it to its own isolated sub-loop), `isolated(job)` is the same boundary as a composable wrapper rather than a predeclared node (fork, run, land back on pass):
|
|
@@ -447,6 +539,19 @@ The error taxonomy backs this: an engine classifies a throttle into a `RATE_LIMI
|
|
|
447
539
|
|
|
448
540
|
Every mode ends with a summary: result, per-loop iterations, review tallies, token usage by model, and any errors.
|
|
449
541
|
|
|
542
|
+
## Supervise a running loop
|
|
543
|
+
|
|
544
|
+
Run with `--supervise` and the loop registers itself under `~/.loops/runs/`, writing its live state there as it goes. Another process reads it with no daemon and no socket, because the filesystem is the channel (the same bet the rest of the library makes).
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
loops run build.loop.ts --supervise # in one terminal
|
|
548
|
+
loops list # in another: every supervised run, with state and iteration
|
|
549
|
+
loops status <runId> # its shape plus where it is now: iteration, last gate verdict, tokens
|
|
550
|
+
loops tail <runId> # stream its events live
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Each run keeps the raw event stream in `events.jsonl` and a smaller semantic stream in `semantic.jsonl` with dispatch, completion, surfacing, `revision-emitted`, and `revision-routed` records. Use `loops records <runId>` to inspect those records without knowing the registry path; add `--kind revision-routed`, `--kind revision` (both revision kinds), `--path ship/implementation`, `--since <time>`, `--last <n>`, or `--json` when an agent needs a filtered machine-readable stream. `list` marks a run dead if its process is gone. The read side is also on the public surface (`listRuns`, `readRunStatus`, `runEventsPath`, `runSemanticRecordsPath`), so an agent supervising a fleet of loops, killing the ones that drift and kicking work back into the ones that hit a problem, reads the same files. Out-of-process control (pause, abort, and kickback from outside) is the next step.
|
|
554
|
+
|
|
450
555
|
## What `loops` is (and isn't)
|
|
451
556
|
|
|
452
557
|
`loops` is a **fresh-context loop primitive**, not a durable workflow engine. The design bet is that **the workspace is the state**: progress _and its reasoning_ live in git (the Ledger), so each iteration can start clean and still know what came before. If the process dies mid-run, you re-run against the same workspace (the worktree holds the files, the scratch files hold the why, the log holds the milestones) and continue. You lose the bookkeeping, not the work.
|
|
@@ -464,7 +569,9 @@ It deliberately does **not** do durable mid-run replay (re-running a half-finish
|
|
|
464
569
|
- [x] **Ledger**, git-memory core: the scratch files (working memory + handoff), grounding, milestone commits
|
|
465
570
|
- [x] Worktree isolation (branches-as-teams) with `--no-ff` land-back
|
|
466
571
|
- [x] Environment axis: provider interface + offline mock
|
|
467
|
-
- [
|
|
572
|
+
- [x] Publish to npm (`@loops-adk/core`, built `dist` + types, CI release)
|
|
573
|
+
- [x] Supervision: a file-based run registry with `loops list` / `status` / `tail`
|
|
574
|
+
- [ ] Out-of-process control: `pause` / `abort` / `kickback` a running loop from outside
|
|
468
575
|
- [ ] Optional `wip:` autosave tier (per-iteration recovery, squashed on convergence)
|
|
469
576
|
- [ ] No-progress / stall detection: the third hard stop, alongside `max` and `budget`
|
|
470
577
|
- [ ] `cost per accepted change` as a first-class reported metric
|
package/assets/logo.png
ADDED
|
Binary file
|
package/bin/loops.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Thin launcher. Registers tsx's ESM loader globally so the CLI can transform a
|
|
3
3
|
// user's `.loop.ts` recipe from any repo (the run-from-anywhere contract), then
|
|
4
|
-
// hands off to the CLI.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// hands off to the CLI. From a checkout the TypeScript source is the entry (the
|
|
5
|
+
// no-build-step dev path); a published install ships no `src`, so it falls back
|
|
6
|
+
// to the built `dist`. Source wins so a stale `dist` never shadows live code.
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
@@ -11,6 +11,6 @@ import { register } from 'tsx/esm/api';
|
|
|
11
11
|
|
|
12
12
|
register();
|
|
13
13
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const
|
|
15
|
-
const entry = existsSync(
|
|
14
|
+
const src = join(here, '..', 'src', 'index.ts');
|
|
15
|
+
const entry = existsSync(src) ? src : join(here, '..', 'dist', 'index.js');
|
|
16
16
|
await import(pathToFileURL(entry).href);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { newAccumulator, mapMessage } from './chunk-CXEPZHSR.js';
|
|
2
|
-
import { SUBAGENT_TOOLS } from './chunk-
|
|
2
|
+
import { SUBAGENT_TOOLS } from './chunk-MA6NDQMO.js';
|
|
3
3
|
import { LoopError } from './chunk-I3STY7U6.js';
|
|
4
4
|
import pTimeout from 'p-timeout';
|
|
5
5
|
|
|
@@ -91,5 +91,5 @@ var AgentSdkEngine = class {
|
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
export { AgentSdkEngine };
|
|
94
|
-
//# sourceMappingURL=agent-sdk-
|
|
95
|
-
//# sourceMappingURL=agent-sdk-
|
|
94
|
+
//# sourceMappingURL=agent-sdk-4QJDWM7N.js.map
|
|
95
|
+
//# sourceMappingURL=agent-sdk-4QJDWM7N.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engines/agent-sdk.ts"],"names":[],"mappings":"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","file":"agent-sdk-
|
|
1
|
+
{"version":3,"sources":["../src/engines/agent-sdk.ts"],"names":[],"mappings":"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","file":"agent-sdk-4QJDWM7N.js","sourcesContent":["/**\n * Engine adapter: the Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`).\n * Each `run` is a fresh `query()` — a clean context per loop iteration, which\n * is the whole point. Uses the host's Claude Code auth, so it needs no API key.\n */\n\nimport pTimeout from 'p-timeout';\n\nimport {\n SUBAGENT_TOOLS,\n type AgentRequest,\n type AgentResult,\n type Engine,\n type EngineEventSink,\n type EngineOptions,\n} from './engine.ts';\nimport { mapMessage, newAccumulator } from './message-map.ts';\nimport { LoopError } from '../core/errors.ts';\n\n/**\n * Best-effort classification of an Agent SDK error into a provider-limit\n * `LoopError`, or `undefined` to fall through to the generic ENGINE mapping.\n * The SDK exposes limit state in a few shapes (a thrown error message, an\n * `error` field carrying an `SDKAssistantMessageError` string, and a\n * `rate_limit_info.resetsAt` epoch). We read defensively rather than depend on\n * an exact internal shape:\n * - a rate-limit / overloaded signal → RATE_LIMIT (resets on its own).\n * - a billing / usage / credits signal → QUOTA. A `resetsAt` (when present)\n * makes it auto-waitable; otherwise QUOTA has no reset.\n */\nfunction classifySdkLimit(error: unknown): LoopError | undefined {\n const err = (error ?? {}) as Record<string, unknown>;\n const tag = typeof err.error === 'string' ? err.error : '';\n const message = error instanceof Error ? error.message : String(error);\n const haystack = `${tag} ${message}`.toLowerCase();\n\n const info = (err.rate_limit_info ?? {}) as Record<string, unknown>;\n const resetAt =\n typeof info.resetsAt === 'number'\n ? info.resetsAt\n : typeof info.overageResetsAt === 'number'\n ? info.overageResetsAt\n : undefined;\n\n const isUsage =\n tag === 'billing_error' ||\n info.errorCode === 'credits_required' ||\n /billing|credit|usage limit|quota/.test(haystack);\n if (isUsage) {\n return new LoopError({\n code: 'QUOTA',\n phase: 'engine',\n message: `agent-sdk usage/billing limit: ${message}`,\n cause: error,\n resetAt,\n });\n }\n const isRate =\n tag === 'rate_limit' ||\n tag === 'overloaded' ||\n /rate limit|rate-limit|too many requests|overloaded/.test(haystack);\n if (isRate) {\n return new LoopError({\n code: 'RATE_LIMIT',\n phase: 'engine',\n message: `agent-sdk rate limited: ${message}`,\n cause: error,\n resetAt,\n });\n }\n return undefined;\n}\n\nexport class AgentSdkEngine implements Engine {\n readonly name = 'agent-sdk';\n constructor(private readonly opts: EngineOptions = {}) {}\n\n async run(\n req: AgentRequest,\n onEvent: EngineEventSink,\n signal: AbortSignal,\n ): Promise<AgentResult> {\n // Lazy import so installs/runs that never touch this engine don't pay for it.\n const { query } = await import('@anthropic-ai/claude-agent-sdk');\n\n const acc = newAccumulator(\n req.model ?? this.opts.defaultModel ?? 'unknown',\n );\n const abort = new AbortController();\n const onAbort = () => abort.abort();\n if (signal.aborted) abort.abort();\n else signal.addEventListener('abort', onAbort, { once: true });\n\n // The SDK option surface drifts across versions; cast at this boundary.\n const options = {\n model: req.model ?? this.opts.defaultModel,\n systemPrompt: req.system,\n cwd: req.cwd,\n allowedTools: req.allowedTools,\n // A leaf agent may not spawn sub-agents — disallow the spawn tool.\n disallowedTools: req.leaf ? SUBAGENT_TOOLS : undefined,\n permissionMode: this.opts.permissionMode,\n includePartialMessages: true,\n abortController: abort,\n } as Record<string, unknown>;\n\n try {\n const response = query({\n prompt: req.prompt,\n options,\n } as never) as AsyncIterable<unknown>;\n const consume = (async () => {\n for await (const message of response) mapMessage(message, acc, onEvent);\n })();\n await (req.timeoutMs\n ? pTimeout(consume, { milliseconds: req.timeoutMs })\n : consume);\n } catch (e) {\n if (signal.aborted)\n throw new LoopError({\n code: 'ABORTED',\n phase: 'engine',\n message: 'agent-sdk run aborted',\n });\n const limit = classifySdkLimit(e);\n if (limit) throw limit;\n throw LoopError.from(e, { code: 'ENGINE', phase: 'engine' });\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n\n onEvent({ type: 'usage', usage: acc.usage, model: acc.model });\n return {\n text: acc.text,\n usage: acc.usage,\n model: acc.model,\n stopReason: acc.stopReason,\n };\n }\n}\n"]}
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { C as ConditionInput, J as Job, R as RevisionRerun, F as FeedbackFinding, a as FeedbackDecision, O as Outcome, G as GraphPosition, b as FeedbackSeverity, c as FeedbackActionSeverity, d as JobContext, e as RevisionRequest, L as LoopConfig, D as DagConfig, f as JobMeta, g as EngineRef, W as Workspace, A as AgentDef, h as Condition, i as EngineOptions, j as Engine, k as EngineName, l as AgentRequest, U as Usage, m as EngineEventSink, n as AgentResult, E as Environment, o as EnvHandle, p as LoopEvent, q as Forge, B as BudgetConfig, r as LimitPolicy } from './types-Cv_3ymr9.js';
|
|
2
|
+
export { s as AgentContractSummary, t as AgentFailureMode, u as AgentHumanGate, v as AgentJobConfig, w as AgentOutputContract, x as AgentSkillRef, y as AgentTier, z as Budget, H as CommitJobConfig, I as ConditionResult, K as DagNode, M as EngineStreamEvent, N as ForgeOpts, P as GhForge, Q as GroundConfig, S as LogLevel, T as LoopError, V as LoopErrorCode, X as MergeOptions, Y as MockForge, Z as MockForgeOptions, _ as OutcomeStatus, $ as PrInput, a0 as PrPatch, a1 as PrRef, a2 as RawPredicate, a3 as RetryPolicy, a4 as SUBAGENT_TOOLS, a5 as Skill, a6 as agentContract, a7 as agentJob, a8 as buildChecksArgs, a9 as buildCreateArgs, aa as buildEditArgs, ab as buildMergeArgs, ac as buildViewArgs, ad as commitJob, ae as defineAgent, af as defineSkill, ag as fnJob, ah as fromFile, ai as isEngine, aj as isEnvironment, ak as isForge, al as resolveSystem } from './types-Cv_3ymr9.js';
|
|
3
|
+
|
|
4
|
+
interface RevisionRequestInput {
|
|
5
|
+
target?: string;
|
|
6
|
+
reason?: string;
|
|
7
|
+
findings?: FeedbackFinding[];
|
|
8
|
+
rerun?: RevisionRerun;
|
|
9
|
+
source?: string;
|
|
10
|
+
decision?: FeedbackDecision;
|
|
11
|
+
}
|
|
12
|
+
declare function normalizeFeedbackSeverity(severity: FeedbackSeverity | undefined): FeedbackActionSeverity;
|
|
13
|
+
declare function isRequiredFeedbackSeverity(severity: FeedbackSeverity | undefined): boolean;
|
|
14
|
+
declare function revisionRequest(input: RevisionRequestInput, over?: Partial<Outcome>): Outcome;
|
|
15
|
+
declare function kickback(to: string, reason: string, over?: Partial<Outcome>): Outcome;
|
|
16
|
+
/**
|
|
17
|
+
* The single accessor for an outcome's revision request. `Outcome.revision` is
|
|
18
|
+
* the one channel a producer sets (`revisionRequest`, `kickback`, `reviewPanel`,
|
|
19
|
+
* dag routing), so there is exactly one place to read it — no parallel `kickback`
|
|
20
|
+
* field or `data` copy to keep in sync.
|
|
21
|
+
*/
|
|
22
|
+
declare function revisionFromOutcome(outcome: Outcome): RevisionRequest | undefined;
|
|
23
|
+
declare function feedbackBlock(outcome: Outcome): string;
|
|
24
|
+
declare function graphPositionBlock(graph: GraphPosition): string;
|
|
25
|
+
type ReviewTarget = {
|
|
26
|
+
name?: string;
|
|
27
|
+
review: ConditionInput;
|
|
28
|
+
} | {
|
|
29
|
+
name?: string;
|
|
30
|
+
job: Job;
|
|
31
|
+
};
|
|
32
|
+
interface ReviewPanelConfig {
|
|
33
|
+
label?: string;
|
|
34
|
+
reviewers: ReviewTarget[];
|
|
35
|
+
/** Default `all`: every reviewer must pass. A number means k-of-n over all reviewers. */
|
|
36
|
+
pass?: 'all' | number;
|
|
37
|
+
/** When set, a failing panel emits a targeted revision request for dag routing. */
|
|
38
|
+
target?: string;
|
|
39
|
+
rerun?: RevisionRerun;
|
|
40
|
+
}
|
|
41
|
+
declare function reviewPanel(config: ReviewPanelConfig): Job;
|
|
42
|
+
interface ReviewContextConfig {
|
|
43
|
+
diff?: boolean;
|
|
44
|
+
files?: string[];
|
|
45
|
+
ledger?: boolean;
|
|
46
|
+
tests?: boolean | {
|
|
47
|
+
command: string;
|
|
48
|
+
args?: string[];
|
|
49
|
+
cwd?: string;
|
|
50
|
+
};
|
|
51
|
+
maxChars?: number;
|
|
52
|
+
}
|
|
53
|
+
declare function reviewContext(config: ReviewContextConfig): (ctx: JobContext, last: Outcome | undefined) => Promise<string>;
|
|
3
54
|
|
|
4
55
|
/**
|
|
5
56
|
* The loop primitive. `loop(config)` returns a `Job`, so loops nest by simply
|
|
@@ -903,6 +954,12 @@ interface RunOptions {
|
|
|
903
954
|
recordTo?: string;
|
|
904
955
|
/** Snapshot the shared run state here at each loop/dag/job boundary. */
|
|
905
956
|
checkpoint?: string;
|
|
957
|
+
/**
|
|
958
|
+
* Register this run in the global registry (`~/.loops/runs/<runId>`) and write
|
|
959
|
+
* its live state there, so another process can `loops list` / `status` / `tail`
|
|
960
|
+
* it. Off by default — opt in to make a run observable from outside.
|
|
961
|
+
*/
|
|
962
|
+
supervise?: boolean;
|
|
906
963
|
/** Restore shared run state written by a prior `checkpoint` before starting. */
|
|
907
964
|
resumeFrom?: string;
|
|
908
965
|
/**
|
|
@@ -930,11 +987,128 @@ interface RunResult {
|
|
|
930
987
|
spent: number;
|
|
931
988
|
remaining: number;
|
|
932
989
|
};
|
|
990
|
+
/** The registry id, when the run was supervised. */
|
|
991
|
+
runId?: string;
|
|
933
992
|
}
|
|
934
993
|
declare function run(job: Job, options?: RunOptions): Promise<RunResult>;
|
|
935
994
|
/** Process exit code mapped from a terminal outcome. */
|
|
936
995
|
declare function exitCodeFor(outcome: Outcome): number;
|
|
937
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Out-of-process supervision. A supervised run registers itself under a global
|
|
999
|
+
* registry (`~/.loops/runs/<runId>/`) and writes its live state there as it goes:
|
|
1000
|
+
*
|
|
1001
|
+
* - `status.json`: a snapshot rewritten at each boundary: the run's shape (the
|
|
1002
|
+
* static `JobMeta`) plus where it is right now (path, iteration, last gate
|
|
1003
|
+
* verdict and confidence, last outcome, token usage, terminal status at end).
|
|
1004
|
+
* - `events.jsonl`: the event stream appended live (the same record `recordTo`
|
|
1005
|
+
* writes, here automatically and in the registry).
|
|
1006
|
+
*
|
|
1007
|
+
* A separate process (a human `loops list/status/tail`, or an agent over MCP)
|
|
1008
|
+
* reads those files. No daemon, no socket: the filesystem is the channel, which
|
|
1009
|
+
* is the same "the workspace is the state" bet the rest of the library makes.
|
|
1010
|
+
* Liveness is a pid check, so a crashed run is distinguishable from a live one.
|
|
1011
|
+
*/
|
|
1012
|
+
|
|
1013
|
+
/** The registry root. `LOOPS_HOME` overrides `~/.loops` (used to isolate tests). */
|
|
1014
|
+
declare function runsHome(): string;
|
|
1015
|
+
interface RunLive {
|
|
1016
|
+
path: string[];
|
|
1017
|
+
iteration: number;
|
|
1018
|
+
lastGate?: {
|
|
1019
|
+
which: string;
|
|
1020
|
+
met: boolean;
|
|
1021
|
+
confidence?: number;
|
|
1022
|
+
reason: string;
|
|
1023
|
+
};
|
|
1024
|
+
lastOutcome?: {
|
|
1025
|
+
status: string;
|
|
1026
|
+
summary?: string;
|
|
1027
|
+
};
|
|
1028
|
+
usage: {
|
|
1029
|
+
inputTokens: number;
|
|
1030
|
+
outputTokens: number;
|
|
1031
|
+
calls: number;
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
interface RunStatus {
|
|
1035
|
+
runId: string;
|
|
1036
|
+
pid: number;
|
|
1037
|
+
cwd: string;
|
|
1038
|
+
title: string;
|
|
1039
|
+
startedAt: number;
|
|
1040
|
+
updatedAt: number;
|
|
1041
|
+
endedAt?: number;
|
|
1042
|
+
/** Stored disposition: `running` until the run ends, then the terminal status. */
|
|
1043
|
+
status: 'running' | Outcome['status'];
|
|
1044
|
+
/** Whether the owning process is still alive: computed on read, not stored. */
|
|
1045
|
+
alive?: boolean;
|
|
1046
|
+
shape?: JobMeta;
|
|
1047
|
+
live: RunLive;
|
|
1048
|
+
}
|
|
1049
|
+
/** Read one run's status, with `alive` computed from its pid. */
|
|
1050
|
+
declare function readRunStatus(runId: string): RunStatus | undefined;
|
|
1051
|
+
/** All known runs, newest first. */
|
|
1052
|
+
declare function listRuns(): RunStatus[];
|
|
1053
|
+
/** Path to a run's appended event stream (for tailing). */
|
|
1054
|
+
declare function runEventsPath(runId: string): string;
|
|
1055
|
+
/** Path to a run's semantic record stream. */
|
|
1056
|
+
declare function runSemanticRecordsPath(runId: string): string;
|
|
1057
|
+
/** A compact one-line rendering of an event, for `loops tail`. */
|
|
1058
|
+
declare function formatEvent(event: LoopEvent): string;
|
|
1059
|
+
|
|
1060
|
+
type SemanticDecision = FeedbackDecision;
|
|
1061
|
+
type SemanticRunRecord = {
|
|
1062
|
+
kind: 'dispatch';
|
|
1063
|
+
ts: number;
|
|
1064
|
+
path: string[];
|
|
1065
|
+
unit: 'job' | 'dag-node';
|
|
1066
|
+
label?: string;
|
|
1067
|
+
node?: string;
|
|
1068
|
+
/** Present for a dag-node: which run this is (1-based; +1 per kickback re-run). */
|
|
1069
|
+
attempt?: number;
|
|
1070
|
+
} | {
|
|
1071
|
+
kind: 'completion';
|
|
1072
|
+
ts: number;
|
|
1073
|
+
path: string[];
|
|
1074
|
+
unit: 'job' | 'loop' | 'dag' | 'dag-node';
|
|
1075
|
+
label?: string;
|
|
1076
|
+
outcome: SemanticOutcome;
|
|
1077
|
+
iterations?: number;
|
|
1078
|
+
/** Present for a dag-node: which run this completion is for. */
|
|
1079
|
+
attempt?: number;
|
|
1080
|
+
} | {
|
|
1081
|
+
kind: 'surfacing';
|
|
1082
|
+
ts: number;
|
|
1083
|
+
path: string[];
|
|
1084
|
+
source: 'loop-review' | 'dag-kickback';
|
|
1085
|
+
decision: SemanticDecision;
|
|
1086
|
+
severity?: FeedbackActionSeverity;
|
|
1087
|
+
from?: string;
|
|
1088
|
+
to?: string;
|
|
1089
|
+
reason: string;
|
|
1090
|
+
note?: string;
|
|
1091
|
+
} | {
|
|
1092
|
+
kind: 'revision-emitted';
|
|
1093
|
+
ts: number;
|
|
1094
|
+
path: string[];
|
|
1095
|
+
sourceEvent: 'job:end';
|
|
1096
|
+
revision: RevisionRequest;
|
|
1097
|
+
} | {
|
|
1098
|
+
kind: 'revision-routed';
|
|
1099
|
+
ts: number;
|
|
1100
|
+
path: string[];
|
|
1101
|
+
sourceEvent: 'loop:review' | 'dag:kickback';
|
|
1102
|
+
decision: SemanticDecision;
|
|
1103
|
+
revision: RevisionRequest;
|
|
1104
|
+
};
|
|
1105
|
+
interface SemanticOutcome {
|
|
1106
|
+
status: Outcome['status'];
|
|
1107
|
+
summary?: string;
|
|
1108
|
+
confidence?: number;
|
|
1109
|
+
}
|
|
1110
|
+
declare function semanticRecordsFromEvent(event: LoopEvent): SemanticRunRecord[];
|
|
1111
|
+
|
|
938
1112
|
/**
|
|
939
1113
|
* Public API. A loop-definition file imports from here and `export default`s a
|
|
940
1114
|
* `Job` (usually a `loop(...)` or `dag(...)`). The CLI runs that default export.
|
|
@@ -946,4 +1120,4 @@ declare function exitCodeFor(outcome: Outcome): number;
|
|
|
946
1120
|
/** Identity helper that pins the type of a default export to `Job`. */
|
|
947
1121
|
declare function defineJob(job: Job): Job;
|
|
948
1122
|
|
|
949
|
-
export { type AgentCheckConfig, AgentDef, AgentRequest, AgentResult, BudgetConfig, type CommitInput, type CommitRecord, type CompactOptions, Condition, ConditionInput, type ConsolidateJobConfig, type ConsolidateOptions, DagConfig, EXIT_PAUSED, Engine, type EngineFactory, EngineName, EngineOptions, EngineRef, EngineRegistry, EnvHandle, Environment, Forge, type GroundOptions, type IsolatedOptions, Job, JobContext, JobMeta, type LedgerEntry, LimitPolicy, type LogQuery, LoopConfig, LoopEvent, type MergeJobConfig, type MergeResult, type MergeSynthesisConfig, type MergeSynthesisResult, MockEngine, type MockEnvOptions, MockEnvironment, type MockResponder, Outcome, type PromptNote, type PullRequestJobConfig, type PushJobConfig, type PushOptions, type PushResult, type RetrieveOptions, type RunOptions, type RunResult, Stats, type StatsSnapshot, type TournamentConfig, Usage, Workspace, type WorktreeHandle, addWorktree, agentCheck, all, always, any, appendLedger, appendPrompt, bodyPassed, commandSucceeds, commit, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, dag, defineJob, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, forgeChecks, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isRepo, isolated, jobMeta, ledgerPath, log, loop, mergeAbort, mergeBranch, mergeJob, mergeNoCommit, mergeSynthesis, minConfidence, mockVerdict, never, not, parallel, predicate, promptPath, pullRequestJob, push, pushJob, quorum, readLedger, readPrompt, removeWorktree, renderPlan, resetLedger, resetPrompt, retrieveLedger, run, sequence, stageAll, toCondition, tournament };
|
|
1123
|
+
export { type AgentCheckConfig, AgentDef, AgentRequest, AgentResult, BudgetConfig, type CommitInput, type CommitRecord, type CompactOptions, Condition, ConditionInput, type ConsolidateJobConfig, type ConsolidateOptions, DagConfig, EXIT_PAUSED, Engine, type EngineFactory, EngineName, EngineOptions, EngineRef, EngineRegistry, EnvHandle, Environment, FeedbackActionSeverity, FeedbackDecision, FeedbackFinding, FeedbackSeverity, Forge, GraphPosition, type GroundOptions, type IsolatedOptions, Job, JobContext, JobMeta, type LedgerEntry, LimitPolicy, type LogQuery, LoopConfig, LoopEvent, type MergeJobConfig, type MergeResult, type MergeSynthesisConfig, type MergeSynthesisResult, MockEngine, type MockEnvOptions, MockEnvironment, type MockResponder, Outcome, type PromptNote, type PullRequestJobConfig, type PushJobConfig, type PushOptions, type PushResult, type RetrieveOptions, type ReviewContextConfig, type ReviewPanelConfig, RevisionRequest, type RevisionRequestInput, RevisionRerun, type RunLive, type RunOptions, type RunResult, type RunStatus, type SemanticDecision, type SemanticOutcome, type SemanticRunRecord, Stats, type StatsSnapshot, type TournamentConfig, Usage, Workspace, type WorktreeHandle, addWorktree, agentCheck, all, always, any, appendLedger, appendPrompt, bodyPassed, commandSucceeds, commit, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, dag, defineJob, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, feedbackBlock, forgeChecks, formatEvent, gateJob, graphPositionBlock, groundingText, hasStagedChanges, headSha, isDirty, isRepo, isRequiredFeedbackSeverity, isolated, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeJob, mergeNoCommit, mergeSynthesis, minConfidence, mockVerdict, never, normalizeFeedbackSeverity, not, parallel, predicate, promptPath, pullRequestJob, push, pushJob, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, retrieveLedger, reviewContext, reviewPanel, revisionFromOutcome, revisionRequest, run, runEventsPath, runSemanticRecordsPath, runsHome, semanticRecordsFromEvent, sequence, stageAll, toCondition, tournament };
|
package/dist/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mergeNoCommit, stageAll, commit, mergeAbort, log, setMeta, jobMeta, isRepo, addWorktree, childContext, composeCommitBody, mergeBranch, removeWorktree, deleteBranch, push, consolidate, toCondition, GhForge } from './chunk-
|
|
2
|
-
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, fnJob, forgeChecks, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, stageAll, toCondition } from './chunk-
|
|
1
|
+
import { mergeNoCommit, stageAll, commit, mergeAbort, log, setMeta, jobMeta, isRepo, addWorktree, childContext, composeCommitBody, mergeBranch, removeWorktree, deleteBranch, push, consolidate, toCondition, revisionFromOutcome, GhForge } from './chunk-WM5QVHM2.js';
|
|
2
|
+
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentContract, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, feedbackBlock, fnJob, forgeChecks, formatEvent, fromFile, gateJob, graphPositionBlock, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, isRequiredFeedbackSeverity, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, normalizeFeedbackSeverity, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, reviewContext, reviewPanel, revisionFromOutcome, revisionRequest, run, runEventsPath, runSemanticRecordsPath, runsHome, semanticRecordsFromEvent, stageAll, toCondition } from './chunk-WM5QVHM2.js';
|
|
3
3
|
import './chunk-JFTXJ7I2.js';
|
|
4
|
-
export { SUBAGENT_TOOLS, isEngine } from './chunk-
|
|
4
|
+
export { SUBAGENT_TOOLS, isEngine } from './chunk-MA6NDQMO.js';
|
|
5
5
|
import './chunk-Y2SD7GBL.js';
|
|
6
6
|
import { LoopError } from './chunk-I3STY7U6.js';
|
|
7
7
|
export { LoopError } from './chunk-I3STY7U6.js';
|
|
@@ -160,6 +160,7 @@ function dag(config) {
|
|
|
160
160
|
const limit = pLimit(limitN);
|
|
161
161
|
const results = /* @__PURE__ */ new Map();
|
|
162
162
|
const memo = /* @__PURE__ */ new Map();
|
|
163
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
163
164
|
let stopped = false;
|
|
164
165
|
const pendingKickback = /* @__PURE__ */ new Map();
|
|
165
166
|
const nodeCtx = (name, workspace, environment) => childContext(parent, {
|
|
@@ -167,7 +168,14 @@ function dag(config) {
|
|
|
167
168
|
path: [...path, name],
|
|
168
169
|
workspace,
|
|
169
170
|
environment,
|
|
170
|
-
lastReview: pendingKickback.get(name)
|
|
171
|
+
lastReview: pendingKickback.get(name),
|
|
172
|
+
graph: {
|
|
173
|
+
dag: config.name,
|
|
174
|
+
node: name,
|
|
175
|
+
path: [...path, name],
|
|
176
|
+
needs: nodes.get(name).needs ?? [],
|
|
177
|
+
dependents: dependents.get(name) ?? []
|
|
178
|
+
}
|
|
171
179
|
});
|
|
172
180
|
const mergeLimit = pLimit(1);
|
|
173
181
|
let forkSeq2 = 0;
|
|
@@ -264,11 +272,12 @@ function dag(config) {
|
|
|
264
272
|
path,
|
|
265
273
|
node: name,
|
|
266
274
|
phase,
|
|
267
|
-
outcome: outcome2
|
|
275
|
+
outcome: outcome2,
|
|
276
|
+
attempt: attempts.get(name)
|
|
268
277
|
});
|
|
269
278
|
if (phase === "done" && outcome2.status !== "pass" && nodes.get(name).optional !== true && stopOnError && // A node requesting a kickback is going to be re-run — don't let its
|
|
270
279
|
// (provisional) non-pass abort siblings before the feedback is resolved.
|
|
271
|
-
!(maxKickbacks > 0 && outcome2
|
|
280
|
+
!(maxKickbacks > 0 && revisionFromOutcome(outcome2)?.target)) {
|
|
272
281
|
stopped = true;
|
|
273
282
|
}
|
|
274
283
|
return outcome2;
|
|
@@ -278,6 +287,7 @@ function dag(config) {
|
|
|
278
287
|
if (existing) return existing;
|
|
279
288
|
const node = nodes.get(name);
|
|
280
289
|
const promise = (async () => {
|
|
290
|
+
attempts.set(name, (attempts.get(name) ?? 0) + 1);
|
|
281
291
|
try {
|
|
282
292
|
const needs = node.needs ?? [];
|
|
283
293
|
const deps = await Promise.all(needs.map(run2));
|
|
@@ -324,7 +334,8 @@ function dag(config) {
|
|
|
324
334
|
ts: ts(),
|
|
325
335
|
path,
|
|
326
336
|
node: name,
|
|
327
|
-
phase: "start"
|
|
337
|
+
phase: "start",
|
|
338
|
+
attempt: attempts.get(name)
|
|
328
339
|
});
|
|
329
340
|
return { outcome: await runNodeJob(name, node), phase: "done" };
|
|
330
341
|
}
|
|
@@ -369,10 +380,15 @@ function dag(config) {
|
|
|
369
380
|
});
|
|
370
381
|
for (; ; ) {
|
|
371
382
|
const from = order.find(
|
|
372
|
-
(n) =>
|
|
383
|
+
(n) => {
|
|
384
|
+
const result = results.get(n);
|
|
385
|
+
return result !== void 0 && revisionFromOutcome(result)?.target !== void 0 && !rejected.has(n);
|
|
386
|
+
}
|
|
373
387
|
);
|
|
374
388
|
if (!from) break;
|
|
375
|
-
const
|
|
389
|
+
const request = revisionFromOutcome(results.get(from));
|
|
390
|
+
const to = request.target;
|
|
391
|
+
const { reason } = request;
|
|
376
392
|
const allow = nodes.get(from).acceptsKickbackTo;
|
|
377
393
|
const note = !nodes.has(to) ? `unknown node "${to}"` : !ancestorsOf(from).has(to) ? `"${to}" is not an ancestor of "${from}"` : allow && !allow.includes(to) ? `"${from}" does not accept kickback to "${to}"` : void 0;
|
|
378
394
|
if (note) {
|
|
@@ -401,7 +417,7 @@ function dag(config) {
|
|
|
401
417
|
pendingKickback.set(to, {
|
|
402
418
|
status: "fail",
|
|
403
419
|
summary: `Kicked back from "${from}": ${reason}`,
|
|
404
|
-
|
|
420
|
+
revision: { ...request, source: request.source ?? from }
|
|
405
421
|
});
|
|
406
422
|
stopped = false;
|
|
407
423
|
await Promise.all(names.map(run2));
|