@h-rig/core 0.0.6-alpha.10 → 0.0.6-alpha.103
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 +6 -3
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +354 -22
- 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 +3 -2
- 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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { ArtifactSummary, ApprovalSummary, EngineReadModel, GraphSummary, QueueEntry, RemoteConnectionSummary, RemoteEndpoint, RunId, RunJournalProjection, RunStatus, RunSummary, TaskId, TaskStatus, TaskSummary, UserInputRequestSummary, WorkspaceId, WorkspaceSummary } from "@rig/contracts";
|
|
2
|
+
import type { TaskDependencyProjection } from "./taskGraph";
|
|
3
|
+
export declare function selectWorkspaces(snapshot: EngineReadModel | null): readonly WorkspaceSummary[];
|
|
4
|
+
export declare function selectPrimaryWorkspace(snapshot: EngineReadModel | null): WorkspaceSummary | null;
|
|
5
|
+
export declare function pickDefaultWorkspaceId(snapshot: EngineReadModel | null): WorkspaceId | null;
|
|
6
|
+
export declare function selectWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): WorkspaceSummary | null;
|
|
7
|
+
export declare function selectTask(snapshot: EngineReadModel | null, taskId: TaskId | null): TaskSummary | null;
|
|
8
|
+
export declare function selectTasksByWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): TaskSummary[];
|
|
9
|
+
export declare const selectTasksForWorkspace: typeof selectTasksByWorkspace;
|
|
10
|
+
export declare function selectTasksByStatus(snapshot: EngineReadModel | null, status: TaskStatus): TaskSummary[];
|
|
11
|
+
export type RigTaskSessionProjection = Pick<RunJournalProjection, "record" | "status" | "lastEventAt"> & {
|
|
12
|
+
readonly taskId?: string | null;
|
|
13
|
+
};
|
|
14
|
+
export interface RigTaskStatusGroupingInput<T extends TaskDependencyProjection = TaskDependencyProjection> {
|
|
15
|
+
readonly tasks: readonly T[];
|
|
16
|
+
readonly sessions?: readonly RigTaskSessionProjection[];
|
|
17
|
+
readonly workspaceId?: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface RigTaskStatusGroup<T extends TaskDependencyProjection = TaskDependencyProjection> {
|
|
20
|
+
readonly status: string;
|
|
21
|
+
readonly tasks: readonly T[];
|
|
22
|
+
}
|
|
23
|
+
export declare function projectTaskStatusForGrouping(status: string | null | undefined): string;
|
|
24
|
+
export declare function projectRunStatusForTaskGrouping(status: RunStatus | null | undefined): string | null;
|
|
25
|
+
export declare function projectTaskStatusWithSessions(task: TaskDependencyProjection, sessionsByTask?: ReadonlyMap<string, RigTaskSessionProjection>): string;
|
|
26
|
+
export declare function selectTasksGroupedByStatus(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): Array<{
|
|
27
|
+
status: string;
|
|
28
|
+
tasks: readonly TaskSummary[];
|
|
29
|
+
}>;
|
|
30
|
+
export declare function selectTasksGroupedByStatus<T extends TaskDependencyProjection>(input: RigTaskStatusGroupingInput<T>): Array<RigTaskStatusGroup<T>>;
|
|
31
|
+
export declare function normalizeTaskAssigneeFilter(assignee: string | null | undefined, currentUserLogin?: string | null): string | null;
|
|
32
|
+
export declare function readTaskAssigneeLogins(task: TaskDependencyProjection): readonly string[];
|
|
33
|
+
export declare function taskMatchesAssigneeFilter(task: TaskDependencyProjection, assignee: string | null | undefined, options?: {
|
|
34
|
+
readonly currentUserLogin?: string | null;
|
|
35
|
+
}): boolean;
|
|
36
|
+
export declare function selectTasksAssignedTo<T extends TaskDependencyProjection>(tasks: readonly T[], assignee: string | null | undefined, options?: {
|
|
37
|
+
readonly currentUserLogin?: string | null;
|
|
38
|
+
}): T[];
|
|
39
|
+
export declare function selectTasksAssignedToMe<T extends TaskDependencyProjection>(tasks: readonly T[], currentUserLogin: string | null | undefined): T[];
|
|
40
|
+
export declare function selectRun(snapshot: EngineReadModel | null, runId: RunId | null): RunSummary | null;
|
|
41
|
+
export declare function selectRunsByTask(snapshot: EngineReadModel | null, taskId: TaskId | null): RunSummary[];
|
|
42
|
+
export declare const selectRunsForTask: typeof selectRunsByTask;
|
|
43
|
+
export declare function selectRunsForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): RunSummary[];
|
|
44
|
+
export declare function selectAdhocRuns(snapshot: EngineReadModel | null): RunSummary[];
|
|
45
|
+
export declare function selectAdhocRunsForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): RunSummary[];
|
|
46
|
+
export declare function selectGraphsForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): GraphSummary[];
|
|
47
|
+
export declare function selectQueueForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): QueueEntry[];
|
|
48
|
+
export declare function selectPendingApprovals(snapshot: EngineReadModel | null): ApprovalSummary[];
|
|
49
|
+
export declare function selectApprovalsForRun(snapshot: EngineReadModel | null, runId: RunId | null): ApprovalSummary[];
|
|
50
|
+
export declare function selectPendingApprovalsForRun(snapshot: EngineReadModel | null, runId: RunId | null): ApprovalSummary[];
|
|
51
|
+
export declare function selectApprovalsForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): ApprovalSummary[];
|
|
52
|
+
export declare function selectUserInputsForRun(snapshot: EngineReadModel | null, runId: RunId | null): UserInputRequestSummary[];
|
|
53
|
+
export declare function selectPendingUserInputs(snapshot: EngineReadModel | null): UserInputRequestSummary[];
|
|
54
|
+
export declare function selectPendingUserInputsForRun(snapshot: EngineReadModel | null, runId: RunId | null): UserInputRequestSummary[];
|
|
55
|
+
export declare function selectUserInputsForWorkspace(snapshot: EngineReadModel | null, workspaceId: WorkspaceId | null): UserInputRequestSummary[];
|
|
56
|
+
export declare function selectRuntimeForRun(snapshot: EngineReadModel | null, runId: RunId | null): {
|
|
57
|
+
readonly id: string & import("effect/Brand").Brand<"EngineRuntimeId">;
|
|
58
|
+
readonly updatedAt: string;
|
|
59
|
+
readonly status: "starting" | "running" | "interrupted" | "failed" | "prepared" | "exited" | "destroyed";
|
|
60
|
+
readonly startedAt: string | null;
|
|
61
|
+
readonly pid: number | null;
|
|
62
|
+
readonly stateDir: string | null;
|
|
63
|
+
readonly workspaceId: string & import("effect/Brand").Brand<"WorkspaceId">;
|
|
64
|
+
readonly workspaceDir: string | null;
|
|
65
|
+
readonly homeDir: string | null;
|
|
66
|
+
readonly tmpDir: string | null;
|
|
67
|
+
readonly cacheDir: string | null;
|
|
68
|
+
readonly logsDir: string | null;
|
|
69
|
+
readonly sessionDir: string | null;
|
|
70
|
+
readonly sessionLogPath: string | null;
|
|
71
|
+
readonly exitedAt: string | null;
|
|
72
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
73
|
+
readonly adapterKind: string;
|
|
74
|
+
readonly sandboxMode: "read-only" | "workspace-write" | "danger-full-access";
|
|
75
|
+
readonly isolationMode: "none" | "env" | "worktree";
|
|
76
|
+
readonly executionTarget?: "remote" | "local" | undefined;
|
|
77
|
+
readonly remoteHostId?: string | null | undefined;
|
|
78
|
+
} | null;
|
|
79
|
+
export declare function selectLatestRuntimeForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
80
|
+
readonly id: string & import("effect/Brand").Brand<"EngineRuntimeId">;
|
|
81
|
+
readonly updatedAt: string;
|
|
82
|
+
readonly status: "starting" | "running" | "interrupted" | "failed" | "prepared" | "exited" | "destroyed";
|
|
83
|
+
readonly startedAt: string | null;
|
|
84
|
+
readonly pid: number | null;
|
|
85
|
+
readonly stateDir: string | null;
|
|
86
|
+
readonly workspaceId: string & import("effect/Brand").Brand<"WorkspaceId">;
|
|
87
|
+
readonly workspaceDir: string | null;
|
|
88
|
+
readonly homeDir: string | null;
|
|
89
|
+
readonly tmpDir: string | null;
|
|
90
|
+
readonly cacheDir: string | null;
|
|
91
|
+
readonly logsDir: string | null;
|
|
92
|
+
readonly sessionDir: string | null;
|
|
93
|
+
readonly sessionLogPath: string | null;
|
|
94
|
+
readonly exitedAt: string | null;
|
|
95
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
96
|
+
readonly adapterKind: string;
|
|
97
|
+
readonly sandboxMode: "read-only" | "workspace-write" | "danger-full-access";
|
|
98
|
+
readonly isolationMode: "none" | "env" | "worktree";
|
|
99
|
+
readonly executionTarget?: "remote" | "local" | undefined;
|
|
100
|
+
readonly remoteHostId?: string | null | undefined;
|
|
101
|
+
} | null;
|
|
102
|
+
export declare function selectActionsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
103
|
+
readonly id: string & import("effect/Brand").Brand<"ActionId">;
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly completedAt: string | null;
|
|
106
|
+
readonly payload: unknown;
|
|
107
|
+
readonly startedAt: string;
|
|
108
|
+
readonly state: string;
|
|
109
|
+
readonly messageId: (string & import("effect/Brand").Brand<"MessageId">) | null;
|
|
110
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
111
|
+
readonly detail: string | null;
|
|
112
|
+
readonly actionType: string;
|
|
113
|
+
}[];
|
|
114
|
+
export declare function selectApprovalsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
115
|
+
readonly id: string;
|
|
116
|
+
readonly createdAt: string;
|
|
117
|
+
readonly status: "pending" | "resolved";
|
|
118
|
+
readonly payload: unknown;
|
|
119
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
120
|
+
readonly actionId: string | null;
|
|
121
|
+
readonly resolvedAt: string | null;
|
|
122
|
+
readonly requestKind: string;
|
|
123
|
+
}[];
|
|
124
|
+
export declare function selectUserInputsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
125
|
+
readonly id: string;
|
|
126
|
+
readonly createdAt: string;
|
|
127
|
+
readonly status: "pending" | "resolved";
|
|
128
|
+
readonly payload: unknown;
|
|
129
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
130
|
+
readonly resolvedAt: string | null;
|
|
131
|
+
}[];
|
|
132
|
+
export declare function selectValidationsForRun(snapshot: EngineReadModel | null, runId: RunId | null): {
|
|
133
|
+
readonly id: string & import("effect/Brand").Brand<"ValidationResultId">;
|
|
134
|
+
readonly status: "running" | "pending" | "failed" | "passed" | "skipped";
|
|
135
|
+
readonly completedAt: string | null;
|
|
136
|
+
readonly startedAt: string;
|
|
137
|
+
readonly taskId: (string & import("effect/Brand").Brand<"TaskId">) | null;
|
|
138
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
139
|
+
readonly validatorKey: string;
|
|
140
|
+
readonly output: unknown;
|
|
141
|
+
}[];
|
|
142
|
+
export declare function selectValidationsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
143
|
+
readonly id: string & import("effect/Brand").Brand<"ValidationResultId">;
|
|
144
|
+
readonly status: "running" | "pending" | "failed" | "passed" | "skipped";
|
|
145
|
+
readonly completedAt: string | null;
|
|
146
|
+
readonly startedAt: string;
|
|
147
|
+
readonly taskId: (string & import("effect/Brand").Brand<"TaskId">) | null;
|
|
148
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
149
|
+
readonly validatorKey: string;
|
|
150
|
+
readonly output: unknown;
|
|
151
|
+
}[];
|
|
152
|
+
export declare function selectFailedValidations(snapshot: EngineReadModel | null, runId: RunId | null): {
|
|
153
|
+
readonly id: string & import("effect/Brand").Brand<"ValidationResultId">;
|
|
154
|
+
readonly status: "running" | "pending" | "failed" | "passed" | "skipped";
|
|
155
|
+
readonly completedAt: string | null;
|
|
156
|
+
readonly startedAt: string;
|
|
157
|
+
readonly taskId: (string & import("effect/Brand").Brand<"TaskId">) | null;
|
|
158
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
159
|
+
readonly validatorKey: string;
|
|
160
|
+
readonly output: unknown;
|
|
161
|
+
}[];
|
|
162
|
+
export declare function selectReviewsForRun(snapshot: EngineReadModel | null, runId: RunId | null): {
|
|
163
|
+
readonly id: string & import("effect/Brand").Brand<"ReviewResultId">;
|
|
164
|
+
readonly createdAt: string;
|
|
165
|
+
readonly status: "running" | "error" | "rejected" | "pending" | "approved";
|
|
166
|
+
readonly completedAt: string | null;
|
|
167
|
+
readonly summary: string | null;
|
|
168
|
+
readonly provider: string;
|
|
169
|
+
readonly mode: "required" | "off" | "advisory";
|
|
170
|
+
readonly taskId: (string & import("effect/Brand").Brand<"TaskId">) | null;
|
|
171
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
172
|
+
readonly output: unknown;
|
|
173
|
+
}[];
|
|
174
|
+
export declare function selectReviewsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
175
|
+
readonly id: string & import("effect/Brand").Brand<"ReviewResultId">;
|
|
176
|
+
readonly createdAt: string;
|
|
177
|
+
readonly status: "running" | "error" | "rejected" | "pending" | "approved";
|
|
178
|
+
readonly completedAt: string | null;
|
|
179
|
+
readonly summary: string | null;
|
|
180
|
+
readonly provider: string;
|
|
181
|
+
readonly mode: "required" | "off" | "advisory";
|
|
182
|
+
readonly taskId: (string & import("effect/Brand").Brand<"TaskId">) | null;
|
|
183
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
184
|
+
readonly output: unknown;
|
|
185
|
+
}[];
|
|
186
|
+
export declare function selectArtifactsForRun(snapshot: EngineReadModel | null, runId: RunId | null): ArtifactSummary[];
|
|
187
|
+
export declare function selectArtifactsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): ArtifactSummary[];
|
|
188
|
+
export declare function selectPolicyDecisionsForTask(snapshot: EngineReadModel | null, taskId: TaskId | null): {
|
|
189
|
+
readonly id: string;
|
|
190
|
+
readonly createdAt: string;
|
|
191
|
+
readonly decision: "allow" | "block" | "warn";
|
|
192
|
+
readonly mode: "off" | "enforce" | "observe";
|
|
193
|
+
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
194
|
+
readonly actionId: (string & import("effect/Brand").Brand<"ActionId">) | null;
|
|
195
|
+
readonly reason: string;
|
|
196
|
+
readonly matchedRules: readonly string[];
|
|
197
|
+
}[];
|
|
198
|
+
export declare function selectRemoteEndpoints(snapshot: EngineReadModel | null): readonly RemoteEndpoint[];
|
|
199
|
+
export declare function selectRemoteConnections(snapshot: EngineReadModel | null): readonly RemoteConnectionSummary[];
|
|
200
|
+
export declare function selectRemoteEndpoint(snapshot: EngineReadModel | null, endpointId: string | null): {
|
|
201
|
+
readonly id: string & import("effect/Brand").Brand<"RemoteEndpointId">;
|
|
202
|
+
readonly port: number;
|
|
203
|
+
readonly host: string;
|
|
204
|
+
readonly lastConnectedAt: string | null;
|
|
205
|
+
readonly alias: string;
|
|
206
|
+
readonly token: string;
|
|
207
|
+
readonly addedAt: string;
|
|
208
|
+
readonly tokenConfigured?: boolean | undefined;
|
|
209
|
+
readonly autoConnect?: boolean | undefined;
|
|
210
|
+
} | null;
|
|
211
|
+
export declare function selectRemoteConnection(snapshot: EngineReadModel | null, endpointId: string | null): {
|
|
212
|
+
readonly error: string | null;
|
|
213
|
+
readonly status: "error" | "disconnected" | "connecting" | "authenticating" | "connected" | "reconnecting";
|
|
214
|
+
readonly endpointId: string & import("effect/Brand").Brand<"RemoteEndpointId">;
|
|
215
|
+
readonly connectedAt: string | null;
|
|
216
|
+
readonly tokenExpiresAt: string | null;
|
|
217
|
+
readonly latencyMs: number | null;
|
|
218
|
+
readonly subscribedEvents: readonly string[];
|
|
219
|
+
} | null;
|
|
220
|
+
export declare function selectConnectedRemoteCount(snapshot: EngineReadModel | null): number;
|
package/dist/src/rigSelectors.js
CHANGED
|
@@ -39,10 +39,40 @@ function projectTaskStatusForGrouping(status) {
|
|
|
39
39
|
case "in_progress":
|
|
40
40
|
case "under_review":
|
|
41
41
|
return "running";
|
|
42
|
+
case null:
|
|
43
|
+
case undefined:
|
|
44
|
+
case "":
|
|
45
|
+
return "unknown";
|
|
42
46
|
default:
|
|
43
47
|
return status;
|
|
44
48
|
}
|
|
45
49
|
}
|
|
50
|
+
function projectRunStatusForTaskGrouping(status) {
|
|
51
|
+
switch (status) {
|
|
52
|
+
case "created":
|
|
53
|
+
case "queued":
|
|
54
|
+
case "preparing":
|
|
55
|
+
return "queued";
|
|
56
|
+
case "running":
|
|
57
|
+
case "waiting-approval":
|
|
58
|
+
case "waiting-user-input":
|
|
59
|
+
case "paused":
|
|
60
|
+
case "validating":
|
|
61
|
+
case "reviewing":
|
|
62
|
+
case "closing-out":
|
|
63
|
+
return "running";
|
|
64
|
+
case "needs-attention":
|
|
65
|
+
return "blocked";
|
|
66
|
+
case "failed":
|
|
67
|
+
case "stopped":
|
|
68
|
+
return "ready";
|
|
69
|
+
case "completed":
|
|
70
|
+
return "completed";
|
|
71
|
+
case null:
|
|
72
|
+
case undefined:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
46
76
|
var STATUS_PRIORITY = [
|
|
47
77
|
"running",
|
|
48
78
|
"blocked",
|
|
@@ -54,11 +84,31 @@ var STATUS_PRIORITY = [
|
|
|
54
84
|
"cancelled",
|
|
55
85
|
"completed"
|
|
56
86
|
];
|
|
57
|
-
function
|
|
58
|
-
|
|
87
|
+
function taskIdFromSession(session) {
|
|
88
|
+
return session.taskId ?? session.record.taskId ?? null;
|
|
89
|
+
}
|
|
90
|
+
function latestSessionByTask(sessions) {
|
|
91
|
+
const byTask = new Map;
|
|
92
|
+
for (const session of sessions) {
|
|
93
|
+
const taskId = taskIdFromSession(session);
|
|
94
|
+
if (!taskId)
|
|
95
|
+
continue;
|
|
96
|
+
const existing = byTask.get(taskId);
|
|
97
|
+
if (!existing || (session.lastEventAt ?? "").localeCompare(existing.lastEventAt ?? "") >= 0) {
|
|
98
|
+
byTask.set(taskId, session);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return byTask;
|
|
102
|
+
}
|
|
103
|
+
function projectTaskStatusWithSessions(task, sessionsByTask = new Map) {
|
|
104
|
+
const sessionStatus = projectRunStatusForTaskGrouping(sessionsByTask.get(task.id)?.status);
|
|
105
|
+
return sessionStatus ?? projectTaskStatusForGrouping(task.status);
|
|
106
|
+
}
|
|
107
|
+
function groupTasksByProjectedStatus(tasks, sessions = []) {
|
|
108
|
+
const sessionsByTask = latestSessionByTask(sessions);
|
|
59
109
|
const byStatus = new Map;
|
|
60
110
|
for (const task of tasks) {
|
|
61
|
-
const projectedStatus =
|
|
111
|
+
const projectedStatus = projectTaskStatusWithSessions(task, sessionsByTask);
|
|
62
112
|
const group = byStatus.get(projectedStatus);
|
|
63
113
|
if (group) {
|
|
64
114
|
group.push(task);
|
|
@@ -82,6 +132,69 @@ function selectTasksGroupedByStatus(snapshot, workspaceId) {
|
|
|
82
132
|
}
|
|
83
133
|
return result;
|
|
84
134
|
}
|
|
135
|
+
function isProjectionGroupingInput(value) {
|
|
136
|
+
return Boolean(value && typeof value === "object" && !("snapshotSequence" in value) && Array.isArray(value.tasks));
|
|
137
|
+
}
|
|
138
|
+
function selectTasksGroupedByStatus(input, workspaceId) {
|
|
139
|
+
if (isProjectionGroupingInput(input)) {
|
|
140
|
+
const filteredTasks = input.workspaceId ? input.tasks.filter((task) => {
|
|
141
|
+
const workspaceTask = task;
|
|
142
|
+
return workspaceTask.workspaceId === input.workspaceId;
|
|
143
|
+
}) : input.tasks;
|
|
144
|
+
return groupTasksByProjectedStatus(filteredTasks, input.sessions);
|
|
145
|
+
}
|
|
146
|
+
const tasks = selectTasksByWorkspace(input, workspaceId ?? null);
|
|
147
|
+
return groupTasksByProjectedStatus(tasks, []);
|
|
148
|
+
}
|
|
149
|
+
function isObjectRecord(value) {
|
|
150
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
151
|
+
}
|
|
152
|
+
function normalizeLogin(value) {
|
|
153
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
function assigneeLoginsFromValue(value) {
|
|
156
|
+
if (!Array.isArray(value))
|
|
157
|
+
return [];
|
|
158
|
+
return value.flatMap((entry) => {
|
|
159
|
+
if (typeof entry === "string" && entry.trim())
|
|
160
|
+
return [normalizeLogin(entry)];
|
|
161
|
+
if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
162
|
+
return [normalizeLogin(entry.login)];
|
|
163
|
+
}
|
|
164
|
+
return [];
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function normalizeTaskAssigneeFilter(assignee, currentUserLogin) {
|
|
168
|
+
const trimmed = assignee?.trim();
|
|
169
|
+
if (!trimmed)
|
|
170
|
+
return null;
|
|
171
|
+
if (trimmed === "@me" || trimmed.toLowerCase() === "me") {
|
|
172
|
+
return currentUserLogin?.trim() ? normalizeLogin(currentUserLogin) : null;
|
|
173
|
+
}
|
|
174
|
+
return normalizeLogin(trimmed);
|
|
175
|
+
}
|
|
176
|
+
function readTaskAssigneeLogins(task) {
|
|
177
|
+
const taskRecord = task;
|
|
178
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
179
|
+
const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
|
|
180
|
+
return Array.from(new Set([
|
|
181
|
+
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
182
|
+
...assigneeLoginsFromValue(metadata?.assignees),
|
|
183
|
+
...assigneeLoginsFromValue(raw?.assignees)
|
|
184
|
+
]));
|
|
185
|
+
}
|
|
186
|
+
function taskMatchesAssigneeFilter(task, assignee, options = {}) {
|
|
187
|
+
const normalized = normalizeTaskAssigneeFilter(assignee, options.currentUserLogin);
|
|
188
|
+
if (!normalized)
|
|
189
|
+
return false;
|
|
190
|
+
return readTaskAssigneeLogins(task).includes(normalized);
|
|
191
|
+
}
|
|
192
|
+
function selectTasksAssignedTo(tasks, assignee, options = {}) {
|
|
193
|
+
return tasks.filter((task) => taskMatchesAssigneeFilter(task, assignee, options));
|
|
194
|
+
}
|
|
195
|
+
function selectTasksAssignedToMe(tasks, currentUserLogin) {
|
|
196
|
+
return selectTasksAssignedTo(tasks, "@me", currentUserLogin === undefined ? {} : { currentUserLogin });
|
|
197
|
+
}
|
|
85
198
|
function selectRun(snapshot, runId) {
|
|
86
199
|
if (!runId)
|
|
87
200
|
return null;
|
|
@@ -247,6 +360,7 @@ function selectConnectedRemoteCount(snapshot) {
|
|
|
247
360
|
return selectRemoteConnections(snapshot).filter((connection) => connection.status === "connected").length;
|
|
248
361
|
}
|
|
249
362
|
export {
|
|
363
|
+
taskMatchesAssigneeFilter,
|
|
250
364
|
selectWorkspaces,
|
|
251
365
|
selectWorkspace,
|
|
252
366
|
selectValidationsForTask,
|
|
@@ -258,6 +372,8 @@ export {
|
|
|
258
372
|
selectTasksForWorkspace,
|
|
259
373
|
selectTasksByWorkspace,
|
|
260
374
|
selectTasksByStatus,
|
|
375
|
+
selectTasksAssignedToMe,
|
|
376
|
+
selectTasksAssignedTo,
|
|
261
377
|
selectTask,
|
|
262
378
|
selectRuntimeForRun,
|
|
263
379
|
selectRunsForWorkspace,
|
|
@@ -289,5 +405,10 @@ export {
|
|
|
289
405
|
selectAdhocRunsForWorkspace,
|
|
290
406
|
selectAdhocRuns,
|
|
291
407
|
selectActionsForTask,
|
|
292
|
-
|
|
408
|
+
readTaskAssigneeLogins,
|
|
409
|
+
projectTaskStatusWithSessions,
|
|
410
|
+
projectTaskStatusForGrouping,
|
|
411
|
+
projectRunStatusForTaskGrouping,
|
|
412
|
+
pickDefaultWorkspaceId,
|
|
413
|
+
normalizeTaskAssigneeFilter
|
|
293
414
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TaskSummary } from "@rig/contracts";
|
|
2
|
+
export type TaskDependencyProjection = Pick<TaskSummary, "id" | "status" | "priority" | "metadata"> & Partial<Pick<TaskSummary, "externalId" | "sourceIssueId" | "dependencies" | "parentChildDeps" | "createdAt" | "updatedAt">> & {
|
|
3
|
+
readonly title?: string | null;
|
|
4
|
+
};
|
|
5
|
+
export type TaskDependencyBadgeKind = "blocked" | "ready" | "dependency";
|
|
6
|
+
export interface TaskDependencyBadge {
|
|
7
|
+
readonly kind: TaskDependencyBadgeKind;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly description: string;
|
|
10
|
+
readonly count?: number;
|
|
11
|
+
readonly taskIds?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface TaskDependencyBadgeSummary {
|
|
14
|
+
readonly taskId: string;
|
|
15
|
+
readonly blockingDepth: number;
|
|
16
|
+
readonly dependencyIds: readonly string[];
|
|
17
|
+
readonly unresolvedDependencyRefs: readonly string[];
|
|
18
|
+
readonly blockedBy: readonly string[];
|
|
19
|
+
readonly blocks: readonly string[];
|
|
20
|
+
readonly blocked: boolean;
|
|
21
|
+
readonly ready: boolean;
|
|
22
|
+
readonly dependencyCount: number;
|
|
23
|
+
readonly dependentCount: number;
|
|
24
|
+
readonly badges: readonly TaskDependencyBadge[];
|
|
25
|
+
}
|
|
26
|
+
export declare function readTaskMetadataStringList(task: TaskDependencyProjection, key: "dependencies" | "parentChildDeps" | "labels"): string[];
|
|
27
|
+
export declare function readTaskDependencyRefs(task: TaskDependencyProjection): string[];
|
|
28
|
+
export declare function readTaskSourceIssueId(task: TaskDependencyProjection): string | null;
|
|
29
|
+
export declare function resolveTaskReference(ref: string, tasksById: ReadonlyMap<string, TaskDependencyProjection>, taskIdByExternalRef: ReadonlyMap<string, string>, taskIdBySourceIssueId: ReadonlyMap<string, string>): string | null;
|
|
30
|
+
export declare function buildTaskReferenceIndex<T extends TaskDependencyProjection>(tasks: readonly T[]): {
|
|
31
|
+
readonly tasksById: Map<string, T>;
|
|
32
|
+
readonly taskIdByExternalRef: Map<string, string>;
|
|
33
|
+
readonly taskIdBySourceIssueId: Map<string, string>;
|
|
34
|
+
};
|
|
35
|
+
export declare function computeTaskBlockingDepths<T extends TaskDependencyProjection>(tasks: readonly T[]): Map<string, number>;
|
|
36
|
+
export declare function isTaskTerminalStatus(status: string | null | undefined): boolean;
|
|
37
|
+
export declare function computeTaskDependencyBadges(tasks: readonly TaskDependencyProjection[]): Map<string, TaskDependencyBadgeSummary>;
|
|
38
|
+
export declare function selectNextReadyTaskByPriority<T extends TaskDependencyProjection>(tasks: readonly T[], options?: {
|
|
39
|
+
readonly excludeTaskIds?: Iterable<string>;
|
|
40
|
+
readonly filter?: (task: T) => boolean;
|
|
41
|
+
}): T | null;
|
package/dist/src/taskGraph.js
CHANGED
|
@@ -3,14 +3,42 @@
|
|
|
3
3
|
function isObjectRecord(value) {
|
|
4
4
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
5
|
}
|
|
6
|
+
function readStringList(value) {
|
|
7
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
8
|
+
}
|
|
9
|
+
function unique(values) {
|
|
10
|
+
return Array.from(new Set(values));
|
|
11
|
+
}
|
|
6
12
|
function readTaskMetadataStringList(task, key) {
|
|
13
|
+
const taskRecord = task;
|
|
14
|
+
const topLevel = readStringList(taskRecord[key]);
|
|
15
|
+
if (topLevel.length > 0)
|
|
16
|
+
return topLevel;
|
|
7
17
|
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
8
|
-
const
|
|
9
|
-
|
|
18
|
+
const metadataList = readStringList(metadata?.[key]);
|
|
19
|
+
if (metadataList.length > 0)
|
|
20
|
+
return metadataList;
|
|
21
|
+
if (key === "dependencies") {
|
|
22
|
+
return readStringList(metadata?.deps);
|
|
23
|
+
}
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
function readTaskDependencyRefs(task) {
|
|
27
|
+
return unique([
|
|
28
|
+
...readTaskMetadataStringList(task, "dependencies"),
|
|
29
|
+
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
30
|
+
]);
|
|
10
31
|
}
|
|
11
32
|
function readTaskSourceIssueId(task) {
|
|
33
|
+
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
34
|
+
return task.sourceIssueId;
|
|
35
|
+
}
|
|
12
36
|
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
13
|
-
|
|
37
|
+
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
38
|
+
return metadata.sourceIssueId;
|
|
39
|
+
}
|
|
40
|
+
const rigMetadata = isObjectRecord(metadata?._rig) ? metadata._rig : null;
|
|
41
|
+
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
14
42
|
}
|
|
15
43
|
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
16
44
|
if (tasksById.has(ref))
|
|
@@ -40,11 +68,7 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
40
68
|
if (!task)
|
|
41
69
|
return 0;
|
|
42
70
|
stack.add(taskId);
|
|
43
|
-
const
|
|
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);
|
|
71
|
+
const blockers = readTaskDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
48
72
|
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
49
73
|
stack.delete(taskId);
|
|
50
74
|
memo.set(taskId, depth);
|
|
@@ -55,10 +79,142 @@ function computeTaskBlockingDepths(tasks) {
|
|
|
55
79
|
}
|
|
56
80
|
return memo;
|
|
57
81
|
}
|
|
82
|
+
function isTaskTerminalStatus(status) {
|
|
83
|
+
switch (status) {
|
|
84
|
+
case "closed":
|
|
85
|
+
case "completed":
|
|
86
|
+
case "done":
|
|
87
|
+
case "cancelled":
|
|
88
|
+
case "canceled":
|
|
89
|
+
return true;
|
|
90
|
+
default:
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isTaskBlockedStatus(status) {
|
|
95
|
+
return status === "blocked";
|
|
96
|
+
}
|
|
97
|
+
function isTaskRunnableStatus(status) {
|
|
98
|
+
if (status === null || status === undefined || status === "")
|
|
99
|
+
return true;
|
|
100
|
+
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
101
|
+
return false;
|
|
102
|
+
switch (status) {
|
|
103
|
+
case "ready":
|
|
104
|
+
case "open":
|
|
105
|
+
case "failed":
|
|
106
|
+
return true;
|
|
107
|
+
default:
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function priorityValue(task) {
|
|
112
|
+
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
113
|
+
}
|
|
114
|
+
function computeTaskDependencyBadges(tasks) {
|
|
115
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
116
|
+
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
117
|
+
const dependencyIdsByTask = new Map;
|
|
118
|
+
const unresolvedRefsByTask = new Map;
|
|
119
|
+
const blocksByTask = new Map;
|
|
120
|
+
for (const task of tasks) {
|
|
121
|
+
const dependencyIds = [];
|
|
122
|
+
const unresolvedRefs = [];
|
|
123
|
+
for (const ref of readTaskDependencyRefs(task)) {
|
|
124
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
125
|
+
if (dependencyId && dependencyId !== task.id) {
|
|
126
|
+
dependencyIds.push(dependencyId);
|
|
127
|
+
const blocks = blocksByTask.get(dependencyId);
|
|
128
|
+
if (blocks) {
|
|
129
|
+
blocks.push(task.id);
|
|
130
|
+
} else {
|
|
131
|
+
blocksByTask.set(dependencyId, [task.id]);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
unresolvedRefs.push(ref);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
138
|
+
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
139
|
+
}
|
|
140
|
+
const summaries = new Map;
|
|
141
|
+
for (const task of tasks) {
|
|
142
|
+
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
143
|
+
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
144
|
+
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
145
|
+
const dependency = index.tasksById.get(dependencyId);
|
|
146
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
147
|
+
});
|
|
148
|
+
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
149
|
+
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
150
|
+
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
151
|
+
const badges = [];
|
|
152
|
+
if (blocked) {
|
|
153
|
+
badges.push({
|
|
154
|
+
kind: "blocked",
|
|
155
|
+
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
156
|
+
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
157
|
+
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
158
|
+
taskIds: blockedBy
|
|
159
|
+
});
|
|
160
|
+
} else if (ready) {
|
|
161
|
+
badges.push({
|
|
162
|
+
kind: "ready",
|
|
163
|
+
label: "ready",
|
|
164
|
+
description: "No open dependencies block this task."
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
168
|
+
badges.push({
|
|
169
|
+
kind: "dependency",
|
|
170
|
+
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
171
|
+
description: [
|
|
172
|
+
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
173
|
+
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
174
|
+
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
175
|
+
].filter((part) => part !== null).join(" "),
|
|
176
|
+
count: dependencyIds.length + blocks.length,
|
|
177
|
+
taskIds: unique([...dependencyIds, ...blocks])
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
summaries.set(task.id, {
|
|
181
|
+
taskId: task.id,
|
|
182
|
+
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
183
|
+
dependencyIds,
|
|
184
|
+
unresolvedDependencyRefs,
|
|
185
|
+
blockedBy,
|
|
186
|
+
blocks,
|
|
187
|
+
blocked,
|
|
188
|
+
ready,
|
|
189
|
+
dependencyCount: dependencyIds.length,
|
|
190
|
+
dependentCount: blocks.length,
|
|
191
|
+
badges
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return summaries;
|
|
195
|
+
}
|
|
196
|
+
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
197
|
+
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
198
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
199
|
+
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) => {
|
|
200
|
+
const priorityDelta = priorityValue(left) - priorityValue(right);
|
|
201
|
+
if (priorityDelta !== 0)
|
|
202
|
+
return priorityDelta;
|
|
203
|
+
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
204
|
+
if (createdDelta !== 0)
|
|
205
|
+
return createdDelta;
|
|
206
|
+
return left.id.localeCompare(right.id);
|
|
207
|
+
});
|
|
208
|
+
return candidates[0] ?? null;
|
|
209
|
+
}
|
|
58
210
|
export {
|
|
211
|
+
selectNextReadyTaskByPriority,
|
|
59
212
|
resolveTaskReference,
|
|
60
213
|
readTaskSourceIssueId,
|
|
61
214
|
readTaskMetadataStringList,
|
|
215
|
+
readTaskDependencyRefs,
|
|
216
|
+
isTaskTerminalStatus,
|
|
217
|
+
computeTaskDependencyBadges,
|
|
62
218
|
computeTaskBlockingDepths,
|
|
63
219
|
buildTaskReferenceIndex
|
|
64
220
|
};
|