@swarmclawai/swarmclaw 1.8.13 → 1.9.2

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 (30) hide show
  1. package/README.md +19 -0
  2. package/package.json +3 -3
  3. package/scripts/ensure-sandbox-browser-image.mjs +12 -2
  4. package/src/app/api/knowledge/hygiene/route.ts +19 -1
  5. package/src/app/api/portability/export/route.test.ts +17 -0
  6. package/src/app/api/portability/export/route.ts +11 -2
  7. package/src/app/api/tasks/task-workspace-route.test.ts +112 -0
  8. package/src/components/tasks/task-card.tsx +49 -1
  9. package/src/components/tasks/task-sheet.tsx +173 -1
  10. package/src/components/ui/info-chip.tsx +3 -2
  11. package/src/features/tasks/queries.ts +2 -1
  12. package/src/lib/server/agents/delegation-advisory.test.ts +1 -0
  13. package/src/lib/server/agents/delegation-advisory.ts +10 -0
  14. package/src/lib/server/chat-execution/iteration-event-handler.ts +24 -8
  15. package/src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts +117 -0
  16. package/src/lib/server/chat-execution/reasoning-tag-scrubber.ts +219 -0
  17. package/src/lib/server/knowledge-sources.test.ts +45 -0
  18. package/src/lib/server/knowledge-sources.ts +33 -0
  19. package/src/lib/server/portability/export.ts +10 -0
  20. package/src/lib/server/session-tools/crud.ts +25 -2
  21. package/src/lib/server/session-tools/manage-tasks.test.ts +7 -2
  22. package/src/lib/server/tasks/task-execution-workspace.test.ts +117 -0
  23. package/src/lib/server/tasks/task-execution-workspace.ts +321 -0
  24. package/src/lib/server/tasks/task-route-service.ts +87 -9
  25. package/src/lib/server/tasks/task-service.test.ts +60 -2
  26. package/src/lib/server/tasks/task-service.ts +35 -0
  27. package/src/lib/tasks.ts +13 -5
  28. package/src/lib/validation/schemas.ts +19 -0
  29. package/src/types/misc.ts +1 -1
  30. package/src/types/task.ts +62 -0
@@ -20,6 +20,28 @@ const TASK_STATUS_VALUES = new Set([
20
20
  'archived',
21
21
  ])
22
22
 
23
+ const ASSIGNMENT_START_WORKFLOW_STATES = new Set(['triage', 'backlog', 'todo'])
24
+
25
+ function hasOwn(record: Record<string, unknown>, key: string): boolean {
26
+ return Object.prototype.hasOwnProperty.call(record, key)
27
+ }
28
+
29
+ export function resolveAssignmentWorkflowStateTransition(params: {
30
+ previousAgentId?: string | null
31
+ nextAgentId?: string | null
32
+ previousWorkflowStateId?: string | null
33
+ explicitWorkflowState?: boolean
34
+ }): string | null {
35
+ if (params.explicitWorkflowState) return null
36
+ const previousAgentId = typeof params.previousAgentId === 'string' ? params.previousAgentId.trim() : ''
37
+ const nextAgentId = typeof params.nextAgentId === 'string' ? params.nextAgentId.trim() : ''
38
+ if (!nextAgentId || nextAgentId === previousAgentId) return null
39
+ const currentWorkflow = typeof params.previousWorkflowStateId === 'string' && params.previousWorkflowStateId.trim()
40
+ ? params.previousWorkflowStateId.trim()
41
+ : 'backlog'
42
+ return ASSIGNMENT_START_WORKFLOW_STATES.has(currentWorkflow) ? 'in_progress' : null
43
+ }
44
+
23
45
  export function deriveTaskTitle(input: { title?: unknown; description?: unknown }): string {
24
46
  const explicit = typeof input.title === 'string' ? input.title.replace(/\s+/g, ' ').trim() : ''
25
47
  if (explicit && !/^untitled task$/i.test(explicit)) return explicit.slice(0, 120)
@@ -157,6 +179,7 @@ export type PrepareTaskCreationResult =
157
179
 
158
180
  export function prepareTaskCreation(options: PrepareTaskCreationOptions): PrepareTaskCreationResult {
159
181
  const seed = options.seed ? { ...options.seed } : {}
182
+ delete seed.workType
160
183
  const explicitTitle = typeof options.input.title === 'string' ? options.input.title.trim() : ''
161
184
  const derivedTitle = deriveTaskTitle(options.input)
162
185
  const nextTitle = options.deriveTitleFromDescription
@@ -194,6 +217,9 @@ export function prepareTaskCreation(options: PrepareTaskCreationOptions): Prepar
194
217
  qualityGate,
195
218
  },
196
219
  })
