digital-tasks 2.1.1 → 2.3.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +560 -0
  3. package/package.json +20 -5
  4. package/src/client.ts +268 -0
  5. package/src/index.ts +12 -10
  6. package/src/markdown.ts +63 -48
  7. package/src/project.ts +57 -42
  8. package/src/queue.ts +76 -37
  9. package/src/task.ts +132 -75
  10. package/src/types.ts +177 -40
  11. package/src/worker.ts +959 -0
  12. package/test/project.test.ts +28 -84
  13. package/test/queue.test.ts +51 -24
  14. package/test/task.test.ts +80 -27
  15. package/test/worker.test.ts +1158 -0
  16. package/tsconfig.json +2 -13
  17. package/vitest.config.ts +48 -0
  18. package/wrangler.jsonc +44 -0
  19. package/.turbo/turbo-build.log +0 -5
  20. package/dist/function-task.d.ts +0 -319
  21. package/dist/function-task.d.ts.map +0 -1
  22. package/dist/function-task.js +0 -286
  23. package/dist/function-task.js.map +0 -1
  24. package/dist/index.d.ts +0 -72
  25. package/dist/index.d.ts.map +0 -1
  26. package/dist/index.js +0 -74
  27. package/dist/index.js.map +0 -1
  28. package/dist/markdown.d.ts +0 -112
  29. package/dist/markdown.d.ts.map +0 -1
  30. package/dist/markdown.js +0 -510
  31. package/dist/markdown.js.map +0 -1
  32. package/dist/project.d.ts +0 -259
  33. package/dist/project.d.ts.map +0 -1
  34. package/dist/project.js +0 -397
  35. package/dist/project.js.map +0 -1
  36. package/dist/queue.d.ts +0 -17
  37. package/dist/queue.d.ts.map +0 -1
  38. package/dist/queue.js +0 -347
  39. package/dist/queue.js.map +0 -1
  40. package/dist/task.d.ts +0 -69
  41. package/dist/task.d.ts.map +0 -1
  42. package/dist/task.js +0 -321
  43. package/dist/task.js.map +0 -1
  44. package/dist/types.d.ts +0 -292
  45. package/dist/types.d.ts.map +0 -1
  46. package/dist/types.js +0 -15
  47. package/dist/types.js.map +0 -1
  48. package/src/index.js +0 -73
  49. package/src/markdown.js +0 -509
  50. package/src/project.js +0 -396
  51. package/src/queue.js +0 -346
  52. package/src/task.js +0 -320
  53. package/src/types.js +0 -14
package/src/task.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  WorkerRef,
12
12
  TaskResult,
13
13
  TaskDependency,
14
+ TaskProgress,
14
15
  } from './types.js'
15
16
  import { taskQueue } from './queue.js'
16
17
 
