@h-rig/core 0.0.6-alpha.13 → 0.0.6-alpha.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/define-config.d.ts +18 -0
- package/dist/src/define-config.js +20 -2
- package/dist/src/define-plugin.d.ts +9 -0
- package/dist/src/define-plugin.js +17 -0
- package/dist/src/engineReadModelReducer.d.ts +12 -0
- package/dist/src/engineReadModelReducer.js +17 -13
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +364 -31
- package/dist/src/load-config.d.ts +2 -0
- package/dist/src/load-config.js +257 -5
- package/dist/src/plugin-host.d.ts +40 -0
- package/dist/src/plugin-host.js +18 -0
- package/dist/src/plugin-runtime.d.ts +64 -0
- package/dist/src/rig-init-builder.d.ts +30 -0
- package/dist/src/rig-init-builder.js +2 -1
- package/dist/src/rigSelectors.d.ts +220 -0
- package/dist/src/rigSelectors.js +125 -4
- package/dist/src/taskGraph.d.ts +41 -0
- package/dist/src/taskGraph.js +164 -8
- package/dist/src/taskGraphCodes.d.ts +3 -0
- package/dist/src/taskGraphLayout.d.ts +60 -0
- package/dist/src/taskGraphLayout.js +22 -3
- package/package.json +6 -12
package/dist/src/index.js
CHANGED
|
@@ -43,11 +43,45 @@ function definePlugin(meta, runtime) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
const declaredHooks = new Map((validated.contributes?.hooks ?? []).map((h) => [h.id, h]));
|
|
47
|
+
for (const hookId of Object.keys(runtime.hooks ?? {})) {
|
|
48
|
+
const metadata = declaredHooks.get(hookId);
|
|
49
|
+
if (!metadata) {
|
|
50
|
+
throw new Error(`definePlugin(${validated.name}): typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
|
|
51
|
+
}
|
|
52
|
+
if (metadata.command) {
|
|
53
|
+
throw new Error(`definePlugin(${validated.name}): hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (runtime.hooks) {
|
|
57
|
+
for (const h of declaredHooks.values()) {
|
|
58
|
+
if (!runtime.hooks[h.id] && !h.command) {
|
|
59
|
+
throw new Error(`definePlugin(${validated.name}): hook metadata "${h.id}" has no implementation (typed function or command)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
return { ...validated, __runtime: runtime };
|
|
47
64
|
}
|
|
48
65
|
// packages/core/src/define-config.ts
|
|
49
66
|
import { Schema as Schema2 } from "effect";
|
|
50
67
|
import { RigConfig } from "@rig/contracts";
|
|
68
|
+
function normalizeWorkspaceConfig(raw) {
|
|
69
|
+
const workspace = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : { mainRepo: "." };
|
|
70
|
+
workspace.checkout = workspace.checkout ?? workspace.isolation ?? "worktree";
|
|
71
|
+
workspace.isolation = workspace.isolation ?? workspace.checkout;
|
|
72
|
+
workspace.sandbox = workspace.sandbox ?? "enforce";
|
|
73
|
+
return workspace;
|
|
74
|
+
}
|
|
75
|
+
function applyConfigDefaults(raw) {
|
|
76
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
77
|
+
return raw;
|
|
78
|
+
const record = raw;
|
|
79
|
+
return {
|
|
80
|
+
...record,
|
|
81
|
+
plugins: Array.isArray(record.plugins) ? record.plugins : [],
|
|
82
|
+
workspace: normalizeWorkspaceConfig(record.workspace)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
51
85
|
function defineConfig(cfg) {
|
|
52
86
|
const runtimeByName = new Map;
|
|
53
87
|
const plugins = cfg.plugins ?? [];
|
|
@@ -56,7 +90,7 @@ function defineConfig(cfg) {
|
|
|
56
90
|
runtimeByName.set(plugin.name, plugin.__runtime);
|
|
57
91
|
}
|
|
58
92
|
}
|
|
59
|
-
const decoded = Schema2.decodeUnknownSync(RigConfig)(cfg);
|
|
93
|
+
const decoded = Schema2.decodeUnknownSync(RigConfig)(applyConfigDefaults(cfg));
|
|
60
94
|
const decodedPlugins = decoded.plugins.map((p) => {
|
|
61
95
|
const runtime = runtimeByName.get(p.name);
|
|
62
96
|
if (!runtime)
|
|
@@ -124,6 +158,24 @@ function assertRuntimeMatchesMetadata(plugin) {
|
|
|
124
158
|
}
|
|
125
159
|
}
|
|
126
160
|
}
|
|
161
|
+
const declaredHooks = new Map((plugin.contributes?.hooks ?? []).map((hook) => [hook.id, hook]));
|
|
162
|
+
const runtimeHooks = plugin.__runtime?.hooks;
|
|
163
|
+
for (const hookId of Object.keys(runtimeHooks ?? {})) {
|
|
164
|
+
const metadata = declaredHooks.get(hookId);
|
|
165
|
+
if (!metadata) {
|
|
166
|
+
throw new Error(`plugin "${plugin.name}" typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
|
|
167
|
+
}
|
|
168
|
+
if (metadata.command) {
|
|
169
|
+
throw new Error(`plugin "${plugin.name}" hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (runtimeHooks) {
|
|
173
|
+
for (const hook of declaredHooks.values()) {
|
|
174
|
+
if (!runtimeHooks[hook.id] && !hook.command) {
|
|
175
|
+
throw new Error(`plugin "${plugin.name}" hook metadata "${hook.id}" has no implementation (typed function or command)`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
127
179
|
}
|
|
128
180
|
function createPluginHost(plugins) {
|
|
129
181
|
assertUniquePluginNames(plugins);
|
|
@@ -257,7 +309,8 @@ function buildRigInitConfigSource(input) {
|
|
|
257
309
|
lines.push(` },`);
|
|
258
310
|
}
|
|
259
311
|
lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
|
|
260
|
-
|
|
312
|
+
const sshTarget = input.sshTarget?.trim();
|
|
313
|
+
lines.push(sshTarget ? ` runtime: { harness: "pi", mode: "yolo", server: { sshTarget: ${JSON.stringify(sshTarget)} } },` : ` runtime: { harness: "pi", mode: "yolo" }, // server.sshTarget unset = local placement`);
|
|
261
314
|
lines.push(` planning: { mode: "auto" },`);
|
|
262
315
|
lines.push(` github: {`);
|
|
263
316
|
lines.push(` issueUpdates: "lifecycle",`);
|
|
@@ -283,6 +336,7 @@ import {
|
|
|
283
336
|
WorkspaceId,
|
|
284
337
|
WorktreeId
|
|
285
338
|
} from "@rig/contracts";
|
|
339
|
+
var CANONICAL_RUNTIME_ADAPTER = "pi";
|
|
286
340
|
function isRecord(value) {
|
|
287
341
|
return typeof value === "object" && value !== null;
|
|
288
342
|
}
|
|
@@ -422,7 +476,7 @@ function mapLegacySessionStatusToRunStatus(status) {
|
|
|
422
476
|
case "error":
|
|
423
477
|
return "failed";
|
|
424
478
|
case "stopped":
|
|
425
|
-
return "
|
|
479
|
+
return "stopped";
|
|
426
480
|
default:
|
|
427
481
|
return "created";
|
|
428
482
|
}
|
|
@@ -458,12 +512,15 @@ function mapTaskStatusFromRunStatus(status, fallback) {
|
|
|
458
512
|
case "paused":
|
|
459
513
|
return "in_progress";
|
|
460
514
|
case "reviewing":
|
|
515
|
+
case "closing-out":
|
|
461
516
|
return "under_review";
|
|
517
|
+
case "needs-attention":
|
|
518
|
+
return "blocked";
|
|
462
519
|
case "completed":
|
|
463
520
|
return "completed";
|
|
464
521
|
case "failed":
|
|
465
522
|
return "ready";
|
|
466
|
-
case "
|
|
523
|
+
case "stopped":
|
|
467
524
|
return "cancelled";
|
|
468
525
|
}
|
|
469
526
|
}
|
|
@@ -647,7 +704,7 @@ function applySyntheticRuntimePreparation(snapshot, event) {
|
|
|
647
704
|
...existingRun,
|
|
648
705
|
taskId: existingRun.taskId ?? nextTaskId,
|
|
649
706
|
runKind: existingRun.runKind === "adhoc" && nextTaskId ? "task" : existingRun.runKind,
|
|
650
|
-
runtimeAdapter:
|
|
707
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
651
708
|
initialPrompt: existingRun.initialPrompt ?? null,
|
|
652
709
|
executionTarget: existingRun.executionTarget ?? "local",
|
|
653
710
|
remoteHostId: existingRun.remoteHostId ?? null,
|
|
@@ -663,7 +720,7 @@ function applySyntheticRuntimePreparation(snapshot, event) {
|
|
|
663
720
|
...runtimeBase,
|
|
664
721
|
workspaceId: nextWorkspaceId,
|
|
665
722
|
runId: runtimeRunId,
|
|
666
|
-
adapterKind:
|
|
723
|
+
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
667
724
|
executionTarget: existingRun.executionTarget ?? "local",
|
|
668
725
|
remoteHostId: existingRun.remoteHostId ?? null,
|
|
669
726
|
status: failed ? "failed" : finished ? "prepared" : "starting",
|
|
@@ -712,7 +769,7 @@ function applySyntheticRuntimePrepared(snapshot, event) {
|
|
|
712
769
|
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(existingRun, event.createdAt),
|
|
713
770
|
workspaceId: nextWorkspaceId,
|
|
714
771
|
runId: runtimeRunId,
|
|
715
|
-
adapterKind:
|
|
772
|
+
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
716
773
|
executionTarget: existingRun.executionTarget ?? "local",
|
|
717
774
|
remoteHostId: existingRun.remoteHostId ?? null,
|
|
718
775
|
status: "prepared",
|
|
@@ -755,7 +812,7 @@ function applyLegacyProjectEvent(snapshot, event) {
|
|
|
755
812
|
title,
|
|
756
813
|
rootPath,
|
|
757
814
|
sourceKind: "native",
|
|
758
|
-
defaultRuntimeAdapter:
|
|
815
|
+
defaultRuntimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
759
816
|
defaultModel: readNullableString(payload, "defaultModel") ?? null,
|
|
760
817
|
createdAt,
|
|
761
818
|
updatedAt
|
|
@@ -850,7 +907,7 @@ function applyLegacyThreadEvent(snapshot, event) {
|
|
|
850
907
|
runtimeMode: "full-access",
|
|
851
908
|
interactionMode: "default",
|
|
852
909
|
status: "created",
|
|
853
|
-
runtimeAdapter:
|
|
910
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
854
911
|
model,
|
|
855
912
|
initialPrompt: null,
|
|
856
913
|
activeRuntimeId: null,
|
|
@@ -1034,7 +1091,7 @@ function applyLegacyThreadEvent(snapshot, event) {
|
|
|
1034
1091
|
const providerName = readNullableString(session, "providerName");
|
|
1035
1092
|
const nextRun = {
|
|
1036
1093
|
...existingRun,
|
|
1037
|
-
runtimeAdapter:
|
|
1094
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1038
1095
|
initialPrompt: existingRun.initialPrompt ?? null,
|
|
1039
1096
|
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
1040
1097
|
status: mapLegacySessionStatusToRunStatus(readString(session, "status")),
|
|
@@ -1047,7 +1104,7 @@ function applyLegacyThreadEvent(snapshot, event) {
|
|
|
1047
1104
|
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, sessionUpdatedAt),
|
|
1048
1105
|
workspaceId: existingRun.workspaceId,
|
|
1049
1106
|
runId: asRunId(runId),
|
|
1050
|
-
adapterKind:
|
|
1107
|
+
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
1051
1108
|
status: mapLegacySessionStatusToRuntimeStatus(readString(session, "status")),
|
|
1052
1109
|
workspaceDir: existingRun.worktreePath,
|
|
1053
1110
|
isolationMode: existingRun.worktreePath ? "worktree" : "env",
|
|
@@ -1281,7 +1338,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1281
1338
|
runtimeMode: payload.runtimeMode,
|
|
1282
1339
|
interactionMode: payload.interactionMode,
|
|
1283
1340
|
status: "created",
|
|
1284
|
-
runtimeAdapter:
|
|
1341
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1285
1342
|
model: payload.model ?? null,
|
|
1286
1343
|
initialPrompt: payload.initialPrompt ?? null,
|
|
1287
1344
|
executionTarget: payload.executionTarget ?? (payload.remoteHostId ? "remote" : "local"),
|
|
@@ -1313,7 +1370,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1313
1370
|
...base,
|
|
1314
1371
|
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1315
1372
|
...run,
|
|
1316
|
-
status: "
|
|
1373
|
+
status: "stopped",
|
|
1317
1374
|
updatedAt: payload.createdAt,
|
|
1318
1375
|
completedAt: payload.createdAt
|
|
1319
1376
|
}))
|
|
@@ -1977,7 +2034,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1977
2034
|
runtimeMode: "full-access",
|
|
1978
2035
|
interactionMode: "default",
|
|
1979
2036
|
status: "created",
|
|
1980
|
-
runtimeAdapter:
|
|
2037
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1981
2038
|
model: null,
|
|
1982
2039
|
initialPrompt: null,
|
|
1983
2040
|
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
@@ -1997,7 +2054,7 @@ function applyEngineEvent(snapshot, event) {
|
|
|
1997
2054
|
workspaceId: asWorkspaceId(workspaceId),
|
|
1998
2055
|
taskId: asTaskId(taskId),
|
|
1999
2056
|
runKind: "task",
|
|
2000
|
-
runtimeAdapter:
|
|
2057
|
+
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
2001
2058
|
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
2002
2059
|
updatedAt: event.createdAt
|
|
2003
2060
|
});
|
|
@@ -2085,10 +2142,40 @@ function projectTaskStatusForGrouping(status) {
|
|
|
2085
2142
|
case "in_progress":
|
|
2086
2143
|
case "under_review":
|
|
2087
2144
|
return "running";
|
|
2145
|
+
case null:
|
|
2146
|
+
case undefined:
|
|
2147
|
+
case "":
|
|
2148
|
+
return "unknown";
|
|
2088
2149
|
default:
|
|
2089
2150
|
return status;
|
|
2090
2151
|
}
|
|
2091
2152
|
}
|
|
2153
|
+
function projectRunStatusForTaskGrouping(status) {
|
|
2154
|
+
switch (status) {
|
|
2155
|
+
case "created":
|
|
2156
|
+
case "queued":
|
|
2157
|
+
case "preparing":
|
|
2158
|
+
return "queued";
|
|
2159
|
+
case "running":
|
|
2160
|
+
case "waiting-approval":
|
|
2161
|
+
case "waiting-user-input":
|
|
2162
|
+
case "paused":
|
|
2163
|
+
case "validating":
|
|
2164
|
+
case "reviewing":
|
|
2165
|
+
case "closing-out":
|
|
2166
|
+
return "running";
|
|
2167
|
+
case "needs-attention":
|
|
2168
|
+
return "blocked";
|
|
2169
|
+
case "failed":
|
|
2170
|
+
case "stopped":
|
|
2171
|
+
return "ready";
|
|
2172
|
+
case "completed":
|
|
2173
|
+
return "completed";
|
|
2174
|
+
case null:
|
|
2175
|
+
case undefined:
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2092
2179
|
var STATUS_PRIORITY = [
|
|
2093
2180
|
"running",
|
|
2094
2181
|
"blocked",
|
|
@@ -2100,11 +2187,31 @@ var STATUS_PRIORITY = [
|
|
|
2100
2187
|
"cancelled",
|
|
2101
2188
|
"completed"
|
|
2102
2189
|
];
|
|
2103
|
-
function
|
|
2104
|
-
|
|
2190
|
+
function taskIdFromSession(session) {
|
|
2191
|
+
return session.taskId ?? session.record.taskId ?? null;
|
|
2192
|
+
}
|
|
2193
|
+
function latestSessionByTask(sessions) {
|
|
2194
|
+
const byTask = new Map;
|
|
2195
|
+
for (const session of sessions) {
|
|
2196
|
+
const taskId = taskIdFromSession(session);
|
|
2197
|
+
if (!taskId)
|
|
2198
|
+
continue;
|
|
2199
|
+
const existing = byTask.get(taskId);
|
|
2200
|
+
if (!existing || (session.lastEventAt ?? "").localeCompare(existing.lastEventAt ?? "") >= 0) {
|
|
2201
|
+
byTask.set(taskId, session);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return byTask;
|
|
2205
|
+
}
|
|
2206
|
+
function projectTaskStatusWithSessions(task, sessionsByTask = new Map) {
|
|
2207
|
+
const sessionStatus = projectRunStatusForTaskGrouping(sessionsByTask.get(task.id)?.status);
|
|
2208
|
+
return sessionStatus ?? projectTaskStatusForGrouping(task.status);
|
|
2209
|
+
}
|
|
2210
|
+
function groupTasksByProjectedStatus(tasks, sessions = []) {
|
|
2211
|
+
const sessionsByTask = latestSessionByTask(sessions);
|
|
2105
2212
|
const byStatus = new Map;
|
|
2106
2213
|
for (const task of tasks) {
|
|
2107
|
-
const projectedStatus =
|
|
2214
|
+
const projectedStatus = projectTaskStatusWithSessions(task, sessionsByTask);
|
|
2108
2215
|
const group = byStatus.get(projectedStatus);
|
|
2109
2216
|
if (group) {
|
|
2110
2217
|
group.push(task);
|
|
@@ -2128,6 +2235,69 @@ function selectTasksGroupedByStatus(snapshot, workspaceId) {
|
|
|
2128
2235
|
}
|
|
2129
2236
|
return result;
|
|
2130
2237
|
}
|
|
2238
|
+
function isProjectionGroupingInput(value) {
|
|
2239
|
+
return Boolean(value && typeof value === "object" && !("snapshotSequence" in value) && Array.isArray(value.tasks));
|
|
2240
|
+
}
|
|
2241
|
+
function selectTasksGroupedByStatus(input, workspaceId) {
|
|
2242
|
+
if (isProjectionGroupingInput(input)) {
|
|
2243
|
+
const filteredTasks = input.workspaceId ? input.tasks.filter((task) => {
|
|
2244
|
+
const workspaceTask = task;
|
|
2245
|
+
return workspaceTask.workspaceId === input.workspaceId;
|
|
2246
|
+
}) : input.tasks;
|
|
2247
|
+
return groupTasksByProjectedStatus(filteredTasks, input.sessions);
|
|
2248
|
+
}
|
|
2249
|
+
const tasks = selectTasksByWorkspace(input, workspaceId ?? null);
|
|
2250
|
+
return groupTasksByProjectedStatus(tasks, []);
|
|
2251
|
+
}
|
|
2252
|
+
function isObjectRecord(value) {
|
|
2253
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2254
|
+
}
|
|
2255
|
+
function normalizeLogin(value) {
|
|
2256
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
2257
|
+
}
|
|
2258
|
+
function assigneeLoginsFromValue(value) {
|
|
2259
|
+
if (!Array.isArray(value))
|
|
2260
|
+
return [];
|
|
2261
|
+
return value.flatMap((entry) => {
|
|
2262
|
+
if (typeof entry === "string" && entry.trim())
|
|
2263
|
+
return [normalizeLogin(entry)];
|
|
2264
|
+
if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
2265
|
+
return [normalizeLogin(entry.login)];
|
|
2266
|
+
}
|
|
2267
|
+
return [];
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
function normalizeTaskAssigneeFilter(assignee, currentUserLogin) {
|
|
2271
|
+
const trimmed = assignee?.trim();
|
|
2272
|
+
if (!trimmed)
|
|
2273
|
+
return null;
|
|
2274
|
+
if (trimmed === "@me" || trimmed.toLowerCase() === "me") {
|
|
2275
|
+
return currentUserLogin?.trim() ? normalizeLogin(currentUserLogin) : null;
|
|
2276
|
+
}
|
|
2277
|
+
return normalizeLogin(trimmed);
|
|
2278
|
+
}
|
|
2279
|
+
function readTaskAssigneeLogins(task) {
|
|
2280
|
+
const taskRecord = task;
|
|
2281
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2282
|
+
const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
|
|
2283
|
+
return Array.from(new Set([
|
|
2284
|
+
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
2285
|
+
...assigneeLoginsFromValue(metadata?.assignees),
|
|
2286
|
+
...assigneeLoginsFromValue(raw?.assignees)
|
|
2287
|
+
]));
|
|
2288
|
+
}
|
|
2289
|
+
function taskMatchesAssigneeFilter(task, assignee, options = {}) {
|
|
2290
|
+
const normalized = normalizeTaskAssigneeFilter(assignee, options.currentUserLogin);
|
|
2291
|
+
if (!normalized)
|
|
2292
|
+
return false;
|
|
2293
|
+
return readTaskAssigneeLogins(task).includes(normalized);
|
|
2294
|
+
}
|
|
2295
|
+
function selectTasksAssignedTo(tasks, assignee, options = {}) {
|
|
2296
|
+
return tasks.filter((task) => taskMatchesAssigneeFilter(task, assignee, options));
|
|
2297
|
+
}
|
|
2298
|
+
function selectTasksAssignedToMe(tasks, currentUserLogin) {
|
|
2299
|
+
return selectTasksAssignedTo(tasks, "@me", currentUserLogin === undefined ? {} : { currentUserLogin });
|
|
2300
|
+
}
|
|
2131
2301
|
function selectRun(snapshot, runId) {
|
|
2132
2302
|
if (!runId)
|
|
2133
2303
|
return null;
|
|
@@ -2189,17 +2359,45 @@ function selectUserInputsForWorkspace(snapshot, workspaceId) {
|
|
|
2189
2359
|
return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
2190
2360
|
}
|
|
2191
2361
|
// packages/core/src/taskGraph.ts
|
|
2192
|
-
function
|
|
2362
|
+
function isObjectRecord2(value) {
|
|
2193
2363
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2194
2364
|
}
|
|
2195
|
-
function
|
|
2196
|
-
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2197
|
-
const value = metadata?.[key];
|
|
2365
|
+
function readStringList(value) {
|
|
2198
2366
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2199
2367
|
}
|
|
2368
|
+
function unique(values) {
|
|
2369
|
+
return Array.from(new Set(values));
|
|
2370
|
+
}
|
|
2371
|
+
function readTaskMetadataStringList(task, key) {
|
|
2372
|
+
const taskRecord = task;
|
|
2373
|
+
const topLevel = readStringList(taskRecord[key]);
|
|
2374
|
+
if (topLevel.length > 0)
|
|
2375
|
+
return topLevel;
|
|
2376
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2377
|
+
const metadataList = readStringList(metadata?.[key]);
|
|
2378
|
+
if (metadataList.length > 0)
|
|
2379
|
+
return metadataList;
|
|
2380
|
+
if (key === "dependencies") {
|
|
2381
|
+
return readStringList(metadata?.deps);
|
|
2382
|
+
}
|
|
2383
|
+
return [];
|
|
2384
|
+
}
|
|
2385
|
+
function readTaskDependencyRefs(task) {
|
|
2386
|
+
return unique([
|
|
2387
|
+
...readTaskMetadataStringList(task, "dependencies"),
|
|
2388
|
+
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
2389
|
+
]);
|
|
2390
|
+
}
|
|
2200
2391
|
function readTaskSourceIssueId(task) {
|
|
2201
|
-
|
|
2202
|
-
|
|
2392
|
+
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
2393
|
+
return task.sourceIssueId;
|
|
2394
|
+
}
|
|
2395
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2396
|
+
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
2397
|
+
return metadata.sourceIssueId;
|
|
2398
|
+
}
|
|
2399
|
+
const rigMetadata = isObjectRecord2(metadata?._rig) ? metadata._rig : null;
|
|
2400
|
+
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
2203
2401
|
}
|
|
2204
2402
|
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
2205
2403
|
if (tasksById.has(ref))
|
|
@@ -2229,11 +2427,7 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
2229
2427
|
if (!task)
|
|
2230
2428
|
return 0;
|
|
2231
2429
|
stack.add(taskId);
|
|
2232
|
-
const
|
|
2233
|
-
...readTaskMetadataStringList(task, "dependencies"),
|
|
2234
|
-
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
2235
|
-
];
|
|
2236
|
-
const blockers = refs.map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
2430
|
+
const blockers = readTaskDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
2237
2431
|
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
2238
2432
|
stack.delete(taskId);
|
|
2239
2433
|
memo.set(taskId, depth);
|
|
@@ -2244,6 +2438,134 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
2244
2438
|
}
|
|
2245
2439
|
return memo;
|
|
2246
2440
|
}
|
|
2441
|
+
function isTaskTerminalStatus(status) {
|
|
2442
|
+
switch (status) {
|
|
2443
|
+
case "closed":
|
|
2444
|
+
case "completed":
|
|
2445
|
+
case "done":
|
|
2446
|
+
case "cancelled":
|
|
2447
|
+
case "canceled":
|
|
2448
|
+
return true;
|
|
2449
|
+
default:
|
|
2450
|
+
return false;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function isTaskBlockedStatus(status) {
|
|
2454
|
+
return status === "blocked";
|
|
2455
|
+
}
|
|
2456
|
+
function isTaskRunnableStatus(status) {
|
|
2457
|
+
if (status === null || status === undefined || status === "")
|
|
2458
|
+
return true;
|
|
2459
|
+
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
2460
|
+
return false;
|
|
2461
|
+
switch (status) {
|
|
2462
|
+
case "ready":
|
|
2463
|
+
case "open":
|
|
2464
|
+
case "failed":
|
|
2465
|
+
return true;
|
|
2466
|
+
default:
|
|
2467
|
+
return false;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function priorityValue(task) {
|
|
2471
|
+
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
2472
|
+
}
|
|
2473
|
+
function computeTaskDependencyBadges(tasks) {
|
|
2474
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
2475
|
+
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
2476
|
+
const dependencyIdsByTask = new Map;
|
|
2477
|
+
const unresolvedRefsByTask = new Map;
|
|
2478
|
+
const blocksByTask = new Map;
|
|
2479
|
+
for (const task of tasks) {
|
|
2480
|
+
const dependencyIds = [];
|
|
2481
|
+
const unresolvedRefs = [];
|
|
2482
|
+
for (const ref of readTaskDependencyRefs(task)) {
|
|
2483
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
2484
|
+
if (dependencyId && dependencyId !== task.id) {
|
|
2485
|
+
dependencyIds.push(dependencyId);
|
|
2486
|
+
const blocks = blocksByTask.get(dependencyId);
|
|
2487
|
+
if (blocks) {
|
|
2488
|
+
blocks.push(task.id);
|
|
2489
|
+
} else {
|
|
2490
|
+
blocksByTask.set(dependencyId, [task.id]);
|
|
2491
|
+
}
|
|
2492
|
+
} else {
|
|
2493
|
+
unresolvedRefs.push(ref);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
2497
|
+
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
2498
|
+
}
|
|
2499
|
+
const summaries = new Map;
|
|
2500
|
+
for (const task of tasks) {
|
|
2501
|
+
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
2502
|
+
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
2503
|
+
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
2504
|
+
const dependency = index.tasksById.get(dependencyId);
|
|
2505
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
2506
|
+
});
|
|
2507
|
+
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
2508
|
+
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
2509
|
+
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
2510
|
+
const badges = [];
|
|
2511
|
+
if (blocked) {
|
|
2512
|
+
badges.push({
|
|
2513
|
+
kind: "blocked",
|
|
2514
|
+
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
2515
|
+
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
2516
|
+
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
2517
|
+
taskIds: blockedBy
|
|
2518
|
+
});
|
|
2519
|
+
} else if (ready) {
|
|
2520
|
+
badges.push({
|
|
2521
|
+
kind: "ready",
|
|
2522
|
+
label: "ready",
|
|
2523
|
+
description: "No open dependencies block this task."
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
2527
|
+
badges.push({
|
|
2528
|
+
kind: "dependency",
|
|
2529
|
+
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
2530
|
+
description: [
|
|
2531
|
+
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
2532
|
+
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
2533
|
+
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
2534
|
+
].filter((part) => part !== null).join(" "),
|
|
2535
|
+
count: dependencyIds.length + blocks.length,
|
|
2536
|
+
taskIds: unique([...dependencyIds, ...blocks])
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
summaries.set(task.id, {
|
|
2540
|
+
taskId: task.id,
|
|
2541
|
+
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
2542
|
+
dependencyIds,
|
|
2543
|
+
unresolvedDependencyRefs,
|
|
2544
|
+
blockedBy,
|
|
2545
|
+
blocks,
|
|
2546
|
+
blocked,
|
|
2547
|
+
ready,
|
|
2548
|
+
dependencyCount: dependencyIds.length,
|
|
2549
|
+
dependentCount: blocks.length,
|
|
2550
|
+
badges
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
return summaries;
|
|
2554
|
+
}
|
|
2555
|
+
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
2556
|
+
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
2557
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
2558
|
+
const candidates = tasks.filter((task) => !excluded.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true).toSorted((left, right) => {
|
|
2559
|
+
const priorityDelta = priorityValue(left) - priorityValue(right);
|
|
2560
|
+
if (priorityDelta !== 0)
|
|
2561
|
+
return priorityDelta;
|
|
2562
|
+
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
2563
|
+
if (createdDelta !== 0)
|
|
2564
|
+
return createdDelta;
|
|
2565
|
+
return left.id.localeCompare(right.id);
|
|
2566
|
+
});
|
|
2567
|
+
return candidates[0] ?? null;
|
|
2568
|
+
}
|
|
2247
2569
|
// packages/core/src/taskGraphCodes.ts
|
|
2248
2570
|
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
2249
2571
|
function extractTaskCode(title) {
|
|
@@ -2283,11 +2605,11 @@ var PALETTE = [
|
|
|
2283
2605
|
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
2284
2606
|
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
2285
2607
|
];
|
|
2286
|
-
function
|
|
2608
|
+
function isObjectRecord3(value) {
|
|
2287
2609
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2288
2610
|
}
|
|
2289
2611
|
function readIssueType(task) {
|
|
2290
|
-
const metadata =
|
|
2612
|
+
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
2291
2613
|
return typeof metadata?.issueType === "string" ? metadata.issueType : null;
|
|
2292
2614
|
}
|
|
2293
2615
|
function isGraphTask(task) {
|
|
@@ -2582,6 +2904,8 @@ export {
|
|
|
2582
2904
|
selectTasksForWorkspace,
|
|
2583
2905
|
selectTasksByWorkspace,
|
|
2584
2906
|
selectTasksByStatus,
|
|
2907
|
+
selectTasksAssignedToMe,
|
|
2908
|
+
selectTasksAssignedTo,
|
|
2585
2909
|
selectTask,
|
|
2586
2910
|
selectRunsForWorkspace,
|
|
2587
2911
|
selectRunsForTask,
|
|
@@ -2590,6 +2914,7 @@ export {
|
|
|
2590
2914
|
selectQueueForWorkspace,
|
|
2591
2915
|
selectPrimaryWorkspace,
|
|
2592
2916
|
selectPendingApprovals,
|
|
2917
|
+
selectNextReadyTaskByPriority,
|
|
2593
2918
|
selectGraphsForWorkspace,
|
|
2594
2919
|
selectApprovalsForWorkspace,
|
|
2595
2920
|
selectApprovalsForRun,
|
|
@@ -2598,13 +2923,21 @@ export {
|
|
|
2598
2923
|
resolveTaskReference,
|
|
2599
2924
|
readTaskSourceIssueId,
|
|
2600
2925
|
readTaskMetadataStringList,
|
|
2926
|
+
readTaskDependencyRefs,
|
|
2927
|
+
readTaskAssigneeLogins,
|
|
2601
2928
|
pruneQueueEntries,
|
|
2929
|
+
projectTaskStatusWithSessions,
|
|
2930
|
+
projectTaskStatusForGrouping,
|
|
2931
|
+
projectRunStatusForTaskGrouping,
|
|
2602
2932
|
pickDefaultWorkspaceId,
|
|
2933
|
+
normalizeTaskAssigneeFilter,
|
|
2934
|
+
isTaskTerminalStatus,
|
|
2603
2935
|
extractTaskGroupKey,
|
|
2604
2936
|
extractTaskCode,
|
|
2605
2937
|
definePlugin,
|
|
2606
2938
|
defineConfig,
|
|
2607
2939
|
createPluginHost,
|
|
2940
|
+
computeTaskDependencyBadges,
|
|
2608
2941
|
computeTaskBlockingDepths,
|
|
2609
2942
|
buildTaskReferenceIndex,
|
|
2610
2943
|
buildTaskGraphLayout,
|