toolroll 0.9.97 → 0.9.98
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 +10 -0
- package/dist/approval-authority.d.ts +10 -1
- package/dist/approval-authority.js +18 -2
- package/dist/browser/workspace.js +1 -1
- package/dist/browser-workspace.d.ts +4 -3
- package/dist/chat-actions.d.ts +40 -5
- package/dist/chat-actions.js +124 -13
- package/dist/chat-card.js +4 -1
- package/dist/chat-channel.d.ts +3 -0
- package/dist/chat-channel.js +24 -7
- package/dist/chat-decide.js +4 -2
- package/dist/chat-task-actions.js +1 -1
- package/dist/chat-terms.d.ts +8 -0
- package/dist/chat-terms.js +14 -0
- package/dist/chat-voice.js +1 -1
- package/dist/contracts/chat-actions.d.ts +6 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +6 -0
- package/dist/contracts/cli.js +3 -0
- package/dist/contracts/lead-tools.d.ts +17 -0
- package/dist/contracts/lead-tools.js +4 -1
- package/dist/contracts/store-json.d.ts +1 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/fast-start.d.ts +83 -6
- package/dist/fast-start.js +112 -15
- package/dist/home-groups.d.ts +39 -0
- package/dist/home-groups.js +33 -0
- package/dist/lead-answer-eval.d.ts +7 -0
- package/dist/lead-answer-eval.js +6 -2
- package/dist/lead-capabilities.js +1 -1
- package/dist/lead-context.js +2 -2
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +10 -7
- package/dist/lead-doors.d.ts +32 -0
- package/dist/lead-doors.js +80 -14
- package/dist/lead-status.d.ts +3 -0
- package/dist/lead-status.js +8 -3
- package/dist/lead-tools.d.ts +20 -0
- package/dist/lead-tools.js +66 -3
- package/dist/lead-voice.js +1 -1
- package/dist/lead.js +18 -2
- package/dist/local-owner.js +2 -0
- package/dist/one-step-chat-schema.d.ts +22 -0
- package/dist/one-step-chat-schema.js +37 -0
- package/dist/operate.d.ts +27 -1
- package/dist/operate.js +206 -32
- package/dist/project-delete.js +9 -0
- package/dist/project-knowledge.d.ts +7 -7
- package/dist/publish.d.ts +2 -0
- package/dist/publish.js +1 -1
- package/dist/pull-request-flow.d.ts +4 -0
- package/dist/pull-request-flow.js +16 -1
- package/dist/reply-voice.d.ts +23 -0
- package/dist/reply-voice.js +37 -0
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +3 -2
- package/dist/server/chat.js +4 -4
- package/dist/server/chrome.js +2 -2
- package/dist/server/home.d.ts +4 -3
- package/dist/server/home.js +33 -28
- package/dist/server/render-chat.js +11 -6
- package/dist/server/render-pages.js +2 -2
- package/dist/setup-guide.d.ts +2 -2
- package/dist/store.d.ts +4 -2
- package/dist/store.js +35 -12
- package/dist/telegram-lead.js +69 -5
- package/dist/telegram-status.js +9 -4
- package/dist/telegram.d.ts +10 -0
- package/dist/telegram.js +92 -8
- package/dist/toolroll-update.js +3 -1
- package/dist/tui-contract.d.ts +2 -0
- package/dist/tui-source.js +3 -0
- package/dist/tui.js +41 -41
- package/dist/typed-start.d.ts +51 -0
- package/dist/typed-start.js +98 -0
- package/dist/wake-hub.d.ts +72 -0
- package/dist/wake-hub.js +151 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a lead reply reads (docs/plans/lead-voice.md, V3): its length, its sentences, the internal words it says and
|
|
3
|
+
* whether it opens by describing its own steps. Measured on the shaped text a person sees, never the raw reply.
|
|
4
|
+
* A measure for the lead-answer eval's report; nothing gates on it and no reply is rewritten from it.
|
|
5
|
+
*/
|
|
6
|
+
import { PLAIN_WORDS } from "./plain-words.js";
|
|
7
|
+
import { shapeReply } from "./reply-shape.js";
|
|
8
|
+
/** A reply longer than this is counted as long in the eval's report. */
|
|
9
|
+
export const LONG_REPLY_CHARACTERS = 320;
|
|
10
|
+
const CODE = /```[\s\S]*?(?:```|$)|`[^`\n]+`/g;
|
|
11
|
+
const OWNER_AS_ADDRESS = /(?<![\w-])the\s+owner(?![\w-])/i;
|
|
12
|
+
const NARRATED_OPENING = /^(?:i\s+checked|i\s+looked\s+at|let\s+me|certainly|sure,)/i;
|
|
13
|
+
export function replyVoice(reply) {
|
|
14
|
+
const shaped = shapeReply(reply);
|
|
15
|
+
const prose = shaped.replace(CODE, " ");
|
|
16
|
+
const internalWords = PLAIN_WORDS.filter(one => one.pattern.test(prose)).map(one => one.never);
|
|
17
|
+
if (OWNER_AS_ADDRESS.test(prose))
|
|
18
|
+
internalWords.push("owner");
|
|
19
|
+
return {
|
|
20
|
+
characters: shaped.length,
|
|
21
|
+
sentences: prose.split(/(?<=[.!?])\s+|\n+/).filter(part => /[\p{L}\p{N}]/u.test(part)).length,
|
|
22
|
+
internalWords,
|
|
23
|
+
narratedOpening: NARRATED_OPENING.test(shaped.trimStart()),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** One model's answers, summed up for the report. */
|
|
27
|
+
export function summarizeVoice(voices) {
|
|
28
|
+
const sorted = voices.map(one => one.characters).sort((a, b) => a - b);
|
|
29
|
+
const mid = Math.floor(sorted.length / 2);
|
|
30
|
+
return {
|
|
31
|
+
answers: voices.length,
|
|
32
|
+
medianCharacters: sorted.length === 0 ? 0 : sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2,
|
|
33
|
+
longShare: voices.length === 0 ? 0 : voices.filter(one => one.characters > LONG_REPLY_CHARACTERS).length / voices.length,
|
|
34
|
+
internalWords: voices.reduce((sum, one) => sum + one.internalWords.length, 0),
|
|
35
|
+
narratedOpenings: voices.filter(one => one.narratedOpening).length,
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/scope.d.ts
CHANGED
|
@@ -796,10 +796,15 @@ export declare function fileAndSealUnderMode(store: Store, input: ScopeInput & {
|
|
|
796
796
|
detail?: string;
|
|
797
797
|
};
|
|
798
798
|
export declare function approve(store: Store, taskId: string, by: string, now: Date, sawDigest: string, token: string, mutation?: Mutation,
|
|
799
|
-
/** A fast start (fast-start.ts): Build is this same yes, with its marker in the proof. Only that road passes it.
|
|
799
|
+
/** A fast start (fast-start.ts): Build is this same yes, with its marker in the proof. Only that road passes it.
|
|
800
|
+
* `typed`: a typed yes to a Start card (one-step-chat.md C5), the message that was the yes, recorded beside the marker. */
|
|
800
801
|
fastStart?: {
|
|
801
802
|
rule: string;
|
|
802
803
|
at: string;
|
|
804
|
+
typed?: {
|
|
805
|
+
provider: string;
|
|
806
|
+
event: string;
|
|
807
|
+
};
|
|
803
808
|
},
|
|
804
809
|
/** A person's own yes in chat (owner's decision, docs/plans/toolroll-cards.md): their tap or signed-in click stands in
|
|
805
810
|
* for a password, so `token` is unused. Every rule below still applies. The caller has proved the person is the one
|
package/dist/scope.js
CHANGED
|
@@ -1075,7 +1075,8 @@ export function fileAndSealUnderMode(store, input) {
|
|
|
1075
1075
|
});
|
|
1076
1076
|
}
|
|
1077
1077
|
export function approve(store, taskId, by, now, sawDigest, token, mutation = {},
|
|
1078
|
-
/** A fast start (fast-start.ts): Build is this same yes, with its marker in the proof. Only that road passes it.
|
|
1078
|
+
/** A fast start (fast-start.ts): Build is this same yes, with its marker in the proof. Only that road passes it.
|
|
1079
|
+
* `typed`: a typed yes to a Start card (one-step-chat.md C5), the message that was the yes, recorded beside the marker. */
|
|
1079
1080
|
fastStart,
|
|
1080
1081
|
/** A person's own yes in chat (owner's decision, docs/plans/toolroll-cards.md): their tap or signed-in click stands in
|
|
1081
1082
|
* for a password, so `token` is unused. Every rule below still applies. The caller has proved the person is the one
|
|
@@ -1216,7 +1217,7 @@ export const APPROVAL_PROOF = "approval proved";
|
|
|
1216
1217
|
export const approvalProofDetail = (at, generation, digest, local) => `${new Date(at).toISOString()} · generation ${generation}${local === undefined ? "" : ` · local ${local.generation} ${local.transport}`} · digest ${digest}`;
|
|
1217
1218
|
/** A fast start's proof: the v1 record proofDetailOf reads, with the marker. `local` keeps the legacy words
|
|
1218
1219
|
* localSealStandsSql reads, so a local owner's seal stands on the same rule. */
|
|
1219
|
-
const fastProofDetail = (fastStart) => (at, generation, digest, local) => JSON.stringify({ v: 1, at: new Date(at).toISOString(), generation, digest, fastStart, ...(local === undefined ? {} : { local: `proof · local ${local.generation} ${local.transport} · seal` }) });
|
|
1220
|
+
const fastProofDetail = ({ typed, ...fastStart }) => (at, generation, digest, local) => JSON.stringify({ v: 1, at: new Date(at).toISOString(), generation, digest, fastStart, ...(typed === undefined ? {} : { typed: { provider: typed.provider, event: typed.event } }), ...(local === undefined ? {} : { local: `proof · local ${local.generation} ${local.transport} · seal` }) });
|
|
1220
1221
|
/** The proof of THIS approval: the approver's own row for these exact bytes (never another approval's words). */
|
|
1221
1222
|
export function approvalProofOf(store, taskId, by, digest) {
|
|
1222
1223
|
const rows = store.handle.prepare("SELECT outcome, detail FROM action_ledger WHERE task_id = ? AND action = ? AND actor = ? ORDER BY id DESC LIMIT 50").all(taskId, APPROVAL_PROOF, by);
|
package/dist/server/chat.js
CHANGED
|
@@ -780,7 +780,7 @@ export function createChatHandlers(runtime) {
|
|
|
780
780
|
const cardJson = String(request.headers["accept"] ?? "").includes("application/json") && !body.has("nonce");
|
|
781
781
|
const cardAnswer = (status, ok, said, taskId = null) => respond(response, status, "application/json", JSON.stringify({ ok, said, taskId }));
|
|
782
782
|
if (principal === null)
|
|
783
|
-
return cardJson ? cardAnswer(403, false, "Your
|
|
783
|
+
return cardJson ? cardAnswer(403, false, "Your sign-in changed. Sign in again.") : refuse(response, who, 403, "Your sign-in changed. Sign in again.", back);
|
|
784
784
|
if (op === "dismiss") {
|
|
785
785
|
const dismissed = dismissLeadProposal(store, principal, id, now);
|
|
786
786
|
if (cardJson)
|
|
@@ -849,7 +849,7 @@ export function createChatHandlers(runtime) {
|
|
|
849
849
|
return refuse(response, who, 403, "proposals are confirmed from the browser or the CLI");
|
|
850
850
|
const principal = leadPrincipal(who);
|
|
851
851
|
if (principal === null)
|
|
852
|
-
return refuse(response, who, 403, "
|
|
852
|
+
return refuse(response, who, 403, "Your sign-in changed. Sign in again.", "/chat");
|
|
853
853
|
const id = Number(url.pathname.split("/")[2]);
|
|
854
854
|
store.sweepCoordinatorProposals(now);
|
|
855
855
|
const back = body.get("return") === null ? "/chat" : safeReturn(body.get("return"));
|
|
@@ -919,7 +919,7 @@ export function createChatHandlers(runtime) {
|
|
|
919
919
|
if (who.role === "approver" && enabled.billing === "subscription" && unbound) {
|
|
920
920
|
const principal = leadPrincipal(who);
|
|
921
921
|
if (principal === null)
|
|
922
|
-
return wantsJson ? said(403, "
|
|
922
|
+
return wantsJson ? said(403, "Your sign-in changed. Sign in again.") : refuse(response, who, 403, "Your sign-in changed. Sign in again.", back);
|
|
923
923
|
const sentRequest = body.get("request");
|
|
924
924
|
if (sentRequest !== null && (body.getAll("request").length !== 1 || !/^[a-f0-9]{32}$/.test(sentRequest)))
|
|
925
925
|
return said(409, "This conversation changed. Reload it before sending your message.");
|
|
@@ -961,7 +961,7 @@ export function createChatHandlers(runtime) {
|
|
|
961
961
|
const requestId = sentRequest ?? randomBytes(16).toString("hex");
|
|
962
962
|
const principal = leadPrincipal(who);
|
|
963
963
|
if (principal === null)
|
|
964
|
-
return wantsJson ? said(403, "
|
|
964
|
+
return wantsJson ? said(403, "Your sign-in changed. Sign in again.") : refuse(response, who, 403, "Your sign-in changed. Sign in again.", back);
|
|
965
965
|
const message = (body.get("message") ?? "").trim();
|
|
966
966
|
if (message === "" || message.length > LEAD_MESSAGE_MAX_CHARS) {
|
|
967
967
|
return said(400, `a message is 1 to ${LEAD_MESSAGE_MAX_CHARS} characters`, leadSession.id);
|
package/dist/server/chrome.js
CHANGED
|
@@ -726,7 +726,7 @@ export function workToolRows(scoped = false, _offersChat = false, offersCode = f
|
|
|
726
726
|
{ key: "tasks", href: "/tasks", label: "Task list", hint: "everything, filterable by state" },
|
|
727
727
|
{ key: "recipes", href: "/recipes", label: "Recipes", hint: "choose, customize, and reuse a workflow" },
|
|
728
728
|
{ key: "workbench", href: "/tasks?scope=all", label: "Every project", hint: "every project's tasks in one list" },
|
|
729
|
-
{ key: "ledger", href: "/ledger", label: "
|
|
729
|
+
{ key: "ledger", href: "/ledger", label: "Audit log", hint: "who acted, what happened, and the result" },
|
|
730
730
|
{ key: "spend", href: "/spend", label: "Spend", hint: "what agent work cost, and monthly budgets" },
|
|
731
731
|
];
|
|
732
732
|
return scoped ? rows.filter(row => row.key !== "workbench" && row.key !== "spend") : rows;
|
|
@@ -737,7 +737,7 @@ export function settingsRows(scoped = false, offersSettings = false) {
|
|
|
737
737
|
{ key: "fleet", href: "/fleet", label: "Fleet", hint: "who is working, and on what" },
|
|
738
738
|
{ key: "caps", href: "/caps", label: "Requirements", hint: "tools and credentials builds need" },
|
|
739
739
|
{ key: "people", href: "/people", label: "People", hint: "who can sign in, and what they have done" },
|
|
740
|
-
{ key: "mode", href: "/mode", label: "Operating mode", hint: "
|
|
740
|
+
{ key: "mode", href: "/mode", label: "Operating mode", hint: "how much this project may do without asking" },
|
|
741
741
|
{ key: "system", href: "/system", label: "System", hint: "workers, providers, and grants" },
|
|
742
742
|
];
|
|
743
743
|
return scoped ? rows.filter(row => row.key === "people" || row.key === "settings") : rows;
|
package/dist/server/home.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Home ("/", UI revamp wave B): one sentence that answers "what needs me?", four counts read from the same work index
|
|
3
|
-
* the Tasks list filters (so a count and the list it opens always agree),
|
|
4
|
-
*
|
|
3
|
+
* the Tasks list filters (so a count and the list it opens always agree), the work in three groups (Waiting on you,
|
|
4
|
+
* Working, Done; home-groups.ts), and the lead's message box. Reads only: every act keeps its own route and rules (an agent's
|
|
5
5
|
* question is answered through POST /d/<id>/answer, the same one-click act as its own page).
|
|
6
6
|
*/
|
|
7
7
|
import type { BrowserHomeView } from "../browser-workspace.js";
|
|
@@ -37,5 +37,6 @@ export declare function homeSentence(needs: number, perProject: readonly {
|
|
|
37
37
|
name: string;
|
|
38
38
|
needs: number;
|
|
39
39
|
}[], scope: string | null, any: boolean): string;
|
|
40
|
-
/** Home's view for one person and scope, from
|
|
40
|
+
/** Home's view for one person and scope, from four reads of the work index (what waits on you, what runs, what waits on
|
|
41
|
+
* Toolroll, what was done), grouped by the one rule every surface shares (home-groups.ts). */
|
|
41
42
|
export declare function homeView(facts: HomeFacts): BrowserHomeView;
|
package/dist/server/home.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { browserWorkActionHref } from "../browser-crew.js";
|
|
2
|
-
import {
|
|
2
|
+
import { HOME_GROUP_LABEL, homeGroupOf } from "../home-groups.js";
|
|
3
3
|
import { projectName } from "../project.js";
|
|
4
4
|
import { workIndexPage } from "../work-index.js";
|
|
5
5
|
import { relativeAge } from "./chrome.js";
|
|
6
6
|
import { taskHref } from "./http.js";
|
|
7
7
|
const TONE = { attention: "needs", ready: "needs", live: "live", done: "done", problem: "failed", muted: "", neutral: "" };
|
|
8
|
-
const ASKS = ["decide", "review", "unblock"];
|
|
9
8
|
const ASK_WORD = { decide: "Decision", review: "Result", unblock: "Blocked" };
|
|
10
9
|
/** How many waiting rows Home lists; the rest open in Tasks. */
|
|
11
10
|
export const HOME_NEEDS_SHOWN = 30;
|
|
12
11
|
const HOME_RUNNING_SHOWN = 8;
|
|
12
|
+
/** How many rows finished in the last 7 days Home lists; the rest open in Tasks. */
|
|
13
|
+
const HOME_DONE_SHOWN = 8;
|
|
13
14
|
/** "7 need you across 2 projects." — the sentence and the figures read the same totals. */
|
|
14
15
|
export function homeSentence(needs, perProject, scope, any) {
|
|
15
16
|
if (!any)
|
|
@@ -51,50 +52,54 @@ function rowOf(store, item, now, multi, canAnswer, closedDoor) {
|
|
|
51
52
|
: href === null || action === null ? null : { label: action.label, href }, question,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
|
-
/** Home's view for one person and scope, from
|
|
55
|
+
/** Home's view for one person and scope, from four reads of the work index (what waits on you, what runs, what waits on
|
|
56
|
+
* Toolroll, what was done), grouped by the one rule every surface shares (home-groups.ts). */
|
|
55
57
|
export function homeView(facts) {
|
|
56
58
|
const { store, now, access, project } = facts;
|
|
57
59
|
const read = (view, limit) => workIndexPage(store, now, access, { view, limit, project, ...(facts.evidenceRoot === undefined ? {} : { root: facts.evidenceRoot }) });
|
|
58
60
|
const needs = read("needs-you", HOME_NEEDS_SHOWN);
|
|
59
61
|
const running = read("running", HOME_RUNNING_SHOWN);
|
|
60
|
-
//
|
|
61
|
-
const waiting =
|
|
62
|
+
// Queued: what waits its turn, or what Toolroll or another task moves on, with no person asked.
|
|
63
|
+
const waiting = read("queued", HOME_RUNNING_SHOWN);
|
|
64
|
+
// Done: the Completed list is ordered by completion time, newest first, so its first rows are the newest finished;
|
|
65
|
+
// only those finished in the last 7 days are listed, and that filter runs before the cap.
|
|
66
|
+
const completed = read("completed", HOME_DONE_SHOWN);
|
|
62
67
|
const totals = needs.totals;
|
|
63
68
|
// Queued: the Tasks list's own Queued count (the same predicate its view=queued filter lists).
|
|
64
69
|
const queued = totals.queued;
|
|
65
70
|
// Every project is said by name (scope=all): /tasks alone would narrow to the session's open project.
|
|
66
71
|
const scopeQuery = project === null ? "scope=all&" : `project=${encodeURIComponent(project)}&`;
|
|
72
|
+
// Each figure keeps the name of the list it opens; Done is the whole Completed list, not just this week.
|
|
67
73
|
const counts = [
|
|
68
|
-
{ key: "needs", label:
|
|
74
|
+
{ key: "needs", label: HOME_GROUP_LABEL.you, value: totals["needs-you"], href: `/tasks?${scopeQuery}view=needs-you` },
|
|
69
75
|
{ key: "running", label: "Running", value: totals.running, href: `/tasks?${scopeQuery}view=running` },
|
|
70
76
|
{ key: "queued", label: "Queued", value: queued, href: `/tasks?${scopeQuery}view=queued` },
|
|
71
|
-
{ key: "finished", label:
|
|
77
|
+
{ key: "finished", label: HOME_GROUP_LABEL.done, value: totals.completed, href: `/tasks?${scopeQuery}view=completed` },
|
|
72
78
|
];
|
|
73
|
-
|
|
79
|
+
// One row per family, in the first group that claims it.
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const take = (items, group) => items.filter(one => {
|
|
82
|
+
if (homeGroupOf(one) !== group || seen.has(one.rootId))
|
|
83
|
+
return false;
|
|
84
|
+
seen.add(one.rootId);
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
// Waiting on you, in today's order of what it asks: decide (with any row whose ask the index couldn't name, which
|
|
88
|
+
// still waits on this person and is never lost), then review, then unblock. Each row's chip names its ask.
|
|
89
|
+
const yours = take(["decide", null, "review", "unblock"].flatMap(ask => needs.items.filter(one => one.ask === ask)), "you");
|
|
90
|
+
// Working: what runs, then what is queued (waits its turn, or on Toolroll or another task), each read with its own bound.
|
|
91
|
+
const working = [...take(running.items, "working"), ...take(waiting.items, "working")];
|
|
92
|
+
const weekAgo = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
93
|
+
const done = take(completed.items.filter(one => (one.completion?.at ?? one.updatedAt) >= weekAgo), "done").slice(0, HOME_DONE_SHOWN);
|
|
94
|
+
const multi = project === null && new Set([...yours, ...working, ...done].map(one => one.repo ?? "")).size > 1;
|
|
74
95
|
const groups = [];
|
|
75
|
-
for (const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
groups.push({ key: ask, label: ASK_LABEL[ask], rows });
|
|
96
|
+
for (const [key, items] of [["you", yours], ["working", working], ["done", done]]) {
|
|
97
|
+
if (items.length > 0)
|
|
98
|
+
groups.push({ key, label: HOME_GROUP_LABEL[key], rows: items.map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor)) });
|
|
79
99
|
}
|
|
80
|
-
// A waiting row whose ask the index couldn't name still waits on this person: it goes under Decide, never lost.
|
|
81
|
-
const unnamed = needs.items.filter(one => one.ask === null).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
82
|
-
if (unnamed.length > 0) {
|
|
83
|
-
const decide = groups.find(one => one.key === "decide");
|
|
84
|
-
if (decide)
|
|
85
|
-
decide.rows.push(...unnamed);
|
|
86
|
-
else
|
|
87
|
-
groups.unshift({ key: "decide", label: ASK_LABEL.decide, rows: unnamed });
|
|
88
|
-
}
|
|
89
|
-
const live = running.items.filter(one => !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
90
|
-
if (live.length > 0)
|
|
91
|
-
groups.push({ key: "running", label: "Running", rows: live });
|
|
92
|
-
const waits = waiting.items.filter(one => one.status.label === "Waiting" && !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
93
|
-
if (waits.length > 0)
|
|
94
|
-
groups.push({ key: "waiting", label: "Waiting", rows: waits });
|
|
95
100
|
const perProject = needs.projects.map(one => ({ name: one.repo === null ? "Unplaced" : projectName(one.repo), needs: one.totals["needs-you"] }));
|
|
96
101
|
const any = totals.all > 0 || totals.completed > 0 || (totals.archived ?? 0) > 0;
|
|
97
|
-
const shown =
|
|
102
|
+
const shown = yours.length;
|
|
98
103
|
return {
|
|
99
104
|
kind: "home",
|
|
100
105
|
say: homeSentence(totals["needs-you"], perProject, project === null ? null : projectName(project), any),
|
|
@@ -396,7 +396,7 @@ export function chatLimitsHtml(facts) {
|
|
|
396
396
|
html `<span>${facts.turnsToday} / ${facts.dailyTurns} turns today</span>`,
|
|
397
397
|
];
|
|
398
398
|
if (facts.subscription)
|
|
399
|
-
rows.push(html `<span>membership login · no dollar
|
|
399
|
+
rows.push(html `<span>membership login · no dollar limit</span>`);
|
|
400
400
|
else if (facts.weekly !== null)
|
|
401
401
|
rows.push(html `<span>this week ${chatMoney(facts.weekly.spent)} of ${chatMoney(facts.weekly.ceiling)}</span>`);
|
|
402
402
|
return html `<details class="chat-limits"><summary>Model & limits<span class="meta">${facts.subscription ? "membership" : facts.provider}</span></summary><div class="chat-budget">${rows}</div></details>`;
|
|
@@ -564,7 +564,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
564
564
|
hold: { label: "Pause work", action: "hold", icon: html `<rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/>` },
|
|
565
565
|
unhold: { label: "Resume work", action: "release hold", icon: html `<path d="m7 4 13 8-13 8z"/>` },
|
|
566
566
|
steer: { label: "Guidance for the agent", action: "add guidance", icon: html `<path d="M5 12h14"/><path d="m13 6 6 6-6 6"/>` },
|
|
567
|
-
scope: { label: "
|
|
567
|
+
scope: { label: "Plan change", action: "save plan", icon: html `<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4z"/>` },
|
|
568
568
|
answer: { label: "Decision answer", action: "confirm answer", icon: html `<path d="M9.1 9a3 3 0 1 1 5.8 1c0 2-3 2-3 4"/><path d="M12 18h.01"/><circle cx="12" cy="12" r="9"/>` },
|
|
569
569
|
cancel: { label: "Cancel task", action: "open task", icon: html `<path d="m15 9-6 6"/><path d="m9 9 6 6"/><circle cx="12" cy="12" r="9"/>` },
|
|
570
570
|
repair: {
|
|
@@ -599,7 +599,8 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
599
599
|
}
|
|
600
600
|
else if (view.kind === "action") {
|
|
601
601
|
const action = sharedActionPayload(payload);
|
|
602
|
-
|
|
602
|
+
// A Start card (one-step-chat.md C2) names what starts and where; its whole terms and the confirm are on the review page.
|
|
603
|
+
what = action === null ? html `<p>This action is unavailable.</p>` : action.operation === "task_start" ? html `<h3>${action.title}</h3>${facts(["project", mono(action.repo.split(/[\\/]/).filter(Boolean).at(-1) ?? action.repo)])}` : (sharedActionNeedsReview(action) ? html `` : html `${action.terms.map(term => html `<p style="white-space:pre-wrap;overflow-wrap:anywhere">${term}</p>`)}`);
|
|
603
604
|
}
|
|
604
605
|
else if (view.kind === "task_action") {
|
|
605
606
|
const operation = payload["operation"];
|
|
@@ -723,7 +724,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
723
724
|
if (view.state === "pending" && !inert) {
|
|
724
725
|
acts =
|
|
725
726
|
view.kind === "action" && sharedActionPayload(payload) !== null && sharedActionNeedsReview(sharedActionPayload(payload))
|
|
726
|
-
? html `<a class="button-link" href="${sharedActionReviewPath(view.id)}"
|
|
727
|
+
? html `<a class="button-link" href="${sharedActionReviewPath(view.id)}">${reviewWords(sharedActionPayload(payload))}</a>${dismissForm(html `<button class="quiet">Dismiss</button>`)}`
|
|
727
728
|
: view.kind === "control"
|
|
728
729
|
? (isChatControl(payload["control"]) ? html `<a class="button-link" href="${chatControlHref(payload["control"], task, payload["run"], payload["project"])}">${CHAT_CONTROLS[payload["control"]].label}</a>` : html `<p>Control unavailable.</p>`)
|
|
729
730
|
: view.kind === "cancel"
|
|
@@ -758,7 +759,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
758
759
|
const sentence = (words) => words.charAt(0).toUpperCase() + words.slice(1);
|
|
759
760
|
const pending = view.state === "pending" && !inert;
|
|
760
761
|
const primary = !pending ? null
|
|
761
|
-
: reviewAction !== null && sharedActionNeedsReview(reviewAction) ? { kind: "link", label:
|
|
762
|
+
: reviewAction !== null && sharedActionNeedsReview(reviewAction) ? { kind: "link", label: reviewWords(reviewAction), href: sharedActionReviewPath(view.id) }
|
|
762
763
|
: view.kind === "control" ? (isChatControl(payload["control"]) ? { kind: "link", label: CHAT_CONTROLS[payload["control"]].label, href: chatControlHref(payload["control"], task, payload["run"], payload["project"]) } : null)
|
|
763
764
|
: view.kind === "cancel" ? { kind: "link", label: "Open task", href: taskHref(task) }
|
|
764
765
|
: { kind: "confirm", label: sentence(presentation.action), irreversible, native: view.kind === "task_action" && payload["operation"] === "resume" };
|
|
@@ -777,6 +778,10 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
777
778
|
};
|
|
778
779
|
return { html: cardHtml, card };
|
|
779
780
|
}
|
|
781
|
+
/** The review link's words: a Start card's is "Review and start". */
|
|
782
|
+
function reviewWords(action) {
|
|
783
|
+
return action?.operation === "task_start" ? "Review and start" : "Review action";
|
|
784
|
+
}
|
|
780
785
|
export function leadProposalCard(proposal, inert, decision, returnTo = null) {
|
|
781
786
|
return leadProposalCardParts(proposal, inert, decision, returnTo).html;
|
|
782
787
|
}
|
|
@@ -981,7 +986,7 @@ export function leadPage(chrome, data) {
|
|
|
981
986
|
data.session === null ? "" : html `<details class="lead-follow"><summary>Automatic crew updates${data.follow?.enabled ? ' · On' : ''}</summary><p class="meta">${data.follow?.detail ?? 'Automatic crew updates are off.'}</p>${postForm("/chat/mate/follow", html `${data.follow?.enabled ? '' : html `<p>The lead responds when results or decisions arrive. Uses this conversation’s ${subscription ? 'membership usage, with no dollar maximum' : 'remaining spend allowance'} and daily turn limit. Existing task approvals still apply.</p>`}<button type="submit" class="quiet">${data.follow?.enabled ? 'Pause updates' : 'Enable updates'}</button>`, { returnTo, hidden: { enabled: data.follow?.enabled ? 'no' : 'yes' } })}</details>`,
|
|
982
987
|
html `<details class="chat-limits chat-session-details"><summary>Conversation details<span class="meta">${subscription ? "membership" : data.config.provider}</span></summary>\n${joinHtml([
|
|
983
988
|
html `<div class="chat-budget"><span class="mono">${data.config.provider} · ${data.config.model}</span><span>${data.turnsToday} / ${data.config.dailyTurns} turns today</span>${subscription
|
|
984
|
-
? html `<span>membership login · no dollar
|
|
989
|
+
? html `<span>membership login · no dollar limit</span>`
|
|
985
990
|
: html `<span>this conversation: ${chatMoney(data.session?.spentMicrousd ?? 0)} of ${chatMoney(data.session?.ceilingMicrousd ?? 0)}</span><span>this week ${chatMoney(data.weeklySpent)} of ${chatMoney(data.config.weeklyCeilingMicrousd)}</span>`}</div>`,
|
|
986
991
|
data.session === null
|
|
987
992
|
? html `<p class="meta">No conversation yet. Send a message to start one.</p>`
|
|
@@ -439,7 +439,7 @@ export function workPage(chrome, data) {
|
|
|
439
439
|
['finished', 'Finished', data.board.finished, 'completed', 'Nothing has finished yet.'],
|
|
440
440
|
].map(([key, label, page, view, empty]) => ({ key, label, count: page.totals[view], href: query({ view, layout: 'list' }), empty, rows: page.items.map(rowOf) }));
|
|
441
441
|
const putAwayLink = data.canPutAway === true && data.view !== 'archived' ? { label: 'Archive old tasks', href: '/tasks/archive' } : null;
|
|
442
|
-
const toolLinks = [['/tasks?view=order', 'Order the queue'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/tasks?scope=all', 'Every project']]), ['/ledger', '
|
|
442
|
+
const toolLinks = [['/tasks?view=order', 'Order the queue'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/tasks?scope=all', 'Every project']]), ['/ledger', 'Audit log']];
|
|
443
443
|
const newTaskHref = lens === null ? '/tasks/new' : `/tasks/new?project=${encodeURIComponent(lens)}`;
|
|
444
444
|
const emptyNow = data.work.items.length > 0 ? null : filtered && !data.previous ? { text: `No tasks are ${data.state} in this view.`, action: { label: 'Show every state', href: href(data.view, undefined, null) } } : {
|
|
445
445
|
text: data.previous ? 'There are no more tasks on this page.' : current.empty,
|
|
@@ -496,7 +496,7 @@ export function workPage(chrome, data) {
|
|
|
496
496
|
? html `<div class="work-empty" data-work-empty="${data.view}"><p>${data.previous ? 'There are no more tasks on this page.' : current.empty}</p>${data.view === 'all' && !data.previous ? html `<p><a class="button-link" href="${chrome.chat === true ? '/chat' : '/tasks/new'}">${chrome.chat === true ? 'Start in chat' : 'Add a task'}</a></p>` : html `<a href="${href('all')}">See all tasks →</a>`}</div>`
|
|
497
497
|
: html `<div class="work-list">${data.work.items.map(rowHtml)}</div>`;
|
|
498
498
|
const pages = !data.previous && data.work.nextCursor === null ? null : html `<nav class="row work-pagination" aria-label="Task pages">${data.previous ? html `<a class="button-link" href="${href(data.view)}">First page</a>` : null}${data.work.nextCursor === null ? null : html `<a class="button-link" rel="next" href="${href(data.view, data.work.nextCursor)}">Next page</a>`}</nav>`;
|
|
499
|
-
const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/', 'Home'], ['/tasks?view=board', 'Board'], ['/tasks?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/tasks?scope=all', 'Every project']]), ['/ledger', '
|
|
499
|
+
const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/', 'Home'], ['/tasks?view=board', 'Board'], ['/tasks?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/tasks?scope=all', 'Every project']]), ['/ledger', 'Audit log']]
|
|
500
500
|
.map(([path, label]) => html `<a href="${path}">${label}</a>`)}</nav></details>`;
|
|
501
501
|
return screen('work', html `<div class="work-head"><h1>Tasks</h1>${putAwayLink === null ? null : html `<a class="button-link" href="${putAwayLink.href}" data-put-away-old>${putAwayLink.label}</a>`}${tools}</div>${limitsHtml(data.limits ?? null)}${legacyTabs}${stateLinks}${list}${pages}`, { chrome, workspace: { view } });
|
|
502
502
|
}
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
+
content?: never;
|
|
18
19
|
ok: false;
|
|
19
20
|
message: string;
|
|
20
21
|
plan?: never;
|
|
21
|
-
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
+
content?: never;
|
|
33
34
|
ok: false;
|
|
34
35
|
message: string;
|
|
35
36
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/store.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { V155_STEPS } from "./fleet-schema.js";
|
|
|
10
10
|
import { V156_STEPS } from "./fallback-schema.js";
|
|
11
11
|
import { V158_STEPS, type LeadFailureDetail, type LeadRoute } from "./lead-latency-schema.js";
|
|
12
12
|
import { V157_STEPS } from "./chat-live-schema.js";
|
|
13
|
+
import { V159_STEPS } from "./one-step-chat-schema.js";
|
|
13
14
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
14
15
|
import { type ChatBinding } from "./chat-core.js";
|
|
15
16
|
import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET } from "./chat-messages.js";
|
|
@@ -250,7 +251,7 @@ export type SealedLedgerEntry = LedgerEntry & {
|
|
|
250
251
|
hash: string;
|
|
251
252
|
} | null;
|
|
252
253
|
};
|
|
253
|
-
export declare const SCHEMA_VERSION =
|
|
254
|
+
export declare const SCHEMA_VERSION = 159;
|
|
254
255
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
255
256
|
export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
|
|
256
257
|
/**
|
|
@@ -442,7 +443,7 @@ export declare const V147_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
442
443
|
}>>>>;
|
|
443
444
|
/** The v147 migration's steps after the epoch, in order: a test seam (OpenOptions.migrationStep) may stop after each. */
|
|
444
445
|
export declare const V147_STEPS: readonly ["epoch", "local-mode", "password-set", "web-session", "console-instance"];
|
|
445
|
-
export type MigrationStep = (typeof V147_STEPS)[number] | (typeof V148_STEPS)[number] | (typeof V149_STEPS)[number] | (typeof V150_STEPS)[number] | (typeof V151_STEPS)[number] | (typeof V152_STEPS)[number] | (typeof V153_STEPS)[number] | (typeof V154_STEPS)[number] | (typeof V156_STEPS)[number] | (typeof V155_STEPS)[number] | (typeof V157_STEPS)[number] | (typeof V158_STEPS)[number];
|
|
446
|
+
export type MigrationStep = (typeof V147_STEPS)[number] | (typeof V148_STEPS)[number] | (typeof V149_STEPS)[number] | (typeof V150_STEPS)[number] | (typeof V151_STEPS)[number] | (typeof V152_STEPS)[number] | (typeof V153_STEPS)[number] | (typeof V154_STEPS)[number] | (typeof V156_STEPS)[number] | (typeof V155_STEPS)[number] | (typeof V157_STEPS)[number] | (typeof V158_STEPS)[number] | (typeof V159_STEPS)[number];
|
|
446
447
|
/**
|
|
447
448
|
* v148 (docs/plans/results-routing.md): every table the results-routing arc uses, landed at once because a current
|
|
448
449
|
* store runs no DDL. Slice 1 writes run_cohort, route_record and models_generation; route_rule, route_exposure* and
|
|
@@ -532,6 +533,7 @@ export { V155_STEPS };
|
|
|
532
533
|
export { V156_STEPS };
|
|
533
534
|
export { V157_STEPS };
|
|
534
535
|
export { V158_STEPS };
|
|
536
|
+
export { V159_STEPS };
|
|
535
537
|
/**
|
|
536
538
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
537
539
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
package/dist/store.js
CHANGED
|
@@ -15,6 +15,7 @@ import { FINDING_SOURCES, FINDING_SOURCES_BEFORE_LEAD, V155_SCHEMA, V155_STEPS }
|
|
|
15
15
|
import { V156_SCHEMA, V156_STEPS } from "./fallback-schema.js";
|
|
16
16
|
import { V158_ADDED_COLUMNS, V158_SCHEMA, V158_STEPS, LEAD_FAILURE_DETAILS, LEAD_ROUTES } from "./lead-latency-schema.js";
|
|
17
17
|
import { V157_SCHEMA, V157_STEPS } from "./chat-live-schema.js";
|
|
18
|
+
import { V159_ADDED_COLUMNS, V159_SCHEMA, V159_STEPS } from "./one-step-chat-schema.js";
|
|
18
19
|
import { refArchived, taskArchived } from "./task-archive.js";
|
|
19
20
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
20
21
|
import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
|
|
@@ -1148,7 +1149,10 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
1148
1149
|
// v158 adds, and changes no saved row (docs/plans/responsive-chat.md, slice 5): the tools each lead step asked for, an
|
|
1149
1150
|
// exact failure detail on a step and a reply, which model answered a reply (mate_turn.route), and the quick-answer
|
|
1150
1151
|
// switch (lead_quick_setting). V158_SCHEMA, V158_ADDED_COLUMNS (lead-latency-schema.ts): saved rows read NULL.
|
|
1151
|
-
|
|
1152
|
+
// v159 adds, and changes no saved row (docs/plans/one-step-chat.md): the lead turn a chat start's preview was prepared
|
|
1153
|
+
// in (fast_preview.turn) and a typed yes's record (chat_typed_start, a new table). V159_SCHEMA, V159_ADDED_COLUMNS
|
|
1154
|
+
// (one-step-chat-schema.ts): saved rows read NULL.
|
|
1155
|
+
export const SCHEMA_VERSION = 159;
|
|
1152
1156
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
1153
1157
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
1154
1158
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -1281,7 +1285,7 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
1281
1285
|
* unchanged), and adds the nullable column in V125_ADDED_COLUMNS, checked the same way. v126 only adds a new table
|
|
1282
1286
|
* (task_source) and the columns in V126_ADDED_COLUMNS (null or 0), checked the same way.
|
|
1283
1287
|
*/
|
|
1284
|
-
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158]);
|
|
1288
|
+
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159]);
|
|
1285
1289
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
1286
1290
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
1287
1291
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -1944,6 +1948,7 @@ export { V155_STEPS };
|
|
|
1944
1948
|
export { V156_STEPS };
|
|
1945
1949
|
export { V157_STEPS };
|
|
1946
1950
|
export { V158_STEPS };
|
|
1951
|
+
export { V159_STEPS };
|
|
1947
1952
|
/**
|
|
1948
1953
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
1949
1954
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -5293,6 +5298,13 @@ CREATE INDEX IF NOT EXISTS run_checkpoint_by_task ON run_checkpoint (task_ref, i
|
|
|
5293
5298
|
for (const [column, { definition }] of Object.entries(columns))
|
|
5294
5299
|
addColumn(db, table, column, definition);
|
|
5295
5300
|
step("lead-latency-columns");
|
|
5301
|
+
// v159: one-step start from chat (V159_SCHEMA, V159_ADDED_COLUMNS: saved rows read NULL). Idempotent, as v158's are.
|
|
5302
|
+
db.exec(V159_SCHEMA);
|
|
5303
|
+
step("one-step-chat-tables");
|
|
5304
|
+
for (const [table, columns] of Object.entries(V159_ADDED_COLUMNS))
|
|
5305
|
+
for (const [column, { definition }] of Object.entries(columns))
|
|
5306
|
+
addColumn(db, table, column, definition);
|
|
5307
|
+
step("one-step-chat-columns");
|
|
5296
5308
|
// Keep the epoch until reclamation succeeds, including a retry after compaction already committed. A new file has
|
|
5297
5309
|
// nothing to reclaim.
|
|
5298
5310
|
if (!fresh)
|
|
@@ -8244,6 +8256,9 @@ export class Store {
|
|
|
8244
8256
|
throw new ReleaseSealRefused();
|
|
8245
8257
|
}
|
|
8246
8258
|
if (changed.changes > 0) {
|
|
8259
|
+
// A real seal is work a builder can take now: wake the loops (live-wakeups.md). Inside the seal's own
|
|
8260
|
+
// transaction, so a seal that rolls back wakes nothing. The claim re-proves the approval regardless.
|
|
8261
|
+
this.bumpWake();
|
|
8247
8262
|
// The project's tools this yes saw (v80): a later addition needs a later yes.
|
|
8248
8263
|
this.sealProjectTools(taskId, String(working["digest"]), now);
|
|
8249
8264
|
// A fresh yes lifts the stale-approval hold and closes its page
|
|
@@ -11754,17 +11769,23 @@ export class Store {
|
|
|
11754
11769
|
* enough, and neither is a stale yes).
|
|
11755
11770
|
*/
|
|
11756
11771
|
markCapability(repo, kind, name, outcome, now, expiresAt = null) {
|
|
11757
|
-
|
|
11758
|
-
|
|
11772
|
+
return this.transact(() => {
|
|
11773
|
+
// The status before this answer, read in the same transaction: only a gap that fills wakes the loop. A
|
|
11774
|
+
// repeated passing probe refreshes the timestamp and says nothing new, so it must not wake (live-wakeups.md).
|
|
11775
|
+
const prior = this.db.prepare("SELECT status FROM capability WHERE repo = ? AND kind = ? AND name = ?").get(repo, kind, name);
|
|
11776
|
+
const { changes } = this.db
|
|
11777
|
+
.prepare(`UPDATE capability SET status = ?, last_verified_at = ?, verified_by = ?, last_result = ?, expires_at = COALESCE(?, expires_at)
|
|
11759
11778
|
WHERE repo = ? AND kind = ? AND name = ?`)
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11779
|
+
.run(outcome.status, outcome.status === "verified" ? now.toISOString() : null, outcome.by, outcome.detail ?? null, expiresAt, repo, kind, name);
|
|
11780
|
+
// A gap that just filled ends its notification episode: the next failure
|
|
11781
|
+
// is a new fact, and must be allowed to say so.
|
|
11782
|
+
if (Number(changes) > 0 && outcome.status === "verified") {
|
|
11783
|
+
this.clearGapEpisode(repo, kind, name);
|
|
11784
|
+
if (prior?.["status"] !== "verified")
|
|
11785
|
+
this.bumpWake();
|
|
11786
|
+
}
|
|
11787
|
+
return Number(changes) > 0;
|
|
11788
|
+
});
|
|
11768
11789
|
}
|
|
11769
11790
|
/** Set what a task needs, as `kind:name` keys, replacing what it needed before. */
|
|
11770
11791
|
setRequirements(taskRef, keys, mutation = {}) {
|
|
@@ -16452,6 +16473,8 @@ export class Store {
|
|
|
16452
16473
|
generation = generation + 1, last_error = NULL
|
|
16453
16474
|
WHERE id = ? AND state = 'waiting-human'`)
|
|
16454
16475
|
.run(id);
|
|
16476
|
+
// The person's yes makes the merge actionable now: wake the loops (live-wakeups.md). The firing CAS re-proves it.
|
|
16477
|
+
this.bumpWake();
|
|
16455
16478
|
return { ok: true };
|
|
16456
16479
|
});
|
|
16457
16480
|
}
|