@intx/workflow-host 0.3.0 → 0.4.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 +21 -4
- package/dist/adapters/mail-part-store.d.ts +46 -0
- package/dist/adapters/mail-part-store.js +251 -0
- package/dist/adapters/repo-store.js +5 -14
- package/dist/adapters/spawn-child.d.ts +42 -6
- package/dist/adapters/spawn-child.js +8 -18
- package/dist/adapters/step-invoker.d.ts +52 -2
- package/dist/adapters/step-invoker.js +230 -60
- package/dist/adapters/substrate-mailbox-store.d.ts +80 -0
- package/dist/adapters/substrate-mailbox-store.js +404 -0
- package/dist/child/child-mailbox-reader.d.ts +10 -0
- package/dist/child/child-mailbox-reader.js +23 -0
- package/dist/child/credential-cell.d.ts +8 -0
- package/dist/child/credential-cell.js +66 -0
- package/dist/child/from-process-env.d.ts +12 -0
- package/dist/child/from-process-env.js +6 -0
- package/dist/child/index.d.ts +4 -1
- package/dist/child/index.js +4 -1
- package/dist/child/mailbox-mutation-bridge.d.ts +61 -0
- package/dist/child/mailbox-mutation-bridge.js +101 -0
- package/dist/child/mailbox-watch-registry.d.ts +17 -0
- package/dist/child/mailbox-watch-registry.js +61 -0
- package/dist/child/outbound-mail-bridge.d.ts +3 -2
- package/dist/child/outbound-mail-bridge.js +20 -32
- package/dist/child/pending-request.d.ts +89 -0
- package/dist/child/pending-request.js +80 -0
- package/dist/child/run-child.d.ts +69 -7
- package/dist/child/run-child.js +307 -75
- package/dist/child/substrate-write-bridge.d.ts +3 -2
- package/dist/child/substrate-write-bridge.js +21 -38
- package/dist/child/supervisor-backed-transport.d.ts +52 -6
- package/dist/child/supervisor-backed-transport.js +205 -62
- package/dist/child/warm-agent-cache.d.ts +44 -4
- package/dist/child/warm-agent-cache.js +41 -10
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/ipc/control-channel.d.ts +93 -2
- package/dist/ipc/control-channel.js +147 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/run-body-then-cleanup.d.ts +17 -0
- package/dist/run-body-then-cleanup.js +38 -0
- package/dist/seams/scheduler.d.ts +12 -0
- package/dist/seams/scheduler.js +13 -4
- package/dist/supervisor/cancel-signing.js +3 -7
- package/dist/supervisor/credentials.d.ts +17 -5
- package/dist/supervisor/recycle.d.ts +5 -1
- package/dist/supervisor/run-event-compaction.d.ts +2 -2
- package/dist/supervisor/run-event-compaction.js +11 -16
- package/dist/supervisor/run-event-recovery.d.ts +34 -0
- package/dist/supervisor/run-event-recovery.js +45 -0
- package/dist/supervisor/supervisor.d.ts +27 -4
- package/dist/supervisor/supervisor.js +644 -58
- package/dist/supervisor/terminal-commit.js +3 -7
- package/dist/supervisor/types.d.ts +30 -0
- package/dist/testing/change-notifier.d.ts +12 -0
- package/dist/testing/change-notifier.js +63 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/log-capture.d.ts +52 -0
- package/dist/testing/log-capture.js +124 -0
- package/dist/testing/mail-bus.d.ts +22 -0
- package/dist/testing/mail-bus.js +78 -0
- package/dist/testing/memory-streams.d.ts +43 -0
- package/dist/testing/memory-streams.js +211 -0
- package/dist/testing/spawn-observer.d.ts +12 -0
- package/dist/testing/spawn-observer.js +36 -0
- package/dist/testing/stub-repo-store.d.ts +10 -0
- package/dist/testing/stub-repo-store.js +39 -0
- package/dist/testing/supervisor-reaper.d.ts +24 -0
- package/dist/testing/supervisor-reaper.js +49 -0
- package/dist/testing/upstream-frames.d.ts +47 -0
- package/dist/testing/upstream-frames.js +94 -0
- package/dist/workflow-definition-loader.d.ts +56 -0
- package/dist/workflow-definition-loader.js +106 -0
- package/package.json +17 -11
- package/dist/conversation-text.d.ts +0 -23
- package/dist/conversation-text.js +0 -56
|
@@ -112,6 +112,112 @@ export async function loadWorkflowDirectorRegistryFromClosure(args) {
|
|
|
112
112
|
logger.debug `loaded ${String(loaded.length)} custom director(s) from ${args.packageDir}`;
|
|
113
113
|
return createWorkflowDirectorRegistry(loaded);
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Compose the `LoopFnRegistry` for a workflow closure from the closure
|
|
117
|
+
* package's OWN `interchange.loops` module. A `loop` primitive's `while` and
|
|
118
|
+
* `carry` refs resolve by EXPORT NAME against that module's exports.
|
|
119
|
+
*
|
|
120
|
+
* Unlike directors there is NO built-in default: a package with no
|
|
121
|
+
* `interchange.loops` field composes to an EMPTY registry that throws on any
|
|
122
|
+
* ref lookup. A workflow that declares a `loop` but ships no loops module thus
|
|
123
|
+
* fails closed when its refs are resolved (eagerly, at establish); a workflow
|
|
124
|
+
* with no `loop` primitive never resolves a ref, so an absent field is valid
|
|
125
|
+
* there. Loading OUTSIDE the definition-hash re-verify is safe: the approved
|
|
126
|
+
* hash pins each ref string, and the closure's SRI pins the module bytes.
|
|
127
|
+
*
|
|
128
|
+
* @throws (from the returned registry) if a requested ref names no export, or
|
|
129
|
+
* names an export that is not a function.
|
|
130
|
+
* @throws if the loops entry path escapes the package or cannot be imported.
|
|
131
|
+
*/
|
|
132
|
+
export async function loadWorkflowLoopFnsFromClosure(args) {
|
|
133
|
+
const importModule = args.importModule ?? ((url) => import(url));
|
|
134
|
+
const pkgJson = await readPackageJSON(args.packageDir);
|
|
135
|
+
const entryRel = pkgJson.interchange?.loops;
|
|
136
|
+
if (entryRel === undefined) {
|
|
137
|
+
// No loops module. A workflow with no loop primitive never calls this; one
|
|
138
|
+
// that declares a loop fails closed here when its ref is resolved.
|
|
139
|
+
return (ref) => {
|
|
140
|
+
throw new Error(`loop fn ${JSON.stringify(ref)} was requested, but the workflow package at ${args.packageDir} declares no interchange.loops module`);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const entryAbs = await resolveContainedEntry(args.packageDir, entryRel, "interchange.loops");
|
|
144
|
+
const importUrl = args.importCacheKey === undefined
|
|
145
|
+
? pathToFileURL(entryAbs).href
|
|
146
|
+
: `${pathToFileURL(entryAbs).href}?importCacheKey=${encodeURIComponent(args.importCacheKey)}`;
|
|
147
|
+
let mod;
|
|
148
|
+
try {
|
|
149
|
+
mod = await importModule(importUrl);
|
|
150
|
+
}
|
|
151
|
+
catch (cause) {
|
|
152
|
+
throw new Error(`failed to import interchange.loops entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir}`, { cause });
|
|
153
|
+
}
|
|
154
|
+
if (mod === null || typeof mod !== "object") {
|
|
155
|
+
throw new Error(`interchange.loops entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir} did not evaluate to a module object`);
|
|
156
|
+
}
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- module namespace object: loop fns resolve by export name
|
|
158
|
+
const loopModule = mod;
|
|
159
|
+
logger.debug `loaded interchange.loops module from ${args.packageDir}`;
|
|
160
|
+
return (ref) => {
|
|
161
|
+
const fn = loopModule[ref];
|
|
162
|
+
if (typeof fn !== "function") {
|
|
163
|
+
throw new Error(`interchange.loops entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir} exports no loop fn named ${JSON.stringify(ref)}`);
|
|
164
|
+
}
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- resolved by export name; the loop runtime applies it as a pure (childOutput, carryState) fn
|
|
166
|
+
return fn;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Compose the action-handler resolver for a workflow closure from the closure
|
|
171
|
+
* package's OWN `interchange.actions` module. An `action` primitive's `handler`
|
|
172
|
+
* ref resolves by EXPORT NAME against that module's exports.
|
|
173
|
+
*
|
|
174
|
+
* Mirrors {@link loadWorkflowLoopFnsFromClosure}: there is NO built-in default,
|
|
175
|
+
* so a package with no `interchange.actions` field composes to a resolver that
|
|
176
|
+
* throws on any lookup. A workflow that declares an `action` but ships no
|
|
177
|
+
* actions module fails closed when its handler is resolved (eagerly, at
|
|
178
|
+
* establish); a workflow with no `action` primitive never resolves a handler.
|
|
179
|
+
* Loading OUTSIDE the definition-hash re-verify is safe: the approved hash pins
|
|
180
|
+
* each handler ref string, and the closure's SRI pins the module bytes.
|
|
181
|
+
*
|
|
182
|
+
* @throws (from the returned resolver) if a requested ref names no export, or an
|
|
183
|
+
* export that is not a function.
|
|
184
|
+
* @throws if the actions entry path escapes the package or cannot be imported.
|
|
185
|
+
*/
|
|
186
|
+
export async function loadWorkflowActionHandlersFromClosure(args) {
|
|
187
|
+
const importModule = args.importModule ?? ((url) => import(url));
|
|
188
|
+
const pkgJson = await readPackageJSON(args.packageDir);
|
|
189
|
+
const entryRel = pkgJson.interchange?.actions;
|
|
190
|
+
if (entryRel === undefined) {
|
|
191
|
+
return (ref) => {
|
|
192
|
+
throw new Error(`action handler ${JSON.stringify(ref)} was requested, but the workflow package at ${args.packageDir} declares no interchange.actions module`);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const entryAbs = await resolveContainedEntry(args.packageDir, entryRel, "interchange.actions");
|
|
196
|
+
const importUrl = args.importCacheKey === undefined
|
|
197
|
+
? pathToFileURL(entryAbs).href
|
|
198
|
+
: `${pathToFileURL(entryAbs).href}?importCacheKey=${encodeURIComponent(args.importCacheKey)}`;
|
|
199
|
+
let mod;
|
|
200
|
+
try {
|
|
201
|
+
mod = await importModule(importUrl);
|
|
202
|
+
}
|
|
203
|
+
catch (cause) {
|
|
204
|
+
throw new Error(`failed to import interchange.actions entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir}`, { cause });
|
|
205
|
+
}
|
|
206
|
+
if (mod === null || typeof mod !== "object") {
|
|
207
|
+
throw new Error(`interchange.actions entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir} did not evaluate to a module object`);
|
|
208
|
+
}
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- module namespace object: action handlers resolve by export name
|
|
210
|
+
const actionModule = mod;
|
|
211
|
+
logger.debug `loaded interchange.actions module from ${args.packageDir}`;
|
|
212
|
+
return (ref) => {
|
|
213
|
+
const fn = actionModule[ref];
|
|
214
|
+
if (typeof fn !== "function") {
|
|
215
|
+
throw new Error(`interchange.actions entry ${JSON.stringify(entryRel)} for workflow package at ${args.packageDir} exports no action handler named ${JSON.stringify(ref)}`);
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- resolved by export name; invoked as an ActionHandler (input, ctx, signal) by createDefaultActionInvoker
|
|
218
|
+
return fn;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
115
221
|
/**
|
|
116
222
|
* Import each declared plugin package's `interchange.tools` module from the
|
|
117
223
|
* materialized workflow closure and collect the `AnnotatedPluginFactory`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intx/workflow-host",
|
|
3
3
|
"description": "Production-host implementations of the abstract WorkflowRuntimeEnv from @intx/workflow",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"license": "LGPL-2.1-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -9,19 +9,25 @@
|
|
|
9
9
|
"intx-src": "./src/index.ts",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"intx-src": "./src/testing/index.ts",
|
|
15
|
+
"types": "./dist/testing/index.d.ts",
|
|
16
|
+
"default": "./dist/testing/index.js"
|
|
12
17
|
}
|
|
13
18
|
},
|
|
14
19
|
"dependencies": {
|
|
15
|
-
"@intx/agent": "0.
|
|
16
|
-
"@intx/crypto": "0.
|
|
17
|
-
"@intx/hub-sessions": "0.
|
|
18
|
-
"@intx/inference": "0.
|
|
19
|
-
"@intx/log": "0.
|
|
20
|
-
"@intx/mail-memory": "0.
|
|
21
|
-
"@intx/
|
|
22
|
-
"@intx/
|
|
23
|
-
"@intx/
|
|
24
|
-
"@intx/
|
|
20
|
+
"@intx/agent": "0.4.0",
|
|
21
|
+
"@intx/crypto": "0.4.0",
|
|
22
|
+
"@intx/hub-sessions": "0.4.0",
|
|
23
|
+
"@intx/inference": "0.4.0",
|
|
24
|
+
"@intx/log": "0.4.0",
|
|
25
|
+
"@intx/mail-memory": "0.4.0",
|
|
26
|
+
"@intx/mailbox": "0.4.0",
|
|
27
|
+
"@intx/mime": "0.4.0",
|
|
28
|
+
"@intx/storage-isogit": "0.4.0",
|
|
29
|
+
"@intx/types": "0.4.0",
|
|
30
|
+
"@intx/workflow": "0.4.0",
|
|
25
31
|
"arktype": "^2.1.29"
|
|
26
32
|
},
|
|
27
33
|
"files": [
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extract the conversation body text from a raw inbound MIME message.
|
|
3
|
-
*
|
|
4
|
-
* Three on-wire shapes are handled, matching every producer the mail
|
|
5
|
-
* bus accepts:
|
|
6
|
-
* 1. The Interchange assembler's `multipart/signed` envelope whose
|
|
7
|
-
* first part is a `multipart/mixed` body carrying the text at part
|
|
8
|
-
* path `1.1`.
|
|
9
|
-
* 2. A `multipart/signed` envelope wrapping a bare `text/plain` part
|
|
10
|
-
* (a sender that signs without the `multipart/mixed` wrapper); the
|
|
11
|
-
* text is at part path `1`.
|
|
12
|
-
* 3. A flat top-level `text/plain` message (no multipart structure at
|
|
13
|
-
* all); the body is the bytes after the header section.
|
|
14
|
-
*
|
|
15
|
-
* The top-level `Content-Type` selects the shape: only a `multipart/*`
|
|
16
|
-
* root walks into parts; anything else reads the single body directly.
|
|
17
|
-
* This mirrors the conversation branch of mail-memory's `fetchFull`
|
|
18
|
-
* while also tolerating the flat single-part case the in-process agent
|
|
19
|
-
* accepts, so a non-standard inbound mail still delivers its text to
|
|
20
|
-
* the agent rather than crashing the run. `messageId` is used only for
|
|
21
|
-
* error attribution.
|
|
22
|
-
*/
|
|
23
|
-
export declare function extractConversationText(raw: Uint8Array, messageId: string): string;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// Shared inbound-mail conversation-text extraction. Two call sites resolve a
|
|
2
|
-
// run's mail input to the text the agent actually sees, each at the layer that
|
|
3
|
-
// knows its bytes are inbound mail: the child's `resolveTriggerPayload` (turn
|
|
4
|
-
// 1, the trigger) reads the mail from the substrate, and the supervisor's
|
|
5
|
-
// dispatch loop (turn 2+, a mail delivered to a parked run as a signal) inlines
|
|
6
|
-
// it. Both must produce the SAME shape -- extracted body text, not the raw MIME
|
|
7
|
-
// envelope -- so the extractor lives here rather than in either caller.
|
|
8
|
-
import { extractPartByPath, parseHeaderSection, parseMimePart, } from "@intx/mime";
|
|
9
|
-
/**
|
|
10
|
-
* Extract the conversation body text from a raw inbound MIME message.
|
|
11
|
-
*
|
|
12
|
-
* Three on-wire shapes are handled, matching every producer the mail
|
|
13
|
-
* bus accepts:
|
|
14
|
-
* 1. The Interchange assembler's `multipart/signed` envelope whose
|
|
15
|
-
* first part is a `multipart/mixed` body carrying the text at part
|
|
16
|
-
* path `1.1`.
|
|
17
|
-
* 2. A `multipart/signed` envelope wrapping a bare `text/plain` part
|
|
18
|
-
* (a sender that signs without the `multipart/mixed` wrapper); the
|
|
19
|
-
* text is at part path `1`.
|
|
20
|
-
* 3. A flat top-level `text/plain` message (no multipart structure at
|
|
21
|
-
* all); the body is the bytes after the header section.
|
|
22
|
-
*
|
|
23
|
-
* The top-level `Content-Type` selects the shape: only a `multipart/*`
|
|
24
|
-
* root walks into parts; anything else reads the single body directly.
|
|
25
|
-
* This mirrors the conversation branch of mail-memory's `fetchFull`
|
|
26
|
-
* while also tolerating the flat single-part case the in-process agent
|
|
27
|
-
* accepts, so a non-standard inbound mail still delivers its text to
|
|
28
|
-
* the agent rather than crashing the run. `messageId` is used only for
|
|
29
|
-
* error attribution.
|
|
30
|
-
*/
|
|
31
|
-
export function extractConversationText(raw, messageId) {
|
|
32
|
-
const { headers, bodyOffset } = parseHeaderSection(raw);
|
|
33
|
-
const rootMime = (headers.get("content-type") ?? "")
|
|
34
|
-
.split(";")[0]
|
|
35
|
-
?.trim()
|
|
36
|
-
.toLowerCase();
|
|
37
|
-
if (rootMime === undefined || !rootMime.startsWith("multipart/")) {
|
|
38
|
-
// Flat single-part message: the body is everything after the
|
|
39
|
-
// header section.
|
|
40
|
-
return new TextDecoder("utf-8", { fatal: false }).decode(raw.subarray(bodyOffset));
|
|
41
|
-
}
|
|
42
|
-
let part1;
|
|
43
|
-
try {
|
|
44
|
-
part1 = parseMimePart(extractPartByPath(raw, "1"));
|
|
45
|
-
}
|
|
46
|
-
catch (cause) {
|
|
47
|
-
throw new Error(`conversation-text: cannot parse inbound mail part 1 for messageId ${messageId}`, { cause });
|
|
48
|
-
}
|
|
49
|
-
const part1Mime = (part1.contentType.split(";")[0] ?? "")
|
|
50
|
-
.trim()
|
|
51
|
-
.toLowerCase();
|
|
52
|
-
const bodyBytes = part1Mime.startsWith("multipart/")
|
|
53
|
-
? parseMimePart(extractPartByPath(raw, "1.1")).body
|
|
54
|
-
: part1.body;
|
|
55
|
-
return new TextDecoder("utf-8", { fatal: false }).decode(bodyBytes);
|
|
56
|
-
}
|