220
+ if (!task.workflowStateId && task.agentId) {
221
+ task.workflowStateId = 'in_progress'
222
+ }
197
223
  task.fingerprint = computeTaskFingerprint(task.title || 'Untitled Task', task.agentId || '')
198
224
 
199
225
  const duplicate = task.fingerprint
@@ -227,6 +253,8 @@ export interface ApplyTaskPatchOptions {
227
253
 
228
254
  export function applyTaskPatch(options: ApplyTaskPatchOptions): BoardTask {
229
255
  const nextPatch = { ...options.patch }
256
+ const previousAgentId = options.task.agentId
257
+ const previousWorkflowStateId = options.task.workflowStateId || null
230
258
  if (Object.prototype.hasOwnProperty.call(nextPatch, 'status')) {
231
259
  const normalized = normalizeTaskStatusInput(nextPatch.status, options.task.status)
232
260
  if (normalized) nextPatch.status = normalized
@@ -240,6 +268,13 @@ export function applyTaskPatch(options: ApplyTaskPatchOptions): BoardTask {
240
268
 
241
269
  Object.assign(options.task, nextPatch, { updatedAt: options.now })
242
270
  if (options.clearProjectIdWhenNull && nextPatch.projectId === null) delete options.task.projectId
271
+ const workflowTransition = resolveAssignmentWorkflowStateTransition({
272
+ previousAgentId,
273
+ nextAgentId: options.task.agentId,
274
+ previousWorkflowStateId,
275
+ explicitWorkflowState: hasOwn(nextPatch, 'workflowStateId'),
276
+ })
277
+ if (workflowTransition) options.task.workflowStateId = workflowTransition
243
278
 
244
279
  if (options.task.status === 'completed') {
245
280
  const { validation } = refreshTaskCompletionValidation(options.task, options.settings)
package/src/lib/tasks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { api } from './app/api-client'
2
- import type { BoardTask } from '../types'
2
+ import type { BoardTask, TaskComment, TaskPreviewLink, TaskRuntimeService } from '../types'
3
3
 
4
4
  export const fetchTasks = (includeArchived = false) =>
5
5
  api<Record<string, BoardTask>>('GET', `/tasks${includeArchived ? '?includeArchived=true' : ''}`)
@@ -29,16 +29,24 @@ export interface GitHubIssueImportResult {
29
29
  skipped: GitHubIssueImportItem[]
30
30
  }
31
31
 
32
- export const createTask = (data: {
32
+ export type TaskWriteInput = Partial<BoardTask> & {
33
+ title?: string
34
+ description?: string
35
+ agentId?: string
36
+ provisionWorkspace?: boolean
37
+ previewLinks?: Array<Partial<TaskPreviewLink> & { url: string }>
38
+ runtimeServices?: Array<Partial<TaskRuntimeService>>
39
+ appendComment?: TaskComment
40
+ }
41
+
42
+ export const createTask = (data: TaskWriteInput & {
33
43
  title: string
34
44
  description: string
35
45
  agentId: string
36
- status?: string
37
- qualityGate?: BoardTask['qualityGate']
38
46
  }) =>
39
47
  api<BoardTask>('POST', '/tasks', data)
40
48
 
41
- export const updateTask = (id: string, data: Partial<BoardTask>) =>
49
+ export const updateTask = (id: string, data: TaskWriteInput) =>
42
50
  api<BoardTask>('PUT', `/tasks/${id}`, data)
43
51
 
44
52
  export const deleteTask = (id: string) =>
@@ -200,6 +200,25 @@ export const TaskCreateSchema = z.object({
200
200
  retryBackoffSec: z.number().optional(),
201
201
  priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
202
202
  dueAt: z.number().nullable().optional(),
203
+ workflowStateId: z.string().nullable().optional(),
204
+ requiredCapabilities: z.array(z.string()).optional(),
205
+ provisionWorkspace: z.boolean().optional(),
206
+ previewLinks: z.array(z.object({
207
+ id: z.string().optional(),
208
+ label: z.string().optional(),
209
+ url: z.string().min(1),
210
+ kind: z.enum(['web', 'api', 'docs', 'custom']).optional(),
211
+ port: z.number().nullable().optional(),
212
+ })).optional(),
213
+ runtimeServices: z.array(z.object({
214
+ id: z.string().optional(),
215
+ name: z.string().optional(),
216
+ status: z.enum(['planned', 'running', 'stopped', 'failed', 'unknown']).optional(),
217
+ command: z.string().nullable().optional(),
218
+ url: z.string().nullable().optional(),
219
+ port: z.number().nullable().optional(),
220
+ startedAt: z.number().nullable().optional(),
221
+ })).optional(),
203
222
  qualityGate: z.object({
204
223
  enabled: z.boolean().optional(),
205
224
  minResultChars: z.number().optional(),
package/src/types/misc.ts CHANGED
@@ -486,7 +486,7 @@ export interface MemoryEntry {
486
486
 
487
487
  export type KnowledgeSourceKind = 'manual' | 'file' | 'url'
488
488
  export type KnowledgeSyncStatus = 'ready' | 'syncing' | 'error'
489
- export type KnowledgeHygieneActionKind = 'sync' | 'reindex' | 'archive' | 'restore' | 'supersede'
489
+ export type KnowledgeHygieneActionKind = 'sync' | 'reindex' | 'archive' | 'restore' | 'supersede' | 'prune'
490
490
  export type KnowledgeHygieneFindingKind = 'stale' | 'duplicate' | 'overlap' | 'broken' | 'archived' | 'superseded'
491
491
 
492
492
  export interface KnowledgeCitation {
package/src/types/task.ts CHANGED
@@ -21,6 +21,64 @@ export interface TaskQualityGateConfig {
21
21
  requireReport?: boolean
22
22
  }
23
23
 
24
+ export type TaskExecutionWorkspaceMode = 'task' | 'project' | 'custom'
25
+
26
+ export interface TaskPreviewLink {
27
+ id: string
28
+ label: string
29
+ url: string
30
+ kind: 'web' | 'api' | 'docs' | 'custom'
31
+ port?: number | null
32
+ addedAt: number
33
+ }
34
+
35
+ export interface TaskRuntimeService {
36
+ id: string
37
+ name: string
38
+ status: 'planned' | 'running' | 'stopped' | 'failed' | 'unknown'
39
+ command?: string | null
40
+ url?: string | null
41
+ port?: number | null
42
+ startedAt?: number | null
43
+ updatedAt: number
44
+ }
45
+
46
+ export interface TaskExecutionWorkspace {
47
+ path: string
48
+ mode: TaskExecutionWorkspaceMode
49
+ sourceCwd?: string | null
50
+ projectId?: string | null
51
+ preparedAt: number
52
+ preparedBy?: string | null
53
+ readmePath?: string | null
54
+ previewLinks: TaskPreviewLink[]
55
+ runtimeServices: TaskRuntimeService[]
56
+ }
57
+
58
+ export type TaskLivenessState =
59
+ | 'not_started'
60
+ | 'ready'
61
+ | 'queued'
62
+ | 'blocked'
63
+ | 'running'
64
+ | 'stale'
65
+ | 'retrying'
66
+ | 'dead_lettered'
67
+ | 'completed'
68
+ | 'failed'
69
+ | 'cancelled'
70
+ | 'archived'
71
+
72
+ export interface TaskLivenessSnapshot {
73
+ state: TaskLivenessState
74
+ reason: string
75
+ checkedAt: number
76
+ lastActivityAt?: number | null
77
+ nextWakeAt?: number | null
78
+ blockerTaskIds?: string[]
79
+ staleMs?: number | null
80
+ }
81
+
24
82
  export interface BoardTask {
25
83
  id: string
26
84
  title: string
@@ -49,6 +107,10 @@ export interface BoardTask {
49
107
  type: 'image' | 'video' | 'pdf' | 'file'
50
108
  filename: string
51
109
  }>
110
+ executionWorkspace?: TaskExecutionWorkspace | null
111
+ previewLinks?: TaskPreviewLink[]
112
+ runtimeServices?: TaskRuntimeService[]
113
+ liveness?: TaskLivenessSnapshot | null
52
114
  comments?: TaskComment[]
53
115
  images?: string[]
54
116
  createdByAgentId?: string | null