toolroll 0.8.2 → 0.9.1
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 +49 -0
- package/dist/agent-onboard.d.ts +24 -1
- package/dist/agent-onboard.js +53 -1
- package/dist/backup-ui.js +4 -2
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +33 -32
- package/dist/browser-workspace.d.ts +22 -2
- package/dist/chat-actions.d.ts +5 -0
- package/dist/chat-actions.js +43 -3
- package/dist/chat-delivery.js +51 -3
- package/dist/chat-quiet.d.ts +28 -0
- package/dist/chat-quiet.js +161 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -1
- package/dist/desktop-update.d.ts +1 -1
- package/dist/digest-times.d.ts +3 -0
- package/dist/digest-times.js +7 -0
- package/dist/dispose.js +2 -0
- package/dist/export.js +1 -1
- package/dist/flow-engine.js +13 -1
- package/dist/flow-pull-request.d.ts +11 -0
- package/dist/flow-pull-request.js +123 -0
- package/dist/flow-starters-ui.d.ts +20 -0
- package/dist/flow-starters-ui.js +35 -0
- package/dist/flow-starters.d.ts +55 -0
- package/dist/flow-starters.js +104 -0
- package/dist/flow-steps.js +14 -0
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +4 -3
- package/dist/flows.d.ts +30 -6
- package/dist/flows.js +110 -6
- package/dist/integrations-ui.d.ts +13 -0
- package/dist/integrations-ui.js +70 -0
- package/dist/integrations.d.ts +113 -0
- package/dist/integrations.js +526 -0
- package/dist/lead-context.d.ts +1 -1
- package/dist/lead-context.js +1 -1
- package/dist/mate-contract.js +1 -1
- package/dist/mate-tools.js +18 -5
- package/dist/models-ui.js +3 -2
- package/dist/monitoring-ui.js +2 -1
- package/dist/operate.d.ts +5 -2
- package/dist/operate.js +169 -4
- package/dist/policy-ui.js +2 -1
- package/dist/project-delete.js +1 -0
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +9 -2
- package/dist/pull-request-flow.d.ts +172 -0
- package/dist/pull-request-flow.js +475 -0
- package/dist/pull-request-schema.d.ts +5 -0
- package/dist/pull-request-schema.js +21 -0
- package/dist/pull-request-ui.d.ts +23 -0
- package/dist/pull-request-ui.js +55 -0
- package/dist/pulls.d.ts +5 -0
- package/dist/pulls.js +11 -0
- package/dist/serve.d.ts +6 -0
- package/dist/serve.js +371 -58
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +2 -1
- package/dist/store.d.ts +61 -0
- package/dist/store.js +144 -3
- package/dist/surface.js +3 -1
- package/dist/telegram-progress.d.ts +1 -0
- package/dist/telegram-progress.js +1 -1
- package/dist/telegram.js +95 -15
- package/dist/when-html.d.ts +8 -0
- package/dist/when-html.js +36 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.1 — 2026-09-30
|
|
6
|
+
|
|
7
|
+
- **Phones show more on a screen.** On a phone the console is about a third
|
|
8
|
+
denser: a row's action sits on its title line, status chips beside the
|
|
9
|
+
name, facts on one line, and times shorten to "16:39", "Yesterday 16:39"
|
|
10
|
+
or "Sep 28" (the full time stays on desktop and on hover). Every tap target
|
|
11
|
+
is still at least 44px and text fields 16px; nothing scrolls sideways at
|
|
12
|
+
320px, and the desktop layout is unchanged.
|
|
13
|
+
- **Flows open pull requests, and starter flows are one yes away.** A new
|
|
14
|
+
Pull request zone opens a PR for a card's built result under the project's
|
|
15
|
+
pull request setup, waits for CI, moves on when it passes and takes the
|
|
16
|
+
failure path (naming the failing check) when it fails; it can merge, squash
|
|
17
|
+
by default, only after a person approved the card. The Issues to PRs
|
|
18
|
+
template runs labelled GitHub issues through build, approval, pull request
|
|
19
|
+
and a closing comment. Settings → Flows and `toolroll onboard --starter`
|
|
20
|
+
switch on Fix failing CI, Issues become tasks and Overnight queue; a task's
|
|
21
|
+
"Do this every time…" and chat offer the matching one.
|
|
22
|
+
|
|
23
|
+
## 0.9.0 — 2026-09-30
|
|
24
|
+
|
|
25
|
+
- **Chat pings only when you're needed.** In Telegram, Slack, Discord and
|
|
26
|
+
Teams each task is one message, updated in place from filed to Ready (and
|
|
27
|
+
tasks filed within a minute share one). A new message arrives only when
|
|
28
|
+
something needs you: a result Ready for review, a question or approval, a
|
|
29
|
+
failure that needs a decision, a sign-in or plan-limit pause, or a security
|
|
30
|
+
alert. Choose Only when I'm needed (the default) or Every step, and an
|
|
31
|
+
optional evening digest, in Settings → Notifications or with
|
|
32
|
+
`toolroll notifications quiet|all|digest <HH:MM>|digest off`. Delivery
|
|
33
|
+
receipts and the console's activity are unchanged.
|
|
34
|
+
- **Complete opens a pull request, and Merge is one step.** Turn on pull
|
|
35
|
+
requests once per project (Projects → Pull requests, or
|
|
36
|
+
`toolroll publish setup`): Toolroll checks the GitHub remote, the default
|
|
37
|
+
branch, `gh` sign-in and push rights, and says what to fix. Complete then
|
|
38
|
+
offers "Complete and open a pull request" beside "Complete only"; the pull
|
|
39
|
+
request opens from the exact completed commit. The task shows the PR link
|
|
40
|
+
and its CI. Green reads Ready to merge with one Merge (behind your
|
|
41
|
+
password; Telegram and Slack link to it), which squashes by default,
|
|
42
|
+
deletes the branch and records the merge commit in the ledger. Red files
|
|
43
|
+
one revision with the failing check's name and a fenced log excerpt, at
|
|
44
|
+
most twice per task, then asks you. "Merge when checks pass" is an
|
|
45
|
+
opt-in project setting.
|
|
46
|
+
- **Settings → Integrations: what works, what's broken.** One list of
|
|
47
|
+
Telegram, Slack, Discord, Teams, GitHub, Linear, email, each project's MCP
|
|
48
|
+
tools, monitoring destinations, Claude and Codex. Each says Connected, Not
|
|
49
|
+
set up or Broken, with its last success and last error, what uses it, and
|
|
50
|
+
one action: Set up, Send test, or Fix in plain words. Checks are read-only,
|
|
51
|
+
run in the background and never hold up a page; `toolroll integrations`
|
|
52
|
+
shows the same, and `toolroll status` adds a line when something is broken.
|
|
53
|
+
|
|
5
54
|
## 0.8.2 — 2026-09-30
|
|
6
55
|
|
|
7
56
|
- **Task checkouts stop using space nobody wants.** A finished task's clean
|
package/dist/agent-onboard.d.ts
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* without running it, and ends with a handoff the agent can relay.
|
|
9
9
|
*
|
|
10
10
|
* Adding the project and writing the skill both need --yes or one yes
|
|
11
|
-
* typed at a terminal.
|
|
11
|
+
* typed at a terminal. Then it offers the starter flows (flow-starters.ts),
|
|
12
|
+
* each switched on by its own yes at a terminal or by --starter; --yes alone
|
|
13
|
+
* never switches one on. The project is the main checkout (never a linked
|
|
12
14
|
* worktree, the home folder, or one of Toolroll's own worktrees). The skill
|
|
13
15
|
* is replaced by the next onboard and deleted by `onboard --remove` only
|
|
14
16
|
* while it is exactly a version Toolroll wrote; a file of the same name
|
|
15
17
|
* that is not ours, or ours with the person's edits, is never touched.
|
|
16
18
|
*/
|
|
17
19
|
import type { ProviderConnection } from "./provider-connection.js";
|
|
20
|
+
import type { StarterView, SwitchedOn } from "./flow-starters.js";
|
|
18
21
|
export type OnboardAgent = "claude" | "codex";
|
|
19
22
|
export declare const ONBOARD_AGENTS: readonly OnboardAgent[];
|
|
20
23
|
export declare const AGENT_NAMES: Record<OnboardAgent, string>;
|
|
@@ -119,6 +122,26 @@ export type OnboardIo = {
|
|
|
119
122
|
message: string;
|
|
120
123
|
}>;
|
|
121
124
|
checkConnection: (agent: OnboardAgent) => Promise<ProviderConnection>;
|
|
125
|
+
/** Whether the project can open pull requests at Complete, in one line: on, the one command that turns it on, or
|
|
126
|
+
* what to fix first. Turning it on stays a person's password step. */
|
|
127
|
+
pullRequests?: (repo: string) => Promise<string>;
|
|
128
|
+
/** --starter, as typed: a comma list of starter flow ids to switch on. */
|
|
129
|
+
starterFlag?: string | undefined;
|
|
130
|
+
/** The project's starter flows, and switching one on — its trigger and zones. */
|
|
131
|
+
starters?: {
|
|
132
|
+
list: (repo: string) => Promise<StarterView[]>;
|
|
133
|
+
switchOn: (repo: string, id: string) => Promise<SwitchedOn>;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
export type StarterAnswer = {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
summary: string;
|
|
140
|
+
does: string[];
|
|
141
|
+
never: string;
|
|
142
|
+
state: "on" | "switched-on" | "off" | "unavailable" | "failed";
|
|
143
|
+
flow?: number;
|
|
144
|
+
said?: string;
|
|
122
145
|
};
|
|
123
146
|
/** Why a found repository cannot be the project, or null when it can. */
|
|
124
147
|
export declare function projectRefusal(found: {
|
package/dist/agent-onboard.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* without running it, and ends with a handoff the agent can relay.
|
|
9
9
|
*
|
|
10
10
|
* Adding the project and writing the skill both need --yes or one yes
|
|
11
|
-
* typed at a terminal.
|
|
11
|
+
* typed at a terminal. Then it offers the starter flows (flow-starters.ts),
|
|
12
|
+
* each switched on by its own yes at a terminal or by --starter; --yes alone
|
|
13
|
+
* never switches one on. The project is the main checkout (never a linked
|
|
12
14
|
* worktree, the home folder, or one of Toolroll's own worktrees). The skill
|
|
13
15
|
* is replaced by the next onboard and deleted by `onboard --remove` only
|
|
14
16
|
* while it is exactly a version Toolroll wrote; a file of the same name
|
|
@@ -215,6 +217,48 @@ export function loginAccount(file) {
|
|
|
215
217
|
export function agentReport(agent, connection) {
|
|
216
218
|
return { agent, name: AGENT_NAMES[agent], state: connection.state, words: connectionWords(connection), ...(connection.plan === undefined ? {} : { plan: connection.plan }) };
|
|
217
219
|
}
|
|
220
|
+
/** Offer each starter flow: switch on the ones --starter names, or ask one yes each at a terminal. */
|
|
221
|
+
async function offerStarters(io, repo) {
|
|
222
|
+
if (io.starters === undefined)
|
|
223
|
+
return [];
|
|
224
|
+
const list = await io.starters.list(repo);
|
|
225
|
+
const named = io.starterFlag === undefined ? null : io.starterFlag.split(",").map(one => one.trim()).filter(one => one !== "");
|
|
226
|
+
const unknown = named?.filter(one => !list.some(starter => starter.id === one)) ?? [];
|
|
227
|
+
if (named !== null && (named.length === 0 || unknown.length > 0))
|
|
228
|
+
return { usage: `--starter takes ${list.map(one => one.id).join(", ")}` };
|
|
229
|
+
const answers = [];
|
|
230
|
+
for (const one of list) {
|
|
231
|
+
const base = { id: one.id, name: one.name, summary: one.summary, does: one.does, never: one.never };
|
|
232
|
+
if (one.on !== null) {
|
|
233
|
+
answers.push({ ...base, state: "on", flow: one.on.flow });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (one.blocked !== null) {
|
|
237
|
+
answers.push({ ...base, state: "unavailable", said: one.blocked });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const wanted = named !== null ? named.includes(one.id) : io.interactive && !io.json && await io.confirm(`Switch on ${one.name}? ${one.summary} ${one.never} [y/N]`);
|
|
241
|
+
if (!wanted) {
|
|
242
|
+
answers.push({ ...base, state: "off" });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const switched = await io.starters.switchOn(repo, one.id);
|
|
246
|
+
answers.push(switched.ok ? { ...base, state: switched.already ? "on" : "switched-on", flow: switched.flow } : { ...base, state: "failed", said: switched.said });
|
|
247
|
+
}
|
|
248
|
+
return answers;
|
|
249
|
+
}
|
|
250
|
+
function starterLines(answers) {
|
|
251
|
+
return answers.map((one, index) => {
|
|
252
|
+
const head = index === 0 ? "starters " : " ";
|
|
253
|
+
switch (one.state) {
|
|
254
|
+
case "on": return `${head} ${one.name}: on`;
|
|
255
|
+
case "switched-on": return `${head} ${one.name}: switched on`;
|
|
256
|
+
case "unavailable": return `${head} ${one.name}: ${one.said}`;
|
|
257
|
+
case "failed": return `${head} ${one.name}: ${one.said}`;
|
|
258
|
+
case "off": return `${head} ${one.name}: ${one.summary} ${one.never} Switch on: toolroll onboard --starter ${one.id}`;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
218
262
|
const real = (path) => {
|
|
219
263
|
try {
|
|
220
264
|
return realpathSync(path);
|
|
@@ -333,6 +377,10 @@ export async function runOnboard(io) {
|
|
|
333
377
|
const handoff = buildHandoff({ url: io.url, login: io.login });
|
|
334
378
|
const mcp = agents.map(agent => ({ agent, command: mcpLine(agent) }));
|
|
335
379
|
const projectData = project.state === "added" || project.state === "already" ? { path: project.path, added: project.state === "added" } : null;
|
|
380
|
+
const pullRequests = projectData === null || io.pullRequests === undefined ? null : await io.pullRequests(projectData.path);
|
|
381
|
+
const offered = projectData === null ? [] : await offerStarters(io, projectData.path);
|
|
382
|
+
if (!Array.isArray(offered))
|
|
383
|
+
return refuse("usage", offered.usage, 2);
|
|
336
384
|
const projectProblem = (() => {
|
|
337
385
|
switch (project.state) {
|
|
338
386
|
case "none": return "not inside a git repository";
|
|
@@ -349,6 +397,8 @@ export async function runOnboard(io) {
|
|
|
349
397
|
command,
|
|
350
398
|
project: projectData,
|
|
351
399
|
...(projectProblem === null ? {} : { projectProblem }),
|
|
400
|
+
...(pullRequests === null ? {} : { pullRequests }),
|
|
401
|
+
...(offered.length === 0 ? {} : { starters: offered.map(one => ({ ...one, ...(one.state === "off" ? { command: `toolroll onboard --starter ${one.id}` } : {}) })) }),
|
|
352
402
|
agents: reports,
|
|
353
403
|
skill: skillData,
|
|
354
404
|
mcp,
|
|
@@ -361,6 +411,8 @@ export async function runOnboard(io) {
|
|
|
361
411
|
: project.state === "none" ? "project none added: run onboard inside the repository you want to hand off" : `project ${projectProblem}`;
|
|
362
412
|
io.write([
|
|
363
413
|
projectLine,
|
|
414
|
+
...(pullRequests === null ? [] : [`pull requests ${pullRequests}`]),
|
|
415
|
+
...starterLines(offered),
|
|
364
416
|
`agents ${reports.map(one => `${one.name}: ${[one.words, one.plan].filter(Boolean).join(" · ")}`).join("; ")}`,
|
|
365
417
|
skillLine(),
|
|
366
418
|
...mcp.map((one, index) => `${index === 0 ? "tools " : " "} ${one.command} (add Toolroll as tools; not run)`),
|
package/dist/backup-ui.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { bytesWords } from "./storage.js";
|
|
2
|
+
import { whenUtc } from "./when-html.js";
|
|
2
3
|
/** The schedules on offer and the most copies kept. Here rather than in backup.ts so the page never loads `node:sqlite`. */
|
|
3
4
|
export const BACKUP_EVERY_HOURS = [1, 6, 12, 24];
|
|
4
5
|
export const MAX_KEEP = 100;
|
|
5
6
|
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
6
|
-
const when = (at) =>
|
|
7
|
+
const when = (at) => whenUtc(at);
|
|
8
|
+
const whenWords = (at) => `${at.slice(0, 16).replace("T", " ")} UTC`;
|
|
7
9
|
const everyWords = (hours) => hours === 1 ? "Every hour" : hours === 24 ? "Every day" : `Every ${hours} hours`;
|
|
8
10
|
const fileName = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
9
11
|
export const BACKUP_CSS = `.backups{max-width:720px;min-width:0;overflow-wrap:anywhere}.backups .backup-state{display:flex;gap:16px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap}` +
|
|
@@ -22,7 +24,7 @@ function state(view) {
|
|
|
22
24
|
const good = view.runs.find(one => one.ok === true);
|
|
23
25
|
const next = !view.settings.enabled ? "Scheduled backups are off."
|
|
24
26
|
: good === undefined ? "The first scheduled backup runs within a minute or two of the console starting."
|
|
25
|
-
: `Next one about ${
|
|
27
|
+
: `Next one about ${whenWords(new Date(Date.parse(good.startedAt) + view.settings.everyHours * 3_600_000).toISOString())}.`;
|
|
26
28
|
if (last === undefined)
|
|
27
29
|
return `<h2>No backups yet</h2><p class="meta">${e(next)}</p>`;
|
|
28
30
|
if (last.ok === null)
|