@stigmer/runner 3.14.0-dev.20260910084630 → 3.14.1

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.
Files changed (56) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/hermetic-activity.d.ts +245 -0
  3. package/dist/__test-utils__/hermetic-activity.js +369 -0
  4. package/dist/__test-utils__/hermetic-activity.js.map +1 -0
  5. package/dist/__test-utils__/mock-client.d.ts +13 -0
  6. package/dist/__test-utils__/mock-client.js +45 -0
  7. package/dist/__test-utils__/mock-client.js.map +1 -0
  8. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +172 -0
  9. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +331 -0
  10. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
  11. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +167 -0
  12. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +239 -0
  13. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
  14. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +97 -0
  15. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +132 -0
  16. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
  17. package/dist/harness/capabilities.d.ts +71 -0
  18. package/dist/harness/capabilities.js +36 -0
  19. package/dist/harness/capabilities.js.map +1 -0
  20. package/dist/harness/registry.d.ts +67 -0
  21. package/dist/harness/registry.js +112 -0
  22. package/dist/harness/registry.js.map +1 -0
  23. package/dist/harness/types.d.ts +268 -0
  24. package/dist/harness/types.js +55 -0
  25. package/dist/harness/types.js.map +1 -0
  26. package/package.json +4 -4
  27. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +229 -0
  28. package/src/__test-utils__/config-fixture.ts +63 -0
  29. package/src/__test-utils__/harness-contract/contract.ts +536 -0
  30. package/src/__test-utils__/harness-contract/recording-sink.ts +96 -0
  31. package/src/__test-utils__/harness-contract/scripted-adapter.ts +289 -0
  32. package/src/__test-utils__/harness-contract/types.ts +100 -0
  33. package/src/__test-utils__/hermetic-activity.ts +477 -0
  34. package/src/__test-utils__/proto-helpers.ts +25 -0
  35. package/src/__tests__/harness-contract.test.ts +25 -0
  36. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +422 -0
  37. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +342 -0
  38. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +166 -0
  39. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
  40. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
  41. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
  42. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
  43. package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
  44. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +45 -0
  45. package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
  46. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
  47. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
  48. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +47 -0
  49. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +201 -0
  50. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
  51. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
  52. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
  53. package/src/harness/__tests__/registry.test.ts +167 -0
  54. package/src/harness/capabilities.ts +75 -0
  55. package/src/harness/registry.ts +123 -0
  56. package/src/harness/types.ts +278 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Harness capability flags — the facts about an engine the runtime branches
