@h-rig/core 0.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/src/define-config.js +24 -0
- package/dist/src/define-plugin.js +50 -0
- package/dist/src/engineReadModelReducer.js +1780 -0
- package/dist/src/index.js +2616 -0
- package/dist/src/load-config.js +49 -0
- package/dist/src/plugin-host.js +158 -0
- package/dist/src/plugin-runtime.js +1 -0
- package/dist/src/rig-init-builder.js +57 -0
- package/dist/src/rigSelectors.js +293 -0
- package/dist/src/taskGraph.js +64 -0
- package/dist/src/taskGraphCodes.js +26 -0
- package/dist/src/taskGraphLayout.js +374 -0
- package/package.json +37 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/load-config.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
import { Schema } from "effect";
|
|
7
|
+
import { RigConfig } from "@rig/contracts";
|
|
8
|
+
var TS_NAMES = ["rig.config.ts", "rig.config.mts"];
|
|
9
|
+
var JSON_NAMES = ["rig.config.json"];
|
|
10
|
+
async function loadConfig(cwd) {
|
|
11
|
+
for (const name of TS_NAMES) {
|
|
12
|
+
const p = join(cwd, name);
|
|
13
|
+
if (existsSync(p)) {
|
|
14
|
+
const mod = await import(pathToFileURL(p).href);
|
|
15
|
+
const raw = mod.default ?? mod.config;
|
|
16
|
+
return decodePreservingRuntime(raw);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const name of JSON_NAMES) {
|
|
20
|
+
const p = join(cwd, name);
|
|
21
|
+
if (existsSync(p)) {
|
|
22
|
+
const raw = JSON.parse(readFileSync(p, "utf-8"));
|
|
23
|
+
return decodePreservingRuntime(raw);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`no rig.config.{ts,mts,json} found in ${cwd}`);
|
|
27
|
+
}
|
|
28
|
+
function decodePreservingRuntime(raw) {
|
|
29
|
+
const runtimeByName = new Map;
|
|
30
|
+
const cfgIn = raw;
|
|
31
|
+
if (Array.isArray(cfgIn?.plugins)) {
|
|
32
|
+
for (const plugin of cfgIn.plugins) {
|
|
33
|
+
if (plugin?.__runtime) {
|
|
34
|
+
runtimeByName.set(plugin.name, plugin.__runtime);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const decoded = Schema.decodeUnknownSync(RigConfig)(raw);
|
|
39
|
+
const plugins = decoded.plugins.map((p) => {
|
|
40
|
+
const runtime = runtimeByName.get(p.name);
|
|
41
|
+
if (!runtime)
|
|
42
|
+
return p;
|
|
43
|
+
return { ...p, __runtime: runtime };
|
|
44
|
+
});
|
|
45
|
+
return { ...decoded, plugins };
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
loadConfig
|
|
49
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/plugin-host.ts
|
|
3
|
+
function indexById(contributions, kind) {
|
|
4
|
+
const map = new Map;
|
|
5
|
+
const registrant = new Map;
|
|
6
|
+
for (const { item, pluginName } of contributions) {
|
|
7
|
+
if (map.has(item.id)) {
|
|
8
|
+
throw new Error(`duplicate ${kind} id "${item.id}": registered by plugins "${registrant.get(item.id)}" and "${pluginName}"`);
|
|
9
|
+
}
|
|
10
|
+
map.set(item.id, item);
|
|
11
|
+
registrant.set(item.id, pluginName);
|
|
12
|
+
}
|
|
13
|
+
return map;
|
|
14
|
+
}
|
|
15
|
+
function assertUniquePluginNames(plugins) {
|
|
16
|
+
const seen = new Set;
|
|
17
|
+
for (const plugin of plugins) {
|
|
18
|
+
if (seen.has(plugin.name)) {
|
|
19
|
+
throw new Error(`duplicate plugin name "${plugin.name}"`);
|
|
20
|
+
}
|
|
21
|
+
seen.add(plugin.name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function assertRuntimeMatchesMetadata(plugin) {
|
|
25
|
+
const declaredValidators = new Map((plugin.contributes?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
26
|
+
const runtimeValidators = new Map((plugin.__runtime?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
27
|
+
for (const validator of runtimeValidators.values()) {
|
|
28
|
+
const metadata = declaredValidators.get(validator.id);
|
|
29
|
+
if (!metadata) {
|
|
30
|
+
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" has no matching metadata entry in contributes.validators`);
|
|
31
|
+
}
|
|
32
|
+
if (metadata.category !== validator.category) {
|
|
33
|
+
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" category "${validator.category}" does not match metadata category "${metadata.category}"`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (plugin.__runtime?.validators) {
|
|
37
|
+
for (const validator of declaredValidators.values()) {
|
|
38
|
+
if (!runtimeValidators.has(validator.id)) {
|
|
39
|
+
throw new Error(`plugin "${plugin.name}" validator metadata "${validator.id}" has no runtime implementation`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const declaredTaskSources = new Map((plugin.contributes?.taskSources ?? []).map((source) => [source.id, source]));
|
|
44
|
+
const runtimeTaskSources = new Map((plugin.__runtime?.taskSources ?? []).map((source) => [source.id, source]));
|
|
45
|
+
for (const source of runtimeTaskSources.values()) {
|
|
46
|
+
const metadata = declaredTaskSources.get(source.id);
|
|
47
|
+
if (!metadata) {
|
|
48
|
+
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" has no matching metadata entry in contributes.taskSources`);
|
|
49
|
+
}
|
|
50
|
+
if (metadata.kind !== source.kind) {
|
|
51
|
+
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" kind "${source.kind}" does not match metadata kind "${metadata.kind}"`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (plugin.__runtime?.taskSources) {
|
|
55
|
+
for (const source of declaredTaskSources.values()) {
|
|
56
|
+
if (!runtimeTaskSources.has(source.id)) {
|
|
57
|
+
throw new Error(`plugin "${plugin.name}" task source metadata "${source.id}" has no runtime implementation`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function createPluginHost(plugins) {
|
|
63
|
+
assertUniquePluginNames(plugins);
|
|
64
|
+
for (const plugin of plugins) {
|
|
65
|
+
assertRuntimeMatchesMetadata(plugin);
|
|
66
|
+
}
|
|
67
|
+
const validators = [];
|
|
68
|
+
const hooks = [];
|
|
69
|
+
const skills = [];
|
|
70
|
+
const repoSources = [];
|
|
71
|
+
const agentRoles = [];
|
|
72
|
+
const taskFieldExtensions = [];
|
|
73
|
+
const taskSources = [];
|
|
74
|
+
const cliCommands = [];
|
|
75
|
+
const executableValidators = [];
|
|
76
|
+
const executableTaskSources = [];
|
|
77
|
+
for (const plugin of plugins) {
|
|
78
|
+
const c = plugin.contributes;
|
|
79
|
+
if (!c)
|
|
80
|
+
continue;
|
|
81
|
+
const pluginName = plugin.name;
|
|
82
|
+
if (plugin.__runtime?.validators) {
|
|
83
|
+
executableValidators.push(...plugin.__runtime.validators.map((item) => ({ item, pluginName })));
|
|
84
|
+
}
|
|
85
|
+
if (plugin.__runtime?.taskSources) {
|
|
86
|
+
executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
|
|
87
|
+
}
|
|
88
|
+
if (c.validators)
|
|
89
|
+
validators.push(...c.validators.map((item) => ({ item, pluginName })));
|
|
90
|
+
if (c.hooks)
|
|
91
|
+
hooks.push(...c.hooks.map((item) => ({ item, pluginName })));
|
|
92
|
+
if (c.skills)
|
|
93
|
+
skills.push(...c.skills.map((item) => ({ item, pluginName })));
|
|
94
|
+
if (c.repoSources)
|
|
95
|
+
repoSources.push(...c.repoSources.map((item) => ({ item, pluginName })));
|
|
96
|
+
if (c.agentRoles)
|
|
97
|
+
agentRoles.push(...c.agentRoles.map((item) => ({ item, pluginName })));
|
|
98
|
+
if (c.taskFieldSchemas)
|
|
99
|
+
taskFieldExtensions.push(...c.taskFieldSchemas.map((item) => ({ item, pluginName })));
|
|
100
|
+
if (c.taskSources)
|
|
101
|
+
taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
|
|
102
|
+
if (c.cliCommands)
|
|
103
|
+
cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
|
|
104
|
+
}
|
|
105
|
+
indexById(executableValidators, "executableValidator");
|
|
106
|
+
indexById(executableTaskSources, "executableTaskSource");
|
|
107
|
+
const taskSourceFactoryByKind = new Map;
|
|
108
|
+
const taskSourceKindRegistrant = new Map;
|
|
109
|
+
for (const { item, pluginName } of executableTaskSources) {
|
|
110
|
+
if (taskSourceFactoryByKind.has(item.kind)) {
|
|
111
|
+
throw new Error(`duplicate task source kind "${item.kind}": registered by plugins "${taskSourceKindRegistrant.get(item.kind)}" and "${pluginName}"`);
|
|
112
|
+
}
|
|
113
|
+
taskSourceFactoryByKind.set(item.kind, item);
|
|
114
|
+
taskSourceKindRegistrant.set(item.kind, pluginName);
|
|
115
|
+
}
|
|
116
|
+
const validatorMap = indexById(validators, "validator");
|
|
117
|
+
const hookMap = indexById(hooks, "hook");
|
|
118
|
+
const skillMap = indexById(skills, "skill");
|
|
119
|
+
const repoSourceMap = indexById(repoSources, "repoSource");
|
|
120
|
+
const agentRoleMap = indexById(agentRoles, "agentRole");
|
|
121
|
+
const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
|
|
122
|
+
const taskSourceMap = indexById(taskSources, "taskSource");
|
|
123
|
+
const cliCommandMap = indexById(cliCommands, "cliCommand");
|
|
124
|
+
const allValidators = validators.map((c) => c.item);
|
|
125
|
+
const allHooks = hooks.map((c) => c.item);
|
|
126
|
+
const allSkills = skills.map((c) => c.item);
|
|
127
|
+
const allRepoSources = repoSources.map((c) => c.item);
|
|
128
|
+
const allAgentRoles = agentRoles.map((c) => c.item);
|
|
129
|
+
const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
|
|
130
|
+
const allTaskSources = taskSources.map((c) => c.item);
|
|
131
|
+
const allCliCommands = cliCommands.map((c) => c.item);
|
|
132
|
+
const allExecutableValidators = executableValidators.map((c) => c.item);
|
|
133
|
+
const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
|
|
134
|
+
return {
|
|
135
|
+
getValidator: (id) => validatorMap.get(id),
|
|
136
|
+
getHook: (id) => hookMap.get(id),
|
|
137
|
+
getSkill: (id) => skillMap.get(id),
|
|
138
|
+
getRepoSource: (id) => repoSourceMap.get(id),
|
|
139
|
+
getAgentRole: (id) => agentRoleMap.get(id),
|
|
140
|
+
getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
|
|
141
|
+
getTaskSource: (id) => taskSourceMap.get(id),
|
|
142
|
+
getCliCommand: (id) => cliCommandMap.get(id),
|
|
143
|
+
listValidators: () => allValidators,
|
|
144
|
+
listHooks: () => allHooks,
|
|
145
|
+
listSkills: () => allSkills,
|
|
146
|
+
listRepoSources: () => allRepoSources,
|
|
147
|
+
listAgentRoles: () => allAgentRoles,
|
|
148
|
+
listTaskFieldExtensions: () => allTaskFieldExtensions,
|
|
149
|
+
listTaskSources: () => allTaskSources,
|
|
150
|
+
listCliCommands: () => allCliCommands,
|
|
151
|
+
listExecutableValidators: () => allExecutableValidators,
|
|
152
|
+
listExecutableTaskSources: () => allExecutableTaskSources,
|
|
153
|
+
resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
createPluginHost
|
|
158
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @bun
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/rig-init-builder.ts
|
|
3
|
+
function buildRigInitConfigSource(input) {
|
|
4
|
+
const lines = [`import { defineConfig } from "@rig/core";`];
|
|
5
|
+
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
6
|
+
lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
7
|
+
} else if (input.useStandardPlugin) {
|
|
8
|
+
lines.push(`import standard from "@rig/standard-plugin";`);
|
|
9
|
+
}
|
|
10
|
+
lines.push(``, `export default defineConfig({`);
|
|
11
|
+
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
12
|
+
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
13
|
+
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
14
|
+
lines.push(` plugins: [standard({`);
|
|
15
|
+
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
16
|
+
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
17
|
+
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
18
|
+
lines.push(` })],`);
|
|
19
|
+
} else {
|
|
20
|
+
lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
|
|
21
|
+
}
|
|
22
|
+
if (input.taskSource.kind === "github-issues") {
|
|
23
|
+
lines.push(` taskSource: {`);
|
|
24
|
+
lines.push(` kind: "github-issues",`);
|
|
25
|
+
lines.push(` owner: ${JSON.stringify(input.taskSource.owner)},`);
|
|
26
|
+
lines.push(` repo: ${JSON.stringify(input.taskSource.repo)},`);
|
|
27
|
+
lines.push(` // labels: ["task"], // uncomment to filter by labels`);
|
|
28
|
+
lines.push(` state: "open",`);
|
|
29
|
+
if (input.taskSource.assignee?.trim()) {
|
|
30
|
+
lines.push(` options: { assignee: ${JSON.stringify(input.taskSource.assignee.trim())} },`);
|
|
31
|
+
}
|
|
32
|
+
lines.push(` },`);
|
|
33
|
+
} else {
|
|
34
|
+
lines.push(` taskSource: {`);
|
|
35
|
+
lines.push(` kind: "files",`);
|
|
36
|
+
lines.push(` path: ${JSON.stringify(input.taskSource.path)},`);
|
|
37
|
+
lines.push(` },`);
|
|
38
|
+
}
|
|
39
|
+
lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
|
|
40
|
+
lines.push(` runtime: { harness: "pi", mode: "yolo" },`);
|
|
41
|
+
lines.push(` planning: { mode: "auto" },`);
|
|
42
|
+
lines.push(` github: {`);
|
|
43
|
+
lines.push(` issueUpdates: "lifecycle",`);
|
|
44
|
+
lines.push(` projects: { enabled: false },`);
|
|
45
|
+
lines.push(` },`);
|
|
46
|
+
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations: 30 },`);
|
|
47
|
+
lines.push(` pr: { mode: "auto", watchChecks: true, autoFixChecks: true, autoFixReview: true },`);
|
|
48
|
+
lines.push(` merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },`);
|
|
49
|
+
lines.push(` issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" },`);
|
|
50
|
+
lines.push(`});`);
|
|
51
|
+
lines.push(``);
|
|
52
|
+
return lines.join(`
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
buildRigInitConfigSource
|
|
57
|
+
};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/rigSelectors.ts
|
|
3
|
+
function selectWorkspaces(snapshot) {
|
|
4
|
+
return snapshot?.workspaces ?? [];
|
|
5
|
+
}
|
|
6
|
+
function selectPrimaryWorkspace(snapshot) {
|
|
7
|
+
const workspaces = selectWorkspaces(snapshot);
|
|
8
|
+
return workspaces.find((workspace) => workspace.sourceKind === "rig-import") ?? workspaces[0] ?? null;
|
|
9
|
+
}
|
|
10
|
+
function pickDefaultWorkspaceId(snapshot) {
|
|
11
|
+
return selectPrimaryWorkspace(snapshot)?.id ?? null;
|
|
12
|
+
}
|
|
13
|
+
function selectWorkspace(snapshot, workspaceId) {
|
|
14
|
+
if (!workspaceId)
|
|
15
|
+
return null;
|
|
16
|
+
return snapshot?.workspaces.find((workspace) => workspace.id === workspaceId) ?? null;
|
|
17
|
+
}
|
|
18
|
+
function selectTask(snapshot, taskId) {
|
|
19
|
+
if (!taskId)
|
|
20
|
+
return null;
|
|
21
|
+
return snapshot?.tasks.find((task) => task.id === taskId) ?? null;
|
|
22
|
+
}
|
|
23
|
+
function selectTasksByWorkspace(snapshot, workspaceId) {
|
|
24
|
+
if (!workspaceId)
|
|
25
|
+
return [];
|
|
26
|
+
return snapshot?.tasks.filter((task) => task.workspaceId === workspaceId) ?? [];
|
|
27
|
+
}
|
|
28
|
+
var selectTasksForWorkspace = selectTasksByWorkspace;
|
|
29
|
+
function selectTasksByStatus(snapshot, status) {
|
|
30
|
+
return snapshot?.tasks.filter((task) => task.status === status) ?? [];
|
|
31
|
+
}
|
|
32
|
+
function projectTaskStatusForGrouping(status) {
|
|
33
|
+
switch (status) {
|
|
34
|
+
case "failed":
|
|
35
|
+
return "ready";
|
|
36
|
+
case "closed":
|
|
37
|
+
return "completed";
|
|
38
|
+
case "running":
|
|
39
|
+
case "in_progress":
|
|
40
|
+
case "under_review":
|
|
41
|
+
return "running";
|
|
42
|
+
default:
|
|
43
|
+
return status;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var STATUS_PRIORITY = [
|
|
47
|
+
"running",
|
|
48
|
+
"blocked",
|
|
49
|
+
"queued",
|
|
50
|
+
"ready",
|
|
51
|
+
"open",
|
|
52
|
+
"draft",
|
|
53
|
+
"unknown",
|
|
54
|
+
"cancelled",
|
|
55
|
+
"completed"
|
|
56
|
+
];
|
|
57
|
+
function selectTasksGroupedByStatus(snapshot, workspaceId) {
|
|
58
|
+
const tasks = selectTasksByWorkspace(snapshot, workspaceId);
|
|
59
|
+
const byStatus = new Map;
|
|
60
|
+
for (const task of tasks) {
|
|
61
|
+
const projectedStatus = projectTaskStatusForGrouping(task.status);
|
|
62
|
+
const group = byStatus.get(projectedStatus);
|
|
63
|
+
if (group) {
|
|
64
|
+
group.push(task);
|
|
65
|
+
} else {
|
|
66
|
+
byStatus.set(projectedStatus, [task]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const result = [];
|
|
70
|
+
for (const status of STATUS_PRIORITY) {
|
|
71
|
+
const group = byStatus.get(status);
|
|
72
|
+
if (group && group.length > 0) {
|
|
73
|
+
result.push({ status, tasks: group });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const overflowStatuses = Array.from(byStatus.keys()).filter((status) => !STATUS_PRIORITY.includes(status)).sort();
|
|
77
|
+
for (const status of overflowStatuses) {
|
|
78
|
+
const group = byStatus.get(status);
|
|
79
|
+
if (group && group.length > 0) {
|
|
80
|
+
result.push({ status, tasks: group });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
function selectRun(snapshot, runId) {
|
|
86
|
+
if (!runId)
|
|
87
|
+
return null;
|
|
88
|
+
return snapshot?.runs.find((run) => run.id === runId) ?? null;
|
|
89
|
+
}
|
|
90
|
+
function selectRunsByTask(snapshot, taskId) {
|
|
91
|
+
if (!taskId)
|
|
92
|
+
return [];
|
|
93
|
+
return snapshot?.runs.filter((run) => run.taskId === taskId) ?? [];
|
|
94
|
+
}
|
|
95
|
+
var selectRunsForTask = selectRunsByTask;
|
|
96
|
+
function selectRunsForWorkspace(snapshot, workspaceId) {
|
|
97
|
+
if (!workspaceId)
|
|
98
|
+
return [];
|
|
99
|
+
return snapshot?.runs.filter((run) => run.workspaceId === workspaceId) ?? [];
|
|
100
|
+
}
|
|
101
|
+
function selectAdhocRuns(snapshot) {
|
|
102
|
+
return snapshot?.runs.filter((run) => run.taskId === null) ?? [];
|
|
103
|
+
}
|
|
104
|
+
function selectAdhocRunsForWorkspace(snapshot, workspaceId) {
|
|
105
|
+
if (!workspaceId)
|
|
106
|
+
return [];
|
|
107
|
+
return snapshot?.runs.filter((run) => run.taskId === null && run.workspaceId === workspaceId) ?? [];
|
|
108
|
+
}
|
|
109
|
+
function selectGraphsForWorkspace(snapshot, workspaceId) {
|
|
110
|
+
if (!workspaceId)
|
|
111
|
+
return [];
|
|
112
|
+
return snapshot?.graphs.filter((graph) => graph.workspaceId === workspaceId) ?? [];
|
|
113
|
+
}
|
|
114
|
+
function selectQueueForWorkspace(snapshot, workspaceId) {
|
|
115
|
+
if (!snapshot || !workspaceId)
|
|
116
|
+
return [];
|
|
117
|
+
const taskIds = new Set(selectTasksByWorkspace(snapshot, workspaceId).map((task) => task.id));
|
|
118
|
+
return snapshot.queue.filter((entry) => taskIds.has(entry.taskId));
|
|
119
|
+
}
|
|
120
|
+
function selectPendingApprovals(snapshot) {
|
|
121
|
+
return snapshot?.approvals.filter((approval) => approval.status === "pending") ?? [];
|
|
122
|
+
}
|
|
123
|
+
function selectApprovalsForRun(snapshot, runId) {
|
|
124
|
+
if (!runId)
|
|
125
|
+
return [];
|
|
126
|
+
return snapshot?.approvals.filter((approval) => approval.runId === runId) ?? [];
|
|
127
|
+
}
|
|
128
|
+
function selectPendingApprovalsForRun(snapshot, runId) {
|
|
129
|
+
return selectApprovalsForRun(snapshot, runId).filter((approval) => approval.status === "pending");
|
|
130
|
+
}
|
|
131
|
+
function selectApprovalsForWorkspace(snapshot, workspaceId) {
|
|
132
|
+
if (!snapshot || !workspaceId)
|
|
133
|
+
return [];
|
|
134
|
+
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
135
|
+
return snapshot.approvals.filter((approval) => runIds.has(approval.runId));
|
|
136
|
+
}
|
|
137
|
+
function selectUserInputsForRun(snapshot, runId) {
|
|
138
|
+
if (!runId)
|
|
139
|
+
return [];
|
|
140
|
+
return (snapshot?.userInputs ?? []).filter((request) => request.runId === runId);
|
|
141
|
+
}
|
|
142
|
+
function selectPendingUserInputs(snapshot) {
|
|
143
|
+
return (snapshot?.userInputs ?? []).filter((request) => request.status === "pending");
|
|
144
|
+
}
|
|
145
|
+
function selectPendingUserInputsForRun(snapshot, runId) {
|
|
146
|
+
return selectUserInputsForRun(snapshot, runId).filter((request) => request.status === "pending");
|
|
147
|
+
}
|
|
148
|
+
function selectUserInputsForWorkspace(snapshot, workspaceId) {
|
|
149
|
+
if (!snapshot || !workspaceId)
|
|
150
|
+
return [];
|
|
151
|
+
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
152
|
+
return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
153
|
+
}
|
|
154
|
+
function selectRuntimeForRun(snapshot, runId) {
|
|
155
|
+
if (!runId)
|
|
156
|
+
return null;
|
|
157
|
+
return snapshot?.runtimes.find((runtime) => runtime.runId === runId) ?? null;
|
|
158
|
+
}
|
|
159
|
+
function selectLatestRuntimeForTask(snapshot, taskId) {
|
|
160
|
+
if (!taskId)
|
|
161
|
+
return null;
|
|
162
|
+
const runs = selectRunsByTask(snapshot, taskId).toSorted((left, right) => (right.startedAt ?? right.createdAt).localeCompare(left.startedAt ?? left.createdAt));
|
|
163
|
+
for (const run of runs) {
|
|
164
|
+
const runtime = selectRuntimeForRun(snapshot, run.id);
|
|
165
|
+
if (runtime)
|
|
166
|
+
return runtime;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function selectActionsForTask(snapshot, taskId) {
|
|
171
|
+
if (!taskId)
|
|
172
|
+
return [];
|
|
173
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
174
|
+
return snapshot?.actions.filter((action) => runIds.has(action.runId)) ?? [];
|
|
175
|
+
}
|
|
176
|
+
function selectApprovalsForTask(snapshot, taskId) {
|
|
177
|
+
if (!taskId)
|
|
178
|
+
return [];
|
|
179
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
180
|
+
return snapshot?.approvals.filter((approval) => runIds.has(approval.runId)) ?? [];
|
|
181
|
+
}
|
|
182
|
+
function selectUserInputsForTask(snapshot, taskId) {
|
|
183
|
+
if (!taskId)
|
|
184
|
+
return [];
|
|
185
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
186
|
+
return (snapshot?.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
187
|
+
}
|
|
188
|
+
function selectValidationsForRun(snapshot, runId) {
|
|
189
|
+
if (!runId)
|
|
190
|
+
return [];
|
|
191
|
+
return snapshot?.validations.filter((validation) => validation.runId === runId) ?? [];
|
|
192
|
+
}
|
|
193
|
+
function selectValidationsForTask(snapshot, taskId) {
|
|
194
|
+
if (!taskId)
|
|
195
|
+
return [];
|
|
196
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
197
|
+
return snapshot?.validations.filter((validation) => runIds.has(validation.runId)) ?? [];
|
|
198
|
+
}
|
|
199
|
+
function selectFailedValidations(snapshot, runId) {
|
|
200
|
+
return selectValidationsForRun(snapshot, runId).filter((validation) => validation.status === "failed");
|
|
201
|
+
}
|
|
202
|
+
function selectReviewsForRun(snapshot, runId) {
|
|
203
|
+
if (!runId)
|
|
204
|
+
return [];
|
|
205
|
+
return snapshot?.reviews.filter((review) => review.runId === runId) ?? [];
|
|
206
|
+
}
|
|
207
|
+
function selectReviewsForTask(snapshot, taskId) {
|
|
208
|
+
if (!taskId)
|
|
209
|
+
return [];
|
|
210
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
211
|
+
return snapshot?.reviews.filter((review) => runIds.has(review.runId)) ?? [];
|
|
212
|
+
}
|
|
213
|
+
function selectArtifactsForRun(snapshot, runId) {
|
|
214
|
+
if (!runId)
|
|
215
|
+
return [];
|
|
216
|
+
return snapshot?.artifacts.filter((artifact) => artifact.runId === runId) ?? [];
|
|
217
|
+
}
|
|
218
|
+
function selectArtifactsForTask(snapshot, taskId) {
|
|
219
|
+
if (!taskId)
|
|
220
|
+
return [];
|
|
221
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
222
|
+
return snapshot?.artifacts.filter((artifact) => runIds.has(artifact.runId)) ?? [];
|
|
223
|
+
}
|
|
224
|
+
function selectPolicyDecisionsForTask(snapshot, taskId) {
|
|
225
|
+
if (!taskId)
|
|
226
|
+
return [];
|
|
227
|
+
const runIds = new Set(selectRunsByTask(snapshot, taskId).map((run) => run.id));
|
|
228
|
+
return snapshot?.policyDecisions.filter((decision) => runIds.has(decision.runId)) ?? [];
|
|
229
|
+
}
|
|
230
|
+
function selectRemoteEndpoints(snapshot) {
|
|
231
|
+
return snapshot?.remoteEndpoints ?? [];
|
|
232
|
+
}
|
|
233
|
+
function selectRemoteConnections(snapshot) {
|
|
234
|
+
return snapshot?.remoteConnections ?? [];
|
|
235
|
+
}
|
|
236
|
+
function selectRemoteEndpoint(snapshot, endpointId) {
|
|
237
|
+
if (!endpointId)
|
|
238
|
+
return null;
|
|
239
|
+
return snapshot?.remoteEndpoints.find((endpoint) => endpoint.id === endpointId) ?? null;
|
|
240
|
+
}
|
|
241
|
+
function selectRemoteConnection(snapshot, endpointId) {
|
|
242
|
+
if (!endpointId)
|
|
243
|
+
return null;
|
|
244
|
+
return snapshot?.remoteConnections.find((connection) => connection.endpointId === endpointId) ?? null;
|
|
245
|
+
}
|
|
246
|
+
function selectConnectedRemoteCount(snapshot) {
|
|
247
|
+
return selectRemoteConnections(snapshot).filter((connection) => connection.status === "connected").length;
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
selectWorkspaces,
|
|
251
|
+
selectWorkspace,
|
|
252
|
+
selectValidationsForTask,
|
|
253
|
+
selectValidationsForRun,
|
|
254
|
+
selectUserInputsForWorkspace,
|
|
255
|
+
selectUserInputsForTask,
|
|
256
|
+
selectUserInputsForRun,
|
|
257
|
+
selectTasksGroupedByStatus,
|
|
258
|
+
selectTasksForWorkspace,
|
|
259
|
+
selectTasksByWorkspace,
|
|
260
|
+
selectTasksByStatus,
|
|
261
|
+
selectTask,
|
|
262
|
+
selectRuntimeForRun,
|
|
263
|
+
selectRunsForWorkspace,
|
|
264
|
+
selectRunsForTask,
|
|
265
|
+
selectRunsByTask,
|
|
266
|
+
selectRun,
|
|
267
|
+
selectReviewsForTask,
|
|
268
|
+
selectReviewsForRun,
|
|
269
|
+
selectRemoteEndpoints,
|
|
270
|
+
selectRemoteEndpoint,
|
|
271
|
+
selectRemoteConnections,
|
|
272
|
+
selectRemoteConnection,
|
|
273
|
+
selectQueueForWorkspace,
|
|
274
|
+
selectPrimaryWorkspace,
|
|
275
|
+
selectPolicyDecisionsForTask,
|
|
276
|
+
selectPendingUserInputsForRun,
|
|
277
|
+
selectPendingUserInputs,
|
|
278
|
+
selectPendingApprovalsForRun,
|
|
279
|
+
selectPendingApprovals,
|
|
280
|
+
selectLatestRuntimeForTask,
|
|
281
|
+
selectGraphsForWorkspace,
|
|
282
|
+
selectFailedValidations,
|
|
283
|
+
selectConnectedRemoteCount,
|
|
284
|
+
selectArtifactsForTask,
|
|
285
|
+
selectArtifactsForRun,
|
|
286
|
+
selectApprovalsForWorkspace,
|
|
287
|
+
selectApprovalsForTask,
|
|
288
|
+
selectApprovalsForRun,
|
|
289
|
+
selectAdhocRunsForWorkspace,
|
|
290
|
+
selectAdhocRuns,
|
|
291
|
+
selectActionsForTask,
|
|
292
|
+
pickDefaultWorkspaceId
|
|
293
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/taskGraph.ts
|
|
3
|
+
function isObjectRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function readTaskMetadataStringList(task, key) {
|
|
7
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
8
|
+
const value = metadata?.[key];
|
|
9
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
10
|
+
}
|
|
11
|
+
function readTaskSourceIssueId(task) {
|
|
12
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
13
|
+
return typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0 ? metadata.sourceIssueId : null;
|
|
14
|
+
}
|
|
15
|
+
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
16
|
+
if (tasksById.has(ref))
|
|
17
|
+
return ref;
|
|
18
|
+
return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
|
|
19
|
+
}
|
|
20
|
+
function buildTaskReferenceIndex(tasks) {
|
|
21
|
+
return {
|
|
22
|
+
tasksById: new Map(tasks.map((task) => [task.id, task])),
|
|
23
|
+
taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
|
|
24
|
+
taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
|
|
25
|
+
const sourceIssueId = readTaskSourceIssueId(task);
|
|
26
|
+
return sourceIssueId ? [[sourceIssueId, task.id]] : [];
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function computeTaskBlockingDepths(tasks) {
|
|
31
|
+
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
32
|
+
const memo = new Map;
|
|
33
|
+
const visit = (taskId, stack) => {
|
|
34
|
+
const cached = memo.get(taskId);
|
|
35
|
+
if (cached !== undefined)
|
|
36
|
+
return cached;
|
|
37
|
+
if (stack.has(taskId))
|
|
38
|
+
return 0;
|
|
39
|
+
const task = tasksById.get(taskId);
|
|
40
|
+
if (!task)
|
|
41
|
+
return 0;
|
|
42
|
+
stack.add(taskId);
|
|
43
|
+
const refs = [
|
|
44
|
+
...readTaskMetadataStringList(task, "dependencies"),
|
|
45
|
+
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
46
|
+
];
|
|
47
|
+
const blockers = refs.map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
48
|
+
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
49
|
+
stack.delete(taskId);
|
|
50
|
+
memo.set(taskId, depth);
|
|
51
|
+
return depth;
|
|
52
|
+
};
|
|
53
|
+
for (const task of tasks) {
|
|
54
|
+
visit(task.id, new Set);
|
|
55
|
+
}
|
|
56
|
+
return memo;
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
resolveTaskReference,
|
|
60
|
+
readTaskSourceIssueId,
|
|
61
|
+
readTaskMetadataStringList,
|
|
62
|
+
computeTaskBlockingDepths,
|
|
63
|
+
buildTaskReferenceIndex
|
|
64
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/taskGraphCodes.ts
|
|
3
|
+
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
4
|
+
function extractTaskCode(title) {
|
|
5
|
+
const match = title.match(TASK_CODE_RE);
|
|
6
|
+
return match?.[1] ?? null;
|
|
7
|
+
}
|
|
8
|
+
function extractTaskGroupKey(title) {
|
|
9
|
+
const code = extractTaskCode(title);
|
|
10
|
+
if (!code)
|
|
11
|
+
return null;
|
|
12
|
+
const parts = code.split("-");
|
|
13
|
+
const suffix = parts.at(-1) ?? "";
|
|
14
|
+
if (/^\d+$/.test(suffix)) {
|
|
15
|
+
return parts.slice(0, -1).join("-");
|
|
16
|
+
}
|
|
17
|
+
return parts[0] ?? code;
|
|
18
|
+
}
|
|
19
|
+
function stripTaskCode(label) {
|
|
20
|
+
return label.replace(TASK_CODE_RE, "");
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
stripTaskCode,
|
|
24
|
+
extractTaskGroupKey,
|
|
25
|
+
extractTaskCode
|
|
26
|
+
};
|