@swarmclawai/swarmclaw 1.3.6 → 1.4.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.
- package/README.md +16 -52
- package/next.config.ts +9 -4
- package/package.json +18 -10
- package/scripts/build-bootstrap-env.mjs +24 -0
- package/scripts/run-next-build.mjs +74 -0
- package/scripts/run-next-typegen.mjs +61 -0
- package/src/app/api/.well-known/agent-card/route.ts +46 -0
- package/src/app/api/a2a/route.ts +56 -0
- package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
- package/src/app/api/approvals/route.test.ts +29 -3
- package/src/app/api/approvals/route.ts +13 -7
- package/src/app/api/chats/[id]/chat/route.test.ts +64 -0
- package/src/app/api/chats/[id]/chat/route.ts +24 -8
- package/src/app/api/chats/[id]/deploy/route.ts +2 -2
- package/src/app/api/chats/chat-route.test.ts +68 -0
- package/src/app/api/connectors/[id]/doctor/route.test.ts +97 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -1
- package/src/app/api/connectors/connector-doctor-route.test.ts +1 -0
- package/src/app/api/logs/route.test.ts +61 -0
- package/src/app/api/logs/route.ts +35 -0
- package/src/app/api/openclaw/sync/route.ts +1 -1
- package/src/app/api/swarmfeed/channels/route.ts +14 -0
- package/src/app/api/swarmfeed/posts/route.ts +60 -0
- package/src/app/api/swarmfeed/route.ts +37 -0
- package/src/app/api/tts/route.test.ts +82 -0
- package/src/app/api/tts/route.ts +13 -6
- package/src/app/api/tts/stream/route.ts +12 -5
- package/src/app/error.tsx +32 -0
- package/src/app/global-error.tsx +33 -0
- package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
- package/src/app/protocols/page.tsx +16 -7
- package/src/app/swarmfeed/page.tsx +7 -0
- package/src/cli/index.js +22 -0
- package/src/cli/spec.js +9 -0
- package/src/components/agents/agent-avatar.tsx +2 -5
- package/src/components/agents/agent-sheet.tsx +10 -0
- package/src/components/auth/access-key-gate.tsx +25 -0
- package/src/components/layout/error-boundary.tsx +12 -30
- package/src/components/layout/error-fallback.tsx +61 -0
- package/src/components/layout/sidebar-rail.tsx +52 -0
- package/src/components/protocols/builder/edge-editor.tsx +43 -0
- package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
- package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
- package/src/components/protocols/builder/edge-types/index.ts +3 -0
- package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
- package/src/components/protocols/builder/node-inspector.tsx +227 -0
- package/src/components/protocols/builder/node-palette.tsx +97 -0
- package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
- package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
- package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
- package/src/components/protocols/builder/node-types/index.ts +9 -0
- package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
- package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
- package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
- package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
- package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
- package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
- package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
- package/src/components/protocols/builder/run-overlay.tsx +29 -0
- package/src/components/protocols/builder/template-gallery.tsx +53 -0
- package/src/components/protocols/builder/validation-panel.tsx +57 -0
- package/src/components/skills/skills-workspace.tsx +1 -9
- package/src/features/protocols/builder/hooks/index.ts +2 -0
- package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
- package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
- package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
- package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
- package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
- package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
- package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
- package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
- package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
- package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
- package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
- package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
- package/src/features/swarmfeed/compose-post.tsx +139 -0
- package/src/features/swarmfeed/feed-page.tsx +136 -0
- package/src/features/swarmfeed/post-card.tsx +114 -0
- package/src/features/swarmfeed/queries.ts +28 -0
- package/src/lib/a2a/agent-card.ts +61 -0
- package/src/lib/a2a/auth.ts +54 -0
- package/src/lib/a2a/client.ts +133 -0
- package/src/lib/a2a/discovery.ts +116 -0
- package/src/lib/a2a/handlers.ts +176 -0
- package/src/lib/a2a/json-rpc-router.ts +38 -0
- package/src/lib/a2a/types.ts +95 -0
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/report-client-error.ts +52 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/providers/anthropic.ts +119 -107
- package/src/lib/providers/ollama.ts +34 -14
- package/src/lib/providers/openai.ts +154 -142
- package/src/lib/providers/openclaw.ts +3 -3
- package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
- package/src/lib/server/agents/main-agent-loop.ts +377 -41
- package/src/lib/server/chat-execution/chat-execution.ts +12 -7
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +19 -12
- package/src/lib/server/connectors/swarmdock.ts +1 -1
- package/src/lib/server/extensions.ts +11 -0
- package/src/lib/server/messages/message-repository.ts +31 -0
- package/src/lib/server/openclaw/sync.ts +4 -4
- package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
- package/src/lib/server/protocols/protocol-normalization.ts +1 -0
- package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
- package/src/lib/server/protocols/protocol-types.ts +1 -0
- package/src/lib/server/provider-health.ts +19 -3
- package/src/lib/server/safe-parse-body.test.ts +32 -0
- package/src/lib/server/safe-parse-body.ts +20 -3
- package/src/lib/server/session-tools/delegate.ts +151 -77
- package/src/lib/server/storage-auth.ts +10 -2
- package/src/lib/server/storage-normalization.ts +11 -0
- package/src/lib/server/storage.ts +113 -4
- package/src/lib/server/working-state/service.test.ts +2 -3
- package/src/lib/server/working-state/service.ts +37 -6
- package/src/lib/swarmfeed-client.ts +157 -0
- package/src/lib/validation/schemas.ts +1 -1
- package/src/stores/slices/data-slice.ts +3 -0
- package/src/stores/use-approval-store.ts +4 -1
- package/src/types/agent.ts +31 -1
- package/src/types/index.ts +1 -0
- package/src/types/protocol.ts +19 -0
- package/src/types/session.ts +1 -1
- package/src/types/swarmfeed.ts +30 -0
- package/tsconfig.json +1 -2
|
@@ -55,6 +55,29 @@ interface DelegateRuntimeState {
|
|
|
55
55
|
cancel?: () => void
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
type DelegateFailureKind = 'auth' | 'unavailable' | 'spawn' | 'permission' | 'runtime' | 'timeout'
|
|
59
|
+
|
|
60
|
+
interface DelegateBackendResult {
|
|
61
|
+
backend: DelegateBackend
|
|
62
|
+
status: 'completed' | 'failed'
|
|
63
|
+
response: string | null
|
|
64
|
+
error: string | null
|
|
65
|
+
failureKind?: DelegateFailureKind
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface DelegateBackendAdapter {
|
|
69
|
+
backend: DelegateBackend
|
|
70
|
+
binaryName: string
|
|
71
|
+
run: (
|
|
72
|
+
binary: string,
|
|
73
|
+
task: string,
|
|
74
|
+
resume: boolean,
|
|
75
|
+
resumeId: string,
|
|
76
|
+
bctx: DelegateContext,
|
|
77
|
+
runtime?: DelegateRuntimeState,
|
|
78
|
+
) => Promise<DelegateBackendResult>
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
function buildDelegateContextFromSessionish(session: unknown): DelegateContext {
|
|
59
82
|
const record = session && typeof session === 'object' ? session as Record<string, unknown> : {}
|
|
60
83
|
const sessionId = typeof record.id === 'string'
|
|
@@ -337,25 +360,73 @@ export function resolveDelegateResumeConfig(
|
|
|
337
360
|
}
|
|
338
361
|
}
|
|
339
362
|
|
|
340
|
-
|
|
363
|
+
function buildDelegateFailure(
|
|
364
|
+
backend: DelegateBackend,
|
|
365
|
+
error: string,
|
|
366
|
+
failureKind: DelegateFailureKind = 'runtime',
|
|
367
|
+
): DelegateBackendResult {
|
|
368
|
+
return {
|
|
369
|
+
backend,
|
|
370
|
+
status: 'failed',
|
|
371
|
+
response: null,
|
|
372
|
+
error: error.trim() || `Delegate backend "${backend}" failed.`,
|
|
373
|
+
failureKind,
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function buildDelegateSuccess(
|
|
378
|
+
backend: DelegateBackend,
|
|
379
|
+
response: string,
|
|
380
|
+
): DelegateBackendResult {
|
|
381
|
+
return {
|
|
382
|
+
backend,
|
|
383
|
+
status: 'completed',
|
|
384
|
+
response: truncate(response, MAX_OUTPUT),
|
|
385
|
+
error: null,
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function formatDelegateResultText(result: DelegateBackendResult): string {
|
|
390
|
+
if (result.status === 'completed') {
|
|
391
|
+
return truncate(result.response?.trim() || 'Task completed.', MAX_OUTPUT)
|
|
392
|
+
}
|
|
393
|
+
const error = result.error?.trim() || `Delegate backend "${result.backend}" failed.`
|
|
394
|
+
return truncate(`Error: ${error}`, MAX_OUTPUT)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const DELEGATE_BACKEND_ADAPTERS: Record<DelegateBackend, DelegateBackendAdapter> = {
|
|
398
|
+
claude: {
|
|
399
|
+
backend: 'claude',
|
|
400
|
+
binaryName: 'claude',
|
|
401
|
+
run: runClaudeDelegate,
|
|
402
|
+
},
|
|
403
|
+
codex: {
|
|
404
|
+
backend: 'codex',
|
|
405
|
+
binaryName: 'codex',
|
|
406
|
+
run: runCodexDelegate,
|
|
407
|
+
},
|
|
408
|
+
opencode: {
|
|
409
|
+
backend: 'opencode',
|
|
410
|
+
binaryName: 'opencode',
|
|
411
|
+
run: runOpenCodeDelegate,
|
|
412
|
+
},
|
|
413
|
+
gemini: {
|
|
414
|
+
backend: 'gemini',
|
|
415
|
+
binaryName: 'gemini',
|
|
416
|
+
run: runGeminiDelegate,
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function runDelegateBackend(args: Record<string, unknown>, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
|
|
341
421
|
const normalized = normalizeDelegateArgs(args)
|
|
342
422
|
const task = normalized.task as string
|
|
343
423
|
const backend = ((normalized.backend as string) || 'claude') as DelegateBackend
|
|
344
424
|
const { resume, resumeId } = resolveDelegateResumeConfig(normalized, backend, bctx)
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
const binary = backends[backend as keyof typeof backends]
|
|
352
|
-
if (!binary) return `Error: Backend "${backend}" unavailable.`
|
|
353
|
-
|
|
354
|
-
if (backend === 'claude') return runClaudeDelegate(binary, task, resume, resumeId, bctx, runtime)
|
|
355
|
-
if (backend === 'codex') return runCodexDelegate(binary, task, resume, resumeId, bctx, runtime)
|
|
356
|
-
if (backend === 'opencode') return runOpenCodeDelegate(binary, task, resume, resumeId, bctx, runtime)
|
|
357
|
-
if (backend === 'gemini') return runGeminiDelegate(binary, task, resume, resumeId, bctx, runtime)
|
|
358
|
-
return `Error: Unsupported backend "${backend}".`
|
|
425
|
+
const adapter = DELEGATE_BACKEND_ADAPTERS[backend]
|
|
426
|
+
if (!adapter) return buildDelegateFailure(backend, `Unsupported backend "${backend}".`, 'unavailable')
|
|
427
|
+
const binary = findBinaryOnPath(adapter.binaryName)
|
|
428
|
+
if (!binary) return buildDelegateFailure(backend, `Backend "${backend}" unavailable.`, 'unavailable')
|
|
429
|
+
return adapter.run(binary, task, resume, resumeId, bctx, runtime)
|
|
359
430
|
}
|
|
360
431
|
|
|
361
432
|
function providerIdForBackend(backend: DelegateBackend): string {
|
|
@@ -369,13 +440,13 @@ function fallbackOrderForBackend(requested: DelegateBackend): DelegateBackend[]
|
|
|
369
440
|
return [requested, ...DELEGATE_BACKEND_ORDER.filter((backend) => backend !== requested)]
|
|
370
441
|
}
|
|
371
442
|
|
|
372
|
-
function isRecoverableDelegateFailure(result:
|
|
373
|
-
|
|
374
|
-
if (
|
|
443
|
+
function isRecoverableDelegateFailure(result: DelegateBackendResult): boolean {
|
|
444
|
+
if (result.status !== 'failed') return false
|
|
445
|
+
if (result.failureKind === 'auth' || result.failureKind === 'unavailable' || result.failureKind === 'spawn' || result.failureKind === 'permission') {
|
|
446
|
+
return true
|
|
447
|
+
}
|
|
448
|
+
const normalized = String(result.error || '').trim().toLowerCase()
|
|
375
449
|
return [
|
|
376
|
-
'not authenticated',
|
|
377
|
-
'backend "',
|
|
378
|
-
'unavailable',
|
|
379
450
|
'enoent',
|
|
380
451
|
'not found',
|
|
381
452
|
'command not found',
|
|
@@ -387,12 +458,16 @@ function isRecoverableDelegateFailure(result: string): boolean {
|
|
|
387
458
|
|
|
388
459
|
function summarizeDelegateAttempts(
|
|
389
460
|
requested: DelegateBackend,
|
|
390
|
-
attempts: Array<{ backend: DelegateBackend; result:
|
|
391
|
-
):
|
|
461
|
+
attempts: Array<{ backend: DelegateBackend; result: DelegateBackendResult }>,
|
|
462
|
+
): DelegateBackendResult {
|
|
392
463
|
const summary = attempts
|
|
393
|
-
.map(({ backend, result }) => `${backend}: ${result.replace(/^Error:\s*/i, '').trim()
|
|
464
|
+
.map(({ backend, result }) => `${backend}: ${result.error?.trim() || formatDelegateResultText(result).replace(/^Error:\s*/i, '').trim()}`)
|
|
394
465
|
.join(' | ')
|
|
395
|
-
return
|
|
466
|
+
return buildDelegateFailure(
|
|
467
|
+
requested,
|
|
468
|
+
`Delegate backend "${requested}" could not complete the task. ${summary}. Continue with another available tool instead of stopping.`,
|
|
469
|
+
'runtime',
|
|
470
|
+
)
|
|
396
471
|
}
|
|
397
472
|
|
|
398
473
|
async function runDelegateBackendWithFallback(
|
|
@@ -400,26 +475,25 @@ async function runDelegateBackendWithFallback(
|
|
|
400
475
|
bctx: DelegateContext,
|
|
401
476
|
runtime?: DelegateRuntimeState,
|
|
402
477
|
opts?: { onAttempt?: (backend: DelegateBackend, attemptIndex: number) => void; onFallback?: (from: DelegateBackend, to: DelegateBackend, reason: string) => void },
|
|
403
|
-
): Promise<{ backend: DelegateBackend; result:
|
|
478
|
+
): Promise<{ backend: DelegateBackend; result: DelegateBackendResult; attempts: Array<{ backend: DelegateBackend; result: DelegateBackendResult }> }> {
|
|
404
479
|
const normalized = normalizeDelegateArgs(args)
|
|
405
480
|
const requested = ((normalized.backend as string) || 'claude') as DelegateBackend
|
|
406
481
|
const orderedBackends = fallbackOrderForBackend(requested)
|
|
407
|
-
const attempts: Array<{ backend: DelegateBackend; result:
|
|
482
|
+
const attempts: Array<{ backend: DelegateBackend; result: DelegateBackendResult }> = []
|
|
408
483
|
|
|
409
484
|
for (const [index, backend] of orderedBackends.entries()) {
|
|
410
485
|
opts?.onAttempt?.(backend, index)
|
|
411
486
|
const result = await runDelegateBackend({ ...normalized, backend }, bctx, runtime)
|
|
412
487
|
attempts.push({ backend, result })
|
|
413
|
-
if (
|
|
414
|
-
markProviderFailure(providerIdForBackend(backend), result)
|
|
415
|
-
} else {
|
|
488
|
+
if (result.status === 'completed') {
|
|
416
489
|
markProviderSuccess(providerIdForBackend(backend))
|
|
417
490
|
return { backend, result, attempts }
|
|
418
491
|
}
|
|
492
|
+
markProviderFailure(providerIdForBackend(backend), formatDelegateResultText(result))
|
|
419
493
|
|
|
420
494
|
const nextBackend = orderedBackends[index + 1]
|
|
421
495
|
if (nextBackend && isRecoverableDelegateFailure(result)) {
|
|
422
|
-
opts?.onFallback?.(backend, nextBackend, result)
|
|
496
|
+
opts?.onFallback?.(backend, nextBackend, result.error || formatDelegateResultText(result))
|
|
423
497
|
continue
|
|
424
498
|
}
|
|
425
499
|
return {
|
|
@@ -520,7 +594,7 @@ async function executeDelegateAction(args: Record<string, unknown>, bctx: Delega
|
|
|
520
594
|
onFallback: (from, to, reason) => {
|
|
521
595
|
appendDelegationCheckpoint(
|
|
522
596
|
job.id,
|
|
523
|
-
`Delegate ${from} failed: ${reason.
|
|
597
|
+
`Delegate ${from} failed: ${reason.trim()}. Falling back to ${to}.`,
|
|
524
598
|
'running',
|
|
525
599
|
)
|
|
526
600
|
},
|
|
@@ -529,22 +603,22 @@ async function executeDelegateAction(args: Record<string, unknown>, bctx: Delega
|
|
|
529
603
|
const latest = getDelegationJob(job.id)
|
|
530
604
|
if (latest?.status === 'cancelled') return { backend, result }
|
|
531
605
|
const resumePatch = buildDelegateResumePatch(bctx)
|
|
532
|
-
if (
|
|
606
|
+
if (result.status === 'failed') {
|
|
533
607
|
appendDelegationCheckpoint(job.id, `Delegate failed on ${backend}`, 'failed')
|
|
534
|
-
failDelegationJob(job.id, result.
|
|
608
|
+
failDelegationJob(job.id, result.error || `Delegate backend "${backend}" failed.`, { ...resumePatch, backend })
|
|
535
609
|
} else {
|
|
536
610
|
appendDelegationCheckpoint(job.id, `Delegate completed on ${backend}`, 'completed')
|
|
537
|
-
completeDelegationJob(job.id, result, { ...resumePatch, backend })
|
|
611
|
+
completeDelegationJob(job.id, result.response || 'Task completed.', { ...resumePatch, backend })
|
|
538
612
|
}
|
|
539
613
|
return { backend, result }
|
|
540
614
|
})
|
|
541
615
|
.catch((err: unknown) => {
|
|
542
616
|
const message = errorMessage(err)
|
|
543
617
|
const latest = getDelegationJob(job.id)
|
|
544
|
-
if (latest?.status === 'cancelled') return { backend: requestedBackend, result:
|
|
618
|
+
if (latest?.status === 'cancelled') return { backend: requestedBackend, result: buildDelegateFailure(requestedBackend, message) }
|
|
545
619
|
appendDelegationCheckpoint(job.id, `Delegate crashed on ${requestedBackend}: ${message}`, 'failed')
|
|
546
620
|
failDelegationJob(job.id, message, { ...buildDelegateResumePatch(bctx), backend: requestedBackend })
|
|
547
|
-
return { backend: requestedBackend, result:
|
|
621
|
+
return { backend: requestedBackend, result: buildDelegateFailure(requestedBackend, message, 'runtime') }
|
|
548
622
|
})
|
|
549
623
|
|
|
550
624
|
if (!waitForCompletion) {
|
|
@@ -560,9 +634,9 @@ async function executeDelegateAction(args: Record<string, unknown>, bctx: Delega
|
|
|
560
634
|
const latest = getDelegationJob(job.id)
|
|
561
635
|
return JSON.stringify({
|
|
562
636
|
jobId: job.id,
|
|
563
|
-
status: latest?.status ||
|
|
637
|
+
status: latest?.status || result.status,
|
|
564
638
|
backend: latest?.backend || backend,
|
|
565
|
-
response: result,
|
|
639
|
+
response: formatDelegateResultText(result),
|
|
566
640
|
})
|
|
567
641
|
}
|
|
568
642
|
|
|
@@ -591,7 +665,7 @@ function parseCodexOutputText(ev: Record<string, unknown>): string | null {
|
|
|
591
665
|
return null
|
|
592
666
|
}
|
|
593
667
|
|
|
594
|
-
async function runCodexDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<
|
|
668
|
+
async function runCodexDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
|
|
595
669
|
try {
|
|
596
670
|
// Build clean env — preserves user's CODEX_HOME for auth
|
|
597
671
|
const env = buildCliEnv()
|
|
@@ -599,13 +673,13 @@ async function runCodexDelegate(binary: string, task: string, resume: boolean, r
|
|
|
599
673
|
// Auth probe BEFORE any temp CODEX_HOME override
|
|
600
674
|
const auth = probeCliAuth(binary, 'codex', env, bctx.cwd)
|
|
601
675
|
if (!auth.authenticated) {
|
|
602
|
-
return
|
|
676
|
+
return buildDelegateFailure('codex', auth.errorMessage || 'Codex CLI is not authenticated. Run `codex login` and retry.', 'auth')
|
|
603
677
|
}
|
|
604
678
|
|
|
605
679
|
const storedResumeId = bctx.readStoredDelegateResumeId?.('codex')
|
|
606
680
|
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
607
681
|
|
|
608
|
-
return await new Promise<
|
|
682
|
+
return await new Promise<DelegateBackendResult>((resolve) => {
|
|
609
683
|
const args: string[] = ['exec']
|
|
610
684
|
if (resumeIdToUse) args.push('resume', resumeIdToUse)
|
|
611
685
|
args.push('--json', '--full-auto', '--skip-git-repo-check', '-')
|
|
@@ -618,10 +692,10 @@ async function runCodexDelegate(binary: string, task: string, resume: boolean, r
|
|
|
618
692
|
let discoveredId: string | null = null
|
|
619
693
|
let settled = false
|
|
620
694
|
|
|
621
|
-
const finish = (
|
|
695
|
+
const finish = (result: DelegateBackendResult) => {
|
|
622
696
|
if (settled) return
|
|
623
697
|
settled = true
|
|
624
|
-
resolve(
|
|
698
|
+
resolve(result)
|
|
625
699
|
}
|
|
626
700
|
|
|
627
701
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -655,39 +729,39 @@ async function runCodexDelegate(binary: string, task: string, resume: boolean, r
|
|
|
655
729
|
clearTimeout(timeoutHandle)
|
|
656
730
|
if (discoveredId) bctx.persistDelegateResumeId?.('codex', discoveredId)
|
|
657
731
|
const output = responseText.trim()
|
|
658
|
-
if (output) return finish(output)
|
|
732
|
+
if (output) return finish(buildDelegateSuccess('codex', output))
|
|
659
733
|
const stderr = stderrBuf.trim()
|
|
660
|
-
if (stderr) return finish(
|
|
661
|
-
return finish(`
|
|
734
|
+
if (stderr) return finish(buildDelegateFailure('codex', stderr))
|
|
735
|
+
return finish(buildDelegateFailure('codex', `Codex exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`, 'runtime'))
|
|
662
736
|
})
|
|
663
737
|
|
|
664
738
|
child.on('error', (err) => {
|
|
665
739
|
clearTimeout(timeoutHandle)
|
|
666
|
-
finish(
|
|
740
|
+
finish(buildDelegateFailure('codex', err.message, 'spawn'))
|
|
667
741
|
})
|
|
668
742
|
|
|
669
743
|
child.stdin?.write(task)
|
|
670
744
|
child.stdin?.end()
|
|
671
745
|
})
|
|
672
746
|
} catch (err: unknown) {
|
|
673
|
-
return
|
|
747
|
+
return buildDelegateFailure('codex', errorMessage(err), 'runtime')
|
|
674
748
|
}
|
|
675
749
|
}
|
|
676
750
|
|
|
677
|
-
async function runOpenCodeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<
|
|
751
|
+
async function runOpenCodeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
|
|
678
752
|
try {
|
|
679
753
|
const env = buildCliEnv()
|
|
680
754
|
|
|
681
755
|
// Auth probe
|
|
682
756
|
const auth = probeCliAuth(binary, 'opencode', env, bctx.cwd)
|
|
683
757
|
if (!auth.authenticated) {
|
|
684
|
-
return
|
|
758
|
+
return buildDelegateFailure('opencode', auth.errorMessage || 'OpenCode CLI is not authenticated.', 'auth')
|
|
685
759
|
}
|
|
686
760
|
|
|
687
761
|
const storedResumeId = bctx.readStoredDelegateResumeId?.('opencode')
|
|
688
762
|
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
689
763
|
|
|
690
|
-
return await new Promise<
|
|
764
|
+
return await new Promise<DelegateBackendResult>((resolve) => {
|
|
691
765
|
const args = ['run', task, '--format', 'json']
|
|
692
766
|
if (resumeIdToUse) args.push('--session', resumeIdToUse)
|
|
693
767
|
|
|
@@ -699,10 +773,10 @@ async function runOpenCodeDelegate(binary: string, task: string, resume: boolean
|
|
|
699
773
|
let discoveredId: string | null = null
|
|
700
774
|
let settled = false
|
|
701
775
|
|
|
702
|
-
const finish = (
|
|
776
|
+
const finish = (result: DelegateBackendResult) => {
|
|
703
777
|
if (settled) return
|
|
704
778
|
settled = true
|
|
705
|
-
resolve(
|
|
779
|
+
resolve(result)
|
|
706
780
|
}
|
|
707
781
|
|
|
708
782
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -742,36 +816,36 @@ async function runOpenCodeDelegate(binary: string, task: string, resume: boolean
|
|
|
742
816
|
clearTimeout(timeoutHandle)
|
|
743
817
|
if (discoveredId) bctx.persistDelegateResumeId?.('opencode', discoveredId)
|
|
744
818
|
const output = responseText.trim()
|
|
745
|
-
if (output) return finish(output)
|
|
819
|
+
if (output) return finish(buildDelegateSuccess('opencode', output))
|
|
746
820
|
const stderr = stderrBuf.trim()
|
|
747
|
-
if (stderr) return finish(
|
|
748
|
-
return finish(`
|
|
821
|
+
if (stderr) return finish(buildDelegateFailure('opencode', stderr))
|
|
822
|
+
return finish(buildDelegateFailure('opencode', `OpenCode exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`, 'runtime'))
|
|
749
823
|
})
|
|
750
824
|
|
|
751
825
|
child.on('error', (err) => {
|
|
752
826
|
clearTimeout(timeoutHandle)
|
|
753
|
-
finish(
|
|
827
|
+
finish(buildDelegateFailure('opencode', err.message, 'spawn'))
|
|
754
828
|
})
|
|
755
829
|
})
|
|
756
830
|
} catch (err: unknown) {
|
|
757
|
-
return
|
|
831
|
+
return buildDelegateFailure('opencode', errorMessage(err), 'runtime')
|
|
758
832
|
}
|
|
759
833
|
}
|
|
760
834
|
|
|
761
|
-
async function runGeminiDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<
|
|
835
|
+
async function runGeminiDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
|
|
762
836
|
try {
|
|
763
837
|
const env = buildCliEnv()
|
|
764
838
|
|
|
765
839
|
// Auth probe
|
|
766
840
|
const auth = probeCliAuth(binary, 'gemini', env, bctx.cwd)
|
|
767
841
|
if (!auth.authenticated) {
|
|
768
|
-
return
|
|
842
|
+
return buildDelegateFailure('gemini', auth.errorMessage || 'Gemini CLI is not authenticated.', 'auth')
|
|
769
843
|
}
|
|
770
844
|
|
|
771
845
|
const storedResumeId = bctx.readStoredDelegateResumeId?.('gemini')
|
|
772
846
|
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
773
847
|
|
|
774
|
-
return await new Promise<
|
|
848
|
+
return await new Promise<DelegateBackendResult>((resolve) => {
|
|
775
849
|
const args = ['--prompt', task, '--output-format', 'stream-json', '--yolo']
|
|
776
850
|
if (resumeIdToUse) args.push('--resume', resumeIdToUse)
|
|
777
851
|
|
|
@@ -783,10 +857,10 @@ async function runGeminiDelegate(binary: string, task: string, resume: boolean,
|
|
|
783
857
|
let discoveredId: string | null = null
|
|
784
858
|
let settled = false
|
|
785
859
|
|
|
786
|
-
const finish = (
|
|
860
|
+
const finish = (result: DelegateBackendResult) => {
|
|
787
861
|
if (settled) return
|
|
788
862
|
settled = true
|
|
789
|
-
resolve(
|
|
863
|
+
resolve(result)
|
|
790
864
|
}
|
|
791
865
|
|
|
792
866
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -830,32 +904,32 @@ async function runGeminiDelegate(binary: string, task: string, resume: boolean,
|
|
|
830
904
|
clearTimeout(timeoutHandle)
|
|
831
905
|
if (discoveredId) bctx.persistDelegateResumeId?.('gemini', discoveredId)
|
|
832
906
|
const output = responseText.trim()
|
|
833
|
-
if (output) return finish(output)
|
|
907
|
+
if (output) return finish(buildDelegateSuccess('gemini', output))
|
|
834
908
|
const stderr = stderrBuf.trim()
|
|
835
|
-
if (stderr) return finish(
|
|
836
|
-
return finish(`
|
|
909
|
+
if (stderr) return finish(buildDelegateFailure('gemini', stderr))
|
|
910
|
+
return finish(buildDelegateFailure('gemini', `Gemini exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`, 'runtime'))
|
|
837
911
|
})
|
|
838
912
|
|
|
839
913
|
child.on('error', (err) => {
|
|
840
914
|
clearTimeout(timeoutHandle)
|
|
841
|
-
finish(
|
|
915
|
+
finish(buildDelegateFailure('gemini', err.message, 'spawn'))
|
|
842
916
|
})
|
|
843
917
|
})
|
|
844
918
|
} catch (err: unknown) {
|
|
845
|
-
return
|
|
919
|
+
return buildDelegateFailure('gemini', errorMessage(err), 'runtime')
|
|
846
920
|
}
|
|
847
921
|
}
|
|
848
922
|
|
|
849
|
-
async function runClaudeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<
|
|
923
|
+
async function runClaudeDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
|
|
850
924
|
try {
|
|
851
925
|
const env = buildCliEnv()
|
|
852
926
|
const auth = probeCliAuth(binary, 'claude', env, bctx.cwd)
|
|
853
|
-
if (!auth.authenticated) return
|
|
927
|
+
if (!auth.authenticated) return buildDelegateFailure('claude', auth.errorMessage || 'Claude Code not authenticated.', 'auth')
|
|
854
928
|
|
|
855
929
|
const storedResumeId = bctx.readStoredDelegateResumeId?.('claudeCode')
|
|
856
930
|
const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
|
|
857
931
|
|
|
858
|
-
return new Promise<
|
|
932
|
+
return new Promise<DelegateBackendResult>((resolve) => {
|
|
859
933
|
const args = ['--print', '--output-format', 'stream-json', '--verbose', '--dangerously-skip-permissions']
|
|
860
934
|
if (resumeIdToUse) args.push('--resume', resumeIdToUse)
|
|
861
935
|
const child = spawn(binary, args, { cwd: bctx.cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
|
|
@@ -865,7 +939,7 @@ async function runClaudeDelegate(binary: string, task: string, resume: boolean,
|
|
|
865
939
|
let discoveredId: string | null = null
|
|
866
940
|
let settled = false
|
|
867
941
|
|
|
868
|
-
const finish = (
|
|
942
|
+
const finish = (result: DelegateBackendResult) => { if (!settled) { settled = true; resolve(result) } }
|
|
869
943
|
const timeoutHandle = setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, bctx.claudeTimeoutMs || 300000)
|
|
870
944
|
|
|
871
945
|
child.stdout?.on('data', (c) => {
|
|
@@ -890,17 +964,17 @@ async function runClaudeDelegate(binary: string, task: string, resume: boolean,
|
|
|
890
964
|
clearTimeout(timeoutHandle)
|
|
891
965
|
if (discoveredId) bctx.persistDelegateResumeId?.('claudeCode', discoveredId)
|
|
892
966
|
const output = assistantText.trim()
|
|
893
|
-
if (code === 0) finish(output || 'Task completed.')
|
|
894
|
-
else finish(
|
|
967
|
+
if (code === 0) finish(buildDelegateSuccess('claude', output || 'Task completed.'))
|
|
968
|
+
else finish(buildDelegateFailure('claude', output || `Code ${code}. ${stderr.trim()}`))
|
|
895
969
|
})
|
|
896
970
|
child.on('error', (err) => {
|
|
897
971
|
clearTimeout(timeoutHandle)
|
|
898
|
-
finish(
|
|
972
|
+
finish(buildDelegateFailure('claude', err.message, 'spawn'))
|
|
899
973
|
})
|
|
900
974
|
child.stdin?.write(task)
|
|
901
975
|
child.stdin?.end()
|
|
902
976
|
})
|
|
903
|
-
} catch (err: unknown) { return
|
|
977
|
+
} catch (err: unknown) { return buildDelegateFailure('claude', errorMessage(err), 'runtime') }
|
|
904
978
|
}
|
|
905
979
|
|
|
906
980
|
/**
|
|
@@ -21,11 +21,19 @@ if (!IS_BUILD_BOOTSTRAP) {
|
|
|
21
21
|
loadEnv()
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Append a key=value to .env.local only if the key doesn't already exist in the file. */
|
|
25
|
+
function appendEnvKeyIfMissing(envPath: string, key: string, value: string): void {
|
|
26
|
+
const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : ''
|
|
27
|
+
const keyPattern = new RegExp(`^${key}=`, 'm')
|
|
28
|
+
if (keyPattern.test(existing)) return
|
|
29
|
+
fs.appendFileSync(envPath, `\n${key}=${value}\n`)
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
// Auto-generate CREDENTIAL_SECRET if missing
|
|
25
33
|
if (!IS_BUILD_BOOTSTRAP && !process.env.CREDENTIAL_SECRET) {
|
|
26
34
|
const secret = crypto.randomBytes(32).toString('hex')
|
|
27
35
|
const envPath = path.join(process.cwd(), '.env.local')
|
|
28
|
-
|
|
36
|
+
appendEnvKeyIfMissing(envPath, 'CREDENTIAL_SECRET', secret)
|
|
29
37
|
process.env.CREDENTIAL_SECRET = secret
|
|
30
38
|
log.info(TAG, 'Generated CREDENTIAL_SECRET in .env.local')
|
|
31
39
|
}
|
|
@@ -35,7 +43,7 @@ const SETUP_FLAG = path.join(DATA_DIR, '.setup_pending')
|
|
|
35
43
|
if (!IS_BUILD_BOOTSTRAP && !process.env.ACCESS_KEY) {
|
|
36
44
|
const key = crypto.randomBytes(16).toString('hex')
|
|
37
45
|
const envPath = path.join(process.cwd(), '.env.local')
|
|
38
|
-
|
|
46
|
+
appendEnvKeyIfMissing(envPath, 'ACCESS_KEY', key)
|
|
39
47
|
process.env.ACCESS_KEY = key
|
|
40
48
|
fs.writeFileSync(SETUP_FLAG, key)
|
|
41
49
|
log.info(TAG, `ACCESS KEY: ${key} — Use this key to connect from the browser.`)
|
|
@@ -517,6 +517,17 @@ function normalizeStoredRecordInner(
|
|
|
517
517
|
if (typeof agent.spentDailyCents !== 'number') agent.spentDailyCents = 0
|
|
518
518
|
if (typeof agent.spentHourlyCents !== 'number') agent.spentHourlyCents = 0
|
|
519
519
|
if (typeof agent.lastSpendRollupAt !== 'number') agent.lastSpendRollupAt = 0
|
|
520
|
+
// SwarmFeed defaults
|
|
521
|
+
if (typeof agent.swarmfeedEnabled !== 'boolean') agent.swarmfeedEnabled = false
|
|
522
|
+
if (agent.swarmfeedJoinedAt === undefined) agent.swarmfeedJoinedAt = null
|
|
523
|
+
if (typeof agent.swarmfeedBio !== 'string' && agent.swarmfeedBio !== null) agent.swarmfeedBio = null
|
|
524
|
+
if (agent.swarmfeedPinnedPostId === undefined) agent.swarmfeedPinnedPostId = null
|
|
525
|
+
if (typeof agent.swarmfeedAutoPost !== 'boolean') agent.swarmfeedAutoPost = false
|
|
526
|
+
if (!Array.isArray(agent.swarmfeedAutoPostChannels)) agent.swarmfeedAutoPostChannels = []
|
|
527
|
+
if (typeof agent.swarmfeedApiKey !== 'string' && agent.swarmfeedApiKey !== null) agent.swarmfeedApiKey = null
|
|
528
|
+
if (typeof agent.swarmfeedAgentId !== 'string' && agent.swarmfeedAgentId !== null) agent.swarmfeedAgentId = null
|
|
529
|
+
if (!agent.origin) agent.origin = 'swarmclaw'
|
|
530
|
+
if (agent.swarmfeedHeartbeat === undefined) agent.swarmfeedHeartbeat = null
|
|
520
531
|
// Org chart normalization
|
|
521
532
|
if (agent.orgChart && typeof agent.orgChart === 'object' && !Array.isArray(agent.orgChart)) {
|
|
522
533
|
const oc = agent.orgChart as Record<string, unknown>
|
|
@@ -6,12 +6,13 @@ import Database from 'better-sqlite3'
|
|
|
6
6
|
import { perf } from '@/lib/server/runtime/perf'
|
|
7
7
|
import { log } from '@/lib/server/logger'
|
|
8
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
9
|
-
|
|
10
|
-
const TAG = 'storage'
|
|
11
9
|
import { DATA_DIR, IS_BUILD_BOOTSTRAP, WORKSPACE_DIR } from './data-dir'
|
|
12
10
|
import { normalizeHeartbeatSettingFields } from '@/lib/runtime/heartbeat-defaults'
|
|
13
11
|
import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
|
|
14
12
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
13
|
+
|
|
14
|
+
const TAG = 'storage'
|
|
15
|
+
const malformedRecordWarnings = new Set<string>()
|
|
15
16
|
import type {
|
|
16
17
|
Agent,
|
|
17
18
|
AppNotification,
|
|
@@ -236,8 +237,16 @@ function loadCollectionWithNormalizationState(table: string): {
|
|
|
236
237
|
if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) continue
|
|
237
238
|
result[id] = normalized as StoredObject
|
|
238
239
|
if (changed) normalizedCount += 1
|
|
239
|
-
} catch {
|
|
240
|
-
|
|
240
|
+
} catch (err) {
|
|
241
|
+
const fingerprint = `${table}:${id}`
|
|
242
|
+
if (!malformedRecordWarnings.has(fingerprint)) {
|
|
243
|
+
malformedRecordWarnings.add(fingerprint)
|
|
244
|
+
log.warn(TAG, 'Ignoring malformed stored record during collection load', {
|
|
245
|
+
table,
|
|
246
|
+
id,
|
|
247
|
+
error: err instanceof Error ? err.message : String(err),
|
|
248
|
+
})
|
|
249
|
+
}
|
|
241
250
|
}
|
|
242
251
|
}
|
|
243
252
|
endPerf({ count: raw.size, normalizedCount })
|
|
@@ -1658,6 +1667,106 @@ export const loadGoal = goalsStore.loadItem
|
|
|
1658
1667
|
export const upsertGoal = goalsStore.upsert
|
|
1659
1668
|
export const deleteGoalItem = goalsStore.deleteItem
|
|
1660
1669
|
|
|
1670
|
+
function legacyMissionStatusToWorkingStatus(value: unknown): 'idle' | 'progress' | 'blocked' | 'completed' {
|
|
1671
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
1672
|
+
if (normalized === 'achieved' || normalized === 'completed' || normalized === 'ok') return 'completed'
|
|
1673
|
+
if (normalized === 'blocked' || normalized === 'waiting' || normalized === 'paused') return 'blocked'
|
|
1674
|
+
if (normalized === 'active' || normalized === 'executing' || normalized === 'progress') return 'progress'
|
|
1675
|
+
return 'idle'
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function buildGoalFromLegacyMission(id: string, mission: StoredObject): StoredObject {
|
|
1679
|
+
const objective = typeof mission.objective === 'string' && mission.objective.trim()
|
|
1680
|
+
? mission.objective.trim()
|
|
1681
|
+
: typeof mission.title === 'string' && mission.title.trim()
|
|
1682
|
+
? mission.title.trim()
|
|
1683
|
+
: 'Legacy mission objective'
|
|
1684
|
+
const title = typeof mission.title === 'string' && mission.title.trim()
|
|
1685
|
+
? mission.title.trim()
|
|
1686
|
+
: objective.slice(0, 120)
|
|
1687
|
+
const status = typeof mission.status === 'string' && mission.status.trim().toLowerCase() === 'achieved'
|
|
1688
|
+
? 'achieved'
|
|
1689
|
+
: typeof mission.status === 'string' && mission.status.trim().toLowerCase() === 'abandoned'
|
|
1690
|
+
? 'abandoned'
|
|
1691
|
+
: 'active'
|
|
1692
|
+
|
|
1693
|
+
return {
|
|
1694
|
+
id,
|
|
1695
|
+
title,
|
|
1696
|
+
description: typeof mission.plannerSummary === 'string' && mission.plannerSummary.trim()
|
|
1697
|
+
? mission.plannerSummary.trim()
|
|
1698
|
+
: typeof mission.description === 'string' && mission.description.trim()
|
|
1699
|
+
? mission.description.trim()
|
|
1700
|
+
: undefined,
|
|
1701
|
+
level: mission.taskId ? 'task' : mission.agentId ? 'agent' : mission.projectId ? 'project' : 'organization',
|
|
1702
|
+
parentGoalId: typeof mission.parentMissionId === 'string' && mission.parentMissionId.trim() ? mission.parentMissionId.trim() : null,
|
|
1703
|
+
projectId: typeof mission.projectId === 'string' && mission.projectId.trim() ? mission.projectId.trim() : null,
|
|
1704
|
+
agentId: typeof mission.agentId === 'string' && mission.agentId.trim() ? mission.agentId.trim() : null,
|
|
1705
|
+
taskId: typeof mission.taskId === 'string' && mission.taskId.trim() ? mission.taskId.trim() : null,
|
|
1706
|
+
objective,
|
|
1707
|
+
constraints: Array.isArray(mission.constraints)
|
|
1708
|
+
? mission.constraints.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
1709
|
+
: [],
|
|
1710
|
+
successMetric: typeof mission.successMetric === 'string' && mission.successMetric.trim() ? mission.successMetric.trim() : null,
|
|
1711
|
+
budgetUsd: typeof mission.budgetUsd === 'number' && Number.isFinite(mission.budgetUsd) ? mission.budgetUsd : null,
|
|
1712
|
+
deadlineAt: typeof mission.deadlineAt === 'number' && Number.isFinite(mission.deadlineAt) ? mission.deadlineAt : null,
|
|
1713
|
+
status,
|
|
1714
|
+
createdAt: typeof mission.createdAt === 'number' && Number.isFinite(mission.createdAt) ? mission.createdAt : Date.now(),
|
|
1715
|
+
updatedAt: typeof mission.updatedAt === 'number' && Number.isFinite(mission.updatedAt) ? mission.updatedAt : Date.now(),
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function buildWorkingStateFromLegacyMission(mission: StoredObject): StoredObject | null {
|
|
1720
|
+
const sessionId = typeof mission.sessionId === 'string' && mission.sessionId.trim()
|
|
1721
|
+
? mission.sessionId.trim()
|
|
1722
|
+
: ''
|
|
1723
|
+
if (!sessionId) return null
|
|
1724
|
+
const currentStep = typeof mission.currentStep === 'string' && mission.currentStep.trim()
|
|
1725
|
+
? mission.currentStep.trim()
|
|
1726
|
+
: ''
|
|
1727
|
+
const blockerSummary = typeof mission.blockerSummary === 'string' && mission.blockerSummary.trim()
|
|
1728
|
+
? mission.blockerSummary.trim()
|
|
1729
|
+
: ''
|
|
1730
|
+
return {
|
|
1731
|
+
sessionId,
|
|
1732
|
+
objective: typeof mission.objective === 'string' && mission.objective.trim() ? mission.objective.trim() : null,
|
|
1733
|
+
summary: typeof mission.plannerSummary === 'string' && mission.plannerSummary.trim() ? mission.plannerSummary.trim() : null,
|
|
1734
|
+
status: legacyMissionStatusToWorkingStatus(mission.status ?? mission.phase),
|
|
1735
|
+
nextAction: currentStep || null,
|
|
1736
|
+
planSteps: currentStep
|
|
1737
|
+
? [{ id: `legacy-mission-${sessionId}`, text: currentStep, status: 'active', createdAt: Date.now(), updatedAt: Date.now() }]
|
|
1738
|
+
: [],
|
|
1739
|
+
blockers: blockerSummary
|
|
1740
|
+
? [{ id: `legacy-mission-blocker-${sessionId}`, summary: blockerSummary, status: 'active', createdAt: Date.now(), updatedAt: Date.now() }]
|
|
1741
|
+
: [],
|
|
1742
|
+
confirmedFacts: [],
|
|
1743
|
+
artifacts: [],
|
|
1744
|
+
decisions: [],
|
|
1745
|
+
openQuestions: [],
|
|
1746
|
+
hypotheses: [],
|
|
1747
|
+
evidenceRefs: [],
|
|
1748
|
+
constraints: Array.isArray(mission.constraints)
|
|
1749
|
+
? mission.constraints.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
1750
|
+
: [],
|
|
1751
|
+
successCriteria: Array.isArray(mission.successCriteria)
|
|
1752
|
+
? mission.successCriteria.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
1753
|
+
: [],
|
|
1754
|
+
updatedAt: typeof mission.updatedAt === 'number' && Number.isFinite(mission.updatedAt) ? mission.updatedAt : Date.now(),
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
export function saveMissions(missions: Record<string, StoredObject>): void {
|
|
1759
|
+
for (const [id, mission] of Object.entries(missions || {})) {
|
|
1760
|
+
upsertGoal(id, buildGoalFromLegacyMission(id, mission))
|
|
1761
|
+
const workingState = buildWorkingStateFromLegacyMission(mission)
|
|
1762
|
+
if (workingState) upsertPersistedWorkingState(String(workingState.sessionId), workingState)
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
export function loadMissions(): Record<string, StoredObject> {
|
|
1767
|
+
return loadGoals() as Record<string, StoredObject>
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1661
1770
|
export function getSessionMessages(sessionId: string): Message[] {
|
|
1662
1771
|
const session = loadSession(sessionId)
|
|
1663
1772
|
return Array.isArray(session?.messages) ? session.messages : []
|