3
+ * on, declared once per adapter and read nowhere else.
4
+ *
5
+ * The runtime is written against the flags, not against harness names: a
6
+ * phase that must differ per engine asks "does this harness accept a system
7
+ * prompt?" rather than "is this Cursor?". That is what keeps a new harness a
8
+ * registry row and an SDK slice instead of a new branch in every phase.
9
+ * Every row of the matrix below is either a flag here or internal to one
10
+ * adapter; nothing in it needed a third kind of thing.
11
+ *
12
+ * The matrix the contract was designed against (2026-09; Claude and Codex are
13
+ * the surveyed SDKs, not built harnesses):
14
+ *
15
+ * | Capability | Native (LangGraph) | Cursor (`@cursor/sdk`) | Claude (`@anthropic-ai/claude-agent-sdk`) | Codex (`@openai/codex-sdk`) |
16
+ * |-------------------|-----------------------------|-----------------------------------------|-----------------------------------------------|------------------------------------------------|
17
+ * | `pausePrimitive` | `interrupt` (checkpoint) | `deny-and-retry` (hook, ledger, cancel) | `callback` (`canUseTool`, in-process) | `none` (approvalPolicy only) → capture-only |
18
+ * | `stateIdSource` | `deterministic` (thread id) | `engine-minted` (agent id) | either (caller-supplied or minted) | `engine-minted` (thread id) |
19
+ * | `systemPrompt` | yes | no (rides the first message) | yes | no (`AGENTS.md` or first message) |
20
+ * | `subAgents` | yes (compiled sub-graphs) | yes (`agents` option) | yes (`AgentDefinition`) | no |
21
+ * | `toolRestriction` | yes (tool list) | no (the hook enforces) | yes (`disallowedTools`, `tools`) | per-server config |
22
+ * | `visionProfile` | PNG, JPEG, WebP, GIF | PNG, JPEG (transport re-sniffs) | (surveyed later) | (surveyed later) |
23
+ *
24
+ * Adapter-internal, deliberately NOT flags: how MCP servers are bound, how
25
+ * metering is routed, how the cost cap is applied inside the engine, how a
26
+ * run is cancelled. Those differ per engine but the runtime never needs to
27
+ * know.
28
+ *
29
+ * Two flags matter to the contract kit directly: `pausePrimitive` (the kit
30
+ * proves the two real primitives are indistinguishable above the contract
31
+ * line — both end a turn `awaiting_approval` and both take the decisions on
32
+ * reinvocation) and `stateIdSource` (an `engine-minted` adapter must bind its
33
+ * id before its first persist; a `deterministic` one must never bind).
34
+ */
35
+ import type { VisionProfile } from "../shared/attachment-vision.js";
36
+ /**
37
+ * How the engine can be made to stop before a gated side effect.
38
+ *
39
+ * - `interrupt`: the engine checkpoints and stops at the gate (LangGraph
40
+ * `interrupt`); the adapter resumes it with the decisions.
41
+ * - `deny-and-retry`: an out-of-process hook denies the tool, the adapter
42
+ * records the denial, cancels the run, and re-runs with grants on the next
43
+ * invocation.
44
+ * - `callback`: an in-process callback decides per tool; the runtime still
45
+ * returns `awaiting_approval` and reinvokes, so the adapter answers the
46
+ * callback with "deny, stop" and resumes with the decisions.
47
+ * - `none`: the engine offers no gate; the runtime confines it to
48
+ * capture-only work (a read-only sandbox) and gated actions never reach
49
+ * the engine.
50
+ */
51
+ export type PausePrimitive = "interrupt" | "deny-and-retry" | "callback" | "none";
52
+ /**
53
+ * Who mints the engine's state id. `deterministic`: the runtime derives it
54
+ * from the session before the first turn (`EnsureThread`), so it is known
55
+ * before any engine exists. `engine-minted`: the engine issues it on first
56
+ * use and the adapter must hand it to the runtime at once
57
+ * (`TurnSink.bindHarnessState`) so a crash mid-turn still resumes.
58
+ */
59
+ export type StateIdSource = "deterministic" | "engine-minted";
60
+ export interface HarnessCapabilities {
61
+ readonly pausePrimitive: PausePrimitive;
62
+ readonly stateIdSource: StateIdSource;
63
+ /** The engine accepts a system prompt; otherwise instructions ride the first user message. */
64
+ readonly systemPrompt: boolean;
65
+ /** The engine runs delegated sub-agents from a definition map. */
66
+ readonly subAgents: boolean;
67
+ /** The engine can hide or deny tools by name; otherwise the gate enforces `enabledTools`. */
68
+ readonly toolRestriction: boolean;
69
+ /** Which image types the engine can display inline; the runtime degrades the rest before the turn. */
70
+ readonly visionProfile: VisionProfile;
71
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Harness capability flags — the facts about an engine the runtime branches
3
+ * on, declared once per adapter and read nowhere else.
4
+ *
5
+ * The runtime is written against the flags, not against harness names: a
6
+ * phase that must differ per engine asks "does this harness accept a system
7
+ * prompt?" rather than "is this Cursor?". That is what keeps a new harness a
8
+ * registry row and an SDK slice instead of a new branch in every phase.
9
+ * Every row of the matrix below is either a flag here or internal to one
10
+ * adapter; nothing in it needed a third kind of thing.
11
+ *
12
+ * The matrix the contract was designed against (2026-09; Claude and Codex are
13
+ * the surveyed SDKs, not built harnesses):
14
+ *
15
+ * | Capability | Native (LangGraph) | Cursor (`@cursor/sdk`) | Claude (`@anthropic-ai/claude-agent-sdk`) | Codex (`@openai/codex-sdk`) |
16
+ * |-------------------|-----------------------------|-----------------------------------------|-----------------------------------------------|------------------------------------------------|
17
+ * | `pausePrimitive` | `interrupt` (checkpoint) | `deny-and-retry` (hook, ledger, cancel) | `callback` (`canUseTool`, in-process) | `none` (approvalPolicy only) → capture-only |
18
+ * | `stateIdSource` | `deterministic` (thread id) | `engine-minted` (agent id) | either (caller-supplied or minted) | `engine-minted` (thread id) |
19
+ * | `systemPrompt` | yes | no (rides the first message) | yes | no (`AGENTS.md` or first message) |
20
+ * | `subAgents` | yes (compiled sub-graphs) | yes (`agents` option) | yes (`AgentDefinition`) | no |
21
+ * | `toolRestriction` | yes (tool list) | no (the hook enforces) | yes (`disallowedTools`, `tools`) | per-server config |
22
+ * | `visionProfile` | PNG, JPEG, WebP, GIF | PNG, JPEG (transport re-sniffs) | (surveyed later) | (surveyed later) |
23
+ *
24
+ * Adapter-internal, deliberately NOT flags: how MCP servers are bound, how
25
+ * metering is routed, how the cost cap is applied inside the engine, how a
26
+ * run is cancelled. Those differ per engine but the runtime never needs to
27
+ * know.
28
+ *
29
+ * Two flags matter to the contract kit directly: `pausePrimitive` (the kit
30
+ * proves the two real primitives are indistinguishable above the contract
31
+ * line — both end a turn `awaiting_approval` and both take the decisions on
32
+ * reinvocation) and `stateIdSource` (an `engine-minted` adapter must bind its
33
+ * id before its first persist; a `deterministic` one must never bind).
34
+ */
35
+ export {};
36
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/harness/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The harness registry — the lifecycle fan-out over the adapters a worker
3
+ * runs, and the byte-pinned binding from harness to Temporal activity name.
4
+ *
5
+ * Plain functions over an `adapters` argument, no module state and no DI
6
+ * (the composition roots are staged plain functions; a missing adapter is a
7
+ * compile error or a loud boot throw, never a silent no-op). ORDER IS
8
+ * LOAD-BEARING: `bootHarnesses` runs adapters in declaration order because
9
+ * the Cursor harness's interceptors must patch `node:http2` before anything
10
+ * dials the control plane, and `shutdownHarnesses` runs them in reverse so
11
+ * what was set up last is torn down first.
12
+ *
13
+ * Error posture, ruled at the entry's gate (Q-S1-10):
14
+ * - Boot validates the whole table BEFORE booting anything (a duplicate name
15
+ * is a configuration defect, and a half-booted worker is the worst state to
16
+ * discover it in), then fails fast at the first adapter that rejects. A
17
+ * worker that cannot boot a harness must not start.
18
+ * - Shutdown and session release CONTINUE past a failing adapter and throw
19
+ * one `AggregateError` at the end naming each failure, so one bad teardown
20
+ * never leaks the others' resources.
21
+ *
22
+ * What is NOT here yet: `createHarnessActivities` (its body is the turn
23
+ * runtime, which does not exist until the extraction entry) and the table of
24
+ * real adapters (they exist once the Cursor and native harnesses implement
25
+ * the contract). Both land with the runtime; no empty table sits on `main`
26
+ * between the two.
27
+ *
28
+ * `HarnessName` lives here and not in `types.ts` on purpose: the wire
29
+ * vocabulary is the registry's concern. An adapter never declares the
30
+ * activity it is bound to (its `name` is a diagnostic identity); the registry
31
+ * row does.
32
+ */
33
+ import type { Config } from "../config.js";
34
+ import type { HarnessAdapter } from "./types.js";
35
+ /** The harnesses the control plane can dispatch to, as the registry knows them. */
36
+ export type HarnessName = "cursor" | "deep-agent";
37
+ /**
38
+ * Byte-pinned Temporal activity names, one per harness. The server side of
39
+ * the pin is `stigmer-server/src/temporal/agentexecution/names.ts`
40
+ * (`EXECUTE_CURSOR_ACTIVITY_NAME`, `EXECUTE_DEEP_AGENT_ACTIVITY_NAME`); the
41
+ * two must stay byte-identical or the workflow schedules an activity no
42
+ * worker registers. Never "cleaned up".
43
+ */
44
+ export declare const HARNESS_ACTIVITY_NAMES: {
45
+ readonly cursor: "ExecuteCursor";
46
+ readonly "deep-agent": "ExecuteDeepAgent";
47
+ };
48
+ export type HarnessActivityName = (typeof HARNESS_ACTIVITY_NAMES)[HarnessName];
49
+ /**
50
+ * Boot every adapter in declaration order, one at a time, awaiting each. The
51
+ * table is validated first; the first rejection stops the boot and propagates
52
+ * (adapters after it are never booted; adapters before it stay booted for the
53
+ * caller's shutdown path to release).
54
+ */
55
+ export declare function bootHarnesses(adapters: readonly HarnessAdapter[], config: Config): Promise<void>;
56
+ /**
57
+ * Shut every adapter down in reverse declaration order, continuing past
58
+ * failures. Rejects with one `AggregateError` carrying every failure once all
59
+ * adapters have been given their chance.
60
+ */
61
+ export declare function shutdownHarnesses(adapters: readonly HarnessAdapter[]): Promise<void>;
62
+ /**
63
+ * Tell every adapter the session is done on this host, in declaration order,
64
+ * continuing past failures. An adapter that parks nothing per session
65
+ * resolves as a no-op; the registry does not know which do.
66
+ */
67
+ export declare function releaseHarnessSession(adapters: readonly HarnessAdapter[], sessionId: string): Promise<void>;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The harness registry — the lifecycle fan-out over the adapters a worker
3
+ * runs, and the byte-pinned binding from harness to Temporal activity name.
4
+ *
5
+ * Plain functions over an `adapters` argument, no module state and no DI
6
+ * (the composition roots are staged plain functions; a missing adapter is a
7
+ * compile error or a loud boot throw, never a silent no-op). ORDER IS
8
+ * LOAD-BEARING: `bootHarnesses` runs adapters in declaration order because
9
+ * the Cursor harness's interceptors must patch `node:http2` before anything
10
+ * dials the control plane, and `shutdownHarnesses` runs them in reverse so
11
+ * what was set up last is torn down first.
12
+ *
13
+ * Error posture, ruled at the entry's gate (Q-S1-10):
14
+ * - Boot validates the whole table BEFORE booting anything (a duplicate name
15
+ * is a configuration defect, and a half-booted worker is the worst state to
16
+ * discover it in), then fails fast at the first adapter that rejects. A
17
+ * worker that cannot boot a harness must not start.
18
+ * - Shutdown and session release CONTINUE past a failing adapter and throw
19
+ * one `AggregateError` at the end naming each failure, so one bad teardown
20
+ * never leaks the others' resources.
21
+ *
22
+ * What is NOT here yet: `createHarnessActivities` (its body is the turn
23
+ * runtime, which does not exist until the extraction entry) and the table of
24
+ * real adapters (they exist once the Cursor and native harnesses implement
25
+ * the contract). Both land with the runtime; no empty table sits on `main`
26
+ * between the two.
27
+ *
28
+ * `HarnessName` lives here and not in `types.ts` on purpose: the wire
29
+ * vocabulary is the registry's concern. An adapter never declares the
30
+ * activity it is bound to (its `name` is a diagnostic identity); the registry
31
+ * row does.
32
+ */
33
+ /**
34
+ * Byte-pinned Temporal activity names, one per harness. The server side of
35
+ * the pin is `stigmer-server/src/temporal/agentexecution/names.ts`
36
+ * (`EXECUTE_CURSOR_ACTIVITY_NAME`, `EXECUTE_DEEP_AGENT_ACTIVITY_NAME`); the
37
+ * two must stay byte-identical or the workflow schedules an activity no
38
+ * worker registers. Never "cleaned up".
39
+ */
40
+ export const HARNESS_ACTIVITY_NAMES = {
41
+ cursor: "ExecuteCursor",
42
+ "deep-agent": "ExecuteDeepAgent",
43
+ };
44
+ /**
45
+ * Refuse a table two of whose adapters share a name. Names are the registry's
46
+ * identity for diagnostics and for this check; a duplicate means two adapters
47
+ * would be indistinguishable in every log line and kit message.
48
+ */
49
+ function assertUniqueNames(adapters) {
50
+ const seen = new Set();
51
+ for (const adapter of adapters) {
52
+ if (seen.has(adapter.name)) {
53
+ throw new Error(`harness registry: duplicate adapter name '${adapter.name}'; every adapter must have a unique name`);
54
+ }
55
+ seen.add(adapter.name);
56
+ }
57
+ }
58
+ /**
59
+ * Boot every adapter in declaration order, one at a time, awaiting each. The
60
+ * table is validated first; the first rejection stops the boot and propagates
61
+ * (adapters after it are never booted; adapters before it stay booted for the
62
+ * caller's shutdown path to release).
63
+ */
64
+ export async function bootHarnesses(adapters, config) {
65
+ assertUniqueNames(adapters);
66
+ for (const adapter of adapters) {
67
+ await adapter.boot(config);
68
+ }
69
+ }
70
+ /**
71
+ * Shut every adapter down in reverse declaration order, continuing past
72
+ * failures. Rejects with one `AggregateError` carrying every failure once all
73
+ * adapters have been given their chance.
74
+ */
75
+ export async function shutdownHarnesses(adapters) {
76
+ const failures = [];
77
+ for (const adapter of [...adapters].reverse()) {
78
+ try {
79
+ await adapter.shutdown();
80
+ }
81
+ catch (err) {
82
+ failures.push(describeFailure(adapter, "shutdown", err));
83
+ }
84
+ }
85
+ throwIfAny(failures, "harness registry: shutdown failed for one or more adapters");
86
+ }
87
+ /**
88
+ * Tell every adapter the session is done on this host, in declaration order,
89
+ * continuing past failures. An adapter that parks nothing per session
90
+ * resolves as a no-op; the registry does not know which do.
91
+ */
92
+ export async function releaseHarnessSession(adapters, sessionId) {
93
+ const failures = [];
94
+ for (const adapter of adapters) {
95
+ try {
96
+ await adapter.releaseSession(sessionId);
97
+ }
98
+ catch (err) {
99
+ failures.push(describeFailure(adapter, `releaseSession('${sessionId}')`, err));
100
+ }
101
+ }
102
+ throwIfAny(failures, `harness registry: releaseSession('${sessionId}') failed for one or more adapters`);
103
+ }
104
+ function describeFailure(adapter, call, err) {
105
+ const cause = err instanceof Error ? err : new Error(String(err));
106
+ return new Error(`${adapter.name}: ${call} rejected: ${cause.message}`, { cause });
107
+ }
108
+ function throwIfAny(failures, message) {
109
+ if (failures.length > 0)
110
+ throw new AggregateError(failures, `${message}: ${failures.map((f) => f.message).join("; ")}`);
111
+ }
112
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * The harness adapter contract — the line between what the turn runtime owns
3
+ * and what a harness owns.
4
+ *
5
+ * Stigmer runs an agent turn through one of several engines ("harnesses"):
6
+ * the native LangGraph deep-agent, the Cursor SDK, and in future the Claude
7
+ * Agent SDK and the Codex SDK. Everything about a turn that does NOT touch a
8
+ * vendor SDK — fetching the execution, resolving the blueprint and the
9
+ * environment, provisioning and locking the workspace, mounting skills,
10
+ * resolving MCP servers and approval policies, seeding the transcript, the
11
+ * persist cadence, the stall watchdog, the Temporal heartbeat, pause vs
12
+ * shutdown, the cost cap, the file-review boundary, the terminal mapping —
13
+ * is the RUNTIME's, written once. What a harness owns is its SDK slice: how
14
+ * the engine is created or resumed, how the prompt is placed, how MCP servers
15
+ * are bound, how the engine is made to stop before a gated side effect, and
16
+ * how its events become transcript rows. This file is the whole of what a
17
+ * harness author has to implement; `__test-utils__/harness-contract/` is the
18
+ * kit every implementation has to pass.
19
+ *
20
+ * Every member here is a rename of a function the Cursor loop already injects
21
+ * (`execute-cursor/turn-stream.ts` `CursorTurnStreamDeps`) or a fact the
22
+ * runtime cannot read anywhere else. Nothing here is speculative: where the
23
+ * program's original sketch and the code disagreed, the code won, and the
24
+ * disagreement was ruled at the entry's gate
25
+ * (stigmer-cloud `_projects/2026-09/20260911.02.sp.harness-contract-and-kit/`).
26
+ *
27
+ * What is deliberately NOT on this contract, and why:
28
+ *
29
+ * - No `dispose()`. One adapter object serves many concurrent turns
30
+ * (`maxConcurrentActivities`), so a per-turn teardown method on the
31
+ * adapter is a race. The adapter owns its per-turn teardown in its own
32
+ * `finally` inside `runTurn` (the Cursor harness already parks its agent
33
+ * there).
34
+ * - No `isCancelled()`, no `heartbeat(details)`, no `ExecutionStatusWriter`
35
+ * base. Each would be a second way of saying something `stopSignal`,
36
+ * `recordActivity()` or `requestPersist()` already says, and two writers of
37
+ * one fact drift (the native builders' `forceNextUpdate` flag is the same
38
+ * fact as a `requestPersist()` call).
39
+ * - No `reason` on `interrupted`, no payload on `completed`, no `retryable`
40
+ * on `failed`. Every cause of stopping is the runtime's own evidence; the
41
+ * final text and structured output are already folded onto the status;
42
+ * Temporal never retries a returned activity, so a retryable flag would
43
+ * have no reader.
44
+ * - No token-rotation hook. `Config.stigmerTokenRef` is the canonical
45
+ * mutable ref; an adapter's transport reads it per request.
46
+ * - No `TurnInput.status`. The runtime seeds `TurnSink.status` from the
47
+ * persisted transcript; a second copy on the input is the drift the
48
+ * single-source-of-truth mandate forbids.
49
+ *
50
+ * Module shape follows `shared/checkpointer/`: `types.ts`, `capabilities.ts`,
51
+ * `registry.ts`, no barrel. Nothing production-facing imports this module
52
+ * until the runtime that consumes it lands (S2 of the program).
53
+ */
54
+ import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
55
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
56
+ import type { Config } from "../config.js";
57
+ import type { NormalizedActivityInput } from "../shared/activity-input.js";
58
+ import type { HarnessCapabilities } from "./capabilities.js";
59
+ /**
60
+ * One harness, as the runtime sees it. ONE adapter object exists per worker
61
+ * process; it serves every concurrent turn of its harness and holds no
62
+ * per-turn state (per-turn state lives in the `runTurn` frame). Three
63
+ * lifetimes meet here — worker (`boot`/`shutdown`), session
64
+ * (`releaseSession`) and turn (`runTurn`) — because the Cursor harness parks
65
+ * an engine per SESSION between turns, longer than a turn and shorter than
66
+ * the worker.
67
+ *
68
+ * `name` is a diagnostic identity (log lines, kit messages, the registry's
69
+ * duplicate check). It is NOT the activity the harness is bound to: the wire
70
+ * binding is the registry row's (`registry.ts` `HARNESS_ACTIVITY_NAMES`),
71
+ * so an adapter never declares a byte-pinned wire name and a test double can
72
+ * implement this interface under its own name.
73
+ */
74
+ export interface HarnessAdapter {
75
+ readonly name: string;
76
+ readonly capabilities: HarnessCapabilities;
77
+ /**
78
+ * Worker lifetime, once per process, run by the registry in declaration
79
+ * order in BOTH composition roots (`runner.ts`, `runner-manager.ts`). Runs
80
+ * BEFORE bootstrap resolution — the Cursor interceptors must patch
81
+ * `node:http2` before the control plane is dialled — so `config` carries
82
+ * no Temporal coordinates yet. Vendor SDKs are imported lazily inside, so a
83
+ * harness that is not selected costs nothing at boot. A rejection here
84
+ * fails the worker's boot; a worker that cannot boot a harness must not
85
+ * start.
86
+ */
87
+ boot(config: Config): Promise<void>;
88
+ /**
89
+ * Worker lifetime, once per process, after the Temporal worker has drained.
90
+ * Releases everything the adapter still holds (the Cursor harness closes
91
+ * every parked agent). Must resolve even when nothing is held.
92
+ */
93
+ shutdown(): Promise<void>;
94
+ /**
95
+ * Session lifetime: the session is done on this host, release anything
96
+ * parked for it (the Cursor harness: the parked agent, its executor and the
97
+ * MCP subprocesses the lease pins, #215). Called by the registry from the
98
+ * manager's `removeSession`. A harness that parks nothing per session
99
+ * resolves as a no-op and says so in its header. Unknown session ids are a
100
+ * no-op, never an error: the runtime does not track which host parked what.
101
+ */
102
+ releaseSession(sessionId: string): Promise<void>;
103
+ /**
104
+ * Turn lifetime: run ONE engine turn against the resolved input, folding
105
+ * the engine's transcript rows into `sink.status` as they arrive, and
106
+ * settle with a {@link TurnOutcome}.
107
+ *
108
+ * The rules every implementation is held to (the kit's invariants):
109
+ *
110
+ * - Resolves, never rejects. A vendor failure becomes
111
+ * `{ kind: "failed", message }` with the user-facing sentence the
112
+ * adapter's classifier produced. A `CancelledFailure` never escapes: the
113
+ * runtime, not the adapter, decides what is a pause and what is a
114
+ * shutdown, and it throws exactly where Temporal semantics require.
115
+ * - Stops promptly when `sink.stopSignal` aborts, whatever the cause,
116
+ * settling `interrupted`. Every call the adapter makes is bounded: the
117
+ * runtime's heartbeat is live for the whole activity, and a live
118
+ * heartbeat over an unbounded call keeps a dead activity alive forever.
119
+ * - Proposes, never adjudicates. A gated side effect surfaces as a
120
+ * WAITING_APPROVAL row on `sink.status` and the turn ends
121
+ * `awaiting_approval`; the decision arrives on the next invocation in
122
+ * `input.approvalDecisions`. APPROVE executes exactly once; REJECT and
123
+ * SKIP never execute.
124
+ * - Owns its own per-turn teardown in a `finally` inside this method.
125
+ */
126
+ runTurn(input: TurnInput, sink: TurnSink): Promise<TurnOutcome>;
127
+ }
128
+ /**
129
+ * What the runtime resolved for this turn and the adapter cannot read
130
+ * anywhere else. This is the core; the runtime extraction (S2) grows it one
131
+ * typed field per phase it takes over from the orchestrators (environment,
132
+ * workspace, skills, MCP servers with merged policies, attachments, memory,
133
+ * prompt bundle, model), in the `setup.ts` `SetupResult` mold.
134
+ *
135
+ * `threadId` is the engine's state id as the runtime knows it: empty on an
136
+ * `engine-minted` harness's first turn (nothing minted yet) and the id the
137
+ * adapter bound through {@link TurnSink.bindHarnessState} on every later
138
+ * invocation; the runtime-minted id on every turn of a `deterministic`
139
+ * harness. An adapter derives create-vs-resume from it and its own state; the
140
+ * contract carries no `isReinvocation` flag because the two harnesses would
141
+ * derive it differently.
142
+ */
143
+ export interface TurnInput extends NormalizedActivityInput {
144
+ /**
145
+ * The session this turn belongs to. Read by the runtime from the fetched
146
+ * execution; the adapter needs it to key anything it parks per session and
147
+ * to recognise a later {@link HarnessAdapter.releaseSession}.
148
+ */
149
+ readonly sessionId: string;
150
+ /**
151
+ * The approval decisions the user has made on this execution's WAITING
152
+ * rows, keyed by tool-call id: the one projection both harness readers
153
+ * agree on (`status.messages[].toolCalls[]` where `approvalAction` is set
154
+ * and `status` is WAITING_APPROVAL). Derived by the runtime from
155
+ * `sink.status` on every invocation, never stored, so it cannot drift from
156
+ * the rows. An adapter reads the ROW for anything else it needs (args,
157
+ * content digest) and this map for the verdict; it never re-derives the
158
+ * verdict from the rows itself.
159
+ */
160
+ readonly approvalDecisions: ReadonlyMap<string, ApprovalAction>;
161
+ }
162
+ /**
163
+ * The runtime's face during one turn: the five things an adapter may ask of
164
+ * it, and the one status it folds into. One sink per turn, owned by the
165
+ * runtime; the adapter never constructs one.
166
+ *
167
+ * Field ownership on `status` before the canonical transcript lands (S4):
168
+ * the adapter appends the engine's transcript rows (assistant messages,
169
+ * tool-call rows and their approval status, sub-agent rows, todos); the
170
+ * runtime writes the phase, the terminal system messages, `streamingUsage`,
171
+ * artifacts, write-backs and the file-review projection. An adapter never
172
+ * writes a phase or a terminal copy: those are Temporal semantics the
173
+ * runtime owns once.
174
+ */
175
+ export interface TurnSink {
176
+ /**
177
+ * The one execution status this turn folds into. On a reinvocation it is
178
+ * seeded by the runtime from the persisted transcript, so the WAITING rows
179
+ * the adapter wrote last time, and their decisions, are already on it.
180
+ */
181
+ readonly status: AgentExecutionStatus;
182
+ /**
183
+ * The ONE way a turn is told to stop, whatever the cause: user pause,
184
+ * worker shutdown, stall, cost cap, platform STOP. The runtime knows why
185
+ * and maps the outcome; the adapter's only job is to settle promptly as
186
+ * `interrupted`. `stopSignal.reason` is the runtime's own evidence — an
187
+ * adapter never branches on it. Check `aborted` at every step boundary and
188
+ * listen for `abort` inside anything long-running. May already be aborted
189
+ * when `runTurn` is entered; then return `interrupted` before doing any
190
+ * work.
191
+ */
192
+ readonly stopSignal: AbortSignal;
193
+ /**
194
+ * "Persist the status soon": schedules a write through the runtime's single
195
+ * persist chokepoint (tool-output offload, size cap, secret withholding,
196
+ * the streaming scheduler). Fire and forget — never awaited by the adapter,
197
+ * never a promise. The runtime persists unconditionally when the turn
198
+ * settles, so no adapter has to flush before returning.
199
+ */
200
+ requestPersist(): void;
201
+ /**
202
+ * "I made progress": resets the runtime's stall watchdog and is carried
203
+ * into the next Temporal heartbeat. Call it on every engine event and every
204
+ * token delta — a long generation emits deltas but few discrete events, and
205
+ * resetting only on events false-positives a stall. Never throws.
206
+ */
207
+ recordActivity(): void;
208
+ /**
209
+ * Token counts for one engine turn. The runtime accumulates, prices and
210
+ * enforces `max_cost_usd`; an adapter reports and never accounts. Every
211
+ * count is a non-negative delta since the previous report.
212
+ */
213
+ reportUsage(delta: UsageDelta): void;
214
+ /**
215
+ * The engine-minted state id, the moment it exists and BEFORE the turn
216
+ * proceeds, so a crash mid-turn still resumes on the next invocation. The
217
+ * runtime writes it to the session at once (the Cursor harness's
218
+ * `harness_state_id`). Called only by adapters whose
219
+ * `capabilities.stateIdSource` is `"engine-minted"`, and before their first
220
+ * `requestPersist`; a `deterministic` harness never calls it. Rejects when
221
+ * the session write fails; the adapter then ends the turn `failed` with
222
+ * that error and executes nothing further.
223
+ */
224
+ bindHarnessState(harnessStateId: string): Promise<void>;
225
+ }
226
+ /**
227
+ * Token counts for one engine turn, as the Cursor loop reads them from the
228
+ * SDK's `turn-ended` delta (`execute-cursor/usage-accumulator.ts`
229
+ * `TurnUsage` is this shape; the runtime extraction collapses the two). Every
230
+ * field is optional because engines report different subsets; a missing
231
+ * field means zero, never "unknown".
232
+ */
233
+ export interface UsageDelta {
234
+ readonly inputTokens?: number;
235
+ readonly outputTokens?: number;
236
+ readonly cacheReadTokens?: number;
237
+ readonly cacheWriteTokens?: number;
238
+ }
239
+ /**
240
+ * How one turn ended, carrying ONLY what the runtime cannot read from
241
+ * `sink.status` or its own evidence.
242
+ *
243
+ * - `completed`: the engine finished. The final text and any structured
244
+ * output are already folded onto the status; nothing rides here.
245
+ * - `awaiting_approval`: the engine proposed at least one gated side effect
246
+ * and stopped. The WAITING_APPROVAL rows are already on the status; the
247
+ * runtime persists them and returns to the workflow, which reinvokes with
248
+ * the decisions.
249
+ * - `failed`: the engine or its transport failed in a way the adapter can
250
+ * name. `message` is the user-facing sentence; `cause` is for the log.
251
+ * The runtime persists FAILED and RETURNS (Temporal does not retry a
252
+ * returned activity; re-running the same prompt would fail the same way).
253
+ * - `interrupted`: `sink.stopSignal` aborted and the adapter stopped. WHY it
254
+ * aborted is the runtime's evidence (its watchdog, its accounting, its
255
+ * heartbeat, its shutdown signal), so no reason rides here; the runtime
256
+ * classifies and applies the throw-vs-return table.
257
+ */
258
+ export type TurnOutcome = {
259
+ readonly kind: "completed";
260
+ } | {
261
+ readonly kind: "awaiting_approval";
262
+ } | {
263
+ readonly kind: "failed";
264
+ readonly message: string;
265
+ readonly cause?: unknown;
266
+ } | {
267
+ readonly kind: "interrupted";
268
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The harness adapter contract — the line between what the turn runtime owns
3
+ * and what a harness owns.
4
+ *
5
+ * Stigmer runs an agent turn through one of several engines ("harnesses"):
6
+ * the native LangGraph deep-agent, the Cursor SDK, and in future the Claude
7
+ * Agent SDK and the Codex SDK. Everything about a turn that does NOT touch a
8
+ * vendor SDK — fetching the execution, resolving the blueprint and the
9
+ * environment, provisioning and locking the workspace, mounting skills,
10
+ * resolving MCP servers and approval policies, seeding the transcript, the
11
+ * persist cadence, the stall watchdog, the Temporal heartbeat, pause vs
12
+ * shutdown, the cost cap, the file-review boundary, the terminal mapping —
13
+ * is the RUNTIME's, written once. What a harness owns is its SDK slice: how
14
+ * the engine is created or resumed, how the prompt is placed, how MCP servers
15
+ * are bound, how the engine is made to stop before a gated side effect, and
16
+ * how its events become transcript rows. This file is the whole of what a
17
+ * harness author has to implement; `__test-utils__/harness-contract/` is the
18
+ * kit every implementation has to pass.
19
+ *
20
+ * Every member here is a rename of a function the Cursor loop already injects
21
+ * (`execute-cursor/turn-stream.ts` `CursorTurnStreamDeps`) or a fact the
22
+ * runtime cannot read anywhere else. Nothing here is speculative: where the
23
+ * program's original sketch and the code disagreed, the code won, and the
24
+ * disagreement was ruled at the entry's gate
25
+ * (stigmer-cloud `_projects/2026-09/20260911.02.sp.harness-contract-and-kit/`).
26
+ *
27
+ * What is deliberately NOT on this contract, and why:
28
+ *
29
+ * - No `dispose()`. One adapter object serves many concurrent turns
30
+ * (`maxConcurrentActivities`), so a per-turn teardown method on the
31
+ * adapter is a race. The adapter owns its per-turn teardown in its own
32
+ * `finally` inside `runTurn` (the Cursor harness already parks its agent
33
+ * there).
34
+ * - No `isCancelled()`, no `heartbeat(details)`, no `ExecutionStatusWriter`
35
+ * base. Each would be a second way of saying something `stopSignal`,
36
+ * `recordActivity()` or `requestPersist()` already says, and two writers of
37
+ * one fact drift (the native builders' `forceNextUpdate` flag is the same
38
+ * fact as a `requestPersist()` call).
39
+ * - No `reason` on `interrupted`, no payload on `completed`, no `retryable`
40
+ * on `failed`. Every cause of stopping is the runtime's own evidence; the
41
+ * final text and structured output are already folded onto the status;
42
+ * Temporal never retries a returned activity, so a retryable flag would
43
+ * have no reader.
44
+ * - No token-rotation hook. `Config.stigmerTokenRef` is the canonical
45
+ * mutable ref; an adapter's transport reads it per request.
46
+ * - No `TurnInput.status`. The runtime seeds `TurnSink.status` from the
47
+ * persisted transcript; a second copy on the input is the drift the
48
+ * single-source-of-truth mandate forbids.
49
+ *
50
+ * Module shape follows `shared/checkpointer/`: `types.ts`, `capabilities.ts`,
51
+ * `registry.ts`, no barrel. Nothing production-facing imports this module
52
+ * until the runtime that consumes it lands (S2 of the program).
53
+ */
54
+ export {};
55
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/harness/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.14.0-dev.20260910084630",
3
+ "version": "3.14.1",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -104,9 +104,9 @@
104
104
  "@opentelemetry/sdk-metrics": "^2.0.0",
105
105
  "@opentelemetry/sdk-trace-base": "^2.0.0",
106
106
  "@opentelemetry/sdk-trace-node": "^2.0.0",
107
- "@stigmer/protos": "3.14.0-dev.20260910084630",
108
- "@stigmer/temporal-codecs": "3.14.0-dev.20260910084630",
109
- "@stigmer/zip-structure": "3.14.0-dev.20260910084630",
107
+ "@stigmer/protos": "3.14.1",
108
+ "@stigmer/temporal-codecs": "3.14.1",
109
+ "@stigmer/zip-structure": "3.14.1",
110
110
  "@temporalio/activity": "1.16.2",
111
111
  "@temporalio/common": "1.16.2",
112
112
  "@temporalio/interceptors-opentelemetry": "1.16.2",