@smithers-orchestrator/graph 0.24.2 → 0.25.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/package.json +2 -2
- package/src/dom/extract.js +78 -44
- package/src/extract.js +24 -20
- package/src/index.d.ts +10 -1
- package/src/index.js +1 -0
- package/src/types.ts +2 -0
- package/src/validateForkSources.js +4 -0
- package/src/worktree-path.js +45 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Framework-neutral Smithers workflow graph model and extraction helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"drizzle-orm": "^0.45.2",
|
|
24
24
|
"zod": "^4.3.6",
|
|
25
|
-
"@smithers-orchestrator/errors": "0.
|
|
25
|
+
"@smithers-orchestrator/errors": "0.25.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/bun": "latest",
|
package/src/dom/extract.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
/** @typedef {import("../HostText.ts").HostText} HostText */
|
|
4
4
|
// @smithers-type-exports-end
|
|
5
5
|
|
|
6
|
-
import { resolveStableId } from "
|
|
6
|
+
import { resolveStableId } from "../utils/tree-ids.js";
|
|
7
7
|
import { getTableName } from "drizzle-orm";
|
|
8
|
-
import { DEFAULT_MERGE_QUEUE_CONCURRENCY, WORKTREE_EMPTY_PATH_ERROR, } from "
|
|
8
|
+
import { DEFAULT_MERGE_QUEUE_CONCURRENCY, WORKTREE_EMPTY_PATH_ERROR, } from "../constants.js";
|
|
9
9
|
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
10
|
-
import { resolveWorktreePath } from "
|
|
10
|
+
import { resolveWorktreePath } from "../worktree-path.js";
|
|
11
11
|
|
|
12
12
|
/** @typedef {import("../ExtractOptions.ts").ExtractOptions} ExtractOptions */
|
|
13
13
|
/** @typedef {import("../ExtractResult.ts").ExtractResult} ExtractResult */
|
|
14
14
|
/** @typedef {import("../HostNode.ts").HostNode} HostNode */
|
|
15
|
+
/** @typedef {import("../TaskDescriptor.ts").TaskDescriptor} TaskDescriptor */
|
|
15
16
|
/** @typedef {import("../XmlNode.ts").XmlNode} XmlNode */
|
|
16
17
|
|
|
17
18
|
// TODO(migration): Delegate extractFromHost to
|
|
@@ -48,13 +49,13 @@ const DEFAULT_LOCAL_TASK_HEARTBEAT_TIMEOUT_MS = envHeartbeatTimeoutMs();
|
|
|
48
49
|
const DEFAULT_SANDBOX_TASK_HEARTBEAT_TIMEOUT_MS = envHeartbeatTimeoutMs();
|
|
49
50
|
/**
|
|
50
51
|
* @param {unknown} value
|
|
51
|
-
* @returns {
|
|
52
|
+
* @returns {value is any}
|
|
52
53
|
*/
|
|
53
54
|
function isDrizzleTable(value) {
|
|
54
55
|
if (!value || typeof value !== "object")
|
|
55
56
|
return false;
|
|
56
57
|
try {
|
|
57
|
-
const name = getTableName(value);
|
|
58
|
+
const name = getTableName(/** @type {any} */ (value));
|
|
58
59
|
return typeof name === "string" && name.length > 0;
|
|
59
60
|
}
|
|
60
61
|
catch {
|
|
@@ -63,7 +64,7 @@ function isDrizzleTable(value) {
|
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
66
|
* @param {unknown} value
|
|
66
|
-
* @returns {
|
|
67
|
+
* @returns {value is import("zod").ZodObject<any>}
|
|
67
68
|
*/
|
|
68
69
|
function isZodObject(value) {
|
|
69
70
|
return Boolean(value && typeof value === "object" && "shape" in value);
|
|
@@ -83,6 +84,15 @@ function parseHeartbeatTimeoutMs(raw) {
|
|
|
83
84
|
}
|
|
84
85
|
return Math.floor(candidate);
|
|
85
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* @param {unknown} value
|
|
89
|
+
* @returns {Record<string, unknown>}
|
|
90
|
+
*/
|
|
91
|
+
function recordOrEmpty(value) {
|
|
92
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
93
|
+
? /** @type {Record<string, unknown>} */ (value)
|
|
94
|
+
: {};
|
|
95
|
+
}
|
|
86
96
|
/**
|
|
87
97
|
* @param {HostNode} node
|
|
88
98
|
* @returns {XmlNode}
|
|
@@ -92,7 +102,7 @@ function toXmlNode(node) {
|
|
|
92
102
|
return { kind: "text", text: node.text };
|
|
93
103
|
}
|
|
94
104
|
const element = {
|
|
95
|
-
kind: "element",
|
|
105
|
+
kind: /** @type {"element"} */ ("element"),
|
|
96
106
|
tag: node.tag,
|
|
97
107
|
props: node.props ?? {},
|
|
98
108
|
children: node.children.map(toXmlNode),
|
|
@@ -112,11 +122,13 @@ function getRalphIteration(opts, id) {
|
|
|
112
122
|
if (map instanceof Map) {
|
|
113
123
|
return map.get(id) ?? fallback;
|
|
114
124
|
}
|
|
115
|
-
const value = map[id];
|
|
125
|
+
const value = /** @type {Record<string, number>} */ (map)[id];
|
|
116
126
|
return typeof value === "number" ? value : fallback;
|
|
117
127
|
}
|
|
118
128
|
/**
|
|
119
129
|
* @param {Record<string, unknown>} raw
|
|
130
|
+
* @param {boolean} [isAgent]
|
|
131
|
+
* @returns {{ retries: number; retryPolicy: import("../RetryPolicy.ts").RetryPolicy | undefined }}
|
|
120
132
|
*/
|
|
121
133
|
function resolveRetryConfig(raw, isAgent = false) {
|
|
122
134
|
const noRetry = Boolean(raw.noRetry);
|
|
@@ -139,7 +151,7 @@ function resolveRetryConfig(raw, isAgent = false) {
|
|
|
139
151
|
const retryPolicy = hasExplicitRetryPolicy
|
|
140
152
|
? /** @type {import("../RetryPolicy.ts").RetryPolicy} */ (raw.retryPolicy)
|
|
141
153
|
: retries > 0
|
|
142
|
-
? { backoff: "exponential", initialDelayMs: 1000 }
|
|
154
|
+
? /** @type {import("../RetryPolicy.ts").RetryPolicy} */ ({ backoff: "exponential", initialDelayMs: 1000 })
|
|
143
155
|
: undefined;
|
|
144
156
|
return { retries, retryPolicy };
|
|
145
157
|
}
|
|
@@ -152,7 +164,9 @@ export function extractFromHost(root, opts) {
|
|
|
152
164
|
if (!root) {
|
|
153
165
|
return { xml: null, tasks: [], mountedTaskIds: [] };
|
|
154
166
|
}
|
|
167
|
+
/** @type {TaskDescriptor[]} */
|
|
155
168
|
const tasks = [];
|
|
169
|
+
/** @type {string[]} */
|
|
156
170
|
const mountedTaskIds = [];
|
|
157
171
|
const seen = new Set();
|
|
158
172
|
const seenRalph = new Set();
|
|
@@ -251,7 +265,10 @@ export function extractFromHost(root, opts) {
|
|
|
251
265
|
if (!pathVal) {
|
|
252
266
|
throw new SmithersError("WORKTREE_EMPTY_PATH", WORKTREE_EMPTY_PATH_ERROR);
|
|
253
267
|
}
|
|
254
|
-
const normPath = resolveWorktreePath(pathVal, {
|
|
268
|
+
const normPath = resolveWorktreePath(pathVal, {
|
|
269
|
+
baseRootDir: opts?.baseRootDir,
|
|
270
|
+
workflowPath: opts?.workflowPath,
|
|
271
|
+
});
|
|
255
272
|
const branch = node.rawProps?.branch ? String(node.rawProps.branch) : undefined;
|
|
256
273
|
const baseBranch = node.rawProps?.baseBranch ? String(node.rawProps.baseBranch) : undefined;
|
|
257
274
|
nextWorktreeStack = [...worktreeStack, { id, path: normPath, branch, baseBranch }];
|
|
@@ -276,7 +293,7 @@ export function extractFromHost(root, opts) {
|
|
|
276
293
|
}
|
|
277
294
|
const outputTable = isDrizzleTable(outputRaw) ? outputRaw : null;
|
|
278
295
|
const outputTableName = outputTable
|
|
279
|
-
? getTableName(outputTable)
|
|
296
|
+
? getTableName(/** @type {any} */ (outputTable))
|
|
280
297
|
: typeof outputRaw === "string"
|
|
281
298
|
? outputRaw
|
|
282
299
|
: "";
|
|
@@ -285,7 +302,9 @@ export function extractFromHost(root, opts) {
|
|
|
285
302
|
const timeoutMs = typeof raw.timeoutMs === "number" ? raw.timeoutMs : null;
|
|
286
303
|
const heartbeatTimeoutMs = parseHeartbeatTimeoutMs(raw);
|
|
287
304
|
const continueOnFail = Boolean(raw.continueOnFail);
|
|
288
|
-
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
305
|
+
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
306
|
+
? /** @type {import("../CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
307
|
+
: undefined;
|
|
289
308
|
const dependsOn = Array.isArray(raw.dependsOn)
|
|
290
309
|
? raw.dependsOn.filter((v) => typeof v === "string")
|
|
291
310
|
: undefined;
|
|
@@ -340,9 +359,9 @@ export function extractFromHost(root, opts) {
|
|
|
340
359
|
}
|
|
341
360
|
return result.output;
|
|
342
361
|
},
|
|
343
|
-
label: raw.label,
|
|
362
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
344
363
|
meta: {
|
|
345
|
-
...raw.meta,
|
|
364
|
+
...recordOrEmpty(raw.meta),
|
|
346
365
|
__subflow: true,
|
|
347
366
|
__subflowMode: mode,
|
|
348
367
|
__subflowInput: raw.__smithersSubflowInput,
|
|
@@ -374,7 +393,7 @@ export function extractFromHost(root, opts) {
|
|
|
374
393
|
}
|
|
375
394
|
const outputTable = isDrizzleTable(outputRaw) ? outputRaw : null;
|
|
376
395
|
const outputTableName = outputTable
|
|
377
|
-
? getTableName(outputTable)
|
|
396
|
+
? getTableName(/** @type {any} */ (outputTable))
|
|
378
397
|
: typeof outputRaw === "string"
|
|
379
398
|
? outputRaw
|
|
380
399
|
: "";
|
|
@@ -383,7 +402,9 @@ export function extractFromHost(root, opts) {
|
|
|
383
402
|
const timeoutMs = typeof raw.timeoutMs === "number" ? raw.timeoutMs : null;
|
|
384
403
|
const heartbeatTimeoutMs = parseHeartbeatTimeoutMs(raw) ?? DEFAULT_SANDBOX_TASK_HEARTBEAT_TIMEOUT_MS;
|
|
385
404
|
const continueOnFail = Boolean(raw.continueOnFail);
|
|
386
|
-
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
405
|
+
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
406
|
+
? /** @type {import("../CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
407
|
+
: undefined;
|
|
387
408
|
const dependsOn = Array.isArray(raw.dependsOn)
|
|
388
409
|
? raw.dependsOn.filter((v) => typeof v === "string")
|
|
389
410
|
: undefined;
|
|
@@ -468,9 +489,9 @@ export function extractFromHost(root, opts) {
|
|
|
468
489
|
},
|
|
469
490
|
});
|
|
470
491
|
},
|
|
471
|
-
label: raw.label,
|
|
492
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
472
493
|
meta: {
|
|
473
|
-
...raw.meta,
|
|
494
|
+
...recordOrEmpty(raw.meta),
|
|
474
495
|
__sandbox: true,
|
|
475
496
|
__sandboxRuntime: runtime,
|
|
476
497
|
__sandboxProvider: provider,
|
|
@@ -520,12 +541,12 @@ export function extractFromHost(root, opts) {
|
|
|
520
541
|
}
|
|
521
542
|
const outputTable = isDrizzleTable(outputRaw) ? outputRaw : null;
|
|
522
543
|
const outputTableName = outputTable
|
|
523
|
-
? getTableName(outputTable)
|
|
544
|
+
? getTableName(/** @type {any} */ (outputTable))
|
|
524
545
|
: typeof outputRaw === "string"
|
|
525
546
|
? outputRaw
|
|
526
547
|
: "";
|
|
527
548
|
const outputRef = !outputTable && isZodObject(outputRaw) ? outputRaw : undefined;
|
|
528
|
-
const outputSchema = raw.outputSchema
|
|
549
|
+
const outputSchema = isZodObject(raw.outputSchema) ? raw.outputSchema : outputRef;
|
|
529
550
|
const waitAsync = Boolean(raw.waitAsync);
|
|
530
551
|
const timeoutMs = typeof raw.timeoutMs === "number" ? raw.timeoutMs : null;
|
|
531
552
|
const heartbeatTimeoutMs = parseHeartbeatTimeoutMs(raw);
|
|
@@ -564,9 +585,9 @@ export function extractFromHost(root, opts) {
|
|
|
564
585
|
prompt: undefined,
|
|
565
586
|
staticPayload: undefined,
|
|
566
587
|
computeFn: undefined,
|
|
567
|
-
label: raw.label,
|
|
588
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
568
589
|
meta: {
|
|
569
|
-
...raw.meta,
|
|
590
|
+
...recordOrEmpty(raw.meta),
|
|
570
591
|
__waitForEvent: true,
|
|
571
592
|
__eventName: raw.__smithersEventName ?? raw.event,
|
|
572
593
|
__correlationId: raw.__smithersCorrelationId ?? raw.correlationId,
|
|
@@ -646,9 +667,9 @@ export function extractFromHost(root, opts) {
|
|
|
646
667
|
prompt: undefined,
|
|
647
668
|
staticPayload: undefined,
|
|
648
669
|
computeFn: undefined,
|
|
649
|
-
label: raw.label
|
|
670
|
+
label: typeof raw.label === "string" ? raw.label : `timer:${nodeId}`,
|
|
650
671
|
meta: {
|
|
651
|
-
...raw.meta,
|
|
672
|
+
...recordOrEmpty(raw.meta),
|
|
652
673
|
__timer: true,
|
|
653
674
|
__timerType: hasDuration ? "duration" : "absolute",
|
|
654
675
|
...(hasDuration ? { __timerDuration: duration } : {}),
|
|
@@ -698,19 +719,21 @@ export function extractFromHost(root, opts) {
|
|
|
698
719
|
}
|
|
699
720
|
const outputTable = isDrizzleTable(outputRaw) ? outputRaw : null;
|
|
700
721
|
const outputTableName = outputTable
|
|
701
|
-
? getTableName(outputTable)
|
|
722
|
+
? getTableName(/** @type {any} */ (outputTable))
|
|
702
723
|
: typeof outputRaw === "string"
|
|
703
724
|
? outputRaw
|
|
704
725
|
: "";
|
|
705
726
|
const outputRef = !outputTable && isZodObject(outputRaw) ? outputRaw : undefined;
|
|
706
|
-
const outputSchema = raw.outputSchema
|
|
727
|
+
const outputSchema = isZodObject(raw.outputSchema) ? raw.outputSchema : outputRef;
|
|
707
728
|
const needsApproval = Boolean(raw.needsApproval);
|
|
708
729
|
const waitAsync = Boolean(raw.waitAsync);
|
|
730
|
+
/** @type {TaskDescriptor["approvalMode"]} */
|
|
709
731
|
const approvalMode = raw.approvalMode === "decision" ||
|
|
710
732
|
raw.approvalMode === "select" ||
|
|
711
733
|
raw.approvalMode === "rank"
|
|
712
734
|
? raw.approvalMode
|
|
713
735
|
: "gate";
|
|
736
|
+
/** @type {TaskDescriptor["approvalOnDeny"]} */
|
|
714
737
|
const approvalOnDeny = raw.approvalOnDeny === "continue" ||
|
|
715
738
|
raw.approvalOnDeny === "skip" ||
|
|
716
739
|
raw.approvalOnDeny === "fail"
|
|
@@ -735,31 +758,36 @@ export function extractFromHost(root, opts) {
|
|
|
735
758
|
const approvalAllowedUsers = Array.isArray(raw.approvalAllowedUsers)
|
|
736
759
|
? raw.approvalAllowedUsers.filter((value) => typeof value === "string")
|
|
737
760
|
: undefined;
|
|
738
|
-
const
|
|
761
|
+
const approvalAutoApproveRaw = raw.approvalAutoApprove && typeof raw.approvalAutoApprove === "object" && !Array.isArray(raw.approvalAutoApprove)
|
|
762
|
+
? /** @type {Record<string, unknown>} */ (raw.approvalAutoApprove)
|
|
763
|
+
: undefined;
|
|
764
|
+
const approvalAutoApprove = approvalAutoApproveRaw
|
|
739
765
|
? {
|
|
740
|
-
...(typeof
|
|
741
|
-
? { after:
|
|
766
|
+
...(typeof approvalAutoApproveRaw.after === "number"
|
|
767
|
+
? { after: approvalAutoApproveRaw.after }
|
|
742
768
|
: {}),
|
|
743
|
-
...(typeof
|
|
744
|
-
? { audit:
|
|
769
|
+
...(typeof approvalAutoApproveRaw.audit === "boolean"
|
|
770
|
+
? { audit: approvalAutoApproveRaw.audit }
|
|
745
771
|
: {}),
|
|
746
|
-
...(typeof
|
|
747
|
-
? { conditionMet:
|
|
772
|
+
...(typeof approvalAutoApproveRaw.conditionMet === "boolean"
|
|
773
|
+
? { conditionMet: approvalAutoApproveRaw.conditionMet }
|
|
748
774
|
: {}),
|
|
749
|
-
...(typeof
|
|
750
|
-
? { revertOnMet:
|
|
775
|
+
...(typeof approvalAutoApproveRaw.revertOnMet === "boolean"
|
|
776
|
+
? { revertOnMet: approvalAutoApproveRaw.revertOnMet }
|
|
751
777
|
: {}),
|
|
752
778
|
}
|
|
753
779
|
: undefined;
|
|
754
780
|
const skipIf = Boolean(raw.skipIf);
|
|
755
|
-
const agent = raw.agent;
|
|
781
|
+
const agent = /** @type {TaskDescriptor["agent"]} */ (raw.agent);
|
|
756
782
|
const kind = raw.__smithersKind;
|
|
757
783
|
const isAgent = kind === "agent" || Boolean(agent);
|
|
758
784
|
const { retries, retryPolicy } = resolveRetryConfig(raw, isAgent);
|
|
759
785
|
const timeoutMs = typeof raw.timeoutMs === "number" ? raw.timeoutMs : null;
|
|
760
786
|
const parsedHeartbeatTimeoutMs = parseHeartbeatTimeoutMs(raw);
|
|
761
787
|
const continueOnFail = Boolean(raw.continueOnFail);
|
|
762
|
-
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
788
|
+
const cachePolicy = raw.cache && typeof raw.cache === "object"
|
|
789
|
+
? /** @type {import("../CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
790
|
+
: undefined;
|
|
763
791
|
const heartbeatTimeoutMs = parsedHeartbeatTimeoutMs ??
|
|
764
792
|
(isAgent ? DEFAULT_LOCAL_TASK_HEARTBEAT_TIMEOUT_MS : null);
|
|
765
793
|
const prompt = isAgent ? String(raw.children ?? "") : undefined;
|
|
@@ -767,8 +795,8 @@ export function extractFromHost(root, opts) {
|
|
|
767
795
|
throw new SmithersError("MDX_PRELOAD_INACTIVE", `Task "${raw.id ?? nodeId}" prompt resolved to [object Object] — MDX preload is likely not active.\n` +
|
|
768
796
|
`Check that bunfig.toml has a top-level preload (not under [run]) and mdxPlugin() is registered.`);
|
|
769
797
|
}
|
|
770
|
-
const isCompute = kind === "compute" && typeof raw.__smithersComputeFn === "function";
|
|
771
|
-
const computeFn = isCompute ? raw.__smithersComputeFn : undefined;
|
|
798
|
+
const isCompute = (kind === "compute" || kind === "human") && typeof raw.__smithersComputeFn === "function";
|
|
799
|
+
const computeFn = isCompute ? /** @type {() => unknown} */ (raw.__smithersComputeFn) : undefined;
|
|
772
800
|
const staticPayload = isAgent || isCompute
|
|
773
801
|
? undefined
|
|
774
802
|
: (raw.__smithersPayload ?? raw.__payload ?? raw.children);
|
|
@@ -811,17 +839,23 @@ export function extractFromHost(root, opts) {
|
|
|
811
839
|
continueOnFail,
|
|
812
840
|
cachePolicy,
|
|
813
841
|
hijack: Boolean(raw.hijack),
|
|
814
|
-
onHijackExit: raw.onHijackExit === "reopen" ? "reopen" : "complete",
|
|
842
|
+
onHijackExit: /** @type {"reopen" | "complete"} */ (raw.onHijackExit === "reopen" ? "reopen" : "complete"),
|
|
815
843
|
agent,
|
|
816
844
|
prompt,
|
|
817
845
|
staticPayload,
|
|
818
846
|
computeFn,
|
|
819
|
-
label: raw.label,
|
|
820
|
-
meta: raw.meta
|
|
821
|
-
|
|
847
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
848
|
+
meta: raw.meta && typeof raw.meta === "object" && !Array.isArray(raw.meta)
|
|
849
|
+
? /** @type {Record<string, unknown>} */ (raw.meta)
|
|
850
|
+
: undefined,
|
|
851
|
+
scorers: raw.scorers && typeof raw.scorers === "object" && !Array.isArray(raw.scorers)
|
|
852
|
+
? /** @type {import("../ScorersMap.ts").ScorersMap} */ (raw.scorers)
|
|
853
|
+
: undefined,
|
|
822
854
|
parallelGroupId: parallelGroup?.id,
|
|
823
855
|
parallelMaxConcurrency: parallelGroup?.max,
|
|
824
|
-
memoryConfig: raw.memory
|
|
856
|
+
memoryConfig: raw.memory && typeof raw.memory === "object" && !Array.isArray(raw.memory)
|
|
857
|
+
? /** @type {TaskDescriptor["memoryConfig"]} */ (raw.memory)
|
|
858
|
+
: undefined,
|
|
825
859
|
};
|
|
826
860
|
// Worktree path is captured in typed fields (worktreeId/worktreePath) and
|
|
827
861
|
// consumed by the engine; avoid attaching untyped ad-hoc properties.
|
package/src/extract.js
CHANGED
|
@@ -2,14 +2,12 @@ import { getTableName } from "drizzle-orm";
|
|
|
2
2
|
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
3
3
|
import { validateForkSources } from "./validateForkSources.js";
|
|
4
4
|
import { resolveWorktreePath } from "./worktree-path.js";
|
|
5
|
+
import { DEFAULT_MERGE_QUEUE_CONCURRENCY, WORKTREE_EMPTY_PATH_ERROR } from "./constants.js";
|
|
5
6
|
/** @typedef {import("./TaskDescriptor.ts").TaskDescriptor} TaskDescriptor */
|
|
6
7
|
/** @typedef {import("./XmlNode.ts").XmlNode} XmlNode */
|
|
7
8
|
/** @typedef {import("./ExtractOptions.ts").ExtractOptions} ExtractOptions */
|
|
8
9
|
/** @typedef {import("./HostNode.ts").HostNode} HostNode */
|
|
9
10
|
/** @typedef {import("./WorkflowGraph.ts").WorkflowGraph} WorkflowGraph */
|
|
10
|
-
|
|
11
|
-
const DEFAULT_MERGE_QUEUE_CONCURRENCY = 1;
|
|
12
|
-
const WORKTREE_EMPTY_PATH_ERROR = "<Worktree> requires a non-empty path prop";
|
|
13
11
|
// Default per-task heartbeat timeout. 10 min is the floor for agent-backed
|
|
14
12
|
// tasks: LLM CLIs (claude, codex, gemini, kimi) can sit silent for several
|
|
15
13
|
// minutes during long deliberation or large-context reads. The previous
|
|
@@ -60,13 +58,13 @@ function isZodObject(value) {
|
|
|
60
58
|
}
|
|
61
59
|
/**
|
|
62
60
|
* @param {unknown} value
|
|
63
|
-
* @returns {
|
|
61
|
+
* @returns {value is any}
|
|
64
62
|
*/
|
|
65
63
|
function isDrizzleTable(value) {
|
|
66
64
|
if (!value || typeof value !== "object")
|
|
67
65
|
return false;
|
|
68
66
|
try {
|
|
69
|
-
const name = getTableName(value);
|
|
67
|
+
const name = getTableName(/** @type {any} */ (value));
|
|
70
68
|
return typeof name === "string" && name.length > 0;
|
|
71
69
|
}
|
|
72
70
|
catch {
|
|
@@ -89,7 +87,7 @@ function resolveOutput(raw) {
|
|
|
89
87
|
}
|
|
90
88
|
const outputTable = isDrizzleTable(outputRaw) ? outputRaw : null;
|
|
91
89
|
const outputTableName = outputTable
|
|
92
|
-
? getTableName(outputTable)
|
|
90
|
+
? getTableName(/** @type {any} */ (outputTable))
|
|
93
91
|
: typeof outputRaw === "string"
|
|
94
92
|
? outputRaw
|
|
95
93
|
: "";
|
|
@@ -119,6 +117,8 @@ function parseHeartbeatTimeoutMs(raw) {
|
|
|
119
117
|
}
|
|
120
118
|
/**
|
|
121
119
|
* @param {Record<string, unknown>} raw
|
|
120
|
+
* @param {boolean} [isAgent]
|
|
121
|
+
* @returns {{ retries: number; retryPolicy: import("./RetryPolicy.ts").RetryPolicy | undefined }}
|
|
122
122
|
*/
|
|
123
123
|
function resolveRetryConfig(raw, isAgent = false) {
|
|
124
124
|
const noRetry = Boolean(raw.noRetry);
|
|
@@ -135,12 +135,12 @@ function resolveRetryConfig(raw, isAgent = false) {
|
|
|
135
135
|
: defaultNoRetryForContinueOnFail
|
|
136
136
|
? (isAgent ? 1 : 0)
|
|
137
137
|
: hasExplicitRetries
|
|
138
|
-
? raw.retries
|
|
138
|
+
? /** @type {number} */ (raw.retries)
|
|
139
139
|
: Infinity;
|
|
140
140
|
const retryPolicy = hasExplicitRetryPolicy
|
|
141
|
-
? raw.retryPolicy
|
|
141
|
+
? /** @type {import("./RetryPolicy.ts").RetryPolicy} */ (raw.retryPolicy)
|
|
142
142
|
: retries > 0
|
|
143
|
-
? { backoff: "exponential", initialDelayMs: 1000 }
|
|
143
|
+
? /** @type {import("./RetryPolicy.ts").RetryPolicy} */ ({ backoff: "exponential", initialDelayMs: 1000 })
|
|
144
144
|
: undefined;
|
|
145
145
|
return { retries, retryPolicy };
|
|
146
146
|
}
|
|
@@ -153,7 +153,7 @@ function toXmlNode(node) {
|
|
|
153
153
|
return { kind: "text", text: node.text };
|
|
154
154
|
}
|
|
155
155
|
const element = {
|
|
156
|
-
kind: "element",
|
|
156
|
+
kind: /** @type {"element"} */ ("element"),
|
|
157
157
|
tag: node.tag,
|
|
158
158
|
props: node.props ?? {},
|
|
159
159
|
children: node.children.map(toXmlNode),
|
|
@@ -173,7 +173,7 @@ function getRalphIteration(opts, id) {
|
|
|
173
173
|
if (map instanceof Map) {
|
|
174
174
|
return map.get(id) ?? fallback;
|
|
175
175
|
}
|
|
176
|
-
const value = map[id];
|
|
176
|
+
const value = /** @type {Record<string, number>} */ (map)[id];
|
|
177
177
|
return typeof value === "number" ? value : fallback;
|
|
178
178
|
}
|
|
179
179
|
/**
|
|
@@ -236,7 +236,7 @@ function approvalAutoApprove(value) {
|
|
|
236
236
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
237
237
|
return undefined;
|
|
238
238
|
}
|
|
239
|
-
const raw = value;
|
|
239
|
+
const raw = /** @type {Record<string, unknown>} */ (value);
|
|
240
240
|
return {
|
|
241
241
|
...(typeof raw.after === "number" ? { after: raw.after } : {}),
|
|
242
242
|
...(typeof raw.audit === "boolean" ? { audit: raw.audit } : {}),
|
|
@@ -343,7 +343,9 @@ export function extractGraph(root, opts) {
|
|
|
343
343
|
if (!root) {
|
|
344
344
|
return { xml: null, tasks: [], mountedTaskIds: [] };
|
|
345
345
|
}
|
|
346
|
+
/** @type {TaskDescriptor[]} */
|
|
346
347
|
const tasks = [];
|
|
348
|
+
/** @type {string[]} */
|
|
347
349
|
const mountedTaskIds = [];
|
|
348
350
|
const seen = new Set();
|
|
349
351
|
const seenRalph = new Set();
|
|
@@ -451,7 +453,7 @@ export function extractGraph(root, opts) {
|
|
|
451
453
|
heartbeatTimeoutMs: parseHeartbeatTimeoutMs(raw),
|
|
452
454
|
continueOnFail: Boolean(raw.continueOnFail),
|
|
453
455
|
cachePolicy: raw.cache && typeof raw.cache === "object"
|
|
454
|
-
? raw.cache
|
|
456
|
+
? /** @type {import("./CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
455
457
|
: undefined,
|
|
456
458
|
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
457
459
|
meta: {
|
|
@@ -484,7 +486,7 @@ export function extractGraph(root, opts) {
|
|
|
484
486
|
heartbeatTimeoutMs: parseHeartbeatTimeoutMs(raw) ?? DEFAULT_SANDBOX_TASK_HEARTBEAT_TIMEOUT_MS,
|
|
485
487
|
continueOnFail: Boolean(raw.continueOnFail),
|
|
486
488
|
cachePolicy: raw.cache && typeof raw.cache === "object"
|
|
487
|
-
? raw.cache
|
|
489
|
+
? /** @type {import("./CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
488
490
|
: undefined,
|
|
489
491
|
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
490
492
|
meta: {
|
|
@@ -622,7 +624,7 @@ export function extractGraph(root, opts) {
|
|
|
622
624
|
const kind = raw.__smithersKind;
|
|
623
625
|
const isAgent = kind === "agent" || Boolean(raw.agent);
|
|
624
626
|
const { retries, retryPolicy } = resolveRetryConfig(raw, isAgent);
|
|
625
|
-
const isCompute = kind === "compute" && typeof raw.__smithersComputeFn === "function";
|
|
627
|
+
const isCompute = (kind === "compute" || kind === "human") && typeof raw.__smithersComputeFn === "function";
|
|
626
628
|
const parsedHeartbeatTimeoutMs = parseHeartbeatTimeoutMs(raw);
|
|
627
629
|
const heartbeatTimeoutMs = parsedHeartbeatTimeoutMs ??
|
|
628
630
|
(isAgent ? DEFAULT_LOCAL_TASK_HEARTBEAT_TIMEOUT_MS : null);
|
|
@@ -649,25 +651,27 @@ export function extractGraph(root, opts) {
|
|
|
649
651
|
heartbeatTimeoutMs,
|
|
650
652
|
continueOnFail: Boolean(raw.continueOnFail),
|
|
651
653
|
cachePolicy: raw.cache && typeof raw.cache === "object"
|
|
652
|
-
? raw.cache
|
|
654
|
+
? /** @type {import("./CachePolicy.ts").CachePolicy<unknown>} */ (raw.cache)
|
|
653
655
|
: undefined,
|
|
654
656
|
hijack: Boolean(raw.hijack),
|
|
655
657
|
onHijackExit: raw.onHijackExit === "reopen" ? "reopen" : "complete",
|
|
656
|
-
agent: raw.agent,
|
|
658
|
+
agent: /** @type {TaskDescriptor["agent"]} */ (raw.agent),
|
|
657
659
|
prompt,
|
|
658
660
|
staticPayload: isAgent || isCompute
|
|
659
661
|
? undefined
|
|
660
662
|
: (raw.__smithersPayload ?? raw.__payload ?? raw.children),
|
|
661
663
|
computeFn: isCompute
|
|
662
|
-
? raw.__smithersComputeFn
|
|
664
|
+
? /** @type {() => unknown} */ (raw.__smithersComputeFn)
|
|
663
665
|
: undefined,
|
|
664
666
|
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
665
667
|
meta: raw.meta && typeof raw.meta === "object" && !Array.isArray(raw.meta)
|
|
666
|
-
? raw.meta
|
|
668
|
+
? /** @type {Record<string, unknown>} */ (raw.meta)
|
|
667
669
|
: undefined,
|
|
668
670
|
scorers: raw.scorers && typeof raw.scorers === "object" && !Array.isArray(raw.scorers)
|
|
669
|
-
? raw.scorers
|
|
671
|
+
? /** @type {import("./ScorersMap.ts").ScorersMap} */ (raw.scorers)
|
|
670
672
|
: undefined,
|
|
673
|
+
groundTruth: raw.groundTruth,
|
|
674
|
+
context: raw.context,
|
|
671
675
|
memoryConfig: raw.memory && typeof raw.memory === "object" && !Array.isArray(raw.memory)
|
|
672
676
|
? raw.memory
|
|
673
677
|
: undefined,
|
package/src/index.d.ts
CHANGED
|
@@ -128,6 +128,8 @@ type TaskDescriptor$1 = {
|
|
|
128
128
|
ralphId?: string;
|
|
129
129
|
dependsOn?: string[];
|
|
130
130
|
needs?: Record<string, string>;
|
|
131
|
+
/** Logical id of the task whose final agent session this task forks. Gates execution and seeds the session. */
|
|
132
|
+
forkSource?: string;
|
|
131
133
|
worktreeId?: string;
|
|
132
134
|
worktreePath?: string;
|
|
133
135
|
worktreeBranch?: string;
|
|
@@ -167,6 +169,8 @@ type TaskDescriptor$1 = {
|
|
|
167
169
|
label?: string;
|
|
168
170
|
meta?: Record<string, unknown>;
|
|
169
171
|
scorers?: ScorersMap$1;
|
|
172
|
+
groundTruth?: unknown;
|
|
173
|
+
context?: unknown;
|
|
170
174
|
memoryConfig?: TaskMemoryConfig$1;
|
|
171
175
|
aspects?: TaskAspects$1;
|
|
172
176
|
};
|
|
@@ -204,13 +208,18 @@ declare function extractGraph(root: HostNode$1 | null, opts?: ExtractOptions$1):
|
|
|
204
208
|
declare function extractFromHost(root: HostNode$1 | null, opts?: ExtractOptions$1): WorkflowGraph$1;
|
|
205
209
|
/**
|
|
206
210
|
* Resolve a <Worktree path> prop exactly the way graph extraction resolves it.
|
|
211
|
+
* Relative paths are resolved against the launch root (`--root`, the nearest
|
|
212
|
+
* `.smithers` anchor, or the operator cwd), never `dirname(workflowPath)`.
|
|
213
|
+
* `workflowPath` is threaded through graph/engine rendering for workflow
|
|
214
|
+
* identity and diagnostics only; it is not a worktree path resolution base.
|
|
207
215
|
*
|
|
208
216
|
* @param {unknown} path
|
|
209
|
-
* @param {{ baseRootDir?: string }} [opts]
|
|
217
|
+
* @param {{ baseRootDir?: string; workflowPath?: string | null }} [opts]
|
|
210
218
|
* @returns {string}
|
|
211
219
|
*/
|
|
212
220
|
declare function resolveWorktreePath(path: unknown, opts?: {
|
|
213
221
|
baseRootDir?: string;
|
|
222
|
+
workflowPath?: string | null;
|
|
214
223
|
}): string;
|
|
215
224
|
type ExtractOptions$1 = ExtractOptions$2;
|
|
216
225
|
type HostNode$1 = HostNode$2;
|
package/src/index.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
/** @typedef {import("./ScorerInput.ts").ScorerInput} ScorerInput */
|
|
23
23
|
/** @typedef {import("./ScorersMap.ts").ScorersMap} ScorersMap */
|
|
24
24
|
/** @typedef {import("./TaskDescriptor.ts").TaskDescriptor} TaskDescriptor */
|
|
25
|
+
/** @typedef {import("./TaskAspects.ts").TaskAspects} TaskAspects */
|
|
25
26
|
/** @typedef {import("./TaskMemoryConfig.ts").TaskMemoryConfig} TaskMemoryConfig */
|
|
26
27
|
/** @typedef {import("./WorkflowGraph.ts").WorkflowGraph} WorkflowGraph */
|
|
27
28
|
/** @typedef {import("./XmlElement.ts").XmlElement} XmlElement */
|
package/src/types.ts
CHANGED
|
@@ -191,6 +191,8 @@ export type TaskDescriptor = {
|
|
|
191
191
|
label?: string;
|
|
192
192
|
meta?: Record<string, unknown>;
|
|
193
193
|
scorers?: ScorersMap;
|
|
194
|
+
groundTruth?: unknown;
|
|
195
|
+
context?: unknown;
|
|
194
196
|
|
|
195
197
|
memoryConfig?: TaskMemoryConfig;
|
|
196
198
|
/** Resolved `<Aspects>` budget configuration enforced by the engine at dispatch. */
|
|
@@ -44,6 +44,10 @@ export function validateForkSources(tasks) {
|
|
|
44
44
|
// fork edge. Used only for cycle detection.
|
|
45
45
|
/** @type {Map<string, Set<string>>} */
|
|
46
46
|
const adjacency = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} from
|
|
49
|
+
* @param {string} to
|
|
50
|
+
*/
|
|
47
51
|
const addEdge = (from, to) => {
|
|
48
52
|
const cleanedFrom = logicalId(from);
|
|
49
53
|
const cleanedTo = logicalId(to);
|
package/src/worktree-path.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { isAbsolute, resolve } from "node:path";
|
|
1
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
2
2
|
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
3
3
|
import { WORKTREE_EMPTY_PATH_ERROR } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Resolve a <Worktree path> prop exactly the way graph extraction resolves it.
|
|
7
|
+
* Relative paths are resolved against the launch root (`--root`, the nearest
|
|
8
|
+
* `.smithers` anchor, or the operator cwd), never `dirname(workflowPath)`.
|
|
9
|
+
* `workflowPath` is threaded through graph/engine rendering for workflow
|
|
10
|
+
* identity and diagnostics only; it is not a worktree path resolution base.
|
|
7
11
|
*
|
|
8
12
|
* @param {unknown} path
|
|
9
|
-
* @param {{ baseRootDir?: string }} [opts]
|
|
13
|
+
* @param {{ baseRootDir?: string; workflowPath?: string | null }} [opts]
|
|
10
14
|
* @returns {string}
|
|
11
15
|
*/
|
|
12
16
|
export function resolveWorktreePath(path, opts) {
|
|
@@ -20,5 +24,43 @@ export function resolveWorktreePath(path, opts) {
|
|
|
20
24
|
const base = typeof opts?.baseRootDir === "string" && opts.baseRootDir.length > 0
|
|
21
25
|
? opts.baseRootDir
|
|
22
26
|
: process.cwd();
|
|
23
|
-
|
|
27
|
+
const resolvedPath = resolve(base, pathVal);
|
|
28
|
+
warnRelativeWorktreePathOnce(pathVal, base, resolvedPath, opts?.workflowPath);
|
|
29
|
+
return resolvedPath;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let didWarnRelativeWorktreePath = false;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reset process-local relative worktree warning state for deterministic tests.
|
|
36
|
+
*
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
39
|
+
export function resetRelativeWorktreePathWarningForTest() {
|
|
40
|
+
didWarnRelativeWorktreePath = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} pathVal
|
|
45
|
+
* @param {string} base
|
|
46
|
+
* @param {string} resolvedPath
|
|
47
|
+
* @param {string | null | undefined} workflowPath
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
50
|
+
function warnRelativeWorktreePathOnce(pathVal, base, resolvedPath, workflowPath) {
|
|
51
|
+
if (didWarnRelativeWorktreePath) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
didWarnRelativeWorktreePath = true;
|
|
55
|
+
const workflowDir = typeof workflowPath === "string" && workflowPath.length > 0
|
|
56
|
+
? dirname(workflowPath)
|
|
57
|
+
: undefined;
|
|
58
|
+
console.warn("relative <Worktree path> resolved against launch root, not the workflow file directory", {
|
|
59
|
+
code: "WORKTREE_RELATIVE_PATH_BASE_ROOT",
|
|
60
|
+
path: pathVal,
|
|
61
|
+
base,
|
|
62
|
+
resolvedPath,
|
|
63
|
+
workflowPath,
|
|
64
|
+
workflowDir,
|
|
65
|
+
});
|
|
24
66
|
}
|