@happyvertical/smrt-agents 0.49.2 → 0.49.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/dist/index.js +1 -1
- package/dist/manifest.json +631 -16
- package/dist/server/data-surface-actions.d.ts +36 -13
- package/dist/server/data-surface-actions.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/jobs-data-surface-action-queue.d.ts +33 -0
- package/dist/server/jobs-data-surface-action-queue.d.ts.map +1 -0
- package/dist/server/sql-data-surface-action-state.d.ts +60 -0
- package/dist/server/sql-data-surface-action-state.d.ts.map +1 -0
- package/dist/server.js +468 -20
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +165 -5
- package/package.json +14 -12
package/dist/server.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { n as executeAsPrincipal, r as AgentConfig } from "./chunks/execute-as-principal-DIyBp1oE.js";
|
|
2
2
|
import { i as loadManifestsFromPackages, n as extractAgentPackagesFromConfig, r as loadManifestsFromConfig, t as extractAgentManifest } from "./chunks/manifest-utils-CtMyFQDx.js";
|
|
3
|
+
import { ObjectRegistry, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
3
4
|
import { sanitizeConfig } from "@happyvertical/smrt-config";
|
|
4
|
-
import {
|
|
5
|
+
import { TenantScoped, getTenantId, tenantId } from "@happyvertical/smrt-tenancy";
|
|
6
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { SmrtJobCollection, backgroundEligible, createHmacDurableJobPayloadSigner, getActiveJobExecutionContext, isRunnerExecutionContext } from "@happyvertical/smrt-jobs";
|
|
5
8
|
//#region src/server/api-routes.ts
|
|
6
9
|
function buildRouteMap(manifests) {
|
|
7
10
|
const routes = /* @__PURE__ */ new Map();
|
|
@@ -92,6 +95,16 @@ var InMemoryDataSurfaceActionStateStore = class {
|
|
|
92
95
|
record.consumedBy = idempotencyKey;
|
|
93
96
|
return true;
|
|
94
97
|
}
|
|
98
|
+
consumeTokenAndReserveIdempotency(token, idempotencyKey, scope, reservation) {
|
|
99
|
+
const tokenRecord = this.tokens.get(token);
|
|
100
|
+
if (!tokenRecord || tokenRecord.consumedBy && tokenRecord.consumedBy !== idempotencyKey) return;
|
|
101
|
+
if (!this.idempotency.get(scope)) this.idempotency.set(scope, {
|
|
102
|
+
status: "reserved",
|
|
103
|
+
...reservation
|
|
104
|
+
});
|
|
105
|
+
tokenRecord.consumedBy = idempotencyKey;
|
|
106
|
+
return this.idempotency.get(scope);
|
|
107
|
+
}
|
|
95
108
|
getIdempotency(key) {
|
|
96
109
|
return this.idempotency.get(key);
|
|
97
110
|
}
|
|
@@ -169,6 +182,22 @@ function stable(value) {
|
|
|
169
182
|
function fingerprint(value) {
|
|
170
183
|
return createHash("sha256").update(stable(value)).digest("hex");
|
|
171
184
|
}
|
|
185
|
+
function envelopeBinding(envelope, key) {
|
|
186
|
+
return createHmacDurableJobPayloadSigner({
|
|
187
|
+
keyId: "data-surface-envelope-v1",
|
|
188
|
+
key
|
|
189
|
+
}).sign(envelope);
|
|
190
|
+
}
|
|
191
|
+
function validSigningKey(key) {
|
|
192
|
+
return key !== void 0 && (typeof key === "string" ? Buffer.byteLength(key) : key.byteLength) >= 32;
|
|
193
|
+
}
|
|
194
|
+
function bindingMatches(envelope, key) {
|
|
195
|
+
const { binding, ...unsigned } = envelope;
|
|
196
|
+
return createHmacDurableJobPayloadSigner({
|
|
197
|
+
keyId: "data-surface-envelope-v1",
|
|
198
|
+
key
|
|
199
|
+
}).verify(unsigned, binding);
|
|
200
|
+
}
|
|
172
201
|
function identityKey(identity) {
|
|
173
202
|
return stable(canonicalIdentity(identity));
|
|
174
203
|
}
|
|
@@ -417,7 +446,7 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
417
446
|
}
|
|
418
447
|
return result(request, true, void 0, outcomesDetails(outcomes));
|
|
419
448
|
}
|
|
420
|
-
async function executeBackgroundOnce(request,
|
|
449
|
+
async function executeBackgroundOnce(request, token, reference) {
|
|
421
450
|
const ownerToken = randomBytes(16).toString("base64url");
|
|
422
451
|
const executionFingerprint = fingerprint({
|
|
423
452
|
kind: "background-execution",
|
|
@@ -445,19 +474,32 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
445
474
|
if (winner.requestFingerprint !== executionFingerprint) return result(request, false, "idempotency_conflict");
|
|
446
475
|
if (winner.status === "completed") return replayResult(request, winner.result);
|
|
447
476
|
if (winner.ownerToken === ownerToken) {
|
|
477
|
+
let refreshed;
|
|
478
|
+
try {
|
|
479
|
+
const resolved = await options.resolveDeferredPrincipal?.(reference);
|
|
480
|
+
if (!resolved || resolved.principal.runAsUserId !== reference.runAsUserId || resolved.principal.tenantId !== reference.tenantId || (resolved.principal.actsAsProfileId ?? null) !== reference.actsAsProfileId || (resolved.onBehalfOfUserId ?? null) !== reference.onBehalfOfUserId || (resolved.agentClass ?? null) !== (reference.agentClass ?? null) || !Array.isArray(resolved.principal.allowedTools)) throw new Error("Deferred data-surface action principal binding could not be resolved safely");
|
|
481
|
+
refreshed = resolved;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
const reason = options.mapError?.(error, request);
|
|
484
|
+
if (!reason) {
|
|
485
|
+
await state.releaseIdempotency(executionScope, ownerToken);
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
const denied = result(request, false, reason);
|
|
489
|
+
if (!await state.completeIdempotency(executionScope, ownerToken, denied)) throw new Error("Lost background action idempotency reservation");
|
|
490
|
+
return denied;
|
|
491
|
+
}
|
|
448
492
|
let executed;
|
|
493
|
+
let mutationStarted = false;
|
|
449
494
|
try {
|
|
450
|
-
const refreshed = await options.resolveDeferredPrincipal?.(reference);
|
|
451
|
-
if (!refreshed || refreshed.principal.runAsUserId !== reference.runAsUserId || refreshed.principal.tenantId !== reference.tenantId || (refreshed.principal.actsAsProfileId ?? null) !== reference.actsAsProfileId || (refreshed.onBehalfOfUserId ?? null) !== reference.onBehalfOfUserId || (refreshed.agentClass ?? null) !== (reference.agentClass ?? null) || !Array.isArray(refreshed.principal.allowedTools)) throw new Error("Deferred data-surface action principal binding could not be resolved safely");
|
|
452
495
|
const { permissions: _permissions, ...livePrincipal } = refreshed;
|
|
453
|
-
executed = await authorizedApply(request, {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}, token, false);
|
|
496
|
+
executed = await authorizedApply(request, { principal: livePrincipal }, token, false, () => {
|
|
497
|
+
mutationStarted = true;
|
|
498
|
+
});
|
|
457
499
|
} catch (error) {
|
|
458
500
|
const reason = options.mapError?.(error, request);
|
|
459
501
|
if (!reason) {
|
|
460
|
-
await state.releaseIdempotency(executionScope, ownerToken);
|
|
502
|
+
if (!mutationStarted) await state.releaseIdempotency(executionScope, ownerToken);
|
|
461
503
|
throw error;
|
|
462
504
|
}
|
|
463
505
|
executed = result(request, false, reason);
|
|
@@ -473,7 +515,7 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
473
515
|
}
|
|
474
516
|
return result(request, false, "idempotency_in_progress");
|
|
475
517
|
}
|
|
476
|
-
async function authorizedApply(request, context, token, allowBackground) {
|
|
518
|
+
async function authorizedApply(request, context, token, allowBackground, beforeMutation) {
|
|
477
519
|
const idempotencyKey = request.idempotencyKey;
|
|
478
520
|
if (!idempotencyKey) return result(request, false, "invalid_request");
|
|
479
521
|
const deferredPrincipalReference = Object.freeze({
|
|
@@ -501,13 +543,25 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
501
543
|
} else if (invocation.action.confirmation === "required") return result(request, false, "confirmation_required");
|
|
502
544
|
else if (invocation.selection.revision !== request.expectedRevision) return result(request, false, "stale_revision");
|
|
503
545
|
if (invocation.action.execution === "background" && allowBackground) {
|
|
504
|
-
if (!options.backgroundQueue || !options.resolveDeferredPrincipal) return result(request, false, "background_unavailable");
|
|
546
|
+
if (!options.backgroundQueue || !validIdentifier(options.backgroundHandlerId) || !options.resolveDeferredPrincipal || !validSigningKey(options.deferredEnvelopeSigningKey)) return result(request, false, "background_unavailable");
|
|
547
|
+
const { confirmationToken: _confirmationToken, ...deferredRequest } = request;
|
|
548
|
+
const unsignedEnvelope = {
|
|
549
|
+
version: 1,
|
|
550
|
+
handlerId: options.backgroundHandlerId ?? "",
|
|
551
|
+
request: deferredRequest,
|
|
552
|
+
principal: deferredPrincipalReference,
|
|
553
|
+
...token ? { previewToken: token } : {}
|
|
554
|
+
};
|
|
505
555
|
const queued = await options.backgroundQueue.enqueue({
|
|
506
556
|
idempotencyKey,
|
|
507
557
|
identity: request.identity,
|
|
508
558
|
actionId: request.actionId,
|
|
509
559
|
rowIds: invocation.selection.rowIds,
|
|
510
|
-
|
|
560
|
+
envelope: {
|
|
561
|
+
...unsignedEnvelope,
|
|
562
|
+
binding: envelopeBinding(unsignedEnvelope, options.deferredEnvelopeSigningKey)
|
|
563
|
+
},
|
|
564
|
+
run: () => executeBackgroundOnce(request, token, deferredPrincipalReference)
|
|
511
565
|
});
|
|
512
566
|
return result(request, true, void 0, {
|
|
513
567
|
accepted: invocation.selection.rowIds.length,
|
|
@@ -519,6 +573,7 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
519
573
|
jobRequestId: request.requestId
|
|
520
574
|
});
|
|
521
575
|
}
|
|
576
|
+
beforeMutation?.();
|
|
522
577
|
return executeForeground(request, invocation);
|
|
523
578
|
});
|
|
524
579
|
}
|
|
@@ -554,16 +609,34 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
554
609
|
token = await state.getToken(confirmationToken);
|
|
555
610
|
if (!token || token.expiresAt <= now()) return result(request, false, "invalid_or_expired_confirmation");
|
|
556
611
|
if (token.actorUserId !== actorUserId || token.tenantId !== tenantId || token.onBehalfOfUserId !== onBehalfOfUserId || token.actsAsProfileId !== actsAsProfileId || token.agentClass !== agentClass || token.identityKey !== identityKey(request.identity) || token.actionId !== request.actionId || token.requestFingerprint !== requestFingerprintValue) return result(request, false, "confirmation_mismatch");
|
|
557
|
-
if (!await state.markTokenConsumed(confirmationToken, idempotencyKey)) return result(request, false, "confirmation_replayed");
|
|
558
612
|
}
|
|
613
|
+
const backgroundPreflight = await runAsPrincipal({
|
|
614
|
+
...boundContext.principal,
|
|
615
|
+
action: "data_surface.action.apply",
|
|
616
|
+
auditMetadata: boundContext.principal.auditMetadata
|
|
617
|
+
}, async (run) => {
|
|
618
|
+
const surface = await options.resolveSurface(run, request.identity);
|
|
619
|
+
if (identityKey(surface.descriptor.identity) !== identityKey(request.identity)) return void 0;
|
|
620
|
+
const action = surface.actions[request.actionId];
|
|
621
|
+
const declared = surface.descriptor.actions.find(({ id }) => id === request.actionId);
|
|
622
|
+
if (!action || !declared || action.descriptor.id !== declared.id) return void 0;
|
|
623
|
+
if (action.execution === "background" && (!options.backgroundQueue || !validIdentifier(options.backgroundHandlerId) || !options.resolveDeferredPrincipal || !validSigningKey(options.deferredEnvelopeSigningKey))) return result(request, false, "background_unavailable");
|
|
624
|
+
});
|
|
625
|
+
if (backgroundPreflight) return backgroundPreflight;
|
|
559
626
|
const ownerToken = randomBytes(16).toString("base64url");
|
|
627
|
+
const reservation = {
|
|
628
|
+
requestFingerprint: requestFingerprintValue,
|
|
629
|
+
ownerToken,
|
|
630
|
+
reservedAt: now()
|
|
631
|
+
};
|
|
632
|
+
let firstWinner;
|
|
633
|
+
if (confirmationToken) {
|
|
634
|
+
firstWinner = await state.consumeTokenAndReserveIdempotency(confirmationToken, idempotencyKey, idempotencyScope, reservation);
|
|
635
|
+
if (!firstWinner) return result(request, false, "confirmation_replayed");
|
|
636
|
+
}
|
|
560
637
|
const maxPolls = Math.max(1, Math.ceil(idempotencyWaitTimeoutMs / idempotencyPollIntervalMs));
|
|
561
638
|
for (let poll = 0; poll <= maxPolls; poll += 1) {
|
|
562
|
-
const winner = await state.reserveIdempotency(idempotencyScope,
|
|
563
|
-
requestFingerprint: requestFingerprintValue,
|
|
564
|
-
ownerToken,
|
|
565
|
-
reservedAt: now()
|
|
566
|
-
});
|
|
639
|
+
const winner = poll === 0 && firstWinner ? firstWinner : await state.reserveIdempotency(idempotencyScope, reservation);
|
|
567
640
|
if (winner.requestFingerprint !== requestFingerprintValue) return result(request, false, "idempotency_conflict");
|
|
568
641
|
if (winner.status === "completed") return replayResult(request, winner.result);
|
|
569
642
|
if (winner.ownerToken === ownerToken) {
|
|
@@ -589,12 +662,112 @@ function createDataSurfaceActionAdapter(options) {
|
|
|
589
662
|
}
|
|
590
663
|
return result(request, false, "idempotency_in_progress");
|
|
591
664
|
}
|
|
665
|
+
async function executeDeferred(envelope) {
|
|
666
|
+
if (!validSigningKey(options.deferredEnvelopeSigningKey) || !bindingMatches(envelope, options.deferredEnvelopeSigningKey)) throw new Error("Invalid durable data-surface action envelope binding");
|
|
667
|
+
const principal = envelope.principal;
|
|
668
|
+
if (envelope.version !== 1 || !options.backgroundHandlerId || envelope.handlerId !== options.backgroundHandlerId || !principal || !validIdentifier(principal.runAsUserId) || principal.tenantId !== null && !validIdentifier(principal.tenantId) || principal.actsAsProfileId !== null && !validIdentifier(principal.actsAsProfileId) || principal.onBehalfOfUserId !== null && !validIdentifier(principal.onBehalfOfUserId) || principal.agentClass !== void 0 && !validIdentifier(principal.agentClass)) return result(envelope.request, false, "invalid_request");
|
|
669
|
+
const invalid = validateRequest(envelope.request, "apply");
|
|
670
|
+
if (invalid) return result(envelope.request, false, invalid);
|
|
671
|
+
return executeBackgroundOnce(snapshotRequest(envelope.request), envelope.previewToken, Object.freeze({ ...envelope.principal }));
|
|
672
|
+
}
|
|
592
673
|
return {
|
|
593
674
|
preview,
|
|
594
|
-
apply
|
|
675
|
+
apply,
|
|
676
|
+
executeDeferred
|
|
595
677
|
};
|
|
596
678
|
}
|
|
597
679
|
//#endregion
|
|
680
|
+
//#region src/server/jobs-data-surface-action-queue.ts
|
|
681
|
+
var __defProp$1 = Object.defineProperty;
|
|
682
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
683
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
684
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
685
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
686
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
687
|
+
return result;
|
|
688
|
+
};
|
|
689
|
+
var handlers = /* @__PURE__ */ new Map();
|
|
690
|
+
var SmrtDataSurfaceActionTask = class extends SmrtObject {
|
|
691
|
+
tenantId = null;
|
|
692
|
+
args = {
|
|
693
|
+
version: 1,
|
|
694
|
+
envelope: {}
|
|
695
|
+
};
|
|
696
|
+
async run(args = this.args, context) {
|
|
697
|
+
const envelope = args?.envelope;
|
|
698
|
+
const executionContext = getActiveJobExecutionContext() ?? context;
|
|
699
|
+
if (executionContext && !isRunnerExecutionContext(executionContext)) throw new Error("Invalid durable data-surface action job context");
|
|
700
|
+
let jobTenantId;
|
|
701
|
+
if (executionContext) {
|
|
702
|
+
const runnerTenantId = executionContext.job.tenantId;
|
|
703
|
+
if (runnerTenantId === null) jobTenantId = null;
|
|
704
|
+
else if (typeof runnerTenantId === "string" && runnerTenantId.length > 0) jobTenantId = runnerTenantId;
|
|
705
|
+
else throw new Error("Invalid durable data-surface action job tenant");
|
|
706
|
+
} else jobTenantId = this.tenantId ?? getTenantId() ?? null;
|
|
707
|
+
if (args?.version !== 1 || envelope?.version !== 1 || typeof envelope.handlerId !== "string" || envelope.handlerId.length === 0 || envelope.principal?.tenantId !== jobTenantId) throw new Error("Invalid durable data-surface action envelope");
|
|
708
|
+
const handler = handlers.get(envelope.handlerId);
|
|
709
|
+
if (!handler) throw new Error(`No data-surface action handler registered for ${envelope.handlerId}`);
|
|
710
|
+
const result = await handler(envelope);
|
|
711
|
+
if (!result.ok && result.reason === "idempotency_in_progress") throw new Error("Data-surface action outcome requires reconciliation");
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
__decorateClass$1([tenantId({ nullable: true })], SmrtDataSurfaceActionTask.prototype, "tenantId", 2);
|
|
716
|
+
__decorateClass$1([field({
|
|
717
|
+
type: "json",
|
|
718
|
+
required: true
|
|
719
|
+
})], SmrtDataSurfaceActionTask.prototype, "args", 2);
|
|
720
|
+
__decorateClass$1([backgroundEligible()], SmrtDataSurfaceActionTask.prototype, "run", 1);
|
|
721
|
+
SmrtDataSurfaceActionTask = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
722
|
+
tableName: "_smrt_data_surface_action_tasks",
|
|
723
|
+
api: false,
|
|
724
|
+
cli: false,
|
|
725
|
+
mcp: false
|
|
726
|
+
})], SmrtDataSurfaceActionTask);
|
|
727
|
+
function registerDataSurfaceBackgroundActionHandler(handlerId, execute) {
|
|
728
|
+
if (!handlerId || handlerId.length > 256) throw new Error("Data-surface action handlerId must contain 1-256 characters");
|
|
729
|
+
const existing = handlers.get(handlerId);
|
|
730
|
+
if (existing && existing !== execute) throw new Error(`Data-surface action handler already registered: ${handlerId}`);
|
|
731
|
+
handlers.set(handlerId, execute);
|
|
732
|
+
return () => {
|
|
733
|
+
if (handlers.get(handlerId) === execute) handlers.delete(handlerId);
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function createJobsDataSurfaceBackgroundQueue(options) {
|
|
737
|
+
return {
|
|
738
|
+
unregister: registerDataSurfaceBackgroundActionHandler(options.handlerId, options.execute),
|
|
739
|
+
async enqueue(job) {
|
|
740
|
+
if (job.envelope.handlerId !== options.handlerId) throw new Error("Data-surface action envelope handler mismatch");
|
|
741
|
+
const persisted = await enqueueDataSurfaceActionJob(options, job.envelope);
|
|
742
|
+
if (!persisted.id) throw new Error("Durable data-surface action job has no ID");
|
|
743
|
+
return {
|
|
744
|
+
jobId: persisted.id,
|
|
745
|
+
details: { queue: persisted.queue }
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
async function enqueueDataSurfaceActionJob(options, envelope) {
|
|
751
|
+
await ObjectRegistry.ensureManifestLoaded("SmrtJob");
|
|
752
|
+
const jobs = await SmrtJobCollection.create({ db: options.db });
|
|
753
|
+
const registered = ObjectRegistry.getClassByConstructor(SmrtDataSurfaceActionTask) ?? ObjectRegistry.getClass("SmrtDataSurfaceActionTask");
|
|
754
|
+
const objectType = registered?.qualifiedName ?? registered?.name ?? SmrtDataSurfaceActionTask.name;
|
|
755
|
+
return jobs.enqueueJob({
|
|
756
|
+
tenantId: envelope.principal.tenantId,
|
|
757
|
+
queue: options.queue ?? "data-surface-actions",
|
|
758
|
+
objectType,
|
|
759
|
+
objectId: null,
|
|
760
|
+
method: "run",
|
|
761
|
+
args: {
|
|
762
|
+
version: 1,
|
|
763
|
+
envelope
|
|
764
|
+
},
|
|
765
|
+
priority: options.priority ?? 70,
|
|
766
|
+
timeout: options.timeout ?? 3e5,
|
|
767
|
+
maxAttempts: options.maxAttempts ?? 3
|
|
768
|
+
}, { tenantJobCap: options.tenantJobCap });
|
|
769
|
+
}
|
|
770
|
+
//#endregion
|
|
598
771
|
//#region src/server/serialization.ts
|
|
599
772
|
function serializeResolvedAgent(resolved) {
|
|
600
773
|
const manifest = resolved.manifest;
|
|
@@ -614,6 +787,281 @@ function serializeResolvedAgent(resolved) {
|
|
|
614
787
|
};
|
|
615
788
|
}
|
|
616
789
|
//#endregion
|
|
617
|
-
|
|
790
|
+
//#region src/server/sql-data-surface-action-state.ts
|
|
791
|
+
var __defProp = Object.defineProperty;
|
|
792
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
793
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
794
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
795
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
796
|
+
if (kind && result) __defProp(target, key, result);
|
|
797
|
+
return result;
|
|
798
|
+
};
|
|
799
|
+
var TOKEN_TABLE = "_smrt_data_surface_action_tokens";
|
|
800
|
+
var IDEMPOTENCY_TABLE = "_smrt_data_surface_action_idempotency";
|
|
801
|
+
var INTERNAL_SURFACE = {
|
|
802
|
+
api: false,
|
|
803
|
+
cli: false,
|
|
804
|
+
mcp: false
|
|
805
|
+
};
|
|
806
|
+
var DataSurfaceActionTokenState = class extends SmrtObject {
|
|
807
|
+
tokenHash = "";
|
|
808
|
+
record = emptyTokenRecord();
|
|
809
|
+
consumedBy = null;
|
|
810
|
+
};
|
|
811
|
+
__decorateClass([field({
|
|
812
|
+
type: "text",
|
|
813
|
+
required: true,
|
|
814
|
+
unique: true
|
|
815
|
+
})], DataSurfaceActionTokenState.prototype, "tokenHash", 2);
|
|
816
|
+
__decorateClass([field({
|
|
817
|
+
type: "json",
|
|
818
|
+
required: true
|
|
819
|
+
})], DataSurfaceActionTokenState.prototype, "record", 2);
|
|
820
|
+
__decorateClass([field({
|
|
821
|
+
type: "text",
|
|
822
|
+
nullable: true
|
|
823
|
+
})], DataSurfaceActionTokenState.prototype, "consumedBy", 2);
|
|
824
|
+
DataSurfaceActionTokenState = __decorateClass([smrt({
|
|
825
|
+
tableName: "_smrt_data_surface_action_tokens",
|
|
826
|
+
...INTERNAL_SURFACE
|
|
827
|
+
})], DataSurfaceActionTokenState);
|
|
828
|
+
var DataSurfaceActionIdempotencyState = class extends SmrtObject {
|
|
829
|
+
keyHash = "";
|
|
830
|
+
status = "reserved";
|
|
831
|
+
requestFingerprint = "";
|
|
832
|
+
ownerHash = null;
|
|
833
|
+
reservedAt = null;
|
|
834
|
+
result = null;
|
|
835
|
+
recovery = null;
|
|
836
|
+
};
|
|
837
|
+
__decorateClass([field({
|
|
838
|
+
type: "text",
|
|
839
|
+
required: true,
|
|
840
|
+
unique: true
|
|
841
|
+
})], DataSurfaceActionIdempotencyState.prototype, "keyHash", 2);
|
|
842
|
+
__decorateClass([field({
|
|
843
|
+
type: "text",
|
|
844
|
+
required: true
|
|
845
|
+
})], DataSurfaceActionIdempotencyState.prototype, "status", 2);
|
|
846
|
+
__decorateClass([field({
|
|
847
|
+
type: "text",
|
|
848
|
+
required: true
|
|
849
|
+
})], DataSurfaceActionIdempotencyState.prototype, "requestFingerprint", 2);
|
|
850
|
+
__decorateClass([field({
|
|
851
|
+
type: "text",
|
|
852
|
+
nullable: true
|
|
853
|
+
})], DataSurfaceActionIdempotencyState.prototype, "ownerHash", 2);
|
|
854
|
+
__decorateClass([field({
|
|
855
|
+
type: "text",
|
|
856
|
+
nullable: true
|
|
857
|
+
})], DataSurfaceActionIdempotencyState.prototype, "reservedAt", 2);
|
|
858
|
+
__decorateClass([field({
|
|
859
|
+
type: "json",
|
|
860
|
+
nullable: true
|
|
861
|
+
})], DataSurfaceActionIdempotencyState.prototype, "result", 2);
|
|
862
|
+
__decorateClass([field({
|
|
863
|
+
type: "json",
|
|
864
|
+
nullable: true
|
|
865
|
+
})], DataSurfaceActionIdempotencyState.prototype, "recovery", 2);
|
|
866
|
+
DataSurfaceActionIdempotencyState = __decorateClass([smrt({
|
|
867
|
+
tableName: "_smrt_data_surface_action_idempotency",
|
|
868
|
+
...INTERNAL_SURFACE
|
|
869
|
+
})], DataSurfaceActionIdempotencyState);
|
|
870
|
+
var DataSurfaceActionStateCorruptionError = class extends Error {
|
|
871
|
+
constructor(table) {
|
|
872
|
+
super(`Malformed durable data-surface action state in ${table}`);
|
|
873
|
+
this.name = "DataSurfaceActionStateCorruptionError";
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
var SqlDataSurfaceActionStateStore = class {
|
|
877
|
+
db;
|
|
878
|
+
now;
|
|
879
|
+
authorizeRecovery;
|
|
880
|
+
constructor(options) {
|
|
881
|
+
this.db = options.db;
|
|
882
|
+
this.now = options.now ?? Date.now;
|
|
883
|
+
this.authorizeRecovery = options.authorizeRecovery;
|
|
884
|
+
}
|
|
885
|
+
async putToken(token, record) {
|
|
886
|
+
const timestamp = new Date(this.now()).toISOString();
|
|
887
|
+
await this.db.query(`INSERT INTO ${TOKEN_TABLE}
|
|
888
|
+
(id, slug, context, created_at, updated_at, token_hash, record, consumed_by)
|
|
889
|
+
VALUES (?, ?, '', ?, ?, ?, ?, NULL)
|
|
890
|
+
ON CONFLICT(token_hash) DO NOTHING`, randomUUID(), `action-token-${randomUUID()}`, timestamp, timestamp, secretHash(token), JSON.stringify(record));
|
|
891
|
+
}
|
|
892
|
+
async getToken(token) {
|
|
893
|
+
const row = (await this.db.query(`SELECT record, consumed_by FROM ${TOKEN_TABLE} WHERE token_hash = ? LIMIT 1`, secretHash(token))).rows[0];
|
|
894
|
+
if (!row) return void 0;
|
|
895
|
+
return {
|
|
896
|
+
...tokenRecord(parseObject(row.record, TOKEN_TABLE)),
|
|
897
|
+
...typeof row.consumed_by === "string" ? { consumedBy: row.consumed_by } : {}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async markTokenConsumed(token, idempotencyKey) {
|
|
901
|
+
return (await this.db.query(`UPDATE ${TOKEN_TABLE}
|
|
902
|
+
SET consumed_by = ?, updated_at = ?
|
|
903
|
+
WHERE token_hash = ? AND consumed_by IS NULL
|
|
904
|
+
RETURNING token_hash`, secretHash(idempotencyKey), new Date(this.now()).toISOString(), secretHash(token))).rows.length === 1;
|
|
905
|
+
}
|
|
906
|
+
async consumeTokenAndReserveIdempotency(token, idempotencyKey, scope, reservation) {
|
|
907
|
+
const transaction = this.db.transaction;
|
|
908
|
+
if (!transaction) throw new Error("Durable data-surface action state requires database transactions");
|
|
909
|
+
return await transaction.call(this.db, async (tx) => {
|
|
910
|
+
const timestamp = new Date(this.now()).toISOString();
|
|
911
|
+
if ((await tx.query(`UPDATE ${TOKEN_TABLE}
|
|
912
|
+
SET consumed_by = ?, updated_at = ?
|
|
913
|
+
WHERE token_hash = ?
|
|
914
|
+
AND (consumed_by IS NULL OR consumed_by = ?)
|
|
915
|
+
RETURNING token_hash`, secretHash(idempotencyKey), timestamp, secretHash(token), secretHash(idempotencyKey))).rows.length !== 1) return void 0;
|
|
916
|
+
await tx.query(`INSERT INTO ${IDEMPOTENCY_TABLE}
|
|
917
|
+
(id, slug, context, created_at, updated_at, key_hash, status,
|
|
918
|
+
request_fingerprint, owner_hash, reserved_at, result, recovery)
|
|
919
|
+
VALUES (?, ?, '', ?, ?, ?, 'reserved', ?, ?, ?, NULL, NULL)
|
|
920
|
+
ON CONFLICT(key_hash) DO NOTHING`, randomUUID(), `action-idempotency-${randomUUID()}`, timestamp, timestamp, secretHash(scope), reservation.requestFingerprint, secretHash(reservation.ownerToken), String(reservation.reservedAt));
|
|
921
|
+
const current = await this.getIdempotencyWithOwner(scope, reservation.ownerToken, tx);
|
|
922
|
+
if (!current) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
923
|
+
return current;
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
async getIdempotency(key) {
|
|
927
|
+
const row = (await this.db.query(`SELECT status, request_fingerprint, owner_hash, reserved_at, result, recovery
|
|
928
|
+
FROM ${IDEMPOTENCY_TABLE} WHERE key_hash = ? LIMIT 1`, secretHash(key))).rows[0];
|
|
929
|
+
return row ? idempotencyRecord(row) : void 0;
|
|
930
|
+
}
|
|
931
|
+
async reserveIdempotency(key, reservation) {
|
|
932
|
+
const timestamp = new Date(this.now()).toISOString();
|
|
933
|
+
await this.db.query(`INSERT INTO ${IDEMPOTENCY_TABLE}
|
|
934
|
+
(id, slug, context, created_at, updated_at, key_hash, status,
|
|
935
|
+
request_fingerprint, owner_hash, reserved_at, result, recovery)
|
|
936
|
+
VALUES (?, ?, '', ?, ?, ?, 'reserved', ?, ?, ?, NULL, NULL)
|
|
937
|
+
ON CONFLICT(key_hash) DO NOTHING`, randomUUID(), `action-idempotency-${randomUUID()}`, timestamp, timestamp, secretHash(key), reservation.requestFingerprint, secretHash(reservation.ownerToken), String(reservation.reservedAt));
|
|
938
|
+
const current = await this.getIdempotencyWithOwner(key, reservation.ownerToken);
|
|
939
|
+
if (!current) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
940
|
+
return current;
|
|
941
|
+
}
|
|
942
|
+
async completeIdempotency(key, ownerToken, result) {
|
|
943
|
+
return (await this.db.query(`UPDATE ${IDEMPOTENCY_TABLE}
|
|
944
|
+
SET status = 'completed', result = ?, owner_hash = NULL,
|
|
945
|
+
reserved_at = NULL, updated_at = ?
|
|
946
|
+
WHERE key_hash = ? AND status = 'reserved' AND owner_hash = ?
|
|
947
|
+
RETURNING key_hash`, JSON.stringify(result), new Date(this.now()).toISOString(), secretHash(key), secretHash(ownerToken))).rows.length === 1;
|
|
948
|
+
}
|
|
949
|
+
async releaseIdempotency(key, ownerToken) {
|
|
950
|
+
return (await this.db.query(`DELETE FROM ${IDEMPOTENCY_TABLE}
|
|
951
|
+
WHERE key_hash = ? AND status = 'reserved' AND owner_hash = ?
|
|
952
|
+
RETURNING key_hash`, secretHash(key), secretHash(ownerToken))).rows.length === 1;
|
|
953
|
+
}
|
|
954
|
+
async reconcileIdempotency(key, request) {
|
|
955
|
+
if (!request.requestFingerprint || !Number.isSafeInteger(request.reservedAt) || request.reservedAt < 0 || !request.authorizedBy || request.authorizedBy.length > 256 || !request.evidence || request.evidence.length > 2048) return false;
|
|
956
|
+
if (!this.authorizeRecovery || !await this.authorizeRecovery(Object.freeze({ ...request }))) return false;
|
|
957
|
+
const recovery = {
|
|
958
|
+
authorizedBy: request.authorizedBy,
|
|
959
|
+
evidence: request.evidence,
|
|
960
|
+
reconciledAt: this.now()
|
|
961
|
+
};
|
|
962
|
+
return (await this.db.query(`UPDATE ${IDEMPOTENCY_TABLE}
|
|
963
|
+
SET status = 'completed', result = ?, recovery = ?, owner_hash = NULL,
|
|
964
|
+
reserved_at = NULL, updated_at = ?
|
|
965
|
+
WHERE key_hash = ? AND status = 'reserved'
|
|
966
|
+
AND request_fingerprint = ? AND reserved_at = ?
|
|
967
|
+
RETURNING key_hash`, JSON.stringify(request.result), JSON.stringify(recovery), new Date(recovery.reconciledAt).toISOString(), secretHash(key), request.requestFingerprint, String(request.reservedAt))).rows.length === 1;
|
|
968
|
+
}
|
|
969
|
+
async getIdempotencyWithOwner(key, ownerToken, db = this.db) {
|
|
970
|
+
const row = (await db.query(`SELECT status, request_fingerprint, owner_hash, reserved_at, result, recovery
|
|
971
|
+
FROM ${IDEMPOTENCY_TABLE} WHERE key_hash = ? LIMIT 1`, secretHash(key))).rows[0];
|
|
972
|
+
if (!row) return void 0;
|
|
973
|
+
const record = idempotencyRecord(row);
|
|
974
|
+
if (record.status === "reserved" && row.owner_hash === secretHash(ownerToken)) return {
|
|
975
|
+
...record,
|
|
976
|
+
ownerToken
|
|
977
|
+
};
|
|
978
|
+
return record;
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
function createSqlDataSurfaceActionStateStore(options) {
|
|
982
|
+
return new SqlDataSurfaceActionStateStore(options);
|
|
983
|
+
}
|
|
984
|
+
function secretHash(value) {
|
|
985
|
+
return createHash("sha256").update(value).digest("hex");
|
|
986
|
+
}
|
|
987
|
+
function parseObject(value, table) {
|
|
988
|
+
let parsed = value;
|
|
989
|
+
if (typeof value === "string") try {
|
|
990
|
+
parsed = JSON.parse(value);
|
|
991
|
+
} catch {
|
|
992
|
+
throw new DataSurfaceActionStateCorruptionError(table);
|
|
993
|
+
}
|
|
994
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new DataSurfaceActionStateCorruptionError(table);
|
|
995
|
+
return parsed;
|
|
996
|
+
}
|
|
997
|
+
function idempotencyRecord(row) {
|
|
998
|
+
const requestFingerprint = row.request_fingerprint;
|
|
999
|
+
if (typeof requestFingerprint !== "string") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
1000
|
+
if (row.status === "reserved") {
|
|
1001
|
+
const reservedAt = Number(row.reserved_at);
|
|
1002
|
+
if (typeof row.owner_hash !== "string" || !Number.isSafeInteger(reservedAt)) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
1003
|
+
return {
|
|
1004
|
+
status: "reserved",
|
|
1005
|
+
requestFingerprint,
|
|
1006
|
+
ownerToken: "",
|
|
1007
|
+
reservedAt
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
if (row.status !== "completed") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
1011
|
+
const result = actionResult(parseObject(row.result, IDEMPOTENCY_TABLE));
|
|
1012
|
+
const recovery = row.recovery == null ? void 0 : recoveryEvidence(parseObject(row.recovery, IDEMPOTENCY_TABLE));
|
|
1013
|
+
return {
|
|
1014
|
+
status: "completed",
|
|
1015
|
+
requestFingerprint,
|
|
1016
|
+
result,
|
|
1017
|
+
...recovery ? { recovery } : {}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function tokenRecord(value) {
|
|
1021
|
+
if (!Number.isSafeInteger(value.expiresAt) || !Number.isSafeInteger(value.revision) || [
|
|
1022
|
+
"actorUserId",
|
|
1023
|
+
"identityKey",
|
|
1024
|
+
"actionId",
|
|
1025
|
+
"actionFingerprint",
|
|
1026
|
+
"queryFingerprint",
|
|
1027
|
+
"selectionFingerprint",
|
|
1028
|
+
"resolvedRowsFingerprint",
|
|
1029
|
+
"requestFingerprint"
|
|
1030
|
+
].some((key) => typeof value[key] !== "string") || ![
|
|
1031
|
+
"tenantId",
|
|
1032
|
+
"onBehalfOfUserId",
|
|
1033
|
+
"actsAsProfileId",
|
|
1034
|
+
"agentClass"
|
|
1035
|
+
].every((key) => value[key] === null || typeof value[key] === "string")) throw new DataSurfaceActionStateCorruptionError(TOKEN_TABLE);
|
|
1036
|
+
return value;
|
|
1037
|
+
}
|
|
1038
|
+
function actionResult(value) {
|
|
1039
|
+
if (value.version !== 1 || typeof value.requestId !== "string" || typeof value.actionId !== "string" || value.phase !== "apply" || typeof value.ok !== "boolean" || !value.identity || typeof value.identity !== "object" || Array.isArray(value.identity) || value.reason !== void 0 && typeof value.reason !== "string") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
1040
|
+
return value;
|
|
1041
|
+
}
|
|
1042
|
+
function recoveryEvidence(value) {
|
|
1043
|
+
if (typeof value.authorizedBy !== "string" || typeof value.evidence !== "string" || !Number.isSafeInteger(value.reconciledAt)) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
|
|
1044
|
+
return value;
|
|
1045
|
+
}
|
|
1046
|
+
function emptyTokenRecord() {
|
|
1047
|
+
return {
|
|
1048
|
+
expiresAt: 0,
|
|
1049
|
+
actorUserId: "",
|
|
1050
|
+
tenantId: null,
|
|
1051
|
+
onBehalfOfUserId: null,
|
|
1052
|
+
actsAsProfileId: null,
|
|
1053
|
+
agentClass: null,
|
|
1054
|
+
identityKey: "",
|
|
1055
|
+
actionId: "",
|
|
1056
|
+
actionFingerprint: "",
|
|
1057
|
+
revision: 0,
|
|
1058
|
+
queryFingerprint: "",
|
|
1059
|
+
selectionFingerprint: "",
|
|
1060
|
+
resolvedRowsFingerprint: "",
|
|
1061
|
+
requestFingerprint: ""
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
//#endregion
|
|
1065
|
+
export { DataSurfaceActionIdempotencyState, DataSurfaceActionStateCorruptionError, DataSurfaceActionTokenState, InMemoryDataSurfaceActionStateStore, SmrtDataSurfaceActionTask, SqlDataSurfaceActionStateStore, buildRouteMap, createDataSurfaceActionAdapter, createJobsDataSurfaceBackgroundQueue, createSqlDataSurfaceActionStateStore, extractAgentManifest, extractAgentPackagesFromConfig, isBoundedDataSurfaceJsonValue, loadManifestsFromConfig, loadManifestsFromPackages, loadSlotConfigs, registerDataSurfaceBackgroundActionHandler, resolveAPIRoute, serializeResolvedAgent };
|
|
618
1066
|
|
|
619
1067
|
//# sourceMappingURL=server.js.map
|