@desplega.ai/agent-swarm 1.156.0 → 1.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-c1msptrw.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-wd41qtdc.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-tz2qrnah.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-c3q1zj2t.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-0be1krkm.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-r00j03f3.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-9j4hd0vw.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-ncgxz1r2.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-112w28qc.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-k7pb5kfz.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-mx3bzbv8.js → cli-20cj37vt.js} +1968 -44
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-24w39x6n.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-90x9ewbz.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-08n8jxcw.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-7j2098n5.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-b05d7szr.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-9w4ex8vh.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-28wkjpdp.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-7pgcpcrg.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-jk26c1sz.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-865swy5p.js → cli-c07r9a2m.js} +3 -2
- package/dist/{cli-nyqza443.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-9pyah9c4.js → cli-ftfvggzx.js} +633 -166
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-y8bfxjtw.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-7vj52450.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-tp0d7vzb.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-s8hpxeg4.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-hc2fkhdv.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-t1cd3mhp.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-xa4j64zh.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-szcyv1mm.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +30 -16
- package/dist/{codex-adapter-g4tj1f2g.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-ad8ha7zt.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-z3gd8ppc.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-bezm97r9.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-a97hc33y.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-wtdy1x02.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-7edgmzng.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fvs58a5b.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-phnf93hp.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-7x131mmf.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-1fktvwxc.js → index-a3vn96n8.js} +31 -28
- package/dist/{index-yp8rasxk.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-50jxgw0d.js → index-chgdkq36.js} +14 -14
- package/dist/{index-7jwk7nsm.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-nnz07ebd.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-mg7qcgww.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-ywmnqrpx.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-c63sqfs1.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-2zj8sqkq.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-jgdfm26f.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-3whntb69.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-3zptcc3p.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-dbvpyysc.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-qwx0j8z9.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-sjbcmvep.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-xezqdkb7.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +3 -2
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-agents-md.ts +140 -17
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/deferred-task-waits.ts +28 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scheduler/scheduler.ts +14 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-adapter.test.ts +126 -1
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/deferred-schedule-reaper.test.ts +193 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/schedules/list-schedules.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/templates/skills/scheduling/content.md +1 -1
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3zw68x72.js +0 -37
package/src/be/db.ts
CHANGED
|
@@ -154,6 +154,7 @@ import {
|
|
|
154
154
|
reservedRoleViolation,
|
|
155
155
|
rowToAgent,
|
|
156
156
|
} from "./db/agents";
|
|
157
|
+
import { type ApprovalRequestListFilters, approvalRequestListClause } from "./db/approvals";
|
|
157
158
|
import {
|
|
158
159
|
computeContentHash,
|
|
159
160
|
createContextVersion,
|
|
@@ -184,6 +185,7 @@ export {
|
|
|
184
185
|
extensionAgentAssignmentError,
|
|
185
186
|
getActiveTaskCount,
|
|
186
187
|
getAgentById,
|
|
188
|
+
getAgentDailyTaskCounts,
|
|
187
189
|
getAgentHarnessProviders,
|
|
188
190
|
getAllAgents,
|
|
189
191
|
getLeadAgent,
|
|
@@ -210,6 +212,7 @@ export {
|
|
|
210
212
|
updateAgentStatus,
|
|
211
213
|
updateAgentStatusFromCapacity,
|
|
212
214
|
} from "./db/agents";
|
|
215
|
+
export { type ApprovalRequestSummary, listApprovalRequestSummaries } from "./db/approvals";
|
|
213
216
|
export {
|
|
214
217
|
computeContentHash,
|
|
215
218
|
createContextVersion,
|
|
@@ -256,6 +259,7 @@ export {
|
|
|
256
259
|
getPendingSlackRelayTasks,
|
|
257
260
|
getRecentlyFinishedWorkerTasks,
|
|
258
261
|
getSlackTasksMissingTree,
|
|
262
|
+
getSupersededTasksWithoutResume,
|
|
259
263
|
getTaskById,
|
|
260
264
|
getTaskStats,
|
|
261
265
|
getTasksByAgentId,
|
|
@@ -318,9 +322,13 @@ type TaskTelemetryContext = {
|
|
|
318
322
|
harnessVersion?: string;
|
|
319
323
|
};
|
|
320
324
|
|
|
325
|
+
/** LIKE pattern matching strings that start with `prefix` literally (use with `ESCAPE '\\'`). */
|
|
326
|
+
function literalPrefixPattern(prefix: string): string {
|
|
327
|
+
return `${prefix.replace(/[\\%_]/g, "\\$&")}%`;
|
|
328
|
+
}
|
|
329
|
+
|
|
321
330
|
function assetKeyPrefixPattern(input: string): string {
|
|
322
|
-
|
|
323
|
-
return `${canonical.replace(/[\\%_]/g, "\\$&")}%`;
|
|
331
|
+
return literalPrefixPattern(normalizeAssetKey(input));
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
function emitTaskLifecycleTelemetryAfterCommit(
|
|
@@ -2486,7 +2494,15 @@ export async function createTaskExtended(
|
|
|
2486
2494
|
if (parent.contextKey && !options.contextKey) {
|
|
2487
2495
|
options.contextKey = parent.contextKey;
|
|
2488
2496
|
}
|
|
2489
|
-
|
|
2497
|
+
// followUpConfig is opt-in: only a continuation of the same work carries
|
|
2498
|
+
// the creator's onCompleted/onFailed forward. Lead follow-ups, new
|
|
2499
|
+
// delegations and thread replies all pass parentTaskId too, and
|
|
2500
|
+
// inheriting there spread one task's instructions down the thread.
|
|
2501
|
+
if (
|
|
2502
|
+
parent.followUpConfig &&
|
|
2503
|
+
!options.followUpConfig &&
|
|
2504
|
+
options.inheritParentFollowUpConfig === true
|
|
2505
|
+
) {
|
|
2490
2506
|
options.followUpConfig = parent.followUpConfig;
|
|
2491
2507
|
}
|
|
2492
2508
|
if (parent.routingAffinityInvalid && options.inheritParentRoutingAffinity !== false) {
|
|
@@ -7634,7 +7650,8 @@ type WorkflowRunRow = {
|
|
|
7634
7650
|
status: string;
|
|
7635
7651
|
triggerType: string;
|
|
7636
7652
|
triggerData: string | null;
|
|
7637
|
-
|
|
7653
|
+
/** Absent from list rows: see `WORKFLOW_RUN_SUMMARY_COLUMNS`. */
|
|
7654
|
+
context?: string | null;
|
|
7638
7655
|
error: string | null;
|
|
7639
7656
|
created_by: string | null;
|
|
7640
7657
|
startedAt: string;
|
|
@@ -7762,8 +7779,18 @@ export type WorkflowRunListOptions = {
|
|
|
7762
7779
|
status?: WorkflowRunStatus;
|
|
7763
7780
|
limit?: number;
|
|
7764
7781
|
offset?: number;
|
|
7782
|
+
/**
|
|
7783
|
+
* Read the run `context`. Off by default: it is the one large column, and
|
|
7784
|
+
* `getWorkflowRun` serves it for a single run.
|
|
7785
|
+
*/
|
|
7786
|
+
includeContext?: boolean;
|
|
7765
7787
|
};
|
|
7766
7788
|
|
|
7789
|
+
// Everything but `context`. `triggerData` stays: it is small, and stored
|
|
7790
|
+
// scripts read it off list rows (dedupe by trigger, failure reports).
|
|
7791
|
+
const WORKFLOW_RUN_SUMMARY_COLUMNS =
|
|
7792
|
+
"id, workflowId, status, triggerType, triggerData, error, created_by, startedAt, lastUpdatedAt, finishedAt";
|
|
7793
|
+
|
|
7767
7794
|
export type WorkflowRunPage = {
|
|
7768
7795
|
runs: WorkflowRun[];
|
|
7769
7796
|
page: {
|
|
@@ -7795,8 +7822,9 @@ export async function listWorkflowRuns(
|
|
|
7795
7822
|
params.push(options.offset);
|
|
7796
7823
|
}
|
|
7797
7824
|
|
|
7825
|
+
const columns = options.includeContext ? "*" : WORKFLOW_RUN_SUMMARY_COLUMNS;
|
|
7798
7826
|
const rows = await getDbClient().query<WorkflowRunRow>(
|
|
7799
|
-
`SELECT
|
|
7827
|
+
`SELECT ${columns} FROM workflow_runs
|
|
7800
7828
|
WHERE ${conditions.join(" AND ")}
|
|
7801
7829
|
ORDER BY startedAt DESC, id DESC${pagination}`,
|
|
7802
7830
|
params,
|
|
@@ -7824,7 +7852,7 @@ export async function countWorkflowRuns(
|
|
|
7824
7852
|
export async function listWorkflowRunsPage(
|
|
7825
7853
|
workflowId: string,
|
|
7826
7854
|
options: Required<Pick<WorkflowRunListOptions, "limit" | "offset">> &
|
|
7827
|
-
Pick<WorkflowRunListOptions, "status">,
|
|
7855
|
+
Pick<WorkflowRunListOptions, "status" | "includeContext">,
|
|
7828
7856
|
): Promise<WorkflowRunPage> {
|
|
7829
7857
|
const runs = await listWorkflowRuns(workflowId, options);
|
|
7830
7858
|
const total = await countWorkflowRuns(workflowId, { status: options.status });
|
|
@@ -8056,11 +8084,13 @@ export async function getLastRunStart(workflowId: string): Promise<WorkflowRun |
|
|
|
8056
8084
|
export async function getRetryableSteps(): Promise<WorkflowRunStep[]> {
|
|
8057
8085
|
const now = new Date().toISOString();
|
|
8058
8086
|
const rows = await getDbClient().query<WorkflowRunStepRow>(
|
|
8059
|
-
`SELECT
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
AND nextRetryAt
|
|
8063
|
-
|
|
8087
|
+
`SELECT s.* FROM workflow_run_steps s
|
|
8088
|
+
JOIN workflow_runs r ON r.id = s.runId
|
|
8089
|
+
WHERE s.status = 'failed'
|
|
8090
|
+
AND s.nextRetryAt IS NOT NULL
|
|
8091
|
+
AND s.nextRetryAt <= ?
|
|
8092
|
+
AND r.status IN ('running', 'waiting', 'failed')
|
|
8093
|
+
ORDER BY s.nextRetryAt ASC`,
|
|
8064
8094
|
[now],
|
|
8065
8095
|
);
|
|
8066
8096
|
return rows.map(rowToWorkflowRunStep);
|
|
@@ -8114,6 +8144,37 @@ export async function getLatestStepForNode(
|
|
|
8114
8144
|
return row ? rowToWorkflowRunStep(row) : null;
|
|
8115
8145
|
}
|
|
8116
8146
|
|
|
8147
|
+
/**
|
|
8148
|
+
* The node's latest step when it already covers the current predecessor
|
|
8149
|
+
* completions: it is running, waiting or completed, and no predecessor has
|
|
8150
|
+
* completed a step inserted after it. A loop iteration always has a newer
|
|
8151
|
+
* predecessor completion, so it never matches. Ordered by rowid, which
|
|
8152
|
+
* follows insert order; startedAt can tie within a millisecond.
|
|
8153
|
+
*/
|
|
8154
|
+
export async function getCurrentStepForNode(
|
|
8155
|
+
runId: string,
|
|
8156
|
+
nodeId: string,
|
|
8157
|
+
predecessorIds: string[],
|
|
8158
|
+
): Promise<WorkflowRunStep | null> {
|
|
8159
|
+
const row = await getDbClient().get<WorkflowRunStepRow>(
|
|
8160
|
+
"SELECT * FROM workflow_run_steps WHERE runId = ? AND nodeId = ? ORDER BY rowid DESC LIMIT 1",
|
|
8161
|
+
[runId, nodeId],
|
|
8162
|
+
);
|
|
8163
|
+
if (!row || !["running", "waiting", "completed"].includes(row.status)) return null;
|
|
8164
|
+
if (predecessorIds.length > 0) {
|
|
8165
|
+
const newer = await getDbClient().get<{ found: number }>(
|
|
8166
|
+
`SELECT 1 AS found FROM workflow_run_steps
|
|
8167
|
+
WHERE runId = ? AND status = 'completed'
|
|
8168
|
+
AND nodeId IN (${predecessorIds.map(() => "?").join(", ")})
|
|
8169
|
+
AND rowid > (SELECT rowid FROM workflow_run_steps WHERE id = ?)
|
|
8170
|
+
LIMIT 1`,
|
|
8171
|
+
[runId, ...predecessorIds, row.id],
|
|
8172
|
+
);
|
|
8173
|
+
if (newer) return null;
|
|
8174
|
+
}
|
|
8175
|
+
return rowToWorkflowRunStep(row);
|
|
8176
|
+
}
|
|
8177
|
+
|
|
8117
8178
|
// --- Workflow Version History ---
|
|
8118
8179
|
|
|
8119
8180
|
type WorkflowVersionRow = {
|
|
@@ -9550,6 +9611,7 @@ export async function resolveApprovalRequest(
|
|
|
9550
9611
|
status: "approved" | "rejected" | "timeout";
|
|
9551
9612
|
responses?: unknown;
|
|
9552
9613
|
resolvedBy?: string;
|
|
9614
|
+
resolutionReason?: string;
|
|
9553
9615
|
},
|
|
9554
9616
|
options?: { requireActionableWorkflow?: boolean },
|
|
9555
9617
|
): Promise<ApprovalRequest | null> {
|
|
@@ -9574,7 +9636,8 @@ export async function resolveApprovalRequest(
|
|
|
9574
9636
|
: "";
|
|
9575
9637
|
const row = await getDbClient().get<ApprovalRequestRow>(
|
|
9576
9638
|
`UPDATE approval_requests
|
|
9577
|
-
SET status = ?, responses = ?, resolvedBy = ?,
|
|
9639
|
+
SET status = ?, responses = ?, resolvedBy = ?, resolutionReason = ?, resolvedAt = ?,
|
|
9640
|
+
updatedAt = ?
|
|
9578
9641
|
WHERE id = ? AND status = 'pending'
|
|
9579
9642
|
${actionableWorkflowClause}
|
|
9580
9643
|
RETURNING *`,
|
|
@@ -9582,6 +9645,7 @@ export async function resolveApprovalRequest(
|
|
|
9582
9645
|
data.status,
|
|
9583
9646
|
data.responses ? JSON.stringify(data.responses) : null,
|
|
9584
9647
|
data.resolvedBy ?? null,
|
|
9648
|
+
data.resolutionReason ?? null,
|
|
9585
9649
|
now,
|
|
9586
9650
|
now,
|
|
9587
9651
|
id,
|
|
@@ -9590,6 +9654,23 @@ export async function resolveApprovalRequest(
|
|
|
9590
9654
|
return row ? rowToApprovalRequest(row) : null;
|
|
9591
9655
|
}
|
|
9592
9656
|
|
|
9657
|
+
// Cancels 1 pending request. The `status = 'pending'` guard lets a concurrent
|
|
9658
|
+
// human answer win; null means the row was not pending.
|
|
9659
|
+
export async function cancelApprovalRequestById(
|
|
9660
|
+
id: string,
|
|
9661
|
+
data: { reason: string; resolvedBy: string | null },
|
|
9662
|
+
): Promise<ApprovalRequest | null> {
|
|
9663
|
+
const now = new Date().toISOString();
|
|
9664
|
+
const row = await getDbClient().get<ApprovalRequestRow>(
|
|
9665
|
+
`UPDATE approval_requests
|
|
9666
|
+
SET status = 'cancelled', resolutionReason = ?, resolvedBy = ?, resolvedAt = ?, updatedAt = ?
|
|
9667
|
+
WHERE id = ? AND status = 'pending'
|
|
9668
|
+
RETURNING *`,
|
|
9669
|
+
[data.reason, data.resolvedBy, now, now, id],
|
|
9670
|
+
);
|
|
9671
|
+
return row ? rowToApprovalRequest(row) : null;
|
|
9672
|
+
}
|
|
9673
|
+
|
|
9593
9674
|
export async function cancelPendingApprovalRequestsForRun(
|
|
9594
9675
|
workflowRunId: string,
|
|
9595
9676
|
reason: string,
|
|
@@ -9725,32 +9806,14 @@ export async function updateApprovalRequestNotifications(
|
|
|
9725
9806
|
);
|
|
9726
9807
|
}
|
|
9727
9808
|
|
|
9728
|
-
export async function listApprovalRequests(
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
}): Promise<ApprovalRequest[]> {
|
|
9733
|
-
const conditions: string[] = [];
|
|
9734
|
-
const params: (string | number)[] = [];
|
|
9735
|
-
|
|
9736
|
-
if (filters?.status) {
|
|
9737
|
-
conditions.push("status = ?");
|
|
9738
|
-
params.push(filters.status);
|
|
9739
|
-
}
|
|
9740
|
-
if (filters?.workflowRunId) {
|
|
9741
|
-
conditions.push("workflowRunId = ?");
|
|
9742
|
-
params.push(filters.workflowRunId);
|
|
9743
|
-
}
|
|
9744
|
-
|
|
9745
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9746
|
-
const limit = filters?.limit ?? 100;
|
|
9747
|
-
params.push(limit);
|
|
9748
|
-
|
|
9809
|
+
export async function listApprovalRequests(
|
|
9810
|
+
filters?: ApprovalRequestListFilters,
|
|
9811
|
+
): Promise<ApprovalRequest[]> {
|
|
9812
|
+
const { sql, params } = approvalRequestListClause(filters);
|
|
9749
9813
|
const rows = await getDbClient().query<ApprovalRequestRow>(
|
|
9750
|
-
`SELECT * FROM approval_requests ${
|
|
9814
|
+
`SELECT * FROM approval_requests ${sql}`,
|
|
9751
9815
|
params,
|
|
9752
9816
|
);
|
|
9753
|
-
|
|
9754
9817
|
return rows.map(rowToApprovalRequest);
|
|
9755
9818
|
}
|
|
9756
9819
|
|
|
@@ -9793,13 +9856,34 @@ export async function getApprovalRequestByStepId(stepId: string): Promise<Approv
|
|
|
9793
9856
|
return row ? rowToApprovalRequest(row) : null;
|
|
9794
9857
|
}
|
|
9795
9858
|
|
|
9796
|
-
//
|
|
9797
|
-
export async function getExpiredPendingApprovals(
|
|
9859
|
+
// Called by timeoutExpiredApprovalRequests in src/be/approval-sweeps.ts on each heartbeat tick.
|
|
9860
|
+
export async function getExpiredPendingApprovals(opts?: {
|
|
9861
|
+
now?: string;
|
|
9862
|
+
}): Promise<ApprovalRequest[]> {
|
|
9798
9863
|
const rows = await getDbClient().query<ApprovalRequestRow>(
|
|
9799
9864
|
`SELECT * FROM approval_requests
|
|
9800
9865
|
WHERE status = 'pending'
|
|
9801
9866
|
AND expiresAt IS NOT NULL
|
|
9802
|
-
AND expiresAt <
|
|
9867
|
+
AND expiresAt < ?
|
|
9868
|
+
ORDER BY expiresAt ASC`,
|
|
9869
|
+
[opts?.now ?? new Date().toISOString()],
|
|
9870
|
+
);
|
|
9871
|
+
return rows.map(rowToApprovalRequest);
|
|
9872
|
+
}
|
|
9873
|
+
|
|
9874
|
+
// Pending requests with no explicit deadline created before `cutoff`. Served
|
|
9875
|
+
// by idx_approval_requests_pending_created (migration 166). Called by
|
|
9876
|
+
// autoCancelStaleApprovalRequests in src/be/approval-sweeps.ts.
|
|
9877
|
+
export async function getStaleApprovalRequests(opts: {
|
|
9878
|
+
cutoff: string;
|
|
9879
|
+
}): Promise<ApprovalRequest[]> {
|
|
9880
|
+
const rows = await getDbClient().query<ApprovalRequestRow>(
|
|
9881
|
+
`SELECT * FROM approval_requests
|
|
9882
|
+
WHERE status = 'pending'
|
|
9883
|
+
AND expiresAt IS NULL
|
|
9884
|
+
AND createdAt < ?
|
|
9885
|
+
ORDER BY createdAt ASC`,
|
|
9886
|
+
[opts.cutoff],
|
|
9803
9887
|
);
|
|
9804
9888
|
return rows.map(rowToApprovalRequest);
|
|
9805
9889
|
}
|
|
@@ -12375,9 +12459,9 @@ export async function listTaskTemplates(opts?: {
|
|
|
12375
12459
|
// ============================================================================
|
|
12376
12460
|
|
|
12377
12461
|
/**
|
|
12378
|
-
* Walk the
|
|
12379
|
-
*
|
|
12380
|
-
*
|
|
12462
|
+
* Walk the chain rooted at `rootTaskId`, ordered by `createdAt` (root first; `rowid` breaks
|
|
12463
|
+
* ties as idx_agent_tasks_created did). `CROSS JOIN` keeps the chain as the outer loop: with
|
|
12464
|
+
* a plain JOIN, SQLite scanned all of agent_tasks to skip the sort.
|
|
12381
12465
|
*/
|
|
12382
12466
|
export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]> {
|
|
12383
12467
|
const rows = await getDbClient().query<AgentTaskRow>(
|
|
@@ -12387,9 +12471,9 @@ export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]>
|
|
|
12387
12471
|
SELECT t.id FROM agent_tasks t
|
|
12388
12472
|
JOIN chain c ON t.parentTaskId = c.id
|
|
12389
12473
|
)
|
|
12390
|
-
SELECT t.* FROM
|
|
12391
|
-
JOIN
|
|
12392
|
-
ORDER BY t.createdAt`,
|
|
12474
|
+
SELECT t.* FROM chain
|
|
12475
|
+
CROSS JOIN agent_tasks t ON t.id = chain.id
|
|
12476
|
+
ORDER BY t.createdAt, t.rowid`,
|
|
12393
12477
|
[rootTaskId],
|
|
12394
12478
|
);
|
|
12395
12479
|
return rows.map(rowToAgentTask);
|
|
@@ -12434,6 +12518,12 @@ interface ListRecentSessionsOpts {
|
|
|
12434
12518
|
q?: string;
|
|
12435
12519
|
/** When set, restrict to root tasks where `requestedByUserId` equals this value. NULL rows are excluded. */
|
|
12436
12520
|
requestedByUserId?: string;
|
|
12521
|
+
/**
|
|
12522
|
+
* When set, restrict to root tasks whose `contextKey` starts with this
|
|
12523
|
+
* literal prefix (`%`, `_` and `\` are escaped). Powers the UI contextual
|
|
12524
|
+
* session panel's per-page dropdown.
|
|
12525
|
+
*/
|
|
12526
|
+
contextKeyPrefix?: string;
|
|
12437
12527
|
/** When true, return slim `SessionListItemSummary` rows (default: full). */
|
|
12438
12528
|
slim?: boolean;
|
|
12439
12529
|
}
|
|
@@ -12469,6 +12559,10 @@ export async function listRecentSessions(
|
|
|
12469
12559
|
conditions.push("r.requestedByUserId = ?");
|
|
12470
12560
|
params.push(requestedByUserId);
|
|
12471
12561
|
}
|
|
12562
|
+
if (opts?.contextKeyPrefix) {
|
|
12563
|
+
conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
|
|
12564
|
+
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
12565
|
+
}
|
|
12472
12566
|
params.push(limit, offset);
|
|
12473
12567
|
|
|
12474
12568
|
const rootRows = await getDbClient().query<
|
|
@@ -12505,7 +12599,7 @@ export async function listRecentSessions(
|
|
|
12505
12599
|
FROM agent_tasks r
|
|
12506
12600
|
JOIN agg a ON a.root_id = r.id
|
|
12507
12601
|
LEFT JOIN latest_status ls ON ls.root_id = r.id
|
|
12508
|
-
ORDER BY a.lastActivityAt DESC
|
|
12602
|
+
ORDER BY a.lastActivityAt DESC, r.createdAt DESC, r.id DESC
|
|
12509
12603
|
LIMIT ? OFFSET ?`,
|
|
12510
12604
|
params,
|
|
12511
12605
|
);
|
|
@@ -12540,7 +12634,7 @@ export async function listRecentSessions(
|
|
|
12540
12634
|
* a plain count, no recursive chain walk needed.
|
|
12541
12635
|
*/
|
|
12542
12636
|
export async function countSessions(
|
|
12543
|
-
opts?: Pick<ListRecentSessionsOpts, "source" | "q" | "requestedByUserId">,
|
|
12637
|
+
opts?: Pick<ListRecentSessionsOpts, "source" | "q" | "requestedByUserId" | "contextKeyPrefix">,
|
|
12544
12638
|
): Promise<number> {
|
|
12545
12639
|
const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
|
|
12546
12640
|
const q = opts?.q?.trim();
|
|
@@ -12562,6 +12656,10 @@ export async function countSessions(
|
|
|
12562
12656
|
conditions.push("requestedByUserId = ?");
|
|
12563
12657
|
params.push(requestedByUserId);
|
|
12564
12658
|
}
|
|
12659
|
+
if (opts?.contextKeyPrefix) {
|
|
12660
|
+
conditions.push(`contextKey LIKE ? ESCAPE '\\'`);
|
|
12661
|
+
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
12662
|
+
}
|
|
12565
12663
|
|
|
12566
12664
|
const row = await getDbClient().get<{ count: number }>(
|
|
12567
12665
|
`SELECT COUNT(*) AS count FROM agent_tasks WHERE ${conditions.join(" AND ")}`,
|
|
@@ -13434,42 +13532,96 @@ export async function incrKv(namespace: string, key: string, by: number): Promis
|
|
|
13434
13532
|
});
|
|
13435
13533
|
}
|
|
13436
13534
|
|
|
13535
|
+
/**
|
|
13536
|
+
* Smallest string greater than every string that starts with `prefix`, in
|
|
13537
|
+
* code-point order (which is SQLite's BINARY order over UTF-8). `null` means
|
|
13538
|
+
* no upper bound exists (the prefix is all U+10FFFF).
|
|
13539
|
+
*/
|
|
13540
|
+
export function kvPrefixUpperBound(prefix: string): string | null {
|
|
13541
|
+
const codePoints = Array.from(prefix);
|
|
13542
|
+
while (codePoints.length > 0) {
|
|
13543
|
+
const last = codePoints.pop()!.codePointAt(0)!;
|
|
13544
|
+
if (last < 0x10ffff) {
|
|
13545
|
+
const next = last + 1 === 0xd800 ? 0xe000 : last + 1;
|
|
13546
|
+
return codePoints.join("") + String.fromCodePoint(next);
|
|
13547
|
+
}
|
|
13548
|
+
}
|
|
13549
|
+
return null;
|
|
13550
|
+
}
|
|
13551
|
+
|
|
13552
|
+
/**
|
|
13553
|
+
* Key-prefix predicate as a range on the (namespace, key) primary key.
|
|
13554
|
+
* `key LIKE 'p%'` cannot use that index (LIKE is case-insensitive, the PK is
|
|
13555
|
+
* BINARY), so it scanned every row of the namespace. A range seeks straight
|
|
13556
|
+
* to the prefix and matches it exactly (case-sensitive).
|
|
13557
|
+
*/
|
|
13558
|
+
function kvPrefixCondition(prefix: string | undefined): { sql: string; params: string[] } {
|
|
13559
|
+
if (prefix === undefined || prefix.length === 0) return { sql: "", params: [] };
|
|
13560
|
+
const upper = kvPrefixUpperBound(prefix);
|
|
13561
|
+
return upper === null
|
|
13562
|
+
? { sql: "AND key >= ?", params: [prefix] }
|
|
13563
|
+
: { sql: "AND key >= ? AND key < ?", params: [prefix, upper] };
|
|
13564
|
+
}
|
|
13565
|
+
|
|
13566
|
+
/** Top-level JSON field equality, evaluated in SQLite before rows reach JS. */
|
|
13567
|
+
export type KvJsonFieldEquals = { field: string; value: string | number | boolean };
|
|
13568
|
+
|
|
13569
|
+
function kvJsonFieldConditions(filters: KvJsonFieldEquals[] | undefined): {
|
|
13570
|
+
sql: string;
|
|
13571
|
+
params: (string | number | boolean)[];
|
|
13572
|
+
} {
|
|
13573
|
+
const parts: string[] = [];
|
|
13574
|
+
const params: (string | number | boolean)[] = [];
|
|
13575
|
+
for (const filter of filters ?? []) {
|
|
13576
|
+
// JSON path quoting has no escape for these characters.
|
|
13577
|
+
if (/["\\]/.test(filter.field)) {
|
|
13578
|
+
throw new Error(`unsupported JSON field name: ${filter.field}`);
|
|
13579
|
+
}
|
|
13580
|
+
// Only safe integers compare exactly: SQLite parses a stored literal like
|
|
13581
|
+
// 1000000000000000100 as an exact INTEGER (and fractions with its own
|
|
13582
|
+
// float parser) while the bound JS number is a double, so SQL could drop
|
|
13583
|
+
// a row that JS `===` matches. Other numbers are left to the caller.
|
|
13584
|
+
if (typeof filter.value === "number" && !Number.isSafeInteger(filter.value)) continue;
|
|
13585
|
+
// json_valid guards the corrupt rows decodeKvRow tolerates: json_extract
|
|
13586
|
+
// would abort the whole query on one of them.
|
|
13587
|
+
parts.push(
|
|
13588
|
+
"AND (CASE WHEN value_type = 'json' AND json_valid(value) THEN json_extract(value, ?) END) = ?",
|
|
13589
|
+
);
|
|
13590
|
+
params.push(`$."${filter.field}"`, filter.value);
|
|
13591
|
+
}
|
|
13592
|
+
return { sql: parts.join(" "), params };
|
|
13593
|
+
}
|
|
13594
|
+
|
|
13437
13595
|
/**
|
|
13438
13596
|
* List entries in a namespace, optionally filtered by prefix. Expired rows
|
|
13439
13597
|
* are filtered out by the SELECT (no inline DELETE — listing should be a
|
|
13440
13598
|
* stable cursor; sweeping happens on point-reads instead).
|
|
13441
13599
|
*
|
|
13600
|
+
* `jsonFieldEquals` narrows the rows in SQL so non-matching values are never
|
|
13601
|
+
* decoded. SQL equality is looser than JS `===` (JSON `true` equals `1`) and
|
|
13602
|
+
* numbers that are not safe integers are not filtered at all, so callers that
|
|
13603
|
+
* need exact semantics re-check the decoded values.
|
|
13604
|
+
*
|
|
13442
13605
|
* `limit` is capped by the caller (HTTP enforces ≤1000); helper does no extra
|
|
13443
13606
|
* bounds-check beyond what SQL accepts.
|
|
13444
13607
|
*/
|
|
13445
13608
|
export async function listKv(
|
|
13446
13609
|
namespace: string,
|
|
13447
|
-
opts: { prefix?: string; limit: number; offset: number },
|
|
13610
|
+
opts: { prefix?: string; limit: number; offset: number; jsonFieldEquals?: KvJsonFieldEquals[] },
|
|
13448
13611
|
): Promise<KvEntry[]> {
|
|
13449
13612
|
const now = Date.now();
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
const escaped = opts.prefix.replace(/[\\%_]/g, "\\$&");
|
|
13453
|
-
const rows = await getDbClient().query<KvRow>(
|
|
13454
|
-
`SELECT namespace, key, value, value_type, expires_at, created_at, updated_at
|
|
13455
|
-
FROM kv_entries
|
|
13456
|
-
WHERE namespace = ?
|
|
13457
|
-
AND (expires_at IS NULL OR expires_at > ?)
|
|
13458
|
-
AND key LIKE ? ESCAPE '\\'
|
|
13459
|
-
ORDER BY key
|
|
13460
|
-
LIMIT ? OFFSET ?`,
|
|
13461
|
-
[namespace, now, `${escaped}%`, opts.limit, opts.offset],
|
|
13462
|
-
);
|
|
13463
|
-
return rows.map(decodeKvRow);
|
|
13464
|
-
}
|
|
13613
|
+
const prefix = kvPrefixCondition(opts.prefix);
|
|
13614
|
+
const json = kvJsonFieldConditions(opts.jsonFieldEquals);
|
|
13465
13615
|
const rows = await getDbClient().query<KvRow>(
|
|
13466
13616
|
`SELECT namespace, key, value, value_type, expires_at, created_at, updated_at
|
|
13467
13617
|
FROM kv_entries
|
|
13468
13618
|
WHERE namespace = ?
|
|
13469
13619
|
AND (expires_at IS NULL OR expires_at > ?)
|
|
13620
|
+
${prefix.sql}
|
|
13621
|
+
${json.sql}
|
|
13470
13622
|
ORDER BY key
|
|
13471
13623
|
LIMIT ? OFFSET ?`,
|
|
13472
|
-
[namespace, now, opts.limit, opts.offset],
|
|
13624
|
+
[namespace, now, ...prefix.params, ...json.params, opts.limit, opts.offset],
|
|
13473
13625
|
);
|
|
13474
13626
|
return rows.map(decodeKvRow);
|
|
13475
13627
|
}
|
|
@@ -13480,22 +13632,13 @@ export async function listKv(
|
|
|
13480
13632
|
*/
|
|
13481
13633
|
export async function countKv(namespace: string, opts: { prefix?: string }): Promise<number> {
|
|
13482
13634
|
const now = Date.now();
|
|
13483
|
-
|
|
13484
|
-
const escaped = opts.prefix.replace(/[\\%_]/g, "\\$&");
|
|
13485
|
-
const row = await getDbClient().get<{ n: number }>(
|
|
13486
|
-
`SELECT COUNT(*) AS n FROM kv_entries
|
|
13487
|
-
WHERE namespace = ?
|
|
13488
|
-
AND (expires_at IS NULL OR expires_at > ?)
|
|
13489
|
-
AND key LIKE ? ESCAPE '\\'`,
|
|
13490
|
-
[namespace, now, `${escaped}%`],
|
|
13491
|
-
);
|
|
13492
|
-
return row?.n ?? 0;
|
|
13493
|
-
}
|
|
13635
|
+
const prefix = kvPrefixCondition(opts.prefix);
|
|
13494
13636
|
const row = await getDbClient().get<{ n: number }>(
|
|
13495
13637
|
`SELECT COUNT(*) AS n FROM kv_entries
|
|
13496
13638
|
WHERE namespace = ?
|
|
13497
|
-
AND (expires_at IS NULL OR expires_at > ?)
|
|
13498
|
-
|
|
13639
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
13640
|
+
${prefix.sql}`,
|
|
13641
|
+
[namespace, now, ...prefix.params],
|
|
13499
13642
|
);
|
|
13500
13643
|
return row?.n ?? 0;
|
|
13501
13644
|
}
|
package/src/be/events.ts
CHANGED
|
@@ -238,6 +238,15 @@ export async function getEventsFiltered(filters: {
|
|
|
238
238
|
since?: string;
|
|
239
239
|
until?: string;
|
|
240
240
|
limit?: number;
|
|
241
|
+
/** Match any of these event names (`event IN (...)`), ANDed with `event`. */
|
|
242
|
+
events?: EventName[];
|
|
243
|
+
/**
|
|
244
|
+
* Return only the newest row per (event, data.field) pair, for every pair of
|
|
245
|
+
* these event names and field values. Each pair is its own indexed
|
|
246
|
+
* `ORDER BY createdAt DESC LIMIT 1` lookup, so the cost does not grow with
|
|
247
|
+
* the agent's event history.
|
|
248
|
+
*/
|
|
249
|
+
latestPerDataField?: { events: EventName[]; dataFields: string[] };
|
|
241
250
|
}): Promise<SwarmEvent[]> {
|
|
242
251
|
const conditions: string[] = [];
|
|
243
252
|
const params: (string | number)[] = [];
|
|
@@ -250,6 +259,10 @@ export async function getEventsFiltered(filters: {
|
|
|
250
259
|
conditions.push("event = ?");
|
|
251
260
|
params.push(filters.event);
|
|
252
261
|
}
|
|
262
|
+
if (filters.events && filters.events.length > 0) {
|
|
263
|
+
conditions.push(`event IN (${filters.events.map(() => "?").join(", ")})`);
|
|
264
|
+
params.push(...filters.events);
|
|
265
|
+
}
|
|
253
266
|
if (filters.status) {
|
|
254
267
|
conditions.push("status = ?");
|
|
255
268
|
params.push(filters.status);
|
|
@@ -283,8 +296,27 @@ export async function getEventsFiltered(filters: {
|
|
|
283
296
|
params.push(filters.until);
|
|
284
297
|
}
|
|
285
298
|
|
|
286
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
287
299
|
const limit = filters.limit ?? 100;
|
|
300
|
+
|
|
301
|
+
if (filters.latestPerDataField) {
|
|
302
|
+
const lookups: string[] = [];
|
|
303
|
+
const lookupParams: (string | number)[] = [];
|
|
304
|
+
for (const event of filters.latestPerDataField.events) {
|
|
305
|
+
for (const field of filters.latestPerDataField.dataFields) {
|
|
306
|
+
const where = [...conditions, "event = ?", "json_extract(data, '$.field') = ?"];
|
|
307
|
+
lookups.push(
|
|
308
|
+
`SELECT * FROM (SELECT * FROM events WHERE ${where.join(" AND ")} ORDER BY createdAt DESC LIMIT 1)`,
|
|
309
|
+
);
|
|
310
|
+
lookupParams.push(...params, event, field);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (lookups.length === 0) return [];
|
|
314
|
+
const sql = `${lookups.join(" UNION ALL ")} ORDER BY createdAt DESC LIMIT ?`;
|
|
315
|
+
const rows = await getDbClient().query<EventRow>(sql, [...lookupParams, limit]);
|
|
316
|
+
return rows.map(rowToSwarmEvent);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
288
320
|
params.push(limit);
|
|
289
321
|
|
|
290
322
|
const sql = `SELECT * FROM events ${where} ORDER BY createdAt DESC LIMIT ?`;
|
|
@@ -118,8 +118,9 @@ export async function expandCandidatesWithGraph(
|
|
|
118
118
|
const parent = parentById.get(row.fromMemoryId);
|
|
119
119
|
if (!parent) continue;
|
|
120
120
|
const strength = typeof row.linkStrength === "number" ? row.linkStrength : 1.0;
|
|
121
|
-
// Derive from the parent's
|
|
122
|
-
//
|
|
121
|
+
// Derive from the parent's pre-decay match score on the shared [0,1]
|
|
122
|
+
// scale (vec cosine, hybrid fused cosine, fts rank). The fts arm ships
|
|
123
|
+
// `similarity` with the parent's recency decay already applied, and
|
|
123
124
|
// rerank() will apply the NEIGHBOR's own decay to this candidate. Using
|
|
124
125
|
// the decayed value would stack two decay factors on one score.
|
|
125
126
|
const parentBase = parent.rawSimilarity ?? parent.similarity;
|
|
@@ -129,6 +130,7 @@ export async function expandCandidatesWithGraph(
|
|
|
129
130
|
neighbors.set(row.id, {
|
|
130
131
|
...rowToCandidate(row, similarity),
|
|
131
132
|
retrievalSource: "graph",
|
|
133
|
+
graphParentId: parent.id,
|
|
132
134
|
// The neighbor's own decay is applied exactly once by rerank().
|
|
133
135
|
recencyDecayApplied: false,
|
|
134
136
|
});
|
|
@@ -153,7 +155,12 @@ export async function expandCandidatesWithGraph(
|
|
|
153
155
|
if (existingIndex !== undefined) {
|
|
154
156
|
// Dedupe against organic candidates: keep whichever entry the reranker
|
|
155
157
|
// will score higher (same memory, so all non-similarity factors match).
|
|
156
|
-
|
|
158
|
+
// rerank() caps a graph entry at its parent's composite, so compare the
|
|
159
|
+
// capped graph score; otherwise a low-quality parent's link replaces a
|
|
160
|
+
// stronger organic match and rerank() then demotes it below the parent.
|
|
161
|
+
const parent = parentById.get(neighbor.graphParentId!)!;
|
|
162
|
+
const graphScore = Math.min(computeScore(neighbor, now), computeScore(parent, now));
|
|
163
|
+
if (graphScore > computeScore(result[existingIndex]!, now)) {
|
|
157
164
|
result[existingIndex] = neighbor;
|
|
158
165
|
}
|
|
159
166
|
continue;
|