@arnilo/prism-tools 0.0.0-stage → 0.13.1
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/CHANGELOG.md +418 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +29 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +584 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { type JsonObject, type OwnershipScope, type SecretRedactor } from "@arnilo/prism";
|
|
3
|
+
import { type ArtifactBodyRef, type ArtifactBodyStore } from "@arnilo/prism/artifacts";
|
|
4
|
+
import type { CheckpointStore } from "@arnilo/prism-extensions/workflows";
|
|
5
|
+
import { WorkToolError } from "./errors.js";
|
|
6
|
+
import { identityKey } from "./idempotency.js";
|
|
7
|
+
import { type AgentIdentity, assertIdentityActive } from "./identity.js";
|
|
8
|
+
import { resolveWorkLimits } from "./limits.js";
|
|
9
|
+
import type { SyncWorkDraftStore, WorkDraft, WorkDraftApproval, WorkDraftStore, WorkLimits, WorkProvider } from "./types.js";
|
|
10
|
+
export const WORK_DRAFT_CHECKPOINT_NAMESPACE = "prism.work.draft";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `checkpoints` service contract conflict code (plan 168 Task 4). The code is the cross-package
|
|
14
|
+
* contract — the store's error class differs per implementation, so match structurally.
|
|
15
|
+
*/
|
|
16
|
+
const CHECKPOINT_CONFLICT_CODE = "ERR_PRISM_CHECKPOINT_CONFLICT";
|
|
17
|
+
|
|
18
|
+
function isCheckpointConflict(error: unknown): boolean {
|
|
19
|
+
return typeof error === "object" && error !== null && (error as { code?: unknown }).code === CHECKPOINT_CONFLICT_CODE;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function canonicalJson(value: unknown): unknown {
|
|
23
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
24
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
25
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
26
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
27
|
+
const out: Record<string, unknown> = {};
|
|
28
|
+
for (const key of Object.keys(value).sort()) {
|
|
29
|
+
const v = (value as Record<string, unknown>)[key];
|
|
30
|
+
if (v !== undefined) {
|
|
31
|
+
out[key] = canonicalJson(v);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
throw new WorkToolError("ERR_PRISM_WORK_INPUT", "Draft payload must be JSON serializable");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function computePayloadDigest(payload: JsonObject): string {
|
|
40
|
+
const canonical = canonicalJson(payload);
|
|
41
|
+
const json = JSON.stringify(canonical);
|
|
42
|
+
return `sha256:${createHash("sha256").update(json).digest("hex")}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function extractDraftRecipients(payload: JsonObject): readonly string[] {
|
|
46
|
+
const recipients: string[] = [];
|
|
47
|
+
const add = (val: unknown) => {
|
|
48
|
+
if (typeof val === "string") {
|
|
49
|
+
for (const part of val.split(/[,;\s]+/)) {
|
|
50
|
+
const trimmed = part.trim().toLowerCase();
|
|
51
|
+
if (trimmed && !recipients.includes(trimmed)) recipients.push(trimmed);
|
|
52
|
+
}
|
|
53
|
+
} else if (Array.isArray(val)) {
|
|
54
|
+
for (const item of val) add(item);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
add(payload.to);
|
|
58
|
+
add(payload.cc);
|
|
59
|
+
add(payload.bcc);
|
|
60
|
+
add(payload.emailAddress);
|
|
61
|
+
add(payload.recipients);
|
|
62
|
+
add(payload.user);
|
|
63
|
+
return Object.freeze(recipients);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function draftCheckpointKey(provider: WorkProvider, draftId: string): string {
|
|
67
|
+
return `${provider}:${draftId}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function extractOwnership(identity: AgentIdentity): OwnershipScope {
|
|
71
|
+
return {
|
|
72
|
+
tenantId: identity.tenantId,
|
|
73
|
+
...(identity.accountId !== undefined ? { accountId: identity.accountId } : {}),
|
|
74
|
+
...(identity.userId !== undefined ? { userId: identity.userId } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function validateApproval(
|
|
79
|
+
draft: WorkDraft,
|
|
80
|
+
approval: WorkDraftApproval,
|
|
81
|
+
options?: { readonly policyRevision?: string; readonly now?: number },
|
|
82
|
+
): void {
|
|
83
|
+
if (approval.draftId !== draft.draftId) {
|
|
84
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Approval draftId ${approval.draftId} does not match draft ${draft.draftId}`);
|
|
85
|
+
}
|
|
86
|
+
if (approval.revision !== draft.revision) {
|
|
87
|
+
throw new WorkToolError(
|
|
88
|
+
"ERR_PRISM_WORK_DRAFT_STALE",
|
|
89
|
+
`Approval revision ${approval.revision} does not match draft revision ${draft.revision}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (approval.payloadDigest !== draft.payloadDigest) {
|
|
93
|
+
throw new WorkToolError(
|
|
94
|
+
"ERR_PRISM_WORK_DRAFT_DIGEST",
|
|
95
|
+
`Approval payloadDigest ${approval.payloadDigest} does not match draft digest ${draft.payloadDigest}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (approval.identityKey && draft.identityKey && approval.identityKey !== draft.identityKey) {
|
|
99
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Approval identity does not match draft identity");
|
|
100
|
+
}
|
|
101
|
+
if (approval.expiresAt) {
|
|
102
|
+
const expiresMs = Date.parse(approval.expiresAt);
|
|
103
|
+
const currentTime = options?.now ?? Date.now();
|
|
104
|
+
if (Number.isFinite(expiresMs) && expiresMs <= currentTime) {
|
|
105
|
+
throw new WorkToolError("ERR_PRISM_WORK_APPROVAL_EXPIRED", "Approval has expired");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const expectedPolicy = options?.policyRevision ?? draft.policyRevision;
|
|
109
|
+
if (expectedPolicy !== undefined && approval.policyRevision !== expectedPolicy) {
|
|
110
|
+
throw new WorkToolError(
|
|
111
|
+
"ERR_PRISM_WORK_POLICY",
|
|
112
|
+
`Policy revision changed (expected ${expectedPolicy}, got ${approval.policyRevision ?? "none"})`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface MemoryWorkDraftStoreOptions {
|
|
118
|
+
readonly ephemeral?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createMemoryWorkDraftStore(options: MemoryWorkDraftStoreOptions = {}): SyncWorkDraftStore {
|
|
122
|
+
const mode = (options.ephemeral ?? true) ? "ephemeral" : ("durable" as const);
|
|
123
|
+
const drafts = new Map<string, { draft: WorkDraft; checkpointVersion: number }>();
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
mode,
|
|
127
|
+
createDraft(input) {
|
|
128
|
+
assertIdentityActive(input.identity);
|
|
129
|
+
input.signal?.throwIfAborted();
|
|
130
|
+
const draftId = input.draftId ?? randomUUID();
|
|
131
|
+
const key = draftCheckpointKey(input.provider, draftId);
|
|
132
|
+
if (drafts.has(key)) {
|
|
133
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", `Draft ${draftId} already exists`);
|
|
134
|
+
}
|
|
135
|
+
const now = new Date().toISOString();
|
|
136
|
+
const payloadDigest = computePayloadDigest(input.payload);
|
|
137
|
+
const recipients = extractDraftRecipients(input.payload);
|
|
138
|
+
const draft: WorkDraft = {
|
|
139
|
+
draftId,
|
|
140
|
+
provider: input.provider,
|
|
141
|
+
op: input.op,
|
|
142
|
+
identityKey: identityKey(input.identity),
|
|
143
|
+
payload: { ...input.payload },
|
|
144
|
+
revision: 1,
|
|
145
|
+
payloadDigest,
|
|
146
|
+
...(recipients.length > 0 ? { recipients } : {}),
|
|
147
|
+
createdAt: now,
|
|
148
|
+
updatedAt: now,
|
|
149
|
+
status: "pending_approval",
|
|
150
|
+
concurrencyToken: randomUUID(),
|
|
151
|
+
...(input.policyRevision ? { policyRevision: input.policyRevision } : {}),
|
|
152
|
+
mode,
|
|
153
|
+
};
|
|
154
|
+
drafts.set(key, { draft, checkpointVersion: 1 });
|
|
155
|
+
return draft;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
getDraft(input) {
|
|
159
|
+
assertIdentityActive(input.identity);
|
|
160
|
+
input.signal?.throwIfAborted();
|
|
161
|
+
const idKey = identityKey(input.identity);
|
|
162
|
+
let entry: { draft: WorkDraft; checkpointVersion: number } | undefined;
|
|
163
|
+
if (input.provider) {
|
|
164
|
+
entry = drafts.get(draftCheckpointKey(input.provider, input.draftId));
|
|
165
|
+
} else {
|
|
166
|
+
for (const [k, v] of drafts) {
|
|
167
|
+
if (k.endsWith(`:${input.draftId}`) || v.draft.draftId === input.draftId) {
|
|
168
|
+
entry = v;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!entry) return undefined;
|
|
174
|
+
if (entry.draft.identityKey !== idKey) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
return entry.draft;
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
updateDraft(input) {
|
|
181
|
+
assertIdentityActive(input.identity);
|
|
182
|
+
input.signal?.throwIfAborted();
|
|
183
|
+
const idKey = identityKey(input.identity);
|
|
184
|
+
let foundKey: string | undefined;
|
|
185
|
+
let entry: { draft: WorkDraft; checkpointVersion: number } | undefined;
|
|
186
|
+
for (const [k, v] of drafts) {
|
|
187
|
+
if (v.draft.draftId === input.draftId) {
|
|
188
|
+
foundKey = k;
|
|
189
|
+
entry = v;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!entry || !foundKey) {
|
|
194
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
195
|
+
}
|
|
196
|
+
if (entry.draft.identityKey !== idKey) {
|
|
197
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
198
|
+
}
|
|
199
|
+
if (entry.draft.status === "executed") {
|
|
200
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_EXECUTED", "Executed draft cannot be modified");
|
|
201
|
+
}
|
|
202
|
+
if (input.expectedRevision !== undefined && entry.draft.revision !== input.expectedRevision) {
|
|
203
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft revision conflict");
|
|
204
|
+
}
|
|
205
|
+
if (input.expectedConcurrencyToken !== undefined && entry.draft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
206
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
207
|
+
}
|
|
208
|
+
const now = new Date().toISOString();
|
|
209
|
+
const payloadDigest = computePayloadDigest(input.payload);
|
|
210
|
+
const recipients = extractDraftRecipients(input.payload);
|
|
211
|
+
const updatedDraft: WorkDraft = {
|
|
212
|
+
...entry.draft,
|
|
213
|
+
payload: { ...input.payload },
|
|
214
|
+
revision: entry.draft.revision + 1,
|
|
215
|
+
payloadDigest,
|
|
216
|
+
recipients: recipients.length > 0 ? recipients : undefined,
|
|
217
|
+
updatedAt: now,
|
|
218
|
+
status: "pending_approval",
|
|
219
|
+
approval: undefined,
|
|
220
|
+
concurrencyToken: randomUUID(),
|
|
221
|
+
...(input.policyRevision ? { policyRevision: input.policyRevision } : {}),
|
|
222
|
+
};
|
|
223
|
+
drafts.set(foundKey, { draft: updatedDraft, checkpointVersion: entry.checkpointVersion + 1 });
|
|
224
|
+
return updatedDraft;
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
approveDraft(input) {
|
|
228
|
+
assertIdentityActive(input.identity);
|
|
229
|
+
input.signal?.throwIfAborted();
|
|
230
|
+
const idKey = identityKey(input.identity);
|
|
231
|
+
let foundKey: string | undefined;
|
|
232
|
+
let entry: { draft: WorkDraft; checkpointVersion: number } | undefined;
|
|
233
|
+
for (const [k, v] of drafts) {
|
|
234
|
+
if (v.draft.draftId === input.draftId) {
|
|
235
|
+
foundKey = k;
|
|
236
|
+
entry = v;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!entry || !foundKey) {
|
|
241
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
242
|
+
}
|
|
243
|
+
if (entry.draft.identityKey !== idKey) {
|
|
244
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
245
|
+
}
|
|
246
|
+
if (entry.draft.status === "executed") {
|
|
247
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_EXECUTED", "Draft already executed");
|
|
248
|
+
}
|
|
249
|
+
if (entry.draft.status === "rejected") {
|
|
250
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_REJECTED", "Draft was rejected");
|
|
251
|
+
}
|
|
252
|
+
if (input.expectedConcurrencyToken !== undefined && entry.draft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
253
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
254
|
+
}
|
|
255
|
+
validateApproval(entry.draft, input.approval);
|
|
256
|
+
if (
|
|
257
|
+
entry.draft.status === "approved" &&
|
|
258
|
+
entry.draft.approval &&
|
|
259
|
+
entry.draft.approval.revision === input.approval.revision &&
|
|
260
|
+
entry.draft.approval.payloadDigest === input.approval.payloadDigest
|
|
261
|
+
) {
|
|
262
|
+
return entry.draft;
|
|
263
|
+
}
|
|
264
|
+
const now = new Date().toISOString();
|
|
265
|
+
const approvedDraft: WorkDraft = {
|
|
266
|
+
...entry.draft,
|
|
267
|
+
status: "approved",
|
|
268
|
+
approval: {
|
|
269
|
+
...input.approval,
|
|
270
|
+
approvedAt: input.approval.approvedAt ?? now,
|
|
271
|
+
},
|
|
272
|
+
updatedAt: now,
|
|
273
|
+
concurrencyToken: randomUUID(),
|
|
274
|
+
};
|
|
275
|
+
drafts.set(foundKey, { draft: approvedDraft, checkpointVersion: entry.checkpointVersion + 1 });
|
|
276
|
+
return approvedDraft;
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
markDraft(input) {
|
|
280
|
+
assertIdentityActive(input.identity);
|
|
281
|
+
input.signal?.throwIfAborted();
|
|
282
|
+
const idKey = identityKey(input.identity);
|
|
283
|
+
let foundKey: string | undefined;
|
|
284
|
+
let entry: { draft: WorkDraft; checkpointVersion: number } | undefined;
|
|
285
|
+
for (const [k, v] of drafts) {
|
|
286
|
+
if (v.draft.draftId === input.draftId) {
|
|
287
|
+
foundKey = k;
|
|
288
|
+
entry = v;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!entry || !foundKey) {
|
|
293
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
294
|
+
}
|
|
295
|
+
if (entry.draft.identityKey !== idKey) {
|
|
296
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
297
|
+
}
|
|
298
|
+
if (input.expectedConcurrencyToken !== undefined && entry.draft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
299
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
300
|
+
}
|
|
301
|
+
const now = new Date().toISOString();
|
|
302
|
+
const markedDraft: WorkDraft = {
|
|
303
|
+
...entry.draft,
|
|
304
|
+
status: input.status,
|
|
305
|
+
concurrencyToken: input.concurrencyToken ?? randomUUID(),
|
|
306
|
+
updatedAt: now,
|
|
307
|
+
...(input.status === "executed" ? { executedAt: now } : {}),
|
|
308
|
+
};
|
|
309
|
+
drafts.set(foundKey, { draft: markedDraft, checkpointVersion: entry.checkpointVersion + 1 });
|
|
310
|
+
return markedDraft;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface CheckpointWorkDraftStoreOptions {
|
|
316
|
+
readonly checkpoints: CheckpointStore;
|
|
317
|
+
readonly bodies?: ArtifactBodyStore;
|
|
318
|
+
readonly limits?: WorkLimits;
|
|
319
|
+
readonly redactor?: SecretRedactor;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function createCheckpointWorkDraftStore(options: CheckpointWorkDraftStoreOptions): WorkDraftStore {
|
|
323
|
+
const checkpoints = options.checkpoints;
|
|
324
|
+
const limits = resolveWorkLimits(options.limits);
|
|
325
|
+
const mode = "durable" as const;
|
|
326
|
+
|
|
327
|
+
async function loadCheckpointEntry(
|
|
328
|
+
identity: AgentIdentity,
|
|
329
|
+
provider: WorkProvider | undefined,
|
|
330
|
+
draftId: string,
|
|
331
|
+
signal?: AbortSignal,
|
|
332
|
+
): Promise<{ checkpointKey: string; checkpoint: Awaited<ReturnType<CheckpointStore["loadCheckpoint"]>> } | undefined> {
|
|
333
|
+
const ownership = extractOwnership(identity);
|
|
334
|
+
if (provider) {
|
|
335
|
+
const key = draftCheckpointKey(provider, draftId);
|
|
336
|
+
const cp = await checkpoints.loadCheckpoint({
|
|
337
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
338
|
+
key,
|
|
339
|
+
...ownership,
|
|
340
|
+
signal,
|
|
341
|
+
});
|
|
342
|
+
return cp ? { checkpointKey: key, checkpoint: cp } : undefined;
|
|
343
|
+
}
|
|
344
|
+
for (const p of ["microsoft365", "google-workspace"] as const) {
|
|
345
|
+
const key = draftCheckpointKey(p, draftId);
|
|
346
|
+
const cp = await checkpoints.loadCheckpoint({
|
|
347
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
348
|
+
key,
|
|
349
|
+
...ownership,
|
|
350
|
+
signal,
|
|
351
|
+
});
|
|
352
|
+
if (cp) return { checkpointKey: key, checkpoint: cp };
|
|
353
|
+
}
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
mode,
|
|
359
|
+
async createDraft(input) {
|
|
360
|
+
assertIdentityActive(input.identity);
|
|
361
|
+
input.signal?.throwIfAborted();
|
|
362
|
+
const draftId = input.draftId ?? randomUUID();
|
|
363
|
+
const key = draftCheckpointKey(input.provider, draftId);
|
|
364
|
+
const ownership = extractOwnership(input.identity);
|
|
365
|
+
const now = new Date().toISOString();
|
|
366
|
+
const payloadDigest = computePayloadDigest(input.payload);
|
|
367
|
+
const recipients = extractDraftRecipients(input.payload);
|
|
368
|
+
|
|
369
|
+
const payloadJson = JSON.stringify(input.payload);
|
|
370
|
+
if (Buffer.byteLength(payloadJson) > limits.maxRequestBytes) {
|
|
371
|
+
throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "Draft payload exceeds byte limit");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let bodyRef: ArtifactBodyRef | undefined;
|
|
375
|
+
if (options.bodies) {
|
|
376
|
+
const bodyContent =
|
|
377
|
+
typeof input.payload.bodyContents === "string"
|
|
378
|
+
? input.payload.bodyContents
|
|
379
|
+
: typeof input.payload.body === "string"
|
|
380
|
+
? input.payload.body
|
|
381
|
+
: undefined;
|
|
382
|
+
if (bodyContent) {
|
|
383
|
+
const bodyBytes = Buffer.from(bodyContent, "utf8");
|
|
384
|
+
const hash = createHash("sha256").update(bodyBytes).digest("hex");
|
|
385
|
+
bodyRef = {
|
|
386
|
+
...ownership,
|
|
387
|
+
threadId: `work-draft:${input.provider}`,
|
|
388
|
+
artifactId: draftId,
|
|
389
|
+
version: 1,
|
|
390
|
+
hash,
|
|
391
|
+
mime: "text/plain",
|
|
392
|
+
size: bodyBytes.byteLength,
|
|
393
|
+
};
|
|
394
|
+
await options.bodies.put(bodyRef, bodyBytes);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const draft: WorkDraft = {
|
|
399
|
+
draftId,
|
|
400
|
+
provider: input.provider,
|
|
401
|
+
op: input.op,
|
|
402
|
+
identityKey: identityKey(input.identity),
|
|
403
|
+
payload: { ...input.payload },
|
|
404
|
+
revision: 1,
|
|
405
|
+
payloadDigest,
|
|
406
|
+
...(recipients.length > 0 ? { recipients } : {}),
|
|
407
|
+
createdAt: now,
|
|
408
|
+
updatedAt: now,
|
|
409
|
+
status: "pending_approval",
|
|
410
|
+
concurrencyToken: randomUUID(),
|
|
411
|
+
...(input.policyRevision ? { policyRevision: input.policyRevision } : {}),
|
|
412
|
+
...(bodyRef ? { bodyRef } : {}),
|
|
413
|
+
mode,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
await checkpoints.saveCheckpoint({
|
|
418
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
419
|
+
key,
|
|
420
|
+
...ownership,
|
|
421
|
+
version: 1,
|
|
422
|
+
expectedVersion: 0,
|
|
423
|
+
value: draft,
|
|
424
|
+
category: "work_draft",
|
|
425
|
+
signal: input.signal,
|
|
426
|
+
});
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (isCheckpointConflict(error)) {
|
|
429
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", `Draft ${draftId} already exists`);
|
|
430
|
+
}
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
return draft;
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
async getDraft(input) {
|
|
437
|
+
assertIdentityActive(input.identity);
|
|
438
|
+
input.signal?.throwIfAborted();
|
|
439
|
+
const idKey = identityKey(input.identity);
|
|
440
|
+
const entry = await loadCheckpointEntry(input.identity, input.provider, input.draftId, input.signal);
|
|
441
|
+
if (!entry?.checkpoint) return undefined;
|
|
442
|
+
const draft = entry.checkpoint.value as WorkDraft;
|
|
443
|
+
if (draft.identityKey !== idKey) return undefined;
|
|
444
|
+
return draft;
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
async updateDraft(input) {
|
|
448
|
+
assertIdentityActive(input.identity);
|
|
449
|
+
input.signal?.throwIfAborted();
|
|
450
|
+
const idKey = identityKey(input.identity);
|
|
451
|
+
const entry = await loadCheckpointEntry(input.identity, undefined, input.draftId, input.signal);
|
|
452
|
+
if (!entry?.checkpoint) {
|
|
453
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
454
|
+
}
|
|
455
|
+
const currentDraft = entry.checkpoint.value as WorkDraft;
|
|
456
|
+
if (currentDraft.identityKey !== idKey) {
|
|
457
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
458
|
+
}
|
|
459
|
+
if (currentDraft.status === "executed") {
|
|
460
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_EXECUTED", "Executed draft cannot be modified");
|
|
461
|
+
}
|
|
462
|
+
if (input.expectedRevision !== undefined && currentDraft.revision !== input.expectedRevision) {
|
|
463
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft revision conflict");
|
|
464
|
+
}
|
|
465
|
+
if (input.expectedConcurrencyToken !== undefined && currentDraft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
466
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ownership = extractOwnership(input.identity);
|
|
470
|
+
const payloadJson = JSON.stringify(input.payload);
|
|
471
|
+
if (Buffer.byteLength(payloadJson) > limits.maxRequestBytes) {
|
|
472
|
+
throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "Draft payload exceeds byte limit");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const newRevision = currentDraft.revision + 1;
|
|
476
|
+
let bodyRef: ArtifactBodyRef | undefined;
|
|
477
|
+
if (options.bodies) {
|
|
478
|
+
const bodyContent =
|
|
479
|
+
typeof input.payload.bodyContents === "string"
|
|
480
|
+
? input.payload.bodyContents
|
|
481
|
+
: typeof input.payload.body === "string"
|
|
482
|
+
? input.payload.body
|
|
483
|
+
: undefined;
|
|
484
|
+
if (bodyContent) {
|
|
485
|
+
const bodyBytes = Buffer.from(bodyContent, "utf8");
|
|
486
|
+
const hash = createHash("sha256").update(bodyBytes).digest("hex");
|
|
487
|
+
bodyRef = {
|
|
488
|
+
...ownership,
|
|
489
|
+
threadId: `work-draft:${currentDraft.provider}`,
|
|
490
|
+
artifactId: currentDraft.draftId,
|
|
491
|
+
version: newRevision,
|
|
492
|
+
hash,
|
|
493
|
+
mime: "text/plain",
|
|
494
|
+
size: bodyBytes.byteLength,
|
|
495
|
+
};
|
|
496
|
+
await options.bodies.put(bodyRef, bodyBytes);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const now = new Date().toISOString();
|
|
501
|
+
const payloadDigest = computePayloadDigest(input.payload);
|
|
502
|
+
const recipients = extractDraftRecipients(input.payload);
|
|
503
|
+
const updatedDraft: WorkDraft = {
|
|
504
|
+
...currentDraft,
|
|
505
|
+
payload: { ...input.payload },
|
|
506
|
+
revision: newRevision,
|
|
507
|
+
payloadDigest,
|
|
508
|
+
recipients: recipients.length > 0 ? recipients : undefined,
|
|
509
|
+
updatedAt: now,
|
|
510
|
+
status: "pending_approval",
|
|
511
|
+
approval: undefined,
|
|
512
|
+
concurrencyToken: randomUUID(),
|
|
513
|
+
...(input.policyRevision ? { policyRevision: input.policyRevision } : {}),
|
|
514
|
+
bodyRef,
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
await checkpoints.saveCheckpoint({
|
|
519
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
520
|
+
key: entry.checkpointKey,
|
|
521
|
+
...ownership,
|
|
522
|
+
version: entry.checkpoint.version + 1,
|
|
523
|
+
expectedVersion: entry.checkpoint.version,
|
|
524
|
+
value: updatedDraft,
|
|
525
|
+
category: "work_draft",
|
|
526
|
+
signal: input.signal,
|
|
527
|
+
});
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (isCheckpointConflict(error)) {
|
|
530
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Concurrent draft modification");
|
|
531
|
+
}
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
return updatedDraft;
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
async approveDraft(input) {
|
|
538
|
+
assertIdentityActive(input.identity);
|
|
539
|
+
input.signal?.throwIfAborted();
|
|
540
|
+
const idKey = identityKey(input.identity);
|
|
541
|
+
const entry = await loadCheckpointEntry(input.identity, undefined, input.draftId, input.signal);
|
|
542
|
+
if (!entry?.checkpoint) {
|
|
543
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
544
|
+
}
|
|
545
|
+
const currentDraft = entry.checkpoint.value as WorkDraft;
|
|
546
|
+
if (currentDraft.identityKey !== idKey) {
|
|
547
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
548
|
+
}
|
|
549
|
+
if (currentDraft.status === "executed") {
|
|
550
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_EXECUTED", "Draft already executed");
|
|
551
|
+
}
|
|
552
|
+
if (currentDraft.status === "rejected") {
|
|
553
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_REJECTED", "Draft was rejected");
|
|
554
|
+
}
|
|
555
|
+
if (input.expectedConcurrencyToken !== undefined && currentDraft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
556
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
557
|
+
}
|
|
558
|
+
validateApproval(currentDraft, input.approval);
|
|
559
|
+
if (
|
|
560
|
+
currentDraft.status === "approved" &&
|
|
561
|
+
currentDraft.approval &&
|
|
562
|
+
currentDraft.approval.revision === input.approval.revision &&
|
|
563
|
+
currentDraft.approval.payloadDigest === input.approval.payloadDigest
|
|
564
|
+
) {
|
|
565
|
+
return currentDraft;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const ownership = extractOwnership(input.identity);
|
|
569
|
+
const now = new Date().toISOString();
|
|
570
|
+
const approvedDraft: WorkDraft = {
|
|
571
|
+
...currentDraft,
|
|
572
|
+
status: "approved",
|
|
573
|
+
approval: {
|
|
574
|
+
...input.approval,
|
|
575
|
+
approvedAt: input.approval.approvedAt ?? now,
|
|
576
|
+
},
|
|
577
|
+
updatedAt: now,
|
|
578
|
+
concurrencyToken: randomUUID(),
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
await checkpoints.saveCheckpoint({
|
|
583
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
584
|
+
key: entry.checkpointKey,
|
|
585
|
+
...ownership,
|
|
586
|
+
version: entry.checkpoint.version + 1,
|
|
587
|
+
expectedVersion: entry.checkpoint.version,
|
|
588
|
+
value: approvedDraft,
|
|
589
|
+
category: "work_draft",
|
|
590
|
+
signal: input.signal,
|
|
591
|
+
});
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (isCheckpointConflict(error)) {
|
|
594
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Concurrent draft modification");
|
|
595
|
+
}
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
return approvedDraft;
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
async markDraft(input) {
|
|
602
|
+
assertIdentityActive(input.identity);
|
|
603
|
+
input.signal?.throwIfAborted();
|
|
604
|
+
const idKey = identityKey(input.identity);
|
|
605
|
+
const entry = await loadCheckpointEntry(input.identity, undefined, input.draftId, input.signal);
|
|
606
|
+
if (!entry?.checkpoint) {
|
|
607
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT", `Draft ${input.draftId} not found`);
|
|
608
|
+
}
|
|
609
|
+
const currentDraft = entry.checkpoint.value as WorkDraft;
|
|
610
|
+
if (currentDraft.identityKey !== idKey) {
|
|
611
|
+
throw new WorkToolError("ERR_PRISM_WORK_IDENTITY", "Draft identity mismatch");
|
|
612
|
+
}
|
|
613
|
+
if (input.expectedConcurrencyToken !== undefined && currentDraft.concurrencyToken !== input.expectedConcurrencyToken) {
|
|
614
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Draft concurrency token conflict");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const ownership = extractOwnership(input.identity);
|
|
618
|
+
const now = new Date().toISOString();
|
|
619
|
+
const markedDraft: WorkDraft = {
|
|
620
|
+
...currentDraft,
|
|
621
|
+
status: input.status,
|
|
622
|
+
concurrencyToken: input.concurrencyToken ?? randomUUID(),
|
|
623
|
+
updatedAt: now,
|
|
624
|
+
...(input.status === "executed" ? { executedAt: now } : {}),
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
await checkpoints.saveCheckpoint({
|
|
629
|
+
namespace: WORK_DRAFT_CHECKPOINT_NAMESPACE,
|
|
630
|
+
key: entry.checkpointKey,
|
|
631
|
+
...ownership,
|
|
632
|
+
version: entry.checkpoint.version + 1,
|
|
633
|
+
expectedVersion: entry.checkpoint.version,
|
|
634
|
+
value: markedDraft,
|
|
635
|
+
category: "work_draft",
|
|
636
|
+
signal: input.signal,
|
|
637
|
+
});
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (isCheckpointConflict(error)) {
|
|
640
|
+
throw new WorkToolError("ERR_PRISM_WORK_DRAFT_CONFLICT", "Concurrent draft modification");
|
|
641
|
+
}
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
return markedDraft;
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
}
|