@@ -27,7 +28,7 @@ function generateTaskId(): string {
27
28
  * @example
28
29
  * ```ts
29
30
  * const task = await createTask({
30
- * function: {
31
+ * tool: {
31
32
  * type: 'generative',
32
33
  * name: 'summarize',
33
34
  * args: { text: 'The text to summarize' },
@@ -44,6 +45,14 @@ export async function createTask<TInput = unknown, TOutput = unknown>(
44
45
  ): Promise<Task<TInput, TOutput>> {
45
46
  const now = new Date()
46
47
 
48
+ // Resolve the underlying Tool from either `tool` (preferred) or
49
+ // the legacy `function` alias. Exactly one path is required; throw
50
+ // a clear error if neither is set so callers see this at runtime.
51
+ const tool = options.tool ?? options.function
52
+ if (!tool) {
53
+ throw new Error("createTask requires a 'tool' (preferred) or legacy 'function' option")
54
+ }
55
+
47
56
  // Convert string dependencies to TaskDependency array
48
57
  let dependencies: TaskDependency[] | undefined
49
58
  if (options.dependencies && options.dependencies.length > 0) {
@@ -54,10 +63,10 @@ export async function createTask<TInput = unknown, TOutput = unknown>(
54
63
  }))
55
64
  }
56
65
 
57
- // Determine allowed workers from function type
66
+ // Determine allowed workers from the Tool's function type
58
67
  let allowedWorkers = options.allowedWorkers
59
68
  if (!allowedWorkers) {
60
- const funcType = options.function.type
69
+ const funcType = tool.type
61
70
  if (funcType === 'human') {
62
71
  allowedWorkers = ['human']
63
72
  } else if (funcType === 'agentic') {
@@ -69,27 +78,70 @@ export async function createTask<TInput = unknown, TOutput = unknown>(
69
78
 
70
79
  const task: Task<TInput, TOutput> = {
71
80
  id: generateTaskId(),
72
- function: options.function,
81
+ $type: 'Task',
82
+ // Action supertype: every Task is an Action of some Verb. Default
83
+ // the verb to the underlying Tool's name when not supplied.
84
+ verb: tool.name,
85
+ // Tool is the canonical field (CONTEXT.md). The legacy `function`
86
+ // alias is populated alongside for the duration of the deprecation
87
+ // window so older readers continue to work.
88
+ tool,
89
+ function: tool,
73
90
  status: options.scheduledFor ? 'pending' : 'queued',
74
91
  priority: options.priority || 'normal',
75
- input: options.input,
76
92
  allowedWorkers,
77
- dependencies,
78
- scheduledFor: options.scheduledFor,
79
- deadline: options.deadline,
80
- timeout: options.timeout,
81
- tags: options.tags,
82
- parentId: options.parentId,
83
- projectId: options.projectId,
84
- metadata: options.metadata,
85
93
  createdAt: now,
86
94
  events: [],
87
95
  }
96
+ if (options.input !== undefined) {
97
+ task.input = options.input
98
+ }
99
+ if (dependencies !== undefined) {
100
+ task.dependencies = dependencies
101
+ }
102
+ if (options.scheduledFor !== undefined) {
103
+ task.scheduledFor = options.scheduledFor
104
+ }
105
+ if (options.deadline !== undefined) {
106
+ task.deadline = options.deadline
107
+ }
108
+ if (options.timeout !== undefined) {
109
+ task.timeout = options.timeout
110
+ }
111
+ if (options.tags !== undefined) {
112
+ task.tags = options.tags
113
+ }
114
+ if (options.parentId !== undefined) {
115
+ task.parentId = options.parentId
116
+ }
117
+ if (options.projectId !== undefined) {
118
+ task.projectId = options.projectId
119
+ }
120
+ if (options.metadata !== undefined) {
121
+ task.metadata = options.metadata
122
+ }
123
+ // Issue-shaped fields (SVO co-design)
124
+ if (options.title !== undefined) {
125
+ task.title = options.title
126
+ }
127
+ if (options.body !== undefined) {
128
+ task.body = options.body
129
+ }
130
+ if (options.labels !== undefined) {
131
+ task.labels = options.labels
132
+ }
133
+ if (options.project !== undefined) {
134
+ task.project = options.project
135
+ }
136
+ if (options.assignees !== undefined) {
137
+ task.assignees = options.assignees
138
+ }
88
139
 
89
- // Auto-assign if specified
90
- if (options.assignTo) {
140
+ // Auto-assign if specified (single primary worker takes precedence)
141
+ const primaryAssignee = options.assignTo ?? options.assignees?.[0]
142
+ if (primaryAssignee) {
91
143
  task.assignment = {
92
- worker: options.assignTo,
144
+ worker: primaryAssignee,
93
145
  assignedAt: now,
94
146
  }
95
147
  task.status = 'assigned'
@@ -97,9 +149,7 @@ export async function createTask<TInput = unknown, TOutput = unknown>(
97
149
 
98
150
  // Check if blocked by dependencies
99
151
  if (dependencies && dependencies.length > 0) {
100
- const hasPendingDeps = dependencies.some(
101
- (d) => d.type === 'blocked_by' && !d.satisfied
102
- )
152
+ const hasPendingDeps = dependencies.some((d) => d.type === 'blocked_by' && !d.satisfied)
103
153
  if (hasPendingDeps) {
104
154
  task.status = 'blocked'
105
155
  }
@@ -121,10 +171,7 @@ export async function getTask(id: string): Promise<AnyTask | undefined> {
121
171
  /**
122
172
  * Start working on a task
123
173
  */
124
- export async function startTask(
125
- taskId: string,
126
- worker: WorkerRef
127
- ): Promise<AnyTask | undefined> {
174
+ export async function startTask(taskId: string, worker: WorkerRef): Promise<AnyTask | undefined> {
128
175
  const task = await taskQueue.get(taskId)
129
176
  if (!task) return undefined
130
177
 
@@ -153,12 +200,15 @@ export async function updateProgress(
153
200
  percent: number,
154
201
  step?: string
155
202
  ): Promise<AnyTask | undefined> {
203
+ const progressUpdate: Partial<TaskProgress> = {
204
+ percent,
205
+ updatedAt: new Date(),
206
+ }
207
+ if (step !== undefined) {
208
+ progressUpdate.step = step
209
+ }
156
210
  return taskQueue.update(taskId, {
157
- progress: {
158
- percent,
159
- step,
160
- updatedAt: new Date(),
161
- },
211
+ progress: progressUpdate,
162
212
  event: {
163
213
  type: 'progress',
164
214
  message: step || `Progress: ${percent}%`,
@@ -188,28 +238,27 @@ export async function completeTask<TOutput>(
188
238
 
189
239
  await taskQueue.complete(taskId, output)
190
240
 
241
+ const metadata: TaskResult<TOutput>['metadata'] = {
242
+ duration: task.startedAt ? Date.now() - task.startedAt.getTime() : 0,
243
+ startedAt: task.startedAt || new Date(),
244
+ completedAt: new Date(),
245
+ }
246
+ if (task.assignment?.worker !== undefined) {
247
+ metadata.worker = task.assignment.worker
248
+ }
249
+
191
250
  return {
192
251
  taskId,
193
252
  success: true,
194
253
  output,
195
- metadata: {
196
- duration: task.startedAt
197
- ? Date.now() - task.startedAt.getTime()
198
- : 0,
199
- startedAt: task.startedAt || new Date(),
200
- completedAt: new Date(),
201
- worker: task.assignment?.worker,
202
- },
254
+ metadata,
203
255
  }
204
256
  }
205
257
 
206
258
  /**
207
259
  * Fail a task with error
208
260
  */
209
- export async function failTask(
210
- taskId: string,
211
- error: string | Error
212
- ): Promise<TaskResult> {
261
+ export async function failTask(taskId: string, error: string | Error): Promise<TaskResult> {
213
262
  const task = await taskQueue.get(taskId)
214
263
  if (!task) {
215
264
  return {
@@ -226,6 +275,15 @@ export async function failTask(
226
275
 
227
276
  await taskQueue.fail(taskId, errorMessage)
228
277
 
278
+ const failMetadata: TaskResult['metadata'] = {
279
+ duration: task.startedAt ? Date.now() - task.startedAt.getTime() : 0,
280
+ startedAt: task.startedAt || new Date(),
281
+ completedAt: new Date(),
282
+ }
283
+ if (task.assignment?.worker !== undefined) {
284
+ failMetadata.worker = task.assignment.worker
285
+ }
286
+
229
287
  return {
230
288
  taskId,
231
289
  success: false,
@@ -234,24 +292,14 @@ export async function failTask(
234
292
  message: errorMessage,
235
293
  details: error instanceof Error ? { stack: error.stack } : undefined,
236
294
  },
237
- metadata: {
238
- duration: task.startedAt
239
- ? Date.now() - task.startedAt.getTime()
240
- : 0,
241
- startedAt: task.startedAt || new Date(),
242
- completedAt: new Date(),
243
- worker: task.assignment?.worker,
244
- },
295
+ metadata: failMetadata,
245
296
  }
246
297
  }
247
298
 
248
299
  /**
249
300
  * Cancel a task
250
301
  */
251
- export async function cancelTask(
252
- taskId: string,
253
- reason?: string
254
- ): Promise<boolean> {
302
+ export async function cancelTask(taskId: string, reason?: string): Promise<boolean> {
255
303
  const task = await taskQueue.get(taskId)
256
304
  if (!task) return false
257
305
 
@@ -274,13 +322,14 @@ export async function addComment(
274
322
  comment: string,
275
323
  author?: WorkerRef
276
324
  ): Promise<AnyTask | undefined> {
277
- return taskQueue.update(taskId, {
278
- event: {
279
- type: 'comment',
280
- actor: author,
281
- message: comment,
282
- },
283
- })
325
+ const event: Omit<import('./types.js').TaskEvent, 'id' | 'timestamp'> = {
326
+ type: 'comment',
327
+ message: comment,
328
+ }
329
+ if (author !== undefined) {
330
+ event.actor = author
331
+ }
332
+ return taskQueue.update(taskId, { event })
284
333
  }
285
334
 
286
335
  /**
@@ -329,22 +378,37 @@ export async function waitForTask(
329
378
  }
330
379
 
331
380
  if (task.status === 'completed') {
381
+ const completedMeta: TaskResult['metadata'] = {
382
+ duration:
383
+ task.completedAt && task.startedAt
384
+ ? task.completedAt.getTime() - task.startedAt.getTime()
385
+ : 0,
386
+ startedAt: task.startedAt || task.createdAt,
387
+ completedAt: task.completedAt || new Date(),
388
+ }
389
+ if (task.assignment?.worker !== undefined) {
390
+ completedMeta.worker = task.assignment.worker
391
+ }
332
392
  return {
333
393
  taskId,
334
394
  success: true,
335
395
  output: task.output,
336
- metadata: {
337
- duration: task.completedAt && task.startedAt
338
- ? task.completedAt.getTime() - task.startedAt.getTime()
339
- : 0,
340
- startedAt: task.startedAt || task.createdAt,
341
- completedAt: task.completedAt || new Date(),
342
- worker: task.assignment?.worker,
343
- },
396
+ metadata: completedMeta,
344
397
  }
345
398
  }
346
399
 
347
400
  if (task.status === 'failed') {
401
+ const failedMeta: TaskResult['metadata'] = {
402
+ duration:
403
+ task.completedAt && task.startedAt
404
+ ? task.completedAt.getTime() - task.startedAt.getTime()
405
+ : 0,
406
+ startedAt: task.startedAt || task.createdAt,
407
+ completedAt: task.completedAt || new Date(),
408
+ }
409
+ if (task.assignment?.worker !== undefined) {
410
+ failedMeta.worker = task.assignment.worker
411
+ }
348
412
  return {
349
413
  taskId,
350
414
  success: false,
@@ -352,14 +416,7 @@ export async function waitForTask(
352
416
  code: 'TASK_FAILED',
353
417
  message: task.error || 'Task failed',
354
418
  },
355
- metadata: {
356
- duration: task.completedAt && task.startedAt
357
- ? task.completedAt.getTime() - task.startedAt.getTime()
358
- : 0,
359
- startedAt: task.startedAt || task.createdAt,
360
- completedAt: task.completedAt || new Date(),
361
- worker: task.assignment?.worker,
362
- },
419
+ metadata: failedMeta,
363
420
  }
364
421
  }
365
422
 
package/src/types.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  /**
2
2
  * Types for digital-tasks
3
3
  *
4
- * Task = Function + metadata (status, progress, assignment, dependencies)
4
+ * Task = Action + issue-shaped metadata (title, body, comments, labels,
5
+ * dependencies, assignees, project). A Task is a specialization of an
6
+ * `Action` (`digital-objects.Action`) with project-management overlay,
7
+ * per the SVO co-design (`docs/plans/2026-05-05-svo-co-design.md`).
5
8
  *
6
- * Every task is a function call. The function can be:
9
+ * Every task wraps a callable Verb historically the `function` field
10
+ * (an `ai-functions.FunctionDefinition`). Per CONTEXT.md the canonical
11
+ * name for callable Verbs is **Tool**; `function` is being renamed to
12
+ * `tool` over time. See the `function` / `tool` fields below.
13
+ *
14
+ * The function (now Tool) can be:
7
15
  * - Code: generates executable code
8
16
  * - Generative: AI generates content (no tools)
9
17
  * - Agentic: AI with tools in a loop
@@ -19,6 +27,7 @@ import type {
19
27
  AgenticFunctionDefinition,
20
28
  HumanFunctionDefinition,
21
29
  } from 'ai-functions'
30
+ import type { Action } from 'digital-objects'
22
31
 
23
32
  // Re-export function types for convenience
24
33
  export type {
@@ -29,6 +38,9 @@ export type {
29
38
  HumanFunctionDefinition,
30
39
  }
31
40
 
41
+ // Re-export Action so consumers can see the supertype
42
+ export type { Action }
43
+
32
44
  // ============================================================================
33
45
  // Task Status and Priority
34
46
  // ============================================================================
@@ -37,15 +49,15 @@ export type {
37
49
  * Task lifecycle status
38
50
  */
39
51
  export type TaskStatus =
40
- | 'pending' // Created but not started
41
- | 'queued' // In queue waiting for worker
42
- | 'assigned' // Assigned to a worker
43
- | 'in_progress' // Being worked on
44
- | 'blocked' // Waiting on dependency
45
- | 'review' // Awaiting review
46
- | 'completed' // Successfully finished
47
- | 'failed' // Failed with error
48
- | 'cancelled' // Cancelled
52
+ | 'pending' // Created but not started
53
+ | 'queued' // In queue waiting for worker
54
+ | 'assigned' // Assigned to a worker
55
+ | 'in_progress' // Being worked on
56
+ | 'blocked' // Waiting on dependency
57
+ | 'review' // Awaiting review
58
+ | 'completed' // Successfully finished
59
+ | 'failed' // Failed with error
60
+ | 'cancelled' // Cancelled
49
61
 
50
62
  /**
51
63
  * Task priority levels
@@ -89,11 +101,11 @@ export interface TaskAssignment {
89
101
  * Dependency type
90
102
  */
91
103
  export type DependencyType =
92
- | 'blocks' // This task blocks another
93
- | 'blocked_by' // This task is blocked by another
94
- | 'related_to' // Related but not blocking
95
- | 'parent' // Parent task
96
- | 'child' // Child subtask
104
+ | 'blocks' // This task blocks another
105
+ | 'blocked_by' // This task is blocked by another
106
+ | 'related_to' // Related but not blocking
107
+ | 'parent' // Parent task
108
+ | 'child' // Child subtask
97
109
 
98
110
  /**
99
111
  * Task dependency
@@ -125,7 +137,17 @@ export interface TaskProgress {
125
137
  */
126
138
  export interface TaskEvent {
127
139
  id: string
128
- type: 'created' | 'assigned' | 'started' | 'progress' | 'blocked' | 'unblocked' | 'completed' | 'failed' | 'cancelled' | 'comment'
140
+ type:
141
+ | 'created'
142
+ | 'assigned'
143
+ | 'started'
144
+ | 'progress'
145
+ | 'blocked'
146
+ | 'unblocked'
147
+ | 'completed'
148
+ | 'failed'
149
+ | 'cancelled'
150
+ | 'comment'
129
151
  timestamp: Date
130
152
  actor?: WorkerRef
131
153
  data?: unknown
@@ -133,23 +155,101 @@ export interface TaskEvent {
133
155
  }
134
156
 
135
157
  // ============================================================================
136
- // Core Task Interface
158
+ // Comments (issue-shaped child Actions)
137
159
  // ============================================================================
138
160
 
139
161
  /**
140
- * Task = Function + metadata
162
+ * Comment - a thin wrapper around a comment posted on a Task.
141
163
  *
142
- * A task wraps a function (code, generative, agentic, or human)
143
- * with lifecycle management, assignment, and dependencies.
164
+ * Per the SVO co-design, comments are child Actions of verb 'commented'
165
+ * whose `cause` role points back at the parent Task's Action id. This
166
+ * shape is the surfaced view of such an Action for issue-shaped UIs.
144
167
  */
145
- export interface Task<TInput = unknown, TOutput = unknown> {
146
- /** Unique task ID */
168
+ export interface Comment {
169
+ /** Comment id (matches the underlying Action id when persisted) */
147
170
  id: string
171
+ /** Markdown body of the comment */
172
+ body: string
173
+ /** Who posted the comment */
174
+ author?: WorkerRef
175
+ /** When the comment was posted */
176
+ createdAt: Date
177
+ }
148
178
 
149
- /** The function this task executes */
150
- function: FunctionDefinition<TInput, TOutput>
179
+ // ============================================================================
180
+ // Core Task Interface
181
+ // ============================================================================
151
182
 
152
- /** Current status */
183
+ /**
184
+ * Task = Action + issue-shaped metadata
185
+ *
186
+ * A Task is a specialization of `digital-objects.Action` carrying
187
+ * project-management overlay (title, body, comments, labels,
188
+ * dependencies, assignees, milestone/project). The Action supertype
189
+ * provides id/verb/subject/object/roles/data/createdAt/completedAt.
190
+ *
191
+ * Status: Action's status taxonomy is lifecycle-only
192
+ * (pending/active/completed/failed/cancelled). Task's status taxonomy
193
+ * is project-management shaped (queued/assigned/in_progress/blocked/
194
+ * review/...). We omit Action's `status` and replace it with the
195
+ * Task-specific `TaskStatus`. Both supersets share `pending`,
196
+ * `completed`, `failed`, `cancelled`.
197
+ *
198
+ * Verb: every Task is an Action of some Verb. For tasks created via
199
+ * the legacy `createTask({ function })` path, `verb` defaults to the
200
+ * function's name (e.g. `'summarize'`).
201
+ *
202
+ * Backward compatibility: all previously-valid Task shapes remain
203
+ * assignable. Newly added fields (title/body/labels/project/assignees/
204
+ * comments/$type/verb) are optional or have defaults populated by
205
+ * `createTask`.
206
+ */
207
+ export type Task<TInput = unknown, TOutput = unknown> = Omit<Action<TInput>, 'status'> & {
208
+ /** MDXLD type discriminator */
209
+ $type?: 'Task'
210
+
211
+ /**
212
+ * The Tool (callable Verb) this task executes. This is the canonical
213
+ * field per CONTEXT.md. `createTask` always populates `tool`; the
214
+ * legacy `function` alias is also populated during the deprecation
215
+ * window so existing readers keep working.
216
+ *
217
+ * Optional on the type so that older literals constructed with only
218
+ * `function` still typecheck; in practice every Task has exactly one
219
+ * underlying callable Verb available via `task.tool ?? task.function`.
220
+ */
221
+ tool?: FunctionDefinition<TInput, TOutput>
222
+
223
+ /**
224
+ * Legacy alias for `tool`.
225
+ *
226
+ * @deprecated Use `tool` instead. The canonical name for callable
227
+ * Verbs is **Tool** (see CONTEXT.md). `createTask` continues to
228
+ * populate this alongside `tool` for the duration of the deprecation
229
+ * window. Slated for removal in the next major version.
230
+ */
231
+ function?: FunctionDefinition<TInput, TOutput>
232
+
233
+ /** Issue title (short, single-line) */
234
+ title?: string
235
+
236
+ /** Rich markdown body / description */
237
+ body?: string
238
+
239
+ /** GitHub-issue-shaped labels */
240
+ labels?: string[]
241
+
242
+ /** Milestone or project reference (string ref; cf. `projectId` for
243
+ * the durable internal id when a `Project` exists in this package). */
244
+ project?: string
245
+
246
+ /** Comments posted on this task (child Actions of verb 'commented') */
247
+ comments?: Comment[]
248
+
249
+ /** Workers assigned to this task */
250
+ assignees?: WorkerRef[]
251
+
252
+ /** Current task status (specialization — see jsdoc above) */
153
253
  status: TaskStatus
154
254
 
155
255
  /** Priority level */
@@ -166,10 +266,10 @@ export interface Task<TInput = unknown, TOutput = unknown> {
166
266
  error?: string
167
267
 
168
268
  // Assignment
169
- /** Who can work on this */
269
+ /** Worker types permitted to claim this task */
170
270
  allowedWorkers?: WorkerType[]
171
271
 
172
- /** Current assignment */
272
+ /** Current assignment (single primary worker) */
173
273
  assignment?: TaskAssignment
174
274
 
175
275
  // Dependencies
@@ -181,9 +281,6 @@ export interface Task<TInput = unknown, TOutput = unknown> {
181
281
  progress?: TaskProgress
182
282
 
183
283
  // Timing
184
- /** When created */
185
- createdAt: Date
186
-
187
284
  /** Scheduled start time */
188
285
  scheduledFor?: Date
189
286
 
@@ -193,9 +290,6 @@ export interface Task<TInput = unknown, TOutput = unknown> {
193
290
  /** When started */
194
291
  startedAt?: Date
195
292
 
196
- /** When completed */
197
- completedAt?: Date
198
-
199
293
  /** Timeout in ms */
200
294
  timeout?: number
201
295
 
@@ -203,7 +297,8 @@ export interface Task<TInput = unknown, TOutput = unknown> {
203
297
  /** Parent task ID */
204
298
  parentId?: string
205
299
 
206
- /** Project ID */
300
+ /** Project ID (internal `Project.id` when this Task belongs to a
301
+ * Project in this package; distinct from `project` ref string) */
207
302
  projectId?: string
208
303
 
209
304
  // Metadata
@@ -217,6 +312,18 @@ export interface Task<TInput = unknown, TOutput = unknown> {
217
312
  events?: TaskEvent[]
218
313
  }
219
314
 
315
+ /**
316
+ * Task dependency tuple — minimal shape from the SVO design doc.
317
+ *
318
+ * `TaskDependency` (above) is the richer in-package shape with a
319
+ * full DependencyType set; `TaskDep` is the issue-shaped pair from
320
+ * the design doc, retained as a convenience export.
321
+ */
322
+ export interface TaskDep {
323
+ taskId: string
324
+ type: 'blocked_by' | 'related'
325
+ }
326
+
220
327
  /**
221
328
  * Any task (for collections)
222
329
  */
@@ -231,7 +338,10 @@ export type AnyTask = Task<any, any>
231
338
  * Create task from a Code function
232
339
  */
233
340
  export interface CodeTaskOptions<TInput = unknown, TOutput = unknown> {
234
- function: CodeFunctionDefinition<TInput, TOutput>
341
+ /** Tool (callable Verb) to run. Preferred over `function`. */
342
+ tool?: CodeFunctionDefinition<TInput, TOutput>
343
+ /** @deprecated Use `tool` instead. */
344
+ function?: CodeFunctionDefinition<TInput, TOutput>
235
345
  input?: TInput
236
346
  priority?: TaskPriority
237
347
  assignTo?: WorkerRef
@@ -246,7 +356,10 @@ export interface CodeTaskOptions<TInput = unknown, TOutput = unknown> {
246
356
  * Create task from a Generative function
247
357
  */
248
358
  export interface GenerativeTaskOptions<TInput = unknown, TOutput = unknown> {
249
- function: GenerativeFunctionDefinition<TInput, TOutput>
359
+ /** Tool (callable Verb) to run. Preferred over `function`. */
360
+ tool?: GenerativeFunctionDefinition<TInput, TOutput>
361
+ /** @deprecated Use `tool` instead. */
362
+ function?: GenerativeFunctionDefinition<TInput, TOutput>
250
363
  input?: TInput
251
364
  priority?: TaskPriority
252
365
  assignTo?: WorkerRef
@@ -261,7 +374,10 @@ export interface GenerativeTaskOptions<TInput = unknown, TOutput = unknown> {
261
374
  * Create task from an Agentic function
262
375
  */
263
376
  export interface AgenticTaskOptions<TInput = unknown, TOutput = unknown> {
264
- function: AgenticFunctionDefinition<TInput, TOutput>
377
+ /** Tool (callable Verb) to run. Preferred over `function`. */
378
+ tool?: AgenticFunctionDefinition<TInput, TOutput>
379
+ /** @deprecated Use `tool` instead. */
380
+ function?: AgenticFunctionDefinition<TInput, TOutput>
265
381
  input?: TInput
266
382
  priority?: TaskPriority
267
383
  assignTo?: WorkerRef
@@ -276,7 +392,10 @@ export interface AgenticTaskOptions<TInput = unknown, TOutput = unknown> {
276
392
  * Create task from a Human function
277
393
  */
278
394
  export interface HumanTaskOptions<TInput = unknown, TOutput = unknown> {
279
- function: HumanFunctionDefinition<TInput, TOutput>
395
+ /** Tool (callable Verb) to run. Preferred over `function`. */
396
+ tool?: HumanFunctionDefinition<TInput, TOutput>
397
+ /** @deprecated Use `tool` instead. */
398
+ function?: HumanFunctionDefinition<TInput, TOutput>
280
399
  input?: TInput
281
400
  priority?: TaskPriority
282
401
  assignTo?: WorkerRef
@@ -289,13 +408,22 @@ export interface HumanTaskOptions<TInput = unknown, TOutput = unknown> {
289
408
 
290
409
  /**
291
410
  * Generic task creation options
411
+ *
412
+ * Either `tool` (preferred) or the deprecated `function` alias must be
413
+ * provided. `createTask` normalizes to `tool` and also populates the
414
+ * legacy `function` alias on the resulting Task for backward compat.
292
415
  */
293
416
  export interface CreateTaskOptions<TInput = unknown, TOutput = unknown> {
294
- function: FunctionDefinition<TInput, TOutput>
417
+ /** Tool (callable Verb) to run. Preferred over `function`. */
418
+ tool?: FunctionDefinition<TInput, TOutput>
419
+ /** @deprecated Use `tool` instead. */
420
+ function?: FunctionDefinition<TInput, TOutput>
295
421
  input?: TInput
296
422
  priority?: TaskPriority
297
423
  allowedWorkers?: WorkerType[]
298
424
  assignTo?: WorkerRef
425
+ /** Multiple assignees (issue-shaped); first becomes the primary `assignment` */
426
+ assignees?: WorkerRef[]
299
427
  dependencies?: string[]
300
428
  scheduledFor?: Date
301
429
  deadline?: Date
@@ -304,6 +432,15 @@ export interface CreateTaskOptions<TInput = unknown, TOutput = unknown> {
304
432
  parentId?: string
305
433
  projectId?: string
306
434
  metadata?: Record<string, unknown>
435
+ // Issue-shaped fields (SVO co-design)
436
+ /** Issue title (short, single-line) */
437
+ title?: string
438
+ /** Rich markdown body / description */
439
+ body?: string
440
+ /** GitHub-issue-shaped labels */
441
+ labels?: string[]
442
+ /** Milestone or project ref string */
443
+ project?: string
307
444
  }
308
445
 
309
446
  /**