@vellumai/assistant 0.5.6 → 0.5.7
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +1 -1
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +8 -14
- package/src/config/feature-flag-registry.json +48 -8
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +227 -51
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +12 -0
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/jobs-store.ts +30 -5
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +27 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.ts +24 -6
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +3 -50
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
package/src/swarm/graph-utils.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared graph utilities for swarm DAG operations.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface GraphNode {
|
|
6
|
-
id: string;
|
|
7
|
-
dependencies: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Detect cycles in a directed graph using Kahn's algorithm.
|
|
12
|
-
* Returns the IDs of nodes involved in cycles, or null if the graph is acyclic.
|
|
13
|
-
*/
|
|
14
|
-
export function detectCycles(nodes: GraphNode[]): string[] | null {
|
|
15
|
-
const inDegree = new Map<string, number>();
|
|
16
|
-
const adj = new Map<string, string[]>();
|
|
17
|
-
|
|
18
|
-
for (const node of nodes) {
|
|
19
|
-
inDegree.set(node.id, 0);
|
|
20
|
-
adj.set(node.id, []);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
for (const node of nodes) {
|
|
24
|
-
for (const dep of node.dependencies) {
|
|
25
|
-
if (adj.has(dep)) {
|
|
26
|
-
adj.get(dep)!.push(node.id);
|
|
27
|
-
inDegree.set(node.id, (inDegree.get(node.id) ?? 0) + 1);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const queue: string[] = [];
|
|
33
|
-
for (const [id, deg] of inDegree) {
|
|
34
|
-
if (deg === 0) queue.push(id);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
let processed = 0;
|
|
38
|
-
while (queue.length > 0) {
|
|
39
|
-
const current = queue.shift()!;
|
|
40
|
-
processed++;
|
|
41
|
-
for (const neighbor of adj.get(current)!) {
|
|
42
|
-
const newDeg = (inDegree.get(neighbor) ?? 0) - 1;
|
|
43
|
-
inDegree.set(neighbor, newDeg);
|
|
44
|
-
if (newDeg === 0) queue.push(neighbor);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (processed < nodes.length) {
|
|
49
|
-
return nodes.filter((n) => (inDegree.get(n.id) ?? 0) > 0).map((n) => n.id);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return null;
|
|
53
|
-
}
|
package/src/swarm/index.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export type { SwarmCheckpoint } from "./checkpoint.js";
|
|
2
|
-
export {
|
|
3
|
-
isCheckpointCompatible,
|
|
4
|
-
loadCheckpoint,
|
|
5
|
-
removeCheckpoint,
|
|
6
|
-
writeCheckpoint,
|
|
7
|
-
} from "./checkpoint.js";
|
|
8
|
-
export type { SwarmLimits } from "./limits.js";
|
|
9
|
-
export {
|
|
10
|
-
getTimeoutForRole,
|
|
11
|
-
resolveSwarmLimits,
|
|
12
|
-
SWARM_HARD_LIMITS,
|
|
13
|
-
} from "./limits.js";
|
|
14
|
-
export type {
|
|
15
|
-
ExecuteSwarmOptions,
|
|
16
|
-
OrchestratorEvent,
|
|
17
|
-
OrchestratorEventKind,
|
|
18
|
-
OrchestratorStatusCallback,
|
|
19
|
-
} from "./orchestrator.js";
|
|
20
|
-
export { executeSwarm } from "./orchestrator.js";
|
|
21
|
-
export {
|
|
22
|
-
SwarmPlanValidationError,
|
|
23
|
-
validateAndNormalizePlan,
|
|
24
|
-
} from "./plan-validator.js";
|
|
25
|
-
export {
|
|
26
|
-
generatePlan,
|
|
27
|
-
makeFallbackPlan,
|
|
28
|
-
parsePlanJSON,
|
|
29
|
-
} from "./router-planner.js";
|
|
30
|
-
export { synthesizeResults } from "./synthesizer.js";
|
|
31
|
-
export type {
|
|
32
|
-
SwarmExecutionSummary,
|
|
33
|
-
SwarmPlan,
|
|
34
|
-
SwarmRole,
|
|
35
|
-
SwarmTaskNode,
|
|
36
|
-
SwarmTaskResult,
|
|
37
|
-
SwarmTaskStatus,
|
|
38
|
-
} from "./types.js";
|
|
39
|
-
export { VALID_SWARM_ROLES } from "./types.js";
|
|
40
|
-
export type {
|
|
41
|
-
ProfilePolicy,
|
|
42
|
-
SwarmWorkerBackend,
|
|
43
|
-
SwarmWorkerBackendInput,
|
|
44
|
-
SwarmWorkerBackendResult,
|
|
45
|
-
WorkerFailureReason,
|
|
46
|
-
WorkerProfile,
|
|
47
|
-
} from "./worker-backend.js";
|
|
48
|
-
export { getProfilePolicy, roleToProfile } from "./worker-backend.js";
|
|
49
|
-
export { buildWorkerPrompt, parseWorkerOutput } from "./worker-prompts.js";
|
|
50
|
-
export type {
|
|
51
|
-
RunWorkerTaskOptions,
|
|
52
|
-
WorkerStatusCallback,
|
|
53
|
-
WorkerStatusKind,
|
|
54
|
-
} from "./worker-runner.js";
|
|
55
|
-
export { runWorkerTask } from "./worker-runner.js";
|
package/src/swarm/limits.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime limits for swarm execution, resolved from config.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { SwarmRole } from "./types.js";
|
|
6
|
-
|
|
7
|
-
export interface SwarmLimits {
|
|
8
|
-
maxWorkers: number;
|
|
9
|
-
maxTasks: number;
|
|
10
|
-
maxRetriesPerTask: number;
|
|
11
|
-
workerTimeoutSec: number;
|
|
12
|
-
/** Per-role timeout overrides. When set, takes precedence over workerTimeoutSec for that role. */
|
|
13
|
-
roleTimeoutsSec: Partial<Record<SwarmRole, number>>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Hard ceilings that config values are clamped to. */
|
|
17
|
-
export const SWARM_HARD_LIMITS = {
|
|
18
|
-
maxWorkers: 6,
|
|
19
|
-
maxTasks: 20,
|
|
20
|
-
maxRetriesPerTask: 3,
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Resolve effective limits from config, clamping to hard ceilings.
|
|
25
|
-
*/
|
|
26
|
-
export function resolveSwarmLimits(config: {
|
|
27
|
-
maxWorkers: number;
|
|
28
|
-
maxTasks: number;
|
|
29
|
-
maxRetriesPerTask: number;
|
|
30
|
-
workerTimeoutSec: number;
|
|
31
|
-
roleTimeoutsSec?: Partial<Record<SwarmRole, number>>;
|
|
32
|
-
}): SwarmLimits {
|
|
33
|
-
const resolvedRoleTimeouts: Partial<Record<SwarmRole, number>> = {};
|
|
34
|
-
if (config.roleTimeoutsSec) {
|
|
35
|
-
for (const [role, timeout] of Object.entries(config.roleTimeoutsSec)) {
|
|
36
|
-
if (timeout != null) {
|
|
37
|
-
resolvedRoleTimeouts[role as SwarmRole] = Math.max(1, timeout);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
maxWorkers: Math.min(
|
|
44
|
-
Math.max(1, config.maxWorkers),
|
|
45
|
-
SWARM_HARD_LIMITS.maxWorkers,
|
|
46
|
-
),
|
|
47
|
-
maxTasks: Math.min(
|
|
48
|
-
Math.max(1, config.maxTasks),
|
|
49
|
-
SWARM_HARD_LIMITS.maxTasks,
|
|
50
|
-
),
|
|
51
|
-
maxRetriesPerTask: Math.min(
|
|
52
|
-
Math.max(0, config.maxRetriesPerTask),
|
|
53
|
-
SWARM_HARD_LIMITS.maxRetriesPerTask,
|
|
54
|
-
),
|
|
55
|
-
workerTimeoutSec: Math.max(1, config.workerTimeoutSec),
|
|
56
|
-
roleTimeoutsSec: resolvedRoleTimeouts,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Get the effective timeout for a given role, falling back to the global workerTimeoutSec. */
|
|
61
|
-
export function getTimeoutForRole(
|
|
62
|
-
limits: SwarmLimits,
|
|
63
|
-
role: SwarmRole,
|
|
64
|
-
): number {
|
|
65
|
-
return limits.roleTimeoutsSec[role] ?? limits.workerTimeoutSec;
|
|
66
|
-
}
|
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import type { ModelIntent, Provider } from "../providers/types.js";
|
|
2
|
-
import { getLogger } from "../util/logger.js";
|
|
3
|
-
import {
|
|
4
|
-
isCheckpointCompatible,
|
|
5
|
-
loadCheckpoint,
|
|
6
|
-
removeCheckpoint,
|
|
7
|
-
writeCheckpoint,
|
|
8
|
-
} from "./checkpoint.js";
|
|
9
|
-
import { detectCycles } from "./graph-utils.js";
|
|
10
|
-
import type { SwarmLimits } from "./limits.js";
|
|
11
|
-
import { getTimeoutForRole } from "./limits.js";
|
|
12
|
-
import { synthesizeResults } from "./synthesizer.js";
|
|
13
|
-
import type {
|
|
14
|
-
SwarmExecutionSummary,
|
|
15
|
-
SwarmPlan,
|
|
16
|
-
SwarmTaskNode,
|
|
17
|
-
SwarmTaskResult,
|
|
18
|
-
} from "./types.js";
|
|
19
|
-
import type { SwarmWorkerBackend } from "./worker-backend.js";
|
|
20
|
-
import { runWorkerTask } from "./worker-runner.js";
|
|
21
|
-
|
|
22
|
-
const log = getLogger("swarm-orchestrator");
|
|
23
|
-
|
|
24
|
-
export type OrchestratorEventKind =
|
|
25
|
-
| "plan_created"
|
|
26
|
-
| "task_started"
|
|
27
|
-
| "task_completed"
|
|
28
|
-
| "task_failed"
|
|
29
|
-
| "task_blocked"
|
|
30
|
-
| "synthesis_started"
|
|
31
|
-
| "done";
|
|
32
|
-
|
|
33
|
-
export interface OrchestratorEvent {
|
|
34
|
-
kind: OrchestratorEventKind;
|
|
35
|
-
taskId?: string;
|
|
36
|
-
message?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type OrchestratorStatusCallback = (event: OrchestratorEvent) => void;
|
|
40
|
-
|
|
41
|
-
export interface ExecuteSwarmOptions {
|
|
42
|
-
plan: SwarmPlan;
|
|
43
|
-
limits: SwarmLimits;
|
|
44
|
-
backend: SwarmWorkerBackend;
|
|
45
|
-
workingDir: string;
|
|
46
|
-
modelIntent?: string;
|
|
47
|
-
/** Provider + model intent for final synthesis. */
|
|
48
|
-
synthesisProvider?: Provider;
|
|
49
|
-
synthesisModelIntent?: ModelIntent;
|
|
50
|
-
onStatus?: OrchestratorStatusCallback;
|
|
51
|
-
signal?: AbortSignal;
|
|
52
|
-
/** Stable identifier for this swarm run, used for checkpoint persistence. */
|
|
53
|
-
runId?: string;
|
|
54
|
-
/** When true, attempt to resume from the last checkpoint for this runId. */
|
|
55
|
-
resume?: boolean;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Execute a validated swarm plan with parallel DAG scheduling,
|
|
60
|
-
* bounded concurrency, and per-task retries.
|
|
61
|
-
*/
|
|
62
|
-
export async function executeSwarm(
|
|
63
|
-
opts: ExecuteSwarmOptions,
|
|
64
|
-
): Promise<SwarmExecutionSummary> {
|
|
65
|
-
const {
|
|
66
|
-
plan,
|
|
67
|
-
limits,
|
|
68
|
-
backend,
|
|
69
|
-
workingDir,
|
|
70
|
-
modelIntent,
|
|
71
|
-
onStatus,
|
|
72
|
-
signal,
|
|
73
|
-
runId,
|
|
74
|
-
resume,
|
|
75
|
-
} = opts;
|
|
76
|
-
const startTime = Date.now();
|
|
77
|
-
|
|
78
|
-
// Safety net: reject cyclic plans even if the caller skipped validation
|
|
79
|
-
const cycleNodes = detectCycles(plan.tasks);
|
|
80
|
-
if (cycleNodes) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Swarm plan contains a dependency cycle: ${cycleNodes.join(" -> ")}`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onStatus?.({
|
|
87
|
-
kind: "plan_created",
|
|
88
|
-
message: `Plan with ${plan.tasks.length} tasks`,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const results = new Map<string, SwarmTaskResult>();
|
|
92
|
-
const blocked = new Set<string>();
|
|
93
|
-
|
|
94
|
-
// Restore from checkpoint if resuming
|
|
95
|
-
if (runId && resume) {
|
|
96
|
-
const checkpoint = loadCheckpoint(runId);
|
|
97
|
-
if (checkpoint && isCheckpointCompatible(checkpoint, plan)) {
|
|
98
|
-
for (const result of checkpoint.results) {
|
|
99
|
-
results.set(result.taskId, result);
|
|
100
|
-
}
|
|
101
|
-
for (const taskId of checkpoint.blockedTaskIds) {
|
|
102
|
-
blocked.add(taskId);
|
|
103
|
-
}
|
|
104
|
-
const restored = checkpoint.results.length;
|
|
105
|
-
const restoredBlocked = checkpoint.blockedTaskIds.length;
|
|
106
|
-
log.info({ runId, restored, restoredBlocked }, "Resumed from checkpoint");
|
|
107
|
-
onStatus?.({
|
|
108
|
-
kind: "plan_created",
|
|
109
|
-
message: `Resumed ${restored} completed tasks from checkpoint`,
|
|
110
|
-
});
|
|
111
|
-
} else if (checkpoint) {
|
|
112
|
-
log.warn(
|
|
113
|
-
{ runId },
|
|
114
|
-
"Checkpoint incompatible with current plan, starting fresh",
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Build adjacency for dependency tracking
|
|
120
|
-
const dependents = new Map<string, string[]>();
|
|
121
|
-
for (const task of plan.tasks) {
|
|
122
|
-
dependents.set(task.id, []);
|
|
123
|
-
}
|
|
124
|
-
for (const task of plan.tasks) {
|
|
125
|
-
for (const dep of task.dependencies) {
|
|
126
|
-
dependents.get(dep)?.push(task.id);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Determine initial ready tasks — skip tasks already completed or blocked
|
|
131
|
-
// from a restored checkpoint
|
|
132
|
-
const ready: SwarmTaskNode[] = [];
|
|
133
|
-
const remaining = new Map<string, SwarmTaskNode>();
|
|
134
|
-
const pendingDeps = new Map<string, Set<string>>();
|
|
135
|
-
|
|
136
|
-
for (const task of plan.tasks) {
|
|
137
|
-
if (results.has(task.id) || blocked.has(task.id)) continue;
|
|
138
|
-
remaining.set(task.id, task);
|
|
139
|
-
// Remove already-completed dependencies from this task's pending set
|
|
140
|
-
const unresolvedDeps = task.dependencies.filter((d) => !results.has(d));
|
|
141
|
-
if (unresolvedDeps.length === 0) {
|
|
142
|
-
ready.push(task);
|
|
143
|
-
} else {
|
|
144
|
-
pendingDeps.set(task.id, new Set(unresolvedDeps));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Concurrent DAG executor — schedule tasks as soon as their prerequisites
|
|
149
|
-
// finish, bounded by maxWorkers. Unlike wave/batch execution, a newly
|
|
150
|
-
// unblocked task can start immediately when a worker slot opens up rather
|
|
151
|
-
// than waiting for the entire previous batch to complete.
|
|
152
|
-
let activeCount = 0;
|
|
153
|
-
let resolve: (() => void) | null = null;
|
|
154
|
-
|
|
155
|
-
// Resolves whenever a running task completes (or the ready queue is refilled)
|
|
156
|
-
// so the main loop can re-evaluate whether to launch more work.
|
|
157
|
-
function signalProgress(): void {
|
|
158
|
-
if (resolve) {
|
|
159
|
-
const r = resolve;
|
|
160
|
-
resolve = null;
|
|
161
|
-
r();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function waitForProgress(): Promise<void> {
|
|
166
|
-
return new Promise<void>((r) => {
|
|
167
|
-
resolve = r;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function processResult(result: SwarmTaskResult): void {
|
|
172
|
-
results.set(result.taskId, result);
|
|
173
|
-
remaining.delete(result.taskId);
|
|
174
|
-
|
|
175
|
-
if (result.status === "completed") {
|
|
176
|
-
onStatus?.({ kind: "task_completed", taskId: result.taskId });
|
|
177
|
-
// Immediately unblock dependents so they enter the ready queue
|
|
178
|
-
for (const depId of dependents.get(result.taskId) ?? []) {
|
|
179
|
-
const pending = pendingDeps.get(depId);
|
|
180
|
-
if (pending) {
|
|
181
|
-
pending.delete(result.taskId);
|
|
182
|
-
if (pending.size === 0) {
|
|
183
|
-
pendingDeps.delete(depId);
|
|
184
|
-
const task = plan.tasks.find((t) => t.id === depId);
|
|
185
|
-
if (task && !blocked.has(depId)) {
|
|
186
|
-
ready.push(task);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
onStatus?.({ kind: "task_failed", taskId: result.taskId });
|
|
193
|
-
blockDependents(result.taskId, dependents, blocked, onStatus);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (runId) {
|
|
197
|
-
writeCheckpoint(runId, plan, results, blocked);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function runTask(task: SwarmTaskNode): Promise<void> {
|
|
202
|
-
onStatus?.({ kind: "task_started", taskId: task.id });
|
|
203
|
-
|
|
204
|
-
const depOutputs = task.dependencies
|
|
205
|
-
.map((depId) => {
|
|
206
|
-
const r = results.get(depId);
|
|
207
|
-
return r ? { taskId: depId, summary: r.summary } : null;
|
|
208
|
-
})
|
|
209
|
-
.filter((d): d is { taskId: string; summary: string } => d != null);
|
|
210
|
-
|
|
211
|
-
let result: SwarmTaskResult;
|
|
212
|
-
try {
|
|
213
|
-
const taskTimeoutMs = getTimeoutForRole(limits, task.role) * 1000;
|
|
214
|
-
|
|
215
|
-
result = await runWorkerTask({
|
|
216
|
-
task,
|
|
217
|
-
upstreamContext: plan.objective,
|
|
218
|
-
dependencyOutputs: depOutputs,
|
|
219
|
-
backend,
|
|
220
|
-
workingDir,
|
|
221
|
-
modelIntent,
|
|
222
|
-
timeoutMs: taskTimeoutMs,
|
|
223
|
-
signal,
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
let retries = 0;
|
|
227
|
-
while (
|
|
228
|
-
result.status === "failed" &&
|
|
229
|
-
retries < limits.maxRetriesPerTask &&
|
|
230
|
-
!signal?.aborted
|
|
231
|
-
) {
|
|
232
|
-
retries++;
|
|
233
|
-
// Exponential backoff with ±25% jitter to prevent thundering herd
|
|
234
|
-
const baseDelayMs = 1000 * Math.pow(2, retries - 1);
|
|
235
|
-
const jitter = baseDelayMs * 0.25 * (2 * Math.random() - 1);
|
|
236
|
-
await abortableSleep(baseDelayMs + jitter, signal);
|
|
237
|
-
if (signal?.aborted) break;
|
|
238
|
-
result = await runWorkerTask({
|
|
239
|
-
task,
|
|
240
|
-
upstreamContext: plan.objective,
|
|
241
|
-
dependencyOutputs: depOutputs,
|
|
242
|
-
backend,
|
|
243
|
-
workingDir,
|
|
244
|
-
modelIntent,
|
|
245
|
-
timeoutMs: taskTimeoutMs,
|
|
246
|
-
signal,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
result.retryCount = retries;
|
|
250
|
-
|
|
251
|
-
if (result.status === "failed") {
|
|
252
|
-
log.error(
|
|
253
|
-
{ taskId: task.id, error: result.summary, retries },
|
|
254
|
-
"Swarm task execution failed",
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
} catch (err) {
|
|
258
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
259
|
-
log.error(
|
|
260
|
-
{ taskId: task.id, error: error.message, stack: error.stack },
|
|
261
|
-
"Swarm task execution failed unexpectedly",
|
|
262
|
-
);
|
|
263
|
-
result = {
|
|
264
|
-
taskId: task.id,
|
|
265
|
-
status: "failed",
|
|
266
|
-
summary: `Unexpected error: ${error.message}`,
|
|
267
|
-
artifacts: [],
|
|
268
|
-
issues: [error.message],
|
|
269
|
-
nextSteps: [],
|
|
270
|
-
rawOutput: "",
|
|
271
|
-
durationMs: 0,
|
|
272
|
-
retryCount: 0,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
processResult(result);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
while (ready.length > 0 || activeCount > 0) {
|
|
280
|
-
if (signal?.aborted) break;
|
|
281
|
-
|
|
282
|
-
// Launch as many ready tasks as worker slots allow
|
|
283
|
-
while (ready.length > 0 && activeCount < limits.maxWorkers) {
|
|
284
|
-
const task = ready.shift()!;
|
|
285
|
-
activeCount++;
|
|
286
|
-
// Fire-and-forget — completion is handled inside runTask.
|
|
287
|
-
// .finally() ensures activeCount is decremented exactly once
|
|
288
|
-
// regardless of where an error occurs (e.g. a throwing onStatus
|
|
289
|
-
// callback inside processResult). .catch() suppresses the
|
|
290
|
-
// unhandled rejection for fire-and-forget usage.
|
|
291
|
-
runTask(task)
|
|
292
|
-
.finally(() => {
|
|
293
|
-
activeCount--;
|
|
294
|
-
signalProgress();
|
|
295
|
-
})
|
|
296
|
-
.catch((err) => {
|
|
297
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
298
|
-
log.error(
|
|
299
|
-
{ taskId: task.id, error: error.message, stack: error.stack },
|
|
300
|
-
"Swarm task runner failed",
|
|
301
|
-
);
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Nothing left to launch and nothing running — we're done
|
|
306
|
-
if (activeCount === 0 && ready.length === 0) break;
|
|
307
|
-
|
|
308
|
-
// Wait until a running task completes (or a new task becomes ready)
|
|
309
|
-
await waitForProgress();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Let in-flight workers settle before finalizing — if we broke out of the
|
|
313
|
-
// loop due to abort, workers may still be running and would otherwise emit
|
|
314
|
-
// events (task_completed / task_failed) after the 'done' event.
|
|
315
|
-
while (activeCount > 0) {
|
|
316
|
-
await waitForProgress();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Mark any remaining tasks that were never reached as blocked
|
|
320
|
-
for (const [taskId] of remaining) {
|
|
321
|
-
if (!results.has(taskId) && !blocked.has(taskId)) {
|
|
322
|
-
blocked.add(taskId);
|
|
323
|
-
onStatus?.({ kind: "task_blocked", taskId });
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Synthesize final answer (skip LLM synthesis when aborted, but still
|
|
328
|
-
// build the markdown fallback so partial results are preserved)
|
|
329
|
-
const allResults = Array.from(results.values());
|
|
330
|
-
|
|
331
|
-
let finalAnswer: string;
|
|
332
|
-
if (opts.synthesisProvider && !signal?.aborted) {
|
|
333
|
-
onStatus?.({ kind: "synthesis_started" });
|
|
334
|
-
finalAnswer = await synthesizeResults({
|
|
335
|
-
objective: plan.objective,
|
|
336
|
-
results: allResults,
|
|
337
|
-
provider: opts.synthesisProvider,
|
|
338
|
-
modelIntent: opts.synthesisModelIntent ?? "quality-optimized",
|
|
339
|
-
});
|
|
340
|
-
} else {
|
|
341
|
-
if (!signal?.aborted) onStatus?.({ kind: "synthesis_started" });
|
|
342
|
-
finalAnswer = buildMarkdownFallback(plan.objective, allResults);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const totalDurationMs = Date.now() - startTime;
|
|
346
|
-
|
|
347
|
-
// Clean up checkpoint on successful completion (not on abort, so it can be resumed)
|
|
348
|
-
if (runId && !signal?.aborted) {
|
|
349
|
-
removeCheckpoint(runId);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
onStatus?.({ kind: "done", message: `Completed in ${totalDurationMs}ms` });
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
objective: plan.objective,
|
|
356
|
-
plan,
|
|
357
|
-
results: allResults,
|
|
358
|
-
finalAnswer,
|
|
359
|
-
stats: {
|
|
360
|
-
totalTasks: plan.tasks.length,
|
|
361
|
-
completed: allResults.filter((r) => r.status === "completed").length,
|
|
362
|
-
failed: allResults.filter((r) => r.status === "failed").length,
|
|
363
|
-
blocked: blocked.size,
|
|
364
|
-
totalDurationMs,
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function blockDependents(
|
|
370
|
-
taskId: string,
|
|
371
|
-
dependents: Map<string, string[]>,
|
|
372
|
-
blocked: Set<string>,
|
|
373
|
-
onStatus?: OrchestratorStatusCallback,
|
|
374
|
-
): void {
|
|
375
|
-
for (const depId of dependents.get(taskId) ?? []) {
|
|
376
|
-
if (!blocked.has(depId)) {
|
|
377
|
-
blocked.add(depId);
|
|
378
|
-
onStatus?.({ kind: "task_blocked", taskId: depId });
|
|
379
|
-
blockDependents(depId, dependents, blocked, onStatus);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/** Resolves after `ms` milliseconds, or immediately if the signal fires first. */
|
|
385
|
-
function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
386
|
-
if (signal?.aborted) return Promise.resolve();
|
|
387
|
-
return new Promise<void>((resolve) => {
|
|
388
|
-
const timer = setTimeout(done, ms);
|
|
389
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
390
|
-
function done() {
|
|
391
|
-
clearTimeout(timer);
|
|
392
|
-
signal?.removeEventListener("abort", done);
|
|
393
|
-
resolve();
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function buildMarkdownFallback(
|
|
399
|
-
objective: string,
|
|
400
|
-
results: SwarmTaskResult[],
|
|
401
|
-
): string {
|
|
402
|
-
const lines: string[] = [`## Swarm Results: ${objective}`, ""];
|
|
403
|
-
|
|
404
|
-
const completed = results.filter((r) => r.status === "completed");
|
|
405
|
-
const failed = results.filter((r) => r.status === "failed");
|
|
406
|
-
|
|
407
|
-
if (completed.length > 0) {
|
|
408
|
-
lines.push("### Completed Tasks");
|
|
409
|
-
for (const r of completed) {
|
|
410
|
-
lines.push(`- **${r.taskId}**: ${r.summary}`);
|
|
411
|
-
}
|
|
412
|
-
lines.push("");
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (failed.length > 0) {
|
|
416
|
-
lines.push("### Failed Tasks");
|
|
417
|
-
for (const r of failed) {
|
|
418
|
-
lines.push(`- **${r.taskId}**: ${r.summary}`);
|
|
419
|
-
}
|
|
420
|
-
lines.push("");
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return lines.join("\n");
|
|
424
|
-
}
|