toolroll 0.9.15 → 0.9.16
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 +19 -0
- package/dist/assignment-brief.d.ts +10 -0
- package/dist/assignment-brief.js +49 -26
- package/dist/assignment.d.ts +8 -7
- package/dist/assignment.js +13 -8
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +22 -22
- package/dist/browser-crew.js +1 -0
- package/dist/browser-workspace.d.ts +5 -0
- package/dist/chat-ask.d.ts +28 -0
- package/dist/chat-ask.js +77 -0
- package/dist/chat-channel.js +6 -1
- package/dist/chat-controls.d.ts +8 -0
- package/dist/chat-controls.js +3 -0
- package/dist/chat-delivery-state.d.ts +6 -1
- package/dist/chat-delivery-state.js +15 -2
- package/dist/chat-delivery.js +20 -3
- package/dist/chat-quiet.js +4 -2
- package/dist/chat-voice.d.ts +3 -6
- package/dist/chat-voice.js +8 -2
- package/dist/discord-chat.js +10 -0
- package/dist/lead-commitments.d.ts +84 -0
- package/dist/lead-commitments.js +208 -0
- package/dist/lead-context.d.ts +32 -3
- package/dist/lead-context.js +133 -19
- package/dist/lead-follow.js +3 -0
- package/dist/lead-identity.d.ts +23 -0
- package/dist/lead-identity.js +33 -0
- package/dist/lead-voice.d.ts +12 -9
- package/dist/lead-voice.js +25 -16
- package/dist/ledger-view.js +3 -2
- package/dist/mate-cli.d.ts +1 -1
- package/dist/mate-cli.js +8 -2
- package/dist/mate-contract.d.ts +1 -1
- package/dist/mate-contract.js +11 -4
- package/dist/mate-progress.js +5 -0
- package/dist/mate-tools.d.ts +18 -1
- package/dist/mate-tools.js +250 -6
- package/dist/mate.d.ts +9 -0
- package/dist/mate.js +34 -38
- package/dist/operate.js +2 -1
- package/dist/project-delete.js +3 -1
- package/dist/result-acts.d.ts +14 -0
- package/dist/result-acts.js +19 -0
- package/dist/retention.js +6 -0
- package/dist/serve.js +111 -29
- package/dist/slack-chat.js +10 -0
- package/dist/store.d.ts +51 -0
- package/dist/store.js +115 -1
- package/dist/surface.js +2 -2
- package/dist/task-status.d.ts +5 -15
- package/dist/task-status.js +18 -5
- package/dist/team-runtime.js +2 -1
- package/dist/teams-chat.js +4 -1
- package/dist/telegram-mate.d.ts +4 -1
- package/dist/telegram-mate.js +17 -1
- package/dist/telegram-progress.js +3 -2
- package/dist/telegram.js +43 -3
- package/dist/test-temp.js +1 -1
- package/dist/when-html.d.ts +8 -3
- package/dist/when-html.js +23 -3
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +9 -6
- package/dist/work-summary.d.ts +1 -1
- package/dist/workspace-ui.d.ts +2 -14
- package/dist/workspace-ui.js +4 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.16 — 2026-10-03
|
|
4
|
+
|
|
5
|
+
- **Name your lead.** Settings → Lead takes a name and a short persona. The
|
|
6
|
+
lead uses them wherever it speaks.
|
|
7
|
+
- **The lead knows who it's talking to.** Every turn it sees your first name,
|
|
8
|
+
today's date, the channel it's on, what needs you, and your projects by name
|
|
9
|
+
with their decisions.
|
|
10
|
+
- **It checks before it speaks.** It says what it checked and marks guesses.
|
|
11
|
+
It searches project memory before recommending anything, and it checks an
|
|
12
|
+
integration before promising work that needs it.
|
|
13
|
+
- **Questions with buttons.** When it needs an answer, it asks one question
|
|
14
|
+
with tap-to-answer options in the console, Telegram, Slack, Discord and
|
|
15
|
+
Teams.
|
|
16
|
+
- **It keeps its promises.** "I'll tell you when the checks pass" is recorded
|
|
17
|
+
and reported once, on the chat where you asked. Corrections become a card
|
|
18
|
+
you confirm, and it remembers them from the next reply.
|
|
19
|
+
- Accepting a result that doesn't match its report needs a reason, enforced by
|
|
20
|
+
the server. Every time on a page shows in your time zone.
|
|
21
|
+
|
|
3
22
|
## 0.9.15 — 2026-10-02
|
|
4
23
|
|
|
5
24
|
- **Accept finishes the task.** One step, Accept and finish, after the
|
|
@@ -53,6 +53,7 @@ export type AssignmentCatchUp = {
|
|
|
53
53
|
decisions: {
|
|
54
54
|
id: number;
|
|
55
55
|
claim: string;
|
|
56
|
+
why: string;
|
|
56
57
|
}[];
|
|
57
58
|
};
|
|
58
59
|
}[];
|
|
@@ -65,6 +66,15 @@ export type AssignmentCatchUp = {
|
|
|
65
66
|
notes: string[];
|
|
66
67
|
};
|
|
67
68
|
};
|
|
69
|
+
/** A reason's first sentence, or its first line. */
|
|
70
|
+
export declare function firstSentenceOf(text: string): string;
|
|
71
|
+
/** A project's active decisions, newest first: each one's title and its reason in one line (first sentence or
|
|
72
|
+
* line). Memory absent on an older database reads as no decisions. */
|
|
73
|
+
export declare function activeDecisionsOf(store: Store, repo: string, text?: (value: string, cap: number) => string): {
|
|
74
|
+
id: number;
|
|
75
|
+
claim: string;
|
|
76
|
+
why: string;
|
|
77
|
+
}[];
|
|
68
78
|
/** A current snapshot, never a receipt to acknowledge. Full assignment reads
|
|
69
79
|
* remain the place to inspect exact saved work and obtain its current digest. */
|
|
70
80
|
export declare function assignmentCatchUp(store: Store, now: Date, access: AssignmentAccess, query?: {
|
package/dist/assignment-brief.js
CHANGED
|
@@ -7,6 +7,26 @@ import { publicChatText } from "./chat-display.js";
|
|
|
7
7
|
const sha = (value) => createHash("sha256").update(value).digest("hex");
|
|
8
8
|
const MAX_BYTES = 16_000;
|
|
9
9
|
const RECENT_MS = 7 * 24 * 60 * 60_000;
|
|
10
|
+
/** A sentence's end: not an abbreviation's full stop ("e.g.", "i.e.", "etc."), and followed by a new sentence. */
|
|
11
|
+
const SENTENCE_END = /(?<!\b(?:e\.g|i\.e|etc|vs|cf|approx|incl|Mr|Mrs|Ms|Dr|St|No))[.!?](?=\s+["'“(]?[\p{Lu}\p{N}])/u;
|
|
12
|
+
/** A reason's first sentence, or its first line. */
|
|
13
|
+
export function firstSentenceOf(text) {
|
|
14
|
+
const line = text.split("\n")[0].trim();
|
|
15
|
+
const end = SENTENCE_END.exec(line);
|
|
16
|
+
return end === null ? line : line.slice(0, end.index + 1);
|
|
17
|
+
}
|
|
18
|
+
/** A project's active decisions, newest first: each one's title and its reason in one line (first sentence or
|
|
19
|
+
* line). Memory absent on an older database reads as no decisions. */
|
|
20
|
+
export function activeDecisionsOf(store, repo, text = publicChatText) {
|
|
21
|
+
try {
|
|
22
|
+
return store.handle.prepare("SELECT id,claim,why FROM project_decision WHERE repo=? AND status='active' ORDER BY id DESC LIMIT 8").all(repo).map(row => ({
|
|
23
|
+
id: Number(row["id"]), claim: text(String(row["claim"]), 160), why: text(firstSentenceOf(String(row["why"])), 160)
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
10
30
|
/** A current snapshot, never a receipt to acknowledge. Full assignment reads
|
|
11
31
|
* remain the place to inspect exact saved work and obtain its current digest. */
|
|
12
32
|
export function assignmentCatchUp(store, now, access, query = {}, evidenceRoot) {
|
|
@@ -26,7 +46,7 @@ export function assignmentCatchUp(store, now, access, query = {}, evidenceRoot)
|
|
|
26
46
|
return result;
|
|
27
47
|
const repos = query.repo === undefined ? access.repos : [query.repo];
|
|
28
48
|
const includeUnplaced = query.repo === undefined && access.principal === "operator" && access.includeUnplaced === true;
|
|
29
|
-
const scopedAccess = access.principal === "operator" ? { principal: "operator", repos, includeUnplaced } : { principal: "coordinator", repos: repos ?? [] };
|
|
49
|
+
const scopedAccess = access.principal === "operator" ? { principal: "operator", repos, includeUnplaced, ...(access.viewer == null ? {} : { viewer: access.viewer }) } : { principal: "coordinator", repos: repos ?? [] };
|
|
30
50
|
const text = (value, cap) => {
|
|
31
51
|
const rendered = publicChatText(value, cap);
|
|
32
52
|
if (rendered !== value)
|
|
@@ -86,37 +106,40 @@ export function assignmentCatchUp(store, now, access, query = {}, evidenceRoot)
|
|
|
86
106
|
result.omissions.decisions += Math.max(0, count - decisions.length);
|
|
87
107
|
}
|
|
88
108
|
// Project knowledge is admitted in SQL before any payload or title read.
|
|
89
|
-
|
|
109
|
+
// A project with settled decisions but no saved instructions yet still has knowledge to bring.
|
|
110
|
+
const known = store.handle.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='project_decision'").get() === undefined ? "SELECT repo FROM project_knowledge"
|
|
111
|
+
: "SELECT repo FROM project_knowledge UNION SELECT repo FROM project_decision WHERE status='active'";
|
|
112
|
+
const projectRows = store.handle.prepare(`SELECT repo FROM (${known}) WHERE ? = 1 OR repo IN (SELECT value FROM json_each(?)) ORDER BY repo LIMIT 9`)
|
|
90
113
|
.all(repos === null ? 1 : 0, JSON.stringify(repos ?? []));
|
|
91
|
-
const projectCount = Number(store.handle.prepare(
|
|
114
|
+
const projectCount = Number(store.handle.prepare(`SELECT COUNT(*) AS n FROM (${known}) WHERE ? = 1 OR repo IN (SELECT value FROM json_each(?))`).get(repos === null ? 1 : 0, JSON.stringify(repos ?? []))?.["n"] ?? 0);
|
|
92
115
|
result.omissions.projects = Math.max(0, projectCount - 8);
|
|
93
116
|
for (const project of projectRows.slice(0, 8)) {
|
|
94
117
|
const repo = String(project["repo"]);
|
|
95
118
|
const knowledge = { status: "unavailable", revision: null, identity: null, sha256: null, instructions: "", sources: [], decisions: [] };
|
|
96
119
|
const row = store.handle.prepare('SELECT * FROM project_knowledge WHERE repo=?').get(repo);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (typeof
|
|
107
|
-
throw Error("invalid stored
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
if (row === undefined)
|
|
121
|
+
knowledge.status = "none";
|
|
122
|
+
else
|
|
123
|
+
try {
|
|
124
|
+
const payload = String(row["payload"]), revision = Number(row["revision"]), identity = String(row["identity"]);
|
|
125
|
+
const history = store.handle.prepare('SELECT sha FROM knowledge_change WHERE repo=? AND identity=? AND revision=?').get(repo, identity, revision);
|
|
126
|
+
if (Buffer.byteLength(payload) > 160_000 || !Number.isSafeInteger(revision) || revision < 1 || sha(payload) !== row["sha"] || history?.["sha"] !== row["sha"])
|
|
127
|
+
throw Error("invalid stored context");
|
|
128
|
+
const saved = JSON.parse(payload);
|
|
129
|
+
if (typeof saved.instructions !== "string" || !Array.isArray(saved.references) || saved.references.length > 12)
|
|
130
|
+
throw Error("invalid stored context");
|
|
131
|
+
const sources = saved.references.map((one) => {
|
|
132
|
+
if (typeof one.id !== "string" || typeof one.title !== "string" || typeof one.content !== "string")
|
|
133
|
+
throw Error("invalid stored source");
|
|
134
|
+
const id = text(one.id, 80), path = typeof one.path === "string" ? text(one.path, 160) : null;
|
|
135
|
+
return { id, title: text(one.title, 120), kind: path === null ? "saved-note" : "repository-document", path,
|
|
136
|
+
sourceRevision: typeof one.sourceRevision === "string" && /^[a-f0-9]{40,64}$/.test(one.sourceRevision) ? one.sourceRevision : null,
|
|
137
|
+
sourceSha: typeof one.sourceSha === "string" && /^[a-f0-9]{40,64}$/.test(one.sourceSha) ? one.sourceSha : null };
|
|
138
|
+
});
|
|
139
|
+
Object.assign(knowledge, { status: "stored", revision, identity: /^[a-f0-9]{64}$/.test(identity) ? identity : null, sha256: String(row["sha"]), instructions: text(saved.instructions, 600), sources });
|
|
140
|
+
}
|
|
141
|
+
catch { /* Integrity failures are explicit; never replay an older version as current. */ }
|
|
142
|
+
knowledge.decisions = activeDecisionsOf(store, repo, text);
|
|
120
143
|
result.projects.push({ repo, knowledge });
|
|
121
144
|
if (!fits()) {
|
|
122
145
|
result.projects.pop();
|
package/dist/assignment.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export type AssignmentSnapshot = {
|
|
|
109
109
|
wait: WaitKey;
|
|
110
110
|
build: number | null;
|
|
111
111
|
} | null;
|
|
112
|
-
/** The person's lead took it on (lead-voice.ts): "
|
|
112
|
+
/** The person's lead took it on (lead-voice.ts): "<name> is on it" until done, handed on, or two quiet hours. */
|
|
113
113
|
lead?: LeadClaim | null;
|
|
114
114
|
/** How many earlier versions of this task are still queued or running; absent when none. */
|
|
115
115
|
earlierActive?: number;
|
|
@@ -195,14 +195,15 @@ type Finished = {
|
|
|
195
195
|
reason: string;
|
|
196
196
|
message: string;
|
|
197
197
|
};
|
|
198
|
-
/** An acceptance this receipt owes before it finishes
|
|
199
|
-
* exception
|
|
200
|
-
* completion: nothing is owed,
|
|
201
|
-
|
|
198
|
+
/** An acceptance this receipt owes before it finishes, by the result page's own rule: the person's own check
|
|
199
|
+
* (no reason needed), or an exception a report that doesn't match its changes needs a reason for. Null when
|
|
200
|
+
* Accept and finish is a plain completion: nothing is owed (no proof, a failed check, or a verified result),
|
|
201
|
+
* or it was already accepted. */
|
|
202
|
+
export declare function owedAcceptance(receipt: AssignmentReceipt | null): "person-check" | "exception" | null;
|
|
202
203
|
/** Accept and finish: the person's acceptance of the exact result they read and its completion, in one
|
|
203
204
|
* transaction. Whether an acceptance is owed is decided here, against the receipt as it stands: only a
|
|
204
|
-
* person's own check or a reasoned exception records one (with its ledger act);
|
|
205
|
-
* completion Mark complete always was. The completion names the receipt the acceptance produced, under the
|
|
205
|
+
* person's own check or a reasoned exception records one (with its ledger act); an exception without its
|
|
206
|
+
* reason is refused; anything else is the plain completion Mark complete always was. The completion names the receipt the acceptance produced, under the
|
|
206
207
|
* same digest a separate Accept then Mark complete leave. `receiptDigest` is the receipt as read, before
|
|
207
208
|
* accepting (`runId`, when named, must be its run); any other change refuses both, and a refused completion
|
|
208
209
|
* keeps no acceptance. `finish` completes the receipt (Mark complete by default; Complete and open a pull
|
package/dist/assignment.js
CHANGED
|
@@ -15,6 +15,7 @@ import { reproveApprover } from "./principal.js";
|
|
|
15
15
|
import { noteAssignmentStatus } from "./assignment-status.js";
|
|
16
16
|
import { historicalAssessmentReason } from "./assignment-presentation.js";
|
|
17
17
|
import { manualReviewOnly } from "./proof.js";
|
|
18
|
+
import { ACCEPT_NEEDS_REASON, cantAcceptYetOf } from "./result-acts.js";
|
|
18
19
|
import { runCheckLevel } from "./check-levels.js";
|
|
19
20
|
import { followUpChecksOf, withFollowUps } from "./result-follow-ups.js";
|
|
20
21
|
import { buildReviewOf, findingWords } from "./review-switch.js";
|
|
@@ -465,20 +466,21 @@ class Undone extends Error {
|
|
|
465
466
|
this.result = result;
|
|
466
467
|
}
|
|
467
468
|
}
|
|
468
|
-
/** An acceptance this receipt owes before it finishes
|
|
469
|
-
* exception
|
|
470
|
-
* completion: nothing is owed,
|
|
471
|
-
|
|
469
|
+
/** An acceptance this receipt owes before it finishes, by the result page's own rule: the person's own check
|
|
470
|
+
* (no reason needed), or an exception a report that doesn't match its changes needs a reason for. Null when
|
|
471
|
+
* Accept and finish is a plain completion: nothing is owed (no proof, a failed check, or a verified result),
|
|
472
|
+
* or it was already accepted. */
|
|
473
|
+
export function owedAcceptance(receipt) {
|
|
472
474
|
if (receipt === null || receipt.proofAcceptance !== null)
|
|
473
475
|
return null;
|
|
474
476
|
if (personCheckPending(receipt))
|
|
475
477
|
return "person-check";
|
|
476
|
-
return
|
|
478
|
+
return cantAcceptYetOf(receipt.proof?.verdict ?? null, receipt.proof?.reasons ?? [], false) === ACCEPT_NEEDS_REASON ? "exception" : null;
|
|
477
479
|
}
|
|
478
480
|
/** Accept and finish: the person's acceptance of the exact result they read and its completion, in one
|
|
479
481
|
* transaction. Whether an acceptance is owed is decided here, against the receipt as it stands: only a
|
|
480
|
-
* person's own check or a reasoned exception records one (with its ledger act);
|
|
481
|
-
* completion Mark complete always was. The completion names the receipt the acceptance produced, under the
|
|
482
|
+
* person's own check or a reasoned exception records one (with its ledger act); an exception without its
|
|
483
|
+
* reason is refused; anything else is the plain completion Mark complete always was. The completion names the receipt the acceptance produced, under the
|
|
482
484
|
* same digest a separate Accept then Mark complete leave. `receiptDigest` is the receipt as read, before
|
|
483
485
|
* accepting (`runId`, when named, must be its run); any other change refuses both, and a refused completion
|
|
484
486
|
* keeps no acceptance. `finish` completes the receipt (Mark complete by default; Complete and open a pull
|
|
@@ -497,7 +499,10 @@ export function acceptAndCompleteAsOperator(store, taskId, input, who, now, root
|
|
|
497
499
|
return { ok: false, reason: "stale", message: "This result changed. Open the current result before accepting it." };
|
|
498
500
|
}
|
|
499
501
|
// Already complete, or nothing owed: the plain completion of the receipt as read.
|
|
500
|
-
const owed = before.state === "ready-to-check" ? owedAcceptance(receipt
|
|
502
|
+
const owed = before.state === "ready-to-check" ? owedAcceptance(receipt) : null;
|
|
503
|
+
// An exception is accepted only with its reason: never a completion that skips it.
|
|
504
|
+
if (owed === "exception" && input.note === null)
|
|
505
|
+
return { ok: false, reason: "needs-reason", message: `${ACCEPT_NEEDS_REASON}.` };
|
|
501
506
|
let digest = receipt.digest;
|
|
502
507
|
if (owed !== null) {
|
|
503
508
|
store.acceptProof(receipt.runId, who.name, input.note, now);
|