toolroll 0.9.14 → 0.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/assignment-brief.d.ts +10 -0
- package/dist/assignment-brief.js +49 -26
- package/dist/assignment-ui.d.ts +1 -0
- package/dist/assignment-ui.js +6 -4
- package/dist/assignment.d.ts +34 -1
- package/dist/assignment.js +86 -7
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +38 -38
- package/dist/browser-crew.d.ts +2 -1
- package/dist/browser-crew.js +3 -1
- package/dist/browser-workspace.d.ts +93 -18
- package/dist/chat-actions.d.ts +1 -1
- package/dist/chat-actions.js +6 -4
- package/dist/chat-ask.d.ts +28 -0
- package/dist/chat-ask.js +77 -0
- package/dist/chat-channel.js +6 -1
- package/dist/chat-controls.d.ts +8 -0
- package/dist/chat-controls.js +3 -0
- package/dist/chat-delivery-state.d.ts +7 -2
- package/dist/chat-delivery-state.js +15 -2
- package/dist/chat-delivery.js +20 -3
- package/dist/chat-quiet.js +4 -2
- package/dist/chat-voice.d.ts +6 -9
- package/dist/chat-voice.js +11 -5
- package/dist/demo.js +82 -2
- package/dist/disclosure.d.ts +6 -0
- package/dist/disclosure.js +13 -0
- package/dist/discord-chat.js +10 -0
- package/dist/lead-commitments.d.ts +84 -0
- package/dist/lead-commitments.js +208 -0
- package/dist/lead-context.d.ts +32 -3
- package/dist/lead-context.js +133 -19
- package/dist/lead-follow.js +3 -0
- package/dist/lead-identity.d.ts +23 -0
- package/dist/lead-identity.js +33 -0
- package/dist/lead-status.js +2 -2
- package/dist/lead-voice.d.ts +12 -9
- package/dist/lead-voice.js +25 -16
- package/dist/ledger-view.js +3 -2
- package/dist/mate-cli.d.ts +1 -1
- package/dist/mate-cli.js +8 -2
- package/dist/mate-contract.d.ts +1 -1
- package/dist/mate-contract.js +11 -4
- package/dist/mate-progress.js +5 -0
- package/dist/mate-tools.d.ts +18 -1
- package/dist/mate-tools.js +250 -6
- package/dist/mate.d.ts +9 -0
- package/dist/mate.js +34 -38
- package/dist/needs-you.d.ts +48 -0
- package/dist/needs-you.js +93 -1
- package/dist/operate.js +2 -1
- package/dist/project-delete.js +3 -1
- package/dist/result-acts.d.ts +63 -5
- package/dist/result-acts.js +53 -3
- package/dist/result-review.d.ts +4 -3
- package/dist/result-review.js +7 -3
- package/dist/retention.js +6 -0
- package/dist/serve.d.ts +3 -0
- package/dist/serve.js +1220 -694
- package/dist/slack-chat.js +11 -1
- package/dist/store.d.ts +51 -0
- package/dist/store.js +115 -1
- package/dist/surface.js +2 -2
- package/dist/task-status.d.ts +26 -19
- package/dist/task-status.js +59 -21
- package/dist/team-runtime.js +2 -1
- package/dist/teams-chat.js +4 -1
- package/dist/telegram-mate.d.ts +4 -1
- package/dist/telegram-mate.js +17 -1
- package/dist/telegram-progress.js +3 -2
- package/dist/telegram.js +43 -3
- package/dist/test-temp.js +1 -1
- package/dist/transitions-recipes.d.ts +1 -1
- package/dist/transitions-recipes.js +5 -9
- package/dist/when-html.d.ts +24 -2
- package/dist/when-html.js +98 -11
- package/dist/work-index.d.ts +5 -1
- package/dist/work-index.js +22 -9
- package/dist/work-summary.d.ts +1 -1
- package/dist/workspace-motion.js +3 -3
- package/dist/workspace-ui.d.ts +44 -20
- package/dist/workspace-ui.js +63 -37
- package/package.json +1 -1
package/dist/chat-voice.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/** How chat sounds: a teammate talking. One or two short sentences, outcome
|
|
2
|
-
* first, plain words, one action at most. Every chat (Telegram, Slack,
|
|
3
|
-
* Discord, Teams) takes its titles and its finished-work lines from here, so a
|
|
4
|
-
* task id, a "— revision" suffix or a person's own act never reaches a chat.
|
|
5
|
-
* The console keeps every exact name and fact. */
|
|
6
1
|
import type { TelegramTransport } from "./telegram.js";
|
|
7
2
|
import type { Store } from "./store.js";
|
|
8
3
|
/** The bot's display name in every chat app. */
|
|
@@ -24,7 +19,7 @@ export type FinishedFact = {
|
|
|
24
19
|
checks: "passed" | "failed" | "not-run" | "off" | null;
|
|
25
20
|
report: boolean;
|
|
26
21
|
completedBy: string | null;
|
|
27
|
-
/** An open pull request carries this result: the ask is "Merge it?" rather than "
|
|
22
|
+
/** An open pull request carries this result: the ask is "Merge it?" rather than "Accept and finish it?". */
|
|
28
23
|
pullRequest?: boolean;
|
|
29
24
|
/** The lead did this (built it, or marked it complete), so the lead says "I". */
|
|
30
25
|
lead?: boolean;
|
|
@@ -34,16 +29,18 @@ export type FinishedFact = {
|
|
|
34
29
|
words: string;
|
|
35
30
|
phrase: string;
|
|
36
31
|
} | null;
|
|
37
|
-
/** The person's lead took it on (lead-voice.ts): "
|
|
32
|
+
/** The person's lead took it on (lead-voice.ts): "<name> is on it", never "It waits for you". */
|
|
38
33
|
leadOnIt?: boolean;
|
|
34
|
+
/** What the person calls their lead (Settings → Lead); "Lead" when unnamed. */
|
|
35
|
+
leadName?: string;
|
|
39
36
|
};
|
|
40
37
|
/** The two buttons under a result ready for a person: the real next step, then a look first. */
|
|
41
38
|
export declare const READY_ACTIONS: {
|
|
42
39
|
readonly merge: "Merge";
|
|
43
|
-
readonly complete: "
|
|
40
|
+
readonly complete: "Accept and finish";
|
|
44
41
|
readonly look: "Look first";
|
|
45
42
|
};
|
|
46
|
-
/** Whether this line asks for the result's next step, so it carries [Merge] or [
|
|
43
|
+
/** Whether this line asks for the result's next step, so it carries [Merge] or [Accept and finish] and [Look first]. */
|
|
47
44
|
export declare function asksToFinish(fact: FinishedFact): boolean;
|
|
48
45
|
/** One update, said plainly: what happened, then what happens next. The lead says "I" for what it did. */
|
|
49
46
|
export declare function finishedLine(fact: FinishedFact): string;
|
package/dist/chat-voice.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/** How chat sounds: a teammate talking. One or two short sentences, outcome
|
|
2
|
+
* first, plain words, one action at most. Every chat (Telegram, Slack,
|
|
3
|
+
* Discord, Teams) takes its titles and its finished-work lines from here, so a
|
|
4
|
+
* task id, a "— revision" suffix or a person's own act never reaches a chat.
|
|
5
|
+
* The console keeps every exact name and fact. */
|
|
6
|
+
import { leadOnIt } from "./task-status.js";
|
|
1
7
|
import { phoneText } from "./telegram-status.js";
|
|
2
8
|
/** The bot's display name in every chat app. */
|
|
3
9
|
export const BOT_NAME = "Toolroll";
|
|
@@ -42,8 +48,8 @@ export function inSentence(summary) {
|
|
|
42
48
|
return /^\p{Lu}\p{Ll}/u.test(summary) ? summary[0].toLowerCase() + summary.slice(1) : summary;
|
|
43
49
|
}
|
|
44
50
|
/** The two buttons under a result ready for a person: the real next step, then a look first. */
|
|
45
|
-
export const READY_ACTIONS = { merge: "Merge", complete: "
|
|
46
|
-
/** Whether this line asks for the result's next step, so it carries [Merge] or [
|
|
51
|
+
export const READY_ACTIONS = { merge: "Merge", complete: "Accept and finish", look: "Look first" };
|
|
52
|
+
/** Whether this line asks for the result's next step, so it carries [Merge] or [Accept and finish] and [Look first]. */
|
|
47
53
|
export function asksToFinish(fact) {
|
|
48
54
|
return fact.update == null && fact.headline === "Ready for review" && !fact.report;
|
|
49
55
|
}
|
|
@@ -55,19 +61,19 @@ export function finishedLine(fact) {
|
|
|
55
61
|
const ready = fact.lead === true ? `I finished ${lower}` : `${name} is ready`;
|
|
56
62
|
// What waited on the person and the lead has taken on (Needs you reads Waiting then).
|
|
57
63
|
if (fact.leadOnIt === true && (fact.headline === "Needs you" || fact.headline === "Waiting" || fact.headline === "Stopped"))
|
|
58
|
-
return `${name}:
|
|
64
|
+
return `${name}: ${leadOnIt(fact.leadName)}`;
|
|
59
65
|
switch (fact.headline) {
|
|
60
66
|
case "Ready for review":
|
|
61
67
|
if (fact.report)
|
|
62
68
|
return fact.lead === true ? `I wrote up ${lower}. The report is ready to read.` : `${name}: the report is ready to read.`;
|
|
63
69
|
if (fact.checks === "passed")
|
|
64
|
-
return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "
|
|
70
|
+
return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "Accept and finish it?"}`;
|
|
65
71
|
if (fact.checks === "off")
|
|
66
72
|
return `${ready}. Checks are off for this project, so look it over first.`;
|
|
67
73
|
return `${ready}, but no tests ran. Look it over first.`;
|
|
68
74
|
case "Failed":
|
|
69
75
|
if (fact.leadOnIt === true)
|
|
70
|
-
return fact.checks === "failed" ? `${name} is built, but its tests failed.
|
|
76
|
+
return fact.checks === "failed" ? `${name} is built, but its tests failed. ${leadOnIt(fact.leadName)}` : `${name} stopped before it finished. ${leadOnIt(fact.leadName)}`;
|
|
71
77
|
if (fact.checks === "failed")
|
|
72
78
|
return fact.lead === true
|
|
73
79
|
? `I built ${lower}, but its tests failed. It waits for you: retry or ask for changes.`
|
package/dist/demo.js
CHANGED
|
@@ -97,6 +97,57 @@ const DEMO_HANDOFF = {
|
|
|
97
97
|
followUps: [],
|
|
98
98
|
decisionsIncorporated: [],
|
|
99
99
|
};
|
|
100
|
+
/** The failed demo build: it took the flag out of settlement and config, but
|
|
101
|
+
* not out of the admin override, and its own report says so. */
|
|
102
|
+
const DEMO_FAILED_PATCH = `diff --git a/src/payout.ts b/src/payout.ts
|
|
103
|
+
index 7c1d2e3..5a1f0c7 100644
|
|
104
|
+
--- a/src/payout.ts
|
|
105
|
+
+++ b/src/payout.ts
|
|
106
|
+
@@ -18,9 +18,5 @@ export function settleAll(rows: PayoutRow[]): number {
|
|
107
|
+
- if (config.LEGACY_PAYOUT) {
|
|
108
|
+
- return rows.reduce((sum, row) => sum + legacySettle(row), 0);
|
|
109
|
+
- }
|
|
110
|
+
-
|
|
111
|
+
- return rows.reduce((sum, row) => sum + settle(row.cents, row.rate), 0);
|
|
112
|
+
+ return rows.reduce((sum, row) => sum + settle(row.cents, row.rate), 0);
|
|
113
|
+
}
|
|
114
|
+
diff --git a/src/config.ts b/src/config.ts
|
|
115
|
+
index 2b3c4d5..8e9f0a1 100644
|
|
116
|
+
--- a/src/config.ts
|
|
117
|
+
+++ b/src/config.ts
|
|
118
|
+
@@ -4,8 +4,6 @@ export const config = {
|
|
119
|
+
PAYOUT_BATCH_SIZE: 500,
|
|
120
|
+
- // Retired once every merchant is on the new settlement path.
|
|
121
|
+
- LEGACY_PAYOUT: process.env.LEGACY_PAYOUT === "1",
|
|
122
|
+
PAYOUT_CURRENCY: "USD",
|
|
123
|
+
};
|
|
124
|
+
`;
|
|
125
|
+
const DEMO_FAILED_HANDOFF = {
|
|
126
|
+
schema: 1,
|
|
127
|
+
outcome: "built",
|
|
128
|
+
committed: true,
|
|
129
|
+
conclusion: "Removed LEGACY_PAYOUT from settlement and config. The admin override still reads it, so one reference remains.",
|
|
130
|
+
changes: ["Dropped the legacy settlement branch in src/payout.ts.", "Removed the LEGACY_PAYOUT flag from src/config.ts."],
|
|
131
|
+
verification: ["Searched the codebase for LEGACY_PAYOUT after the change."],
|
|
132
|
+
followUps: [],
|
|
133
|
+
decisionsIncorporated: [],
|
|
134
|
+
};
|
|
135
|
+
const DEMO_FAILED_PROOF = {
|
|
136
|
+
version: 1,
|
|
137
|
+
criteria: [
|
|
138
|
+
{
|
|
139
|
+
id: "c1",
|
|
140
|
+
statement: "No reference to LEGACY_PAYOUT remains in the codebase.",
|
|
141
|
+
verdict: "met",
|
|
142
|
+
how: "Removed the flag and the settlement branch behind it.",
|
|
143
|
+
evidence: [{ kind: "changed-path", ref: "src/payout.ts" }, { kind: "changed-path", ref: "src/config.ts" }],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
checks: [],
|
|
147
|
+
changed: ["src/payout.ts", "src/config.ts"],
|
|
148
|
+
caveats: ["c1: src/admin/overrides.ts still reads LEGACY_PAYOUT for the per-merchant override toggle, so one reference remains."],
|
|
149
|
+
screenshots: [],
|
|
150
|
+
};
|
|
100
151
|
/** The evidence bundle's own manifest (Acceptance Contract v2 demo): the
|
|
101
152
|
* seeded sandbox answers its OWN signed rubric by exact id, with typed
|
|
102
153
|
* evidence references — the same shape a real builder writes — so a
|
|
@@ -472,14 +523,15 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
472
523
|
store.finishRun(buildingPriorRun, { outcome: "refused", reason: "plan-revised", now: hoursAgo(1.6) });
|
|
473
524
|
// The board's "building" lane keys off a live claim — take one through
|
|
474
525
|
// the real claim machinery so the card wears worker and lease honestly.
|
|
475
|
-
acquire(store, buildingRef, "night-shift-1", {
|
|
526
|
+
const liveClaim = acquire(store, buildingRef, "night-shift-1", {
|
|
476
527
|
now: hoursAgo(0.4),
|
|
477
528
|
token: nightShift.token,
|
|
478
529
|
ttlMs: 4 * 3_600_000,
|
|
479
530
|
});
|
|
480
531
|
const liveRun = store.startRun({
|
|
481
532
|
taskRef: buildingRef,
|
|
482
|
-
|
|
533
|
+
// The claim's own lease, so the build reads as live: the run the task's current claim owns.
|
|
534
|
+
leaseId: liveClaim.ok ? liveClaim.claim.leaseId : "demo-lease-live",
|
|
483
535
|
runner: "night-shift-1",
|
|
484
536
|
branch: `toolroll/${building}`,
|
|
485
537
|
worktree: join(repos.api, ".demo-worktree-2"),
|
|
@@ -603,6 +655,34 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
603
655
|
now: hoursAgo(6),
|
|
604
656
|
...presentedRoute(store, store.refFor("built-in", failed).id, "builder"),
|
|
605
657
|
});
|
|
658
|
+
// What the failed build left: its saved changes, its report and the
|
|
659
|
+
// machine's reading of them. It removed the flag from settlement but left
|
|
660
|
+
// the admin override reading it, and said so in its own note — so the
|
|
661
|
+
// signed requirement is missed, and the task card can name it.
|
|
662
|
+
store.stampRun(failedRun, { baseRevision: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", scopeDigest: failedProposed.digest });
|
|
663
|
+
store.recordOutcomeFacts(failedRun, { headRevision: "5a1f0c7e2b9d4e6f8a0b1c2d3e4f5a6b7c8d9e0f" });
|
|
664
|
+
storeEvidence(store, evidenceRoot, failedRun, "terminal-diff", "terminal-diff.patch", Buffer.from(DEMO_FAILED_PATCH, "utf8"), "git diff --no-ext-diff --no-textconv --no-color 4b825dc6..HEAD (exit 0) [demo: synthetic]", hoursAgo(5.6));
|
|
665
|
+
const failedStat = {
|
|
666
|
+
schema: 1, base: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", head: "5a1f0c7e2b9d4e6f8a0b1c2d3e4f5a6b7c8d9e0f",
|
|
667
|
+
fileCount: 2, additions: 1, deletions: 7, binaryCount: 0,
|
|
668
|
+
files: [{ path: "src/payout.ts", additions: 1, deletions: 5 }, { path: "src/config.ts", additions: 0, deletions: 2 }],
|
|
669
|
+
filesTruncated: false,
|
|
670
|
+
};
|
|
671
|
+
storeEvidence(store, evidenceRoot, failedRun, "diff-stat", "diff-stat.json", budgetedStatJson(failedStat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(5.6));
|
|
672
|
+
storeEvidence(store, evidenceRoot, failedRun, "handoff", "handoff.json", Buffer.from(JSON.stringify(DEMO_FAILED_HANDOFF, null, 2), "utf8"), "composed at completion [demo: synthetic]", hoursAgo(5.6));
|
|
673
|
+
storeEvidence(store, evidenceRoot, failedRun, "proof", "proof.json", Buffer.from(JSON.stringify(DEMO_FAILED_PROOF, null, 2), "utf8"), "agent-authored proof (validated, re-serialized) [demo: synthetic]", hoursAgo(5.6));
|
|
674
|
+
const failedAdjudicated = adjudicate({
|
|
675
|
+
proofArtifactPresent: true,
|
|
676
|
+
proofParse: parseProof(JSON.stringify(DEMO_FAILED_PROOF)),
|
|
677
|
+
handoffPresent: true,
|
|
678
|
+
terminalDiffPresent: true,
|
|
679
|
+
terminalDiffCaptureStatus: "ok",
|
|
680
|
+
diffStat: { captured: true, truncated: false, paths: new Set(failedStat.files.map(one => one.path)) },
|
|
681
|
+
verifyCommand: { configured: false },
|
|
682
|
+
screenshots: [],
|
|
683
|
+
approvedCriteria: failedProposed.acceptance,
|
|
684
|
+
});
|
|
685
|
+
store.saveProofVerdict(failedRun, failedAdjudicated.verdict, failedAdjudicated.reasons, hoursAgo(5.5), failedAdjudicated.matrix);
|
|
606
686
|
store.finishRun(failedRun, {
|
|
607
687
|
outcome: "failed",
|
|
608
688
|
reason: "acceptance",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** One disclosure glyph everywhere: the chevron. It points right while a fold is shut and turns down when it opens,
|
|
2
|
+
* on the server pages, the enhanced folds (workspace-motion.ts) and the React views (lucide's ChevronRight). Every
|
|
3
|
+
* other marker (the native triangle, ▸/▾, +/−, ⌄ and the old arrows) is turned off here, last, so no page can bring
|
|
4
|
+
* a second glyph back. A summary that carries its own icon keeps it. Drawn with two borders: the page's content
|
|
5
|
+
* policy allows no image, not even a data: one. */
|
|
6
|
+
export declare const DISCLOSURE_CSS: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One disclosure glyph everywhere: the chevron. It points right while a fold is shut and turns down when it opens,
|
|
2
|
+
* on the server pages, the enhanced folds (workspace-motion.ts) and the React views (lucide's ChevronRight). Every
|
|
3
|
+
* other marker (the native triangle, ▸/▾, +/−, ⌄ and the old arrows) is turned off here, last, so no page can bring
|
|
4
|
+
* a second glyph back. A summary that carries its own icon keeps it. Drawn with two borders: the page's content
|
|
5
|
+
* policy allows no image, not even a data: one. */
|
|
6
|
+
export const DISCLOSURE_CSS = `details>summary{list-style:none}details>summary::-webkit-details-marker{display:none}` +
|
|
7
|
+
`details>summary::after{content:none!important}` +
|
|
8
|
+
`details>summary:not(:has(>svg))::before{content:""!important;display:inline-block;flex:none;box-sizing:border-box;width:7px!important;height:7px!important;` +
|
|
9
|
+
`margin:0 .55rem 0 .2rem!important;padding:0!important;position:static!important;vertical-align:.1em;font-size:0;background:none!important;` +
|
|
10
|
+
`border:0 solid currentColor!important;border-right-width:1.5px!important;border-bottom-width:1.5px!important;opacity:.65;` +
|
|
11
|
+
`transform:rotate(-45deg)!important;transition:transform .15s ease}` +
|
|
12
|
+
`details[open]>summary:not(:has(>svg))::before{transform:rotate(45deg)!important}` +
|
|
13
|
+
`@media (prefers-reduced-motion:reduce){details>summary::before{transition:none!important}}`;
|
package/dist/discord-chat.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Discord messages and buttons transport the shared assistant's saved actions. */
|
|
2
2
|
import { chatQuestionButtons } from "./teammate-question.js";
|
|
3
|
+
import { chatAskButtons } from "./chat-ask.js";
|
|
3
4
|
import { chatFlowButtons } from "./chat-flow.js";
|
|
4
5
|
import { channelInbox } from "./chat-inbox.js";
|
|
5
6
|
import { roomCommand } from "./chat-rooms.js";
|
|
@@ -272,6 +273,15 @@ export async function deliverDiscordPart(options) {
|
|
|
272
273
|
custom_id: `so_${one.token}`,
|
|
273
274
|
}))
|
|
274
275
|
: []),
|
|
276
|
+
// The lead's question to its owner: its options, then "Something else".
|
|
277
|
+
...(content.ask
|
|
278
|
+
? chatAskButtons(state, row.id, now).map((one) => ({
|
|
279
|
+
type: 2,
|
|
280
|
+
style: one.words ? 2 : 1,
|
|
281
|
+
label: one.label.slice(0, 80),
|
|
282
|
+
custom_id: `so_${one.token}`,
|
|
283
|
+
}))
|
|
284
|
+
: []),
|
|
275
285
|
...link(options.origin(), content.link),
|
|
276
286
|
...(content.also ?? []).flatMap(one => link(options.origin(), one)),
|
|
277
287
|
];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** What the lead promised to follow up on. A commitment records the promise in the lead's words, the condition
|
|
2
|
+
* that completes it, when to look next, where to report and when it lapses. The follow pass reads open ones
|
|
3
|
+
* without a model and says one short line when the condition is met, on the channel the promise was made on;
|
|
4
|
+
* nothing otherwise.
|
|
5
|
+
* The table is additive (no schema version bump), so a build that predates it still opens the store. */
|
|
6
|
+
import type { Notification, Store } from './store.js';
|
|
7
|
+
import { type AssignmentSnapshot } from './assignment.js';
|
|
8
|
+
/** The table: `LEAD_COMMITMENT_SCHEMA` in store.ts (additive, no version bump). */
|
|
9
|
+
export declare const COMMITMENT_DAYS = 7;
|
|
10
|
+
export type TaskState = AssignmentSnapshot['state'];
|
|
11
|
+
export declare const TASK_STATES: readonly TaskState[];
|
|
12
|
+
export declare const RUN_OUTCOMES: readonly ['finished', 'built', 'failed'];
|
|
13
|
+
export declare const CHECK_RESULTS: readonly ['passed', 'failed', 'either'];
|
|
14
|
+
/** Where a promise was made, and so where it is reported: `chat` is the console or terminal conversation; a phone
|
|
15
|
+
* or team chat also gets the line as the lead's own message, on that chat only. */
|
|
16
|
+
export declare const COMMITMENT_CHANNELS: readonly ['chat', 'telegram', 'slack', 'discord', 'teams'];
|
|
17
|
+
export type CommitmentChannel = typeof COMMITMENT_CHANNELS[number];
|
|
18
|
+
/** `afterRun`: only a result newer than the one there was when the promise was made counts. */
|
|
19
|
+
export type CommitmentCondition = {
|
|
20
|
+
kind: 'task';
|
|
21
|
+
task: string;
|
|
22
|
+
states: TaskState[];
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'run';
|
|
25
|
+
run: number;
|
|
26
|
+
outcome: typeof RUN_OUTCOMES[number];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'check';
|
|
29
|
+
task: string;
|
|
30
|
+
result: typeof CHECK_RESULTS[number];
|
|
31
|
+
afterRun: number;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'check';
|
|
34
|
+
run: number;
|
|
35
|
+
result: typeof CHECK_RESULTS[number];
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'time';
|
|
38
|
+
at: string;
|
|
39
|
+
};
|
|
40
|
+
export type Commitment = {
|
|
41
|
+
id: number;
|
|
42
|
+
owner: string;
|
|
43
|
+
repo: string | null;
|
|
44
|
+
thread: number;
|
|
45
|
+
turn: number | null;
|
|
46
|
+
channel: string;
|
|
47
|
+
what: string;
|
|
48
|
+
condition: CommitmentCondition;
|
|
49
|
+
checkAt: string;
|
|
50
|
+
expiresAt: string;
|
|
51
|
+
state: 'open' | 'done' | 'cancelled' | 'expired';
|
|
52
|
+
createdAt: string;
|
|
53
|
+
checkedAt: string | null;
|
|
54
|
+
closedAt: string | null;
|
|
55
|
+
closedBy: string | null;
|
|
56
|
+
outcome: string | null;
|
|
57
|
+
};
|
|
58
|
+
/** The condition in a few words, for Settings and for the lead's own catch-up. */
|
|
59
|
+
export declare function conditionWords(store: Store, condition: CommitmentCondition): string;
|
|
60
|
+
export type CommitInput = {
|
|
61
|
+
owner: string;
|
|
62
|
+
repo: string | null;
|
|
63
|
+
thread: number;
|
|
64
|
+
turn: number | null;
|
|
65
|
+
channel?: string;
|
|
66
|
+
what: string;
|
|
67
|
+
condition: CommitmentCondition;
|
|
68
|
+
checkAt?: string | null;
|
|
69
|
+
};
|
|
70
|
+
/** Record a promise. The caller has already proved the task, attempt or project is within the owner's access. */
|
|
71
|
+
export declare function recordCommitment(store: Store, input: CommitInput, now: Date): Commitment;
|
|
72
|
+
export declare function getCommitment(store: Store, id: number): Commitment | null;
|
|
73
|
+
/** The owner's open promises that they heard, oldest first. */
|
|
74
|
+
export declare function openCommitments(store: Store, owner: string, limit?: number): Commitment[];
|
|
75
|
+
/** The phone or team chat a lead message is for, when it is a met promise (`lead-promise:<channel>:<id>`); null for
|
|
76
|
+
* every other notification, which goes wherever it always went. */
|
|
77
|
+
export declare function promiseChannelOf(row: Pick<Notification, 'dedupeKey'>): string | null;
|
|
78
|
+
/** Stop following up. Only the owner (from Settings or through their lead) cancels; a closed one stays as it was. */
|
|
79
|
+
export declare function cancelCommitment(store: Store, owner: string, id: number, by: string, reason: string, now: Date): boolean;
|
|
80
|
+
/** The newest result run for a task, for a check condition's baseline. */
|
|
81
|
+
export declare function latestResultRun(store: Store, task: string, repos: readonly string[], now: Date): number;
|
|
82
|
+
/** One deterministic pass over due promises (no model): expire, drop what can no longer be delivered or seen,
|
|
83
|
+
* and report a met condition once — the state change and the message share one transaction. Returns how many reported. */
|
|
84
|
+
export declare function checkLeadCommitments(store: Store, now: Date, root?: string): number;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { assignmentOf } from './assignment.js';
|
|
2
|
+
import { LEAD_SAY_KIND } from './lead-voice.js';
|
|
3
|
+
import { leadNameOf } from './lead-identity.js';
|
|
4
|
+
/** The table: `LEAD_COMMITMENT_SCHEMA` in store.ts (additive, no version bump). */
|
|
5
|
+
export const COMMITMENT_DAYS = 7;
|
|
6
|
+
const DAY_MS = 86_400_000;
|
|
7
|
+
/** How long an unmet condition waits before it is looked at again. */
|
|
8
|
+
const RECHECK_MS = 30_000;
|
|
9
|
+
const PASS_LIMIT = 50;
|
|
10
|
+
export const TASK_STATES = ['working', 'checking', 'needs-decision', 'ready-to-check', 'complete', 'cancelled'];
|
|
11
|
+
export const RUN_OUTCOMES = ['finished', 'built', 'failed'];
|
|
12
|
+
export const CHECK_RESULTS = ['passed', 'failed', 'either'];
|
|
13
|
+
/** Where a promise was made, and so where it is reported: `chat` is the console or terminal conversation; a phone
|
|
14
|
+
* or team chat also gets the line as the lead's own message, on that chat only. */
|
|
15
|
+
export const COMMITMENT_CHANNELS = ['chat', 'telegram', 'slack', 'discord', 'teams'];
|
|
16
|
+
const PROMISE_KEY = 'lead-promise:';
|
|
17
|
+
const STATE_WORDS = { working: 'being worked on', checking: 'being checked', 'needs-decision': 'waiting for you',
|
|
18
|
+
'ready-to-check': 'ready for you to check', complete: 'complete', cancelled: 'cancelled' };
|
|
19
|
+
const STAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
20
|
+
function readCondition(text) {
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(text);
|
|
23
|
+
if (value?.kind === 'task' && typeof value.task === 'string' && Array.isArray(value.states) && value.states.every((one) => TASK_STATES.includes(one)))
|
|
24
|
+
return value;
|
|
25
|
+
if (value?.kind === 'run' && Number.isSafeInteger(value.run) && RUN_OUTCOMES.includes(value.outcome))
|
|
26
|
+
return value;
|
|
27
|
+
if (value?.kind === 'check' && CHECK_RESULTS.includes(value.result) && (typeof value.task === 'string' && Number.isSafeInteger(value.afterRun) || Number.isSafeInteger(value.run)))
|
|
28
|
+
return value;
|
|
29
|
+
if (value?.kind === 'time' && typeof value.at === 'string' && STAMP.test(value.at))
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
catch { /* unreadable: the row stays visible and is never acted on */ }
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function readCommitment(row) {
|
|
36
|
+
const condition = readCondition(String(row['condition_json']));
|
|
37
|
+
if (condition === null)
|
|
38
|
+
return null;
|
|
39
|
+
const text = (key) => row[key] == null ? null : String(row[key]);
|
|
40
|
+
return { id: Number(row['id']), owner: String(row['owner']), repo: text('repo'), thread: Number(row['thread']), turn: row['turn'] == null ? null : Number(row['turn']),
|
|
41
|
+
channel: String(row['channel']), what: String(row['what']), condition, checkAt: String(row['check_at']), expiresAt: String(row['expires_at']),
|
|
42
|
+
state: String(row['state']), createdAt: String(row['created_at']), checkedAt: text('checked_at'),
|
|
43
|
+
closedAt: text('closed_at'), closedBy: text('closed_by'), outcome: text('outcome') };
|
|
44
|
+
}
|
|
45
|
+
/** The condition in a few words, for Settings and for the lead's own catch-up. */
|
|
46
|
+
export function conditionWords(store, condition) {
|
|
47
|
+
const title = (task) => `“${store.getTask(task)?.title ?? task}”`;
|
|
48
|
+
if (condition.kind === 'task')
|
|
49
|
+
return `when ${title(condition.task)} is ${condition.states.map(one => STATE_WORDS[one]).join(' or ')}`;
|
|
50
|
+
if (condition.kind === 'run')
|
|
51
|
+
return `when the attempt on ${attemptOf(store, condition.run)} ${condition.outcome === 'finished' ? 'finishes' : condition.outcome === 'built' ? 'finishes with a result' : 'fails'}`;
|
|
52
|
+
if (condition.kind === 'check') {
|
|
53
|
+
const on = `the checks on ${'task' in condition ? title(condition.task) : attemptOf(store, condition.run)}`;
|
|
54
|
+
return `when ${on} ${condition.result === 'either' ? 'finish' : condition.result === 'passed' ? 'pass' : 'fail'}`;
|
|
55
|
+
}
|
|
56
|
+
return `at ${condition.at.slice(0, 16).replace('T', ' ')} UTC`;
|
|
57
|
+
}
|
|
58
|
+
/** The task an attempt belongs to, by title. */
|
|
59
|
+
function attemptOf(store, run) {
|
|
60
|
+
const task = store.refById(store.getRun(run)?.taskRef ?? -1)?.externalId;
|
|
61
|
+
return task === undefined ? 'that attempt' : `“${store.getTask(task)?.title ?? task}”`;
|
|
62
|
+
}
|
|
63
|
+
/** Record a promise. The caller has already proved the task, attempt or project is within the owner's access. */
|
|
64
|
+
export function recordCommitment(store, input, now) {
|
|
65
|
+
const what = input.what.trim().replace(/\s+/g, ' ');
|
|
66
|
+
if (what.length < 3 || what.length > 240)
|
|
67
|
+
throw Error('Say what you promised in one short sentence.');
|
|
68
|
+
const channel = input.channel ?? 'chat';
|
|
69
|
+
if (!COMMITMENT_CHANNELS.includes(channel))
|
|
70
|
+
throw Error('Report on a chat this conversation uses.');
|
|
71
|
+
if (readCondition(JSON.stringify(input.condition)) === null)
|
|
72
|
+
throw Error('Choose a task, attempt, check or time to wait for.');
|
|
73
|
+
const expiresAt = new Date(now.getTime() + COMMITMENT_DAYS * DAY_MS).toISOString();
|
|
74
|
+
let checkAt = input.checkAt ?? now.toISOString();
|
|
75
|
+
if (input.condition.kind === 'time') {
|
|
76
|
+
if (input.condition.at > expiresAt)
|
|
77
|
+
throw Error(`A promise lasts ${COMMITMENT_DAYS} days; choose a time before then.`);
|
|
78
|
+
checkAt = input.condition.at > checkAt ? input.condition.at : checkAt;
|
|
79
|
+
}
|
|
80
|
+
if (!STAMP.test(checkAt) || checkAt > expiresAt)
|
|
81
|
+
throw Error(`Check within the next ${COMMITMENT_DAYS} days.`);
|
|
82
|
+
const id = Number(store.handle.prepare(`INSERT INTO lead_commitment (owner, repo, thread, turn, channel, what, condition_json, check_at, expires_at, created_at)
|
|
83
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.owner, input.repo, input.thread, input.turn, channel, what, JSON.stringify(input.condition), checkAt, expiresAt, now.toISOString()).lastInsertRowid);
|
|
84
|
+
return getCommitment(store, id);
|
|
85
|
+
}
|
|
86
|
+
export function getCommitment(store, id) {
|
|
87
|
+
const row = store.handle.prepare('SELECT * FROM lead_commitment WHERE id = ?').get(id);
|
|
88
|
+
return row === undefined ? null : readCommitment(row);
|
|
89
|
+
}
|
|
90
|
+
/** A promise only counts once the reply that made it was answered: one from a reply still being written, or one
|
|
91
|
+
* that never finished, was never heard. */
|
|
92
|
+
const HEARD = "(c.turn IS NULL OR EXISTS (SELECT 1 FROM mate_turn t WHERE t.id = c.turn AND t.state = 'answered'))";
|
|
93
|
+
/** The owner's open promises that they heard, oldest first. */
|
|
94
|
+
export function openCommitments(store, owner, limit = 20) {
|
|
95
|
+
return store.handle.prepare(`SELECT c.* FROM lead_commitment c WHERE c.owner = ? AND c.state = 'open' AND ${HEARD} ORDER BY c.id LIMIT ?`).all(owner, limit)
|
|
96
|
+
.map(readCommitment).filter((one) => one !== null);
|
|
97
|
+
}
|
|
98
|
+
/** The phone or team chat a lead message is for, when it is a met promise (`lead-promise:<channel>:<id>`); null for
|
|
99
|
+
* every other notification, which goes wherever it always went. */
|
|
100
|
+
export function promiseChannelOf(row) {
|
|
101
|
+
return row.dedupeKey.startsWith(PROMISE_KEY) ? row.dedupeKey.slice(PROMISE_KEY.length).split(':')[0] ?? null : null;
|
|
102
|
+
}
|
|
103
|
+
/** Stop following up. Only the owner (from Settings or through their lead) cancels; a closed one stays as it was. */
|
|
104
|
+
export function cancelCommitment(store, owner, id, by, reason, now) {
|
|
105
|
+
const changed = store.handle.prepare("UPDATE lead_commitment SET state = 'cancelled', closed_at = ?, closed_by = ?, outcome = ? WHERE id = ? AND owner = ? AND state = 'open'")
|
|
106
|
+
.run(now.toISOString(), by, reason.trim().slice(0, 240) || 'Cancelled.', id, owner);
|
|
107
|
+
return Number(changed.changes) === 1;
|
|
108
|
+
}
|
|
109
|
+
/** The newest result run for a task, for a check condition's baseline. */
|
|
110
|
+
export function latestResultRun(store, task, repos, now) {
|
|
111
|
+
return assignmentOf(store, task, now, { principal: 'operator', repos })?.receipt?.runId ?? 0;
|
|
112
|
+
}
|
|
113
|
+
/** What was observed when the condition is met, in one sentence; null while it is not. */
|
|
114
|
+
function observe(store, one, now, root) {
|
|
115
|
+
const c = one.condition, repos = one.repo === null ? [] : [one.repo];
|
|
116
|
+
const title = (task) => `“${store.getTask(task)?.title ?? task}”`;
|
|
117
|
+
if (c.kind === 'time')
|
|
118
|
+
return now.toISOString() >= c.at ? 'It is time.' : null;
|
|
119
|
+
if (c.kind === 'task') {
|
|
120
|
+
const a = assignmentOf(store, c.task, now, { principal: 'operator', repos }, root);
|
|
121
|
+
return a !== null && c.states.includes(a.state) ? `${title(a.rootId)} is ${STATE_WORDS[a.state]}.` : null;
|
|
122
|
+
}
|
|
123
|
+
if (c.kind === 'run') {
|
|
124
|
+
const run = store.getRun(c.run);
|
|
125
|
+
if (run === null || run.outcome === null)
|
|
126
|
+
return null;
|
|
127
|
+
const ended = run.outcome === 'built' ? 'finished with a result' : run.outcome === 'failed' ? 'failed' : `ended (${run.outcome})`;
|
|
128
|
+
if (c.outcome === 'built' && run.outcome !== 'built' || c.outcome === 'failed' && run.outcome !== 'failed')
|
|
129
|
+
return null;
|
|
130
|
+
return `The attempt on ${attemptOf(store, run.id)} ${ended}.`;
|
|
131
|
+
}
|
|
132
|
+
const matches = (status) => (status === 'passed' || status === 'failed') && (c.result === 'either' || c.result === status);
|
|
133
|
+
if ('task' in c) {
|
|
134
|
+
const a = assignmentOf(store, c.task, now, { principal: 'operator', repos }, root);
|
|
135
|
+
const receipt = a?.receipt;
|
|
136
|
+
if (!a || !receipt || receipt.runId <= c.afterRun || !matches(receipt.checks.status))
|
|
137
|
+
return null;
|
|
138
|
+
return `The checks on ${title(a.rootId)} ${receipt.checks.status}.`;
|
|
139
|
+
}
|
|
140
|
+
const check = store.runCheckFor(c.run);
|
|
141
|
+
if (check === null || !matches(check.status))
|
|
142
|
+
return null;
|
|
143
|
+
return `The checks on ${attemptOf(store, c.run)} ${check.status}.`;
|
|
144
|
+
}
|
|
145
|
+
/** The promise as the end of "I said I would …": "I'll tell you when…" and "Tell you when…" both read "tell you when…". */
|
|
146
|
+
function promised(what) {
|
|
147
|
+
const bare = what.replace(/^I(?:'ll| will)\s+/i, '').replace(/[.!]+$/, '');
|
|
148
|
+
return /^[A-Z][a-z]/.test(bare) ? bare.charAt(0).toLowerCase() + bare.slice(1) : bare;
|
|
149
|
+
}
|
|
150
|
+
/** One deterministic pass over due promises (no model): expire, drop what can no longer be delivered or seen,
|
|
151
|
+
* and report a met condition once — the state change and the message share one transaction. Returns how many reported. */
|
|
152
|
+
export function checkLeadCommitments(store, now, root) {
|
|
153
|
+
const at = now.toISOString();
|
|
154
|
+
let reported = 0;
|
|
155
|
+
store.handle.prepare("UPDATE lead_commitment SET state = 'expired', closed_at = ?, outcome = 'Expired after 7 days without the condition being met.' WHERE state = 'open' AND expires_at <= ?").run(at, at);
|
|
156
|
+
// A promise made in a reply that was not delivered (it failed, or its turn is gone) was never heard: drop it now,
|
|
157
|
+
// whenever it was next due, so nothing waits on it.
|
|
158
|
+
store.handle.prepare(`UPDATE lead_commitment SET state = 'cancelled', closed_at = ?, closed_by = 'lead', outcome = 'The reply that made this promise was not delivered.'
|
|
159
|
+
WHERE state = 'open' AND turn IS NOT NULL AND NOT EXISTS (SELECT 1 FROM mate_turn t WHERE t.id = lead_commitment.turn AND t.state IN ('queued', 'running', 'answered'))`).run(at);
|
|
160
|
+
const due = store.handle.prepare("SELECT * FROM lead_commitment WHERE state = 'open' AND check_at <= ? ORDER BY check_at, id LIMIT ?").all(at, PASS_LIMIT);
|
|
161
|
+
for (const row of due) {
|
|
162
|
+
const one = readCommitment(row);
|
|
163
|
+
const id = Number(row['id']);
|
|
164
|
+
const close = (state, outcome) => Number(store.handle.prepare("UPDATE lead_commitment SET state = ?, closed_at = ?, closed_by = 'lead', outcome = ?, checked_at = ? WHERE id = ? AND state = 'open'").run(state, at, outcome, at, id).changes) === 1;
|
|
165
|
+
if (one === null) {
|
|
166
|
+
close('cancelled', 'Its condition could not be read.');
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// A reply still being written may yet be delivered; one that was not is dropped above.
|
|
170
|
+
const turn = one.turn === null ? null : store.getMateTurn(one.turn);
|
|
171
|
+
if (turn !== null && turn.state !== 'answered')
|
|
172
|
+
continue;
|
|
173
|
+
const account = store.accountOf(one.owner);
|
|
174
|
+
if (account === null || account.revokedAt !== null || one.repo !== null && !store.accountCanAccess(one.owner, one.repo)) {
|
|
175
|
+
close('cancelled', 'Access to this project ended.');
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const thread = store.getMateThread(one.thread);
|
|
179
|
+
if (thread === null || thread.approver !== one.owner || thread.closedAt !== null) {
|
|
180
|
+
close('cancelled', 'The conversation it was promised in was closed.');
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
let seen;
|
|
184
|
+
try {
|
|
185
|
+
seen = observe(store, one, now, root);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
seen = null;
|
|
189
|
+
}
|
|
190
|
+
if (seen === null) {
|
|
191
|
+
store.handle.prepare("UPDATE lead_commitment SET checked_at = ?, check_at = ? WHERE id = ? AND state = 'open'").run(at, new Date(now.getTime() + RECHECK_MS).toISOString(), id);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const line = `${seen} (I said I would ${promised(one.what)}.)`;
|
|
195
|
+
store.transact(() => {
|
|
196
|
+
if (!close('done', seen))
|
|
197
|
+
return;
|
|
198
|
+
// The shared conversation always keeps the line; a promise made on a phone or team chat is also said there, as
|
|
199
|
+
// the lead's own message to its owner, on that chat alone (the usual delivery receipts apply).
|
|
200
|
+
store.appendMateMessage({ thread: one.thread, turn: null, role: 'assistant', text: line }, now);
|
|
201
|
+
if (one.channel !== 'chat')
|
|
202
|
+
store.enqueueNotification({ dedupeKey: `${PROMISE_KEY}${one.channel}:${one.id}`, kind: LEAD_SAY_KIND, subject: leadNameOf(store, one.owner),
|
|
203
|
+
body: line.slice(0, 300), recipient: one.owner, source: { installation: true } }, now);
|
|
204
|
+
reported++;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return reported;
|
|
208
|
+
}
|
package/dist/lead-context.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
/** Bounded DB memory shared by browser and terminal lead turns. No provider,
|
|
2
|
-
* repository scan, mutation, or hidden approval happens while catching up.
|
|
2
|
+
* repository scan, mutation, or hidden approval happens while catching up.
|
|
3
|
+
*
|
|
4
|
+
* The bundle is ordered by importance and built fresh each turn: who the lead
|
|
5
|
+
* is (name, persona), who it is talking to (first name, time zone, today), the
|
|
6
|
+
* channel, what needs them now, projects by name with their active decisions,
|
|
7
|
+
* then the rest. Over 8 KB, the least important goes first. */
|
|
3
8
|
import type { Store } from './store.js';
|
|
4
9
|
import { type AssignmentCatchUp } from './assignment-brief.js';
|
|
5
|
-
export declare
|
|
10
|
+
export declare const LEAD_CONTEXT_MAX_BYTES = 8000;
|
|
11
|
+
/** Where this turn's conversation happens. */
|
|
12
|
+
export type LeadChannel = 'console' | 'terminal' | 'telegram' | 'slack' | 'discord' | 'teams';
|
|
13
|
+
export type LeadContextOptions = {
|
|
14
|
+
evidenceRoot?: string;
|
|
15
|
+
/** The person this turn talks to (their account name). */
|
|
16
|
+
owner?: string;
|
|
17
|
+
channel?: LeadChannel;
|
|
18
|
+
/** A shared team conversation's own lead, in place of the person's own name for it. */
|
|
19
|
+
leadName?: string;
|
|
20
|
+
/** This conversation: its open promises and the corrections confirmed since the lead's last reply. */
|
|
21
|
+
thread?: number;
|
|
22
|
+
/** Their time zone; this computer's when absent. */
|
|
23
|
+
timeZone?: string;
|
|
24
|
+
/** Scrubs every string in the bundle (paths, digests, account names); the person's first name and the project
|
|
25
|
+
* labels are put back afterwards, on purpose. */
|
|
26
|
+
redact?: (text: string) => string;
|
|
27
|
+
/** A project's display name, already safe to show; the bundle never carries paths. */
|
|
28
|
+
projectName?: (path: string, index: number) => string;
|
|
29
|
+
};
|
|
30
|
+
/** "alex.pelletier@example.com" → "Alex". */
|
|
31
|
+
export declare function firstNameOf(account: string): string;
|
|
32
|
+
/** The channel a chat surface's label names; an unknown label names none rather than a guess. */
|
|
33
|
+
export declare function leadChannelOf(label: string): LeadChannel | undefined;
|
|
34
|
+
export declare function leadContext(store: Store, repos: readonly string[], now: Date, options?: LeadContextOptions): string;
|
|
6
35
|
/** The same saved brief is useful before chat spending is authorized. */
|
|
7
36
|
export declare function leadBriefHtml(brief: AssignmentCatchUp): string;
|
|
8
|
-
export declare const LEAD_CONTEXT_CSS = ".lead-brief{margin:1rem 0;min-width:0}.lead-brief h2{font-size:1.1rem;margin:0 0 .8rem}.repository-context input[name=q]{display:block;box-sizing:border-box;min-height:44px;width:100%;margin:.4rem 0 .75rem;padding:.6rem .75rem;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:inherit;font:inherit}.repository-context form{margin:.5rem 0 1rem}.repository-context summary{min-height:44px;padding:.75rem 0;overflow-wrap:anywhere}.lead-brief h3{font-size:.85rem;margin:1rem 0 .4rem;color:var(--muted-foreground)}.lead-brief ul{list-style:none;margin:0;padding:0}.lead-brief li{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:.75rem;align-items:center;padding:.35rem 0;border-bottom:1px solid var(--border);min-width:0}.lead-brief li a{display:flex;min-height:44px;align-items:center;font-weight:500;overflow-wrap:anywhere;text-decoration:none}.lead-brief li a:hover{text-decoration:underline}.lead-brief-state{font-size:.75rem;font-weight:600;padding:.15rem .5rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink);white-space:nowrap}.lead-brief-state--needs-decision,.lead-brief-state--ready-to-check{background:var(--so-attention-soft);color:var(--so-attention)}.lead-brief-state--working,.lead-brief-state--checking{background:var(--so-info-soft);color:var(--so-info)}.lead-brief-state--complete{background:var(--so-success-soft);color:var(--so-success)}.lead-brief li a.lead-brief-act{grid-column:1/-1;justify-self:start;display:inline-flex;min-height:40px;margin:.15rem 0 .4rem;font-weight:600}@media(max-width:760px){.lead-brief li a.lead-brief-act{justify-self:stretch;justify-content:center;min-height:44px}}.lead-brief-detail{grid-column:1/-1;font-size:.85rem;color:var(--muted-foreground);overflow-wrap:anywhere;padding-bottom:.35rem}@media(max-width:760px){.lead-brief{margin:.5rem 0}.lead-brief h2{margin:0 0 .25rem}.lead-brief h3{margin:.5rem 0 0}.lead-brief li{padding:0 0 .375rem}.lead-brief-detail{margin-top:-.375rem;padding-bottom:0;line-height:1.35;pointer-events:none}}";
|
|
37
|
+
export declare const LEAD_CONTEXT_CSS = ".lead-promise-list{list-style:none;margin:0;padding:0}.lead-promise-list li{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:.75rem;align-items:center;padding:.5rem 0;border-bottom:1px solid var(--border);min-width:0}.lead-promise-list li:last-child{border-bottom:0}.lead-promise-list p{margin:0;overflow-wrap:anywhere}.lead-promise-list form{grid-column:2;grid-row:1/span 2;margin:0}.lead-promise-list button{min-height:44px;width:auto;white-space:nowrap}.lead-promise-list .nowrap{white-space:nowrap}.lead-brief{margin:1rem 0;min-width:0}.lead-brief h2{font-size:1.1rem;margin:0 0 .8rem}.repository-context input[name=q]{display:block;box-sizing:border-box;min-height:44px;width:100%;margin:.4rem 0 .75rem;padding:.6rem .75rem;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:inherit;font:inherit}.repository-context form{margin:.5rem 0 1rem}.repository-context summary{min-height:44px;padding:.75rem 0;overflow-wrap:anywhere}.lead-brief h3{font-size:.85rem;margin:1rem 0 .4rem;color:var(--muted-foreground)}.lead-brief ul{list-style:none;margin:0;padding:0}.lead-brief li{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:.75rem;align-items:center;padding:.35rem 0;border-bottom:1px solid var(--border);min-width:0}.lead-brief li a{display:flex;min-height:44px;align-items:center;font-weight:500;overflow-wrap:anywhere;text-decoration:none}.lead-brief li a:hover{text-decoration:underline}.lead-brief-state{font-size:.75rem;font-weight:600;padding:.15rem .5rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink);white-space:nowrap}.lead-brief-state--needs-decision,.lead-brief-state--ready-to-check{background:var(--so-attention-soft);color:var(--so-attention)}.lead-brief-state--working,.lead-brief-state--checking{background:var(--so-info-soft);color:var(--so-info)}.lead-brief-state--complete{background:var(--so-success-soft);color:var(--so-success)}.lead-brief li a.lead-brief-act{grid-column:1/-1;justify-self:start;display:inline-flex;min-height:40px;margin:.15rem 0 .4rem;font-weight:600}@media(max-width:760px){.lead-brief li a.lead-brief-act{justify-self:stretch;justify-content:center;min-height:44px}}.lead-brief-detail{grid-column:1/-1;font-size:.85rem;color:var(--muted-foreground);overflow-wrap:anywhere;padding-bottom:.35rem}@media(max-width:760px){.lead-brief{margin:.5rem 0}.lead-brief h2{margin:0 0 .25rem}.lead-brief h3{margin:.5rem 0 0}.lead-brief li{padding:0 0 .375rem}.lead-brief-detail{margin-top:-.375rem;padding-bottom:0;line-height:1.35;pointer-events:none}}";
|