@atom-workflow-agent/atom-executor 0.1.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/LICENSE +201 -0
- package/README.md +31 -0
- package/README.zh-CN.md +31 -0
- package/dist/attempt/atom-attempt-runner.d.ts +30 -0
- package/dist/attempt/atom-attempt-runner.d.ts.map +1 -0
- package/dist/attempt/atom-attempt-runner.js +11 -0
- package/dist/attempt/atom-attempt-runner.js.map +1 -0
- package/dist/attempt/atom-worker-protocol.d.ts +92 -0
- package/dist/attempt/atom-worker-protocol.d.ts.map +1 -0
- package/dist/attempt/atom-worker-protocol.js +2 -0
- package/dist/attempt/atom-worker-protocol.js.map +1 -0
- package/dist/attempt/process-atom-attempt-runner.d.ts +16 -0
- package/dist/attempt/process-atom-attempt-runner.d.ts.map +1 -0
- package/dist/attempt/process-atom-attempt-runner.js +244 -0
- package/dist/attempt/process-atom-attempt-runner.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/lifecycle-driver.d.ts +68 -0
- package/dist/lifecycle/lifecycle-driver.d.ts.map +1 -0
- package/dist/lifecycle/lifecycle-driver.js +141 -0
- package/dist/lifecycle/lifecycle-driver.js.map +1 -0
- package/dist/resources/attempt-resource-manager.d.ts +12 -0
- package/dist/resources/attempt-resource-manager.d.ts.map +1 -0
- package/dist/resources/attempt-resource-manager.js +33 -0
- package/dist/resources/attempt-resource-manager.js.map +1 -0
- package/dist/timeout/timeout.d.ts +2 -0
- package/dist/timeout/timeout.d.ts.map +1 -0
- package/dist/timeout/timeout.js +39 -0
- package/dist/timeout/timeout.js.map +1 -0
- package/dist/worker/atom-worker-entry.mjs +200 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/timeout/timeout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAA8C,EAC9C,SAAwB,EACxB,YAA0B;IAE1B,IAAI,YAAY,EAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;QACnC,MAAM,MAAM,YAAY,iBAAiB;YACvC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,IAAI,iBAAiB,CAAC,WAAW,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAChI,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,GAAS,EAAE;QAC/B,MAAM,MAAM,GAAG,YAAY,EAAE,MAAM,CAAC;QACpC,UAAU,CAAC,KAAK,CAAC,MAAM,YAAY,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,WAAW,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7L,CAAC,CAAC;IACF,YAAY,EAAE,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,SAAS,KAAK,IAAI;QAC9B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC,SAAS,EAAE,mBAAmB,SAAS,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAE5H,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YAC/C,MAAM,eAAe,GAAG,GAAS,EAAE;gBACjC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;gBACxC,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAAC,CAAC;YACvG,CAAC,CAAC;YACF,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7E,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,eAAe,EAAE,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;YACxB,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;YAC5B,OAAO;SACR,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,YAAY,EAAE,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { isAtomTransition, createAtomLifecycleHelpers } from "@atom-workflow-agent/atom-sdk";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
const pending = new Map();
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
let started = false;
|
|
7
|
+
|
|
8
|
+
process.on("message", (message) => {
|
|
9
|
+
if (!message || typeof message !== "object") return;
|
|
10
|
+
if (message.type === "abort") {
|
|
11
|
+
controller.abort(new Error(message.reason || "Atom Attempt aborted"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (message.type === "rpc.result") {
|
|
15
|
+
const request = pending.get(message.requestId);
|
|
16
|
+
if (!request) return;
|
|
17
|
+
pending.delete(message.requestId);
|
|
18
|
+
if (message.ok) request.resolve(message.value);
|
|
19
|
+
else request.reject(workerError(message.error));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (message.type !== "run" || started) return;
|
|
23
|
+
started = true;
|
|
24
|
+
void execute(message.request);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function execute(request) {
|
|
28
|
+
const attemptId = request.context.runtime.attemptId;
|
|
29
|
+
emit(request, "process.started", { pid: process.pid });
|
|
30
|
+
const attemptResources = resources("attempt");
|
|
31
|
+
const instanceResources = resources("instance");
|
|
32
|
+
const logger = createLogger(request);
|
|
33
|
+
let definition;
|
|
34
|
+
let failure;
|
|
35
|
+
try {
|
|
36
|
+
definition = await loadDefinition(request);
|
|
37
|
+
emit(request, "atom.loaded", { sourceId: request.source.sourceId, atom: `${request.atom.id}@${request.atom.version}` });
|
|
38
|
+
if (definition.initialize) {
|
|
39
|
+
emit(request, "initialize.started");
|
|
40
|
+
await definition.initialize({ cwd: request.context.cwd, logger, resources: instanceResources, metadata: request.context.metadata });
|
|
41
|
+
emit(request, "initialize.completed");
|
|
42
|
+
}
|
|
43
|
+
const context = {
|
|
44
|
+
runtime: request.context.runtime,
|
|
45
|
+
cwd: request.context.cwd,
|
|
46
|
+
signal: controller.signal,
|
|
47
|
+
logger,
|
|
48
|
+
metadata: request.context.metadata,
|
|
49
|
+
...(request.context.decision ? { decision: request.context.decision } : {}),
|
|
50
|
+
resources: attemptResources,
|
|
51
|
+
artifacts: {
|
|
52
|
+
put: input => rpc({ method: "artifacts.put", input }),
|
|
53
|
+
get: id => rpc({ method: "artifacts.get", id }),
|
|
54
|
+
},
|
|
55
|
+
...createAtomLifecycleHelpers(),
|
|
56
|
+
};
|
|
57
|
+
const hook = request.phase;
|
|
58
|
+
emit(request, `${hook}.started`);
|
|
59
|
+
if (hook === "resume" && !definition.resume) throw coded("ATOM_RESUME_UNAVAILABLE", `Atom does not define resume(): ${request.atom.id}@${request.atom.version}`);
|
|
60
|
+
const raw = hook === "execute"
|
|
61
|
+
? await definition.execute(request.input, context)
|
|
62
|
+
: await definition.resume(request.input, request.resumeInput, context);
|
|
63
|
+
emit(request, `${hook}.completed`);
|
|
64
|
+
const invocation = isAtomTransition(raw)
|
|
65
|
+
? { kind: "transition", result: raw.result }
|
|
66
|
+
: { kind: "output", output: raw };
|
|
67
|
+
await attemptResources.disposeAll(isAtomTransition(raw) ? "interrupted" : "completed");
|
|
68
|
+
if (definition.dispose) {
|
|
69
|
+
emit(request, "dispose.started");
|
|
70
|
+
await definition.dispose({ cwd: request.context.cwd, logger, resources: instanceResources, metadata: request.context.metadata });
|
|
71
|
+
emit(request, "dispose.completed");
|
|
72
|
+
}
|
|
73
|
+
await instanceResources.disposeAll("shutdown");
|
|
74
|
+
emit(request, "process.completed");
|
|
75
|
+
await finish({ type: "completed", invocation });
|
|
76
|
+
return;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
failure = error;
|
|
79
|
+
}
|
|
80
|
+
try { await attemptResources.disposeAll(controller.signal.aborted ? "cancelled" : "failed"); } catch (error) { failure = new AggregateError([failure, error], "Atom Attempt and cleanup failed"); }
|
|
81
|
+
try {
|
|
82
|
+
if (definition?.dispose) {
|
|
83
|
+
emit(request, "dispose.started");
|
|
84
|
+
await definition.dispose({ cwd: request.context.cwd, logger, resources: instanceResources, metadata: request.context.metadata });
|
|
85
|
+
emit(request, "dispose.completed");
|
|
86
|
+
}
|
|
87
|
+
await instanceResources.disposeAll("shutdown");
|
|
88
|
+
} catch (error) { failure = new AggregateError([failure, error], "Atom Attempt and dispose failed"); }
|
|
89
|
+
await finish({ type: "failed", error: serializeError(failure) });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function loadDefinition(request) {
|
|
93
|
+
if (request.protocolVersion !== 1) throw coded("ATOM_WORKER_PROTOCOL_UNSUPPORTED", `Unsupported protocol version: ${request.protocolVersion}`);
|
|
94
|
+
const module = await import(request.source.moduleUrl);
|
|
95
|
+
const atomPackage = module.atomPackage ?? module.default;
|
|
96
|
+
if (!atomPackage || !Array.isArray(atomPackage.atoms)) throw coded("ATOM_PACKAGE_EXPORT_INVALID", `Atom module did not export atomPackage: ${request.source.sourceId}`);
|
|
97
|
+
const definition = atomPackage.atoms.find((candidate) => candidate?.manifest?.id === request.atom.id && candidate?.manifest?.version === request.atom.version);
|
|
98
|
+
if (!definition) throw coded("ATOM_NOT_FOUND", `Atom module does not export ${request.atom.id}@${request.atom.version}`);
|
|
99
|
+
if (typeof definition.execute !== "function") throw coded("INVALID_ATOM", `Atom execute() is missing: ${request.atom.id}@${request.atom.version}`);
|
|
100
|
+
return definition;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resources(scope) {
|
|
104
|
+
const registered = new Map();
|
|
105
|
+
let disposed = false;
|
|
106
|
+
return {
|
|
107
|
+
scope,
|
|
108
|
+
register(resource) {
|
|
109
|
+
if (disposed) throw new Error("Atom resources are already disposed");
|
|
110
|
+
if (!resource || typeof resource.id !== "string" || typeof resource.dispose !== "function") throw new TypeError("Atom resource is invalid");
|
|
111
|
+
if (registered.has(resource.id)) throw new Error(`Duplicate Atom resource: ${resource.id}`);
|
|
112
|
+
registered.set(resource.id, resource);
|
|
113
|
+
},
|
|
114
|
+
async disposeAll(reason) {
|
|
115
|
+
if (disposed) return;
|
|
116
|
+
disposed = true;
|
|
117
|
+
const failures = [];
|
|
118
|
+
for (const resource of [...registered.values()].reverse()) {
|
|
119
|
+
try { await resource.dispose(reason); } catch (error) { failures.push(error); }
|
|
120
|
+
}
|
|
121
|
+
registered.clear();
|
|
122
|
+
if (failures.length) throw new AggregateError(failures, "Atom resource cleanup failed");
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createLogger(request) {
|
|
128
|
+
const write = (level, message, fields) => emit(request, "atom.log", { level, message, ...(fields ? { fields } : {}) });
|
|
129
|
+
return {
|
|
130
|
+
debug: (message, fields) => write("debug", message, fields),
|
|
131
|
+
info: (message, fields) => write("info", message, fields),
|
|
132
|
+
warn: (message, fields) => write("warn", message, fields),
|
|
133
|
+
error: (message, fields) => write("error", message, fields),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function rpc(request) {
|
|
138
|
+
if (controller.signal.aborted) return Promise.reject(controller.signal.reason ?? new Error("Atom Attempt aborted"));
|
|
139
|
+
const requestId = randomUUID();
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
pending.set(requestId, { resolve, reject });
|
|
142
|
+
send({ type: "rpc", requestId, request }).catch((error) => {
|
|
143
|
+
pending.delete(requestId);
|
|
144
|
+
reject(error);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function emit(request, type, data) {
|
|
150
|
+
void send({
|
|
151
|
+
type: "event",
|
|
152
|
+
event: {
|
|
153
|
+
schemaVersion: "1.0",
|
|
154
|
+
type,
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
attemptId: request.context.runtime.attemptId,
|
|
157
|
+
phase: request.phase,
|
|
158
|
+
...(data === undefined ? {} : { data }),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function coded(code, message) {
|
|
164
|
+
return Object.assign(new Error(message), { code });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function workerError(error) {
|
|
168
|
+
return Object.assign(new Error(error?.message ?? "Atom worker RPC failed"), {
|
|
169
|
+
name: error?.name ?? "Error",
|
|
170
|
+
code: error?.code ?? "ATOM_WORKER_RPC_FAILED",
|
|
171
|
+
retryable: error?.retryable === true,
|
|
172
|
+
...(error?.details === undefined ? {} : { details: error.details }),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function serializeError(error) {
|
|
177
|
+
const candidate = error instanceof Error ? error : new Error(String(error));
|
|
178
|
+
return {
|
|
179
|
+
name: candidate.name,
|
|
180
|
+
code: typeof candidate.code === "string" ? candidate.code : "ATOM_EXECUTION_FAILED",
|
|
181
|
+
message: candidate.message,
|
|
182
|
+
retryable: candidate.retryable === true,
|
|
183
|
+
...(candidate.details === undefined ? {} : { details: candidate.details }),
|
|
184
|
+
...(candidate.stack ? { stack: candidate.stack } : {}),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function finish(message) {
|
|
189
|
+
try { await send(message); } finally {
|
|
190
|
+
process.disconnect?.();
|
|
191
|
+
setImmediate(() => process.exit(0));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function send(message) {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
if (!process.send) return reject(new Error("Atom worker IPC channel is unavailable"));
|
|
198
|
+
process.send(message, (error) => error ? reject(error) : resolve());
|
|
199
|
+
});
|
|
200
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atom-workflow-agent/atom-executor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Isolated lifecycle execution for one atom attempt.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/joekbits/atom-workflow-agent.git",
|
|
9
|
+
"directory": "packages/atom-executor"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/joekbits/atom-workflow-agent/tree/main/packages/atom-executor#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/joekbits/atom-workflow-agent/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"atom",
|
|
17
|
+
"workflow",
|
|
18
|
+
"executor",
|
|
19
|
+
"worker"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"README.zh-CN.md"
|
|
26
|
+
],
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@atom-workflow-agent/contracts": "0.1.0",
|
|
42
|
+
"@atom-workflow-agent/atom-sdk": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.json && mkdir -p dist/worker && cp src/worker/atom-worker-entry.mjs dist/worker/atom-worker-entry.mjs",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
}
|
|
49
|
+
}
|