@piagent/platform 1.4.1 → 1.5.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 +58 -0
- package/README.md +5 -1
- package/README.vi.md +21 -1
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/languages.json +2 -2
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +6 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/piagent-webui/document-workspace-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/document-workspace-v1.valid.json +42 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +2 -1
- package/package.json +1 -1
- package/packages/piagent-core/extensions/document-intake.ts +6 -3
- package/packages/piagent-core/extensions/piagent-guard.ts +2 -2
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/input/attachment-document-command.ts +39 -0
- package/packages/piagent-core/runtime/input/attachment-reservation.ts +36 -0
- package/packages/{piagent-webui/extension → piagent-core/runtime/input}/attachment-store.ts +221 -44
- package/packages/piagent-core/runtime/inspection/document-workspace-projection.ts +276 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +20 -5
- package/packages/piagent-core/runtime/inspection/session-identity.ts +20 -0
- package/packages/piagent-core/runtime/registration/workflow-commands.ts +5 -4
- package/packages/piagent-core/runtime/workflows/input-routing.ts +13 -2
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-Uouhx1QO.js +29 -0
- package/packages/piagent-webui/dist/client/assets/index-BV-_03yl.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-MBZq8XLV.js +174 -0
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/extension/piagent-webui.ts +15 -2
- package/packages/piagent-webui/extension/same-session-bridge.ts +9 -9
- package/packages/piagent-webui/gateway/ai-capability-inspection.ts +53 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +23 -3
- package/packages/piagent-webui/gateway/native-project-picker.ts +63 -7
- package/packages/piagent-webui/gateway/session-attachment-registry.ts +165 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +62 -9
- package/packages/piagent-webui/gateway/session-command-controller.ts +47 -14
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +30 -10
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +19 -10
- package/packages/piagent-webui/package.json +3 -1
- package/packages/piagent-webui/server/core-inspection-provider.ts +48 -11
- package/packages/piagent-webui/server/ipc-read-model-client.ts +2 -0
- package/packages/piagent-webui/server/loopback-server.ts +63 -25
- package/packages/piagent-webui/server/read-model-provider.ts +2 -0
- package/packages/piagent-webui/server/read-only-router.ts +6 -0
- package/packages/piagent-webui/server/transcript-projection.ts +43 -2
- package/schemas/piagent-webui/attachment-v1.schema.json +24 -4
- package/schemas/piagent-webui/capabilities-v1.schema.json +2 -2
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/document-workspace-v1.schema.json +155 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +14 -3
- package/schemas/piagent-webui/transcript-v1.schema.json +27 -0
- package/scripts/verify-local.sh +16 -1
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +0 -1
- package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +0 -172
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp-servers" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp-servers" hreflang="en" lang="en">EN</a>
|
package/docs-site/mcp.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/mcp" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/mcp" hreflang="en" lang="en">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/permissions" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/permissions" hreflang="en" lang="en">EN</a>
|
package/docs-site/profiles.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/profiles" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/profiles" hreflang="en" lang="en">EN</a>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/quickstart" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/quickstart" hreflang="en" lang="en">EN</a>
|
package/docs-site/runtime.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/runtime" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/runtime" hreflang="en" lang="en">EN</a>
|
package/docs-site/scope.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/scope" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/scope" hreflang="en" lang="en">EN</a>
|
package/docs-site/security.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/security" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/security" hreflang="en" lang="en">EN</a>
|
package/docs-site/team.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/team" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/team" hreflang="en" lang="en">EN</a>
|
package/docs-site/whats-new.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/whats-new" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/whats-new" hreflang="en" lang="en">EN</a>
|
package/docs-site/workflows.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<span>Pi Agent Platform</span>
|
|
31
31
|
</a>
|
|
32
32
|
<div class="top-actions">
|
|
33
|
-
<span class="pill"><span class="pill-dot"></span>v1.
|
|
33
|
+
<span class="pill"><span class="pill-dot"></span>v1.5.1 docs · VI</span>
|
|
34
34
|
<nav class="language-switch" aria-label="Ngôn ngữ">
|
|
35
35
|
<a class="language-option current" href="/workflows" hreflang="vi" lang="vi" aria-current="true">VI</a>
|
|
36
36
|
<a class="language-option" href="/en/workflows" hreflang="en" lang="en">EN</a>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "piagent-webui-document-workspace-v1",
|
|
4
|
+
"messageType": "document",
|
|
5
|
+
"generatedAt": "2026-08-17T09:00:00.000Z",
|
|
6
|
+
"documentRef": "document_01",
|
|
7
|
+
"state": "unavailable",
|
|
8
|
+
"name": null,
|
|
9
|
+
"relativePath": null,
|
|
10
|
+
"rootRef": null,
|
|
11
|
+
"format": null,
|
|
12
|
+
"text": "# Ke hoach quy ba\n\nContent that must never accompany a refusal.\n",
|
|
13
|
+
"sizeBytes": null,
|
|
14
|
+
"truncated": false,
|
|
15
|
+
"redacted": false,
|
|
16
|
+
"reasonCode": "document-protected"
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "piagent-webui-document-workspace-v1",
|
|
4
|
+
"messageType": "listing",
|
|
5
|
+
"generatedAt": "2026-08-17T09:00:00.000Z",
|
|
6
|
+
"state": "ready",
|
|
7
|
+
"roots": [
|
|
8
|
+
{ "rootRef": "document-root_01", "path": "/workspace/project", "source": "project", "documentCount": 2 },
|
|
9
|
+
{ "rootRef": "document-root_02", "path": "/home/operator/Downloads", "source": "profile", "documentCount": 1 }
|
|
10
|
+
],
|
|
11
|
+
"documents": [
|
|
12
|
+
{
|
|
13
|
+
"documentRef": "document_01",
|
|
14
|
+
"rootRef": "document-root_01",
|
|
15
|
+
"name": "ke-hoach.md",
|
|
16
|
+
"relativePath": "tai-lieu/ke-hoach.md",
|
|
17
|
+
"extension": "md",
|
|
18
|
+
"sizeBytes": 4096,
|
|
19
|
+
"modifiedAt": "2026-08-17T08:30:00.000Z"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"documentRef": "document_02",
|
|
23
|
+
"rootRef": "document-root_01",
|
|
24
|
+
"name": "bao-cao.docx",
|
|
25
|
+
"relativePath": "tai-lieu/bao-cao.docx",
|
|
26
|
+
"extension": "docx",
|
|
27
|
+
"sizeBytes": 245760,
|
|
28
|
+
"modifiedAt": "2026-08-17T08:45:00.000Z"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"documentRef": "document_03",
|
|
32
|
+
"rootRef": "document-root_02",
|
|
33
|
+
"name": "vendor-spec.pdf",
|
|
34
|
+
"relativePath": "vendor-spec.pdf",
|
|
35
|
+
"extension": "pdf",
|
|
36
|
+
"sizeBytes": 1048576,
|
|
37
|
+
"modifiedAt": "2026-08-16T17:10:00.000Z"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"truncated": false,
|
|
41
|
+
"reasonCode": null
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -51,7 +51,7 @@ export type DocumentExtraction =
|
|
|
51
51
|
| { status: "ok"; text: string; truncated: boolean; kind: "text" | "docx" | "pdf" }
|
|
52
52
|
| { status: "error"; reason: string };
|
|
53
53
|
|
|
54
|
-
type DocumentCommandResult = { status: number | null; stdout: string; stderr: string; error?: Error };
|
|
54
|
+
export type DocumentCommandResult = { status: number | null; stdout: string; stderr: string; error?: Error };
|
|
55
55
|
|
|
56
56
|
export function expandHome(candidate: string, home: string | undefined): string {
|
|
57
57
|
if (candidate === "~") return home ?? candidate;
|
|
@@ -450,7 +450,10 @@ export function extractPdfFromDescriptor(
|
|
|
450
450
|
// stdout out. The converter is handed the already-open descriptor rather than
|
|
451
451
|
// a path, so it reads the file whose identity was verified and cannot be
|
|
452
452
|
// pointed somewhere else by a name that changed in the meantime.
|
|
453
|
-
|
|
453
|
+
return extractPdfCommandResult(run("pdftotext", ["-layout", "-enc", "UTF-8", "-", "-"], fd));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function extractPdfCommandResult(result: DocumentCommandResult): DocumentExtraction {
|
|
454
457
|
if (result.error) {
|
|
455
458
|
return {
|
|
456
459
|
status: "error",
|
|
@@ -471,7 +474,7 @@ export function extractPdfFromDescriptor(
|
|
|
471
474
|
|
|
472
475
|
// spawnSync blocks the whole runtime, so an external converter that never
|
|
473
476
|
// returns would hang the session rather than fail the read.
|
|
474
|
-
const PDF_TIMEOUT_MS = 20_000;
|
|
477
|
+
export const PDF_TIMEOUT_MS = 20_000;
|
|
475
478
|
|
|
476
479
|
export function probeExecutableOnPath(executable: string): DocumentCommandResult {
|
|
477
480
|
const result = spawnSync("/bin/sh", ["-c", 'command -v "$1"', "piagent-command-probe", executable], {
|
|
@@ -115,7 +115,7 @@ import {
|
|
|
115
115
|
activeTaskToolGroups
|
|
116
116
|
} from "../runtime/tools/tool-groups.ts";
|
|
117
117
|
import type { PiagentToolGroup } from "../runtime/tools/tool-groups.ts";
|
|
118
|
-
import { shortTaskLabel } from "../runtime/workflows/input-routing.ts";
|
|
118
|
+
import { freshRequestParts, shortTaskLabel } from "../runtime/workflows/input-routing.ts";
|
|
119
119
|
import {
|
|
120
120
|
automaticAcceptanceCriteria,
|
|
121
121
|
automaticReviewLenses,
|
|
@@ -4705,7 +4705,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4705
4705
|
safeTaskId, searchContextIndex, searchContextIndexV2, searchMemoryFiles, selectRuntimeAction,
|
|
4706
4706
|
selectValueFromUi, selectVerificationPlan, semanticCompactionInstructions, sendWorkflowFollowUp, setPermissionOverrideForContext,
|
|
4707
4707
|
semanticRepairCompletionBlock: (cwd: string, taskRunId: string) => semanticRepairRuntime.completionBlock(cwd, taskRunId),
|
|
4708
|
-
shellArg, shortTaskLabel, solverShadow, summarizeAttempt, taskChangedFileEvidence,
|
|
4708
|
+
freshRequestParts, shellArg, shortTaskLabel, solverShadow, summarizeAttempt, taskChangedFileEvidence,
|
|
4709
4709
|
techContextDirPath, techContextFilePath, techContextRelativePath, techOptionById, techStackPath,
|
|
4710
4710
|
telemetry, toolRegistryConfig, trajectoryRuntime, uniqueStrings, usageExactCommands,
|
|
4711
4711
|
validTaskScopePattern, validateNewWorkPlan, verifierCommandInstructions, verifyProjectCapabilityState, workingTreeEvidenceDigest,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { MAX_DOCUMENT_BYTES, PDF_TIMEOUT_MS, probeExecutableOnPath,
|
|
4
|
+
type DocumentCommandResult } from "../../extensions/document-intake.ts";
|
|
5
|
+
|
|
6
|
+
export type AttachmentDocumentCommand = (executable: string, args: string[], input?: Buffer) =>
|
|
7
|
+
DocumentCommandResult | Promise<DocumentCommandResult>;
|
|
8
|
+
|
|
9
|
+
// The converter is external and may consume its full timeout. Run it without a
|
|
10
|
+
// shell and without blocking Pi's event loop, while bounding both output pipes.
|
|
11
|
+
export function runAttachmentDocumentCommand(executable: string, args: string[], input?: Buffer):
|
|
12
|
+
DocumentCommandResult | Promise<DocumentCommandResult> {
|
|
13
|
+
if (executable === "command") return probeExecutableOnPath(args[1] ?? "");
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let child: ReturnType<typeof spawn>;
|
|
16
|
+
try { child = spawn(executable, args, { stdio: ["pipe", "pipe", "pipe"] }); }
|
|
17
|
+
catch (cause) { resolve({ status: null, stdout: "", stderr: "", error: cause as Error }); return; }
|
|
18
|
+
const stdout: Buffer[] = [], stderr: Buffer[] = []; let stdoutBytes = 0, stderrBytes = 0, settled = false;
|
|
19
|
+
const finish = (result: DocumentCommandResult) => {
|
|
20
|
+
if (settled) return; settled = true; clearTimeout(timer); resolve(result);
|
|
21
|
+
};
|
|
22
|
+
const overflow = () => {
|
|
23
|
+
child.kill("SIGKILL");
|
|
24
|
+
const error = new Error("document converter output exceeded its limit"); (error as NodeJS.ErrnoException).code = "ENOBUFS";
|
|
25
|
+
finish({ status: null, stdout: "", stderr: "", error });
|
|
26
|
+
};
|
|
27
|
+
child.stdout.on("data", (chunk: Buffer) => { stdoutBytes += chunk.length; if (stdoutBytes > MAX_DOCUMENT_BYTES) overflow(); else stdout.push(chunk); });
|
|
28
|
+
child.stderr.on("data", (chunk: Buffer) => { stderrBytes += chunk.length; if (stderrBytes > MAX_DOCUMENT_BYTES) overflow(); else stderr.push(chunk); });
|
|
29
|
+
child.on("error", (error) => finish({ status: null, stdout: "", stderr: "", error }));
|
|
30
|
+
child.on("close", (status) => finish({ status, stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8") }));
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
child.kill("SIGKILL"); const error = new Error("document converter ETIMEDOUT");
|
|
33
|
+
finish({ status: null, stdout: "", stderr: "", error });
|
|
34
|
+
}, PDF_TIMEOUT_MS);
|
|
35
|
+
timer.unref();
|
|
36
|
+
child.stdin.on("error", () => undefined);
|
|
37
|
+
child.stdin.end(input);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type AttachmentReservation<T> = {
|
|
2
|
+
prepared: T;
|
|
3
|
+
commit(): void;
|
|
4
|
+
release(): void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// A small transaction primitive for staged attachments. The map is the lock:
|
|
8
|
+
// only one dispatch may prepare a ref, and only that dispatch may consume it.
|
|
9
|
+
export function reserveAttachments<TRecord extends { attachmentRef: string }, TPrepared>(input: {
|
|
10
|
+
records: TRecord[];
|
|
11
|
+
live: Map<string, TRecord>;
|
|
12
|
+
reservations: Map<string, string>;
|
|
13
|
+
token: string;
|
|
14
|
+
prepared: TPrepared;
|
|
15
|
+
consume(record: TRecord): void;
|
|
16
|
+
}): AttachmentReservation<TPrepared> {
|
|
17
|
+
for (const record of input.records) input.reservations.set(record.attachmentRef, input.token);
|
|
18
|
+
let state: "active" | "committed" | "released" = "active";
|
|
19
|
+
return {
|
|
20
|
+
prepared: input.prepared,
|
|
21
|
+
commit: () => {
|
|
22
|
+
if (state === "committed") return;
|
|
23
|
+
if (state !== "active" || input.records.some((record) => input.live.get(record.attachmentRef) !== record
|
|
24
|
+
|| input.reservations.get(record.attachmentRef) !== input.token)) throw new Error("attachment-reservation-unavailable");
|
|
25
|
+
state = "committed";
|
|
26
|
+
input.records.forEach(input.consume);
|
|
27
|
+
},
|
|
28
|
+
release: () => {
|
|
29
|
+
if (state !== "active") return;
|
|
30
|
+
state = "released";
|
|
31
|
+
for (const record of input.records) {
|
|
32
|
+
if (input.reservations.get(record.attachmentRef) === input.token) input.reservations.delete(record.attachmentRef);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|