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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.37 — 2026-10-06
|
|
4
|
+
|
|
5
|
+
- **Zod revamp, waves 1 and 2 finished.** Every handoff between a model and
|
|
6
|
+
Toolroll, and the context passed between steps, is now one schema each in
|
|
7
|
+
`src/contracts/`: the lead's 51 tools and the 25 MCP gateway tools, the
|
|
8
|
+
scout report, task sizing, reviewer findings, decisions, teammate turns and
|
|
9
|
+
sort answers, the lead's context bundle, project knowledge, memory and
|
|
10
|
+
skills, chat actions and channel callbacks, and the desktop coding handoff,
|
|
11
|
+
context and session record. A model gets the schema as `--json-schema`
|
|
12
|
+
where its harness takes one; a refusal names the field
|
|
13
|
+
(`steps[0].routes[0].goesTo: required`). Everything that was accepted before
|
|
14
|
+
is still accepted: tool calls with extra or loose arguments, model answers
|
|
15
|
+
with nulls or overlong text (clipped as before), saved reports, rows,
|
|
16
|
+
sessions, chat buttons and proposals. Replayed against real saved data
|
|
17
|
+
(scout reports, findings, decisions, 410 scopes and 796 routes, 279 chat
|
|
18
|
+
buttons, every saved flow and card), all of it reads the same.
|
|
19
|
+
- **Research can read PostHog.** A research run gets a connected service's
|
|
20
|
+
query tool (PostHog's `exec`) only when the access the service actually
|
|
21
|
+
granted is read-only. Otherwise it skips it and says why on the run and the
|
|
22
|
+
integration, with a **Reconnect read-only** action. Connecting PostHog as
|
|
23
|
+
usual is unchanged, and builds keep every tool. Connections made before this
|
|
24
|
+
release didn't record what was granted, so research skips them until you
|
|
25
|
+
reconnect read-only.
|
|
26
|
+
- **A question's recommendation can carry its reason.** `connect: because…`
|
|
27
|
+
now counts as recommending `connect`, instead of failing the question.
|
|
28
|
+
- **A Telegram update Toolroll can't read** is logged and answered OK, so
|
|
29
|
+
Telegram doesn't keep resending it. A Slack, Discord or Teams message part
|
|
30
|
+
that can't be read is dropped with its reason instead of retried forever.
|
|
31
|
+
- **Rolling back:** flow cards written by 0.9.37 read as empty in 0.9.36, so a
|
|
32
|
+
rollback loses the step outputs of cards still in progress.
|
|
33
|
+
- **Scopes, acceptance criteria and sealed routes have one contract.** A task's
|
|
34
|
+
scope (goal, exclusions, touch paths, rubric) and an acceptance criterion are
|
|
35
|
+
described once from the plan contract's own fields (`src/contracts/scope.ts`);
|
|
36
|
+
a sealed agent route, in both versions it has been saved in, with its
|
|
37
|
+
overrides and task size, is one schema (`src/contracts/route.ts`). The shared
|
|
38
|
+
rubric input schema is ready for the lead-tool migration. Every saved scope,
|
|
39
|
+
standing order and sealed route reads and digests exactly as before, so no
|
|
40
|
+
approval changes.
|
|
41
|
+
Nothing new is refused: a scope still names up to 50 paths and a criterion
|
|
42
|
+
still ignores keys it doesn't use. Rubric and route refusals are now
|
|
43
|
+
field-named lines (`acceptance[0].statement over 1,000 bytes`), and a route
|
|
44
|
+
made by a newer Toolroll says so.
|
|
45
|
+
- **What a flow's steps hand on has one contract.** A finished zone's
|
|
46
|
+
`{{stage.…}}` words, a card's saved outputs, and what Send to me and Person
|
|
47
|
+
chooses send are each described once (`src/contracts/stage-output.ts`,
|
|
48
|
+
`flow-send.ts`, `flow-card.ts`). Saved cards and sends read exactly as
|
|
49
|
+
before. A build or research task filed after a step whose output was too
|
|
50
|
+
long to pass on now gets that output whole, not just a link to it.
|
|
51
|
+
- **Tightened, on purpose.** Saving a flow with a new `{{stage.…}}` that its
|
|
52
|
+
zone doesn't hand on is refused, naming where:
|
|
53
|
+
`stages[2].instructions: stage.research.unknown is not available (Research
|
|
54
|
+
hands on {{stage.research}}, {{stage.research.items}} and
|
|
55
|
+
{{stage.research.report}})`. This covers the canvas, `toolroll flows
|
|
56
|
+
create/edit/import`, the lead and templates. A reference a flow already had
|
|
57
|
+
is never refused, and nothing changes for flows that are only read or run.
|
|
58
|
+
Card outputs and kept sends are saved with a version; ones a newer Toolroll
|
|
59
|
+
wrote are refused rather than misread or overwritten.
|
|
60
|
+
|
|
3
61
|
## 0.9.36 — 2026-10-06
|
|
4
62
|
|
|
5
63
|
- **Flows have one contract.** Every kind of zone, step, route, answer,
|
|
@@ -1,87 +1,52 @@
|
|
|
1
1
|
import { type AssignmentSnapshot } from "./assignment.js";
|
|
2
2
|
import type { Store } from "./store.js";
|
|
3
|
-
|
|
4
|
-
export
|
|
3
|
+
import { type AssignmentInput, type AssignmentOperation } from "./contracts/gateway-tools.js";
|
|
4
|
+
export declare const ASSIGNMENT_ACTIONS: AssignmentOperation[];
|
|
5
|
+
export type { AssignmentOperation };
|
|
5
6
|
type Args = Record<string, unknown>;
|
|
6
7
|
type Failure = {
|
|
7
8
|
ok: false;
|
|
8
9
|
reason: string;
|
|
9
10
|
message: string;
|
|
10
11
|
};
|
|
12
|
+
/** The gateway's assignment tools: names and words. Each one's input is ASSIGNMENT_INPUTS[operation]
|
|
13
|
+
* (src/contracts/gateway-tools.ts), which `toolroll assignment` reads its flags with too. */
|
|
11
14
|
export declare const ASSIGNMENT_TOOLS: readonly [{
|
|
12
15
|
readonly name: "get_assignment";
|
|
13
16
|
readonly operation: "show";
|
|
14
17
|
readonly description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts.";
|
|
15
|
-
readonly inputSchema: {
|
|
16
|
-
type: string;
|
|
17
|
-
properties: Record<string, unknown>;
|
|
18
|
-
required: string[];
|
|
19
|
-
additionalProperties: boolean;
|
|
20
|
-
};
|
|
21
18
|
}, {
|
|
22
19
|
readonly name: "list_assignment_updates";
|
|
23
20
|
readonly operation: "updates";
|
|
24
21
|
readonly description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything.";
|
|
25
|
-
readonly inputSchema: {
|
|
26
|
-
type: string;
|
|
27
|
-
properties: Record<string, unknown>;
|
|
28
|
-
required: string[];
|
|
29
|
-
additionalProperties: boolean;
|
|
30
|
-
};
|
|
31
22
|
}, {
|
|
32
23
|
readonly name: "claim_assignment";
|
|
33
24
|
readonly operation: "claim";
|
|
34
25
|
readonly description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority.";
|
|
35
|
-
readonly inputSchema: {
|
|
36
|
-
type: string;
|
|
37
|
-
properties: Record<string, unknown>;
|
|
38
|
-
required: string[];
|
|
39
|
-
additionalProperties: boolean;
|
|
40
|
-
};
|
|
41
26
|
}, {
|
|
42
27
|
readonly name: "acknowledge_assignment";
|
|
43
28
|
readonly operation: "check";
|
|
44
29
|
readonly description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy.";
|
|
45
|
-
readonly inputSchema: {
|
|
46
|
-
type: string;
|
|
47
|
-
properties: Record<string, unknown>;
|
|
48
|
-
required: string[];
|
|
49
|
-
additionalProperties: boolean;
|
|
50
|
-
};
|
|
51
30
|
}, {
|
|
52
31
|
readonly name: "get_assignment_brief";
|
|
53
32
|
readonly operation: "brief";
|
|
54
33
|
readonly description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation.";
|
|
55
|
-
readonly inputSchema: {
|
|
56
|
-
type: string;
|
|
57
|
-
properties: Record<string, unknown>;
|
|
58
|
-
required: string[];
|
|
59
|
-
additionalProperties: boolean;
|
|
60
|
-
};
|
|
61
34
|
}, {
|
|
62
35
|
readonly name: "get_assignment_inbox";
|
|
63
36
|
readonly operation: "inbox";
|
|
64
37
|
readonly description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work.";
|
|
65
|
-
readonly inputSchema: {
|
|
66
|
-
type: string;
|
|
67
|
-
properties: Record<string, unknown>;
|
|
68
|
-
required: string[];
|
|
69
|
-
additionalProperties: boolean;
|
|
70
|
-
};
|
|
71
38
|
}, {
|
|
72
39
|
readonly name: "acknowledge_assignment_delivery";
|
|
73
40
|
readonly operation: "ack";
|
|
74
41
|
readonly description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision.";
|
|
75
|
-
readonly inputSchema: {
|
|
76
|
-
type: string;
|
|
77
|
-
properties: Record<string, unknown>;
|
|
78
|
-
required: string[];
|
|
79
|
-
additionalProperties: boolean;
|
|
80
|
-
};
|
|
81
42
|
}];
|
|
82
|
-
/** The CLI and MCP
|
|
83
|
-
*
|
|
84
|
-
|
|
43
|
+
/** The CLI and MCP read an assignment call with the same schema; MCP reads it before dispatch too, and this protects
|
|
44
|
+
* direct adapter callers. A refusal is its path-named lines; a ref or project path with control characters, which
|
|
45
|
+
* JSON Schema can't say, is refused after parsing. */
|
|
46
|
+
export declare function readAssignmentArgs<O extends AssignmentOperation>(operation: O, args: Args): {
|
|
47
|
+
ok: true;
|
|
48
|
+
value: AssignmentInput<O>;
|
|
49
|
+
} | Failure;
|
|
85
50
|
/** Every read and mutation authenticates inside the same transaction as its
|
|
86
51
|
* projection. A cached name/allowlist cannot keep a revoked identity alive. */
|
|
87
52
|
export declare function assignmentForCoordinator(store: Store, token: string, operation: AssignmentOperation, args: Args, now: Date, evidenceRoot?: string): Failure | {
|
|
@@ -179,4 +144,3 @@ export declare function coordinatorToken(flags: Map<string, string | true>, env:
|
|
|
179
144
|
token: string | null;
|
|
180
145
|
} | Failure;
|
|
181
146
|
export declare function runAssignmentCommand(positional: readonly string[], flags: Map<string, string | true>, context: AssignmentCliContext): number;
|
|
182
|
-
export {};
|
|
@@ -8,47 +8,36 @@ import { assignmentCatchUp } from "./assignment-brief.js";
|
|
|
8
8
|
import { assignmentInbox, acknowledgeAssignmentDelivery } from "./assignment-delivery.js";
|
|
9
9
|
import { syncAssignmentStatuses } from "./assignment-status.js";
|
|
10
10
|
import { envelopeJson } from "./envelope.js";
|
|
11
|
-
|
|
11
|
+
import { parseContract } from "./contracts/contract.js";
|
|
12
|
+
import { ASSIGNMENT_INPUTS } from "./contracts/gateway-tools.js";
|
|
13
|
+
export const ASSIGNMENT_ACTIONS = Object.keys(ASSIGNMENT_INPUTS);
|
|
12
14
|
const failure = (reason, message) => ({ ok: false, reason, message });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const batchId = { type: "string", minLength: 32, maxLength: 32, pattern: "^[a-f0-9]{32}$" };
|
|
16
|
-
const consumer = { type: "string", minLength: 1, maxLength: 64, pattern: "^[a-z0-9][a-z0-9-]{0,63}$" };
|
|
17
|
-
const object = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
|
|
15
|
+
/** The gateway's assignment tools: names and words. Each one's input is ASSIGNMENT_INPUTS[operation]
|
|
16
|
+
* (src/contracts/gateway-tools.ts), which `toolroll assignment` reads its flags with too. */
|
|
18
17
|
export const ASSIGNMENT_TOOLS = [
|
|
19
|
-
{ name: "get_assignment", operation: "show", description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts."
|
|
20
|
-
{ name: "list_assignment_updates", operation: "updates", description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything."
|
|
21
|
-
{ name: "claim_assignment", operation: "claim", description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority."
|
|
22
|
-
{ name: "acknowledge_assignment", operation: "check", description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy."
|
|
23
|
-
{ name: "get_assignment_brief", operation: "brief", description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation."
|
|
24
|
-
{ name: "get_assignment_inbox", operation: "inbox", description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work."
|
|
25
|
-
{ name: "acknowledge_assignment_delivery", operation: "ack", description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision."
|
|
18
|
+
{ name: "get_assignment", operation: "show", description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts." },
|
|
19
|
+
{ name: "list_assignment_updates", operation: "updates", description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything." },
|
|
20
|
+
{ name: "claim_assignment", operation: "claim", description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority." },
|
|
21
|
+
{ name: "acknowledge_assignment", operation: "check", description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy." },
|
|
22
|
+
{ name: "get_assignment_brief", operation: "brief", description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation." },
|
|
23
|
+
{ name: "get_assignment_inbox", operation: "inbox", description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work." },
|
|
24
|
+
{ name: "acknowledge_assignment_delivery", operation: "ack", description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision." },
|
|
26
25
|
];
|
|
27
|
-
/** The CLI and MCP
|
|
28
|
-
*
|
|
29
|
-
|
|
26
|
+
/** The CLI and MCP read an assignment call with the same schema; MCP reads it before dispatch too, and this protects
|
|
27
|
+
* direct adapter callers. A refusal is its path-named lines; a ref or project path with control characters, which
|
|
28
|
+
* JSON Schema can't say, is refused after parsing. */
|
|
29
|
+
export function readAssignmentArgs(operation, args) {
|
|
30
30
|
if (!ASSIGNMENT_ACTIONS.includes(operation))
|
|
31
|
-
return "Choose show, updates, claim, check, brief, inbox, or ack.";
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return "Use the exact 64-character receipt digest from assignment show.";
|
|
40
|
-
if ((operation === "inbox" || operation === "ack") && (typeof args["consumer"] !== "string" || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(args["consumer"])))
|
|
41
|
-
return "Use a stable consumer name with 1–64 lowercase letters, digits or hyphens.";
|
|
42
|
-
if (operation === "ack" && (typeof args["batchId"] !== "string" || !/^[a-f0-9]{32}$/.test(args["batchId"])))
|
|
43
|
-
return "Use the exact batchId from assignment inbox.";
|
|
44
|
-
if (args["repo"] !== undefined && (typeof args["repo"] !== "string" || args["repo"].length < 1 || args["repo"].length > 4096 || /[\u0000-\u001f\u007f]/.test(args["repo"])))
|
|
45
|
-
return "Choose a project path without control characters.";
|
|
46
|
-
for (const [name, min, max] of [["after", 0, Number.MAX_SAFE_INTEGER], ["limit", 1, operation === "brief" ? 25 : 100]]) {
|
|
47
|
-
const value = args[name];
|
|
48
|
-
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max))
|
|
49
|
-
return `${name} must be an integer from ${min} to ${max}.`;
|
|
31
|
+
return failure("usage", "Choose show, updates, claim, check, brief, inbox, or ack.");
|
|
32
|
+
const read = parseContract(ASSIGNMENT_INPUTS[operation], args);
|
|
33
|
+
if (!read.ok)
|
|
34
|
+
return failure("usage", read.issues.map(one => one.line).join("; "));
|
|
35
|
+
const value = read.value;
|
|
36
|
+
for (const field of ["ref", "repo"]) {
|
|
37
|
+
if (typeof value[field] === "string" && /[\u0000-\u001f\u007f]/.test(value[field]))
|
|
38
|
+
return failure("usage", `${field}: must not contain control characters`);
|
|
50
39
|
}
|
|
51
|
-
return
|
|
40
|
+
return { ok: true, value };
|
|
52
41
|
}
|
|
53
42
|
function readAssignment(store, operation, args, now, access, root) {
|
|
54
43
|
if (operation === "brief")
|
|
@@ -61,9 +50,10 @@ function readAssignment(store, operation, args, now, access, root) {
|
|
|
61
50
|
/** Every read and mutation authenticates inside the same transaction as its
|
|
62
51
|
* projection. A cached name/allowlist cannot keep a revoked identity alive. */
|
|
63
52
|
export function assignmentForCoordinator(store, token, operation, args, now, evidenceRoot) {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
return
|
|
53
|
+
const read = readAssignmentArgs(operation, args);
|
|
54
|
+
if (!read.ok)
|
|
55
|
+
return read;
|
|
56
|
+
args = read.value;
|
|
67
57
|
// Authenticate before maintenance, then authenticate again inside delivery.
|
|
68
58
|
// Reconciliation owns one short transaction per family and cannot sit under
|
|
69
59
|
// the delivery transaction's writer reservation.
|
|
@@ -225,11 +215,11 @@ export function runAssignmentCommand(positional, flags, context) {
|
|
|
225
215
|
for (const flag of operationFlags) {
|
|
226
216
|
const value = flags.get(flag);
|
|
227
217
|
if (value !== undefined)
|
|
228
|
-
args[flag === "batch" ? "batchId" : flag] = ["after", "limit"].includes(flag)
|
|
218
|
+
args[flag === "batch" ? "batchId" : flag] = ["after", "limit"].includes(flag) && typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
|
|
229
219
|
}
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
232
|
-
return emit(
|
|
220
|
+
const read = readAssignmentArgs(operation, args);
|
|
221
|
+
if (!read.ok)
|
|
222
|
+
return emit(read);
|
|
233
223
|
const credential = coordinatorToken(flags, context.env ?? process.env);
|
|
234
224
|
if ("ok" in credential)
|
|
235
225
|
return emit(credential);
|
package/dist/build-review.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
2
|
import type { Runner } from "./builder.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type BuildFinding } from "./contracts/review-findings.js";
|
|
4
4
|
export { buildReviewOf, buildReviewHeadline, buildReviewLines, findingWords, type BuildReviewView } from "./review-switch.js";
|
|
5
5
|
export declare const AUTOMATIC_REVIEWER = "automatic review";
|
|
6
6
|
/**
|
package/dist/build-review.js
CHANGED
|
@@ -29,6 +29,7 @@ import { runCheckLevel } from "./check-levels.js";
|
|
|
29
29
|
import { requestResultChanges } from "./result-actions.js";
|
|
30
30
|
import { revisionSourceOf } from "./result-review.js";
|
|
31
31
|
import { parseBuildFindings, REVIEW_PATCH_NAME } from "./reviewer.js";
|
|
32
|
+
import { FINDINGS_VERSION } from "./contracts/review-findings.js";
|
|
32
33
|
import { heartbeat as runnerHeartbeat } from "./runner.js";
|
|
33
34
|
import { TELEGRAM_TOKEN_ENVS } from "./names.js";
|
|
34
35
|
import { CLAUDE_LIMITS } from "./scope.js";
|
|
@@ -90,7 +91,7 @@ function queueLocked(store, repo, runId, root, now) {
|
|
|
90
91
|
}
|
|
91
92
|
function settle(store, runId, state, fields, now) {
|
|
92
93
|
store.handle.prepare("UPDATE build_review SET state = ?, findings_json = ?, reason = ?, sent_back_as = ?, finished_at = ? WHERE run = ? AND state = 'pending'")
|
|
93
|
-
.run(state, fields.findings === undefined ? null : JSON.stringify({ version:
|
|
94
|
+
.run(state, fields.findings === undefined ? null : JSON.stringify({ version: FINDINGS_VERSION, findings: fields.findings }), fields.reason ?? null, fields.sentBackAs ?? null, now.toISOString(), runId);
|
|
94
95
|
store.bumpWake();
|
|
95
96
|
}
|
|
96
97
|
/** Only the first automatic send-back in a task family: its revision's HIGH goes to the person. */
|
|
@@ -39,7 +39,7 @@ export declare function readAcceptanceEvidence(store: Store, who: VerifiedApprov
|
|
|
39
39
|
requirement: string | null;
|
|
40
40
|
state: string;
|
|
41
41
|
details: (string | null)[];
|
|
42
|
-
requiredEvidence: readonly
|
|
42
|
+
requiredEvidence: readonly ("changed-path" | "check" | "manual-review" | "screenshot")[];
|
|
43
43
|
reviewer: {
|
|
44
44
|
judgement: import("./proof.js").CriterionJudgementWord;
|
|
45
45
|
note: string | null;
|
package/dist/chat-actions.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Store } from "./store.js";
|
|
2
2
|
import { type VerifiedApprover } from "./principal.js";
|
|
3
|
+
import { CHAT_ACTION_FIELDS, type SharedAction } from "./contracts/chat-actions.js";
|
|
4
|
+
export { CHAT_ACTION_FIELDS, type SharedAction };
|
|
3
5
|
export declare const CHAT_ACTIONS: {
|
|
4
6
|
readonly skill_import: {
|
|
5
7
|
readonly label: "Add skill";
|
|
@@ -235,20 +237,11 @@ export declare const CHALLENGE_ACTIONS: ReadonlySet<ChatAction>;
|
|
|
235
237
|
/** Actions about the person themselves, in no project: their card carries no project (repo ""). */
|
|
236
238
|
export declare const OWNER_ACTIONS: ReadonlySet<ChatAction>;
|
|
237
239
|
export declare function isChatAction(value: unknown): value is ChatAction;
|
|
238
|
-
export type SharedAction = {
|
|
239
|
-
operation: ChatAction;
|
|
240
|
-
request: Record<string, unknown>;
|
|
241
|
-
repo: string;
|
|
242
|
-
title: string;
|
|
243
|
-
terms: string[];
|
|
244
|
-
stamp: string;
|
|
245
|
-
state: Record<string, unknown>;
|
|
246
|
-
};
|
|
247
|
-
export declare const CHAT_ACTION_FIELDS: Record<ChatAction, readonly string[]>;
|
|
248
240
|
/** No writes, including at preview. Store-derived snapshots are never accepted
|
|
249
241
|
* from a model. Rebuilding the same request before execution catches changes. */
|
|
250
242
|
export declare function prepareSharedAction(store: Store, who: VerifiedApprover, operation: ChatAction, input: Record<string, unknown>, root?: string, now?: Date): SharedAction;
|
|
251
|
-
|
|
243
|
+
/** A saved proposal's action, read by its schema (contracts/chat-actions.ts); null when it can't be read. */
|
|
244
|
+
export declare function sharedActionPayload(value: unknown): SharedAction | null;
|
|
252
245
|
export declare function sharedActionNeedsReview(action: SharedAction): boolean;
|
|
253
246
|
/** Whether a paired phone may confirm this protected action behind its own
|
|
254
247
|
* explicit challenge: the action is on the challenge list, needs no
|
|
@@ -256,7 +249,20 @@ export declare function sharedActionNeedsReview(action: SharedAction): boolean;
|
|
|
256
249
|
export declare function sharedActionAllowsChallenge(action: SharedAction): boolean;
|
|
257
250
|
export declare function sharedActionReviewPath(id: number): string;
|
|
258
251
|
export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
|
|
259
|
-
payload:
|
|
252
|
+
payload: {
|
|
253
|
+
version: 1;
|
|
254
|
+
operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "task_cancel" | "task_resume" | "teammate_answer" | "teammate_create" | "teammate_memory" | "teammate_note" | "teammate_routine" | "teammate_soul" | "teammate_state" | "teammate_tools" | "teammate_undo" | "tool_add" | "tool_remove";
|
|
255
|
+
request: {
|
|
256
|
+
[x: string]: unknown;
|
|
257
|
+
};
|
|
258
|
+
repo: string;
|
|
259
|
+
title: string;
|
|
260
|
+
terms: string[];
|
|
261
|
+
stamp: string;
|
|
262
|
+
state: {
|
|
263
|
+
[x: string]: unknown;
|
|
264
|
+
};
|
|
265
|
+
};
|
|
260
266
|
nonce: string;
|
|
261
267
|
};
|
|
262
268
|
export type SharedActionOptions = {
|
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 };
|