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/chat-actions.js
CHANGED
|
@@ -35,6 +35,8 @@ import { starterFlowOf, starterOf, startersFor, starterTerms, STARTER_FLOWS, swi
|
|
|
35
35
|
import { answerTeammateQuestion } from "./teammate-work.js";
|
|
36
36
|
import { callWords, checkRule, defaultRule, grantListed, revokeTool, ruleWords, setToolRules } from "./teammate-tools.js";
|
|
37
37
|
import { aboutYouOf, checkAboutYouLine, saveAboutYou, withAboutYouLine } from "./lead-about.js";
|
|
38
|
+
import { CHAT_ACTION_FIELDS, readChatActionRequest, readSharedAction, SHARED_ACTION_VERSION } from "./contracts/chat-actions.js";
|
|
39
|
+
export { CHAT_ACTION_FIELDS };
|
|
38
40
|
/** A tool from the lead's card: a common tool by its id, or the operator's own program or address. */
|
|
39
41
|
function toolSpecFromRequest(input) {
|
|
40
42
|
if (input["catalog"] !== undefined) {
|
|
@@ -118,6 +120,9 @@ export const CHAT_ACTIONS = {
|
|
|
118
120
|
task_cancel: { label: "Cancel task", protected: true, password: false },
|
|
119
121
|
task_resume: { label: "Resume task", protected: true, password: true },
|
|
120
122
|
};
|
|
123
|
+
// Every action has a schema (contracts/chat-actions.ts) and every schema an action: a mismatch fails to compile.
|
|
124
|
+
const SAME_ACTIONS = true;
|
|
125
|
+
void SAME_ACTIONS;
|
|
121
126
|
/** Protected actions a paired phone may confirm behind its own explicit
|
|
122
127
|
* yes/cancel challenge instead of the console's secure screen. Password
|
|
123
128
|
* actions and long or redacted terms never qualify. */
|
|
@@ -128,53 +133,6 @@ export function isChatAction(value) {
|
|
|
128
133
|
return typeof value === "string" && Object.hasOwn(CHAT_ACTIONS, value);
|
|
129
134
|
}
|
|
130
135
|
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
131
|
-
export const CHAT_ACTION_FIELDS = {
|
|
132
|
-
skill_import: ["repo", "content", "files"],
|
|
133
|
-
skill_enable: ["repo", "version"],
|
|
134
|
-
skill_disable: ["repo", "version"],
|
|
135
|
-
skill_restore: ["repo", "restore"],
|
|
136
|
-
skill_test: ["repo", "version", "sample", "nonce"],
|
|
137
|
-
knowledge_instructions: ["repo", "instructions"],
|
|
138
|
-
knowledge_save: ["repo", "title", "content", "id"],
|
|
139
|
-
knowledge_remove: ["repo", "id"],
|
|
140
|
-
knowledge_restore: ["repo", "restore"],
|
|
141
|
-
tool_add: ["repo", "catalog", "name", "command", "args", "url", "secrets", "about"],
|
|
142
|
-
tool_remove: ["repo", "name"],
|
|
143
|
-
flow_create: ["repo", "name", "definition", "trigger"],
|
|
144
|
-
flow_starter: ["repo", "starter"],
|
|
145
|
-
flow_edit: ["flow", "name", "definition"],
|
|
146
|
-
flow_card_add: ["flow", "title", "description", "zone"],
|
|
147
|
-
flow_card_move: ["card", "zone"],
|
|
148
|
-
flow_card_approve: ["card", "note"],
|
|
149
|
-
flow_card_send_back: ["card", "note"],
|
|
150
|
-
flow_card_choose: ["card", "choice", "note"],
|
|
151
|
-
flow_card_cancel: ["card"],
|
|
152
|
-
flow_card_comment: ["card", "note"],
|
|
153
|
-
flow_card_assign: ["card", "owner"],
|
|
154
|
-
flow_card_watch: ["card", "watching"],
|
|
155
|
-
flow_script_save: ["repo", "script"],
|
|
156
|
-
flow_trigger_add: ["flow", "trigger"],
|
|
157
|
-
flow_trigger_pause: ["trigger"],
|
|
158
|
-
flow_trigger_resume: ["trigger"],
|
|
159
|
-
flow_trigger_remove: ["trigger"],
|
|
160
|
-
teammate_create: ["repo", "template", "name", "soul"],
|
|
161
|
-
teammate_soul: ["teammate", "soul"],
|
|
162
|
-
teammate_state: ["teammate", "state"],
|
|
163
|
-
teammate_note: ["teammate", "note"],
|
|
164
|
-
teammate_answer: ["question", "choice", "text"],
|
|
165
|
-
teammate_tools: ["teammate", "tool", "change", "action", "use", "limitField", "limitOver", "undoWith"],
|
|
166
|
-
teammate_memory: ["teammate", "memory", "change", "text"],
|
|
167
|
-
teammate_routine: ["teammate", "change", "routine", "schedule", "text"],
|
|
168
|
-
teammate_undo: ["teammate", "call"],
|
|
169
|
-
kit_setup: ["repo", "kit"],
|
|
170
|
-
decision_record: ["repo", "claim", "why", "supersedes", "source"],
|
|
171
|
-
decision_retire: ["repo", "decision", "reason"],
|
|
172
|
-
lead_about_you: ["line", "replaces"],
|
|
173
|
-
scope_approve: ["task"],
|
|
174
|
-
result_accept: ["task", "run"],
|
|
175
|
-
task_cancel: ["task"],
|
|
176
|
-
task_resume: ["task", "run"],
|
|
177
|
-
};
|
|
178
136
|
const nonceHash = (value) => createHash("sha256").update(value).digest("hex");
|
|
179
137
|
function requireActor(store, who, repo, operation) {
|
|
180
138
|
if (!store.schemaCurrent())
|
|
@@ -277,9 +235,11 @@ const quoted = (value) => `“${value}”`;
|
|
|
277
235
|
export function prepareSharedAction(store, who, operation, input, root, now = new Date()) {
|
|
278
236
|
if (!isChatAction(operation))
|
|
279
237
|
throw Error("Choose an available action.");
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
238
|
+
// The action's own schema: its fields and their kinds, refused by path. The request itself is used as given, so a
|
|
239
|
+
// confirmation rebuilds the same stamp from the same bytes.
|
|
240
|
+
const read = readChatActionRequest(operation, input);
|
|
241
|
+
if (!read.ok)
|
|
242
|
+
throw Error(`This action can't be read — ${read.issues.map(issue => issue.line).join("; ")}.`);
|
|
283
243
|
if (operation === "lead_about_you") {
|
|
284
244
|
// The owner's own note: the card shows the line, and the line it replaces beside it.
|
|
285
245
|
const checked = checkAboutYouLine(input["line"]);
|
|
@@ -300,7 +260,7 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
300
260
|
const state = replaces === 0 ? {} : { was: lines[replaces - 1] };
|
|
301
261
|
const title = replaces === 0 ? "Remember about you" : "Update what your lead knows about you";
|
|
302
262
|
const stamp = hash({ operation, request, repo: "", state, terms, actor: who.name, generation: who.generation, ceiling: who.ceilingDigest });
|
|
303
|
-
return { operation, request, repo: "", title, terms, stamp, state };
|
|
263
|
+
return { version: SHARED_ACTION_VERSION, operation, request, repo: "", title, terms, stamp, state };
|
|
304
264
|
}
|
|
305
265
|
const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("teammate_") || operation.startsWith("kit_")
|
|
306
266
|
? null
|
|
@@ -965,22 +925,12 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
965
925
|
generation: who.generation,
|
|
966
926
|
ceiling: who.ceilingDigest,
|
|
967
927
|
});
|
|
968
|
-
return { operation, request, repo, title, terms, stamp, state };
|
|
928
|
+
return { version: SHARED_ACTION_VERSION, operation, request, repo, title, terms, stamp, state };
|
|
969
929
|
}
|
|
930
|
+
/** A saved proposal's action, read by its schema (contracts/chat-actions.ts); null when it can't be read. */
|
|
970
931
|
export function sharedActionPayload(value) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
typeof value["repo"] === "string" &&
|
|
974
|
-
typeof value["title"] === "string" &&
|
|
975
|
-
Array.isArray(value["terms"]) &&
|
|
976
|
-
value["terms"].every((t) => typeof t === "string") &&
|
|
977
|
-
value["request"] !== null &&
|
|
978
|
-
typeof value["request"] === "object" &&
|
|
979
|
-
!Array.isArray(value["request"]) &&
|
|
980
|
-
value["state"] !== null &&
|
|
981
|
-
typeof value["state"] === "object"
|
|
982
|
-
? value
|
|
983
|
-
: null;
|
|
932
|
+
const read = readSharedAction(value);
|
|
933
|
+
return read.ok ? read.value : null;
|
|
984
934
|
}
|
|
985
935
|
/** A shortened or redacted preview is not complete consent. This rule lives
|
|
986
936
|
* in the shared door, so a forged transport callback cannot bypass it. */
|
package/dist/chat-ask.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chatHash } from "./chat-delivery-state.js";
|
|
1
|
+
import { chatHash, partContent, savedChatPart } from "./chat-delivery-state.js";
|
|
2
2
|
import { MATE_ASK_OTHER } from "./store.js";
|
|
3
3
|
/** The question as text with its options, where no buttons are drawn. */
|
|
4
4
|
export function chatAskText(ask) {
|
|
@@ -10,7 +10,7 @@ export function chatAskButtons(state, part, now) {
|
|
|
10
10
|
const row = state.prepare("SELECT p.payload,e.channel,b.channel AS own FROM chat_part p JOIN chat_event e ON e.id=p.event JOIN chat_binding b ON b.id=e.binding WHERE p.id=?").get(part);
|
|
11
11
|
if (row === undefined)
|
|
12
12
|
return [];
|
|
13
|
-
const content =
|
|
13
|
+
const content = partContent(String(row["payload"]));
|
|
14
14
|
if ((content.channel ?? row["channel"]) !== row["own"])
|
|
15
15
|
return [];
|
|
16
16
|
const options = content.ask?.options ?? [];
|
|
@@ -32,9 +32,9 @@ function repaint(state, part, event, line, now) {
|
|
|
32
32
|
state.plan(event.id, [{ text: line }], now);
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
const before =
|
|
35
|
+
const before = partContent(String(row["payload"]));
|
|
36
36
|
const content = { text: `${before.text}\n\n${line}`.slice(0, 3400), edit: event.ts, ...(before.channel === undefined ? {} : { channel: before.channel }) };
|
|
37
|
-
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(
|
|
37
|
+
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(savedChatPart(content), part);
|
|
38
38
|
state.finish(event.id);
|
|
39
39
|
}
|
|
40
40
|
/** A tapped ask button, inside the action's transaction; false when the token isn't one. */
|
|
@@ -84,7 +84,7 @@ export function applyChatAskTap(options, event, binding, token, now) {
|
|
|
84
84
|
state.enqueue({
|
|
85
85
|
id: chatHash(`${state.channel}:ask:${event.id}`).slice(0, 32), installation: event.installation, binding: binding.id, kind: "message",
|
|
86
86
|
channel: event.channel, member: event.member, ts: event.ts, thread: event.thread,
|
|
87
|
-
payload:
|
|
87
|
+
payload: { text: option, originalLength: option.length }, created: now.toISOString(),
|
|
88
88
|
});
|
|
89
89
|
repaint(state, part, event, `You chose: ${option}`, now);
|
|
90
90
|
return true;
|
package/dist/chat-decide.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/** Decisions finish in the chat app. A pushed card about a result, a failed task, a plan waiting for approval or a
|
|
2
|
+
* pull request ready to merge carries buttons that act in place: Accept and finish, Request changes, Retry,
|
|
3
|
+
* Approve & start, Merge. Every act that changes something asks twice ("Accept and finish 'X'? [Yes] [Cancel]"),
|
|
4
|
+
* and each button is one opaque token bound to the exact result, receipt, plan or commit the card showed: a card a
|
|
5
|
+
* newer result, a finished task or a changed plan has overtaken says so and acts on nothing.
|
|
6
|
+
*
|
|
7
|
+
* Approving a plan or merging needs the password, which never goes in chat, so they act in chat only under a
|
|
8
|
+
* repository's signed mode that carries the chatApprove term, for the signer's own chat; otherwise, and for a plan
|
|
9
|
+
* that widens permissions, exceeds the mode's budget or touches protected paths, the card keeps its link.
|
|
10
|
+
*
|
|
11
|
+
* Channel-agnostic: Telegram renders these today, and Slack, Discord and Teams reach the same acts through the same
|
|
12
|
+
* tokens (the `channel` column). Every act runs through the door the console uses. */
|
|
13
|
+
import { DECIDE_ACTS } from "./contracts/chat-callback-rows.js";
|
|
1
14
|
import { type ModeTerms } from "./modes.js";
|
|
2
15
|
import { type VerifiedApprover } from "./principal.js";
|
|
3
16
|
import type { Scope } from "./scope.js";
|
|
4
17
|
import type { Store } from "./store.js";
|
|
5
18
|
/** The tokens live in chat_decide_action and the open "What should change?" in chat_decide_prompt (store.ts). */
|
|
6
|
-
export type DecideAct =
|
|
19
|
+
export type DecideAct = (typeof DECIDE_ACTS)[number];
|
|
7
20
|
export type DecideTarget = {
|
|
8
21
|
kind: "result";
|
|
9
22
|
taskId: string;
|
package/dist/chat-decide.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Channel-agnostic: Telegram renders these today, and Slack, Discord and Teams reach the same acts through the same
|
|
12
12
|
* tokens (the `channel` column). Every act runs through the door the console uses. */
|
|
13
|
+
import { DECIDE_ACTS, readDecideActionRow } from "./contracts/chat-callback-rows.js";
|
|
13
14
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
15
|
import { acceptAndCompleteAsOperator, assignmentOf, owedAcceptance } from "./assignment.js";
|
|
15
16
|
import { chatControlHref, chatResultHref } from "./chat-controls.js";
|
|
@@ -258,11 +259,12 @@ function retryStamp(store, taskId, who) {
|
|
|
258
259
|
export function offerFingerprint(offer) {
|
|
259
260
|
return sha(JSON.stringify(offer));
|
|
260
261
|
}
|
|
261
|
-
function readAction(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
function readAction(raw) {
|
|
263
|
+
const read = readDecideActionRow(raw);
|
|
264
|
+
if (!read.ok)
|
|
265
|
+
return read;
|
|
266
|
+
const { task_id: taskId, expires_at: expiresAt, consumed_at: consumedAt, created_at: _created, ...row } = read.value;
|
|
267
|
+
return { ok: true, value: { ...row, taskId, expiresAt, consumedAt } };
|
|
266
268
|
}
|
|
267
269
|
function mint(store, seat, message, spec, taskId, phase, now) {
|
|
268
270
|
const token = `d:${randomBytes(12).toString("hex")}`;
|
|
@@ -378,7 +380,11 @@ export function applyDecideTap(store, seat, input) {
|
|
|
378
380
|
const raw = input.token.startsWith("d:") ? store.handle.prepare("SELECT * FROM chat_decide_action WHERE token = ?").get(input.token) : undefined;
|
|
379
381
|
if (raw === undefined)
|
|
380
382
|
return null;
|
|
381
|
-
const
|
|
383
|
+
const read = readAction(raw);
|
|
384
|
+
// A saved button that can't be read acts on nothing, and says why.
|
|
385
|
+
if (!read.ok)
|
|
386
|
+
return { ack: `That button can't be read (${read.issues.map(issue => issue.line).join("; ")}). Nothing was done.`, ignored: true };
|
|
387
|
+
const row = read.value;
|
|
382
388
|
const { now } = input;
|
|
383
389
|
if (row.channel !== seat.channel || row.binding !== seat.binding || row.chat !== seat.chat || (row.message !== null && row.message !== input.message))
|
|
384
390
|
return { ack: "That button isn't for this chat.", ignored: true };
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type Store } from "./store.js";
|
|
2
|
+
import { chatSchema, chatTables } from "./contracts/chat-tables.js";
|
|
3
|
+
import { savedChatPart, type ChatContent, type ChatEventBody } from "./contracts/chat-content.js";
|
|
4
|
+
export { savedChatPart, type ChatContent, type ChatEventBody };
|
|
5
|
+
export { chatSchema, chatTables };
|
|
2
6
|
export type ChatIdentity = {
|
|
3
7
|
installation: string;
|
|
4
8
|
team?: string;
|
|
@@ -57,72 +61,6 @@ export type ChatRoom = {
|
|
|
57
61
|
binding: number;
|
|
58
62
|
cursor: number;
|
|
59
63
|
};
|
|
60
|
-
export type ChatContent = {
|
|
61
|
-
text: string;
|
|
62
|
-
/** Where the part is sent when not the binding's own DM: a room's channel. */
|
|
63
|
-
channel?: string;
|
|
64
|
-
proposal?: number;
|
|
65
|
-
image?: {
|
|
66
|
-
taskId: string;
|
|
67
|
-
run: number;
|
|
68
|
-
artifact: number;
|
|
69
|
-
sha256: string;
|
|
70
|
-
};
|
|
71
|
-
/** A screenshot sent with a result (result-shots.ts): it follows that result's message part, in its thread when
|
|
72
|
-
* the app allows; a refused upload becomes one plain line, and one removed by retention goes quietly. */
|
|
73
|
-
shot?: {
|
|
74
|
-
follows: number | null;
|
|
75
|
-
};
|
|
76
|
-
task?: string;
|
|
77
|
-
run?: number;
|
|
78
|
-
edit?: string;
|
|
79
|
-
phase?: "armed";
|
|
80
|
-
link?: {
|
|
81
|
-
label: string;
|
|
82
|
-
path: string;
|
|
83
|
-
};
|
|
84
|
-
/** More link buttons after `link`: [Look first] beside [Merge] or [Accept and finish]. */
|
|
85
|
-
also?: Array<{
|
|
86
|
-
label: string;
|
|
87
|
-
path: string;
|
|
88
|
-
}>;
|
|
89
|
-
/** A flow decision's buttons ride this part (v88): minted when it is planned. */
|
|
90
|
-
flow?: {
|
|
91
|
-
card: number;
|
|
92
|
-
entry: number;
|
|
93
|
-
actions: Array<"approve" | "edit" | "send-back">;
|
|
94
|
-
};
|
|
95
|
-
/** A flow's "Person chooses" buttons ride this part: minted when it is planned. */
|
|
96
|
-
choose?: {
|
|
97
|
-
card: number;
|
|
98
|
-
entry: number;
|
|
99
|
-
options: Array<{
|
|
100
|
-
choice: number;
|
|
101
|
-
label: string;
|
|
102
|
-
}>;
|
|
103
|
-
};
|
|
104
|
-
/** "Use this as your note?" Yes / No about the person's message `held` (an event id), for one choice's visit. */
|
|
105
|
-
note?: {
|
|
106
|
-
card: number;
|
|
107
|
-
entry: number;
|
|
108
|
-
held: string;
|
|
109
|
-
};
|
|
110
|
-
/** A teammate's question's buttons ride this part (v93): each option, then one to answer in words (choice null). */
|
|
111
|
-
question?: {
|
|
112
|
-
id: number;
|
|
113
|
-
choices: Array<{
|
|
114
|
-
choice: string | null;
|
|
115
|
-
label: string;
|
|
116
|
-
}>;
|
|
117
|
-
};
|
|
118
|
-
/** The lead's question to its owner rides this part: its options, then "Something else". */
|
|
119
|
-
ask?: {
|
|
120
|
-
turn: number;
|
|
121
|
-
options: string[];
|
|
122
|
-
};
|
|
123
|
-
/** The lead's own reply, already shaped (reply-shape.ts): the channel renders its bold anchors and labelled links in its own format. */
|
|
124
|
-
voice?: true;
|
|
125
|
-
};
|
|
126
64
|
export declare const chatHash: (text: string) => string;
|
|
127
65
|
export declare class ChatState {
|
|
128
66
|
readonly store: Store;
|
|
@@ -170,7 +108,10 @@ export declare class ChatState {
|
|
|
170
108
|
revoke(installation: string, now?: Date): void;
|
|
171
109
|
pairing(installation: string, approver: string, generation: number, now?: Date): string;
|
|
172
110
|
pair(identity: ChatIdentity, hash: string, member: string, channel: string, now: Date): ChatBinding | null;
|
|
173
|
-
|
|
111
|
+
/** Save a received event with its body (contracts/chat-content.ts), versioned. */
|
|
112
|
+
enqueue(event: Omit<ChatEvent, "session" | "state" | "next_at" | "payload"> & {
|
|
113
|
+
payload: ChatEventBody;
|
|
114
|
+
}): boolean;
|
|
174
115
|
event(id: string): ChatEvent | null;
|
|
175
116
|
next(installation: string, now: Date): ChatEvent | null;
|
|
176
117
|
defer(event: string, problem: string, until: Date): void;
|
|
@@ -181,11 +122,12 @@ export declare class ChatState {
|
|
|
181
122
|
lease(installation: string, owner: string, now: Date): boolean;
|
|
182
123
|
owns(installation: string, owner: string, now?: Date): boolean;
|
|
183
124
|
}
|
|
184
|
-
|
|
185
|
-
export declare
|
|
125
|
+
/** A saved part's content. One that can't be read is a delivery problem that names the field, never a guess. */
|
|
126
|
+
export declare function partContent(payload: string): ChatContent;
|
|
186
127
|
export declare class ChatDeliveryError extends Error {
|
|
187
128
|
readonly code: string;
|
|
188
129
|
readonly retryMs: number;
|
|
189
130
|
readonly uncertain: boolean;
|
|
190
|
-
|
|
131
|
+
readonly permanent: boolean;
|
|
132
|
+
constructor(code: string, retryMs?: number, uncertain?: boolean, permanent?: boolean);
|
|
191
133
|
}
|
|
@@ -1,136 +1,10 @@
|
|
|
1
1
|
/** Durable private-chat receipts. Each transport has its own tables and lease. */
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { MATE_ASK_TTL_MS } from "./store.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
approver TEXT NOT NULL, generation INTEGER NOT NULL, created TEXT NOT NULL, revoked TEXT
|
|
9
|
-
);
|
|
10
|
-
CREATE UNIQUE INDEX IF NOT EXISTS chat_one_binding_member ON chat_binding(installation, member) WHERE revoked IS NULL;
|
|
11
|
-
CREATE TABLE IF NOT EXISTS chat_pair (
|
|
12
|
-
hash TEXT PRIMARY KEY, installation TEXT NOT NULL, approver TEXT NOT NULL,
|
|
13
|
-
generation INTEGER NOT NULL, expires TEXT NOT NULL, consumed TEXT
|
|
14
|
-
);
|
|
15
|
-
CREATE TABLE IF NOT EXISTS chat_event (
|
|
16
|
-
id TEXT PRIMARY KEY, installation TEXT NOT NULL, binding INTEGER REFERENCES chat_binding(id),
|
|
17
|
-
kind TEXT NOT NULL CHECK(kind IN ('message','action','pair','notice')),
|
|
18
|
-
channel TEXT NOT NULL, member TEXT NOT NULL, ts TEXT NOT NULL, thread TEXT NOT NULL,
|
|
19
|
-
payload TEXT NOT NULL, created TEXT NOT NULL,
|
|
20
|
-
session INTEGER REFERENCES mate_session(id), state TEXT NOT NULL DEFAULT 'queued' CHECK(state IN ('queued','done','dropped')),
|
|
21
|
-
next_at TEXT, problem TEXT
|
|
22
|
-
);
|
|
23
|
-
CREATE INDEX IF NOT EXISTS chat_pending_event ON chat_event(installation,state,next_at);
|
|
24
|
-
CREATE TABLE IF NOT EXISTS chat_part (
|
|
25
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, event TEXT NOT NULL REFERENCES chat_event(id), ordinal INTEGER NOT NULL,
|
|
26
|
-
payload TEXT NOT NULL, state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending','sent','dropped')),
|
|
27
|
-
message TEXT, file TEXT, uploaded INTEGER NOT NULL DEFAULT 0, attempts INTEGER NOT NULL DEFAULT 0,
|
|
28
|
-
uncertain INTEGER NOT NULL DEFAULT 0, next_at TEXT, problem TEXT, created TEXT NOT NULL,
|
|
29
|
-
UNIQUE(event,ordinal)
|
|
30
|
-
);
|
|
31
|
-
CREATE TABLE IF NOT EXISTS chat_action (
|
|
32
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id),
|
|
33
|
-
proposal INTEGER NOT NULL REFERENCES mate_proposal(id), phase TEXT NOT NULL CHECK(phase IN ('confirm','dismiss','yes','cancel')),
|
|
34
|
-
expires TEXT NOT NULL, consumed TEXT
|
|
35
|
-
);
|
|
36
|
-
CREATE TABLE IF NOT EXISTS chat_progress (
|
|
37
|
-
binding INTEGER NOT NULL REFERENCES chat_binding(id), run INTEGER NOT NULL REFERENCES run(id),
|
|
38
|
-
part INTEGER NOT NULL REFERENCES chat_part(id), digest TEXT NOT NULL,
|
|
39
|
-
PRIMARY KEY(binding,run)
|
|
40
|
-
);
|
|
41
|
-
CREATE TABLE IF NOT EXISTS chat_runtime (
|
|
42
|
-
installation TEXT PRIMARY KEY, owner TEXT, lease_until TEXT, connected TEXT,
|
|
43
|
-
problem TEXT, retry_at TEXT, notification INTEGER NOT NULL DEFAULT 0
|
|
44
|
-
);
|
|
45
|
-
`;
|
|
46
|
-
/** v73: a chat's place in the shared team conversations — a channel follows one
|
|
47
|
-
* conversation, a DM may select one. Rows are revoked, never deleted. */
|
|
48
|
-
const CHAT_ROOM_SCHEMA = `
|
|
49
|
-
CREATE TABLE IF NOT EXISTS chat_room (
|
|
50
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, installation TEXT NOT NULL, chat TEXT NOT NULL,
|
|
51
|
-
kind TEXT NOT NULL CHECK(kind IN ('group','private')), conversation TEXT NOT NULL,
|
|
52
|
-
binding INTEGER NOT NULL REFERENCES chat_binding(id),
|
|
53
|
-
bound_by TEXT NOT NULL, bound TEXT NOT NULL, cursor INTEGER NOT NULL DEFAULT 0,
|
|
54
|
-
revoked TEXT, revoked_by TEXT
|
|
55
|
-
);
|
|
56
|
-
CREATE UNIQUE INDEX IF NOT EXISTS chat_room_live ON chat_room(installation, chat) WHERE revoked IS NULL;
|
|
57
|
-
CREATE UNIQUE INDEX IF NOT EXISTS chat_room_group ON chat_room(conversation) WHERE revoked IS NULL AND kind='group';
|
|
58
|
-
CREATE TABLE IF NOT EXISTS chat_meta (
|
|
59
|
-
installation TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, updated TEXT NOT NULL,
|
|
60
|
-
PRIMARY KEY(installation, key)
|
|
61
|
-
);
|
|
62
|
-
`;
|
|
63
|
-
/** v88: flow decisions in the chat app, as on Telegram (telegram-flow.ts).
|
|
64
|
-
* A button is one opaque token for one visit (card, entry) of one card, on
|
|
65
|
-
* the part it rides; Edit and Send back open a prompt the person's next
|
|
66
|
-
* message in their DM answers. */
|
|
67
|
-
const CHAT_FLOW_SCHEMA = `
|
|
68
|
-
CREATE TABLE IF NOT EXISTS chat_flow_action (
|
|
69
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id),
|
|
70
|
-
card INTEGER NOT NULL REFERENCES flow_card(id), entry INTEGER NOT NULL,
|
|
71
|
-
action TEXT NOT NULL CHECK(action IN ('approve','edit','send-back')), expires TEXT NOT NULL, consumed TEXT
|
|
72
|
-
);
|
|
73
|
-
CREATE INDEX IF NOT EXISTS chat_flow_action_visit ON chat_flow_action(card, entry);
|
|
74
|
-
CREATE TABLE IF NOT EXISTS chat_flow_prompt (
|
|
75
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, binding INTEGER NOT NULL REFERENCES chat_binding(id),
|
|
76
|
-
card INTEGER NOT NULL REFERENCES flow_card(id), entry INTEGER NOT NULL,
|
|
77
|
-
mode TEXT NOT NULL CHECK(mode IN ('edit','send-back')), created TEXT NOT NULL, expires TEXT NOT NULL, consumed TEXT
|
|
78
|
-
);
|
|
79
|
-
CREATE INDEX IF NOT EXISTS chat_flow_prompt_open ON chat_flow_prompt(binding, consumed);
|
|
80
|
-
`;
|
|
81
|
-
/** A flow's "Person chooses" zone in the chat app (flow-send.ts): one button per option (choice is its index, label its words
|
|
82
|
-
* when sent) for one visit of one card, on the part it rides. A reply in that notice's thread is the note; chat_flow_note
|
|
83
|
-
* is "Use this as your note?" Yes / No about one other message (held: its event; words: what it said, kept only until
|
|
84
|
-
* answered, as an event's own payload is cleared once it is handled), asked once per visit.
|
|
85
|
-
* Never in CHAT_TABLES: an older database has no such table until this schema creates it. */
|
|
86
|
-
const CHAT_FLOW_CHOICE_SCHEMA = `
|
|
87
|
-
CREATE TABLE IF NOT EXISTS chat_flow_choice (
|
|
88
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id),
|
|
89
|
-
card INTEGER NOT NULL REFERENCES flow_card(id), entry INTEGER NOT NULL,
|
|
90
|
-
choice INTEGER NOT NULL, label TEXT NOT NULL, expires TEXT NOT NULL, consumed TEXT
|
|
91
|
-
);
|
|
92
|
-
CREATE INDEX IF NOT EXISTS chat_flow_choice_visit ON chat_flow_choice(card, entry);
|
|
93
|
-
CREATE TABLE IF NOT EXISTS chat_flow_note (
|
|
94
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id),
|
|
95
|
-
card INTEGER NOT NULL REFERENCES flow_card(id), entry INTEGER NOT NULL,
|
|
96
|
-
held TEXT NOT NULL, words TEXT, answer TEXT NOT NULL CHECK(answer IN ('yes','no')), expires TEXT NOT NULL, consumed TEXT
|
|
97
|
-
);
|
|
98
|
-
CREATE INDEX IF NOT EXISTS chat_flow_note_visit ON chat_flow_note(card, entry);
|
|
99
|
-
`;
|
|
100
|
-
/** v93: a teammate's question in the chat app. One button per option (choice)
|
|
101
|
-
* and one to answer in words (choice NULL); that one opens a prompt the
|
|
102
|
-
* person's next message in their DM answers. Never in CHAT_TABLES: an older
|
|
103
|
-
* database has no such tables until this schema creates them. */
|
|
104
|
-
const CHAT_QUESTION_SCHEMA = `
|
|
105
|
-
CREATE TABLE IF NOT EXISTS chat_question_action (
|
|
106
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id),
|
|
107
|
-
question INTEGER NOT NULL REFERENCES teammate_question(id), choice TEXT, expires TEXT NOT NULL, consumed TEXT
|
|
108
|
-
);
|
|
109
|
-
CREATE INDEX IF NOT EXISTS chat_question_action_question ON chat_question_action(question);
|
|
110
|
-
CREATE TABLE IF NOT EXISTS chat_question_prompt (
|
|
111
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, binding INTEGER NOT NULL REFERENCES chat_binding(id),
|
|
112
|
-
question INTEGER NOT NULL REFERENCES teammate_question(id), created TEXT NOT NULL, expires TEXT NOT NULL, consumed TEXT
|
|
113
|
-
);
|
|
114
|
-
CREATE INDEX IF NOT EXISTS chat_question_prompt_open ON chat_question_prompt(binding, consumed);
|
|
115
|
-
`;
|
|
116
|
-
/** The lead's question to its owner (ask_owner) in the chat app: one button per option (choice is its index) and one for
|
|
117
|
-
* "Something else" (choice NULL). A tap sends the option as the owner's next message. Never in CHAT_TABLES. */
|
|
118
|
-
const CHAT_ASK_SCHEMA = `
|
|
119
|
-
CREATE TABLE IF NOT EXISTS chat_ask_action (
|
|
120
|
-
token TEXT PRIMARY KEY, part INTEGER NOT NULL REFERENCES chat_part(id) ON DELETE CASCADE,
|
|
121
|
-
turn INTEGER NOT NULL, choice INTEGER, expires TEXT NOT NULL, consumed TEXT
|
|
122
|
-
);
|
|
123
|
-
CREATE INDEX IF NOT EXISTS chat_ask_action_turn ON chat_ask_action(turn);
|
|
124
|
-
`;
|
|
125
|
-
const CHAT_TABLES = [
|
|
126
|
-
"chat_binding",
|
|
127
|
-
"chat_pair",
|
|
128
|
-
"chat_event",
|
|
129
|
-
"chat_part",
|
|
130
|
-
"chat_action",
|
|
131
|
-
"chat_progress",
|
|
132
|
-
"chat_runtime",
|
|
133
|
-
];
|
|
4
|
+
import { chatSchema, chatTables } from "./contracts/chat-tables.js";
|
|
5
|
+
import { readChatPart, savedChatEventBody, savedChatPart } from "./contracts/chat-content.js";
|
|
6
|
+
export { savedChatPart };
|
|
7
|
+
export { chatSchema, chatTables };
|
|
134
8
|
export const chatHash = (text) => createHash("sha256").update(text).digest("hex");
|
|
135
9
|
export class ChatState {
|
|
136
10
|
store;
|
|
@@ -275,8 +149,9 @@ export class ChatState {
|
|
|
275
149
|
return this.bindingFor(identity.installation, member);
|
|
276
150
|
});
|
|
277
151
|
}
|
|
152
|
+
/** Save a received event with its body (contracts/chat-content.ts), versioned. */
|
|
278
153
|
enqueue(event) {
|
|
279
|
-
return (Number(this.prepare("INSERT OR IGNORE INTO chat_event(id,installation,binding,kind,channel,member,ts,thread,payload,created) VALUES(?,?,?,?,?,?,?,?,?,?)").run(event.id, event.installation, event.binding, event.kind, event.channel, event.member, event.ts, event.thread, event.payload, event.created).changes) === 1);
|
|
154
|
+
return (Number(this.prepare("INSERT OR IGNORE INTO chat_event(id,installation,binding,kind,channel,member,ts,thread,payload,created) VALUES(?,?,?,?,?,?,?,?,?,?)").run(event.id, event.installation, event.binding, event.kind, event.channel, event.member, event.ts, event.thread, savedChatEventBody(event.payload), event.created).changes) === 1);
|
|
280
155
|
}
|
|
281
156
|
event(id) {
|
|
282
157
|
return (this.prepare("SELECT * FROM chat_event WHERE id=?").get(id) ?? null);
|
|
@@ -296,7 +171,7 @@ export class ChatState {
|
|
|
296
171
|
plan(event, parts, now) {
|
|
297
172
|
this.store.transact(() => {
|
|
298
173
|
for (const [ordinal, part] of parts.entries()) {
|
|
299
|
-
const inserted = this.prepare("INSERT OR IGNORE INTO chat_part(event,ordinal,payload,created) VALUES(?,?,?,?)").run(event, ordinal,
|
|
174
|
+
const inserted = this.prepare("INSERT OR IGNORE INTO chat_part(event,ordinal,payload,created) VALUES(?,?,?,?)").run(event, ordinal, savedChatPart(part), now.toISOString());
|
|
300
175
|
if (Number(inserted.changes) && part.proposal)
|
|
301
176
|
this.tokens(Number(inserted.lastInsertRowid), part.proposal, ["confirm", "dismiss"], now);
|
|
302
177
|
if (Number(inserted.changes) && part.question)
|
|
@@ -332,16 +207,23 @@ export class ChatState {
|
|
|
332
207
|
return !!this.prepare("SELECT 1 FROM chat_runtime WHERE installation=? AND owner=? AND lease_until>?").get(installation, owner, now.toISOString());
|
|
333
208
|
}
|
|
334
209
|
}
|
|
335
|
-
|
|
336
|
-
export
|
|
210
|
+
/** A saved part's content. One that can't be read is a delivery problem that names the field, never a guess. */
|
|
211
|
+
export function partContent(payload) {
|
|
212
|
+
const read = readChatPart(payload);
|
|
213
|
+
if (!read.ok)
|
|
214
|
+
throw new ChatDeliveryError(`This saved message can't be read: ${read.issues.map(issue => issue.line).join("; ")}`, 0, false, true);
|
|
215
|
+
return read.value;
|
|
216
|
+
}
|
|
337
217
|
export class ChatDeliveryError extends Error {
|
|
338
218
|
code;
|
|
339
219
|
retryMs;
|
|
340
220
|
uncertain;
|
|
341
|
-
|
|
221
|
+
permanent;
|
|
222
|
+
constructor(code, retryMs = 5000, uncertain = false, permanent = false) {
|
|
342
223
|
super(code);
|
|
343
224
|
this.code = code;
|
|
344
225
|
this.retryMs = retryMs;
|
|
345
226
|
this.uncertain = uncertain;
|
|
227
|
+
this.permanent = permanent;
|
|
346
228
|
}
|
|
347
229
|
}
|