@retinue/agentkit 0.1.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/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow definitions and executions in Postgres — #187, #186.
|
|
3
|
+
*
|
|
4
|
+
* Two things are enforced by the schema rather than by this file, because a rule in code is a rule one code path
|
|
5
|
+
* can miss:
|
|
6
|
+
*
|
|
7
|
+
* - **A version cannot be overwritten.** `(tenant_id, flow_id, version)` is the primary key and the insert has no
|
|
8
|
+
* `ON CONFLICT`, so a second write of the same version is a constraint violation. An execution pins a version
|
|
9
|
+
* and reads it for its whole life; a definition that could change under it would change an automation's shape
|
|
10
|
+
* halfway through.
|
|
11
|
+
* - **A save cannot move an execution backwards.** The update carries `WHERE steps <= $n`, so a stale document
|
|
12
|
+
* from a slower worker updates nothing. A flow that goes backwards re-performs external writes, which is the
|
|
13
|
+
* failure the whole module exists to prevent — and doing it in the `WHERE` means two concurrent saves are
|
|
14
|
+
* ordered by the database rather than by whichever read happened first.
|
|
15
|
+
*/
|
|
16
|
+
import { AgentPlatformError } from "../../core/errors.js";
|
|
17
|
+
const int = (value) => (typeof value === "number" ? value : Number.parseInt(value, 10));
|
|
18
|
+
const iso = (value) => (value instanceof Date ? value.toISOString() : value);
|
|
19
|
+
const toDefinition = (row) => ({
|
|
20
|
+
flowId: row.flow_id,
|
|
21
|
+
version: int(row.version),
|
|
22
|
+
name: row.name,
|
|
23
|
+
kind: row.kind === "team" ? "team" : "flow",
|
|
24
|
+
// `jsonb` comes back parsed from `pg`, and as a string from some executors. Handling both means the adapter
|
|
25
|
+
// works under PGlite and a pool without a second code path for "which driver is this".
|
|
26
|
+
definition: typeof row.definition === "string" ? JSON.parse(row.definition) : row.definition,
|
|
27
|
+
createdAt: iso(row.created_at),
|
|
28
|
+
...(row.created_by === null ? {} : { createdBy: row.created_by }),
|
|
29
|
+
});
|
|
30
|
+
const take = (limit) => Math.max(1, Math.min(limit ?? 50, 500));
|
|
31
|
+
export const createPostgresFlowDefinitionStore = (sql) => ({
|
|
32
|
+
async put({ tenantId, definition }) {
|
|
33
|
+
try {
|
|
34
|
+
await sql.query(`INSERT INTO flow_definitions (tenant_id, flow_id, version, name, kind, definition, created_at, created_by)
|
|
35
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::timestamptz, $8)`, [
|
|
36
|
+
tenantId,
|
|
37
|
+
definition.flowId,
|
|
38
|
+
definition.version,
|
|
39
|
+
definition.name,
|
|
40
|
+
definition.kind,
|
|
41
|
+
JSON.stringify(definition.definition),
|
|
42
|
+
definition.createdAt,
|
|
43
|
+
definition.createdBy ?? null,
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
/**
|
|
48
|
+
* Translated, not passed through.
|
|
49
|
+
*
|
|
50
|
+
* The port's contract is that overwriting a version is refused; a caller should not have to recognise a
|
|
51
|
+
* Postgres unique-violation code to know that happened. Anything else is rethrown, because swallowing an
|
|
52
|
+
* unrelated failure here would report a conflict for a disk that is full.
|
|
53
|
+
*/
|
|
54
|
+
const code = error.code;
|
|
55
|
+
if (code === "23505" || /duplicate key|unique/i.test(String(error.message))) {
|
|
56
|
+
throw new AgentPlatformError({
|
|
57
|
+
code: "conflict",
|
|
58
|
+
message: `${definition.flowId} version ${definition.version} already exists — publish a new version instead`,
|
|
59
|
+
retryable: false,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async get({ tenantId, flowId, version }) {
|
|
66
|
+
const rows = await sql.query(`SELECT flow_id, version, name, kind, definition, created_at, created_by
|
|
67
|
+
FROM flow_definitions WHERE tenant_id = $1 AND flow_id = $2 AND version = $3`, [tenantId, flowId, version]);
|
|
68
|
+
return rows[0] === undefined ? null : toDefinition(rows[0]);
|
|
69
|
+
},
|
|
70
|
+
async latest({ tenantId, flowId }) {
|
|
71
|
+
const rows = await sql.query(`SELECT flow_id, version, name, kind, definition, created_at, created_by
|
|
72
|
+
FROM flow_definitions WHERE tenant_id = $1 AND flow_id = $2
|
|
73
|
+
ORDER BY version DESC LIMIT 1`, [tenantId, flowId]);
|
|
74
|
+
return rows[0] === undefined ? null : toDefinition(rows[0]);
|
|
75
|
+
},
|
|
76
|
+
async list({ tenantId, limit, cursor }) {
|
|
77
|
+
const size = take(limit);
|
|
78
|
+
/**
|
|
79
|
+
* The latest version per flow, in one query.
|
|
80
|
+
*
|
|
81
|
+
* `DISTINCT ON` rather than a window function or a join against a grouped subquery: it is the shortest
|
|
82
|
+
* statement that says "one row per flow_id, the highest version", and a caller asking what flows exist means
|
|
83
|
+
* distinct flows rather than every version of every one.
|
|
84
|
+
*/
|
|
85
|
+
const rows = await sql.query(`SELECT * FROM (
|
|
86
|
+
SELECT DISTINCT ON (flow_id) flow_id, version, name, kind, definition, created_at, created_by
|
|
87
|
+
FROM flow_definitions WHERE tenant_id = $1
|
|
88
|
+
ORDER BY flow_id, version DESC
|
|
89
|
+
) latest
|
|
90
|
+
WHERE ($2::text IS NULL OR flow_id > $2)
|
|
91
|
+
ORDER BY flow_id
|
|
92
|
+
LIMIT $3`, [tenantId, cursor ?? null, size + 1]);
|
|
93
|
+
const page = rows.slice(0, size).map(toDefinition);
|
|
94
|
+
return {
|
|
95
|
+
items: page,
|
|
96
|
+
...(rows.length > size && page.length > 0 ? { nextCursor: page[page.length - 1].flowId } : {}),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const toExecution = (row) => ({
|
|
101
|
+
id: row.id,
|
|
102
|
+
flowId: row.flow_id,
|
|
103
|
+
flowVersion: int(row.flow_version),
|
|
104
|
+
runId: row.run_id,
|
|
105
|
+
status: row.status,
|
|
106
|
+
currentStep: row.current_step,
|
|
107
|
+
steps: int(row.steps),
|
|
108
|
+
execution: typeof row.execution === "string" ? JSON.parse(row.execution) : row.execution,
|
|
109
|
+
...(row.waiting_signal === null ? {} : { waitingSignal: row.waiting_signal }),
|
|
110
|
+
...(row.waiting_run_id === null ? {} : { waitingRunId: row.waiting_run_id }),
|
|
111
|
+
startedAt: iso(row.started_at),
|
|
112
|
+
...(row.finished_at === null ? {} : { finishedAt: iso(row.finished_at) }),
|
|
113
|
+
});
|
|
114
|
+
const EXECUTION_COLUMNS = `id, flow_id, flow_version, run_id, status, current_step, steps, execution,
|
|
115
|
+
waiting_signal, waiting_run_id, started_at, finished_at`;
|
|
116
|
+
export const createPostgresFlowExecutionStore = (sql) => ({
|
|
117
|
+
async create({ tenantId, execution }) {
|
|
118
|
+
try {
|
|
119
|
+
await sql.query(`INSERT INTO flow_executions
|
|
120
|
+
(tenant_id, id, flow_id, flow_version, run_id, status, current_step, steps, execution,
|
|
121
|
+
waiting_signal, waiting_run_id, started_at, finished_at)
|
|
122
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12::timestamptz, $13::timestamptz)`, [
|
|
123
|
+
tenantId,
|
|
124
|
+
execution.id,
|
|
125
|
+
execution.flowId,
|
|
126
|
+
execution.flowVersion,
|
|
127
|
+
execution.runId,
|
|
128
|
+
execution.status,
|
|
129
|
+
execution.currentStep,
|
|
130
|
+
execution.steps,
|
|
131
|
+
JSON.stringify(execution.execution),
|
|
132
|
+
execution.waitingSignal ?? null,
|
|
133
|
+
execution.waitingRunId ?? null,
|
|
134
|
+
execution.startedAt,
|
|
135
|
+
execution.finishedAt ?? null,
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const code = error.code;
|
|
140
|
+
if (code === "23505" || /duplicate key|unique/i.test(String(error.message))) {
|
|
141
|
+
throw new AgentPlatformError({
|
|
142
|
+
code: "conflict",
|
|
143
|
+
message: `flow execution ${execution.id} already exists`,
|
|
144
|
+
retryable: false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async save({ tenantId, execution }) {
|
|
151
|
+
/**
|
|
152
|
+
* `steps <= $8` in the predicate, so the database orders two concurrent saves.
|
|
153
|
+
*
|
|
154
|
+
* `<=` and not `<`: parking on a question completes no step, so an equal count is a legitimate status change.
|
|
155
|
+
* Rejecting it would make "waiting" unrecordable, which is a whole class of state that could never be stored.
|
|
156
|
+
*/
|
|
157
|
+
await sql.query(`UPDATE flow_executions
|
|
158
|
+
SET status = $3, current_step = $4, steps = $5, execution = $6::jsonb,
|
|
159
|
+
waiting_signal = $7, waiting_run_id = $10, finished_at = $9::timestamptz
|
|
160
|
+
WHERE tenant_id = $1 AND id = $2 AND steps <= $8`, [
|
|
161
|
+
tenantId,
|
|
162
|
+
execution.id,
|
|
163
|
+
execution.status,
|
|
164
|
+
execution.currentStep,
|
|
165
|
+
execution.steps,
|
|
166
|
+
JSON.stringify(execution.execution),
|
|
167
|
+
execution.waitingSignal ?? null,
|
|
168
|
+
execution.steps,
|
|
169
|
+
execution.finishedAt ?? null,
|
|
170
|
+
execution.waitingRunId ?? null,
|
|
171
|
+
]);
|
|
172
|
+
},
|
|
173
|
+
async get({ tenantId, executionId }) {
|
|
174
|
+
const rows = await sql.query(`SELECT ${EXECUTION_COLUMNS} FROM flow_executions WHERE tenant_id = $1 AND id = $2`, [tenantId, executionId]);
|
|
175
|
+
return rows[0] === undefined ? null : toExecution(rows[0]);
|
|
176
|
+
},
|
|
177
|
+
async waitingOnSignal({ tenantId, signal, limit }) {
|
|
178
|
+
// `status = 'waiting'` in the predicate, not only the signal: a signal must not resume an execution that
|
|
179
|
+
// never asked for it, and a stale `waiting_signal` on a running row is exactly that.
|
|
180
|
+
const rows = await sql.query(`SELECT ${EXECUTION_COLUMNS} FROM flow_executions
|
|
181
|
+
WHERE tenant_id = $1 AND status = 'waiting' AND waiting_signal = $2
|
|
182
|
+
ORDER BY started_at LIMIT $3`, [tenantId, signal, take(limit)]);
|
|
183
|
+
return rows.map(toExecution);
|
|
184
|
+
},
|
|
185
|
+
async waitingOnRun({ tenantId, runId }) {
|
|
186
|
+
// `status = 'waiting'` as well as the id: a stale `waiting_run_id` on a running execution is not something a
|
|
187
|
+
// settled run should resume, because it is not waiting for one.
|
|
188
|
+
const rows = await sql.query(`SELECT ${EXECUTION_COLUMNS} FROM flow_executions
|
|
189
|
+
WHERE tenant_id = $1 AND status = 'waiting' AND waiting_run_id = $2
|
|
190
|
+
LIMIT 1`, [tenantId, runId]);
|
|
191
|
+
return rows[0] === undefined ? null : toExecution(rows[0]);
|
|
192
|
+
},
|
|
193
|
+
async listByFlow({ tenantId, flowId, limit, cursor }) {
|
|
194
|
+
const size = take(limit);
|
|
195
|
+
const rows = await sql.query(`SELECT ${EXECUTION_COLUMNS} FROM flow_executions
|
|
196
|
+
WHERE tenant_id = $1 AND flow_id = $2 AND ($3::text IS NULL OR id > $3)
|
|
197
|
+
ORDER BY started_at DESC, id
|
|
198
|
+
LIMIT $4`, [tenantId, flowId, cursor ?? null, size + 1]);
|
|
199
|
+
const page = rows.slice(0, size).map(toExecution);
|
|
200
|
+
return {
|
|
201
|
+
items: page,
|
|
202
|
+
...(rows.length > size && page.length > 0 ? { nextCursor: page[page.length - 1].id } : {}),
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
//# sourceMappingURL=flows.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ApprovalGrantStore, InteractionStore } from "../../persistence/index.js";
|
|
2
|
+
import type { SqlExecutor } from "./sql.js";
|
|
3
|
+
export declare const createPostgresInteractionStore: (sql: SqlExecutor) => InteractionStore;
|
|
4
|
+
export declare const createPostgresApprovalGrantStore: (sql: SqlExecutor) => ApprovalGrantStore;
|
|
5
|
+
//# sourceMappingURL=hitl.d.ts.map
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL `InteractionStore` and `ApprovalGrantStore` (#99) — durable questions, approvals and
|
|
3
|
+
* standing grants.
|
|
4
|
+
*
|
|
5
|
+
* Approvals gate every external write, so the two failure modes are asymmetric but both bad: a lost
|
|
6
|
+
* approval blocks the work forever, and a *replayed* one authorises the same side effect twice — a
|
|
7
|
+
* duplicate publish, a duplicate charge. The second is why `idempotency_key` carries a unique index
|
|
8
|
+
* and why resolution is a compare-and-set rather than a read-then-write.
|
|
9
|
+
*
|
|
10
|
+
* **Resolution is guarded in the statement.** `UPDATE … WHERE answered_at IS NULL RETURNING *`: zero
|
|
11
|
+
* rows means someone already resolved it, so the store reads back to distinguish "already resolved"
|
|
12
|
+
* from "no such interaction". A read-then-write would let two concurrent answers both report
|
|
13
|
+
* `alreadyResolved: false`, and the harness is explicit about what that costs — the continuation gets
|
|
14
|
+
* queued twice.
|
|
15
|
+
*/
|
|
16
|
+
import { AgentPlatformError } from "../../core/errors.js";
|
|
17
|
+
const notFound = (id) => new AgentPlatformError({ code: "not_found", message: `Interaction ${id} not found`, retryable: false });
|
|
18
|
+
const iso = (v) => (v instanceof Date ? v.toISOString() : new Date(v).toISOString());
|
|
19
|
+
const isoOrUndefined = (v) => (v === null ? undefined : iso(v));
|
|
20
|
+
/** jsonb arrives parsed from PGlite and as text from some drivers; normalise both. */
|
|
21
|
+
const json = (value) => (typeof value === "string" ? JSON.parse(value) : value);
|
|
22
|
+
const toQuestion = (r) => ({
|
|
23
|
+
id: r.id,
|
|
24
|
+
tenantId: r.tenant_id,
|
|
25
|
+
runId: r.run_id,
|
|
26
|
+
questions: json(r.questions),
|
|
27
|
+
createdAt: iso(r.created_at),
|
|
28
|
+
// Spread-in rather than assigned: the type marks both optional, and writing `answeredAt: undefined`
|
|
29
|
+
// would put the key in the object with an undefined value, which `toEqual` treats differently.
|
|
30
|
+
...(r.answered_at === null ? {} : { answeredAt: iso(r.answered_at) }),
|
|
31
|
+
...(r.answers === null || r.answers === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { answers: json(r.answers) }),
|
|
34
|
+
});
|
|
35
|
+
const QUESTION_COLUMNS = `tenant_id, id, run_id, questions, created_at, answered_at, answers`;
|
|
36
|
+
const toApproval = (r) => ({
|
|
37
|
+
id: r.id,
|
|
38
|
+
tenantId: r.tenant_id,
|
|
39
|
+
runId: r.run_id,
|
|
40
|
+
toolName: r.tool_name,
|
|
41
|
+
normalizedInput: json(r.normalized_input),
|
|
42
|
+
riskCategory: r.risk_category,
|
|
43
|
+
summary: r.summary,
|
|
44
|
+
...(r.estimated_cost_minor_units === null
|
|
45
|
+
? {}
|
|
46
|
+
: { estimatedCostMinorUnits: r.estimated_cost_minor_units }),
|
|
47
|
+
expiresAt: iso(r.expires_at),
|
|
48
|
+
idempotencyKey: r.idempotency_key,
|
|
49
|
+
...(r.decided_at === null ? {} : { decidedAt: iso(r.decided_at) }),
|
|
50
|
+
...(r.decision === null ? {} : { decision: r.decision }),
|
|
51
|
+
...(r.consumed_at === null ? {} : { consumedAt: iso(r.consumed_at) }),
|
|
52
|
+
});
|
|
53
|
+
const APPROVAL_COLUMNS = `tenant_id, id, run_id, tool_name, normalized_input, risk_category, summary,
|
|
54
|
+
estimated_cost_minor_units, expires_at, idempotency_key, decided_at, decision, consumed_at`;
|
|
55
|
+
export const createPostgresInteractionStore = (sql) => {
|
|
56
|
+
const readQuestion = async (tenantId, id) => {
|
|
57
|
+
const rows = await sql.query(`SELECT ${QUESTION_COLUMNS} FROM interaction_questions WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
|
|
58
|
+
const row = rows[0];
|
|
59
|
+
return row ? toQuestion(row) : null;
|
|
60
|
+
};
|
|
61
|
+
const readApproval = async (tenantId, id) => {
|
|
62
|
+
const rows = await sql.query(`SELECT ${APPROVAL_COLUMNS} FROM interaction_approvals WHERE tenant_id = $1 AND id = $2`, [tenantId, id]);
|
|
63
|
+
const row = rows[0];
|
|
64
|
+
return row ? toApproval(row) : null;
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
async createQuestion({ tenantId, question }) {
|
|
68
|
+
await sql.query(`INSERT INTO interaction_questions (tenant_id, id, run_id, questions, created_at, answered_at, answers)
|
|
69
|
+
VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7::jsonb)
|
|
70
|
+
ON CONFLICT (tenant_id, id) DO NOTHING`, [
|
|
71
|
+
tenantId,
|
|
72
|
+
question.id,
|
|
73
|
+
question.runId,
|
|
74
|
+
JSON.stringify(question.questions),
|
|
75
|
+
question.createdAt,
|
|
76
|
+
question.answeredAt ?? null,
|
|
77
|
+
question.answers === undefined ? null : JSON.stringify(question.answers),
|
|
78
|
+
]);
|
|
79
|
+
},
|
|
80
|
+
async findPendingQuestion({ tenantId, runId }) {
|
|
81
|
+
const rows = await sql.query(`SELECT ${QUESTION_COLUMNS} FROM interaction_questions
|
|
82
|
+
WHERE tenant_id = $1 AND run_id = $2 AND answered_at IS NULL
|
|
83
|
+
ORDER BY created_at, id
|
|
84
|
+
LIMIT 1`, [tenantId, runId]);
|
|
85
|
+
const row = rows[0];
|
|
86
|
+
return row ? toQuestion(row) : null;
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* The newest answered question for the run — #163. Ordered by `answered_at` rather than `created_at`: a run
|
|
90
|
+
* that asked twice has both answered, and the one to tell the model about is the one just answered.
|
|
91
|
+
*/
|
|
92
|
+
async findAnsweredQuestion({ tenantId, runId }) {
|
|
93
|
+
const rows = await sql.query(`SELECT ${QUESTION_COLUMNS} FROM interaction_questions
|
|
94
|
+
WHERE tenant_id = $1 AND run_id = $2 AND answered_at IS NOT NULL
|
|
95
|
+
ORDER BY answered_at DESC, id DESC
|
|
96
|
+
LIMIT 1`, [tenantId, runId]);
|
|
97
|
+
const row = rows[0];
|
|
98
|
+
return row ? toQuestion(row) : null;
|
|
99
|
+
},
|
|
100
|
+
async answerQuestion({ tenantId, interactionId, answers, at }) {
|
|
101
|
+
// The guard is `answered_at IS NULL`, so exactly one of two concurrent answers can win. The
|
|
102
|
+
// loser gets zero rows and reports alreadyResolved, which is what keeps the continuation from
|
|
103
|
+
// being queued twice.
|
|
104
|
+
const rows = await sql.query(`UPDATE interaction_questions
|
|
105
|
+
SET answered_at = $3::timestamptz, answers = $4::jsonb
|
|
106
|
+
WHERE tenant_id = $1 AND id = $2 AND answered_at IS NULL
|
|
107
|
+
RETURNING ${QUESTION_COLUMNS}`, [tenantId, interactionId, at, JSON.stringify(answers)]);
|
|
108
|
+
const row = rows[0];
|
|
109
|
+
if (row)
|
|
110
|
+
return { question: toQuestion(row), alreadyResolved: false };
|
|
111
|
+
const existing = await readQuestion(tenantId, interactionId);
|
|
112
|
+
if (!existing)
|
|
113
|
+
throw notFound(interactionId);
|
|
114
|
+
return { question: existing, alreadyResolved: true };
|
|
115
|
+
},
|
|
116
|
+
async createApproval({ tenantId, approval }) {
|
|
117
|
+
await sql.query(`INSERT INTO interaction_approvals
|
|
118
|
+
(tenant_id, id, run_id, tool_name, normalized_input, risk_category, summary,
|
|
119
|
+
estimated_cost_minor_units, expires_at, idempotency_key, decided_at, decision, consumed_at)
|
|
120
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9::timestamptz, $10, $11::timestamptz, $12,
|
|
121
|
+
$13::timestamptz)
|
|
122
|
+
ON CONFLICT (tenant_id, id) DO NOTHING`, [
|
|
123
|
+
tenantId,
|
|
124
|
+
approval.id,
|
|
125
|
+
approval.runId,
|
|
126
|
+
approval.toolName,
|
|
127
|
+
JSON.stringify(approval.normalizedInput ?? null),
|
|
128
|
+
approval.riskCategory,
|
|
129
|
+
approval.summary,
|
|
130
|
+
approval.estimatedCostMinorUnits ?? null,
|
|
131
|
+
approval.expiresAt,
|
|
132
|
+
approval.idempotencyKey,
|
|
133
|
+
approval.decidedAt ?? null,
|
|
134
|
+
approval.decision ?? null,
|
|
135
|
+
approval.consumedAt ?? null,
|
|
136
|
+
]);
|
|
137
|
+
},
|
|
138
|
+
async findPendingApproval({ tenantId, runId }) {
|
|
139
|
+
const rows = await sql.query(`SELECT ${APPROVAL_COLUMNS} FROM interaction_approvals
|
|
140
|
+
WHERE tenant_id = $1 AND run_id = $2 AND decided_at IS NULL
|
|
141
|
+
ORDER BY expires_at, id
|
|
142
|
+
LIMIT 1`, [tenantId, runId]);
|
|
143
|
+
const row = rows[0];
|
|
144
|
+
return row ? toApproval(row) : null;
|
|
145
|
+
},
|
|
146
|
+
async decideApproval({ tenantId, interactionId, decision, at }) {
|
|
147
|
+
// AC-5 lives here: a denied approval can never become granted, because the second decision
|
|
148
|
+
// matches no row and the store returns the stored one unchanged.
|
|
149
|
+
const rows = await sql.query(`UPDATE interaction_approvals
|
|
150
|
+
SET decided_at = $3::timestamptz, decision = $4
|
|
151
|
+
WHERE tenant_id = $1 AND id = $2 AND decided_at IS NULL
|
|
152
|
+
RETURNING ${APPROVAL_COLUMNS}`, [tenantId, interactionId, at, decision]);
|
|
153
|
+
const row = rows[0];
|
|
154
|
+
if (row)
|
|
155
|
+
return { approval: toApproval(row), alreadyResolved: false };
|
|
156
|
+
const existing = await readApproval(tenantId, interactionId);
|
|
157
|
+
if (!existing)
|
|
158
|
+
throw notFound(interactionId);
|
|
159
|
+
return { approval: existing, alreadyResolved: true };
|
|
160
|
+
},
|
|
161
|
+
async findApproval({ tenantId, interactionId }) {
|
|
162
|
+
return readApproval(tenantId, interactionId);
|
|
163
|
+
},
|
|
164
|
+
async findDecidedApproval({ tenantId, runId }) {
|
|
165
|
+
const rows = await sql.query(`SELECT ${APPROVAL_COLUMNS} FROM interaction_approvals
|
|
166
|
+
WHERE tenant_id = $1 AND run_id = $2 AND decided_at IS NOT NULL AND consumed_at IS NULL
|
|
167
|
+
ORDER BY decided_at, id
|
|
168
|
+
LIMIT 1`, [tenantId, runId]);
|
|
169
|
+
const row = rows[0];
|
|
170
|
+
return row ? toApproval(row) : null;
|
|
171
|
+
},
|
|
172
|
+
async claimApproval({ tenantId, interactionId, at }) {
|
|
173
|
+
// The whole of `allow-once` is this WHERE clause. `consumed_at IS NULL` is what makes the claim
|
|
174
|
+
// exclusive under concurrency, and `decided_at IS NOT NULL` is what stops an undecided
|
|
175
|
+
// interaction from being claimable at all — an unwired dependency must never become permission.
|
|
176
|
+
const rows = await sql.query(`UPDATE interaction_approvals
|
|
177
|
+
SET consumed_at = $3::timestamptz
|
|
178
|
+
WHERE tenant_id = $1 AND id = $2 AND decided_at IS NOT NULL AND consumed_at IS NULL
|
|
179
|
+
RETURNING ${APPROVAL_COLUMNS}`, [tenantId, interactionId, at]);
|
|
180
|
+
const row = rows[0];
|
|
181
|
+
if (row)
|
|
182
|
+
return { approval: toApproval(row), claimed: true };
|
|
183
|
+
const existing = await readApproval(tenantId, interactionId);
|
|
184
|
+
if (!existing)
|
|
185
|
+
throw notFound(interactionId);
|
|
186
|
+
return { approval: existing, claimed: false };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
const toGrant = (r) => ({
|
|
191
|
+
id: r.id,
|
|
192
|
+
tenantId: r.tenant_id,
|
|
193
|
+
scope: r.scope,
|
|
194
|
+
toolNameOrCategory: r.tool_name_or_category,
|
|
195
|
+
...(r.conversation_id === null ? {} : { conversationId: r.conversation_id }),
|
|
196
|
+
grantedAt: iso(r.granted_at),
|
|
197
|
+
...(isoOrUndefined(r.expires_at) === undefined ? {} : { expiresAt: iso(r.expires_at) }),
|
|
198
|
+
...(isoOrUndefined(r.revoked_at) === undefined ? {} : { revokedAt: iso(r.revoked_at) }),
|
|
199
|
+
});
|
|
200
|
+
const GRANT_COLUMNS = `tenant_id, id, scope, tool_name_or_category, conversation_id,
|
|
201
|
+
granted_at, expires_at, revoked_at`;
|
|
202
|
+
export const createPostgresApprovalGrantStore = (sql) => ({
|
|
203
|
+
async grant({ tenantId, grant }) {
|
|
204
|
+
await sql.query(`INSERT INTO approval_grants
|
|
205
|
+
(tenant_id, id, scope, tool_name_or_category, conversation_id, granted_at, expires_at, revoked_at)
|
|
206
|
+
VALUES ($1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz, $8::timestamptz)
|
|
207
|
+
ON CONFLICT (tenant_id, id) DO UPDATE
|
|
208
|
+
SET scope = excluded.scope,
|
|
209
|
+
tool_name_or_category = excluded.tool_name_or_category,
|
|
210
|
+
conversation_id = excluded.conversation_id,
|
|
211
|
+
granted_at = excluded.granted_at,
|
|
212
|
+
expires_at = excluded.expires_at,
|
|
213
|
+
revoked_at = excluded.revoked_at`, [
|
|
214
|
+
tenantId,
|
|
215
|
+
grant.id,
|
|
216
|
+
grant.scope,
|
|
217
|
+
grant.toolNameOrCategory,
|
|
218
|
+
grant.conversationId ?? null,
|
|
219
|
+
grant.grantedAt,
|
|
220
|
+
grant.expiresAt ?? null,
|
|
221
|
+
grant.revokedAt ?? null,
|
|
222
|
+
]);
|
|
223
|
+
},
|
|
224
|
+
async findActive({ tenantId, toolNameOrCategory, now, conversationId }) {
|
|
225
|
+
// The scope predicate is the safety-critical part. A conversation-scoped grant must match only
|
|
226
|
+
// its own conversation and must not match at all when no conversation is supplied — otherwise a
|
|
227
|
+
// one-conversation approval silently becomes a standing one. Expressed as SQL rather than
|
|
228
|
+
// filtered afterwards so the index can serve it and no code path can skip it.
|
|
229
|
+
const rows = await sql.query(`SELECT ${GRANT_COLUMNS} FROM approval_grants
|
|
230
|
+
WHERE tenant_id = $1
|
|
231
|
+
AND tool_name_or_category = $2
|
|
232
|
+
AND revoked_at IS NULL
|
|
233
|
+
AND (expires_at IS NULL OR expires_at > $3::timestamptz)
|
|
234
|
+
AND (scope <> 'conversation' OR ($4::text IS NOT NULL AND conversation_id = $4::text))
|
|
235
|
+
ORDER BY granted_at DESC, id
|
|
236
|
+
LIMIT 1`, [tenantId, toolNameOrCategory, now, conversationId ?? null]);
|
|
237
|
+
const row = rows[0];
|
|
238
|
+
return row ? toGrant(row) : null;
|
|
239
|
+
},
|
|
240
|
+
async revoke({ tenantId, grantId, at }) {
|
|
241
|
+
// Silent on a missing grant, matching the reference adapter: revoking something that is not there
|
|
242
|
+
// has already achieved its purpose, and throwing would make idempotent revocation impossible.
|
|
243
|
+
await sql.query(`UPDATE approval_grants SET revoked_at = $3::timestamptz
|
|
244
|
+
WHERE tenant_id = $1 AND id = $2 AND revoked_at IS NULL`, [tenantId, grantId, at]);
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
//# sourceMappingURL=hitl.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL adapters — the production reference storage. Implements the storage ports over a
|
|
3
|
+
* `SqlExecutor`; verified by the shared conformance harness.
|
|
4
|
+
*/
|
|
5
|
+
import type { AdapterCapability } from "../../persistence/index.js";
|
|
6
|
+
export * from "./sql.js";
|
|
7
|
+
export * from "./migrations.js";
|
|
8
|
+
export * from "./schema.js";
|
|
9
|
+
export * from "./conversation-store.js";
|
|
10
|
+
export * from "./run-store.js";
|
|
11
|
+
export * from "./run-event-log.js";
|
|
12
|
+
export * from "./checkpoint-store.js";
|
|
13
|
+
export * from "./message-store.js";
|
|
14
|
+
export * from "./session-state.js";
|
|
15
|
+
export * from "./transaction.js";
|
|
16
|
+
export * from "./run-coordinator.js";
|
|
17
|
+
export * from "./unit-of-work.js";
|
|
18
|
+
export * from "./hitl.js";
|
|
19
|
+
export * from "./usage.js";
|
|
20
|
+
export * from "./usage-limits.js";
|
|
21
|
+
export * from "./config.js";
|
|
22
|
+
export * from "./memory.js";
|
|
23
|
+
export * from "./pg-executor.js";
|
|
24
|
+
/** Capabilities a PostgreSQL deployment can advertise (docs/02 capability declarations). */
|
|
25
|
+
export declare const POSTGRES_CAPABILITIES: readonly AdapterCapability[];
|
|
26
|
+
export * from "./files.js";
|
|
27
|
+
export * from "./artifacts.js";
|
|
28
|
+
export * from "./artifact-exports.js";
|
|
29
|
+
export * from "./knowledge.js";
|
|
30
|
+
export * from "./rollups.js";
|
|
31
|
+
export * from "./evaluation.js";
|
|
32
|
+
export * from "./retention.js";
|
|
33
|
+
export * from "./file-content.js";
|
|
34
|
+
export * from "./flows.js";
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export * from "./sql.js";
|
|
2
|
+
export * from "./migrations.js";
|
|
3
|
+
export * from "./schema.js";
|
|
4
|
+
export * from "./conversation-store.js";
|
|
5
|
+
export * from "./run-store.js";
|
|
6
|
+
export * from "./run-event-log.js";
|
|
7
|
+
export * from "./checkpoint-store.js";
|
|
8
|
+
export * from "./message-store.js";
|
|
9
|
+
export * from "./session-state.js";
|
|
10
|
+
export * from "./transaction.js";
|
|
11
|
+
export * from "./run-coordinator.js";
|
|
12
|
+
export * from "./unit-of-work.js";
|
|
13
|
+
export * from "./hitl.js";
|
|
14
|
+
export * from "./usage.js";
|
|
15
|
+
export * from "./usage-limits.js";
|
|
16
|
+
export * from "./config.js";
|
|
17
|
+
export * from "./memory.js";
|
|
18
|
+
export * from "./pg-executor.js";
|
|
19
|
+
/** Capabilities a PostgreSQL deployment can advertise (docs/02 capability declarations). */
|
|
20
|
+
export const POSTGRES_CAPABILITIES = [
|
|
21
|
+
"transactions",
|
|
22
|
+
"row-level-security",
|
|
23
|
+
"full-text-search",
|
|
24
|
+
"distributed-locking",
|
|
25
|
+
];
|
|
26
|
+
export * from "./files.js";
|
|
27
|
+
export * from "./artifacts.js";
|
|
28
|
+
export * from "./artifact-exports.js";
|
|
29
|
+
export * from "./knowledge.js";
|
|
30
|
+
export * from "./rollups.js";
|
|
31
|
+
export * from "./evaluation.js";
|
|
32
|
+
export * from "./retention.js";
|
|
33
|
+
export * from "./file-content.js";
|
|
34
|
+
export * from "./flows.js";
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pgvector `KnowledgeStore` and `VectorIndex` (#135).
|
|
3
|
+
*
|
|
4
|
+
* One table backs both ports, which is the point of splitting them: a deployment on pgvector answers both from
|
|
5
|
+
* `knowledge_chunks`, and a deployment on a dedicated vector database answers them from two systems, with
|
|
6
|
+
* nothing above changing.
|
|
7
|
+
*
|
|
8
|
+
* **AC-3 is a `WHERE` clause, and it is the reason this adapter exists rather than a filter above it.** The
|
|
9
|
+
* permission predicate is in the same statement as the nearest-neighbour scan, so an unauthorised chunk is
|
|
10
|
+
* never a candidate. Filtering afterwards leaks through result counts — ask for ten, get three, and you have
|
|
11
|
+
* learned that seven exist you may not see. `EXPLAIN` shows the predicate inside the scan, which is what the
|
|
12
|
+
* test asserts.
|
|
13
|
+
*
|
|
14
|
+
* **`replaceSource` is one transaction-shaped pair of statements.** Delete-then-insert, in that order, so a
|
|
15
|
+
* changed document's old chunks are never searchable alongside its new ones. Appending would leave a stale
|
|
16
|
+
* chunk that cites text no longer in the document.
|
|
17
|
+
*/
|
|
18
|
+
import type { EmbeddingModelRef, KeywordIndex, KnowledgeStore, VectorIndex } from "../../persistence/index.js";
|
|
19
|
+
import type { SqlExecutor } from "./sql.js";
|
|
20
|
+
/**
|
|
21
|
+
* pgvector's literal form: `[0.1,0.2,...]`.
|
|
22
|
+
*
|
|
23
|
+
* Built as a string rather than passed as an array, because node-postgres has no vector type and would send an
|
|
24
|
+
* array as `{...}` — which pgvector rejects. `Number.isFinite` is checked because a `NaN` in a vector makes
|
|
25
|
+
* every distance `NaN`, and the symptom is a search that returns nothing rather than an error.
|
|
26
|
+
*/
|
|
27
|
+
export declare const toVectorLiteral: (embedding: readonly number[]) => string;
|
|
28
|
+
export declare const createPostgresKnowledgeStore: (sql: SqlExecutor) => KnowledgeStore;
|
|
29
|
+
export declare const createPostgresVectorIndex: (sql: SqlExecutor) => VectorIndex;
|
|
30
|
+
/** Re-exported so a caller comparing a stored chunk's model to the current one does it the same way. */
|
|
31
|
+
export declare const sameEmbeddingModel: (a: EmbeddingModelRef, b: EmbeddingModelRef) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Postgres full-text `KeywordIndex` (#136).
|
|
34
|
+
*
|
|
35
|
+
* Over `knowledge_chunks.content_tsv`, the same rows the vector index searches, so a chunk cannot be visible to
|
|
36
|
+
* one signal and invisible to the other.
|
|
37
|
+
*
|
|
38
|
+
* **`websearch_to_tsquery`, not `to_tsquery`.** The latter throws on ordinary user input — an unbalanced quote,
|
|
39
|
+
* a bare `&`, a trailing operator — and a search box that errors on a stray character is a search box that
|
|
40
|
+
* errors. `websearch_to_tsquery` accepts anything and interprets quotes and `or` the way a person expects.
|
|
41
|
+
*
|
|
42
|
+
* **The score is normalised against the best hit in the result set**, not against an absolute. `ts_rank_cd`
|
|
43
|
+
* has no upper bound and its scale depends on the corpus, so an absolute threshold would mean different things
|
|
44
|
+
* in different tenants — and fusing an unbounded score with a bounded cosine similarity would let the
|
|
45
|
+
* unbounded one dominate whenever it happened to be larger.
|
|
46
|
+
*/
|
|
47
|
+
export declare const createPostgresKeywordIndex: (sql: SqlExecutor) => KeywordIndex;
|
|
48
|
+
//# sourceMappingURL=knowledge.d.ts.map
|