@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.
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +245 -0
- package/dist/__test-utils__/hermetic-activity.js +369 -0
- package/dist/__test-utils__/hermetic-activity.js.map +1 -0
- package/dist/__test-utils__/mock-client.d.ts +13 -0
- package/dist/__test-utils__/mock-client.js +45 -0
- package/dist/__test-utils__/mock-client.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +172 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +331 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +167 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +239 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +97 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +132 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
- package/dist/harness/capabilities.d.ts +71 -0
- package/dist/harness/capabilities.js +36 -0
- package/dist/harness/capabilities.js.map +1 -0
- package/dist/harness/registry.d.ts +67 -0
- package/dist/harness/registry.js +112 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/types.d.ts +268 -0
- package/dist/harness/types.js +55 -0
- package/dist/harness/types.js.map +1 -0
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +229 -0
- package/src/__test-utils__/config-fixture.ts +63 -0
- package/src/__test-utils__/harness-contract/contract.ts +536 -0
- package/src/__test-utils__/harness-contract/recording-sink.ts +96 -0
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +289 -0
- package/src/__test-utils__/harness-contract/types.ts +100 -0
- package/src/__test-utils__/hermetic-activity.ts +477 -0
- package/src/__test-utils__/proto-helpers.ts +25 -0
- package/src/__tests__/harness-contract.test.ts +25 -0
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +422 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +342 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +166 -0
- package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +45 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +47 -0
- package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +201 -0
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
- package/src/harness/__tests__/registry.test.ts +167 -0
- package/src/harness/capabilities.ts +75 -0
- package/src/harness/registry.ts +123 -0
- 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.
|
|
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.
|
|
108
|
-
"@stigmer/temporal-codecs": "3.14.
|
|
109
|
-
"@stigmer/zip-structure": "3.14.
|
|
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",
|