toolroll 0.9.36 → 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 +58 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/chat-acceptance.d.ts +1 -1
- 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 +7 -2
- package/dist/contracts/contract.js +21 -12
- 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-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +5 -0
- package/dist/contracts/flow.js +5 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -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/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/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/desktop-update.d.ts +2 -2
- package/dist/discord-chat.js +31 -20
- package/dist/flow-engine.js +14 -17
- 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.js +7 -1
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-steps.js +3 -2
- package/dist/flows.d.ts +2 -4
- package/dist/flows.js +9 -5
- 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 +108 -275
- package/dist/mcp-connect.d.ts +24 -4
- package/dist/mcp-connect.js +78 -15
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- 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/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- 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 +9 -2
- package/dist/setup-guide.d.ts +2 -2
- 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/task-review-brief.d.ts +1 -7
- package/dist/task-review-brief.js +6 -6
- 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 +142 -1
- package/dist/text-limits.js +144 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/package.json +1 -1
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/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-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;
|
package/dist/flow-share.js
CHANGED
|
@@ -22,6 +22,7 @@ import { readFlowSecrets } from "./flow-secrets.js";
|
|
|
22
22
|
import { scriptDigest, validateScript } from "./flow-scripts.js";
|
|
23
23
|
import { addFlowTriggerTo, describeTrigger, HOOK_PATH, readHooksBase, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
24
24
|
import { durationMinutes, durationWords, FlowContractError, flowTerms, inStepWords, LANGUAGE_WORDS, validateFlowDefinition } from "./flows.js";
|
|
25
|
+
import { stageReferenceProblems } from "./contracts/stage-output.js";
|
|
25
26
|
import { readVersioned } from "./contracts/contract.js";
|
|
26
27
|
import { FLOW_ALIASES, FLOW_FILE_FORMAT, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
|
|
27
28
|
import { parseSchedule } from "./routine.js";
|
|
@@ -319,7 +320,12 @@ function definitionOf(zones) {
|
|
|
319
320
|
const stages = zones.map((step, index) => stageInputOf(step, index, find));
|
|
320
321
|
// Its zones are the file's, in order: a zone's problem names the file's zone.
|
|
321
322
|
try {
|
|
322
|
-
|
|
323
|
+
const definition = validateFlowDefinition({ version: 1, start: stages[0]?.["id"], stages });
|
|
324
|
+
// An imported flow is a new one: each {{stage.…}} must be one its zone hands on.
|
|
325
|
+
const references = stageReferenceProblems(definition, null);
|
|
326
|
+
if (references.length > 0)
|
|
327
|
+
throw new FlowContractError(references.map(one => one.line));
|
|
328
|
+
return definition;
|
|
323
329
|
}
|
|
324
330
|
catch (error) {
|
|
325
331
|
return refuse(error instanceof FlowContractError ? error.lines.map(line => inStepWords(line, "zones", zones.map(one => typeof one["kind"] === "string" ? one["kind"] : undefined))).join("\n") : error instanceof Error ? error.message : "Those zones aren't a flow.");
|
package/dist/flow-sort.d.ts
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SortDecision } from "./contracts/sort-answer.js";
|
|
2
|
+
import type { FlowSort, FlowStage } from "./flows.js";
|
|
2
3
|
export declare const JEV_URL = "https://openrouter.ai/api/alpha/decisions";
|
|
3
4
|
export declare const JEV_MODEL = "~typesafe/jev-latest";
|
|
4
|
-
export type SortNoteAnswer
|
|
5
|
-
id: string;
|
|
6
|
-
question: string;
|
|
7
|
-
kind: "score" | "yes-no";
|
|
8
|
-
answer: string;
|
|
9
|
-
sure: number;
|
|
10
|
-
};
|
|
11
|
-
/** What a sort step decided, kept on its run (decision_json) and shown on the card. */
|
|
12
|
-
export type SortDecision = {
|
|
13
|
-
model: string;
|
|
14
|
-
/** The answer as the zone names it, and how sure Jev was (0–1). */
|
|
15
|
-
answer: string;
|
|
16
|
-
sure: number;
|
|
17
|
-
sureAt: number;
|
|
18
|
-
/** Sure enough to act alone, and where the card went (null: it waited in the zone). */
|
|
19
|
-
confident: boolean;
|
|
20
|
-
to: string | null;
|
|
21
|
-
/** Every answer's chance, by name. */
|
|
22
|
-
chances: Record<string, number>;
|
|
23
|
-
notes: SortNoteAnswer[];
|
|
24
|
-
cost: number | null;
|
|
25
|
-
ms: number;
|
|
26
|
-
};
|
|
5
|
+
export type { SortDecision, SortNoteAnswer } from "./contracts/sort-answer.js";
|
|
27
6
|
export type SortCard = {
|
|
28
7
|
title: string;
|
|
29
8
|
description: string | null;
|
|
@@ -67,7 +46,10 @@ export type JevAsked = {
|
|
|
67
46
|
};
|
|
68
47
|
/** Ask Jev through OpenRouter. Trouble reaching it (or a refused key, or no credit) is a reason to try again, in words. */
|
|
69
48
|
export declare function askJev(fetcher: typeof fetch, key: string, request: JevRequest): Promise<JevAsked>;
|
|
70
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Jev's answers, read through the reply's contract (src/contracts/sort-answer.ts) and then against the zone's own
|
|
51
|
+
* options only; anything else is a problem, never a route. Confidences and chances are clamped into 0–1.
|
|
52
|
+
*/
|
|
71
53
|
export declare function readJevAnswers(stage: FlowStage, body: Record<string, unknown>, ms: number): SortDecision | {
|
|
72
54
|
problem: string;
|
|
73
55
|
};
|
|
@@ -79,4 +61,3 @@ export declare function sortLog(stage: FlowStage, decision: SortDecision): strin
|
|
|
79
61
|
export declare function parseSortDecision(json: string | null): SortDecision | null;
|
|
80
62
|
/** A card's chip on the canvas: the answer and how sure, and the first score's short level. */
|
|
81
63
|
export declare function sortChip(decision: SortDecision): string;
|
|
82
|
-
export {};
|
package/dist/flow-sort.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* is instructions: an answer is only ever one of the zone's own options.
|
|
9
9
|
*/
|
|
10
10
|
import { redactSecretLines, scanForSecrets } from "./evidence.js";
|
|
11
|
-
import { sortKeyOf } from "./
|
|
11
|
+
import { SORT_ROUTE_KEY, sortKeyOf, sortNoteKeyOf } from "./contracts/flow.js";
|
|
12
|
+
import { readJevReply } from "./contracts/sort-answer.js";
|
|
12
13
|
export const JEV_URL = "https://openrouter.ai/api/alpha/decisions";
|
|
13
14
|
export const JEV_MODEL = "~typesafe/jev-latest";
|
|
14
15
|
/** Jev reads up to 32,000 tokens; the card is kept well under that. */
|
|
@@ -39,10 +40,10 @@ export function sortState(card, earlier) {
|
|
|
39
40
|
/** The one request for a card: the zone's question over its answers, plus what it also notes. */
|
|
40
41
|
export function sortRequest(sort, state) {
|
|
41
42
|
const questions = {
|
|
42
|
-
|
|
43
|
+
[SORT_ROUTE_KEY]: { type: "choice", instructions: `${sort.question} Read the card's title and details.`, criteria: Object.fromEntries(sort.answers.map(one => [sortKeyOf(one.answer), one.means])) },
|
|
43
44
|
};
|
|
44
45
|
for (const note of sort.notes) {
|
|
45
|
-
questions[
|
|
46
|
+
questions[sortNoteKeyOf(note.id)] = note.kind === "score" && note.levels !== null
|
|
46
47
|
? { type: "score", instructions: note.question, criteria: note.levels }
|
|
47
48
|
: { type: "noul", instructions: note.question };
|
|
48
49
|
}
|
|
@@ -80,38 +81,42 @@ export async function askJev(fetcher, key, request) {
|
|
|
80
81
|
return { ok: true, body, ms: Date.now() - started };
|
|
81
82
|
}
|
|
82
83
|
const shortLevel = (level) => level.split(":")[0].trim();
|
|
83
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Jev's answers, read through the reply's contract (src/contracts/sort-answer.ts) and then against the zone's own
|
|
86
|
+
* options only; anything else is a problem, never a route. Confidences and chances are clamped into 0–1.
|
|
87
|
+
*/
|
|
84
88
|
export function readJevAnswers(stage, body, ms) {
|
|
85
89
|
const sort = stage.sort;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
90
|
+
const read = readJevReply(body);
|
|
91
|
+
if (!read.ok)
|
|
92
|
+
return { problem: `Jev's answer didn't say which way to go (${read.issues.map(issue => issue.line).join("; ")}).` };
|
|
93
|
+
const reply = read.value;
|
|
94
|
+
const route = reply.answers[SORT_ROUTE_KEY];
|
|
95
|
+
const picked = sort.answers.find(one => sortKeyOf(one.answer) === route.choice);
|
|
91
96
|
if (picked === undefined)
|
|
92
|
-
return { problem: "Jev picked an answer this zone doesn't have." };
|
|
97
|
+
return { problem: "answers.route.choice: Jev picked an answer this zone doesn't have." };
|
|
93
98
|
const number = (value) => typeof value === "number" && Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
|
|
94
|
-
const sure = number(route
|
|
95
|
-
const probabilities = (route
|
|
99
|
+
const sure = number(route.confidence);
|
|
100
|
+
const probabilities = (route.probabilities ?? {});
|
|
96
101
|
const chances = Object.fromEntries(sort.answers.map(one => [one.answer, Math.round(number(probabilities[sortKeyOf(one.answer)]) * 100) / 100]));
|
|
97
102
|
const notes = [];
|
|
98
103
|
for (const note of sort.notes) {
|
|
99
|
-
const said = answers
|
|
104
|
+
const said = reply.answers[sortNoteKeyOf(note.id)];
|
|
100
105
|
if (said === undefined)
|
|
101
106
|
continue;
|
|
102
|
-
if (note.kind === "score" && note.levels !== null && typeof said
|
|
103
|
-
const level = note.levels[Math.min(note.levels.length - 1, Math.max(0, Math.round(said
|
|
104
|
-
notes.push({ id: note.id, question: note.question, kind: "score", answer: level, sure: Math.round(number(said
|
|
107
|
+
if (note.kind === "score" && note.levels !== null && typeof said.score === "number") {
|
|
108
|
+
const level = note.levels[Math.min(note.levels.length - 1, Math.max(0, Math.round(said.score)))];
|
|
109
|
+
notes.push({ id: note.id, question: note.question, kind: "score", answer: level, sure: Math.round(number(said.confidence) * 100) / 100 });
|
|
105
110
|
}
|
|
106
|
-
else if (typeof said
|
|
107
|
-
const yes = number(said
|
|
111
|
+
else if (typeof said.noul === "number") {
|
|
112
|
+
const yes = number(said.noul);
|
|
108
113
|
notes.push({ id: note.id, question: note.question, kind: "yes-no", answer: yes >= 0.5 ? "yes" : "no", sure: Math.round(Math.max(yes, 1 - yes) * 100) / 100 });
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
const confident = sure >= sort.sureAt;
|
|
112
|
-
const usage =
|
|
117
|
+
const usage = reply.usage;
|
|
113
118
|
return {
|
|
114
|
-
model: typeof
|
|
119
|
+
model: typeof reply.model === "string" ? reply.model : JEV_MODEL,
|
|
115
120
|
answer: picked.answer, sure: Math.round(sure * 100) / 100, sureAt: sort.sureAt,
|
|
116
121
|
confident, to: confident ? picked.to : stage.onFail, chances, notes,
|
|
117
122
|
cost: typeof usage?.cost === "number" ? usage.cost : null, ms,
|
package/dist/flow-steps.js
CHANGED
|
@@ -44,6 +44,7 @@ import { ASKED, teammateReady, teammateTurn } from "./teammate-work.js";
|
|
|
44
44
|
import { budgetHoldWords, claudeMachineBilling, monthOf } from "./spend.js";
|
|
45
45
|
import { toolRefusal } from "./policy.js";
|
|
46
46
|
import { pullRequestStep } from "./flow-pull-request.js";
|
|
47
|
+
import { withStageHandoff } from "./contracts/stage-output.js";
|
|
47
48
|
const RETRY_MS = [5 * 60_000, 15 * 60_000];
|
|
48
49
|
/** A teammate's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
|
|
49
50
|
const TEAMMATE_RETRY_MS = [60_000, 5 * 60_000];
|
|
@@ -248,12 +249,12 @@ function settle(store, definition, stage, card, outcome, now, durationMs) {
|
|
|
248
249
|
}
|
|
249
250
|
store.finishFlowStep(card.id, card.entry, { state: outcome.state === "passed" ? "passed" : "failed", result: outcome.said, ...kept }, now);
|
|
250
251
|
// What the steps after it read: whole up to TEXT_LIMITS.stageOutput; longer is attached whole to the card's discussion,
|
|
251
|
-
// and they read a link to it. Never cut.
|
|
252
|
+
// and they read a link to it, while a task filed after it is given it whole (the card keeps it attached). Never cut.
|
|
252
253
|
const whole = outcome.output ?? outcome.said;
|
|
253
254
|
const passed = passOn(whole, TEXT_LIMITS.stageOutput, { label: "the card's discussion", href: flowCardHref(card.flow, card.id) });
|
|
254
255
|
if (passed.kept)
|
|
255
256
|
store.addFlowComment({ card: card.id, author: "flow", body: `What ${stage.title} produced, in full (${whole.length.toLocaleString("en-US")} characters):\n\n${whole}`, mentions: [] }, now);
|
|
256
|
-
store.updateFlowCard(card.id, { outputs:
|
|
257
|
+
store.updateFlowCard(card.id, { outputs: withStageHandoff(card.outputs, stage.id, { text: passed.text }), ...(passed.kept ? { attached: { [stage.id]: whole } } : {}), waiting: null }, now);
|
|
257
258
|
const titleOf = (id) => definition.stages.find(one => one.id === id)?.title ?? "another zone";
|
|
258
259
|
if (outcome.state === "passed") {
|
|
259
260
|
// A sort names where the card goes; one it isn't sure about is a person's to place.
|
package/dist/flows.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ContractIssue, type ContractResult } from "./contracts/contract.js";
|
|
2
|
-
import { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, type FlowDefinition, type FlowStage, type FlowStageKind, type FlowStepFields, type FlowWait, type ScriptLanguage, type TriggerInput } from "./contracts/flow.js";
|
|
3
|
-
export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME };
|
|
2
|
+
import { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, type FlowDefinition, type FlowStage, type FlowStageKind, type FlowStepFields, type FlowWait, type ScriptLanguage, type TriggerInput } from "./contracts/flow.js";
|
|
3
|
+
export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
|
|
4
4
|
export type { FlowChoice, FlowColor, FlowDefinition, FlowEmail, FlowLimit, FlowMergeMethod, FlowRequest, FlowSort, FlowSortAnswer, FlowSortNote, FlowStage, FlowStageKind, FlowStepInput, FlowTool, FlowWait, FlowZone, ScriptLanguage, } from "./contracts/flow.js";
|
|
5
5
|
/** What each kind is called and does, in the words the canvas uses. */
|
|
6
6
|
export declare const FLOW_KIND_WORDS: Record<FlowStageKind, {
|
|
@@ -8,8 +8,6 @@ export declare const FLOW_KIND_WORDS: Record<FlowStageKind, {
|
|
|
8
8
|
about: string;
|
|
9
9
|
}>;
|
|
10
10
|
export declare const LANGUAGE_WORDS: Record<ScriptLanguage, string>;
|
|
11
|
-
/** How an answer is named to Jev: its words as a short key. */
|
|
12
|
-
export declare const sortKeyOf: (answer: string) => string;
|
|
13
11
|
/** The default for "sure enough to act alone". */
|
|
14
12
|
export declare const SORT_SURE_AT = 0.8;
|
|
15
13
|
/** "3 days", "4 hours", "90 minutes", "1 week" (or a number of minutes) as minutes; null when it isn't one. */
|
package/dist/flows.js
CHANGED
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
51
51
|
import { createHash } from "node:crypto";
|
|
52
52
|
import { parseContract, readVersioned } from "./contracts/contract.js";
|
|
53
|
-
import {
|
|
54
|
-
|
|
53
|
+
import { stageReferenceProblems } from "./contracts/stage-output.js";
|
|
54
|
+
import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
|
|
55
|
+
export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
|
|
55
56
|
/** What each kind is called and does, in the words the canvas uses. */
|
|
56
57
|
export const FLOW_KIND_WORDS = {
|
|
57
58
|
inbox: { label: "Holding", about: "Cards wait here until someone moves them." },
|
|
@@ -74,8 +75,6 @@ export const FLOW_KIND_WORDS = {
|
|
|
74
75
|
done: { label: "Done", about: "The end of the flow." },
|
|
75
76
|
};
|
|
76
77
|
export const LANGUAGE_WORDS = { shell: "Shell", python: "Python", node: "Node" };
|
|
77
|
-
/** How an answer is named to Jev: its words as a short key. */
|
|
78
|
-
export const sortKeyOf = (answer) => answer.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "answer";
|
|
79
78
|
/** The default for "sure enough to act alone". */
|
|
80
79
|
export const SORT_SURE_AT = 0.8;
|
|
81
80
|
const HEADER = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
|
|
@@ -813,8 +812,13 @@ export function flowFromSteps(input, previous = null) {
|
|
|
813
812
|
});
|
|
814
813
|
// The drawing is checked as the canvas's is; its zones are the steps, in order, so a zone's problem names its step.
|
|
815
814
|
const drawn = readFlowDefinition({ version: FLOW_DEFINITION_VERSION, start: stages[0].id, stages });
|
|
815
|
+
const refuse = (issues) => new FlowContractError(issues.map(one => inStepWords(one.line, "steps", stages.map(stage => stage.kind))));
|
|
816
816
|
if (!drawn.ok)
|
|
817
|
-
throw
|
|
817
|
+
throw refuse(drawn.issues);
|
|
818
|
+
// A {{stage.…}} these steps add must be one its zone hands on (src/contracts/stage-output.ts); ones the flow had stay.
|
|
819
|
+
const references = stageReferenceProblems(drawn.value, previous);
|
|
820
|
+
if (references.length > 0)
|
|
821
|
+
throw refuse(references);
|
|
818
822
|
return drawn.value;
|
|
819
823
|
}
|
|
820
824
|
/** A choose step's options: each with its words and the step it goes to ("end", or no step, ignores the card); kept ones carry over. */
|