toolroll 0.9.6 → 0.9.7
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 +38 -0
- package/dist/actor.d.ts +21 -0
- package/dist/actor.js +48 -0
- package/dist/assignment-adapters.d.ts +1 -0
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.d.ts +9 -0
- package/dist/assignment.js +62 -13
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +28 -24
- package/dist/browser-workspace.d.ts +117 -2
- package/dist/builder.d.ts +1 -0
- package/dist/builder.js +33 -13
- package/dist/chat-actions.js +2 -1
- package/dist/chat-delivery.js +2 -0
- package/dist/chat-quiet.js +11 -1
- package/dist/chat-task-actions.d.ts +13 -0
- package/dist/chat-task-actions.js +19 -1
- package/dist/check-levels-ui.d.ts +21 -0
- package/dist/check-levels-ui.js +70 -0
- package/dist/check-levels.d.ts +67 -0
- package/dist/check-levels.js +171 -0
- package/dist/claim.d.ts +22 -0
- package/dist/claim.js +43 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -0
- package/dist/demo-chat.js +4 -1
- package/dist/dispatch.js +7 -2
- package/dist/dispose.d.ts +8 -0
- package/dist/dispose.js +7 -1
- package/dist/export.js +3 -2
- package/dist/first-run.d.ts +34 -0
- package/dist/first-run.js +37 -1
- package/dist/flow-gallery-ui.d.ts +30 -0
- package/dist/flow-gallery-ui.js +85 -0
- package/dist/flow-gallery.d.ts +111 -0
- package/dist/flow-gallery.js +445 -0
- package/dist/flow-share.d.ts +102 -0
- package/dist/flow-share.js +614 -0
- package/dist/flow-starters.d.ts +2 -0
- package/dist/flow-starters.js +1 -1
- package/dist/flow-steps.js +7 -0
- package/dist/flow-triggers.js +2 -0
- package/dist/flows-cli.d.ts +56 -0
- package/dist/flows-cli.js +109 -3
- package/dist/flows-ui.d.ts +15 -1
- package/dist/flows-ui.js +31 -6
- package/dist/guides.js +27 -2
- package/dist/lead-context.js +6 -2
- package/dist/mate-doors.d.ts +0 -15
- package/dist/mate-doors.js +5 -1
- package/dist/mate-tools.js +9 -4
- package/dist/models-ui.js +1 -1
- package/dist/operate.d.ts +4 -1
- package/dist/operate.js +601 -85
- package/dist/project-concurrency.d.ts +62 -0
- package/dist/project-concurrency.js +116 -0
- package/dist/project-delete-ui.d.ts +7 -0
- package/dist/project-delete-ui.js +19 -2
- package/dist/project-delete.js +4 -0
- package/dist/pull-request-flow.d.ts +9 -0
- package/dist/pull-request-flow.js +21 -3
- package/dist/result-follow-ups.d.ts +101 -0
- package/dist/result-follow-ups.js +333 -0
- package/dist/scope.js +11 -0
- package/dist/serve.d.ts +211 -2
- package/dist/serve.js +1009 -141
- package/dist/store.d.ts +76 -3
- package/dist/store.js +311 -22
- package/dist/surface.js +31 -8
- package/dist/tailnet.d.ts +11 -0
- package/dist/tailnet.js +47 -0
- package/dist/task-outcome-cli.js +2 -2
- package/dist/task-status.d.ts +8 -1
- package/dist/task-status.js +28 -10
- package/dist/team-cli.js +3 -0
- package/dist/telegram-status.js +2 -2
- package/dist/verification-evidence.js +21 -1
- package/dist/webhooks.js +5 -0
- package/dist/work-index.js +39 -6
- package/dist/work-summary.d.ts +0 -4
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +3 -0
- package/dist/workspace-ui.js +5 -3
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import type { WorkSummaryAccess } from './work-summary.js';
|
|
|
9
9
|
import type { StatusTone } from './workspace-ui.js';
|
|
10
10
|
import type { AssignmentCard } from './assignment-ui.js';
|
|
11
11
|
import type { TaskStatus } from './task-status.js';
|
|
12
|
-
import type { FirstRunStep, FirstTaskSuggestion } from './first-run.js';
|
|
12
|
+
import type { FirstRunStep, FirstTaskSuggestion, JourneyStep } from './first-run.js';
|
|
13
13
|
export type BrowserProject = {
|
|
14
14
|
name: string;
|
|
15
15
|
path: string;
|
|
@@ -206,6 +206,12 @@ export type BrowserSettingsView = {
|
|
|
206
206
|
chat?: {
|
|
207
207
|
mode: 'quiet' | 'all';
|
|
208
208
|
digestAt: string | null;
|
|
209
|
+
/** Each project this person can see; a muted one sends no pings but stays in Tasks and the digest. */
|
|
210
|
+
projects?: {
|
|
211
|
+
repo: string;
|
|
212
|
+
name: string;
|
|
213
|
+
muted: boolean;
|
|
214
|
+
}[];
|
|
209
215
|
} | null;
|
|
210
216
|
digest: {
|
|
211
217
|
every: string;
|
|
@@ -285,12 +291,39 @@ export type BrowserTaskSection = {
|
|
|
285
291
|
open: boolean;
|
|
286
292
|
count: number | null;
|
|
287
293
|
};
|
|
294
|
+
/** `at`: a moment, shown in the reader's own time ("16:39", "Yesterday 16:39", "Sep 28"). */
|
|
288
295
|
export type BrowserTaskFact = {
|
|
289
296
|
label: string;
|
|
290
297
|
parts: (string | BrowserLink | {
|
|
291
298
|
seal: string;
|
|
299
|
+
} | {
|
|
300
|
+
at: string;
|
|
292
301
|
})[];
|
|
293
302
|
};
|
|
303
|
+
/** One entry in a task's thread, in time order beside the task's own
|
|
304
|
+
* conversation: the plan, the agent's progress notes and results, its
|
|
305
|
+
* questions, and the person's replies. `html` is the server's own card (same
|
|
306
|
+
* forms and ids) when the entry carries one. */
|
|
307
|
+
export type BrowserTaskThreadItem = {
|
|
308
|
+
key: string;
|
|
309
|
+
at: string;
|
|
310
|
+
kind: 'filed' | 'plan' | 'progress' | 'result' | 'question' | 'reply';
|
|
311
|
+
who: 'agent' | 'person';
|
|
312
|
+
author: string;
|
|
313
|
+
title: string;
|
|
314
|
+
text: string | null;
|
|
315
|
+
link: BrowserLink | null;
|
|
316
|
+
html: string;
|
|
317
|
+
more: {
|
|
318
|
+
summary: string;
|
|
319
|
+
html: string;
|
|
320
|
+
} | null;
|
|
321
|
+
};
|
|
322
|
+
/** The task's metadata, grouped for the Details panel (desk) or sheet (phone). */
|
|
323
|
+
export type BrowserTaskDetailGroup = {
|
|
324
|
+
title: 'Work' | 'Links' | 'Review' | 'About';
|
|
325
|
+
facts: BrowserTaskFact[];
|
|
326
|
+
};
|
|
294
327
|
export type BrowserTaskView = {
|
|
295
328
|
kind: 'task';
|
|
296
329
|
id: string;
|
|
@@ -328,6 +361,13 @@ export type BrowserTaskView = {
|
|
|
328
361
|
href: string;
|
|
329
362
|
starter: string;
|
|
330
363
|
} | null;
|
|
364
|
+
/** Until the first Ready result: Plan → You approve → Build → Checks → Ready, filled in as it moves. */
|
|
365
|
+
journey?: JourneyStep[] | null;
|
|
366
|
+
/** The thread's server-side entries; the conversation's messages join them in time order. */
|
|
367
|
+
thread?: BrowserTaskThreadItem[];
|
|
368
|
+
details?: BrowserTaskDetailGroup[];
|
|
369
|
+
/** Where to message the agent when this page carries no conversation (null: messaging isn't available here). */
|
|
370
|
+
chatHref?: string | null;
|
|
331
371
|
};
|
|
332
372
|
/** One project on the Projects page; opening it is a POST to /projects/open. */
|
|
333
373
|
export type BrowserProjectRow = {
|
|
@@ -343,6 +383,11 @@ export type BrowserProjectRow = {
|
|
|
343
383
|
href: string;
|
|
344
384
|
on: boolean;
|
|
345
385
|
};
|
|
386
|
+
/** Settings → Projects → Checks for this project, and its level in words. */
|
|
387
|
+
checks?: {
|
|
388
|
+
href: string;
|
|
389
|
+
level: string;
|
|
390
|
+
};
|
|
346
391
|
/** What waits, runs, queues or finished today; null when not scanned. */
|
|
347
392
|
peek: {
|
|
348
393
|
label: string;
|
|
@@ -752,6 +797,8 @@ export type BrowserFlowView = {
|
|
|
752
797
|
usedHere: string[];
|
|
753
798
|
language: "shell" | "python" | "node";
|
|
754
799
|
file: string | null;
|
|
800
|
+
/** It came with an imported flow and waits for a person's approval before it runs. */
|
|
801
|
+
held: boolean;
|
|
755
802
|
}[];
|
|
756
803
|
start: string;
|
|
757
804
|
stages: BrowserFlowStage[];
|
|
@@ -811,12 +858,80 @@ export type BrowserWorkspace = {
|
|
|
811
858
|
update?: BrowserUpdateNotice;
|
|
812
859
|
/** Chat's first run, until the first Ready result: the three steps and first tasks to try. */
|
|
813
860
|
firstRun?: BrowserFirstRun;
|
|
861
|
+
/** Chat, after the first Ready result until put away: use it from the phone. */
|
|
862
|
+
phone?: BrowserPhoneCard;
|
|
863
|
+
/** The Chat landing's live view: who is working now, four counts, and Catch up in tabs. */
|
|
864
|
+
home?: BrowserHome | null;
|
|
865
|
+
};
|
|
866
|
+
/** One agent at work now: its task and the machine's own phase, in words. */
|
|
867
|
+
export type BrowserHomeAgent = {
|
|
868
|
+
runId: number;
|
|
869
|
+
taskId: string;
|
|
870
|
+
title: string;
|
|
871
|
+
href: string;
|
|
872
|
+
agent: string;
|
|
873
|
+
phase: string;
|
|
874
|
+
project: string | null;
|
|
875
|
+
since: string;
|
|
876
|
+
};
|
|
877
|
+
export type BrowserHomeCount = {
|
|
878
|
+
key: 'working' | 'waiting' | 'ready' | 'done';
|
|
879
|
+
label: string;
|
|
880
|
+
value: number;
|
|
881
|
+
href: string;
|
|
814
882
|
};
|
|
815
|
-
|
|
883
|
+
export type BrowserCatchUpTab = 'needs-you' | 'ready' | 'running' | 'all';
|
|
884
|
+
export type BrowserCatchUpItem = {
|
|
885
|
+
id: string;
|
|
886
|
+
title: string;
|
|
887
|
+
href: string;
|
|
888
|
+
project: string | null;
|
|
889
|
+
tab: Exclude<BrowserCatchUpTab, 'all'> | 'finished';
|
|
890
|
+
label: string;
|
|
891
|
+
tone: StatusTone;
|
|
892
|
+
detail: string;
|
|
893
|
+
at: string;
|
|
894
|
+
};
|
|
895
|
+
/** Plan-window use stands in for spend: subscriptions don't bill per run. */
|
|
896
|
+
export type BrowserHome = {
|
|
897
|
+
agents: BrowserHomeAgent[];
|
|
898
|
+
counts: BrowserHomeCount[];
|
|
899
|
+
planUse: {
|
|
900
|
+
name: string;
|
|
901
|
+
window: string;
|
|
902
|
+
percent: number;
|
|
903
|
+
detail: string;
|
|
904
|
+
tone: 'neutral' | 'warning' | 'danger';
|
|
905
|
+
}[];
|
|
906
|
+
catchUp: BrowserCatchUpItem[];
|
|
907
|
+
allHref: string;
|
|
908
|
+
};
|
|
909
|
+
/** `sandbox`: the demo command, offered beside the sign-in command while no agent is signed in. `intro`: how it works,
|
|
910
|
+
* once, above the composer with the suggestions. `lead`: the one line saying what runs the lead, and where to change it.
|
|
911
|
+
* `recheck`: while no agent is signed in, where the page asks again on its own. */
|
|
816
912
|
export type BrowserFirstRun = {
|
|
817
913
|
steps: FirstRunStep[];
|
|
818
914
|
suggestions: FirstTaskSuggestion[];
|
|
819
915
|
sandbox: string | null;
|
|
916
|
+
intro?: string;
|
|
917
|
+
lead?: {
|
|
918
|
+
words: string;
|
|
919
|
+
href: string;
|
|
920
|
+
} | null;
|
|
921
|
+
recheck?: string | null;
|
|
922
|
+
};
|
|
923
|
+
/** After the first Ready result: the phone, through a chat app or the console over Tailscale. `tailnet.restart`: the
|
|
924
|
+
* command that makes the console listen beyond this computer, when it doesn't yet. */
|
|
925
|
+
export type BrowserPhoneCard = {
|
|
926
|
+
chatApps: {
|
|
927
|
+
label: string;
|
|
928
|
+
href: string;
|
|
929
|
+
}[];
|
|
930
|
+
tailnet: {
|
|
931
|
+
address: string;
|
|
932
|
+
restart: string | null;
|
|
933
|
+
} | null;
|
|
934
|
+
dismissHref: string;
|
|
820
935
|
};
|
|
821
936
|
/** The console's update notice: neutral, never the accent — an update does not need a person. */
|
|
822
937
|
export type BrowserUpdateNotice = {
|
package/dist/builder.d.ts
CHANGED
|
@@ -368,6 +368,7 @@ export declare const NO_HANDOFF_WORDS = "The agent stopped before handing off; i
|
|
|
368
368
|
export declare const WIP_COMMIT_WORDS = "Work in progress: the agent stopped before handing off. Kept so the next attempt continues from it.";
|
|
369
369
|
/** The short turn a stopped agent's own session is resumed with. */
|
|
370
370
|
export declare function handoffResumePrompt(done: string): string;
|
|
371
|
+
export declare const SERVICE_STOP_WORDS = "Toolroll stopped while this task was building.";
|
|
371
372
|
/**
|
|
372
373
|
* What a non-zero agent exit means, in words a person can act on: the
|
|
373
374
|
* harness's own result line names the ending (a turn ceiling, an error
|
package/dist/builder.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OBSERVATION_MAILBOX, observationBrief, parseObservationCases, collectObservations } from "./observations.js";
|
|
2
|
+
import { checkCommandFor, effectiveCheckLevel, recordRunCheckLevel } from "./check-levels.js";
|
|
2
3
|
import { skillsContext } from "./project-skills.js";
|
|
3
4
|
import { failedVerificationEvidence, sealVerificationReceipt, verificationEvidence, reuseObservationVerification } from "./verification-evidence.js";
|
|
4
5
|
import { learningContext } from "./project-learning.js";
|
|
@@ -1469,11 +1470,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1469
1470
|
// own words.
|
|
1470
1471
|
if (stopRequestedFor(store, request.runId, request.shouldStop)) {
|
|
1471
1472
|
quarantineMailboxes(worktree, root, request.runId);
|
|
1472
|
-
return {
|
|
1473
|
-
ok: false,
|
|
1474
|
-
reason: "stopped",
|
|
1475
|
-
message: stopWords(store, request.runId, worktree, `the operator stopped this watch while the agent ran — the work is preserved uncommitted in ${worktree}`),
|
|
1476
|
-
};
|
|
1473
|
+
return { ok: false, reason: "stopped", message: await stoppedWords(captured) };
|
|
1477
1474
|
}
|
|
1478
1475
|
if (result.timedOut) {
|
|
1479
1476
|
// A mailbox cut down mid-write is quarantined, never ingested: whatever
|
|
@@ -1782,11 +1779,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1782
1779
|
// (audit IV-1): an operator's stop beats an agent's finish. The work
|
|
1783
1780
|
// stays in the worktree, uncommitted, preserved for the successor.
|
|
1784
1781
|
if (stopRequestedFor(store, request.runId, request.shouldStop)) {
|
|
1785
|
-
return {
|
|
1786
|
-
ok: false,
|
|
1787
|
-
reason: "stopped",
|
|
1788
|
-
message: stopWords(store, request.runId, worktree, `the operator stopped this watch while the agent ran — the work is preserved uncommitted in ${worktree}`),
|
|
1789
|
-
};
|
|
1782
|
+
return { ok: false, reason: "stopped", message: await stoppedWords(captured) };
|
|
1790
1783
|
}
|
|
1791
1784
|
store.setRunPhase(request.runId, "committing");
|
|
1792
1785
|
// Kept work that is already complete is already committed: the attempt's
|
|
@@ -2145,7 +2138,14 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2145
2138
|
// re-proved before every authorized spawn, and tracked post-commit changes
|
|
2146
2139
|
// stop recovery rather than being silently certified.
|
|
2147
2140
|
const repo = store.getWorktree(worktree)?.repo ?? null;
|
|
2148
|
-
|
|
2141
|
+
// The check level (check-levels.ts): the task's own choice, else the
|
|
2142
|
+
// project's. Quick runs its own approved command (or the full one when it
|
|
2143
|
+
// has none); Off runs nothing. The level that ran is recorded either way.
|
|
2144
|
+
const checkChoice = effectiveCheckLevel(store, repo, request.taskId);
|
|
2145
|
+
const chosenCheck = repo === null ? null : checkCommandFor(store, repo, checkChoice.level);
|
|
2146
|
+
const configured = chosenCheck?.command ?? null;
|
|
2147
|
+
if (repo !== null && chosenCheck !== null)
|
|
2148
|
+
recordRunCheckLevel(store, { id: runId, taskId: request.taskId, repo }, chosenCheck.level, checkChoice.from, now());
|
|
2149
2149
|
let verifyCommand;
|
|
2150
2150
|
const checkLog = [];
|
|
2151
2151
|
const checkOutcomes = [];
|
|
@@ -2238,7 +2238,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2238
2238
|
const liveRecoverySetup = () => {
|
|
2239
2239
|
if (repo === null || recoveryDigest === null)
|
|
2240
2240
|
return null;
|
|
2241
|
-
const liveVerify = store.liveVerifyCommand(repo);
|
|
2241
|
+
const liveVerify = store.liveVerifyCommand(configured.repo);
|
|
2242
2242
|
const liveSetup = store.liveWorktreeSetup(repo);
|
|
2243
2243
|
return liveVerify !== null &&
|
|
2244
2244
|
liveVerify.digest === configured.digest &&
|
|
@@ -2374,7 +2374,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2374
2374
|
: checkSuites.some(one => one.status === "failed") ? "failed"
|
|
2375
2375
|
: "not-run";
|
|
2376
2376
|
const suites = checkSuites.length > 0 ? checkSuites : verifyCommand.configured === false ? [] : [{
|
|
2377
|
-
name: reused === null ? "Project check" : "Project check (reused)",
|
|
2377
|
+
name: reused === null ? chosenCheck?.level === "quick" ? "Quick check" : "Project check" : "Project check (reused)",
|
|
2378
2378
|
status: checkStatus,
|
|
2379
2379
|
exitCode: "ran" in verifyCommand && verifyCommand.ran ? verifyCommand.exitCode : null,
|
|
2380
2380
|
}];
|
|
@@ -2589,6 +2589,26 @@ async function resumeUnhandedWork(captured, sessionId) {
|
|
|
2589
2589
|
}
|
|
2590
2590
|
return null;
|
|
2591
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* The words for an attempt a stop ended after its agent ran. A task stop
|
|
2594
|
+
* names who asked. A SERVICE stop (the builder stopping or restarting) also
|
|
2595
|
+
* saves the work as a patch in the run's evidence before saying so: the
|
|
2596
|
+
* changes stay uncommitted in the checkout, and the requeued task resumes
|
|
2597
|
+
* from them on the builder's next pass.
|
|
2598
|
+
*/
|
|
2599
|
+
async function stoppedWords(captured) {
|
|
2600
|
+
const { store, request, git, worktree, root } = captured;
|
|
2601
|
+
if (store.applicableStopFor(request.runId) !== null) {
|
|
2602
|
+
return stopWords(store, request.runId, worktree, `stopped while the agent ran — the work is preserved uncommitted in ${worktree}`);
|
|
2603
|
+
}
|
|
2604
|
+
const changes = await unhandedChanges(git, worktree);
|
|
2605
|
+
const saved = changes === null || changes.length === 0 ? null
|
|
2606
|
+
: await saveWorkPatch(git, worktree, join(root, String(request.runId), "service-stop-work.patch"), `# work in ${worktree} when Toolroll stopped\n# kept ${captured.clock().toISOString()}\n`);
|
|
2607
|
+
const kept = changes !== null && changes.length === 0 ? "It had not changed any files yet"
|
|
2608
|
+
: saved !== null && saved.ok ? `Its work is kept in ${worktree} and saved as ${saved.file}` : `Its work is kept uncommitted in ${worktree}`;
|
|
2609
|
+
return `${SERVICE_STOP_WORDS} ${kept}; the task is back in the queue and resumes from it when the builder runs again.`;
|
|
2610
|
+
}
|
|
2611
|
+
export const SERVICE_STOP_WORDS = "Toolroll stopped while this task was building.";
|
|
2592
2612
|
/**
|
|
2593
2613
|
* The attempt ended without a handoff, even after any resumed turn. When it
|
|
2594
2614
|
* left changes, they are never discarded: first saved as a patch in this
|
package/dist/chat-actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Shared, exact-state actions. Models may prepare; only the existing human
|
|
2
2
|
* confirmation door executes. Protected actions use a one-use review receipt. */
|
|
3
|
+
import { withActor } from "./actor.js";
|
|
3
4
|
import { publicChatText } from "./chat-display.js";
|
|
4
5
|
import { gateWords } from "./approval-policy.js";
|
|
5
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -1102,7 +1103,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1102
1103
|
throw Error(completed.message);
|
|
1103
1104
|
}
|
|
1104
1105
|
else if (payload.operation === "task_cancel") {
|
|
1105
|
-
const result = store.cancelTask(task, now);
|
|
1106
|
+
const result = withActor({ account: who.name, lead: false }, () => store.cancelTask(task, now));
|
|
1106
1107
|
if (!result.ok)
|
|
1107
1108
|
throw Error(`Task was not cancelled: ${result.reason}.`);
|
|
1108
1109
|
}
|
package/dist/chat-delivery.js
CHANGED
|
@@ -627,6 +627,8 @@ export async function planChatNotifications(options) {
|
|
|
627
627
|
const personal = notification.recipient !== null;
|
|
628
628
|
if (notification.createdAt >= binding.created &&
|
|
629
629
|
notification.resolvedAt === null &&
|
|
630
|
+
// Pings follow responsibility: the lead's work, this person's own act and a muted project stay in the console.
|
|
631
|
+
store.pingAllowed(notification, binding.approver) &&
|
|
630
632
|
// A flow decision for "anyone who approves" reaches every approver who can see the project.
|
|
631
633
|
// A notification addressed to this person reaches them whatever project
|
|
632
634
|
// their channel follows (a sign-in pause is the installation's, v108).
|
package/dist/chat-quiet.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { assignmentOf } from "./assignment.js";
|
|
8
8
|
import { chatControlHref } from "./chat-controls.js";
|
|
9
9
|
import { telegramProgressCard } from "./telegram-progress.js";
|
|
10
|
-
import { assignmentStatusFacts, headlineEmoji, taskStatusOf } from "./task-status.js";
|
|
10
|
+
import { assignmentStatusFacts, headlineEmoji, replacedWords, taskStatusOf } from "./task-status.js";
|
|
11
11
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
12
12
|
import { isLifecycleNotification } from "./store.js";
|
|
13
13
|
/** What always makes a new message, even in quiet mode: a question or approval waiting, a failure or other
|
|
@@ -55,6 +55,16 @@ function taskLine(store, taskRef, now, root) {
|
|
|
55
55
|
return null;
|
|
56
56
|
const task = store.getTask(ref.externalId);
|
|
57
57
|
const title = phoneText(task?.title ?? ref.externalId, 88);
|
|
58
|
+
// Replaced, never "Cancelled": the card names its successor and links to it.
|
|
59
|
+
const successor = task?.state === "cancelled" ? store.replacementOf(ref.externalId) : null;
|
|
60
|
+
if (successor !== null) {
|
|
61
|
+
const heading = `${headlineEmoji("Stopped")} ${replacedWords(successor)}`;
|
|
62
|
+
const text = [title, heading, "", `${projectLabel(ref.repo)} · ${ref.externalId}`].join("\n");
|
|
63
|
+
return { title, icon: headlineEmoji("Stopped"), status: replacedWords(successor), project: ref.repo, view: {
|
|
64
|
+
text, entities: [{ type: "bold", offset: 0, length: title.length }, { type: "bold", offset: title.length + 1, length: heading.length }],
|
|
65
|
+
link: { label: `Open ${phoneText(successor, 40)}`, path: chatControlHref("task", successor) },
|
|
66
|
+
} };
|
|
67
|
+
}
|
|
58
68
|
const run = cardRun(store, taskRef);
|
|
59
69
|
if (run !== null) {
|
|
60
70
|
const view = telegramProgressCard(store, run, ref.externalId, ref.repo, now, root);
|
|
@@ -26,6 +26,18 @@ export declare const CHAT_TASK_ACTIONS: {
|
|
|
26
26
|
readonly label: "Remove dependency";
|
|
27
27
|
readonly detail: "Stops waiting for the selected task. Other requirements still apply.";
|
|
28
28
|
};
|
|
29
|
+
readonly run_checks: {
|
|
30
|
+
readonly label: "Run checks";
|
|
31
|
+
readonly detail: "Runs the project's quick check on this result's exact commit. A pass upgrades its status; a failure stays visible.";
|
|
32
|
+
};
|
|
33
|
+
readonly run_full_checks: {
|
|
34
|
+
readonly label: "Run full checks";
|
|
35
|
+
readonly detail: "Runs the project's full check on this result's exact commit. A pass upgrades its status; a failure stays visible.";
|
|
36
|
+
};
|
|
37
|
+
readonly add_tests: {
|
|
38
|
+
readonly label: "Add tests";
|
|
39
|
+
readonly detail: "Files a small task to write tests for this change. Nothing runs until it is approved.";
|
|
40
|
+
};
|
|
29
41
|
};
|
|
30
42
|
export declare function isChatTaskAction(value: unknown): value is keyof typeof CHAT_TASK_ACTIONS;
|
|
31
43
|
/** Bind cards to a specific execution and its current state, not the family's moving pointer. */
|
|
@@ -34,6 +46,7 @@ export declare function chatTaskStamp(store: Store, who: VerifiedApprover, task:
|
|
|
34
46
|
export declare function chatTaskRun(store: Store, task: string, operation: "stop" | "resume", now: Date): number | null;
|
|
35
47
|
export declare function applyChatTaskAction(store: Store, who: VerifiedApprover, payload: Record<string, unknown>, now: Date, web: boolean, controls?: Pick<StopRequest, "held" | "deferSignal"> & {
|
|
36
48
|
via?: StopRequest["via"];
|
|
49
|
+
evidenceRoot?: string;
|
|
37
50
|
}): {
|
|
38
51
|
ok: true;
|
|
39
52
|
taskId: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { verifiedAuthor } from "./store.js";
|
|
2
2
|
import { requestTaskStop, taskControlOf } from "./task-control.js";
|
|
3
3
|
import { authorizePlanUnderMode } from "./plan-auto.js";
|
|
4
|
+
import { fileAddTestsTask, requestFollowUpChecks } from "./result-follow-ups.js";
|
|
4
5
|
export const CHAT_TASK_ACTIONS = {
|
|
5
6
|
// Build #1604 feedback: one short consequence that still says saved work
|
|
6
7
|
// remains, other tasks continue, and the pause waits for acknowledgement.
|
|
@@ -10,6 +11,10 @@ export const CHAT_TASK_ACTIONS = {
|
|
|
10
11
|
plan: { label: "Plan first", detail: "Requests a plan. Building still requires approval." },
|
|
11
12
|
wait_for: { label: "Add dependency", detail: "Waits for the selected task to finish." },
|
|
12
13
|
stop_waiting: { label: "Remove dependency", detail: "Stops waiting for the selected task. Other requirements still apply." },
|
|
14
|
+
// Follow-ups on a result (result-follow-ups.ts): the same actions as the result page.
|
|
15
|
+
run_checks: { label: "Run checks", detail: "Runs the project's quick check on this result's exact commit. A pass upgrades its status; a failure stays visible." },
|
|
16
|
+
run_full_checks: { label: "Run full checks", detail: "Runs the project's full check on this result's exact commit. A pass upgrades its status; a failure stays visible." },
|
|
17
|
+
add_tests: { label: "Add tests", detail: "Files a small task to write tests for this change. Nothing runs until it is approved." },
|
|
13
18
|
};
|
|
14
19
|
export function isChatTaskAction(value) {
|
|
15
20
|
return typeof value === "string" && Object.hasOwn(CHAT_TASK_ACTIONS, value);
|
|
@@ -52,7 +57,7 @@ export function applyChatTaskAction(store, who, payload, now, web, controls = {}
|
|
|
52
57
|
// Resume remains the existing nonce + password ceremony. A proposal only opens it.
|
|
53
58
|
if (operation === "resume")
|
|
54
59
|
return { ok: true, taskId: task, said: `Resume review requested for ${task}. Complete the password confirmation on the task to resume.` };
|
|
55
|
-
const { via, ...rest } = controls;
|
|
60
|
+
const { via, evidenceRoot: _root, ...rest } = controls;
|
|
56
61
|
const result = requestTaskStop(store, { taskId: task, runId: run, by: verifiedAuthor(who.name), via: via ?? (web ? "web" : "cli"), ...rest }, now);
|
|
57
62
|
return result.ok ? { ok: true, taskId: task, said: `Stop requested for ${task}, run #${run}.` }
|
|
58
63
|
: { ok: false, message: result.detail };
|
|
@@ -61,6 +66,19 @@ export function applyChatTaskAction(store, who, payload, now, web, controls = {}
|
|
|
61
66
|
const result = store.requeueTask(task, who.name, now);
|
|
62
67
|
return result.ok ? { ok: true, taskId: task, said: "Task queued again." } : { ok: false, message: result.reason === "claimed" ? "This task is already running." : result.reason === "not-stalled" ? "This task does not need another attempt." : "That task is no longer available." };
|
|
63
68
|
}
|
|
69
|
+
if (operation === "run_checks" || operation === "run_full_checks" || operation === "add_tests") {
|
|
70
|
+
const result = store.runsFor(ref.id).find(one => one.role === "builder" && one.finishedAt !== null && one.headRevision !== null) ?? null;
|
|
71
|
+
if (result === null)
|
|
72
|
+
return { ok: false, message: "This task has no finished result yet." };
|
|
73
|
+
if (operation === "add_tests") {
|
|
74
|
+
const filed = fileAddTestsTask(store, controls.evidenceRoot ?? null, { runId: result.id, actor: who.name, filedVia: web ? "console" : "cli", admittedRepos: who.repos }, now);
|
|
75
|
+
return filed.ok ? { ok: true, taskId: filed.id, said: filed.existing ? `Tests are already filed as ${filed.id}.` : `Filed ${filed.id} to add tests. Review and approve it to start.` }
|
|
76
|
+
: { ok: false, message: filed.message };
|
|
77
|
+
}
|
|
78
|
+
const asked = requestFollowUpChecks(store, { runId: result.id, level: operation === "run_full_checks" ? "full" : "quick", actor: who.name }, now);
|
|
79
|
+
return asked.ok ? { ok: true, taskId: task, said: `${asked.level === "full" ? "Full" : "Quick"} checks ${asked.existing ? "are already" : "are"} queued on this result's commit.` }
|
|
80
|
+
: { ok: false, message: asked.message };
|
|
81
|
+
}
|
|
64
82
|
if (operation === "plan") {
|
|
65
83
|
const result = store.requestPlan(ref.id, now);
|
|
66
84
|
if (!result.ok)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Settings → Projects → Checks: how much checks after each build, in plain
|
|
2
|
+
* words. One choice (Quick, Full, Off) and the quick command beside the full
|
|
3
|
+
* one. Every change takes the password and lands in the ledger; approving a
|
|
4
|
+
* command restates exactly what will run, unattended, before the yes. */
|
|
5
|
+
import { type CheckLevel } from "./check-levels.js";
|
|
6
|
+
import type { VerifyCommand } from "./store.js";
|
|
7
|
+
export type CheckSettingsView = {
|
|
8
|
+
repo: string;
|
|
9
|
+
name: string;
|
|
10
|
+
csrf: string;
|
|
11
|
+
canChange: boolean;
|
|
12
|
+
level: CheckLevel;
|
|
13
|
+
full: VerifyCommand | null;
|
|
14
|
+
quick: VerifyCommand | null;
|
|
15
|
+
/** A starting point for the quick command, from the project's scripts. */
|
|
16
|
+
suggestion: string | null;
|
|
17
|
+
said: string | null;
|
|
18
|
+
problem: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function checkSettingsHtml(view: CheckSettingsView): string;
|
|
21
|
+
export declare const CHECK_SETTINGS_CSS = "\n .check-settings { display: grid; gap: .6rem; max-width: 40rem; }\n .check-settings p, .check-settings h2 { margin: 0; }\n .check-settings h2 { font-size: 1rem; }\n .check-settings fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: .25rem; }\n .check-settings legend { font-weight: 600; margin-bottom: .25rem; }\n .check-settings label { display: grid; gap: .25rem; }\n .check-settings .check-choice { display: flex; align-items: flex-start; gap: .6rem; min-height: 44px; padding: .35rem 0; cursor: pointer; }\n .check-settings .check-choice > span { display: grid; gap: .1rem; }\n .check-settings .check-choice input { margin-top: .2rem; }\n .check-settings input[type=password], .check-settings input[type=number], .check-settings textarea { min-height: 44px; box-sizing: border-box; width: 100%; }\n .check-settings button { min-height: 44px; justify-self: start; }\n .check-settings .check-command { overflow-wrap: anywhere; }\n .check-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n @media (max-width: 480px) { .check-settings button { justify-self: stretch; } }\n .follow-ups .follow-up-list { margin: .4rem 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; }\n .follow-ups .follow-up-act { margin: 0; }\n .follow-ups button { min-height: 44px; }\n @media (max-width: 480px) { .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: grid; } .follow-ups .follow-up-act button { width: 100%; } }\n";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Settings → Projects → Checks: how much checks after each build, in plain
|
|
2
|
+
* words. One choice (Quick, Full, Off) and the quick command beside the full
|
|
3
|
+
* one. Every change takes the password and lands in the ledger; approving a
|
|
4
|
+
* command restates exactly what will run, unattended, before the yes. */
|
|
5
|
+
import { CHECK_LEVELS, CHECK_LEVEL_HINTS, CHECK_LEVEL_WORDS } from "./check-levels.js";
|
|
6
|
+
const escape = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7
|
+
export function checkSettingsHtml(view) {
|
|
8
|
+
const hidden = (act) => `<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="${act}">`;
|
|
9
|
+
const password = `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
|
|
10
|
+
const notes = [
|
|
11
|
+
view.said === null ? "" : `<p class="notice" role="status">${escape(view.said)}</p>`,
|
|
12
|
+
view.problem === null ? "" : `<p class="problem" role="alert">${escape(view.problem)}</p>`,
|
|
13
|
+
].join("");
|
|
14
|
+
const head = `<p class="meta">${escape(view.name)} · <span class="mono">${escape(view.repo)}</span></p>${notes}`;
|
|
15
|
+
const command = (one) => `<p class="mono check-command">${escape(one.command)}</p><p class="meta">Up to ${Math.round(one.timeoutMs / 1000)}s · approved by ${escape(one.approvedBy)}</p>`;
|
|
16
|
+
// Quick with no quick command runs the full check; say so where the choice is made.
|
|
17
|
+
const fallsBack = view.level === "quick" && view.quick === null;
|
|
18
|
+
const current = `<p><strong>${CHECK_LEVEL_WORDS[view.level]}</strong> · ${escape(CHECK_LEVEL_HINTS[view.level])}</p>` +
|
|
19
|
+
(fallsBack ? `<p class="meta">No quick command yet, so builds run the full check.</p>` : "") +
|
|
20
|
+
(view.level !== "full" && view.full !== null ? `<p class="meta">The full check still runs when a pull request opens, and Merge waits for it.</p>` : "");
|
|
21
|
+
if (!view.canChange) {
|
|
22
|
+
return head + `<section class="card check-settings">${current}</section>` +
|
|
23
|
+
`<section class="card check-settings"><h2>Quick check</h2>${view.quick === null ? `<p class="meta">None yet.</p>` : command(view.quick)}</section>` +
|
|
24
|
+
`<section class="card check-settings"><h2>Full check</h2>${view.full === null ? `<p class="meta">None yet.</p>` : command(view.full)}</section>`;
|
|
25
|
+
}
|
|
26
|
+
const choices = CHECK_LEVELS.map(level => `<label class="check-choice"><input type="radio" name="level" value="${level}"${level === view.level ? " checked" : ""}>` +
|
|
27
|
+
`<span><strong>${CHECK_LEVEL_WORDS[level]}</strong><span class="meta">${escape(CHECK_LEVEL_HINTS[level])}</span></span></label>`).join("");
|
|
28
|
+
const levelForm = `<form method="post" action="/settings/checks" class="card check-settings">${hidden("level")}` +
|
|
29
|
+
`<fieldset><legend>After each build</legend>${choices}</fieldset>` +
|
|
30
|
+
(fallsBack ? `<p class="meta">No quick command yet, so Quick runs the full check.</p>` : "") +
|
|
31
|
+
`<p class="meta">A task can choose its own checks when it's filed. With Quick or Off, the full check runs when a pull request opens, and Merge waits for it unless you merge anyway.</p>` +
|
|
32
|
+
`${password}<button type="submit">Save</button></form>`;
|
|
33
|
+
const quickTerms = `<ul class="check-terms"><li>Runs unattended right after every Quick build, in that build's checkout.</li><li>Plain environment: no credentials.</li><li>A failure shows on the result; nothing is blocked.</li></ul>`;
|
|
34
|
+
const quickForm = (value, submit) => `<form method="post" action="/settings/checks" class="check-settings">${hidden("quick")}` +
|
|
35
|
+
`<label>Command<textarea name="command" rows="2" maxlength="2000" spellcheck="false" class="mono" required>${escape(value)}</textarea></label>` +
|
|
36
|
+
`<label>Time limit (seconds)<input type="number" name="timeout" min="1" max="3600" value="${view.quick === null ? 180 : Math.round(view.quick.timeoutMs / 1000)}" required></label>` +
|
|
37
|
+
`${quickTerms}${password}<button type="submit">${submit}</button></form>`;
|
|
38
|
+
const quick = view.quick === null
|
|
39
|
+
? `<section class="card check-settings"><h2>Quick check</h2>` +
|
|
40
|
+
(view.suggestion === null ? `<p class="meta">Typecheck and the tests near the change, ideally under 2 minutes.</p>` : `<p class="meta">Suggested from the project's scripts. Edit it before approving.</p>`) +
|
|
41
|
+
quickForm(view.suggestion ?? "", "Approve quick check") + `</section>`
|
|
42
|
+
: `<section class="card check-settings"><h2>Quick check</h2>${command(view.quick)}` +
|
|
43
|
+
`<details class="settings-more"><summary>Change quick check</summary>${quickForm(view.quick.command, "Approve change")}</details>` +
|
|
44
|
+
`<details class="settings-more"><summary>Remove quick check</summary><form method="post" action="/settings/checks" class="check-settings">${hidden("quick-clear")}` +
|
|
45
|
+
`<p class="meta">Quick builds run the full check instead.</p>${password}<button type="submit" class="danger">Remove</button></form></details></section>`;
|
|
46
|
+
const full = `<details class="settings-more"><summary>Full check</summary><section class="card check-settings">` +
|
|
47
|
+
(view.full === null ? `<p class="meta">None yet. Approve one with <span class="mono">toolroll verify set --repo … --command "…"</span>.</p>` : command(view.full)) + `</section></details>`;
|
|
48
|
+
return head + levelForm + quick + full;
|
|
49
|
+
}
|
|
50
|
+
export const CHECK_SETTINGS_CSS = `
|
|
51
|
+
.check-settings { display: grid; gap: .6rem; max-width: 40rem; }
|
|
52
|
+
.check-settings p, .check-settings h2 { margin: 0; }
|
|
53
|
+
.check-settings h2 { font-size: 1rem; }
|
|
54
|
+
.check-settings fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: .25rem; }
|
|
55
|
+
.check-settings legend { font-weight: 600; margin-bottom: .25rem; }
|
|
56
|
+
.check-settings label { display: grid; gap: .25rem; }
|
|
57
|
+
.check-settings .check-choice { display: flex; align-items: flex-start; gap: .6rem; min-height: 44px; padding: .35rem 0; cursor: pointer; }
|
|
58
|
+
.check-settings .check-choice > span { display: grid; gap: .1rem; }
|
|
59
|
+
.check-settings .check-choice input { margin-top: .2rem; }
|
|
60
|
+
.check-settings input[type=password], .check-settings input[type=number], .check-settings textarea { min-height: 44px; box-sizing: border-box; width: 100%; }
|
|
61
|
+
.check-settings button { min-height: 44px; justify-self: start; }
|
|
62
|
+
.check-settings .check-command { overflow-wrap: anywhere; }
|
|
63
|
+
.check-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
64
|
+
@media (max-width: 480px) { .check-settings button { justify-self: stretch; } }
|
|
65
|
+
.follow-ups .follow-up-list { margin: .4rem 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
66
|
+
.follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; }
|
|
67
|
+
.follow-ups .follow-up-act { margin: 0; }
|
|
68
|
+
.follow-ups button { min-height: 44px; }
|
|
69
|
+
@media (max-width: 480px) { .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: grid; } .follow-ups .follow-up-act button { width: 100%; } }
|
|
70
|
+
`;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Store, VerifyCommand } from "./store.js";
|
|
2
|
+
export declare const CHECK_LEVELS: readonly ["quick", "full", "off"];
|
|
3
|
+
export type CheckLevel = (typeof CHECK_LEVELS)[number];
|
|
4
|
+
export declare const isCheckLevel: (value: unknown) => value is CheckLevel;
|
|
5
|
+
export declare const CHECK_LEVEL_WORDS: Readonly<Record<CheckLevel, string>>;
|
|
6
|
+
export declare const CHECK_LEVEL_HINTS: Readonly<Record<CheckLevel, string>>;
|
|
7
|
+
/** Ledger actions. The ledger is append-only, so the newest entry is the setting. */
|
|
8
|
+
export declare const PROJECT_LEVEL_ACTION = "check level changed";
|
|
9
|
+
export declare const TASK_LEVEL_ACTION = "task checks chosen";
|
|
10
|
+
export declare const RUN_LEVEL_ACTION = "checks used";
|
|
11
|
+
/** The quick command's grant lives beside the full one under its own key. */
|
|
12
|
+
export declare const quickVerifyKey: (repo: string) => string;
|
|
13
|
+
export declare function liveQuickCommand(store: Store, repo: string): VerifyCommand | null;
|
|
14
|
+
/** Ledger reads go through the repo index: `repo = ?` (or IS NULL for unplaced work). */
|
|
15
|
+
export declare const repoClause: (repo: string | null) => [string, string[]];
|
|
16
|
+
export declare function repoOfRun(store: Store, runId: number): string | null;
|
|
17
|
+
export type ProjectCheckLevel = {
|
|
18
|
+
level: CheckLevel;
|
|
19
|
+
setBy: string | null;
|
|
20
|
+
at: string | null;
|
|
21
|
+
};
|
|
22
|
+
/** A project's level. With nothing recorded it is Full: a project that existed
|
|
23
|
+
* before levels keeps exactly the check it had. New projects are stamped Quick
|
|
24
|
+
* when they are added (stampNewProjectLevel). */
|
|
25
|
+
export declare function projectCheckLevel(store: Store, repo: string): ProjectCheckLevel;
|
|
26
|
+
/** An approver's act: set the project's level. The ledger keeps before → after. */
|
|
27
|
+
export declare function setProjectCheckLevel(store: Store, repo: string, level: CheckLevel, by: string, now: Date): {
|
|
28
|
+
changed: boolean;
|
|
29
|
+
before: CheckLevel;
|
|
30
|
+
};
|
|
31
|
+
/** A project added from now on starts on Quick. One that already has a level,
|
|
32
|
+
* or an approved check from before levels existed, is left exactly as it is. */
|
|
33
|
+
export declare function stampNewProjectLevel(store: Store, repo: string, now: Date): boolean;
|
|
34
|
+
/** A task's own choice, if it made one; a revision inherits the task it revises. */
|
|
35
|
+
export declare function taskCheckLevel(store: Store, taskId: string): CheckLevel | null;
|
|
36
|
+
/** A filing choice: this task's checks, overriding the project. Fixed once its
|
|
37
|
+
* plan is approved, so what the approver saw is what runs. */
|
|
38
|
+
export declare function setTaskCheckLevel(store: Store, taskId: string, level: CheckLevel, by: string, now: Date): {
|
|
39
|
+
ok: true;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
export type EffectiveCheckLevel = {
|
|
45
|
+
level: CheckLevel;
|
|
46
|
+
from: "task" | "project";
|
|
47
|
+
};
|
|
48
|
+
export declare function effectiveCheckLevel(store: Store, repo: string | null, taskId: string | null): EffectiveCheckLevel;
|
|
49
|
+
/** The command a level runs. Quick without its own command runs the full one
|
|
50
|
+
* (and says so: the level that ran is Full). Off runs nothing. */
|
|
51
|
+
export declare function checkCommandFor(store: Store, repo: string, level: CheckLevel): {
|
|
52
|
+
level: CheckLevel;
|
|
53
|
+
command: VerifyCommand | null;
|
|
54
|
+
};
|
|
55
|
+
/** The builder records which level a run used, so its result says it honestly. */
|
|
56
|
+
export declare function recordRunCheckLevel(store: Store, run: {
|
|
57
|
+
id: number;
|
|
58
|
+
taskId: string;
|
|
59
|
+
repo: string | null;
|
|
60
|
+
}, level: CheckLevel, from: EffectiveCheckLevel["from"], now: Date): void;
|
|
61
|
+
/** The level a finished run's checks used; null for a run from before levels. */
|
|
62
|
+
export declare function runCheckLevel(store: Store, runId: number): CheckLevel | null;
|
|
63
|
+
/** A starting point for the quick command, from the project's own scripts.
|
|
64
|
+
* It is only a suggestion: a person approves the exact command. */
|
|
65
|
+
export declare function suggestQuickCommand(repo: string): string | null;
|
|
66
|
+
/** "skip the tests", "run the full checks": a filing request in chat, as a level. */
|
|
67
|
+
export declare function checkLevelFromWords(text: string): CheckLevel | null;
|