@a5c-ai/babysitter-sdk 0.0.31 → 0.0.33

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.
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AA4sCA,wBAAgB,mBAAmB;eAEf,MAAM,EAAE,GAA2B,OAAO,CAAC,MAAM,CAAC;kBAyCpD,MAAM;EAIvB"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AA20CA,wBAAgB,mBAAmB;eAEf,MAAM,EAAE,GAA2B,OAAO,CAAC,MAAM,CAAC;kBAyCpD,MAAM;EAIvB"}
package/dist/cli/main.js CHANGED
@@ -37,7 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.createBabysitterCli = createBabysitterCli;
38
38
  const node_fs_1 = require("node:fs");
39
39
  const path = __importStar(require("node:path"));
40
- const nodeTaskRunner_1 = require("./nodeTaskRunner");
40
+ const commitEffectResult_1 = require("../runtime/commitEffectResult");
41
41
  const orchestrateIteration_1 = require("../runtime/orchestrateIteration");
42
42
  const createRun_1 = require("../runtime/createRun");
43
43
  const effectIndex_1 = require("../runtime/replay/effectIndex");
@@ -52,7 +52,7 @@ const USAGE = `Usage:
52
52
  babysitter run:events <runDir> [--runs-dir <dir>] [--json] [--limit <n>] [--reverse] [--filter-type <type>]
53
53
  babysitter run:rebuild-state <runDir> [--runs-dir <dir>] [--json] [--dry-run]
54
54
  babysitter run:iterate <runDir> [--runs-dir <dir>] [--json] [--verbose] [--iteration <n>]
55
- babysitter task:run <runDir> <effectId> [--runs-dir <dir>] [--json] [--dry-run]
55
+ babysitter task:post <runDir> <effectId> --status <ok|error> [--runs-dir <dir>] [--json] [--dry-run] [--value <file>] [--error <file>] [--stdout-ref <ref>] [--stderr-ref <ref>] [--stdout-file <file>] [--stderr-file <file>] [--started-at <iso8601>] [--finished-at <iso8601>] [--metadata <file>] [--invocation-key <key>]
56
56
  babysitter run:step <runDir> [--runs-dir <dir>] [--json] [--now <iso8601>]
57
57
  babysitter task:list <runDir> [--runs-dir <dir>] [--pending] [--kind <kind>] [--json]
58
58
  babysitter task:show <runDir> <effectId> [--runs-dir <dir>] [--json]
@@ -129,6 +129,55 @@ function parseArgs(argv) {
129
129
  parsed.filterType = expectFlagValue(rest, ++i, "--filter-type");
130
130
  continue;
131
131
  }
132
+ if (arg === "--status") {
133
+ const raw = expectFlagValue(rest, ++i, "--status");
134
+ const normalized = raw.toLowerCase();
135
+ if (normalized !== "ok" && normalized !== "error") {
136
+ throw new Error(`--status must be "ok" or "error" (received: ${raw})`);
137
+ }
138
+ parsed.taskStatus = normalized === "ok" ? "ok" : "error";
139
+ continue;
140
+ }
141
+ if (arg === "--value") {
142
+ parsed.valuePath = expectFlagValue(rest, ++i, "--value");
143
+ continue;
144
+ }
145
+ if (arg === "--error") {
146
+ parsed.errorPath = expectFlagValue(rest, ++i, "--error");
147
+ continue;
148
+ }
149
+ if (arg === "--stdout-ref") {
150
+ parsed.stdoutRef = expectFlagValue(rest, ++i, "--stdout-ref");
151
+ continue;
152
+ }
153
+ if (arg === "--stderr-ref") {
154
+ parsed.stderrRef = expectFlagValue(rest, ++i, "--stderr-ref");
155
+ continue;
156
+ }
157
+ if (arg === "--stdout-file") {
158
+ parsed.stdoutFile = expectFlagValue(rest, ++i, "--stdout-file");
159
+ continue;
160
+ }
161
+ if (arg === "--stderr-file") {
162
+ parsed.stderrFile = expectFlagValue(rest, ++i, "--stderr-file");
163
+ continue;
164
+ }
165
+ if (arg === "--started-at") {
166
+ parsed.startedAt = expectFlagValue(rest, ++i, "--started-at");
167
+ continue;
168
+ }
169
+ if (arg === "--finished-at") {
170
+ parsed.finishedAt = expectFlagValue(rest, ++i, "--finished-at");
171
+ continue;
172
+ }
173
+ if (arg === "--metadata") {
174
+ parsed.metadataPath = expectFlagValue(rest, ++i, "--metadata");
175
+ continue;
176
+ }
177
+ if (arg === "--invocation-key") {
178
+ parsed.invocationKey = expectFlagValue(rest, ++i, "--invocation-key");
179
+ continue;
180
+ }
132
181
  if (arg === "--process-id") {
133
182
  parsed.processId = expectFlagValue(rest, ++i, "--process-id");
134
183
  continue;
@@ -159,7 +208,7 @@ function parseArgs(argv) {
159
208
  }
160
209
  positionals.push(arg);
161
210
  }
162
- if (parsed.command === "task:run") {
211
+ if (parsed.command === "task:post") {
163
212
  [parsed.runDirArg, parsed.effectId] = positionals;
164
213
  }
165
214
  else if (parsed.command === "task:list") {
@@ -293,12 +342,8 @@ function logVerbose(command, parsed, details) {
293
342
  .join(" ");
294
343
  console.error(`[${command}] verbose ${formatted}`);
295
344
  }
296
- function determineTaskStatus(result) {
297
- if (result.timedOut)
298
- return "timeout";
299
- if (result.exitCode == null)
300
- return "skipped";
301
- return result.exitCode === 0 ? "ok" : "error";
345
+ function isJsonRecord(value) {
346
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
302
347
  }
303
348
  function toRunRelativePosix(runDir, absolutePath) {
304
349
  if (!absolutePath)
@@ -418,7 +463,7 @@ async function handleRunCreate(parsed) {
418
463
  runId: parsed.runIdOverride,
419
464
  inputsPath: parsed.inputsPath ? path.resolve(parsed.inputsPath) : undefined,
420
465
  });
421
- let inputs;
466
+ let inputs = undefined;
422
467
  if (parsed.inputsPath) {
423
468
  try {
424
469
  inputs = await readInputsFile(parsed.inputsPath);
@@ -653,71 +698,170 @@ async function handleRunRebuildState(parsed) {
653
698
  console.log(`[run:rebuild-state] runDir=${runDir}${suffix}`);
654
699
  return 0;
655
700
  }
656
- async function handleTaskRun(parsed) {
701
+ async function handleTaskPost(parsed) {
657
702
  if (!parsed.runDirArg || !parsed.effectId) {
658
703
  console.error(USAGE);
659
704
  return 1;
660
705
  }
706
+ if (!parsed.taskStatus) {
707
+ console.error(`[task:post] missing required --status <ok|error>`);
708
+ return 1;
709
+ }
710
+ if (parsed.stdoutRef && parsed.stdoutFile) {
711
+ console.error(`[task:post] cannot combine --stdout-ref with --stdout-file`);
712
+ return 1;
713
+ }
714
+ if (parsed.stderrRef && parsed.stderrFile) {
715
+ console.error(`[task:post] cannot combine --stderr-ref with --stderr-file`);
716
+ return 1;
717
+ }
661
718
  const runDir = resolveRunDir(parsed.runsDir, parsed.runDirArg);
662
719
  const secretLogsAllowed = allowSecretLogs(parsed);
663
- const streamers = buildTaskRunStreamers(parsed);
664
- logVerbose("task:run", parsed, {
720
+ logVerbose("task:post", parsed, {
665
721
  runDir,
666
722
  effectId: parsed.effectId,
723
+ status: parsed.taskStatus,
667
724
  dryRun: parsed.dryRun,
668
725
  json: parsed.json,
669
726
  secretLogsAllowed,
670
727
  });
671
- const index = await buildEffectIndexSafe(runDir, "task:run");
728
+ const index = await buildEffectIndexSafe(runDir, "task:post");
672
729
  if (!index)
673
730
  return 1;
674
731
  const record = index.getByEffectId(parsed.effectId);
675
732
  if (!record) {
676
- console.error(`[task:run] effect ${parsed.effectId} not found at ${runDir}`);
733
+ console.error(`[task:post] effect ${parsed.effectId} not found at ${runDir}`);
677
734
  return 1;
678
735
  }
679
- const kind = (record.kind ?? "").toLowerCase();
680
- if (kind !== "node") {
681
- console.error(`[task:run] effect ${parsed.effectId} has kind=${record.kind ?? "unknown"}; task:run only supports kind="node"`);
736
+ if (record.status !== "requested") {
737
+ console.error(`[task:post] effect ${parsed.effectId} is not requested (status=${record.status ?? "unknown"})`);
682
738
  return 1;
683
739
  }
684
- const result = await (0, nodeTaskRunner_1.runNodeTaskFromCli)({
740
+ const nowIso = new Date().toISOString();
741
+ const startedAt = parsed.startedAt ?? nowIso;
742
+ const finishedAt = parsed.finishedAt ?? nowIso;
743
+ const resolveMaybeRunRelative = (candidate) => {
744
+ if (!candidate)
745
+ return undefined;
746
+ if (candidate === "-")
747
+ return candidate;
748
+ if (path.isAbsolute(candidate) || /^[A-Za-z]:[\\/]/.test(candidate)) {
749
+ return candidate;
750
+ }
751
+ return path.join(runDir, candidate);
752
+ };
753
+ const readJsonFile = async (_label, filename) => {
754
+ if (!filename)
755
+ return undefined;
756
+ if (filename === "-") {
757
+ const raw = await readStdinUtf8();
758
+ const trimmed = raw.trim();
759
+ return trimmed.length ? JSON.parse(trimmed) : undefined;
760
+ }
761
+ const absolute = resolveMaybeRunRelative(filename);
762
+ const raw = await node_fs_1.promises.readFile(absolute, "utf8");
763
+ const trimmed = raw.trim();
764
+ return trimmed.length ? JSON.parse(trimmed) : undefined;
765
+ };
766
+ const readTextFile = async (_label, filename) => {
767
+ if (!filename)
768
+ return undefined;
769
+ if (filename === "-") {
770
+ return await readStdinUtf8();
771
+ }
772
+ const absolute = resolveMaybeRunRelative(filename);
773
+ return await node_fs_1.promises.readFile(absolute, "utf8");
774
+ };
775
+ const metadataRaw = await readJsonFile("metadata", parsed.metadataPath);
776
+ const metadata = isJsonRecord(metadataRaw) ? metadataRaw : undefined;
777
+ const stdout = parsed.stdoutFile ? await readTextFile("stdout", parsed.stdoutFile) : undefined;
778
+ const stderr = parsed.stderrFile ? await readTextFile("stderr", parsed.stderrFile) : undefined;
779
+ let value = undefined;
780
+ let errorPayload = undefined;
781
+ if (parsed.taskStatus === "ok") {
782
+ value = await readJsonFile("value", parsed.valuePath);
783
+ }
784
+ else {
785
+ errorPayload =
786
+ (await readJsonFile("error", parsed.errorPath)) ??
787
+ {
788
+ name: "Error",
789
+ message: "Task reported failure",
790
+ };
791
+ }
792
+ const invocationKey = parsed.invocationKey ?? record.invocationKey;
793
+ const plan = {
794
+ runDir: toRunRelativePosix(runDir, runDir) ?? runDir,
795
+ effectId: parsed.effectId,
796
+ status: parsed.taskStatus,
797
+ valueProvided: parsed.valuePath ? true : false,
798
+ errorProvided: parsed.errorPath ? true : false,
799
+ stdoutRef: parsed.stdoutRef ?? null,
800
+ stderrRef: parsed.stderrRef ?? null,
801
+ stdoutFile: parsed.stdoutFile ?? null,
802
+ stderrFile: parsed.stderrFile ?? null,
803
+ };
804
+ if (parsed.dryRun) {
805
+ if (parsed.json) {
806
+ console.log(JSON.stringify({ status: "skipped", dryRun: true, plan }));
807
+ }
808
+ else {
809
+ console.log(`[task:post] status=skipped`);
810
+ console.error(`[task:post] dry-run plan ${JSON.stringify(plan)}`);
811
+ }
812
+ return 0;
813
+ }
814
+ const committed = await (0, commitEffectResult_1.commitEffectResult)({
685
815
  runDir,
686
816
  effectId: parsed.effectId,
687
- invocationKey: record.invocationKey,
688
- dryRun: parsed.dryRun,
689
- onStdoutChunk: streamers.onStdoutChunk,
690
- onStderrChunk: streamers.onStderrChunk,
817
+ invocationKey,
818
+ result: parsed.taskStatus === "ok"
819
+ ? {
820
+ status: "ok",
821
+ value,
822
+ stdout,
823
+ stderr,
824
+ stdoutRef: parsed.stdoutRef,
825
+ stderrRef: parsed.stderrRef,
826
+ startedAt,
827
+ finishedAt,
828
+ metadata,
829
+ }
830
+ : {
831
+ status: "error",
832
+ error: errorPayload,
833
+ stdout,
834
+ stderr,
835
+ stdoutRef: parsed.stdoutRef,
836
+ stderrRef: parsed.stderrRef,
837
+ startedAt,
838
+ finishedAt,
839
+ metadata,
840
+ },
691
841
  });
692
- const status = determineTaskStatus(result);
693
- const stdoutRef = resolveTaskRunArtifactRef(runDir, result.committed?.stdoutRef, result.io.stdoutPath);
694
- const stderrRef = resolveTaskRunArtifactRef(runDir, result.committed?.stderrRef, result.io.stderrPath);
695
- const outputRef = resolveTaskRunArtifactRef(runDir, result.committed?.resultRef, result.io.outputJsonPath);
696
- const planPayload = parsed.dryRun ? buildTaskRunPlanPayload(runDir, result) : null;
697
- if (parsed.dryRun && planPayload) {
698
- logTaskRunPlan(planPayload);
699
- }
842
+ const stdoutRef = normalizeArtifactRef(runDir, committed.stdoutRef) ?? null;
843
+ const stderrRef = normalizeArtifactRef(runDir, committed.stderrRef) ?? null;
844
+ const resultRef = normalizeArtifactRef(runDir, committed.resultRef) ?? null;
700
845
  if (parsed.json) {
701
- const payload = {
702
- status,
703
- committed: result.committed ?? null,
704
- stdoutRef: stdoutRef ?? null,
705
- stderrRef: stderrRef ?? null,
706
- resultRef: outputRef ?? null,
707
- };
708
- console.log(JSON.stringify(payload));
846
+ console.log(JSON.stringify({
847
+ status: parsed.taskStatus,
848
+ committed,
849
+ stdoutRef,
850
+ stderrRef,
851
+ resultRef,
852
+ }));
709
853
  }
710
854
  else {
711
- const parts = [`[task:run] status=${status}`];
855
+ const parts = [`[task:post] status=${parsed.taskStatus}`];
712
856
  if (stdoutRef)
713
857
  parts.push(`stdoutRef=${stdoutRef}`);
714
858
  if (stderrRef)
715
859
  parts.push(`stderrRef=${stderrRef}`);
716
- if (outputRef)
717
- parts.push(`resultRef=${outputRef}`);
860
+ if (resultRef)
861
+ parts.push(`resultRef=${resultRef}`);
718
862
  console.log(parts.join(" "));
719
863
  }
720
- return status === "ok" || status === "skipped" ? 0 : 1;
864
+ return parsed.taskStatus === "ok" ? 0 : 1;
721
865
  }
722
866
  function parseNowOverride(nowOverride) {
723
867
  if (!nowOverride)
@@ -976,42 +1120,13 @@ async function loadTaskResultPreview(runDir, effectId, record) {
976
1120
  const data = await (0, tasks_1.readTaskResult)(runDir, effectId, record.resultRef);
977
1121
  return { result: data ?? undefined, large: false };
978
1122
  }
979
- function buildTaskRunPlanPayload(runDir, result) {
980
- if (!result.command) {
981
- throw new Error("task runner did not supply command metadata for dry-run planning");
982
- }
983
- const normalize = (absolute) => toRunRelativePosix(runDir, absolute) ?? absolute;
984
- return {
985
- command: {
986
- binary: normalize(result.command.binary),
987
- args: result.command.args.map((arg, index) => (index === 0 ? normalize(arg) : arg)),
988
- cwd: normalize(result.command.cwd),
989
- },
990
- io: {
991
- input: normalize(result.io.inputJsonPath),
992
- output: normalize(result.io.outputJsonPath),
993
- stdout: normalize(result.io.stdoutPath),
994
- stderr: normalize(result.io.stderrPath),
995
- },
996
- };
997
- }
998
- function logTaskRunPlan(plan) {
999
- console.error(`[task:run] dry-run plan ${JSON.stringify(plan)}`);
1000
- }
1001
- function resolveTaskRunArtifactRef(runDir, committedRef, fallbackPath) {
1002
- return normalizeArtifactRef(runDir, committedRef) ?? toRunRelativePosix(runDir, fallbackPath);
1003
- }
1004
- function buildTaskRunStreamers(parsed) {
1005
- const stdoutTarget = parsed.json ? process.stderr : process.stdout;
1006
- const stderrTarget = process.stderr;
1007
- return {
1008
- onStdoutChunk: (chunk) => {
1009
- stdoutTarget.write(chunk);
1010
- },
1011
- onStderrChunk: (chunk) => {
1012
- stderrTarget.write(chunk);
1013
- },
1014
- };
1123
+ async function readStdinUtf8() {
1124
+ return await new Promise((resolve, reject) => {
1125
+ const chunks = [];
1126
+ process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
1127
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1128
+ process.stdin.on("error", reject);
1129
+ });
1015
1130
  }
1016
1131
  function deriveRunState(lastLifecycleEventType, pendingTotal) {
1017
1132
  if (lastLifecycleEventType === "RUN_COMPLETED")
@@ -1152,8 +1267,8 @@ function createBabysitterCli() {
1152
1267
  if (parsed.command === "run:events") {
1153
1268
  return await handleRunEvents(parsed);
1154
1269
  }
1155
- if (parsed.command === "task:run") {
1156
- return await handleTaskRun(parsed);
1270
+ if (parsed.command === "task:post") {
1271
+ return await handleTaskPost(parsed);
1157
1272
  }
1158
1273
  if (parsed.command === "run:step") {
1159
1274
  return await handleRunStep(parsed);
package/dist/index.d.ts CHANGED
@@ -3,8 +3,6 @@ export * from "./runtime/types";
3
3
  export * from "./storage";
4
4
  export * from "./storage/types";
5
5
  export * from "./tasks";
6
- export * from "./runner";
7
- export * from "./cli/nodeTaskRunner";
8
6
  export * from "./cli/main";
9
7
  export * from "./testing";
10
8
  export * from "./hooks";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -19,8 +19,6 @@ __exportStar(require("./runtime/types"), exports);
19
19
  __exportStar(require("./storage"), exports);
20
20
  __exportStar(require("./storage/types"), exports);
21
21
  __exportStar(require("./tasks"), exports);
22
- __exportStar(require("./runner"), exports);
23
- __exportStar(require("./cli/nodeTaskRunner"), exports);
24
22
  __exportStar(require("./cli/main"), exports);
25
23
  __exportStar(require("./testing"), exports);
26
24
  __exportStar(require("./hooks"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a5c-ai/babysitter-sdk",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "Storage and run-registry primitives for event-sourced babysitter workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "commonjs",
@@ -1,16 +0,0 @@
1
- import { TaskDef } from "../tasks/types";
2
- import { RunNodeTaskOptions, RunNodeTaskResult } from "../runner/nodeRunner";
3
- import { CommitEffectResultArtifacts, ProcessLogger } from "../runtime/types";
4
- export interface CliRunNodeTaskOptions extends Omit<RunNodeTaskOptions, "task" | "hydration" | "baseEnv"> {
5
- task?: TaskDef;
6
- baseEnv?: NodeJS.ProcessEnv;
7
- invocationKey?: string;
8
- logger?: ProcessLogger;
9
- }
10
- export interface CliRunNodeTaskResult extends RunNodeTaskResult {
11
- hydratedKeys: string[];
12
- missingKeys: string[];
13
- committed?: CommitEffectResultArtifacts;
14
- }
15
- export declare function runNodeTaskFromCli(options: CliRunNodeTaskOptions): Promise<CliRunNodeTaskResult>;
16
- //# sourceMappingURL=nodeTaskRunner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nodeTaskRunner.d.ts","sourceRoot":"","sources":["../../src/cli/nodeTaskRunner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAiC,MAAM,sBAAsB,CAAC;AAC5G,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAG9E,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IACvG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,2BAA2B,CAAC;CACzC;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqEtG"}
@@ -1,85 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.runNodeTaskFromCli = runNodeTaskFromCli;
7
- const path_1 = __importDefault(require("path"));
8
- const tasks_1 = require("../storage/tasks");
9
- const env_1 = require("../runner/env");
10
- const nodeRunner_1 = require("../runner/nodeRunner");
11
- const runtime_1 = require("../runtime/hooks/runtime");
12
- async function runNodeTaskFromCli(options) {
13
- const task = options.task ?? (await loadTaskDefinition(options.runDir, options.effectId));
14
- const hydration = (0, env_1.hydrateCliNodeTaskEnv)(task, {
15
- cleanEnv: options.cleanEnv,
16
- envOverrides: options.envOverrides,
17
- baseEnv: options.baseEnv ?? process.env,
18
- });
19
- const runId = extractRunIdFromPath(options.runDir);
20
- const taskStartTime = Date.now();
21
- // Compute project root for hook calls (parent of .a5c dir where plugins/ is located)
22
- // runDir is like: /path/to/project/.a5c/runs/<runId>
23
- // So we need 3 levels up: runs -> .a5c -> project
24
- const projectRoot = path_1.default.dirname(path_1.default.dirname(path_1.default.dirname(options.runDir)));
25
- // Call on-task-start hook
26
- await (0, runtime_1.callRuntimeHook)("on-task-start", {
27
- runId,
28
- effectId: options.effectId,
29
- taskId: extractInvocationKey(task) || options.effectId,
30
- kind: "node",
31
- }, {
32
- cwd: projectRoot,
33
- logger: options.logger,
34
- });
35
- const result = await (0, nodeRunner_1.runNodeTask)({
36
- ...options,
37
- task,
38
- hydration,
39
- });
40
- let committed;
41
- if (!options.dryRun) {
42
- committed = await (0, nodeRunner_1.commitNodeResult)({
43
- runDir: options.runDir,
44
- effectId: options.effectId,
45
- invocationKey: options.invocationKey ?? extractInvocationKey(task),
46
- logger: options.logger,
47
- result,
48
- });
49
- }
50
- // Call on-task-complete hook
51
- await (0, runtime_1.callRuntimeHook)("on-task-complete", {
52
- runId,
53
- effectId: options.effectId,
54
- taskId: extractInvocationKey(task) || options.effectId,
55
- status: result.exitCode === 0 ? "ok" : "error",
56
- duration: Date.now() - taskStartTime,
57
- }, {
58
- cwd: projectRoot,
59
- logger: options.logger,
60
- });
61
- return {
62
- ...result,
63
- hydratedKeys: hydration.hydratedKeys,
64
- missingKeys: hydration.missingKeys,
65
- committed,
66
- };
67
- }
68
- async function loadTaskDefinition(runDir, effectId) {
69
- const def = await (0, tasks_1.readTaskDefinition)(runDir, effectId);
70
- if (!def) {
71
- throw new Error(`Task definition for effect ${effectId} is missing`);
72
- }
73
- return def;
74
- }
75
- function extractInvocationKey(task) {
76
- const raw = task?.invocationKey;
77
- return typeof raw === "string" && raw.trim().length > 0 ? raw : undefined;
78
- }
79
- function extractRunIdFromPath(runDir) {
80
- // Extract runId from path like .a5c/runs/<runId> or absolute path ending with runId
81
- const normalized = path_1.default.normalize(runDir);
82
- const parts = normalized.split(path_1.default.sep);
83
- // Get the last non-empty part
84
- return parts[parts.length - 1] || "unknown";
85
- }
@@ -1,58 +0,0 @@
1
- import { JsonRecord } from "../storage/types";
2
- import { TaskDef } from "../tasks/types";
3
- export interface NodeEnvHydrationOptions {
4
- /**
5
- * Env map already present on the TaskDef (safe, non-secret values).
6
- */
7
- nodeEnv?: Record<string, string | undefined>;
8
- /**
9
- * Task metadata containing the `redactedEnvKeys` array emitted by the node helper.
10
- */
11
- metadata?: JsonRecord;
12
- /**
13
- * Source environment to inherit values from (defaults to `process.env`).
14
- */
15
- baseEnv?: NodeJS.ProcessEnv;
16
- /**
17
- * Explicit overrides (e.g., CLI --env) that should win over `baseEnv` for hydrated keys.
18
- */
19
- overrides?: Record<string, string | undefined>;
20
- /**
21
- * When false, the returned env starts empty (aside from sanitized + hydrated keys).
22
- * Redacted keys may still be sourced from `baseEnv`/`overrides` even if inheritance is disabled.
23
- */
24
- inheritProcessEnv?: boolean;
25
- }
26
- export interface NodeEnvHydrationResult {
27
- env: Record<string, string>;
28
- hydratedKeys: string[];
29
- missingKeys: string[];
30
- }
31
- export interface ResolveNodeTaskEnvOptions extends Omit<NodeEnvHydrationOptions, "nodeEnv" | "metadata"> {
32
- /**
33
- * Optional metadata override; defaults to task.metadata.
34
- */
35
- metadata?: JsonRecord;
36
- }
37
- export interface CliHydratedEnvOptions {
38
- /**
39
- * When true, start from an empty env (aside from overrides and hydrated keys).
40
- */
41
- cleanEnv?: boolean;
42
- /**
43
- * Additional env pairs provided via CLI flags.
44
- */
45
- envOverrides?: Record<string, string | undefined>;
46
- /**
47
- * Base env for hydration (defaults to process.env).
48
- */
49
- baseEnv?: NodeJS.ProcessEnv;
50
- }
51
- /**
52
- * Merge the sanitized TaskDef env map with any redacted keys pulled from the caller's environment.
53
- */
54
- export declare function hydrateNodeTaskEnv(options?: NodeEnvHydrationOptions): NodeEnvHydrationResult;
55
- export declare function resolveNodeTaskEnv(task: TaskDef, options?: ResolveNodeTaskEnvOptions): NodeEnvHydrationResult;
56
- export declare function hydrateCliNodeTaskEnv(task: TaskDef, options?: CliHydratedEnvOptions): NodeEnvHydrationResult;
57
- export declare function extractRedactedEnvKeys(metadata?: JsonRecord): string[];
58
- //# sourceMappingURL=env.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/runner/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C;;OAEG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC5B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,uBAAuB,EAAE,SAAS,GAAG,UAAU,CAAC;IACtG;;OAEG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD;;QAEI;IACJ,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,uBAA4B,GAAG,sBAAsB,CAuChG;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,yBAA8B,GAAG,sBAAsB,CASjH;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,qBAA0B,GAAG,sBAAsB,CAMhH;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,EAAE,CAqBtE"}
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hydrateNodeTaskEnv = hydrateNodeTaskEnv;
4
- exports.resolveNodeTaskEnv = resolveNodeTaskEnv;
5
- exports.hydrateCliNodeTaskEnv = hydrateCliNodeTaskEnv;
6
- exports.extractRedactedEnvKeys = extractRedactedEnvKeys;
7
- /**
8
- * Merge the sanitized TaskDef env map with any redacted keys pulled from the caller's environment.
9
- */
10
- function hydrateNodeTaskEnv(options = {}) {
11
- const inherit = options.inheritProcessEnv !== false;
12
- const baseEnv = options.baseEnv ?? process.env;
13
- const env = inherit ? cloneProcessEnv(baseEnv) : {};
14
- if (options.nodeEnv) {
15
- for (const [key, value] of Object.entries(options.nodeEnv)) {
16
- if (!key || value === undefined || value === null)
17
- continue;
18
- env[key] = String(value);
19
- }
20
- }
21
- const overrides = normalizeOverrides(options.overrides);
22
- const metadataKeys = extractRedactedEnvKeys(options.metadata);
23
- const hydratedKeys = [];
24
- const missingKeys = [];
25
- for (const key of metadataKeys) {
26
- const overrideValue = overrides[key];
27
- const sourceValue = overrideValue !== undefined ? overrideValue : baseEnv?.[key];
28
- if (sourceValue === undefined) {
29
- delete env[key];
30
- missingKeys.push(key);
31
- continue;
32
- }
33
- env[key] = sourceValue;
34
- hydratedKeys.push(key);
35
- }
36
- for (const [key, value] of Object.entries(overrides)) {
37
- if (!key)
38
- continue;
39
- env[key] = value;
40
- }
41
- return {
42
- env,
43
- hydratedKeys,
44
- missingKeys,
45
- };
46
- }
47
- function resolveNodeTaskEnv(task, options = {}) {
48
- const metadata = options.metadata ?? task.metadata;
49
- return hydrateNodeTaskEnv({
50
- nodeEnv: task.node?.env,
51
- metadata,
52
- baseEnv: options.baseEnv,
53
- overrides: options.overrides,
54
- inheritProcessEnv: options.inheritProcessEnv,
55
- });
56
- }
57
- function hydrateCliNodeTaskEnv(task, options = {}) {
58
- return resolveNodeTaskEnv(task, {
59
- baseEnv: options.baseEnv,
60
- overrides: options.envOverrides,
61
- inheritProcessEnv: options.cleanEnv ? false : true,
62
- });
63
- }
64
- function extractRedactedEnvKeys(metadata) {
65
- if (!metadata) {
66
- return [];
67
- }
68
- const value = metadata["redactedEnvKeys"];
69
- const raw = Array.isArray(value) ? value : [];
70
- const seen = new Set();
71
- const keys = [];
72
- for (const entry of raw) {
73
- if (typeof entry !== "string")
74
- continue;
75
- const key = entry.trim();
76
- if (!key || seen.has(key))
77
- continue;
78
- seen.add(key);
79
- keys.push(key);
80
- }
81
- keys.sort((a, b) => {
82
- if (a < b)
83
- return -1;
84
- if (a > b)
85
- return 1;
86
- return 0;
87
- });
88
- return keys;
89
- }
90
- function cloneProcessEnv(source) {
91
- if (!source) {
92
- return {};
93
- }
94
- const env = {};
95
- for (const [key, value] of Object.entries(source)) {
96
- if (!key || typeof value !== "string")
97
- continue;
98
- env[key] = value;
99
- }
100
- return env;
101
- }
102
- function normalizeOverrides(overrides) {
103
- if (!overrides) {
104
- return {};
105
- }
106
- const result = {};
107
- for (const [key, value] of Object.entries(overrides)) {
108
- if (!key || value === undefined || value === null)
109
- continue;
110
- result[key] = String(value);
111
- }
112
- return result;
113
- }
@@ -1,3 +0,0 @@
1
- export * from "./env";
2
- export * from "./nodeRunner";
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runner/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC"}
@@ -1,18 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./env"), exports);
18
- __exportStar(require("./nodeRunner"), exports);
@@ -1,60 +0,0 @@
1
- import { CommitEffectResultArtifacts, ProcessLogger } from "../runtime/types";
2
- import { TaskDef } from "../tasks/types";
3
- import { NodeEnvHydrationResult } from "./env";
4
- export interface NodeCommandDetails {
5
- binary: string;
6
- args: string[];
7
- cwd: string;
8
- }
9
- export interface RunNodeTaskOptions {
10
- runDir: string;
11
- effectId: string;
12
- task?: TaskDef;
13
- workspaceRoot?: string;
14
- nodeBinaryPath?: string;
15
- envOverrides?: Record<string, string | undefined>;
16
- cleanEnv?: boolean;
17
- inheritProcessEnv?: boolean;
18
- baseEnv?: NodeJS.ProcessEnv;
19
- hydration?: NodeEnvHydrationResult;
20
- timeoutMs?: number;
21
- dryRun?: boolean;
22
- onStdoutChunk?: (chunk: string) => void;
23
- onStderrChunk?: (chunk: string) => void;
24
- }
25
- export interface RunNodeTaskResult {
26
- task: TaskDef;
27
- command: NodeCommandDetails;
28
- stdout: string;
29
- stderr: string;
30
- exitCode: number | null;
31
- signal: NodeJS.Signals | null;
32
- timedOut: boolean;
33
- startedAt: string;
34
- finishedAt: string;
35
- durationMs: number;
36
- timeoutMs: number;
37
- output?: unknown;
38
- io: ResolvedIoPaths;
39
- hydrated: NodeEnvHydrationResult;
40
- }
41
- export declare class NodeTaskRunnerError extends Error {
42
- readonly code: string;
43
- constructor(message: string, code: string);
44
- }
45
- export declare function runNodeTask(options: RunNodeTaskOptions): Promise<RunNodeTaskResult>;
46
- export interface CommitNodeResultOptions {
47
- runDir: string;
48
- effectId: string;
49
- invocationKey?: string;
50
- logger?: ProcessLogger;
51
- result: RunNodeTaskResult;
52
- }
53
- export declare function commitNodeResult(options: CommitNodeResultOptions): Promise<CommitEffectResultArtifacts>;
54
- export interface ResolvedIoPaths {
55
- inputJsonPath: string;
56
- outputJsonPath: string;
57
- stdoutPath: string;
58
- stderrPath: string;
59
- }
60
- //# sourceMappingURL=nodeRunner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nodeRunner.d.ts","sourceRoot":"","sources":["../../src/runner/nodeRunner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAe,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAsB,MAAM,OAAO,CAAC;AAInE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC5B,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,EAAE,eAAe,CAAC;IACpB,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aACC,IAAI,EAAE,MAAM;gBAA7C,OAAO,EAAE,MAAM,EAAkB,IAAI,EAAE,MAAM;CAI1D;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwGzF;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAmC7G;AA8CD,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -1,354 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.NodeTaskRunnerError = void 0;
7
- exports.runNodeTask = runNodeTask;
8
- exports.commitNodeResult = commitNodeResult;
9
- const child_process_1 = require("child_process");
10
- const fs_1 = require("fs");
11
- const path_1 = __importDefault(require("path"));
12
- const tasks_1 = require("../storage/tasks");
13
- const commitEffectResult_1 = require("../runtime/commitEffectResult");
14
- const env_1 = require("./env");
15
- const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
16
- class NodeTaskRunnerError extends Error {
17
- code;
18
- constructor(message, code) {
19
- super(message);
20
- this.code = code;
21
- this.name = "NodeTaskRunnerError";
22
- }
23
- }
24
- exports.NodeTaskRunnerError = NodeTaskRunnerError;
25
- async function runNodeTask(options) {
26
- const task = options.task ?? (await loadNodeTask(options.runDir, options.effectId));
27
- validateNodeTask(task, options.effectId);
28
- const hydration = options.hydration ??
29
- (0, env_1.resolveNodeTaskEnv)(task, {
30
- baseEnv: options.baseEnv ?? process.env,
31
- overrides: options.envOverrides,
32
- inheritProcessEnv: options.cleanEnv ? false : options.inheritProcessEnv,
33
- });
34
- const entryPath = resolveWorkspacePath(options.workspaceRoot ?? options.runDir, task.node?.entry, "node.entry", options.effectId);
35
- const cwdPath = resolveWorkspacePath(options.workspaceRoot ?? options.runDir, task.node?.cwd, "node.cwd", options.effectId);
36
- const args = task.node?.args ? [...task.node.args] : [];
37
- const timeoutMs = typeof options.timeoutMs === "number" ? options.timeoutMs : task.node?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
38
- const commandDetails = {
39
- binary: options.nodeBinaryPath ?? process.execPath,
40
- args: [entryPath, ...args],
41
- cwd: cwdPath ?? options.runDir,
42
- };
43
- const envForProcess = { ...hydration.env };
44
- const ioPaths = applyIoEnv(envForProcess, options.runDir, options.effectId, task.io);
45
- if (options.dryRun) {
46
- const nowIso = new Date().toISOString();
47
- return {
48
- task,
49
- stdout: "",
50
- stderr: "",
51
- exitCode: null,
52
- signal: null,
53
- timedOut: false,
54
- startedAt: nowIso,
55
- finishedAt: nowIso,
56
- durationMs: 0,
57
- timeoutMs,
58
- command: commandDetails,
59
- io: ioPaths,
60
- hydrated: hydration,
61
- };
62
- }
63
- await ensureParentDir(ioPaths.inputJsonPath);
64
- await ensureParentDir(ioPaths.outputJsonPath);
65
- await ensureParentDir(ioPaths.stdoutPath);
66
- await ensureParentDir(ioPaths.stderrPath);
67
- await stageTaskInputs({
68
- task,
69
- runDir: options.runDir,
70
- effectId: options.effectId,
71
- inputJsonPath: ioPaths.inputJsonPath,
72
- });
73
- const startedAtMs = Date.now();
74
- const startedAtIso = new Date(startedAtMs).toISOString();
75
- const runResult = await spawnNodeProcess({
76
- command: commandDetails.binary,
77
- args: commandDetails.args,
78
- cwd: commandDetails.cwd,
79
- env: envForProcess,
80
- timeoutMs,
81
- stdoutPath: ioPaths.stdoutPath,
82
- stderrPath: ioPaths.stderrPath,
83
- onStdoutChunk: options.onStdoutChunk,
84
- onStderrChunk: options.onStderrChunk,
85
- });
86
- const finishedAtMs = Date.now();
87
- const finishedAtIso = new Date(finishedAtMs).toISOString();
88
- let parsedOutput;
89
- parsedOutput = await readOptionalJson(ioPaths.outputJsonPath);
90
- return {
91
- task,
92
- stdout: runResult.stdout,
93
- stderr: runResult.stderr,
94
- exitCode: runResult.exitCode,
95
- signal: runResult.signal,
96
- timedOut: runResult.timedOut,
97
- startedAt: startedAtIso,
98
- finishedAt: finishedAtIso,
99
- durationMs: finishedAtMs - startedAtMs,
100
- timeoutMs,
101
- output: parsedOutput,
102
- command: commandDetails,
103
- io: ioPaths,
104
- hydrated: hydration,
105
- };
106
- }
107
- async function commitNodeResult(options) {
108
- const { runDir, effectId, invocationKey, logger, result } = options;
109
- const stdoutRef = toRunRelativePosix(runDir, result.io.stdoutPath);
110
- const stderrRef = toRunRelativePosix(runDir, result.io.stderrPath);
111
- const outputJsonRef = toRunRelativePosix(runDir, result.io.outputJsonPath);
112
- const status = !result.timedOut && (result.exitCode === null || result.exitCode === 0) ? "ok" : "error";
113
- const metadata = {
114
- durationMs: result.durationMs,
115
- timeoutMs: result.timeoutMs,
116
- exitCode: result.exitCode,
117
- signal: result.signal ?? undefined,
118
- timedOut: result.timedOut,
119
- outputJsonRef,
120
- };
121
- return (0, commitEffectResult_1.commitEffectResult)({
122
- runDir,
123
- effectId,
124
- invocationKey,
125
- logger,
126
- result: {
127
- status,
128
- value: status === "ok" ? result.output : undefined,
129
- error: status === "error" ? buildNodeTaskErrorPayload(result, outputJsonRef) : undefined,
130
- stdout: result.stdout,
131
- stderr: result.stderr,
132
- stdoutRef,
133
- stderrRef,
134
- startedAt: result.startedAt,
135
- finishedAt: result.finishedAt,
136
- metadata,
137
- },
138
- });
139
- }
140
- async function loadNodeTask(runDir, effectId) {
141
- const json = await (0, tasks_1.readTaskDefinition)(runDir, effectId);
142
- if (!json) {
143
- throw new NodeTaskRunnerError(`Task definition for effect ${effectId} is missing`, "missing_task");
144
- }
145
- return json;
146
- }
147
- function validateNodeTask(task, effectId) {
148
- if (task.kind !== "node" || !task.node) {
149
- throw new NodeTaskRunnerError(`Effect ${effectId} is not a node task`, "invalid_task_kind");
150
- }
151
- if (!task.node.entry || typeof task.node.entry !== "string") {
152
- throw new NodeTaskRunnerError(`Effect ${effectId} node.entry is missing`, "missing_entry");
153
- }
154
- }
155
- function resolveWorkspacePath(baseDir, value, field, effectId) {
156
- if (!value) {
157
- return undefined;
158
- }
159
- const normalized = value.replace(/\\/g, "/");
160
- const isAbsolute = path_1.default.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized);
161
- const resolved = isAbsolute ? normalized : path_1.default.join(baseDir, ...normalized.split("/"));
162
- if (!resolved) {
163
- throw new NodeTaskRunnerError(`Effect ${effectId} has an invalid ${field}`, "invalid_path");
164
- }
165
- return path_1.default.normalize(resolved);
166
- }
167
- function resolveRunRelativePath(runDir, relative) {
168
- if (!relative)
169
- return undefined;
170
- if (path_1.default.isAbsolute(relative) || /^[A-Za-z]:[\\/]/.test(relative)) {
171
- return path_1.default.normalize(relative);
172
- }
173
- const normalized = relative.replace(/\\/g, "/");
174
- return path_1.default.join(runDir, ...normalized.split("/"));
175
- }
176
- function applyIoEnv(env, runDir, effectId, io) {
177
- const normalizedIo = withDefaultIoHints(effectId, io);
178
- const inputJsonPath = resolveRunRelativePath(runDir, normalizedIo.inputJsonPath);
179
- const outputJsonPath = resolveRunRelativePath(runDir, normalizedIo.outputJsonPath);
180
- const stdoutPath = resolveRunRelativePath(runDir, normalizedIo.stdoutPath);
181
- const stderrPath = resolveRunRelativePath(runDir, normalizedIo.stderrPath);
182
- env.BABYSITTER_INPUT_JSON = inputJsonPath;
183
- env.BABYSITTER_OUTPUT_JSON = outputJsonPath;
184
- env.BABYSITTER_STDOUT_PATH = stdoutPath;
185
- env.BABYSITTER_STDERR_PATH = stderrPath;
186
- env.BABYSITTER_EFFECT_ID = effectId;
187
- return { inputJsonPath, outputJsonPath, stdoutPath, stderrPath };
188
- }
189
- async function readOptionalJson(filePath) {
190
- try {
191
- const contents = await fs_1.promises.readFile(filePath, "utf8");
192
- if (!contents.trim()) {
193
- return undefined;
194
- }
195
- return JSON.parse(contents);
196
- }
197
- catch (error) {
198
- const err = error;
199
- if (err.code === "ENOENT") {
200
- return undefined;
201
- }
202
- throw error;
203
- }
204
- }
205
- function withDefaultIoHints(effectId, io) {
206
- const defaulted = {
207
- inputJsonPath: `tasks/${effectId}/inputs.json`,
208
- outputJsonPath: `tasks/${effectId}/result.json`,
209
- stdoutPath: `tasks/${effectId}/stdout.log`,
210
- stderrPath: `tasks/${effectId}/stderr.log`,
211
- };
212
- return {
213
- inputJsonPath: typeof io?.inputJsonPath === "string" && io.inputJsonPath.trim().length > 0
214
- ? io.inputJsonPath
215
- : defaulted.inputJsonPath,
216
- outputJsonPath: typeof io?.outputJsonPath === "string" && io.outputJsonPath.trim().length > 0
217
- ? io.outputJsonPath
218
- : defaulted.outputJsonPath,
219
- stdoutPath: typeof io?.stdoutPath === "string" && io.stdoutPath.trim().length > 0 ? io.stdoutPath : defaulted.stdoutPath,
220
- stderrPath: typeof io?.stderrPath === "string" && io.stderrPath.trim().length > 0 ? io.stderrPath : defaulted.stderrPath,
221
- };
222
- }
223
- async function spawnNodeProcess(options) {
224
- return new Promise((resolve, reject) => {
225
- const child = (0, child_process_1.spawn)(options.command, options.args, {
226
- cwd: options.cwd,
227
- env: options.env,
228
- stdio: ["ignore", "pipe", "pipe"],
229
- });
230
- const stdoutStream = (0, fs_1.createWriteStream)(options.stdoutPath, { flags: "w" });
231
- const stderrStream = (0, fs_1.createWriteStream)(options.stderrPath, { flags: "w" });
232
- let streamsClosed = false;
233
- const finishStreams = () => {
234
- if (streamsClosed)
235
- return;
236
- streamsClosed = true;
237
- stdoutStream.end();
238
- stderrStream.end();
239
- };
240
- let settled = false;
241
- const finishWithError = (error) => {
242
- if (settled)
243
- return;
244
- settled = true;
245
- if (timeoutHandle)
246
- clearTimeout(timeoutHandle);
247
- finishStreams();
248
- reject(error);
249
- };
250
- const finishWithSuccess = (payload) => {
251
- if (settled)
252
- return;
253
- settled = true;
254
- if (timeoutHandle)
255
- clearTimeout(timeoutHandle);
256
- finishStreams();
257
- resolve(payload);
258
- };
259
- let stdout = "";
260
- let stderr = "";
261
- let timedOut = false;
262
- child.stdout?.setEncoding("utf8");
263
- child.stdout?.on("data", (chunk) => {
264
- stdout += chunk;
265
- stdoutStream.write(chunk);
266
- options.onStdoutChunk?.(chunk);
267
- });
268
- child.stderr?.setEncoding("utf8");
269
- child.stderr?.on("data", (chunk) => {
270
- stderr += chunk;
271
- stderrStream.write(chunk);
272
- options.onStderrChunk?.(chunk);
273
- });
274
- stdoutStream.on("error", (error) => {
275
- child.kill();
276
- finishWithError(new NodeTaskRunnerError(`Failed to write stdout log: ${error.message}`, "stdout_log_error"));
277
- });
278
- stderrStream.on("error", (error) => {
279
- child.kill();
280
- finishWithError(new NodeTaskRunnerError(`Failed to write stderr log: ${error.message}`, "stderr_log_error"));
281
- });
282
- const timeoutHandle = options.timeoutMs > 0
283
- ? setTimeout(() => {
284
- timedOut = true;
285
- child.kill();
286
- }, options.timeoutMs)
287
- : undefined;
288
- child.on("error", (error) => {
289
- finishWithError(new NodeTaskRunnerError(`Failed to spawn node task: ${error.message}`, "spawn_error"));
290
- });
291
- child.on("close", (code, signal) => {
292
- finishWithSuccess({ stdout, stderr, exitCode: code, signal, timedOut });
293
- });
294
- });
295
- }
296
- async function ensureParentDir(filePath) {
297
- await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
298
- }
299
- async function stageTaskInputs(options) {
300
- const inlineInputs = options.task.inputs;
301
- const hasInlineInputs = inlineInputs !== undefined;
302
- const maybeInputsRef = options.task.inputsRef;
303
- const inputsRef = typeof maybeInputsRef === "string" ? maybeInputsRef : undefined;
304
- if (!hasInlineInputs && !inputsRef) {
305
- return;
306
- }
307
- const payload = hasInlineInputs && inlineInputs !== undefined
308
- ? inlineInputs
309
- : await readInputsRefValue(options.runDir, inputsRef, options.effectId);
310
- await fs_1.promises.writeFile(options.inputJsonPath, JSON.stringify(payload, null, 2) + "\n", "utf8");
311
- }
312
- async function readInputsRefValue(runDir, ref, effectId) {
313
- const resolved = resolveRunRelativePath(runDir, ref);
314
- if (!resolved) {
315
- throw new NodeTaskRunnerError(`Effect ${effectId} has an invalid inputsRef path`, "invalid_inputs_ref");
316
- }
317
- try {
318
- const contents = await fs_1.promises.readFile(resolved, "utf8");
319
- if (!contents.trim()) {
320
- throw new NodeTaskRunnerError(`Effect ${effectId} inputsRef file is empty`, "empty_inputs_ref");
321
- }
322
- return JSON.parse(contents);
323
- }
324
- catch (error) {
325
- const err = error;
326
- if (err.code === "ENOENT") {
327
- throw new NodeTaskRunnerError(`Effect ${effectId} inputsRef file is missing`, "missing_inputs_ref");
328
- }
329
- throw error;
330
- }
331
- }
332
- function buildNodeTaskErrorPayload(result, outputJsonRef) {
333
- const base = result.exitCode === null
334
- ? "Node task exited without a code"
335
- : `Node task exited with code ${result.exitCode}${result.signal ? ` (signal ${result.signal})` : ""}`;
336
- const message = result.timedOut ? `Node task timed out after ${result.timeoutMs}ms` : base;
337
- return {
338
- message,
339
- exitCode: result.exitCode,
340
- signal: result.signal ?? undefined,
341
- timedOut: result.timedOut,
342
- stdout: result.stdout,
343
- stderr: result.stderr,
344
- outputJsonRef,
345
- };
346
- }
347
- function toRunRelativePosix(runDir, absolutePath) {
348
- if (!absolutePath)
349
- return undefined;
350
- const relative = path_1.default.relative(runDir, absolutePath);
351
- if (!relative || relative.startsWith(".."))
352
- return undefined;
353
- return relative.replace(/\\/g, "/");
354
- }