cloving 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -20
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/authority/commands.d.ts +1 -0
- package/dist/src/authority/commands.js +33 -4
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/reconciliation.d.ts +3 -0
- package/dist/src/authority/reconciliation.js +21 -5
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +5 -0
- package/dist/src/broker/actor/authority-queries.d.ts +4 -0
- package/dist/src/broker/actor/authority-queries.js +51 -2
- package/dist/src/broker/actor/authority-session.d.ts +2 -0
- package/dist/src/broker/actor/authority-session.js +3 -4
- package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
- package/dist/src/broker/actor/effect-delivery.js +9 -0
- package/dist/src/broker/authority-admission.js +13 -5
- package/dist/src/chats/analysis-batches.d.ts +42 -0
- package/dist/src/chats/analysis-batches.js +97 -0
- package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
- package/dist/src/chats/analysis-checkpoint.js +31 -0
- package/dist/src/chats/analysis-contract.d.ts +80 -0
- package/dist/src/chats/analysis-contract.js +16 -0
- package/dist/src/chats/analysis-data.d.ts +3 -0
- package/dist/src/chats/analysis-data.js +85 -0
- package/dist/src/chats/analysis-input.d.ts +18 -0
- package/dist/src/chats/analysis-input.js +97 -0
- package/dist/src/chats/analysis-links.d.ts +2 -0
- package/dist/src/chats/analysis-links.js +25 -0
- package/dist/src/chats/analysis-merge.d.ts +16 -0
- package/dist/src/chats/analysis-merge.js +134 -0
- package/dist/src/chats/analysis-prompt.d.ts +18 -0
- package/dist/src/chats/analysis-prompt.js +42 -0
- package/dist/src/chats/analysis-proposal.d.ts +4 -0
- package/dist/src/chats/analysis-proposal.js +41 -0
- package/dist/src/chats/analysis-provenance.d.ts +4 -0
- package/dist/src/chats/analysis-provenance.js +99 -0
- package/dist/src/chats/analysis-shape.d.ts +4 -0
- package/dist/src/chats/analysis-shape.js +235 -0
- package/dist/src/chats/analysis.d.ts +3 -0
- package/dist/src/chats/analysis.js +42 -0
- package/dist/src/chats/corpus-input.d.ts +5 -0
- package/dist/src/chats/corpus-input.js +288 -0
- package/dist/src/chats/destination.js +3 -1
- package/dist/src/chats/import.d.ts +8 -6
- package/dist/src/chats/import.js +195 -23
- package/dist/src/chats/input-file.d.ts +3 -0
- package/dist/src/chats/input-file.js +32 -0
- package/dist/src/chats/normalize.d.ts +11 -2
- package/dist/src/chats/normalize.js +18 -2
- package/dist/src/chats/previous-import.d.ts +5 -0
- package/dist/src/chats/previous-import.js +34 -0
- package/dist/src/chats/reader.d.ts +7 -1
- package/dist/src/chats/reader.js +46 -15
- package/dist/src/chats/redact-import.d.ts +14 -0
- package/dist/src/chats/redact-import.js +141 -0
- package/dist/src/chats/redaction.d.ts +41 -0
- package/dist/src/chats/redaction.js +161 -0
- package/dist/src/chats/reference-mapping.d.ts +16 -0
- package/dist/src/chats/reference-mapping.js +153 -0
- package/dist/src/chats/revision-input.d.ts +3 -0
- package/dist/src/chats/revision-input.js +180 -0
- package/dist/src/chats/revision.d.ts +48 -0
- package/dist/src/chats/revision.js +94 -0
- package/dist/src/chats/selection.d.ts +25 -0
- package/dist/src/chats/selection.js +109 -0
- package/dist/src/chats/source.d.ts +23 -0
- package/dist/src/chats/source.js +135 -0
- package/dist/src/chats/structure.d.ts +57 -0
- package/dist/src/chats/structure.js +216 -0
- package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
- package/dist/src/chats/workflow-generation-projection.js +75 -0
- package/dist/src/chats/workflow-generation-source.d.ts +25 -0
- package/dist/src/chats/workflow-generation-source.js +124 -0
- package/dist/src/chats/workflow-generation-text.d.ts +23 -0
- package/dist/src/chats/workflow-generation-text.js +74 -0
- package/dist/src/chats/workflow-generation.d.ts +69 -0
- package/dist/src/chats/workflow-generation.js +142 -0
- package/dist/src/cli/arguments.d.ts +8 -0
- package/dist/src/cli/arguments.js +94 -13
- package/dist/src/cli/client.d.ts +1 -0
- package/dist/src/cli/client.js +38 -1
- package/dist/src/cli/commands.d.ts +3 -0
- package/dist/src/cli/commands.js +29 -11
- package/dist/src/cli/main.d.ts +2 -1
- package/dist/src/cli/main.js +61 -14
- package/dist/src/cli/render.d.ts +3 -0
- package/dist/src/cli/render.js +122 -7
- package/dist/src/cli/usage-render.d.ts +3 -0
- package/dist/src/cli/usage-render.js +28 -0
- package/dist/src/cli/usage.d.ts +3 -2
- package/dist/src/cli/usage.js +42 -7
- package/dist/src/cli/watch-actions.d.ts +10 -0
- package/dist/src/cli/watch-actions.js +58 -0
- package/dist/src/cli/watch-board-view.d.ts +44 -1
- package/dist/src/cli/watch-board-view.js +137 -18
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +48 -11
- package/dist/src/cli/watch-card-client.d.ts +1 -0
- package/dist/src/cli/watch-card-client.js +44 -0
- package/dist/src/cli/watch-card-forms.d.ts +2 -0
- package/dist/src/cli/watch-card-forms.js +33 -0
- package/dist/src/cli/watch-card-reply.d.ts +5 -0
- package/dist/src/cli/watch-card-reply.js +34 -0
- package/dist/src/cli/watch-card-view.d.ts +40 -0
- package/dist/src/cli/watch-card-view.js +86 -0
- package/dist/src/cli/watch-client.d.ts +1 -0
- package/dist/src/cli/watch-client.js +122 -0
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +61 -67
- package/dist/src/cli/watch-page.d.ts +1 -1
- package/dist/src/cli/watch-page.js +89 -86
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-pr-board.d.ts +3 -0
- package/dist/src/cli/watch-pr-board.js +26 -0
- package/dist/src/cli/watch-pr-command.d.ts +18 -0
- package/dist/src/cli/watch-pr-command.js +74 -0
- package/dist/src/cli/watch-pr-identity.d.ts +32 -0
- package/dist/src/cli/watch-pr-identity.js +87 -0
- package/dist/src/cli/watch-pr-observer.d.ts +20 -0
- package/dist/src/cli/watch-pr-observer.js +150 -0
- package/dist/src/cli/watch-pr-settings.d.ts +8 -0
- package/dist/src/cli/watch-pr-settings.js +47 -0
- package/dist/src/cli/watch-pull-request.d.ts +10 -0
- package/dist/src/cli/watch-pull-request.js +130 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +44 -18
- package/dist/src/cli/watch-render.d.ts +29 -2
- package/dist/src/cli/watch-render.js +11 -3
- package/dist/src/cli/watch-run-input.d.ts +14 -0
- package/dist/src/cli/watch-run-input.js +49 -0
- package/dist/src/cli/watch-server.d.ts +3 -0
- package/dist/src/cli/watch-server.js +77 -8
- package/dist/src/cli/watch-start-view.d.ts +18 -0
- package/dist/src/cli/watch-start-view.js +77 -0
- package/dist/src/cli/watch-turn.d.ts +1 -0
- package/dist/src/cli/watch-turn.js +8 -3
- package/dist/src/cli/watch-usage.d.ts +6 -0
- package/dist/src/cli/watch-usage.js +65 -0
- package/dist/src/cli/watch-view.d.ts +22 -0
- package/dist/src/cli/watch-view.js +111 -19
- package/dist/src/cli.js +48 -6
- package/dist/src/daemon/analysis-launch.d.ts +5 -0
- package/dist/src/daemon/analysis-launch.js +81 -0
- package/dist/src/daemon/analysis-token.d.ts +6 -0
- package/dist/src/daemon/analysis-token.js +88 -0
- package/dist/src/daemon/analysis-worker.d.ts +20 -0
- package/dist/src/daemon/analysis-worker.js +271 -0
- package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
- package/dist/src/daemon/chats-batch-binding.js +99 -0
- package/dist/src/daemon/chats-capture.d.ts +18 -0
- package/dist/src/daemon/chats-capture.js +194 -0
- package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
- package/dist/src/daemon/chats-checkpoints.js +186 -0
- package/dist/src/daemon/chats-files.d.ts +42 -0
- package/dist/src/daemon/chats-files.js +252 -0
- package/dist/src/daemon/chats-follow-up.d.ts +38 -0
- package/dist/src/daemon/chats-follow-up.js +180 -0
- package/dist/src/daemon/chats-question.d.ts +15 -0
- package/dist/src/daemon/chats-question.js +93 -0
- package/dist/src/daemon/chats-worker.d.ts +13 -0
- package/dist/src/daemon/chats-worker.js +201 -0
- package/dist/src/daemon/claude-turn.d.ts +11 -0
- package/dist/src/daemon/claude-turn.js +96 -15
- package/dist/src/daemon/claude-worker.d.ts +2 -1
- package/dist/src/daemon/claude-worker.js +92 -45
- package/dist/src/daemon/codex-stream.js +16 -0
- package/dist/src/daemon/daemon.d.ts +8 -1
- package/dist/src/daemon/daemon.js +51 -14
- package/dist/src/daemon/delivery-router.js +6 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +170 -0
- package/dist/src/daemon/github-merge-call.d.ts +14 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +52 -95
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +84 -16
- package/dist/src/daemon/github-operation.d.ts +2 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +49 -0
- package/dist/src/daemon/github-ticket-status.d.ts +3 -0
- package/dist/src/daemon/github-ticket-status.js +294 -0
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +2 -0
- package/dist/src/daemon/issue-queue.js +186 -43
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +176 -37
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/operation-journal.js +4 -2
- package/dist/src/daemon/retrying-turn.d.ts +12 -0
- package/dist/src/daemon/retrying-turn.js +231 -0
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +8 -4
- package/dist/src/daemon/ticket-registration.js +76 -22
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/execution-pin.d.ts +4 -0
- package/dist/src/execution-pin.js +23 -2
- package/dist/src/host/start-delivery.d.ts +2 -1
- package/dist/src/host/start-delivery.js +1 -1
- package/dist/src/host/step-bridge-host.d.ts +1 -0
- package/dist/src/host/step-bridge-host.js +16 -0
- package/dist/src/host/world-reconciliation.d.ts +3 -0
- package/dist/src/host/world-reconciliation.js +9 -0
- package/dist/src/host/world-runs.js +14 -1
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +8 -2
- package/dist/src/service/registry.js +20 -8
- package/dist/src/service/replies.d.ts +2 -0
- package/dist/src/service/run-handlers.js +54 -30
- package/dist/src/service/service.d.ts +3 -0
- package/dist/src/service/service.js +3 -1
- package/dist/src/service/submission-handler.d.ts +1 -0
- package/dist/src/service/submission-handler.js +92 -13
- package/dist/src/service/worker-totals.d.ts +3 -0
- package/dist/src/service/worker-totals.js +52 -0
- package/dist/src/step-bridge.d.ts +9 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status.d.ts +8 -0
- package/dist/src/ticket-status.js +48 -0
- package/dist/src/token-prices.d.ts +21 -0
- package/dist/src/token-prices.js +108 -0
- package/dist/src/worker-usage.d.ts +31 -0
- package/dist/src/worker-usage.js +44 -0
- package/dist/src/workflow-admission.d.ts +1 -0
- package/dist/src/workflow-candidate.d.ts +29 -0
- package/dist/src/workflow-candidate.js +60 -0
- package/dist/src/workflow-contracts.d.ts +4 -0
- package/dist/src/workflow-contracts.js +32 -5
- package/dist/src/workflow-declaration.d.ts +12 -1
- package/dist/src/workflow-declaration.js +71 -13
- package/dist/src/workflow-discovery.d.ts +18 -0
- package/dist/src/workflow-discovery.js +33 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +39 -0
- package/dist/src/workflows.d.ts +3 -1
- package/dist/src/workflows.js +2 -0
- package/dist/tools/admit-workflow.d.ts +13 -0
- package/dist/tools/admit-workflow.js +94 -0
- package/dist/tools/self-host-processes.js +7 -2
- package/dist/tools/self-host-project.d.ts +17 -0
- package/dist/tools/self-host-project.js +177 -0
- package/dist/tools/self-host-workflows.d.ts +1 -1
- package/dist/tools/self-host-workflows.js +2 -2
- package/dist/tools/self-host.js +30 -84
- package/dist/tools/self-update-host.d.ts +7 -0
- package/dist/tools/self-update-host.js +48 -1
- package/dist/tools/self-update.d.ts +1 -0
- package/dist/tools/self-update.js +85 -13
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +8 -0
- package/dist/tools/workflow-candidate-counts.js +251 -0
- package/dist/tools/workflow-candidate-program.d.ts +11 -0
- package/dist/tools/workflow-candidate-program.js +78 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +107 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
- package/dist/tools/workflow-candidate-syntax.js +219 -0
- package/dist/tools/workflow-folder-capture.d.ts +17 -0
- package/dist/tools/workflow-folder-capture.js +216 -0
- package/dist/tools/workflow-folders.d.ts +33 -0
- package/dist/tools/workflow-folders.js +150 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/workflows/analyze-chats.d.ts +15 -0
- package/dist/workflows/analyze-chats.declaration.json +22 -0
- package/dist/workflows/analyze-chats.js +250 -0
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.d.ts +1 -7
- package/dist/workflows/develop-ticket.js +47 -141
- package/dist/workflows/issue-labels.d.ts +3 -1
- package/dist/workflows/issue-labels.js +34 -0
- package/dist/workflows/ticket-prompts.d.ts +35 -0
- package/dist/workflows/ticket-prompts.js +143 -0
- package/package.json +25 -5
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { record } from "../protocol.js";
|
|
5
|
+
import { openInput } from "../chats/input-file.js";
|
|
6
|
+
import { createPrivateFile, validateDestination, } from "../chats/destination.js";
|
|
7
|
+
import { analysisDocumentBytes, } from "../chats/analysis-input.js";
|
|
8
|
+
import { analysisCaptureBytes } from "../chats/analysis-batches.js";
|
|
9
|
+
export async function readAnalysisCorpus(path, options = {}) {
|
|
10
|
+
const handle = await openInput(path);
|
|
11
|
+
let output;
|
|
12
|
+
try {
|
|
13
|
+
if (options.permissions !== undefined &&
|
|
14
|
+
((await handle.stat()).mode & 0o777) !== options.permissions)
|
|
15
|
+
throw new Error("analysis-output-failed");
|
|
16
|
+
if (options.output !== undefined)
|
|
17
|
+
output = await createPrivateFile(options.output);
|
|
18
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
19
|
+
const buffer = Buffer.alloc(65_536);
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let size = 0;
|
|
22
|
+
for (;;) {
|
|
23
|
+
const { bytesRead } = await handle.read({
|
|
24
|
+
buffer,
|
|
25
|
+
length: Math.min(buffer.length, analysisCaptureBytes + 1 - size),
|
|
26
|
+
});
|
|
27
|
+
if (bytesRead === 0)
|
|
28
|
+
break;
|
|
29
|
+
size += bytesRead;
|
|
30
|
+
if (size > analysisCaptureBytes)
|
|
31
|
+
throw new Error("analysis-too-large");
|
|
32
|
+
if (output !== undefined)
|
|
33
|
+
await output.writeFile(buffer.subarray(0, bytesRead));
|
|
34
|
+
chunks.push(decoder.decode(buffer.subarray(0, bytesRead), { stream: true }));
|
|
35
|
+
}
|
|
36
|
+
chunks.push(decoder.decode());
|
|
37
|
+
return chunks.join("");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
try {
|
|
41
|
+
await handle.close();
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await output?.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function analysisDirectory(runtime, importId) {
|
|
49
|
+
if (process.platform !== "darwin")
|
|
50
|
+
throw new Error("platform-unsupported");
|
|
51
|
+
const directory = resolve(runtime, "chats", importId);
|
|
52
|
+
await checkedDirectory(directory);
|
|
53
|
+
return directory;
|
|
54
|
+
}
|
|
55
|
+
async function checkedDirectory(directory) {
|
|
56
|
+
try {
|
|
57
|
+
if (!(await lstat(directory)).isDirectory())
|
|
58
|
+
throw new Error("analysis-input-refused");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (Reflect.get(Object(error), "code") !== "ENOENT")
|
|
62
|
+
throw error;
|
|
63
|
+
const parent = dirname(directory);
|
|
64
|
+
if (parent === directory)
|
|
65
|
+
throw error;
|
|
66
|
+
await checkedDirectory(parent);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
await validateDestination(directory);
|
|
70
|
+
}
|
|
71
|
+
export async function boundedAnalysisFile(path, options = {}) {
|
|
72
|
+
const handle = await openInput(path);
|
|
73
|
+
try {
|
|
74
|
+
if (options.permissions !== undefined &&
|
|
75
|
+
((await handle.stat()).mode & 0o777) !== options.permissions)
|
|
76
|
+
throw new Error("analysis-output-failed");
|
|
77
|
+
const buffer = Buffer.alloc(analysisDocumentBytes + 1);
|
|
78
|
+
let size = 0;
|
|
79
|
+
while (size < buffer.length) {
|
|
80
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, null);
|
|
81
|
+
if (bytesRead === 0)
|
|
82
|
+
break;
|
|
83
|
+
size += bytesRead;
|
|
84
|
+
}
|
|
85
|
+
if (size > analysisDocumentBytes)
|
|
86
|
+
throw new Error("analysis-too-large");
|
|
87
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(buffer.subarray(0, size));
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await handle.close();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function readAnalysisSnapshot(directory, input) {
|
|
94
|
+
const { importId, ...options } = input;
|
|
95
|
+
const required = async (name) => {
|
|
96
|
+
try {
|
|
97
|
+
return name === "redacted.jsonl"
|
|
98
|
+
? await readAnalysisCorpus(join(directory, name), options)
|
|
99
|
+
: await boundedAnalysisFile(join(directory, name), options);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (Reflect.get(Object(error), "code") === "ENOENT")
|
|
103
|
+
throw new Error("redaction-required", { cause: error });
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
importId,
|
|
109
|
+
redaction: await required("redaction.json"),
|
|
110
|
+
corpus: await required("redacted.jsonl"),
|
|
111
|
+
structure: await boundedAnalysisFile(join(directory, "structure.json"), options),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export async function matchingPrivateFile(path, text) {
|
|
115
|
+
try {
|
|
116
|
+
const existing = await boundedAnalysisFile(path, { permissions: 0o600 });
|
|
117
|
+
if (existing !== text)
|
|
118
|
+
throw new Error("analysis-output-failed");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (Reflect.get(Object(error), "code") !== "ENOENT")
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
const handle = await createPrivateFile(path);
|
|
126
|
+
try {
|
|
127
|
+
await handle.writeFile(text);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await handle.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function requireAbsent(directory, names) {
|
|
134
|
+
for (const name of names) {
|
|
135
|
+
const exists = await lstat(join(directory, name)).catch((error) => {
|
|
136
|
+
if (error instanceof Error &&
|
|
137
|
+
"code" in error &&
|
|
138
|
+
error.code === "ENOENT")
|
|
139
|
+
return undefined;
|
|
140
|
+
throw error;
|
|
141
|
+
});
|
|
142
|
+
if (exists !== undefined)
|
|
143
|
+
throw new Error("analysis-output-failed");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function outputClaim(directory, output) {
|
|
147
|
+
return {
|
|
148
|
+
path: join(directory, `${"draft" in output ? "final-claim" : "claim"}-${output.cycleId}.json`),
|
|
149
|
+
text: JSON.stringify({
|
|
150
|
+
schemaVersion: 1,
|
|
151
|
+
kind: "reply" in output ? "draft" : "final",
|
|
152
|
+
digest: createHash("sha256")
|
|
153
|
+
.update(JSON.stringify(output))
|
|
154
|
+
.digest("hex"),
|
|
155
|
+
}) + "\n",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function inspectOutputClaim(claim) {
|
|
159
|
+
const text = await boundedAnalysisFile(claim.path, {
|
|
160
|
+
permissions: 0o600,
|
|
161
|
+
}).catch((error) => {
|
|
162
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
163
|
+
return undefined;
|
|
164
|
+
throw error;
|
|
165
|
+
});
|
|
166
|
+
if (text !== undefined && text !== claim.text)
|
|
167
|
+
throw new Error("analysis-output-failed");
|
|
168
|
+
}
|
|
169
|
+
export async function inspectAnalysisFiles(directory, output) {
|
|
170
|
+
if (Buffer.byteLength(JSON.stringify(output)) > analysisDocumentBytes)
|
|
171
|
+
throw new Error("analysis-too-large");
|
|
172
|
+
await inspectOutputClaim(outputClaim(directory, output));
|
|
173
|
+
if (output.draft === undefined)
|
|
174
|
+
await requireAbsent(directory, [`draft-${output.cycleId}.json`]);
|
|
175
|
+
else if ((await inspectAnalysisDraftRecord(directory, output.draft)) === undefined)
|
|
176
|
+
throw new Error("analysis-output-failed");
|
|
177
|
+
const proposal = join(directory, `proposal-${output.cycleId}.txt`);
|
|
178
|
+
const analysis = join(directory, `analysis-${output.cycleId}.json`);
|
|
179
|
+
const marker = await boundedAnalysisFile(analysis, {
|
|
180
|
+
permissions: 0o600,
|
|
181
|
+
}).catch((error) => {
|
|
182
|
+
if (Reflect.get(Object(error), "code") === "ENOENT")
|
|
183
|
+
return undefined;
|
|
184
|
+
throw error;
|
|
185
|
+
});
|
|
186
|
+
if (marker === undefined)
|
|
187
|
+
return "absent";
|
|
188
|
+
if (marker !== output.analysis ||
|
|
189
|
+
(await boundedAnalysisFile(proposal, { permissions: 0o600 })) !==
|
|
190
|
+
output.proposal)
|
|
191
|
+
throw new Error("analysis-output-failed");
|
|
192
|
+
return "complete";
|
|
193
|
+
}
|
|
194
|
+
export async function saveAnalysisFiles(directory, output) {
|
|
195
|
+
if ((await inspectAnalysisFiles(directory, output)) === "complete")
|
|
196
|
+
return;
|
|
197
|
+
const claim = outputClaim(directory, output);
|
|
198
|
+
await matchingPrivateFile(claim.path, claim.text);
|
|
199
|
+
const proposal = join(directory, `proposal-${output.cycleId}.txt`);
|
|
200
|
+
const analysis = join(directory, `analysis-${output.cycleId}.json`);
|
|
201
|
+
await matchingPrivateFile(proposal, output.proposal);
|
|
202
|
+
await matchingPrivateFile(analysis, output.analysis);
|
|
203
|
+
}
|
|
204
|
+
function draftRecord(input, expiresAt) {
|
|
205
|
+
return {
|
|
206
|
+
schemaVersion: 1,
|
|
207
|
+
...input,
|
|
208
|
+
candidateDigest: createHash("sha256").update(input.reply).digest("hex"),
|
|
209
|
+
expiresAt,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export async function inspectAnalysisDraft(directory, input) {
|
|
213
|
+
await requireAbsent(directory, [
|
|
214
|
+
`analysis-${input.cycleId}.json`,
|
|
215
|
+
`proposal-${input.cycleId}.txt`,
|
|
216
|
+
]);
|
|
217
|
+
return inspectAnalysisDraftRecord(directory, input);
|
|
218
|
+
}
|
|
219
|
+
export async function inspectAnalysisDraftRecord(directory, input) {
|
|
220
|
+
await inspectOutputClaim(outputClaim(directory, input));
|
|
221
|
+
const text = await boundedAnalysisFile(join(directory, `draft-${input.cycleId}.json`), { permissions: 0o600 }).catch((error) => {
|
|
222
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
223
|
+
return undefined;
|
|
224
|
+
throw error;
|
|
225
|
+
});
|
|
226
|
+
if (text === undefined)
|
|
227
|
+
return undefined;
|
|
228
|
+
const expiresAt = record(JSON.parse(text))?.["expiresAt"];
|
|
229
|
+
if (typeof expiresAt !== "string" ||
|
|
230
|
+
!Number.isFinite(Date.parse(expiresAt)) ||
|
|
231
|
+
new Date(expiresAt).toISOString() !== expiresAt)
|
|
232
|
+
throw new Error("analysis-output-failed");
|
|
233
|
+
const expected = draftRecord(input, expiresAt);
|
|
234
|
+
if (text !== JSON.stringify(expected) + "\n")
|
|
235
|
+
throw new Error("analysis-output-failed");
|
|
236
|
+
return { candidateDigest: expected.candidateDigest, expiresAt };
|
|
237
|
+
}
|
|
238
|
+
export async function saveAnalysisDraft(directory, saving) {
|
|
239
|
+
const { input, now } = saving;
|
|
240
|
+
const existing = await inspectAnalysisDraft(directory, input);
|
|
241
|
+
if (existing !== undefined)
|
|
242
|
+
return existing;
|
|
243
|
+
const claim = outputClaim(directory, input);
|
|
244
|
+
await matchingPrivateFile(claim.path, claim.text);
|
|
245
|
+
const expiresAt = new Date(Date.parse(now()) + 86_400_000).toISOString();
|
|
246
|
+
const draft = draftRecord(input, expiresAt);
|
|
247
|
+
const text = JSON.stringify(draft) + "\n";
|
|
248
|
+
if (Buffer.byteLength(text) > analysisDocumentBytes)
|
|
249
|
+
throw new Error("analysis-output-failed");
|
|
250
|
+
await matchingPrivateFile(join(directory, `draft-${input.cycleId}.json`), text);
|
|
251
|
+
return { candidateDigest: draft.candidateDigest, expiresAt };
|
|
252
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AdapterOutcome, DeliveryIntent } from "../broker/actor/delivery-intents.ts";
|
|
2
|
+
import { type Fields } from "../protocol.ts";
|
|
3
|
+
import { type AnalysisSnapshot } from "../chats/analysis-input.ts";
|
|
4
|
+
import type { AnalysisQuestion } from "../chats/analysis-contract.ts";
|
|
5
|
+
import { analysisBatchData } from "../chats/analysis-batches.ts";
|
|
6
|
+
import { type AnalysisDraftInput } from "./chats-files.ts";
|
|
7
|
+
export type ChatsResult = ReturnType<typeof analysisBatchData> | ({
|
|
8
|
+
readonly kind: "analysis_batch";
|
|
9
|
+
} & ReturnType<typeof analysisBatchData>) | {
|
|
10
|
+
readonly kind: "analysis_partial";
|
|
11
|
+
readonly reason: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "analysis_draft";
|
|
14
|
+
readonly cycleId: string;
|
|
15
|
+
readonly candidateDigest: string;
|
|
16
|
+
readonly expiresAt: string;
|
|
17
|
+
readonly question: AnalysisQuestion;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "handoff_requested";
|
|
20
|
+
readonly cycleId: string;
|
|
21
|
+
readonly promotionId: string;
|
|
22
|
+
};
|
|
23
|
+
interface FollowUpSave {
|
|
24
|
+
readonly intent: DeliveryIntent;
|
|
25
|
+
readonly payload: Fields;
|
|
26
|
+
readonly mode: "deliver" | "reconcile";
|
|
27
|
+
readonly input: {
|
|
28
|
+
readonly cycleId: string;
|
|
29
|
+
readonly snapshot: AnalysisSnapshot;
|
|
30
|
+
readonly candidate: unknown;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare function storedAnalysisDraft(directory: string, call: FollowUpSave): Promise<AnalysisDraftInput | undefined>;
|
|
34
|
+
export declare function saveFollowUp(runtime: string, call: FollowUpSave): Promise<AdapterOutcome>;
|
|
35
|
+
export declare function saveAcceptedAnalysis(runtime: string, call: FollowUpSave & {
|
|
36
|
+
readonly draft?: AnalysisDraftInput;
|
|
37
|
+
}): Promise<AdapterOutcome>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { record } from "../protocol.js";
|
|
4
|
+
import { analysisFollowUp } from "../chats/analysis-prompt.js";
|
|
5
|
+
import { analysisEvidence, } from "../chats/analysis-input.js";
|
|
6
|
+
import { validateAnalysis } from "../chats/analysis.js";
|
|
7
|
+
import { analysisProposal } from "../chats/analysis-proposal.js";
|
|
8
|
+
import { analysisBatchData } from "../chats/analysis-batches.js";
|
|
9
|
+
import { finalizeAnalysis } from "./chats-checkpoints.js";
|
|
10
|
+
import { operationKey } from "./operation-journal.js";
|
|
11
|
+
import { analysisDirectory, boundedAnalysisFile, inspectAnalysisDraftRecord, inspectAnalysisFiles, saveAnalysisFiles, } from "./chats-files.js";
|
|
12
|
+
function storedDraftIdentity(stored, call) {
|
|
13
|
+
return (stored?.["runId"] === call.intent.runId &&
|
|
14
|
+
stored["cycleId"] === call.input.cycleId &&
|
|
15
|
+
typeof stored["operationId"] === "string" &&
|
|
16
|
+
typeof stored["inputDigest"] === "string" &&
|
|
17
|
+
typeof stored["reply"] === "string" &&
|
|
18
|
+
stored["snapshotDigest"] ===
|
|
19
|
+
createHash("sha256")
|
|
20
|
+
.update(JSON.stringify(call.input.snapshot))
|
|
21
|
+
.digest("hex"));
|
|
22
|
+
}
|
|
23
|
+
export async function storedAnalysisDraft(directory, call) {
|
|
24
|
+
const { cycleId } = call.input;
|
|
25
|
+
const text = await boundedAnalysisFile(join(directory, `draft-${cycleId}.json`), { permissions: 0o600 }).catch((error) => {
|
|
26
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
27
|
+
return undefined;
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
if (text === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
const stored = record(JSON.parse(text));
|
|
33
|
+
if (!storedDraftIdentity(stored, call))
|
|
34
|
+
throw new Error("analysis-follow-up-refused");
|
|
35
|
+
const draft = {
|
|
36
|
+
runId: call.intent.runId,
|
|
37
|
+
cycleId,
|
|
38
|
+
operationId: stored["operationId"],
|
|
39
|
+
inputDigest: stored["inputDigest"],
|
|
40
|
+
snapshotDigest: stored["snapshotDigest"],
|
|
41
|
+
reply: stored["reply"],
|
|
42
|
+
...("batch" in stored ? { batch: stored["batch"] } : {}),
|
|
43
|
+
};
|
|
44
|
+
if ((await inspectAnalysisDraftRecord(directory, draft)) === undefined)
|
|
45
|
+
throw new Error("analysis-follow-up-refused");
|
|
46
|
+
return draft;
|
|
47
|
+
}
|
|
48
|
+
async function boundDraft(directory, call) {
|
|
49
|
+
const followUp = analysisFollowUp(call.payload["followUp"]);
|
|
50
|
+
const draft = await storedAnalysisDraft(directory, call);
|
|
51
|
+
if (draft === undefined ||
|
|
52
|
+
JSON.stringify(draft.batch) !== JSON.stringify(call.payload["batch"]) ||
|
|
53
|
+
JSON.stringify(JSON.parse(draft.reply)) !== JSON.stringify(followUp.draft))
|
|
54
|
+
throw new Error("analysis-follow-up-refused");
|
|
55
|
+
return draft;
|
|
56
|
+
}
|
|
57
|
+
function refused(reason, codes = [reason]) {
|
|
58
|
+
return {
|
|
59
|
+
kind: "failed",
|
|
60
|
+
reason,
|
|
61
|
+
result: JSON.stringify({ kind: "blocked", codes }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function saveFollowUp(runtime, call) {
|
|
65
|
+
const { snapshot, candidate } = call.input;
|
|
66
|
+
try {
|
|
67
|
+
if ("batch" in call.payload &&
|
|
68
|
+
Object.hasOwn(record(candidate) ?? {}, "openQuestion"))
|
|
69
|
+
return refused("analysis-question-limit");
|
|
70
|
+
if (!("batch" in call.payload))
|
|
71
|
+
return await saveSnapshotFollowUp(runtime, call);
|
|
72
|
+
const directory = await analysisDirectory(runtime, snapshot.importId);
|
|
73
|
+
const draft = await boundDraft(directory, call);
|
|
74
|
+
return await saveAcceptedAnalysis(runtime, { ...call, draft });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return refused("analysis-follow-up-refused");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function saveSnapshotFollowUp(runtime, call) {
|
|
81
|
+
const { snapshot, cycleId, candidate } = call.input;
|
|
82
|
+
const validation = validateAnalysis(candidate, analysisEvidence(snapshot));
|
|
83
|
+
if (!validation.accepted)
|
|
84
|
+
return refused("analysis-follow-up-refused", validation.codes);
|
|
85
|
+
if (validation.result.openQuestion !== undefined)
|
|
86
|
+
return refused("analysis-question-limit");
|
|
87
|
+
const directory = await analysisDirectory(runtime, snapshot.importId);
|
|
88
|
+
const draft = await boundDraft(directory, call);
|
|
89
|
+
const output = {
|
|
90
|
+
cycleId,
|
|
91
|
+
draft,
|
|
92
|
+
analysis: JSON.stringify(validation.result) + "\n",
|
|
93
|
+
proposal: analysisProposal(validation.result),
|
|
94
|
+
};
|
|
95
|
+
if (call.mode === "reconcile" &&
|
|
96
|
+
(await inspectAnalysisFiles(directory, output)) === "absent")
|
|
97
|
+
return { kind: "absent" };
|
|
98
|
+
if (call.mode === "deliver")
|
|
99
|
+
await saveAnalysisFiles(directory, output);
|
|
100
|
+
return {
|
|
101
|
+
kind: "completed",
|
|
102
|
+
worker: "chats",
|
|
103
|
+
receipt: operationKey(call.intent.operationId),
|
|
104
|
+
result: JSON.stringify({
|
|
105
|
+
kind: "handoff_requested",
|
|
106
|
+
cycleId,
|
|
107
|
+
promotionId: `analysis:${snapshot.importId}:${cycleId}`,
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function acceptedDraft(directory, call) {
|
|
112
|
+
const draft = await storedAnalysisDraft(directory, call);
|
|
113
|
+
if (draft === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
const batch = record(call.payload["batch"]);
|
|
116
|
+
const prior = record(draft.batch);
|
|
117
|
+
if (prior === undefined ||
|
|
118
|
+
batch === undefined ||
|
|
119
|
+
Number(batch["batchIndex"]) === Number(prior["batchIndex"]))
|
|
120
|
+
throw new Error("analysis-follow-up-refused");
|
|
121
|
+
if (Number(batch["batchIndex"]) < Number(prior["batchIndex"]))
|
|
122
|
+
return undefined;
|
|
123
|
+
return draft;
|
|
124
|
+
}
|
|
125
|
+
export async function saveAcceptedAnalysis(runtime, call) {
|
|
126
|
+
const { snapshot, cycleId, candidate } = call.input;
|
|
127
|
+
const { importId } = snapshot;
|
|
128
|
+
try {
|
|
129
|
+
const directory = await analysisDirectory(runtime, importId);
|
|
130
|
+
const draft = call.draft ??
|
|
131
|
+
("batch" in call.payload
|
|
132
|
+
? await acceptedDraft(directory, call)
|
|
133
|
+
: undefined);
|
|
134
|
+
const saved = await finalizeAnalysis(directory, {
|
|
135
|
+
runId: call.intent.runId,
|
|
136
|
+
importId,
|
|
137
|
+
cycleId,
|
|
138
|
+
snapshot: "snapshot" in call.payload ? snapshot : undefined,
|
|
139
|
+
candidate,
|
|
140
|
+
mode: call.mode,
|
|
141
|
+
batch: call.payload["batch"],
|
|
142
|
+
...(draft === undefined ? {} : { draft }),
|
|
143
|
+
});
|
|
144
|
+
return savedOutcome(call, saved);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new Error("analysis-output-failed");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function savedOutcome(call, saved) {
|
|
151
|
+
const { cycleId, snapshot } = call.input;
|
|
152
|
+
const { importId } = snapshot;
|
|
153
|
+
if (saved.kind === "absent")
|
|
154
|
+
return { kind: "absent" };
|
|
155
|
+
if (saved.kind === "refused")
|
|
156
|
+
return refused(call.draft !== undefined &&
|
|
157
|
+
!saved.codes.includes("analysis-merge-conflict")
|
|
158
|
+
? "analysis-follow-up-refused"
|
|
159
|
+
: (saved.codes[0] ?? "analysis-input-refused"), saved.codes);
|
|
160
|
+
let result = {
|
|
161
|
+
kind: "handoff_requested",
|
|
162
|
+
cycleId,
|
|
163
|
+
promotionId: `analysis:${importId}:${cycleId}`,
|
|
164
|
+
};
|
|
165
|
+
if (saved.next?.kind === "refused")
|
|
166
|
+
result = { kind: "analysis_partial", reason: saved.next.code };
|
|
167
|
+
if (saved.next?.kind === "batch")
|
|
168
|
+
result = {
|
|
169
|
+
kind: "analysis_batch",
|
|
170
|
+
...analysisBatchData(saved.next, {
|
|
171
|
+
digest: saved.checkpoint.capturedDigest,
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
kind: "completed",
|
|
176
|
+
worker: "chats",
|
|
177
|
+
receipt: operationKey(call.intent.operationId),
|
|
178
|
+
result: JSON.stringify(result),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AdapterOutcome, DeliveryIntent } from "../broker/actor/delivery-intents.ts";
|
|
2
|
+
import type { Fields } from "../protocol.ts";
|
|
3
|
+
import { type analysisSaveInput } from "../chats/analysis-input.ts";
|
|
4
|
+
interface QuestionSettings {
|
|
5
|
+
readonly runtimeDirectory: string;
|
|
6
|
+
readonly now?: () => string;
|
|
7
|
+
}
|
|
8
|
+
interface QuestionSave {
|
|
9
|
+
readonly intent: DeliveryIntent;
|
|
10
|
+
readonly mode: "deliver" | "reconcile";
|
|
11
|
+
readonly input: ReturnType<typeof analysisSaveInput>;
|
|
12
|
+
readonly payload: Fields;
|
|
13
|
+
}
|
|
14
|
+
export declare function saveQuestion(settings: QuestionSettings, call: QuestionSave): Promise<AdapterOutcome>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { validateAnalysis } from "../chats/analysis.js";
|
|
3
|
+
import { analysisEvidence, } from "../chats/analysis-input.js";
|
|
4
|
+
import { mergeAnalysisBatch } from "../chats/analysis-merge.js";
|
|
5
|
+
import { boundAnalysisBatch } from "./chats-batch-binding.js";
|
|
6
|
+
import { analysisDirectory, inspectAnalysisDraft, saveAnalysisDraft, } from "./chats-files.js";
|
|
7
|
+
import { operationKey } from "./operation-journal.js";
|
|
8
|
+
import { storedAnalysisDraft } from "./chats-follow-up.js";
|
|
9
|
+
async function validateQuestion(directory, call) {
|
|
10
|
+
const { input, payload, intent } = call;
|
|
11
|
+
if (!("batch" in payload))
|
|
12
|
+
return validateAnalysis(input.candidate, analysisEvidence(input.snapshot));
|
|
13
|
+
const bound = await boundAnalysisBatch(directory, {
|
|
14
|
+
runId: intent.runId,
|
|
15
|
+
importId: input.snapshot.importId,
|
|
16
|
+
snapshot: "snapshot" in payload ? input.snapshot : undefined,
|
|
17
|
+
batch: payload["batch"],
|
|
18
|
+
});
|
|
19
|
+
return mergeAnalysisBatch({
|
|
20
|
+
previousResult: bound.previous.result,
|
|
21
|
+
candidate: input.candidate,
|
|
22
|
+
evidence: analysisEvidence(bound.snapshot),
|
|
23
|
+
nextRecordIndex: bound.batch.nextRecordIndex,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function questionDraft(call) {
|
|
27
|
+
const { cycleId, snapshot, reply } = call.input;
|
|
28
|
+
return {
|
|
29
|
+
runId: call.intent.runId,
|
|
30
|
+
cycleId,
|
|
31
|
+
operationId: call.intent.operationId,
|
|
32
|
+
inputDigest: call.intent.inputDigest,
|
|
33
|
+
snapshotDigest: createHash("sha256")
|
|
34
|
+
.update(JSON.stringify(snapshot))
|
|
35
|
+
.digest("hex"),
|
|
36
|
+
reply,
|
|
37
|
+
...("batch" in call.payload ? { batch: call.payload["batch"] } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function additionalBatchQuestion(directory, call) {
|
|
41
|
+
const earlier = await storedAnalysisDraft(directory, call);
|
|
42
|
+
return (earlier !== undefined &&
|
|
43
|
+
"batch" in call.payload &&
|
|
44
|
+
earlier.operationId !== call.intent.operationId);
|
|
45
|
+
}
|
|
46
|
+
export async function saveQuestion(settings, call) {
|
|
47
|
+
try {
|
|
48
|
+
const { cycleId, snapshot } = call.input;
|
|
49
|
+
const directory = await analysisDirectory(settings.runtimeDirectory, snapshot.importId);
|
|
50
|
+
if (await additionalBatchQuestion(directory, call))
|
|
51
|
+
return {
|
|
52
|
+
kind: "failed",
|
|
53
|
+
reason: "analysis-question-limit",
|
|
54
|
+
result: JSON.stringify({
|
|
55
|
+
kind: "blocked",
|
|
56
|
+
codes: ["analysis-question-limit"],
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
const validation = await validateQuestion(directory, call);
|
|
60
|
+
if (!validation.accepted)
|
|
61
|
+
return {
|
|
62
|
+
kind: "failed",
|
|
63
|
+
reason: validation.codes[0] ?? "analysis-input-refused",
|
|
64
|
+
result: JSON.stringify({ kind: "blocked", codes: validation.codes }),
|
|
65
|
+
};
|
|
66
|
+
const question = validation.result.openQuestion;
|
|
67
|
+
if (question === undefined)
|
|
68
|
+
throw new Error("analysis-input-refused");
|
|
69
|
+
const draft = questionDraft(call);
|
|
70
|
+
const metadata = call.mode === "reconcile"
|
|
71
|
+
? await inspectAnalysisDraft(directory, draft)
|
|
72
|
+
: await saveAnalysisDraft(directory, {
|
|
73
|
+
input: draft,
|
|
74
|
+
now: settings.now ?? (() => new Date().toISOString()),
|
|
75
|
+
});
|
|
76
|
+
if (metadata === undefined)
|
|
77
|
+
return { kind: "absent" };
|
|
78
|
+
return {
|
|
79
|
+
kind: "completed",
|
|
80
|
+
worker: "chats",
|
|
81
|
+
receipt: operationKey(call.intent.operationId),
|
|
82
|
+
result: JSON.stringify({
|
|
83
|
+
kind: "analysis_draft",
|
|
84
|
+
cycleId,
|
|
85
|
+
...metadata,
|
|
86
|
+
question,
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new Error("analysis-output-failed");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AdapterOutcome, DeliveryIntent } from "../broker/actor/delivery-intents.ts";
|
|
2
|
+
import type { EffectDelivery } from "./delivery-router.ts";
|
|
3
|
+
export interface ChatsWorkerSettings {
|
|
4
|
+
readonly runtimeDirectory: string;
|
|
5
|
+
readonly document: (digest: string) => Promise<string | undefined>;
|
|
6
|
+
readonly workflowId: (runId: string) => Promise<string | undefined>;
|
|
7
|
+
readonly now?: () => string;
|
|
8
|
+
}
|
|
9
|
+
export interface ChatsWorker extends EffectDelivery {
|
|
10
|
+
readonly deliver: (intent: DeliveryIntent, signal: AbortSignal) => Promise<AdapterOutcome>;
|
|
11
|
+
readonly close: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function chatsWorker(settings: ChatsWorkerSettings): ChatsWorker;
|