toolroll 0.9.34 → 0.9.37
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 +102 -0
- package/README.md +7 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +2 -2
- package/dist/chat-actions.d.ts +18 -12
- package/dist/chat-actions.js +15 -65
- package/dist/chat-ask.js +5 -5
- package/dist/chat-decide.d.ts +14 -1
- package/dist/chat-decide.js +12 -6
- package/dist/chat-delivery-state.d.ts +12 -70
- package/dist/chat-delivery-state.js +17 -135
- package/dist/chat-delivery.js +58 -30
- package/dist/chat-flow.js +8 -6
- package/dist/chat-inbox.js +1 -1
- package/dist/coding-context.d.ts +2 -39
- package/dist/coding-context.js +2 -1
- package/dist/coding-handoff.d.ts +6 -25
- package/dist/coding-handoff.js +11 -5
- package/dist/coding-types.d.ts +2 -26
- package/dist/coding-workspace.d.ts +2 -0
- package/dist/coding-workspace.js +50 -14
- package/dist/contracts/acceptance-terms.d.ts +14 -0
- package/dist/contracts/acceptance-terms.js +16 -0
- package/dist/contracts/chat-actions.d.ts +328 -0
- package/dist/contracts/chat-actions.js +133 -0
- package/dist/contracts/chat-callback-rows.d.ts +59 -0
- package/dist/contracts/chat-callback-rows.js +46 -0
- package/dist/contracts/chat-content.d.ts +276 -0
- package/dist/contracts/chat-content.js +127 -0
- package/dist/contracts/chat-tables.d.ts +6 -0
- package/dist/contracts/chat-tables.js +136 -0
- package/dist/contracts/coding-context.d.ts +97 -0
- package/dist/contracts/coding-context.js +60 -0
- package/dist/contracts/coding-handoff.d.ts +44 -0
- package/dist/contracts/coding-handoff.js +47 -0
- package/dist/contracts/coding-workspace.d.ts +165 -0
- package/dist/contracts/coding-workspace.js +71 -0
- package/dist/contracts/contract.d.ts +40 -7
- package/dist/contracts/contract.js +155 -19
- package/dist/contracts/decision.d.ts +45 -0
- package/dist/contracts/decision.js +45 -0
- package/dist/contracts/discord-callback.d.ts +45 -0
- package/dist/contracts/discord-callback.js +34 -0
- package/dist/contracts/flow-card.d.ts +50 -0
- package/dist/contracts/flow-card.js +46 -0
- package/dist/contracts/flow-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +19405 -0
- package/dist/contracts/flow.js +364 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -0
- package/dist/contracts/lead-context.d.ts +165 -0
- package/dist/contracts/lead-context.js +73 -0
- package/dist/contracts/lead-tools.d.ts +1905 -0
- package/dist/contracts/lead-tools.js +265 -0
- package/dist/contracts/memory-pass.d.ts +158 -0
- package/dist/contracts/memory-pass.js +102 -0
- package/dist/contracts/project-knowledge.d.ts +126 -0
- package/dist/contracts/project-knowledge.js +93 -0
- package/dist/contracts/project-memory.d.ts +117 -0
- package/dist/contracts/project-memory.js +51 -0
- package/dist/contracts/project-skills.d.ts +121 -0
- package/dist/contracts/project-skills.js +76 -0
- package/dist/contracts/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -0
- package/dist/contracts/review-findings.d.ts +46 -0
- package/dist/contracts/review-findings.js +48 -0
- package/dist/contracts/route.d.ts +484 -0
- package/dist/contracts/route.js +128 -0
- package/dist/contracts/scope.d.ts +105 -0
- package/dist/contracts/scope.js +114 -0
- package/dist/contracts/scout-report.d.ts +234 -0
- package/dist/contracts/scout-report.js +141 -0
- package/dist/contracts/slack-callback.d.ts +43 -0
- package/dist/contracts/slack-callback.js +35 -0
- package/dist/contracts/sort-answer.d.ts +83 -0
- package/dist/contracts/sort-answer.js +77 -0
- package/dist/contracts/stage-output.d.ts +127 -0
- package/dist/contracts/stage-output.js +166 -0
- package/dist/contracts/task-sizing.d.ts +24 -0
- package/dist/contracts/task-sizing.js +28 -0
- package/dist/contracts/teammate-turn.d.ts +43 -0
- package/dist/contracts/teammate-turn.js +51 -0
- package/dist/contracts/teams-callback.d.ts +14 -0
- package/dist/contracts/teams-callback.js +14 -0
- package/dist/contracts/telegram-callback.d.ts +120 -0
- package/dist/contracts/telegram-callback.js +97 -0
- package/dist/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/demo.js +28 -11
- package/dist/discord-chat.js +31 -20
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-engine.js +14 -17
- package/dist/flow-gallery.d.ts +3 -2
- package/dist/flow-items.d.ts +4 -13
- package/dist/flow-items.js +1 -15
- package/dist/flow-send.d.ts +6 -37
- package/dist/flow-send.js +18 -14
- package/dist/flow-share.d.ts +10 -32
- package/dist/flow-share.js +72 -84
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-steps.js +3 -2
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -220
- package/dist/flows.js +356 -387
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +111 -308
- package/dist/mcp-connect.d.ts +27 -4
- package/dist/mcp-connect.js +92 -17
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/operate.js +31 -2
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- package/dist/project-knowledge.d.ts +16 -35
- package/dist/project-knowledge.js +42 -21
- package/dist/project-memory.d.ts +2 -28
- package/dist/project-memory.js +28 -13
- package/dist/project-skills.d.ts +16 -38
- package/dist/project-skills.js +41 -32
- package/dist/project-tools.d.ts +1 -0
- package/dist/project-tools.js +4 -4
- package/dist/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- package/dist/result-shots.d.ts +1 -1
- package/dist/result-shots.js +2 -1
- package/dist/review-switch.d.ts +3 -8
- package/dist/reviewer.d.ts +8 -12
- package/dist/reviewer.js +11 -35
- package/dist/scope.d.ts +21 -43
- package/dist/scope.js +42 -128
- package/dist/scout-report.d.ts +9 -194
- package/dist/scout-report.js +113 -264
- package/dist/scout.js +15 -11
- package/dist/serve.js +17 -101
- package/dist/slack-chat.js +26 -20
- package/dist/slack-state.js +2 -1
- package/dist/store.d.ts +9 -32
- package/dist/store.js +18 -15
- package/dist/structured-output.js +2 -1
- package/dist/subscription-chat.d.ts +4 -0
- package/dist/subscription-chat.js +4 -1
- package/dist/surface.js +8 -1
- package/dist/task-review-brief.d.ts +69 -0
- package/dist/task-review-brief.js +179 -0
- package/dist/task-sizing.d.ts +3 -27
- package/dist/task-sizing.js +11 -26
- package/dist/task-text.d.ts +0 -5
- package/dist/task-text.js +1 -3
- package/dist/teammate-question.js +7 -5
- package/dist/teammate-work.js +9 -5
- package/dist/teammates.d.ts +13 -64
- package/dist/teammates.js +32 -33
- package/dist/teams-chat.js +20 -11
- package/dist/telegram-flow.js +4 -3
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +8 -7
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +52 -42
- package/dist/text-limits.d.ts +212 -1
- package/dist/text-limits.js +214 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
package/dist/demo.js
CHANGED
|
@@ -38,6 +38,7 @@ import { fileTaskProposal, fileRoutineProposal } from "./proposal.js";
|
|
|
38
38
|
import { storeEvidence, budgetedStatJson, imageDimensions } from "./evidence.js";
|
|
39
39
|
import { parseProof, adjudicate } from "./proof.js";
|
|
40
40
|
import { sealVerificationReceipt } from "./verification-evidence.js";
|
|
41
|
+
import { HANDOFF_VERSION, readHandoffArtifact } from "./contracts/handoff.js";
|
|
41
42
|
import { parseExecutionPlanDocument, milestonesOf } from "./plan.js";
|
|
42
43
|
import { maybeTriggerRepair } from "./dispose.js";
|
|
43
44
|
import { deflateSync } from "node:zlib";
|
|
@@ -84,8 +85,21 @@ index 11aa0b2..c44d1f7 100644
|
|
|
84
85
|
+ expect(settle(1005, 0.031)).toBe(31.16);
|
|
85
86
|
+ });
|
|
86
87
|
`;
|
|
88
|
+
/** A demo run's handoff artifact: the run's own identity around the story's conclusion and lists, written through
|
|
89
|
+
* the handoff schema like a real one. */
|
|
90
|
+
function demoHandoffBytes(store, runId, told) {
|
|
91
|
+
const run = store.getRun(runId);
|
|
92
|
+
const head = run.headRevision ?? run.baseRevision ?? "0".repeat(40);
|
|
93
|
+
const read = readHandoffArtifact({
|
|
94
|
+
version: HANDOFF_VERSION, taskId: store.externalIdFor(run.taskRef) ?? String(run.taskRef), runId, provider: run.provider,
|
|
95
|
+
sessionId: null, branch: run.branch ?? "", worktree: run.worktree ?? "", base: run.baseRevision ?? head, head, ...told,
|
|
96
|
+
freshness: { stampedAt: run.startedAt, currentAsOf: head },
|
|
97
|
+
});
|
|
98
|
+
if (!read.ok)
|
|
99
|
+
throw new Error(`demo handoff: ${read.issues.map(issue => issue.line).join("; ")}`);
|
|
100
|
+
return Buffer.from(JSON.stringify(read.value, null, 2), "utf8");
|
|
101
|
+
}
|
|
87
102
|
const DEMO_HANDOFF = {
|
|
88
|
-
schema: 1,
|
|
89
103
|
outcome: "built",
|
|
90
104
|
committed: true,
|
|
91
105
|
conclusion: "Fixed the payout rounding drift: settle() now rounds at cent precision instead of accumulating half-cent errors. Added boundary tests against the ledger fixtures. All 214 tests pass.",
|
|
@@ -123,7 +137,6 @@ index 2b3c4d5..8e9f0a1 100644
|
|
|
123
137
|
};
|
|
124
138
|
`;
|
|
125
139
|
const DEMO_FAILED_HANDOFF = {
|
|
126
|
-
schema: 1,
|
|
127
140
|
outcome: "built",
|
|
128
141
|
committed: true,
|
|
129
142
|
conclusion: "Removed LEGACY_PAYOUT from settlement and config. The admin override still reads it, so one reference remains.",
|
|
@@ -193,7 +206,6 @@ index a1b2c3d..d4e5f6a 100644
|
|
|
193
206
|
+export const EMPTY_STATE_ILLUSTRATION = "quiet-inbox";
|
|
194
207
|
`;
|
|
195
208
|
const DEMO_COPY_HANDOFF = {
|
|
196
|
-
schema: 1,
|
|
197
209
|
outcome: "built",
|
|
198
210
|
committed: true,
|
|
199
211
|
conclusion: "Rewrote the inbox's empty-state copy so it explains why the list is empty instead of just saying so.",
|
|
@@ -605,7 +617,7 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
605
617
|
filesTruncated: false,
|
|
606
618
|
};
|
|
607
619
|
storeEvidence(store, evidenceRoot, doneRun, "diff-stat", "diff-stat.json", budgetedStatJson(stat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(8.5));
|
|
608
|
-
storeEvidence(store, evidenceRoot, doneRun, "handoff", "handoff.json",
|
|
620
|
+
storeEvidence(store, evidenceRoot, doneRun, "handoff", "handoff.json", demoHandoffBytes(store, doneRun, DEMO_HANDOFF), "composed at completion [demo: synthetic]", hoursAgo(8.4));
|
|
609
621
|
// The evidence bundle (Priority 2): a validated proof, its claimed
|
|
610
622
|
// screenshot stored as immutable image evidence, the plane's own re-run
|
|
611
623
|
// check, and the closed verdict — computed once, exactly as the real
|
|
@@ -669,7 +681,7 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
669
681
|
filesTruncated: false,
|
|
670
682
|
};
|
|
671
683
|
storeEvidence(store, evidenceRoot, failedRun, "diff-stat", "diff-stat.json", budgetedStatJson(failedStat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(5.6));
|
|
672
|
-
storeEvidence(store, evidenceRoot, failedRun, "handoff", "handoff.json",
|
|
684
|
+
storeEvidence(store, evidenceRoot, failedRun, "handoff", "handoff.json", demoHandoffBytes(store, failedRun, DEMO_FAILED_HANDOFF), "composed at completion [demo: synthetic]", hoursAgo(5.6));
|
|
673
685
|
storeEvidence(store, evidenceRoot, failedRun, "proof", "proof.json", Buffer.from(JSON.stringify(DEMO_FAILED_PROOF, null, 2), "utf8"), "agent-authored proof (validated, re-serialized) [demo: synthetic]", hoursAgo(5.6));
|
|
674
686
|
const failedAdjudicated = adjudicate({
|
|
675
687
|
proofArtifactPresent: true,
|
|
@@ -734,7 +746,7 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
734
746
|
filesTruncated: false,
|
|
735
747
|
};
|
|
736
748
|
storeEvidence(store, evidenceRoot, copyReviewRun, "diff-stat", "diff-stat.json", budgetedStatJson(copyStat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(3.6));
|
|
737
|
-
storeEvidence(store, evidenceRoot, copyReviewRun, "handoff", "handoff.json",
|
|
749
|
+
storeEvidence(store, evidenceRoot, copyReviewRun, "handoff", "handoff.json", demoHandoffBytes(store, copyReviewRun, DEMO_COPY_HANDOFF), "composed at completion [demo: synthetic]", hoursAgo(3.5));
|
|
738
750
|
storeEvidence(store, evidenceRoot, copyReviewRun, "proof", "proof.json", Buffer.from(JSON.stringify(DEMO_COPY_PROOF, null, 2), "utf8"), "agent-authored proof (validated, re-serialized) [demo: synthetic]", hoursAgo(3.5));
|
|
739
751
|
const copyReviewProofParse = parseProof(JSON.stringify(DEMO_COPY_PROOF));
|
|
740
752
|
const copyReviewAdjudicated = adjudicate({
|
|
@@ -815,10 +827,15 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
815
827
|
};
|
|
816
828
|
storeEvidence(store, evidenceRoot, reviewedRun, "diff-stat", "diff-stat.json", budgetedStatJson(reviewedStat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(4.6));
|
|
817
829
|
const DEMO_REPAIR_HANDOFF = {
|
|
818
|
-
|
|
819
|
-
|
|
830
|
+
outcome: "built",
|
|
831
|
+
committed: true,
|
|
832
|
+
conclusion: "Added a TODO comment marking the per-account lock — ran out of turns before wiring the actual guard.",
|
|
833
|
+
changes: ["Marked the per-account lock in src/payout-limiter.ts."],
|
|
834
|
+
verification: [],
|
|
835
|
+
followUps: [],
|
|
836
|
+
decisionsIncorporated: [],
|
|
820
837
|
};
|
|
821
|
-
storeEvidence(store, evidenceRoot, reviewedRun, "handoff", "handoff.json",
|
|
838
|
+
storeEvidence(store, evidenceRoot, reviewedRun, "handoff", "handoff.json", demoHandoffBytes(store, reviewedRun, DEMO_REPAIR_HANDOFF), "composed at completion [demo: synthetic]", hoursAgo(4.5));
|
|
822
839
|
const DEMO_REPAIR_PROOF = {
|
|
823
840
|
version: 1,
|
|
824
841
|
criteria: [
|
|
@@ -1389,7 +1406,7 @@ export function createDemoLead(input) {
|
|
|
1389
1406
|
};
|
|
1390
1407
|
const shotPath = `evidence/${plan.kind}.png`;
|
|
1391
1408
|
const png = demoScreenshot(plan.screenshot.accent);
|
|
1392
|
-
const handoff = {
|
|
1409
|
+
const handoff = { outcome: "built", committed: true, conclusion: plan.conclusion, changes: diffs.map(one => `Changed ${one.path}.`), verification: [plan.checks[0]], followUps: [], decisionsIncorporated: [] };
|
|
1393
1410
|
const proof = {
|
|
1394
1411
|
version: 1,
|
|
1395
1412
|
criteria: [
|
|
@@ -1404,7 +1421,7 @@ export function createDemoLead(input) {
|
|
|
1404
1421
|
const synthetic = "[demo: scripted]";
|
|
1405
1422
|
storeEvidence(store, evidenceRoot, run, "terminal-diff", "terminal-diff.patch", Buffer.from(diffs.map(one => one.patch).join(""), "utf8"), `git diff --no-ext-diff --no-textconv --no-color ${base.slice(0, 8)}..HEAD (exit 0) ${synthetic}`, at);
|
|
1406
1423
|
storeEvidence(store, evidenceRoot, run, "diff-stat", "diff-stat.json", budgetedStatJson(stat), `parsed from git diff --numstat -z ${synthetic}`, at);
|
|
1407
|
-
storeEvidence(store, evidenceRoot, run, "handoff", "handoff.json",
|
|
1424
|
+
storeEvidence(store, evidenceRoot, run, "handoff", "handoff.json", demoHandoffBytes(store, run, handoff), `composed at completion ${synthetic}`, at);
|
|
1408
1425
|
storeEvidence(store, evidenceRoot, run, "proof", "proof.json", Buffer.from(JSON.stringify(proof, null, 2), "utf8"), `agent-authored proof (validated, re-serialized) ${synthetic}`, at);
|
|
1409
1426
|
storeEvidence(store, evidenceRoot, run, "screenshot", `screenshot-${plan.kind}.png`, png, `agent-claimed screenshot at ${shotPath} (validated png) ${synthetic}`, at);
|
|
1410
1427
|
const log = `=== Attempt summary ===\n- Project check · attempt 1: (exit 0)\n\n=== Project check · attempt 1 ===\n$ ${DEMO_CHECK_COMMAND}\n(exit 0)\n\n--- stdout ---\n${plan.checkOutput}\n--- stderr ---\n`;
|
package/dist/discord-chat.js
CHANGED
|
@@ -5,7 +5,9 @@ import { chatFlowButtons } from "./chat-flow.js";
|
|
|
5
5
|
import { refuseResultShots, resultShotsPruned, shotWaitsUntil } from "./result-shots.js";
|
|
6
6
|
import { channelInbox } from "./chat-inbox.js";
|
|
7
7
|
import { roomCommand } from "./chat-rooms.js";
|
|
8
|
-
import { ChatState, ChatDeliveryError, chatHash, } from "./chat-delivery-state.js";
|
|
8
|
+
import { ChatState, ChatDeliveryError, chatHash, partContent, } from "./chat-delivery-state.js";
|
|
9
|
+
import { readChatPart } from "./contracts/chat-content.js";
|
|
10
|
+
import { readDiscordButtonToken, readDiscordComponentInteraction } from "./contracts/discord-callback.js";
|
|
9
11
|
import { processChatEvent, planChatNotifications, channelAccess, chatObject as object, planRoomMessages, } from "./chat-delivery.js";
|
|
10
12
|
import { chatResultHref } from "./chat-controls.js";
|
|
11
13
|
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
@@ -69,21 +71,25 @@ export function receiveDiscord(state, identity, type, raw, now) {
|
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
else if (type === "INTERACTION_CREATE") {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
(isRoom ? object(body.channel).type !== 0 : object(body.channel).type !== 1) ||
|
|
76
|
-
object(message.author).id !== identity.bot ||
|
|
77
|
-
message.channel_id !== body.channel_id ||
|
|
78
|
-
!/^so_[a-f0-9]{32}$/.test(String(data.custom_id)))
|
|
74
|
+
// Only a tap on one of this bot's own messages, in a DM (or a followed guild text channel), from this app.
|
|
75
|
+
const interaction = readDiscordComponentInteraction(body);
|
|
76
|
+
if (!interaction.ok)
|
|
79
77
|
return false;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const tap = interaction.value;
|
|
79
|
+
if (tap.application_id !== identity.app ||
|
|
80
|
+
tap.channel.type !== (isRoom ? 0 : 1) ||
|
|
81
|
+
tap.message.author.id !== identity.bot ||
|
|
82
|
+
tap.message.channel_id !== tap.channel_id)
|
|
83
|
+
return false;
|
|
84
|
+
member = isRoom ? tap.member?.user.id : tap.user?.id;
|
|
85
|
+
channel = tap.channel_id;
|
|
86
|
+
id = tap.id;
|
|
87
|
+
ts = tap.message.id;
|
|
88
|
+
thread = tap.message.id;
|
|
85
89
|
kind = "action";
|
|
86
|
-
|
|
90
|
+
// A button that isn't one Toolroll made is answered with why, once the tap is proved to be the paired person's below.
|
|
91
|
+
const button = readDiscordButtonToken(tap);
|
|
92
|
+
payload = button.ok ? { token: button.value } : { problem: button.issues.map(issue => issue.line).join("; ") };
|
|
87
93
|
}
|
|
88
94
|
else
|
|
89
95
|
return false;
|
|
@@ -94,9 +100,10 @@ export function receiveDiscord(state, identity, type, raw, now) {
|
|
|
94
100
|
!discordId(thread) ||
|
|
95
101
|
member === identity.bot)
|
|
96
102
|
return false;
|
|
103
|
+
const words = "text" in payload ? payload.text ?? "" : "";
|
|
97
104
|
// v89: a channel that feeds a flow takes anyone's message (as a card, with no say over anything); "flow 12" connects one.
|
|
98
|
-
const inbox = isRoom && kind === "message" ? channelInbox(state.store, "discord", identity.installation, String(channel),
|
|
99
|
-
const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(
|
|
105
|
+
const inbox = isRoom && kind === "message" ? channelInbox(state.store, "discord", identity.installation, String(channel), words) : { watched: false, command: false };
|
|
106
|
+
const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
|
|
100
107
|
if (isRoom && !roomish)
|
|
101
108
|
return false;
|
|
102
109
|
const binding = state.bindingFor(identity.installation, member);
|
|
@@ -116,7 +123,7 @@ export function receiveDiscord(state, identity, type, raw, now) {
|
|
|
116
123
|
member,
|
|
117
124
|
ts,
|
|
118
125
|
thread,
|
|
119
|
-
payload
|
|
126
|
+
payload,
|
|
120
127
|
created: now.toISOString(),
|
|
121
128
|
});
|
|
122
129
|
}
|
|
@@ -200,7 +207,7 @@ export async function deliverDiscordPart(options) {
|
|
|
200
207
|
const session = event.session === null ? null : store.getMateSession(event.session), repos = await channelAccess(shared, binding, session?.ceilingDigest);
|
|
201
208
|
if (event.kind === "notice" && options.canNotify?.() === false)
|
|
202
209
|
return false;
|
|
203
|
-
const content =
|
|
210
|
+
const content = partContent(row.payload);
|
|
204
211
|
const destination = content.channel ?? binding.channel;
|
|
205
212
|
if (content.task &&
|
|
206
213
|
!repos.includes(store.lookupRef(content.task)?.repo ?? ""))
|
|
@@ -398,9 +405,13 @@ export async function deliverDiscordPart(options) {
|
|
|
398
405
|
return true;
|
|
399
406
|
}
|
|
400
407
|
catch (error) {
|
|
408
|
+
if (error instanceof ChatDeliveryError && error.permanent) {
|
|
409
|
+
state.prepare("UPDATE chat_part SET state='dropped',next_at=NULL,problem=? WHERE id=?").run(error.message, row.id);
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
401
412
|
// No permission to attach files here: one plain line instead of the result's screenshots.
|
|
402
|
-
const
|
|
403
|
-
if (content.image && content.shot && error instanceof DiscordError && error.code === DISCORD_REFUSED) {
|
|
413
|
+
const saved = readChatPart(row.payload), content = saved.ok ? saved.value : null;
|
|
414
|
+
if (content !== null && content.image && content.shot && error instanceof DiscordError && error.code === DISCORD_REFUSED) {
|
|
404
415
|
refuseResultShots(state, store, row, content, "Discord", options.clock?.() ?? new Date());
|
|
405
416
|
return true;
|
|
406
417
|
}
|
package/dist/evidence.d.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { type ParsedReport, type ReportImage } from "./scout-report.js";
|
|
17
17
|
import { type ParsedProof } from "./proof.js";
|
|
18
|
+
import { type HandoffArtifact } from "./contracts/handoff.js";
|
|
19
|
+
export type { HandoffArtifact } from "./contracts/handoff.js";
|
|
18
20
|
import type { Artifact, Store } from "./store.js";
|
|
19
21
|
import type { ExecResult } from "./exec.js";
|
|
20
22
|
/** The park mailbox pattern. Each run's actual name carries a nonce; see `mailboxName`. */
|
|
@@ -254,45 +256,6 @@ export type DiffStat = {
|
|
|
254
256
|
* recorded as null — never coerced to zero.
|
|
255
257
|
*/
|
|
256
258
|
export declare function parseNumstat(raw: string, base: string, head: string): DiffStat;
|
|
257
|
-
/** The freshness-stamped handoff artifact's shape (M6.10). */
|
|
258
|
-
export type HandoffArtifact = {
|
|
259
|
-
schema: 1;
|
|
260
|
-
taskId: string;
|
|
261
|
-
runId: number;
|
|
262
|
-
provider: string;
|
|
263
|
-
/** v47: the exact model that ran, and the sealed route leg it ran as —
|
|
264
|
-
* provenance a later reader can hold against the approval. Absent on
|
|
265
|
-
* artifacts written before routes existed. */
|
|
266
|
-
model?: string;
|
|
267
|
-
route?: {
|
|
268
|
-
digest: string;
|
|
269
|
-
phase: "plan" | "build" | "repair" | "review";
|
|
270
|
-
provider: string;
|
|
271
|
-
model: string | null;
|
|
272
|
-
chosen: "recommended" | "override" | "pinned" | "legacy" | "fallback";
|
|
273
|
-
};
|
|
274
|
-
sessionId: string | null;
|
|
275
|
-
branch: string;
|
|
276
|
-
worktree: string;
|
|
277
|
-
base: string;
|
|
278
|
-
head: string;
|
|
279
|
-
outcome: "built" | "no-change";
|
|
280
|
-
committed: boolean;
|
|
281
|
-
/** Decision ids whose answers were in this run's brief — causality, not time. */
|
|
282
|
-
decisionsIncorporated: number[];
|
|
283
|
-
/** The agent's conclusion — agent-reported, and labeled so by its position here. */
|
|
284
|
-
conclusion: string;
|
|
285
|
-
/** Structured operator-facing output. Optional for artifacts written by
|
|
286
|
-
* older versions; readers treat absence as an empty list. */
|
|
287
|
-
changes?: string[];
|
|
288
|
-
verification?: string[];
|
|
289
|
-
followUps?: string[];
|
|
290
|
-
freshness: {
|
|
291
|
-
stampedAt: string;
|
|
292
|
-
/** A successor proves this against the branch before trusting anything above. */
|
|
293
|
-
currentAsOf: string;
|
|
294
|
-
};
|
|
295
|
-
};
|
|
296
259
|
/**
|
|
297
260
|
* Write the handoff artifact (M6.10): the machine's own statement of where
|
|
298
261
|
* a finished run left the world — workspace identity, exact base and head,
|
|
@@ -301,7 +264,7 @@ export type HandoffArtifact = {
|
|
|
301
264
|
* context. This is what makes a cold takeover context-bearing rather than
|
|
302
265
|
* repo-only; a provider session is memory, not a freshness proof.
|
|
303
266
|
*/
|
|
304
|
-
export declare function storeHandoffArtifact(store: Store, root: string, payload: HandoffArtifact, now: Date): number;
|
|
267
|
+
export declare function storeHandoffArtifact(store: Store, root: string, payload: Omit<HandoffArtifact, "version">, now: Date): number;
|
|
305
268
|
/**
|
|
306
269
|
* High-confidence secret shapes ONLY (audit IV-7): a detector that cries
|
|
307
270
|
* wolf trains people to approve wolves. Each pattern is a format no
|
package/dist/evidence.js
CHANGED
|
@@ -21,6 +21,7 @@ import { LIMITS } from "./decision.js";
|
|
|
21
21
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
22
22
|
import { parseReport, REPORT_LIMITS } from "./scout-report.js";
|
|
23
23
|
import { parseProof, PROOF_LIMITS } from "./proof.js";
|
|
24
|
+
import { HANDOFF_VERSION, readHandoffArtifact } from "./contracts/handoff.js";
|
|
24
25
|
import { encodeBaseTreeSnapshot, parseBaseTreeSnapshot } from "./peek.js";
|
|
25
26
|
/** The park mailbox pattern. Each run's actual name carries a nonce; see `mailboxName`. */
|
|
26
27
|
export const MAILBOX_PREFIX = "STANDING-ORDERS-PARK-";
|
|
@@ -618,7 +619,10 @@ export function parseNumstat(raw, base, head) {
|
|
|
618
619
|
* repo-only; a provider session is memory, not a freshness proof.
|
|
619
620
|
*/
|
|
620
621
|
export function storeHandoffArtifact(store, root, payload, now) {
|
|
621
|
-
|
|
622
|
+
const read = readHandoffArtifact({ version: HANDOFF_VERSION, ...payload });
|
|
623
|
+
if (!read.ok)
|
|
624
|
+
throw new Error(`The handoff does not match its contract: ${read.issues.map(issue => issue.line).join("; ")}`);
|
|
625
|
+
return storeEvidence(store, root, payload.runId, "handoff", "handoff.json", Buffer.from(JSON.stringify(read.value, null, 2), "utf8"), "machine-authored handoff (exit 0)", now);
|
|
622
626
|
}
|
|
623
627
|
/**
|
|
624
628
|
* High-confidence secret shapes ONLY (audit IV-7): a detector that cries
|
package/dist/flow-engine.js
CHANGED
|
@@ -19,6 +19,7 @@ import { cardFollowers, notifyPeople } from "./flow-people.js";
|
|
|
19
19
|
import { DRAFT_CHARS, keptDraft } from "./flow-draft.js";
|
|
20
20
|
import { chooseStep, flowPersonOf, sendStep } from "./flow-send.js";
|
|
21
21
|
import { parseSoul, teammateLabel } from "./teammates.js";
|
|
22
|
+
import { withStageHandoff } from "./contracts/stage-output.js";
|
|
22
23
|
/** The flow's drawing, or null when it can't be read (the card then says so and waits). */
|
|
23
24
|
export function flowDefinitionOf(flow) {
|
|
24
25
|
try {
|
|
@@ -302,7 +303,7 @@ function workStage(store, flow, definition, stage, card, now, options, outcome,
|
|
|
302
303
|
const checks = assignmentOf(store, current, now, { principal: "operator", repos: [repo] }, options.evidenceRoot)?.receipt?.checks ?? null;
|
|
303
304
|
if (checks?.status === "failed") {
|
|
304
305
|
const said = `The checks failed on its result: ${checks.detail}`;
|
|
305
|
-
store.updateFlowCard(card.id, { outputs:
|
|
306
|
+
store.updateFlowCard(card.id, { outputs: withStageHandoff(card.outputs, stage.id, { text: `${said} (task ${current})` }), primaryTask: current }, now);
|
|
306
307
|
if (stage.onFail !== null)
|
|
307
308
|
onward("fail", said);
|
|
308
309
|
else if (card.waiting !== `${said} Look at the result, then move the card on or back.`)
|
|
@@ -310,27 +311,21 @@ function workStage(store, flow, definition, stage, card, now, options, outcome,
|
|
|
310
311
|
return;
|
|
311
312
|
}
|
|
312
313
|
}
|
|
313
|
-
|
|
314
|
+
let handoff = { text: `Result ready on task ${current}.` };
|
|
314
315
|
if (stage.kind === "report") {
|
|
315
316
|
const ref = store.lookupRef(current);
|
|
316
317
|
const summary = ref === null ? null : reportSummaryFor(store, options.evidenceRoot, ref.id);
|
|
317
|
-
// The summary whole (a report's summary is within TEXT_LIMITS.reportSummary bytes; an older, longer one is linked).
|
|
318
|
-
outputs[stage.id] = summary !== null && "summary" in summary ? passOn(summary.summary, TEXT_LIMITS.stageOutput, { label: `task ${current}`, href: null }).text : "The report is ready on its task.";
|
|
319
318
|
// {{stage.<id>.items}} and {{stage.<id>.report}}: what it found and the whole report, for the zones after it.
|
|
320
319
|
// A visit's report replaces the last visit's, so nothing stale is left behind.
|
|
321
320
|
const view = ref === null || options.evidenceRoot === undefined ? null : readVerifiedReport(store, options.evidenceRoot, ref.id);
|
|
322
321
|
const found = view?.ok === true ? reportFillIns(view.report) : { items: "", report: "" };
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
outputs[stage.id] = `Result ready on task ${current}.`;
|
|
322
|
+
handoff = {
|
|
323
|
+
// The summary whole (a report's summary is within TEXT_LIMITS.reportSummary bytes; an older, longer one is linked).
|
|
324
|
+
text: summary !== null && "summary" in summary ? passOn(summary.summary, TEXT_LIMITS.stageOutput, { label: `task ${current}`, href: null }).text : "The report is ready on its task.",
|
|
325
|
+
...(found.items === "" ? {} : { items: found.items }), ...(found.report === "" ? {} : { report: found.report }),
|
|
326
|
+
};
|
|
332
327
|
}
|
|
333
|
-
store.updateFlowCard(card.id, { outputs, ...(stage.kind === "task" ? { primaryTask: current } : {}), waiting: null }, now);
|
|
328
|
+
store.updateFlowCard(card.id, { outputs: withStageHandoff(card.outputs, stage.id, handoff), ...(stage.kind === "task" ? { primaryTask: current } : {}), waiting: null }, now);
|
|
334
329
|
// A "Send to me" or "Person chooses" zone next reads what this task produced: the task goes with the card there.
|
|
335
330
|
const following = stage.next === null ? undefined : flowDefinitionStage(definition, stage.next);
|
|
336
331
|
onward("ok", undefined, following?.kind === "send" || following?.kind === "choose" ? current : undefined);
|
|
@@ -380,8 +375,10 @@ function goalCuts(store, taskId, goal) {
|
|
|
380
375
|
if (seen.has(match[1]))
|
|
381
376
|
continue;
|
|
382
377
|
seen.add(match[1]);
|
|
383
|
-
|
|
384
|
-
|
|
378
|
+
// An output too long to pass on is kept whole on the card (src/contracts/stage-output.ts): the agent gets it whole.
|
|
379
|
+
const whole = match[2] !== undefined && match[3] === undefined ? card.attached[match[2]] : undefined;
|
|
380
|
+
const text = whole ?? fillFlowText(`{{${match[1]}}}`, card);
|
|
381
|
+
if (text === "" || (whole === undefined && goal.includes(text)))
|
|
385
382
|
continue;
|
|
386
383
|
cuts.push({ label: flowValueLabel(match[1], id => titleIn(definition, id)), text: text.slice(0, FLOW_ATTACHED_LIMIT) });
|
|
387
384
|
}
|
|
@@ -496,7 +493,7 @@ export function decideFlowCard(store, input, now) {
|
|
|
496
493
|
return { ok: false, message: `Keep the draft to ${DRAFT_CHARS.toLocaleString("en-US")} characters; this is ${input.draft.trim().length.toLocaleString("en-US")}.` };
|
|
497
494
|
}
|
|
498
495
|
if (draft !== null && typeof input.draft === "string" && input.draft.trim() !== "" && input.draft.trim() !== card.outputs[draft.id]?.trim()) {
|
|
499
|
-
store.updateFlowCard(card.id, { outputs:
|
|
496
|
+
store.updateFlowCard(card.id, { outputs: withStageHandoff(card.outputs, draft.id, { text: keptDraft(input.draft) }) }, now);
|
|
500
497
|
store.addFlowComment({ card: card.id, author: input.actor, body: "Edited the draft before approving it.", mentions: [] }, now);
|
|
501
498
|
}
|
|
502
499
|
if (stage.next === null) {
|
package/dist/flow-gallery.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FlowDefinition, type FlowStepInput } from "./flows.js";
|
|
2
|
+
import type { TriggerInput } from "./contracts/flow.js";
|
|
2
3
|
import type { Store } from "./store.js";
|
|
3
4
|
export declare const GALLERY_GROUPS: readonly [{
|
|
4
5
|
readonly id: "ship";
|
|
@@ -46,7 +47,7 @@ export type GalleryTemplate = {
|
|
|
46
47
|
/** The steps in Toolroll's own repository, when they differ. */
|
|
47
48
|
ownSteps?: FlowStepInput[];
|
|
48
49
|
definition?: FlowDefinition;
|
|
49
|
-
triggers:
|
|
50
|
+
triggers: TriggerInput[];
|
|
50
51
|
scripts?: {
|
|
51
52
|
name: string;
|
|
52
53
|
about: string;
|
|
@@ -100,7 +101,7 @@ export type GalleryBuilt = {
|
|
|
100
101
|
repo: string;
|
|
101
102
|
answers: GalleryAnswers;
|
|
102
103
|
definition: FlowDefinition;
|
|
103
|
-
triggers:
|
|
104
|
+
triggers: TriggerInput[];
|
|
104
105
|
scripts: {
|
|
105
106
|
name: string;
|
|
106
107
|
about: string;
|
package/dist/flow-items.d.ts
CHANGED
|
@@ -8,17 +8,10 @@
|
|
|
8
8
|
* in its own format. Its words go through the channel's own cleaner first. Fitted to a channel's limit, each why is
|
|
9
9
|
* shortened evenly first; then the summary, then the titles; past that it is split into parts. Links are never cut.
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/** Where the link goes, by name: "Mobbin", "Linear", "Apple". */
|
|
16
|
-
source: string;
|
|
17
|
-
/** One line: how it plugs in (from the why, else the image's own caption). */
|
|
18
|
-
plug: string;
|
|
19
|
-
/** The item's screenshot (its evidence id in the report's run), when it has one. */
|
|
20
|
-
shot: number | null;
|
|
21
|
-
};
|
|
11
|
+
import type { FlowSendItem } from "./contracts/flow-send.js";
|
|
12
|
+
/** One item as the message lists it, and kept items read back (src/contracts/flow-send.ts). */
|
|
13
|
+
export type { FlowSendItem };
|
|
14
|
+
export { readFlowItems } from "./contracts/flow-send.js";
|
|
22
15
|
/** A link's source by name: a few known sites as they write themselves, else the site's own name from its address. */
|
|
23
16
|
export declare function itemSource(url: string): string;
|
|
24
17
|
/** A why as plain lines: its own lines, or "the pattern; why it fits us; how it plugs in" one to a line. */
|
|
@@ -42,5 +35,3 @@ export declare function cleanFlowMessage(message: FlowMessage, clean: (text: str
|
|
|
42
35
|
* blocks (else between lines). Every link stays.
|
|
43
36
|
*/
|
|
44
37
|
export declare function fitFlowMessage(message: FlowMessage, limit: number, measure: (shaped: string) => number): string[];
|
|
45
|
-
/** Items read back from a kept content; anything malformed is left out. */
|
|
46
|
-
export declare function readFlowItems(raw: unknown): FlowSendItem[];
|
package/dist/flow-items.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* in its own format. Its words go through the channel's own cleaner first. Fitted to a channel's limit, each why is
|
|
9
9
|
* shortened evenly first; then the summary, then the titles; past that it is split into parts. Links are never cut.
|
|
10
10
|
*/
|
|
11
|
+
export { readFlowItems } from "./contracts/flow-send.js";
|
|
11
12
|
const PLUG_CHARS = 160;
|
|
12
13
|
const NAMES = {
|
|
13
14
|
"mobbin.com": "Mobbin", "github.com": "GitHub", "figma.com": "Figma", "dribbble.com": "Dribbble", "behance.net": "Behance",
|
|
@@ -135,18 +136,3 @@ export function fitFlowMessage(message, limit, measure) {
|
|
|
135
136
|
return [compose(message, 0, 0, title)];
|
|
136
137
|
return pack(blocks(message, Infinity, Infinity, Infinity), "\n\n", fits, block => pack(block.split("\n"), "\n", fits, line => chop(line, fits)));
|
|
137
138
|
}
|
|
138
|
-
/** Items read back from a kept content; anything malformed is left out. */
|
|
139
|
-
export function readFlowItems(raw) {
|
|
140
|
-
if (!Array.isArray(raw))
|
|
141
|
-
return [];
|
|
142
|
-
return raw.flatMap((one) => {
|
|
143
|
-
if (typeof one !== "object" || one === null)
|
|
144
|
-
return [];
|
|
145
|
-
const item = one;
|
|
146
|
-
const text = (key) => typeof item[key] === "string" ? item[key] : null;
|
|
147
|
-
const [title, why, url, source, plug] = [text("title"), text("why"), text("url"), text("source"), text("plug")];
|
|
148
|
-
if (title === null || why === null || url === null || source === null || plug === null || !/^https?:\/\//.test(url))
|
|
149
|
-
return [];
|
|
150
|
-
return [{ title, why, url, source, plug, shot: typeof item["shot"] === "number" && Number.isSafeInteger(item["shot"]) ? item["shot"] : null }];
|
|
151
|
-
});
|
|
152
|
-
}
|
package/dist/flow-send.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FlowDecision } from "./flow-engine.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type FlowChoiceAnswer, type FlowSendContent, type FlowSendLink } from "./contracts/flow-send.js";
|
|
3
3
|
import { type FlowDefinition, type FlowStage } from "./flows.js";
|
|
4
4
|
import { type FlowCardRow, type FlowRow, type Store } from "./store.js";
|
|
5
5
|
/** The notice a "Send to me" visit sends: `flow-send:<card>:<entry>`. */
|
|
@@ -8,39 +8,14 @@ export declare const FLOW_SEND_KEY: RegExp;
|
|
|
8
8
|
export declare const FLOW_CHOOSE_KEY: RegExp;
|
|
9
9
|
/** What the person is asked under the buttons. */
|
|
10
10
|
export declare const REPLY_ASK = "Or reply with what you'd change.";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
path: string;
|
|
14
|
-
} | {
|
|
15
|
-
label: string;
|
|
16
|
-
url: string;
|
|
17
|
-
};
|
|
18
|
-
export type FlowSendContent = {
|
|
19
|
-
/** The card's title and the step it comes from. */
|
|
20
|
-
title: string;
|
|
21
|
-
/** The step it comes from, when known. */
|
|
22
|
-
from?: string;
|
|
23
|
-
summary: string;
|
|
24
|
-
links: FlowSendLink[];
|
|
25
|
-
/** The build's result whose screenshots go with it, when it saved any. */
|
|
26
|
-
shots: {
|
|
27
|
-
taskId: string;
|
|
28
|
-
run: number;
|
|
29
|
-
} | null;
|
|
30
|
-
/** After research: the report's items, numbered in this order in every message and screenshot caption (flow-items.ts). */
|
|
31
|
-
items?: FlowSendItem[];
|
|
32
|
-
/** choose: the options as they were offered, and whether a reply is taken. */
|
|
33
|
-
options?: {
|
|
34
|
-
choice: number;
|
|
35
|
-
label: string;
|
|
36
|
-
}[];
|
|
37
|
-
reply?: boolean;
|
|
38
|
-
};
|
|
11
|
+
/** A visit's content and its links (src/contracts/flow-send.ts). */
|
|
12
|
+
export type { FlowSendContent, FlowSendLink };
|
|
39
13
|
/** Who a card's "Send to me" and "Person chooses" zones are for: its owner, else the flow's. */
|
|
40
14
|
export declare function flowPersonOf(card: Pick<FlowCardRow, "owner">, flow: Pick<FlowRow, "owner">): string;
|
|
41
15
|
/** What a visit sends its person, worked out from the card as it is now. */
|
|
42
16
|
export declare function flowSendContent(store: Store, flow: FlowRow, definition: FlowDefinition, stage: FlowStage, card: FlowCardRow, now: Date, evidenceRoot?: string): FlowSendContent;
|
|
43
|
-
/** A kept content, read back
|
|
17
|
+
/** A kept content, read back (src/contracts/flow-send.ts: one kept before it carried a version reads as it did); null
|
|
18
|
+
* when it can't be, and the visit's content is worked out again. */
|
|
44
19
|
export declare function readFlowSend(json: string): FlowSendContent | null;
|
|
45
20
|
/** The words of a sent visit: its title, summary and pull request (the one link that isn't a page of Toolroll's). */
|
|
46
21
|
export declare function flowSendText(content: FlowSendContent): string;
|
|
@@ -71,13 +46,7 @@ export declare function flowChoiceAt(store: Store, cardId: number, entry: number
|
|
|
71
46
|
* reply (`choice` null) that becomes the note for where replies go. `where` names the place it was made, for the card's
|
|
72
47
|
* history and the ledger: "Telegram", "Slack", "the console".
|
|
73
48
|
*/
|
|
74
|
-
export declare function chooseFlowCard(store: Store, input: {
|
|
75
|
-
card: number;
|
|
76
|
-
entry?: number;
|
|
77
|
-
choice: number | null;
|
|
78
|
-
label?: string;
|
|
79
|
-
to?: string;
|
|
80
|
-
note: string | null;
|
|
49
|
+
export declare function chooseFlowCard(store: Store, input: FlowChoiceAnswer & {
|
|
81
50
|
actor: string;
|
|
82
51
|
where: string;
|
|
83
52
|
repos: readonly string[];
|
package/dist/flow-send.js
CHANGED
|
@@ -17,7 +17,9 @@ import { assignmentOf } from "./assignment.js";
|
|
|
17
17
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
18
18
|
import { withActor } from "./actor.js";
|
|
19
19
|
import { flowCardHref, flowDefinitionOf, revisionWithNote } from "./flow-engine.js";
|
|
20
|
-
import { itemPlug, itemSource
|
|
20
|
+
import { itemPlug, itemSource } from "./flow-items.js";
|
|
21
|
+
import { withStageHandoff } from "./contracts/stage-output.js";
|
|
22
|
+
import { flowSendForStore, readFlowChoiceAnswer, readFlowItems, readFlowSendPayload } from "./contracts/flow-send.js";
|
|
21
23
|
import { notifyPeople } from "./flow-people.js";
|
|
22
24
|
import { readVerifiedReport } from "./evidence.js";
|
|
23
25
|
import { FLOW_END, replyTarget } from "./flows.js";
|
|
@@ -120,21 +122,18 @@ export function flowSendContent(store, flow, definition, stage, card, now, evide
|
|
|
120
122
|
...(options === undefined ? {} : { options, reply: replyTarget(stage) !== null }),
|
|
121
123
|
};
|
|
122
124
|
}
|
|
123
|
-
/** A kept content, read back
|
|
125
|
+
/** A kept content, read back (src/contracts/flow-send.ts: one kept before it carried a version reads as it did); null
|
|
126
|
+
* when it can't be, and the visit's content is worked out again. */
|
|
124
127
|
export function readFlowSend(json) {
|
|
128
|
+
let raw;
|
|
125
129
|
try {
|
|
126
|
-
|
|
127
|
-
if (typeof raw.title !== "string" || typeof raw.summary !== "string" || !Array.isArray(raw.links))
|
|
128
|
-
return null;
|
|
129
|
-
if (raw.items === undefined)
|
|
130
|
-
return raw;
|
|
131
|
-
const items = readFlowItems(raw.items);
|
|
132
|
-
const { items: _, ...rest } = raw;
|
|
133
|
-
return items.length === 0 ? rest : { ...rest, items };
|
|
130
|
+
raw = JSON.parse(json);
|
|
134
131
|
}
|
|
135
132
|
catch {
|
|
136
133
|
return null;
|
|
137
134
|
}
|
|
135
|
+
const read = readFlowSendPayload(raw);
|
|
136
|
+
return read.ok ? read.value : null;
|
|
138
137
|
}
|
|
139
138
|
/** The words of a sent visit: its title, summary and pull request (the one link that isn't a page of Toolroll's). */
|
|
140
139
|
export function flowSendText(content) {
|
|
@@ -153,7 +152,7 @@ function keep(store, flow, definition, stage, card, now, evidenceRoot) {
|
|
|
153
152
|
if (kept !== null && read !== null)
|
|
154
153
|
return { content: read, person: kept.person, fresh: false };
|
|
155
154
|
const content = flowSendContent(store, flow, definition, stage, card, now, evidenceRoot);
|
|
156
|
-
const fresh = store.recordFlowSend({ card: card.id, entry: card.entry, stage: stage.id, person, contentJson:
|
|
155
|
+
const fresh = store.recordFlowSend({ card: card.id, entry: card.entry, stage: stage.id, person, contentJson: flowSendForStore(content) }, now);
|
|
157
156
|
return { content, person, fresh };
|
|
158
157
|
}
|
|
159
158
|
/** One notice to the person, and its screenshots after it. Never as anyone's own act: they asked for it in the flow. */
|
|
@@ -174,7 +173,7 @@ export function sendStep(store, flow, definition, stage, card, now, evidenceRoot
|
|
|
174
173
|
const { content, person, fresh } = keep(store, flow, definition, stage, card, now, evidenceRoot);
|
|
175
174
|
if (!fresh)
|
|
176
175
|
return;
|
|
177
|
-
store.updateFlowCard(card.id, { outputs:
|
|
176
|
+
store.updateFlowCard(card.id, { outputs: withStageHandoff(card.outputs, stage.id, { text: flowSendText(content) }) }, now);
|
|
178
177
|
enqueue(store, flow, card, person, content, { key: `flow-send:${card.id}:${card.entry}`, kind: "flow-card", attention: false,
|
|
179
178
|
subject: `${flow.name}: ${content.title}`, body: [content.summary, ...content.links.filter((one) => "url" in one).map(one => `${one.label}: ${one.url}`)].join("\n\n") }, now);
|
|
180
179
|
}
|
|
@@ -217,11 +216,16 @@ const via = (where) => where === "" ? "" : ` in ${where}`;
|
|
|
217
216
|
* history and the ledger: "Telegram", "Slack", "the console".
|
|
218
217
|
*/
|
|
219
218
|
export function chooseFlowCard(store, input, now) {
|
|
220
|
-
|
|
219
|
+
// The answer itself (src/contracts/flow-send.ts): an option that isn't one of the places offered is a changed option.
|
|
220
|
+
const { actor: _actor, where: _where, repos: _repos, evidenceRoot: _root, ...given } = input;
|
|
221
|
+
const answer = readFlowChoiceAnswer(given);
|
|
222
|
+
if (!answer.ok)
|
|
223
|
+
return { ok: false, message: answer.issues.every(one => one.path === "choice") ? "Those options changed since; nothing was changed." : "That card is no longer waiting." };
|
|
224
|
+
const card = store.getFlowCard(answer.value.card);
|
|
221
225
|
const flow = card === null ? null : store.getFlow(card.flow);
|
|
222
226
|
if (card === null || flow === null || card.state !== "active" || !input.repos.includes(flow.repo))
|
|
223
227
|
return { ok: false, message: "That card is no longer waiting." };
|
|
224
|
-
const visit = flowChoiceAt(store, card.id,
|
|
228
|
+
const visit = flowChoiceAt(store, card.id, answer.value.entry ?? card.entry);
|
|
225
229
|
if (visit === null)
|
|
226
230
|
return { ok: false, message: "That card has moved on since; nothing was changed." };
|
|
227
231
|
const { stage, definition, person } = visit;
|