task-while 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/bin/task-while.mjs +22 -0
  4. package/package.json +72 -0
  5. package/src/agents/claude.ts +175 -0
  6. package/src/agents/codex.ts +231 -0
  7. package/src/agents/provider-options.ts +45 -0
  8. package/src/agents/types.ts +69 -0
  9. package/src/batch/config.ts +109 -0
  10. package/src/batch/discovery.ts +35 -0
  11. package/src/batch/provider.ts +79 -0
  12. package/src/commands/batch.ts +266 -0
  13. package/src/commands/run.ts +270 -0
  14. package/src/core/engine-helpers.ts +114 -0
  15. package/src/core/engine-outcomes.ts +166 -0
  16. package/src/core/engine.ts +223 -0
  17. package/src/core/orchestrator-helpers.ts +52 -0
  18. package/src/core/orchestrator-integrate-resume.ts +149 -0
  19. package/src/core/orchestrator-review-resume.ts +228 -0
  20. package/src/core/orchestrator-task-attempt.ts +257 -0
  21. package/src/core/orchestrator.ts +99 -0
  22. package/src/core/runtime.ts +175 -0
  23. package/src/core/task-topology.ts +85 -0
  24. package/src/index.ts +121 -0
  25. package/src/prompts/implementer.ts +18 -0
  26. package/src/prompts/reviewer.ts +26 -0
  27. package/src/runtime/fs-runtime.ts +209 -0
  28. package/src/runtime/git.ts +137 -0
  29. package/src/runtime/github-pr-snapshot-decode.ts +307 -0
  30. package/src/runtime/github-pr-snapshot-queries.ts +137 -0
  31. package/src/runtime/github-pr-snapshot.ts +139 -0
  32. package/src/runtime/github.ts +232 -0
  33. package/src/runtime/path-layout.ts +13 -0
  34. package/src/runtime/workspace-resolver.ts +125 -0
  35. package/src/schema/index.ts +127 -0
  36. package/src/schema/model.ts +233 -0
  37. package/src/schema/shared.ts +93 -0
  38. package/src/task-sources/openspec/cli-json.ts +79 -0
  39. package/src/task-sources/openspec/context-files.ts +121 -0
  40. package/src/task-sources/openspec/parse-tasks-md.ts +57 -0
  41. package/src/task-sources/openspec/session.ts +235 -0
  42. package/src/task-sources/openspec/source.ts +59 -0
  43. package/src/task-sources/registry.ts +22 -0
  44. package/src/task-sources/spec-kit/parse-tasks-md.ts +48 -0
  45. package/src/task-sources/spec-kit/session.ts +174 -0
  46. package/src/task-sources/spec-kit/source.ts +30 -0
  47. package/src/task-sources/types.ts +47 -0
  48. package/src/types.ts +29 -0
  49. package/src/utils/fs.ts +31 -0
  50. package/src/workflow/config.ts +127 -0
  51. package/src/workflow/direct-preset.ts +44 -0
  52. package/src/workflow/finalize-task-checkbox.ts +24 -0
  53. package/src/workflow/preset.ts +86 -0
  54. package/src/workflow/pull-request-preset.ts +312 -0
  55. package/src/workflow/remote-reviewer.ts +243 -0
@@ -0,0 +1,175 @@
1
+ import type { TaskSourceSession } from '../task-sources/types'
2
+ import type {
3
+ FinalReport,
4
+ ImplementArtifact,
5
+ IntegrateArtifact,
6
+ ReviewArtifact,
7
+ TaskGraph,
8
+ WorkflowEvent,
9
+ WorkflowState,
10
+ } from '../types'
11
+
12
+ export interface AttemptArtifactKey {
13
+ attempt: number
14
+ generation: number
15
+ taskHandle: string
16
+ }
17
+
18
+ export interface WorkflowStore {
19
+ appendEvent: (event: WorkflowEvent) => Promise<void>
20
+ loadGraph: () => Promise<null | TaskGraph>
21
+ loadImplementArtifact: (
22
+ key: AttemptArtifactKey,
23
+ ) => Promise<ImplementArtifact | null>
24
+ loadReviewArtifact: (
25
+ key: AttemptArtifactKey,
26
+ ) => Promise<null | ReviewArtifact>
27
+ loadState: () => Promise<null | WorkflowState>
28
+ readReport: () => Promise<FinalReport | null>
29
+ reset: () => Promise<void>
30
+ saveGraph: (graph: TaskGraph) => Promise<void>
31
+ saveImplementArtifact: (artifact: ImplementArtifact) => Promise<void>
32
+ saveIntegrateArtifact: (artifact: IntegrateArtifact) => Promise<void>
33
+ saveReport: (report: FinalReport) => Promise<void>
34
+ saveReviewArtifact: (artifact: ReviewArtifact) => Promise<void>
35
+ saveState: (state: WorkflowState) => Promise<void>
36
+ }
37
+
38
+ export interface GitCheckoutBranchOptions {
39
+ create?: boolean
40
+ startPoint?: string
41
+ }
42
+
43
+ export interface GitCommitTaskInput {
44
+ message: string
45
+ }
46
+
47
+ export interface GitCommitTaskResult {
48
+ commitSha: string
49
+ }
50
+
51
+ export interface GitPushBranchOptions {
52
+ setUpstream?: boolean
53
+ }
54
+
55
+ export interface GitPort {
56
+ checkoutBranch: (
57
+ name: string,
58
+ options?: GitCheckoutBranchOptions,
59
+ ) => Promise<void>
60
+ checkoutRemoteBranch: (name: string) => Promise<void>
61
+ commitTask: (input: GitCommitTaskInput) => Promise<GitCommitTaskResult>
62
+ deleteLocalBranch: (name: string) => Promise<void>
63
+ getChangedFilesSinceHead: () => Promise<string[]>
64
+ getCurrentBranch: () => Promise<string>
65
+ getHeadSha: () => Promise<string>
66
+ getHeadSubject: () => Promise<string>
67
+ getHeadTimestamp: () => Promise<string>
68
+ pullFastForward: (branch: string) => Promise<void>
69
+ pushBranch: (name: string, options?: GitPushBranchOptions) => Promise<void>
70
+ requireCleanWorktree: () => Promise<void>
71
+ }
72
+
73
+ export interface PullRequestRef {
74
+ number: number
75
+ title: string
76
+ url: string
77
+ }
78
+
79
+ export interface MergedPullRequestRef extends PullRequestRef {
80
+ mergeCommitSha: string
81
+ }
82
+
83
+ export interface PullRequestReaction {
84
+ content: string
85
+ createdAt: string
86
+ userLogin: string
87
+ }
88
+
89
+ export interface PullRequestReviewSummary {
90
+ body: string
91
+ id: number
92
+ state: string
93
+ submittedAt: null | string
94
+ url: string
95
+ userLogin: string
96
+ }
97
+
98
+ export interface PullRequestDiscussionComment {
99
+ body: string
100
+ createdAt: string
101
+ id: number
102
+ url: string
103
+ userLogin: string
104
+ }
105
+
106
+ export interface PullRequestReviewThreadComment {
107
+ body: string
108
+ createdAt: string
109
+ line: null | number
110
+ path: string
111
+ url: string
112
+ userLogin: string
113
+ }
114
+
115
+ export interface PullRequestReviewThread {
116
+ comments: PullRequestReviewThreadComment[]
117
+ id: string
118
+ isOutdated: boolean
119
+ isResolved: boolean
120
+ }
121
+
122
+ export interface PullRequestSnapshot {
123
+ changedFiles: string[]
124
+ discussionComments: PullRequestDiscussionComment[]
125
+ reactions: PullRequestReaction[]
126
+ reviewSummaries: PullRequestReviewSummary[]
127
+ reviewThreads: PullRequestReviewThread[]
128
+ }
129
+
130
+ export interface GitHubPort {
131
+ createPullRequest: (input: CreatePullRequestInput) => Promise<PullRequestRef>
132
+ findMergedPullRequestByHeadBranch: (
133
+ input: FindMergedPullRequestByHeadBranchInput,
134
+ ) => Promise<MergedPullRequestRef | null>
135
+ findOpenPullRequestByHeadBranch: (
136
+ input: FindOpenPullRequestByHeadBranchInput,
137
+ ) => Promise<null | PullRequestRef>
138
+ getPullRequestSnapshot: (
139
+ input: GetPullRequestSnapshotInput,
140
+ ) => Promise<PullRequestSnapshot>
141
+ squashMergePullRequest: (
142
+ input: SquashMergePullRequestInput,
143
+ ) => Promise<GitCommitTaskResult>
144
+ }
145
+
146
+ export interface CreatePullRequestInput {
147
+ baseBranch: string
148
+ body: string
149
+ headBranch: string
150
+ title: string
151
+ }
152
+
153
+ export interface FindMergedPullRequestByHeadBranchInput {
154
+ headBranch: string
155
+ }
156
+
157
+ export interface FindOpenPullRequestByHeadBranchInput {
158
+ headBranch: string
159
+ }
160
+
161
+ export interface GetPullRequestSnapshotInput {
162
+ pullRequestNumber: number
163
+ }
164
+
165
+ export interface SquashMergePullRequestInput {
166
+ pullRequestNumber: number
167
+ subject: string
168
+ }
169
+
170
+ export interface OrchestratorRuntime {
171
+ git: GitPort
172
+ github: GitHubPort
173
+ store: WorkflowStore
174
+ taskSource: TaskSourceSession
175
+ }
@@ -0,0 +1,85 @@
1
+ import type { TaskSourceSession } from '../task-sources/types'
2
+
3
+ export interface TaskTopologyEntry {
4
+ commitSubject: string
5
+ dependsOn: string[]
6
+ handle: string
7
+ }
8
+
9
+ export interface TaskTopology {
10
+ featureId: string
11
+ maxIterations: number
12
+ tasks: TaskTopologyEntry[]
13
+ }
14
+
15
+ function ensureUniqueHandles(handles: string[]) {
16
+ const seen = new Set<string>()
17
+ for (const handle of handles) {
18
+ if (seen.has(handle)) {
19
+ throw new Error(`Duplicate task handle: ${handle}`)
20
+ }
21
+ seen.add(handle)
22
+ }
23
+ }
24
+
25
+ function ensureDependenciesExist(tasks: TaskTopologyEntry[]) {
26
+ const handles = new Set(tasks.map((task) => task.handle))
27
+ for (const task of tasks) {
28
+ for (const dependency of task.dependsOn) {
29
+ if (!handles.has(dependency)) {
30
+ throw new Error(
31
+ `Unknown dependency: ${dependency} for task ${task.handle}`,
32
+ )
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ function ensureAcyclic(tasks: TaskTopologyEntry[]) {
39
+ const graph = new Map(tasks.map((task) => [task.handle, task.dependsOn]))
40
+ const visiting = new Set<string>()
41
+ const visited = new Set<string>()
42
+
43
+ const visit = (handle: string) => {
44
+ if (visiting.has(handle)) {
45
+ throw new Error(`Dependency cycle detected at ${handle}`)
46
+ }
47
+ if (visited.has(handle)) {
48
+ return
49
+ }
50
+ visiting.add(handle)
51
+ for (const dependency of graph.get(handle) ?? []) {
52
+ visit(dependency)
53
+ }
54
+ visiting.delete(handle)
55
+ visited.add(handle)
56
+ }
57
+
58
+ for (const task of tasks) {
59
+ visit(task.handle)
60
+ }
61
+ }
62
+
63
+ export function buildTaskTopology(
64
+ session: TaskSourceSession,
65
+ featureId: string,
66
+ maxIterations: number,
67
+ ): TaskTopology {
68
+ const handles = session.listTasks()
69
+ ensureUniqueHandles(handles)
70
+
71
+ const tasks = handles.map((handle) => ({
72
+ commitSubject: session.buildCommitSubject(handle),
73
+ dependsOn: session.getTaskDependencies(handle),
74
+ handle,
75
+ }))
76
+
77
+ ensureDependenciesExist(tasks)
78
+ ensureAcyclic(tasks)
79
+
80
+ return {
81
+ featureId,
82
+ maxIterations,
83
+ tasks,
84
+ }
85
+ }
package/src/index.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { inspect } from 'node:util'
2
+
3
+ import arg from 'arg'
4
+
5
+ import { runBatchCommand } from './commands/batch'
6
+ import { runCommand } from './commands/run'
7
+ import { resolveWorkspaceContext } from './runtime/workspace-resolver'
8
+ import { loadWorkflowConfig } from './workflow/config'
9
+
10
+ interface PositionalArgs {
11
+ _: string[]
12
+ }
13
+
14
+ interface RunOptions {
15
+ feature?: string
16
+ untilTaskId?: string
17
+ verbose: boolean
18
+ }
19
+
20
+ interface BatchOptions {
21
+ configPath: string
22
+ verbose: boolean
23
+ }
24
+
25
+ function assertNoPositionalArgs(values: PositionalArgs) {
26
+ if (values._.length !== 0) {
27
+ throw new Error(`Unexpected positional arguments: ${values._.join(' ')}`)
28
+ }
29
+ }
30
+
31
+ function parseRunOptions(args: string[]) {
32
+ const values = arg(
33
+ {
34
+ '--feature': String,
35
+ '--until-task': String,
36
+ '--verbose': Boolean,
37
+ },
38
+ { argv: args },
39
+ )
40
+ assertNoPositionalArgs(values)
41
+ const options: RunOptions = {
42
+ verbose: values['--verbose'] ?? false,
43
+ }
44
+ if (values['--until-task']) {
45
+ options.untilTaskId = values['--until-task']
46
+ }
47
+ if (values['--feature']) {
48
+ options.feature = values['--feature']
49
+ }
50
+ return options
51
+ }
52
+
53
+ function parseBatchOptions(args: string[]) {
54
+ const values = arg(
55
+ {
56
+ '--config': String,
57
+ '--verbose': Boolean,
58
+ },
59
+ { argv: args },
60
+ )
61
+ assertNoPositionalArgs(values)
62
+ const configPath = values['--config']
63
+ if (!configPath) {
64
+ throw new Error('Missing required --config')
65
+ }
66
+ return {
67
+ configPath,
68
+ verbose: values['--verbose'] ?? false,
69
+ } satisfies BatchOptions
70
+ }
71
+
72
+ export async function runCli(argv = process.argv.slice(2)) {
73
+ const [command = 'run', ...args] = argv
74
+ switch (command) {
75
+ case 'batch': {
76
+ const options = parseBatchOptions(args)
77
+ const result = await runBatchCommand({
78
+ configPath: options.configPath,
79
+ cwd: process.cwd(),
80
+ verbose: options.verbose,
81
+ })
82
+ process.stdout.write(
83
+ `${inspect(result, { colors: false, depth: null })}\n`,
84
+ )
85
+ return
86
+ }
87
+ case 'run': {
88
+ const options = parseRunOptions(args)
89
+ const config = await loadWorkflowConfig(process.cwd())
90
+ const context = await resolveWorkspaceContext({
91
+ cwd: process.cwd(),
92
+ ...(options.feature ? { feature: options.feature } : {}),
93
+ taskSource: config.task.source,
94
+ })
95
+ const result = await runCommand(context, {
96
+ config,
97
+ ...(options.untilTaskId ? { untilTaskId: options.untilTaskId } : {}),
98
+ verbose: options.verbose,
99
+ })
100
+ process.stdout.write(
101
+ `${inspect(result, { colors: false, depth: null })}\n`,
102
+ )
103
+ return
104
+ }
105
+ default:
106
+ throw new Error(`Unknown command: ${command}`)
107
+ }
108
+ }
109
+
110
+ export async function main() {
111
+ return runCli()
112
+ }
113
+
114
+ export function handleFatalError(error: unknown) {
115
+ process.stderr.write(
116
+ `${error instanceof Error ? error.message : String(error)}\n`,
117
+ )
118
+ process.exitCode = 1
119
+ }
120
+
121
+ void main().catch(handleFatalError)
@@ -0,0 +1,18 @@
1
+ import type { ImplementAgentInput } from '../agents/types'
2
+
3
+ export async function buildImplementerPrompt(input: ImplementAgentInput) {
4
+ return [
5
+ 'Return JSON only.',
6
+ ...input.prompt.instructions,
7
+ `Task Handle: ${input.taskHandle}`,
8
+ ...[
9
+ ...input.prompt.sections,
10
+ { content: String(input.attempt), title: 'Attempt' },
11
+ { content: String(input.generation), title: 'Generation' },
12
+ {
13
+ content: JSON.stringify(input.lastFindings),
14
+ title: 'Previous Findings',
15
+ },
16
+ ].map((section) => `${section.title}:\n${section.content}`),
17
+ ].join('\n\n')
18
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReviewAgentInput } from '../agents/types'
2
+
3
+ export async function buildReviewerPrompt(input: ReviewAgentInput) {
4
+ return [
5
+ 'Return JSON only.',
6
+ ...input.prompt.instructions,
7
+ `Task Handle: ${input.taskHandle}`,
8
+ ...[
9
+ ...input.prompt.sections,
10
+ { content: String(input.attempt), title: 'Attempt' },
11
+ { content: String(input.generation), title: 'Generation' },
12
+ {
13
+ content: JSON.stringify(input.lastFindings),
14
+ title: 'Previous Findings',
15
+ },
16
+ {
17
+ content: JSON.stringify(input.actualChangedFiles),
18
+ title: 'Actual Changed Files',
19
+ },
20
+ {
21
+ content: JSON.stringify(input.implement),
22
+ title: 'Implement Result',
23
+ },
24
+ ].map((section) => `${section.title}:\n${section.content}`),
25
+ ].join('\n\n')
26
+ }
@@ -0,0 +1,209 @@
1
+ import { appendFile, readFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import * as fsExtra from 'fs-extra'
5
+
6
+ import {
7
+ validateFinalReport,
8
+ validateImplementArtifact,
9
+ validateIntegrateArtifact,
10
+ validateReviewArtifact,
11
+ validateTaskGraph,
12
+ validateWorkflowEvent,
13
+ validateWorkflowState,
14
+ } from '../schema/index'
15
+ import { writeJsonAtomic } from '../utils/fs'
16
+ import { GitRuntime } from './git'
17
+ import { GitHubRuntime } from './github'
18
+ import { createRuntimePaths } from './path-layout'
19
+
20
+ import type { AttemptArtifactKey, OrchestratorRuntime } from '../core/runtime'
21
+ import type { TaskSourceSession } from '../task-sources/types'
22
+
23
+ function createArtifactDir(
24
+ featureDir: string,
25
+ taskId: string,
26
+ generation: number,
27
+ attempt: number,
28
+ ) {
29
+ const runtimePaths = createRuntimePaths(featureDir)
30
+ return path.join(
31
+ runtimePaths.tasksDir,
32
+ taskId,
33
+ `g${generation}`,
34
+ `a${attempt}`,
35
+ )
36
+ }
37
+
38
+ async function readTextFileIfExists(filePath: string) {
39
+ const exists = await fsExtra.pathExists(filePath)
40
+ if (!exists) {
41
+ return null
42
+ }
43
+ return readFile(filePath, 'utf8')
44
+ }
45
+
46
+ async function readValidatedJsonFileIfExists<T>(
47
+ filePath: string,
48
+ validate: (value: unknown) => T,
49
+ ): Promise<null | T> {
50
+ const raw = await readTextFileIfExists(filePath)
51
+ if (raw === null) {
52
+ return null
53
+ }
54
+ return validate(JSON.parse(raw))
55
+ }
56
+
57
+ export interface CreateOrchestratorRuntimeInput {
58
+ featureDir: string
59
+ taskSource?: TaskSourceSession
60
+ workspaceRoot: string
61
+ }
62
+
63
+ export function createOrchestratorRuntime(
64
+ input: CreateOrchestratorRuntimeInput,
65
+ ): OrchestratorRuntime {
66
+ const runtimePaths = createRuntimePaths(input.featureDir)
67
+
68
+ return {
69
+ git: new GitRuntime(input.workspaceRoot, runtimePaths.runtimeDir),
70
+ github: new GitHubRuntime(input.workspaceRoot),
71
+ store: {
72
+ async appendEvent(event) {
73
+ const value = validateWorkflowEvent(event)
74
+ await fsExtra.ensureDir(path.dirname(runtimePaths.events))
75
+ await appendFile(runtimePaths.events, `${JSON.stringify(value)}\n`)
76
+ },
77
+ async loadGraph() {
78
+ return readValidatedJsonFileIfExists(
79
+ runtimePaths.graph,
80
+ validateTaskGraph,
81
+ )
82
+ },
83
+ async loadImplementArtifact(key: AttemptArtifactKey) {
84
+ const filePath = path.join(
85
+ createArtifactDir(
86
+ input.featureDir,
87
+ key.taskHandle,
88
+ key.generation,
89
+ key.attempt,
90
+ ),
91
+ 'implement.json',
92
+ )
93
+ return readValidatedJsonFileIfExists(
94
+ filePath,
95
+ validateImplementArtifact,
96
+ )
97
+ },
98
+ async loadReviewArtifact(key: AttemptArtifactKey) {
99
+ const filePath = path.join(
100
+ createArtifactDir(
101
+ input.featureDir,
102
+ key.taskHandle,
103
+ key.generation,
104
+ key.attempt,
105
+ ),
106
+ 'review.json',
107
+ )
108
+ return readValidatedJsonFileIfExists(filePath, validateReviewArtifact)
109
+ },
110
+ async loadState() {
111
+ return readValidatedJsonFileIfExists(
112
+ runtimePaths.state,
113
+ validateWorkflowState,
114
+ )
115
+ },
116
+ async readReport() {
117
+ return readValidatedJsonFileIfExists(
118
+ runtimePaths.report,
119
+ validateFinalReport,
120
+ )
121
+ },
122
+ async reset() {
123
+ await fsExtra.remove(runtimePaths.runtimeDir)
124
+ },
125
+ async saveGraph(graph) {
126
+ await writeJsonAtomic(runtimePaths.graph, validateTaskGraph(graph))
127
+ },
128
+ async saveImplementArtifact(artifact) {
129
+ const value = validateImplementArtifact(artifact)
130
+ const targetPath = path.join(
131
+ createArtifactDir(
132
+ input.featureDir,
133
+ artifact.taskHandle,
134
+ artifact.generation,
135
+ artifact.attempt,
136
+ ),
137
+ 'implement.json',
138
+ )
139
+ await writeJsonAtomic(targetPath, value)
140
+ },
141
+ async saveIntegrateArtifact(artifact) {
142
+ const value = validateIntegrateArtifact(artifact)
143
+ const targetPath = path.join(
144
+ createArtifactDir(
145
+ input.featureDir,
146
+ artifact.taskHandle,
147
+ artifact.generation,
148
+ artifact.attempt,
149
+ ),
150
+ 'integrate.json',
151
+ )
152
+ await writeJsonAtomic(targetPath, value)
153
+ },
154
+ async saveReport(report) {
155
+ await writeJsonAtomic(runtimePaths.report, validateFinalReport(report))
156
+ },
157
+ async saveReviewArtifact(artifact) {
158
+ const value = validateReviewArtifact(artifact)
159
+ const targetPath = path.join(
160
+ createArtifactDir(
161
+ input.featureDir,
162
+ artifact.taskHandle,
163
+ artifact.generation,
164
+ artifact.attempt,
165
+ ),
166
+ 'review.json',
167
+ )
168
+ await writeJsonAtomic(targetPath, value)
169
+ },
170
+ async saveState(state) {
171
+ await writeJsonAtomic(runtimePaths.state, validateWorkflowState(state))
172
+ },
173
+ },
174
+ taskSource:
175
+ input.taskSource ??
176
+ ({
177
+ async applyTaskCompletion() {
178
+ throw new Error('task source is not configured')
179
+ },
180
+ buildCommitSubject() {
181
+ throw new Error('task source is not configured')
182
+ },
183
+ async buildImplementPrompt() {
184
+ throw new Error('task source is not configured')
185
+ },
186
+ async buildReviewPrompt() {
187
+ throw new Error('task source is not configured')
188
+ },
189
+ async getCompletionCriteria() {
190
+ throw new Error('task source is not configured')
191
+ },
192
+ getTaskDependencies() {
193
+ throw new Error('task source is not configured')
194
+ },
195
+ async isTaskCompleted() {
196
+ throw new Error('task source is not configured')
197
+ },
198
+ listTasks() {
199
+ throw new Error('task source is not configured')
200
+ },
201
+ resolveTaskSelector() {
202
+ throw new Error('task source is not configured')
203
+ },
204
+ async revertTaskCompletion() {
205
+ throw new Error('task source is not configured')
206
+ },
207
+ } satisfies TaskSourceSession),
208
+ }
209
+ }