toolroll 0.9.84 → 0.9.85
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 +42 -0
- package/README.md +94 -1193
- package/dist/agent-fence.d.ts +3 -3
- package/dist/agent-fence.js +3 -3
- package/dist/agent-onboard.d.ts +2 -0
- package/dist/agent-onboard.js +3 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +31 -31
- package/dist/browser-workspace.d.ts +5 -0
- package/dist/chat-channel.d.ts +10 -1
- package/dist/chat-channel.js +35 -5
- package/dist/chat-decide.d.ts +16 -4
- package/dist/chat-decide.js +239 -31
- package/dist/chat-messages.d.ts +5 -0
- package/dist/chat-messages.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-review.d.ts +4 -0
- package/dist/chat-review.js +13 -4
- package/dist/chat-terms.d.ts +9 -0
- package/dist/chat-terms.js +46 -0
- package/dist/contracts/console-api.d.ts +2 -1
- package/dist/contracts/console-api.js +2 -1
- package/dist/contracts/lead-tools.d.ts +30 -0
- package/dist/contracts/lead-tools.js +3 -0
- package/dist/contracts/youiyoux-request.d.ts +248 -0
- package/dist/contracts/youiyoux-request.js +152 -0
- package/dist/flow-cadence.d.ts +8 -2
- package/dist/flow-cadence.js +16 -3
- package/dist/flow-card-task.d.ts +9 -0
- package/dist/flow-card-task.js +15 -6
- package/dist/integrations-ui.js +9 -4
- package/dist/integrations.d.ts +9 -1
- package/dist/integrations.js +43 -2
- package/dist/landed-cleanup.d.ts +4 -0
- package/dist/landed-cleanup.js +3 -2
- package/dist/lead-capabilities.js +2 -1
- package/dist/lead-cli.js +3 -1
- package/dist/lead-tools.d.ts +2 -0
- package/dist/lead-tools.js +30 -4
- package/dist/lead.d.ts +2 -0
- package/dist/lead.js +3 -3
- package/dist/operate.d.ts +1 -2
- package/dist/operate.js +11 -15
- package/dist/project-activity.d.ts +20 -0
- package/dist/project-activity.js +23 -0
- package/dist/project-cli.d.ts +1 -0
- package/dist/project-cli.js +36 -19
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/project-outcomes.d.ts +119 -0
- package/dist/project-outcomes.js +79 -0
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +41 -9
- package/dist/result-review.d.ts +9 -0
- package/dist/result-review.js +45 -5
- package/dist/server/console.css +12 -0
- package/dist/server/pages.js +11 -1
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +2 -2
- package/dist/server/render-pages.js +7 -2
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-tasks.d.ts +1 -0
- package/dist/server/render-tasks.js +15 -5
- package/dist/server/route-table.js +1 -0
- package/dist/server/settings.js +18 -1
- package/dist/server/task-views.js +6 -0
- package/dist/server/tasks.js +42 -6
- package/dist/store.d.ts +46 -3
- package/dist/store.js +165 -6
- package/dist/task-source.d.ts +13 -1
- package/dist/task-source.js +17 -1
- package/dist/telegram-lead.js +7 -5
- package/dist/telegram.js +1 -1
- package/dist/toolroll-update.js +2 -2
- package/dist/webhooks.d.ts +6 -0
- package/dist/webhooks.js +25 -0
- package/dist/work-index.d.ts +4 -0
- package/dist/work-index.js +32 -0
- package/dist/youiyoux-intake.d.ts +234 -0
- package/dist/youiyoux-intake.js +908 -0
- package/package.json +11 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseFeedbackImage } from "../result-review.js";
|
|
1
2
|
import { assignmentSummaryHtml } from "../assignment-ui.js";
|
|
2
3
|
import {} from "../assignment.js";
|
|
3
4
|
import {} from "../browser-workspace.js";
|
|
@@ -610,8 +611,8 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
610
611
|
const snapshot = payload["snapshot"];
|
|
611
612
|
const ids = Array.isArray(payload["notes"]) ? payload["notes"] : [];
|
|
612
613
|
const selected = snapshot?.notes.filter(one => ids.includes(one.id)) ?? [];
|
|
613
|
-
const notes = [...selected, ...(text("note") === "" ? [] : [{ note: text("note"), path: text("path") || null, line: typeof payload["line"] === "number" ? payload["line"] : null }])];
|
|
614
|
-
what = html `<h3>${text("taskTitle") || task}</h3><ul class="proposal-review-notes">${notes.map(one => html `<li>${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : html `:${one.line}`}</span> · `}${one.note}</li>`)}</ul>${text("operation") === "revise" ? html `<p class="meta">Updates this task. Your approval settings apply.</p>` : html `<p class="meta">No work starts.</p>`}`;
|
|
614
|
+
const notes = [...selected, ...(text("note") === "" ? [] : [{ note: text("note"), path: text("path") || null, line: typeof payload["line"] === "number" ? payload["line"] : null, image: parseFeedbackImage(payload["image"]) || null }])];
|
|
615
|
+
what = html `<h3>${text("taskTitle") || task}</h3><ul class="proposal-review-notes">${notes.map(one => html `<li>${one.image == null ? "" : html `<a href="/r/${one.image.run}/evidence/${one.image.artifact}">Screenshot ${one.image.artifact} · result #${one.image.run}</a><br>`}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : html `:${one.line}`}</span> · `}${one.note}</li>`)}</ul>${text("operation") === "revise" ? html `<p class="meta">Updates this task. Your approval settings apply.</p>` : html `<p class="meta">No work starts.</p>`}`;
|
|
615
616
|
}
|
|
616
617
|
else if (view.kind === "next") {
|
|
617
618
|
what = html `<h3>Move <a href="${taskHref(task)}">${task}</a> to the front</h3>${facts(["current position", html `${String(payload["position"] ?? "?")} of ${String(payload["of"] ?? "?")}`], ["project", mono(repoId)])}`;
|
|
@@ -3,7 +3,7 @@ import { type TaskOrigin } from "../task-source.js";
|
|
|
3
3
|
import { type Html } from "../html.js";
|
|
4
4
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
5
5
|
import { type BoardCard } from "../board.js";
|
|
6
|
-
import { type BrowserLimits } from "../browser-workspace.js";
|
|
6
|
+
import { type BrowserLimits, type BrowserProjectsView } from "../browser-workspace.js";
|
|
7
7
|
import { type CheckLevel } from "../check-levels.js";
|
|
8
8
|
import { type DispatchDiagnosis } from "../dispatch.js";
|
|
9
9
|
import { type FirstRunStep } from "../first-run.js";
|
|
@@ -451,7 +451,7 @@ export declare function projectsPage(chrome: Chrome, recent: {
|
|
|
451
451
|
path: string;
|
|
452
452
|
name: string;
|
|
453
453
|
lastOpenedAt: string;
|
|
454
|
-
}[], candidates: string[], open: string | null, problem: string | null, _unscopedMode: boolean, browsable?: boolean, onboard?: OnboardCardState | null, peeks?: Record<string, ProjectPeek | null>, returnTo?: string, pullRequestsOn?: (path: string) => boolean, checkLevelOf?: (path: string) => CheckLevel): Screen;
|
|
454
|
+
}[], candidates: string[], open: string | null, problem: string | null, _unscopedMode: boolean, browsable?: boolean, onboard?: OnboardCardState | null, peeks?: Record<string, ProjectPeek | null>, returnTo?: string, pullRequestsOn?: (path: string) => boolean, checkLevelOf?: (path: string) => CheckLevel, activity?: BrowserProjectsView["activity"]): Screen;
|
|
455
455
|
/** The no-script "move to the front" sentinel: the form cannot name the
|
|
456
456
|
* front of a partition, so the handler resolves it (slice 1b, fix 1). */
|
|
457
457
|
export declare const QUEUE_FRONT = "__TOP__";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activityMetrics, activityScope, activityLimit } from '../project-activity.js';
|
|
1
2
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
2
3
|
import { originNote } from "../task-source.js";
|
|
3
4
|
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
@@ -921,7 +922,10 @@ export function githubReposPage(chrome, data) {
|
|
|
921
922
|
html `<p class="row" style="margin-top:.6rem"><a class="badge" href="/projects">← back to projects</a></p>`,
|
|
922
923
|
], "\n"), { chrome });
|
|
923
924
|
}
|
|
924
|
-
export function projectsPage(chrome, recent, candidates, open, problem, _unscopedMode, browsable = false, onboard = null, peeks = {}, returnTo = "/", pullRequestsOn, checkLevelOf) {
|
|
925
|
+
export function projectsPage(chrome, recent, candidates, open, problem, _unscopedMode, browsable = false, onboard = null, peeks = {}, returnTo = "/", pullRequestsOn, checkLevelOf, activity) {
|
|
926
|
+
const activityHtml = activity === undefined ? null : html `<details><summary>Activity · ${activity.project}</summary>${activity.report === null
|
|
927
|
+
? html `<p role="alert">Activity is unavailable.</p><a href="/projects">Reload</a>`
|
|
928
|
+
: html `<p>${activityScope(activity.report)}</p>${activity.report.truncated ? html `<p>${activityLimit(activity.report)}</p>` : null}${activityMetrics(activity.report).map(metric => html `<details><summary>${metric.label} · ${metric.value}</summary><p>${metric.detail}</p>${metric.rows.length === 0 ? html `<p>No tasks in this group.</p>` : html `<ul>${metric.rows.map(row => html `<li><a href="${row.href}">${row.title}</a> · Recorded checks: ${row.checks}</li>`)}</ul>`}</details>`)}`}</details>`;
|
|
925
929
|
// The onboarding card (repo onboarding, findings 1-39): preview first,
|
|
926
930
|
// then a password-confirmed clone into a configured root. Disabled
|
|
927
931
|
// states explain themselves in words (finding 28/39).
|
|
@@ -1047,11 +1051,12 @@ export function projectsPage(chrome, recent, candidates, open, problem, _unscope
|
|
|
1047
1051
|
recentItems.length === 0 && candidateItems.length === 0
|
|
1048
1052
|
? html `<div class="card"><p><strong>Nothing to open yet.</strong></p><p class="meta">Add one below — opening it registers it here for next time.</p></div>`
|
|
1049
1053
|
: null,
|
|
1054
|
+
activityHtml,
|
|
1050
1055
|
recentItems.length > 0 ? html `<h2>Recent</h2>${cards(recentItems)}` : null,
|
|
1051
1056
|
candidateItems.length > 0 ? html `<h2>Available</h2>${cards(candidateItems)}` : null,
|
|
1052
1057
|
addCard,
|
|
1053
1058
|
], "\n"), { chrome, workspace: { view: {
|
|
1054
|
-
kind: "projects", choosing, problem, returnTo,
|
|
1059
|
+
kind: "projects", choosing, problem, returnTo, ...(activity === undefined ? {} : { activity }),
|
|
1055
1060
|
recent: recent.map(one => rowOf(one.path, one.name, one.lastOpenedAt)),
|
|
1056
1061
|
available: candidates.map(path => rowOf(path, projectName(path), null)),
|
|
1057
1062
|
add: { browse: browsable ? "/projects/browse" : null, github: onboard === null ? null : "/projects/github", html: htmlString(addForms) },
|
|
@@ -614,6 +614,8 @@ export type ResultDetail = {
|
|
|
614
614
|
fileAnchors: ReadonlyMap<string, string>;
|
|
615
615
|
comments: DiffComment[];
|
|
616
616
|
pastComments?: DiffComment[];
|
|
617
|
+
/** Live saved notes whose screenshot would be refused now (result-actions.ts feedbackImageProblem). */
|
|
618
|
+
unusableImageNotes?: readonly number[];
|
|
617
619
|
reviewerFindings: DiffComment[];
|
|
618
620
|
/** The build's one automatic review, when its project had review on. */
|
|
619
621
|
automaticReview?: BuildReviewView | null;
|
|
@@ -1068,7 +1068,7 @@ export function proofBundleView(store, run, artifacts, root) {
|
|
|
1068
1068
|
catch {
|
|
1069
1069
|
problem = "the file could not be read";
|
|
1070
1070
|
}
|
|
1071
|
-
return [{ path, caption: captionFor(path), artifactId: artifact.id, problem }];
|
|
1071
|
+
return [{ path, caption: captionFor(path), artifactId: artifact.id, sha256: artifact.sha256, problem }];
|
|
1072
1072
|
});
|
|
1073
1073
|
const stored = new Set(screenshots.map(one => one.path));
|
|
1074
1074
|
const uncapturedScreenshots = (proof?.screenshots ?? []).map(one => one.path).filter(path => !stored.has(path));
|
|
@@ -1598,6 +1598,8 @@ export function resultPanelParts(detail, o) {
|
|
|
1598
1598
|
const physicalReasons = receipt.reasons.filter(reason => !assessmentReasons.has(reason));
|
|
1599
1599
|
const runId = run.id;
|
|
1600
1600
|
const shots = proof?.screenshots ?? [];
|
|
1601
|
+
const picture = (shot) => html `<figure class="result-image"><a class="receipt-shot" href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"><span>${shot.caption}</span></a>${detail.canAnnotate && o.csrf !== "" && shot.sha256 !== undefined
|
|
1602
|
+
? html `<button type="button" class="quiet" data-comment-image="${JSON.stringify({ run: runId, artifact: shot.artifactId, sha256: shot.sha256 })}" data-image-caption="${shot.caption}">Comment</button>` : ""}</figure>`;
|
|
1601
1603
|
const shown = shots.filter(one => one.problem === null);
|
|
1602
1604
|
const stat = terminal?.stat ?? null;
|
|
1603
1605
|
const statOk = stat !== null && !("problem" in stat);
|
|
@@ -1694,7 +1696,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1694
1696
|
}
|
|
1695
1697
|
else if (lead === "screenshots") {
|
|
1696
1698
|
summaryParts.push(html `<div class="receipt-visuals result-visuals" aria-label="validated screenshots">${shown
|
|
1697
|
-
.map(
|
|
1699
|
+
.map(picture)}</div>`);
|
|
1698
1700
|
}
|
|
1699
1701
|
const unavailableShots = shots.filter(one => one.problem !== null);
|
|
1700
1702
|
if (lead === "changes") {
|
|
@@ -1848,7 +1850,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1848
1850
|
? directAssessment && !screenshotRequired ? html `` : html `<p class="meta" data-cockpit-source="screenshots">No screenshots${screenshotRequired ? " were captured, although one was required" : " were needed"}.</p>`
|
|
1849
1851
|
: lead === "screenshots"
|
|
1850
1852
|
? html `<p class="meta" data-cockpit-source="screenshots">${shown.length} validated screenshot${shown.length === 1 ? "" : "s"} shown in Summary${unavailableShots.length > 0 ? `; ${unavailableShots.length} unavailable` : ""}.</p>`
|
|
1851
|
-
: html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(
|
|
1853
|
+
: html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(picture)}</div></details>`);
|
|
1852
1854
|
checkParts.push(receipt.caveats.length === 0
|
|
1853
1855
|
? directAssessment ? html `` : html `<p class="meta" data-cockpit-source="caveats">No caveats were reported.</p>`
|
|
1854
1856
|
: html `<details class="cockpit-proof-group" data-cockpit-source="caveats"><summary>Agent caveats · ${receipt.caveats.length}</summary><ul>${receipt.caveats.map(one => html `<li>${one}</li>`)}</ul></details>`);
|
|
@@ -1863,19 +1865,29 @@ export function resultPanelParts(detail, o) {
|
|
|
1863
1865
|
checkParts.push(o.extraChecks);
|
|
1864
1866
|
// ---- request changes: both feedback styles, one sealed road ------------
|
|
1865
1867
|
const requestParts = [html `<h3 class="so-sr-only">What should change?</h3>`];
|
|
1868
|
+
const imageNote = (one) => {
|
|
1869
|
+
if (one.image == null)
|
|
1870
|
+
return html ``;
|
|
1871
|
+
const shot = shots.find(shot => shot.artifactId === one.image.artifact && shot.sha256 === one.image.sha256 && one.image.run === runId && shot.problem === null);
|
|
1872
|
+
// A live saved note whose screenshot no longer verifies can be kept without it, so it never blocks a revision.
|
|
1873
|
+
const drop = o.csrf === "" || !(detail.unusableImageNotes ?? []).includes(one.id) ? html ``
|
|
1874
|
+
: postForm(`/r/${runId}/comment`, html `<input type="hidden" name="intent" value="drop-image"><input type="hidden" name="comment" value="${String(one.id)}"><button type="submit" class="quiet">Keep note without screenshot</button>`, { attrs: { class: "feedback-image-drop" }, returnTo: o.returnTo, hidden: { tab: o.tab } });
|
|
1875
|
+
return html `<figure class="feedback-image">${shot === undefined ? html `<figcaption>Screenshot ${one.image.artifact} is unavailable.</figcaption>`
|
|
1876
|
+
: html `<a href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"></a><figcaption>${shot.caption} · Screenshot ${shot.artifactId}</figcaption>`}${drop}</figure>`;
|
|
1877
|
+
};
|
|
1866
1878
|
const pathWords = (path, line) => {
|
|
1867
1879
|
const shownPath = `${path}${line === null ? "" : `:${line}`}`;
|
|
1868
1880
|
const href = detail.editor === null ? null : editorFileHref(detail.editor.worktree, path, line);
|
|
1869
1881
|
return href === null ? html `<span class="mono">${shownPath}</span> ` : html `<a class="mono" href="${href}">${shownPath}</a> `;
|
|
1870
1882
|
};
|
|
1871
1883
|
if (detail.comments.length > 0) {
|
|
1872
|
-
requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment"
|
|
1884
|
+
requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment">${imageNote(one)}<span class="diff-comment-pin" aria-hidden="true"></span><p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div>`);
|
|
1873
1885
|
if (o.csrf !== "" && !detail.canAnnotate) {
|
|
1874
1886
|
requestParts.push(postForm(`/r/${runId}/revise`, html `${revisionSealFields(detail.comments, detail.sourceDigest)}<div><strong>${detail.comments.length} note${detail.comments.length === 1 ? "" : "s"} ready</strong></div><button type="submit">Request changes</button>`, { attrs: { class: "card revision-from-comments" }, returnTo: o.returnTo }));
|
|
1875
1887
|
}
|
|
1876
1888
|
}
|
|
1877
1889
|
if ((detail.pastComments?.length ?? 0) > 0) {
|
|
1878
|
-
requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment"
|
|
1890
|
+
requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment">${imageNote(one)}<p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div></details>`);
|
|
1879
1891
|
}
|
|
1880
1892
|
for (const revision of detail.revisions) {
|
|
1881
1893
|
// The child's standing is the shared status projection's own words
|
|
@@ -1892,6 +1904,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1892
1904
|
requestParts.push(html `<details class="result-request-open result-request-form"${detail.comments.length > 0 ? " open" : ""}><summary>What should change?</summary>${postForm(`/r/${runId}/comment`, joinHtml([
|
|
1893
1905
|
revisionSealFields(detail.comments, detail.sourceDigest),
|
|
1894
1906
|
html `<input type="hidden" data-recorded-requests value="${[...detail.comments, ...(detail.pastComments ?? [])].filter(one => one.sourceKey?.startsWith(`review:${o.user}:`)).map(one => one.sourceKey.slice(`review:${o.user}:`.length)).join(",")}">`,
|
|
1907
|
+
html `<input type="hidden" name="image" value=""><figure class="feedback-image" data-feedback-image hidden><img alt="Selected screenshot" hidden><figcaption></figcaption><button type="button" class="quiet" data-remove-image>Remove image</button></figure>`,
|
|
1895
1908
|
html `<textarea name="note" rows="2" maxlength="${LIMITS.note}" placeholder="Describe the change…" aria-label="review comment" aria-describedby="comment-note-limit"${o.noted ? " autofocus" : ""}></textarea>`,
|
|
1896
1909
|
html `<span class="meta diff-comment-limit" id="comment-note-limit">up to ${LIMITS.note} characters</span>`,
|
|
1897
1910
|
html `<details class="result-pin"><summary>Attach to a file or line</summary>`,
|
|
@@ -1980,7 +1993,7 @@ export function personCheckItems(proof, patchText, shots, runId) {
|
|
|
1980
1993
|
return {
|
|
1981
1994
|
id: row.id, statement: row.statement, words: personCheckWords(row.statement), note: notes.length === 0 ? null : notes.join(" "),
|
|
1982
1995
|
excerpts: files.filter(one => one.lines.length > 0).slice(0, 3),
|
|
1983
|
-
shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption })),
|
|
1996
|
+
shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption, ...(shot.sha256 === undefined ? {} : { image: { run: runId, artifact: shot.artifactId, sha256: shot.sha256 } }) })),
|
|
1984
1997
|
};
|
|
1985
1998
|
});
|
|
1986
1999
|
}
|
|
@@ -2215,7 +2228,7 @@ export function runPage(chrome, run, taskId, artifacts, terminal = null, notes =
|
|
|
2215
2228
|
noted,
|
|
2216
2229
|
requestToken: result.requestToken,
|
|
2217
2230
|
hrefFor: one => (one === "summary" ? `/r/${run.id}` : `/r/${run.id}?tab=${one}`),
|
|
2218
|
-
returnTo: `/r/${run.id}`,
|
|
2231
|
+
returnTo: `/r/${run.id}?record=1`,
|
|
2219
2232
|
back: null,
|
|
2220
2233
|
});
|
|
2221
2234
|
return screen(`build #${run.id}`, joinHtml([
|
|
@@ -398,6 +398,7 @@ export type RevisionView = {
|
|
|
398
398
|
line: number | null;
|
|
399
399
|
note: string;
|
|
400
400
|
author: string;
|
|
401
|
+
image?: import("../result-review.js").FeedbackImage | null;
|
|
401
402
|
}[];
|
|
402
403
|
/** The lineage and ACTUAL terms (contract handoff task 2), read from
|
|
403
404
|
* the child's own rows — the same projection chat and the approval
|
|
@@ -50,7 +50,7 @@ import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
|
50
50
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
51
51
|
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
52
52
|
import {} from "./session.js";
|
|
53
|
-
import { linkWords, originNote } from "../task-source.js";
|
|
53
|
+
import { linkWords, originNote, requestName, requestWords } from "../task-source.js";
|
|
54
54
|
export function contractChangeHtml(change) {
|
|
55
55
|
const tag = html `<span class="contract-change-kind contract-change-${change.kind}">${change.kind}</span>`;
|
|
56
56
|
switch (change.field) {
|
|
@@ -203,7 +203,8 @@ const fragmentString = (fragment) => fragment === "" ? "" : htmlString(fragment)
|
|
|
203
203
|
function originWords(origin) {
|
|
204
204
|
const flow = origin.flow === null ? null : origin.flow === "hidden" ? "from a flow" : origin.flow === "removed" ? "from a flow (removed)" : `from flow “${origin.flow.name}”`;
|
|
205
205
|
const source = origin.source === null ? null : `${origin.source.label} (${origin.source.proved})`;
|
|
206
|
-
|
|
206
|
+
const request = origin.request == null ? null : `from ${requestName(origin.request)}`;
|
|
207
|
+
return [flow, source, request].filter(one => one !== null).join(" · ");
|
|
207
208
|
}
|
|
208
209
|
/** v126: a task's origin as a fact: the flow, linked for those who may open it, and the outside event, linked to where
|
|
209
210
|
* it lives, with that link's host always shown. */
|
|
@@ -219,6 +220,12 @@ function originParts(origin) {
|
|
|
219
220
|
if (origin.source.url !== null)
|
|
220
221
|
parts.push(" · ", { label: linkWords(origin.source.url), href: origin.source.url });
|
|
221
222
|
}
|
|
223
|
+
if (origin.request != null) {
|
|
224
|
+
if (parts.length > 0)
|
|
225
|
+
parts.push(" · ");
|
|
226
|
+
const where = requestWords(origin.request);
|
|
227
|
+
parts.push(`${requestName(origin.request)}${where === "" ? "" : `: ${where}`}`);
|
|
228
|
+
}
|
|
222
229
|
return parts;
|
|
223
230
|
}
|
|
224
231
|
export function approvalSheetHtml(input) {
|
|
@@ -294,7 +301,7 @@ export function approvalSheetHtml(input) {
|
|
|
294
301
|
const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
|
|
295
302
|
html `<div class="revision-card" data-revision-feedback>`,
|
|
296
303
|
html `<p><a href="${input.revisionSourceHref}"${input.surface === "chat" ? html ` data-revision-source` : ""}>${input.surface === "chat" ? html `Original result: build #${input.revision.sourceRun} →` : html `build #${input.revision.sourceRun}`}</a></p>`,
|
|
297
|
-
input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> · `}${one.note} <span class="meta">— ${one.author}</span></li>`)}</ul>`,
|
|
304
|
+
input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> · `}${one.note} <span class="meta">— ${one.author}</span></li>`)}</ul>`,
|
|
298
305
|
revisionLineageHtml(input.revision.lineage),
|
|
299
306
|
html `</div>`,
|
|
300
307
|
]));
|
|
@@ -1172,6 +1179,9 @@ export function executionPlanHtml(document, compact = false) {
|
|
|
1172
1179
|
html `</div>`,
|
|
1173
1180
|
]);
|
|
1174
1181
|
}
|
|
1182
|
+
function revisionImageLink(image) {
|
|
1183
|
+
return image == null ? html `` : html `<a href="/r/${image.run}/evidence/${image.artifact}">Screenshot ${image.artifact} · result #${image.run}</a> · `;
|
|
1184
|
+
}
|
|
1175
1185
|
/**
|
|
1176
1186
|
* A revision's lineage and ACTUAL terms, in the same words on the task
|
|
1177
1187
|
* page, the approval card, and chat (contract handoff task 2): which
|
|
@@ -1594,7 +1604,7 @@ export function taskBodyParts(data) {
|
|
|
1594
1604
|
: joinHtml([
|
|
1595
1605
|
html `<div class="revision-card" data-revision-feedback>`,
|
|
1596
1606
|
html `<p><strong>${data.revision.kind === "ci-repair" ? "CI repair" : data.revision.kind === "criterion-repair" ? "Criterion repair" : "Revision feedback"}</strong> <span class="meta">from <a href="/r/${data.revision.sourceRun}">build #${data.revision.sourceRun}</a></span></p>`,
|
|
1597
|
-
...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
|
|
1607
|
+
...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
|
|
1598
1608
|
revisionLineageHtml(data.revision.lineage),
|
|
1599
1609
|
html `</div>`,
|
|
1600
1610
|
], "\n");
|
|
@@ -2170,7 +2180,7 @@ export function taskBodyParts(data) {
|
|
|
2170
2180
|
const feedback = data.revision;
|
|
2171
2181
|
thread.push({ key: "revision", at: task.createdAt, kind: "reply", who: "person", author: feedback.comments[0]?.author ?? "You",
|
|
2172
2182
|
title: feedback.kind === "ci-repair" ? "Asked for a CI repair" : feedback.kind === "criterion-repair" ? "Asked for a repair" : "Asked for changes",
|
|
2173
|
-
text: feedback.comments.map(one => `${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
|
|
2183
|
+
text: feedback.comments.map(one => `${one.image ? `Screenshot ${one.image.artifact} · result #${one.image.run} — ` : ""}${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
|
|
2174
2184
|
link: { label: `From build #${feedback.sourceRun}`, href: `/r/${feedback.sourceRun}` }, html: "", more: null });
|
|
2175
2185
|
}
|
|
2176
2186
|
for (const decision of data.decisions) {
|
|
@@ -336,6 +336,7 @@ for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in"
|
|
|
336
336
|
post(one === "sessions" ? "people" : "settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`, { ...(["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups", "data", "monitoring", "tools/connect", "tools/change"].includes(one) ? { scope: "step-up" } : {}), ...SETTINGS_ACT_ROLES[one] });
|
|
337
337
|
}
|
|
338
338
|
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
|
|
339
|
+
post("settings", "settings.integrations-youiyoux", "/settings/integrations/youiyoux", { limited: "collection", ...approver("An approver turns youiyoux requests on or off.", "/settings/integrations") });
|
|
339
340
|
const MODELS = approver("Sign in as an approver to change models.", "/settings/models");
|
|
340
341
|
for (const one of ["check", "watch", "update"])
|
|
341
342
|
post("settings", `settings.models-${one}`, `/settings/models/${one}`, MODELS);
|
package/dist/server/settings.js
CHANGED
|
@@ -17,7 +17,7 @@ import { grantTool } from "../subagent-tools.js";
|
|
|
17
17
|
import { learningHtml } from "../workspace-ui.js";
|
|
18
18
|
import { adapterPolicy } from "./route-policy.js";
|
|
19
19
|
import { createHash } from "node:crypto";
|
|
20
|
-
import { dirname, join } from "node:path";
|
|
20
|
+
import { basename, dirname, join } from "node:path";
|
|
21
21
|
import { INSTALLATION_SCOPE } from "../agentconfig.js";
|
|
22
22
|
import { rulesSummary } from "../approval-rules-ui.js";
|
|
23
23
|
import { installMethod } from "../install-method.js";
|
|
@@ -64,6 +64,7 @@ import { startersHtml } from "../flow-starters-ui.js";
|
|
|
64
64
|
import { starterOf, startersFor, switchOnStarter } from "../flow-starters.js";
|
|
65
65
|
import { disconnectGoogle, googleConnected, googleConsent, readGoogleMail, saveGoogleClient } from "../google-mail.js";
|
|
66
66
|
import { integrationsHtml } from "../integrations-ui.js";
|
|
67
|
+
import { setYouiyouxIntake } from "../youiyoux-intake.js";
|
|
67
68
|
import { clearProviderKey, keyStatus, plausibleKey, PROVIDER_KEY_ENV, readAuthMode, readProviderKey, saveProviderKey, setAuthMode, SUBSCRIPTION_CAPABLE, verdictWords, verifyProviderKey } from "../keys.js";
|
|
68
69
|
import { decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml } from "../knowledge-ui.js";
|
|
69
70
|
import { aboutYouOf, checkAboutYou, saveAboutYou } from "../lead-about.js";
|
|
@@ -1386,6 +1387,21 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1386
1387
|
const switched = switchOnStarter(store, starter, repo, who.name, clock(), options.configDir ?? null);
|
|
1387
1388
|
return switched.ok ? back("said", switched.said) : back("problem", switched.said);
|
|
1388
1389
|
}
|
|
1390
|
+
// Settings → Integrations → youiyoux → Turn off / Turn on: whether a project's change requests become tasks.
|
|
1391
|
+
async function integrationsYouiyoux(ctx) {
|
|
1392
|
+
const { who, response, posted } = ctx;
|
|
1393
|
+
const body = readForm(posted, CONSOLE_FORMS.integrationsYouiyoux);
|
|
1394
|
+
const repo = body.get("repo") ?? "";
|
|
1395
|
+
const back = sayTo(response, "/settings/integrations");
|
|
1396
|
+
const known = [...new Set([...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()])];
|
|
1397
|
+
if (store.isDemo())
|
|
1398
|
+
return refuse(response, who, 403, "An approver turns youiyoux requests on or off.", "/settings/integrations");
|
|
1399
|
+
if (!known.includes(repo) || !visible(repo))
|
|
1400
|
+
return refuse(response, who, 404, "No such project.", "/settings/integrations");
|
|
1401
|
+
const on = body.get("on") === "1";
|
|
1402
|
+
setYouiyouxIntake(store, repo, on, who.name, clock());
|
|
1403
|
+
return back("said", on ? `youiyoux requests in ${basename(repo)} become tasks again.` : `youiyoux requests in ${basename(repo)} are off.`);
|
|
1404
|
+
}
|
|
1389
1405
|
// Settings → Integrations → Send test: one harmless read-only check of one integration, now.
|
|
1390
1406
|
async function integrationsTest(ctx) {
|
|
1391
1407
|
const { who, response, posted } = ctx;
|
|
@@ -2642,6 +2658,7 @@ ${modeWords(terms)
|
|
|
2642
2658
|
"settings.backups-send": backupsSave,
|
|
2643
2659
|
"settings.data-send": dataExport,
|
|
2644
2660
|
"settings.integrations-test-send": integrationsTest,
|
|
2661
|
+
"settings.integrations-youiyoux": integrationsYouiyoux,
|
|
2645
2662
|
"settings.monitoring-send": monitoringSave,
|
|
2646
2663
|
"settings.tools-connect-send": toolsConnect,
|
|
2647
2664
|
"settings.tools-change-send": toolsChange,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { feedbackImageProblem } from "../result-actions.js";
|
|
2
|
+
import { parseFeedbackImage } from "../result-review.js";
|
|
1
3
|
import { releaseViewOf } from "../release-view.js";
|
|
2
4
|
import { releaseCiRunningOf } from "../release-ci-hold.js";
|
|
3
5
|
import { releaseObligationsOf } from "../release-recheck.js";
|
|
@@ -229,6 +231,7 @@ export function createTaskViews(runtime) {
|
|
|
229
231
|
line: one.line === null || one.line === undefined ? null : Number(one.line),
|
|
230
232
|
note: String(one.note ?? ""),
|
|
231
233
|
author: String(one.author ?? "?"),
|
|
234
|
+
image: parseFeedbackImage(one.image) || null,
|
|
232
235
|
})),
|
|
233
236
|
};
|
|
234
237
|
}
|
|
@@ -1119,6 +1122,9 @@ export function createTaskViews(runtime) {
|
|
|
1119
1122
|
outsideTouches: files.filter(one => one.outsideTouches).map(one => one.path),
|
|
1120
1123
|
fileAnchors,
|
|
1121
1124
|
comments: runtime.store.liveDiffComments(run.id).filter(isRevisionFeedback),
|
|
1125
|
+
// The same check that refuses a saved note's screenshot at revision time decides when it can be removed.
|
|
1126
|
+
unusableImageNotes: runtime.store.liveDiffComments(run.id).filter(one => isRevisionFeedback(one) && one.image != null
|
|
1127
|
+
&& feedbackImageProblem(runtime.store, runtime.evidenceRoot, run.id, one.image) !== null).map(one => one.id),
|
|
1122
1128
|
pastComments: runtime.store.allDiffComments(run.id).filter(one => one.reviewerRun === null && (one.consumedBy !== null || one.supersededBy !== null)),
|
|
1123
1129
|
reviewerFindings: runtime.store.allDiffComments(run.id).filter(one => one.reviewerRun !== null),
|
|
1124
1130
|
automaticReview: buildReviewOf(runtime.store, run.id),
|
package/dist/server/tasks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseFeedbackImage, sameFeedbackImage } from "../result-review.js";
|
|
2
|
+
import { dropFeedbackImage, releaseAuthorityOfTask, revokeRelease, runLiveCommands } from "../store.js";
|
|
2
3
|
import { steerCapabilityOf } from "../steer-capabilities.js";
|
|
3
4
|
import { canReveal, localCheckoutOf, revealInFinder } from "../checkout-proof.js";
|
|
4
5
|
/** A form's way back to a flow card ("flow:<flow>:<card>"), read strictly: numbers only, never a path. */
|
|
@@ -48,7 +49,7 @@ import { fileTaskProposal } from "../proposal.js";
|
|
|
48
49
|
import { isProviderId, PROVIDER_IDS, validateSpec } from "../provider.js";
|
|
49
50
|
import { completeAndOpenPullRequest, mergeAsPerson } from '../pull-request-flow.js';
|
|
50
51
|
import { isQualityMode } from "../quality.js";
|
|
51
|
-
import { createResultRevision, requestResultChanges } from "../result-actions.js";
|
|
52
|
+
import { createResultRevision, requestResultChanges, feedbackImageProblem } from "../result-actions.js";
|
|
52
53
|
import { structuredHandoffView, terminalDiffView } from "../result-evidence-readers.js";
|
|
53
54
|
import { fileAddTestsTask, requestFollowUpChecks } from '../result-follow-ups.js';
|
|
54
55
|
import { commentSourceKey, isRevisionFeedback, parseResultTab, resultReturnTarget } from "../result-review.js";
|
|
@@ -1402,15 +1403,40 @@ export function createTasksHandlers(runtime) {
|
|
|
1402
1403
|
const viewParam = returnTo.startsWith("/t/") ? "section" : "tab";
|
|
1403
1404
|
const back = selectedTab === "summary" ? returnTo
|
|
1404
1405
|
: `${returnTo.replace(/§ion=(?:summary|changes|checks)$/, "")}${returnTo.includes("?") ? "&" : "?"}${viewParam}=${selectedTab}`;
|
|
1406
|
+
if (body.get("intent") === "drop-image") {
|
|
1407
|
+
// A saved note whose screenshot is gone or changed would block every revision that seals it. Its words stay:
|
|
1408
|
+
// the note is superseded by an exact imageless copy, and the original remains in history.
|
|
1409
|
+
// Who: a signed-in browser approver admitted to this run's project (checked above) — the same authority that
|
|
1410
|
+
// seals any saved note into a revision. What: only a screenshot that is unusable right now, re-proved inside
|
|
1411
|
+
// dropFeedbackImage's transaction, and recorded in the ledger under this person.
|
|
1412
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
1413
|
+
return refuse(response, who, 403, "Only a signed-in approver can change saved feedback.", back);
|
|
1414
|
+
const comment = Number(body.get("comment") ?? "");
|
|
1415
|
+
if (!Number.isSafeInteger(comment) || comment < 1)
|
|
1416
|
+
return refuse(response, who, 400, "Choose a saved note.", back);
|
|
1417
|
+
const dropped = dropFeedbackImage(store, id, comment, who.name, one => feedbackImageProblem(store, evidenceRoot, id, one), now);
|
|
1418
|
+
if (!dropped.ok && dropped.reason === "not-authorized")
|
|
1419
|
+
return refuse(response, who, 403, "Only a signed-in approver on this project can change saved feedback.", back);
|
|
1420
|
+
if (!dropped.ok)
|
|
1421
|
+
return refuse(response, who, 409, dropped.reason === "image-usable"
|
|
1422
|
+
? "That screenshot still verifies, so the note keeps it. Reload the result." : "That saved note changed or was already used. Reload the result.", back);
|
|
1423
|
+
return redirect(response, `${back}#request-changes`);
|
|
1424
|
+
}
|
|
1425
|
+
const image = parseFeedbackImage(body.get("image"));
|
|
1426
|
+
if (image === false)
|
|
1427
|
+
return refuse(response, who, 400, "Choose a screenshot from this result again. Your draft is unchanged.", back);
|
|
1405
1428
|
if (body.get("intent") === "revise") {
|
|
1406
1429
|
const result = requestResultChanges(store, evidenceRoot, {
|
|
1407
1430
|
run: id, batch: body.get("batch"), source: body.get("source"), actor: who.name,
|
|
1408
1431
|
repos: admissionList(), includeUnplaced: visible(null), allowMode: who.via === "cookie",
|
|
1409
|
-
note: body.get("note") ?? "", path: body.get("path") ?? "", line: body.get("line") ?? "", request: body.get("request"),
|
|
1432
|
+
note: body.get("note") ?? "", path: body.get("path") ?? "", line: body.get("line") ?? "", request: body.get("request"), image,
|
|
1410
1433
|
}, now);
|
|
1411
1434
|
if (!result.ok) {
|
|
1412
1435
|
const request = body.get("request") ?? "";
|
|
1413
|
-
|
|
1436
|
+
// The queue's link can stop resolving when a newer attempt finishes. The exact
|
|
1437
|
+
// run record still hosts this account/run's recoverable feedback draft.
|
|
1438
|
+
const retryBase = image === null ? back : `/r/${id}?record=1`;
|
|
1439
|
+
const retryBack = /^[a-f0-9]{32}$/.test(request) ? `${retryBase}${retryBase.includes("?") ? "&" : "?"}conflict=${request}#request-changes` : retryBase;
|
|
1414
1440
|
return refuse(response, who, result.status, result.message, retryBack);
|
|
1415
1441
|
}
|
|
1416
1442
|
return redirect(response, revisionDestination(result.id, returnTo));
|
|
@@ -1445,7 +1471,17 @@ export function createTasksHandlers(runtime) {
|
|
|
1445
1471
|
const request = body.get("request");
|
|
1446
1472
|
const sourceKey = commentSourceKey(who.name, request);
|
|
1447
1473
|
const path = rawPath === "" ? null : rawPath;
|
|
1448
|
-
|
|
1474
|
+
// Freshness, custody and the insert in one write transaction: a newer attempt finishing in between can't leave a
|
|
1475
|
+
// stale screenshot attached (check and write together).
|
|
1476
|
+
const saved = store.transact(() => {
|
|
1477
|
+
const imageProblem = feedbackImageProblem(store, evidenceRoot, id, image);
|
|
1478
|
+
if (imageProblem !== null)
|
|
1479
|
+
return { problem: imageProblem };
|
|
1480
|
+
return { inserted: store.addDiffComment({ artifactId: terminal.id, runId: id, path, line, note: note.note, author: who.name, image, ...(sourceKey === undefined ? {} : { sourceKey }) }, now) };
|
|
1481
|
+
});
|
|
1482
|
+
if ("problem" in saved)
|
|
1483
|
+
return refuse(response, who, 409, saved.problem, `/r/${id}?record=1#request-changes`);
|
|
1484
|
+
const inserted = saved.inserted;
|
|
1449
1485
|
if (inserted === null && sourceKey !== undefined) {
|
|
1450
1486
|
// The token was used before (repair 2026-09-14, finding 1). A
|
|
1451
1487
|
// request identity is IMMUTABLE: it names exactly one note on
|
|
@@ -1459,7 +1495,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1459
1495
|
if (earlier === null || earlier.run !== id) {
|
|
1460
1496
|
return refuse(response, who, 409, `this note's request identity was already used on another result (build #${earlier === null ? "?" : earlier.run}) — go back; your words are kept, and the form will carry a new identity`, conflictBack);
|
|
1461
1497
|
}
|
|
1462
|
-
if (earlier.path !== path || earlier.line !== line || earlier.note !== note.note) {
|
|
1498
|
+
if (earlier.path !== path || earlier.line !== line || earlier.note !== note.note || !sameFeedbackImage(earlier.image, image)) {
|
|
1463
1499
|
return refuse(response, who, 409, `this request identity already recorded a different note on this result (${earlier.path === null ? "no file" : `${earlier.path}${earlier.line === null ? "" : `:${earlier.line}`}`}: "${oneLineOf(earlier.note, 80)}") — go back; your edited words are kept as a new note, and the form will carry a new identity`, conflictBack);
|
|
1464
1500
|
}
|
|
1465
1501
|
}
|
package/dist/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FeedbackImage } from "./result-review.js";
|
|
1
2
|
import { type WritesMode } from "./writes-mode.js";
|
|
2
3
|
import { type CheckAccess } from "./check-access.js";
|
|
3
4
|
import { type ProjectSandbox } from "./project-sandbox.js";
|
|
@@ -139,6 +140,18 @@ export declare const V141_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
139
140
|
definition: string;
|
|
140
141
|
saved: string;
|
|
141
142
|
}>>>>;
|
|
143
|
+
/** v142 (youiyoux-intake.ts): youiyoux change requests as tasks. `youiyoux_intake` is one row per project the intake
|
|
144
|
+
* has looked at: a person's on/off choice (`enabled` NULL: automatic, on while the project uses youiyoux) and the last
|
|
145
|
+
* pass's answer (whether the project uses youiyoux, how many request files couldn't be read, and the problem, if any).
|
|
146
|
+
* `youiyoux_request` is one row per request taken, keyed by project and request id: the task it became (NULL: left
|
|
147
|
+
* out, `note` says why), never filed again, the status last written beside it, with the console address its links
|
|
148
|
+
* named ('' for none), and where the person pointed (route, component, file:line), cleaned, for the task page. An
|
|
149
|
+
* options request (`kind`) also keeps the options file its agent writes (`proposal`, p-<its hex>), set when filed and
|
|
150
|
+
* never read from the request again: whether it went to the project's main checkout from which result (`proposal_state`,
|
|
151
|
+
* `proposal_run`, and why not in `proposal_note`; `taken`: a file was already there, so it was left and is never read
|
|
152
|
+
* for a decision), the digest of what was copied apart from what a decision changes (`proposal_digest`), and what a
|
|
153
|
+
* person decided on it in youiyoux's hub (`decided`, `decided_note`: what Toolroll did about it). New tables only. */
|
|
154
|
+
export declare const V142_SCHEMA = "\nCREATE TABLE IF NOT EXISTS youiyoux_intake (\n repo TEXT PRIMARY KEY,\n enabled INTEGER CHECK (enabled IS NULL OR enabled IN (0, 1)),\n uses INTEGER NOT NULL DEFAULT 0 CHECK (uses IN (0, 1)),\n changed_by TEXT,\n changed_at TEXT,\n checked_at TEXT,\n invalid INTEGER NOT NULL DEFAULT 0 CHECK (invalid >= 0),\n problem TEXT\n);\nCREATE TABLE IF NOT EXISTS youiyoux_request (\n repo TEXT NOT NULL,\n request TEXT NOT NULL CHECK (length(request) = 12 AND request GLOB 'r-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'),\n task_id TEXT,\n note TEXT,\n filed_at TEXT NOT NULL,\n status TEXT CHECK (status IS NULL OR status IN ('queued','needs-you','building','ready','done','failed','stopped')),\n detail TEXT,\n status_at TEXT,\n written_at TEXT,\n written_base TEXT,\n write_problem TEXT,\n route TEXT,\n component TEXT,\n source_at TEXT,\n kind TEXT NOT NULL DEFAULT 'change' CHECK (kind IN ('change','options')),\n proposal TEXT CHECK (proposal IS NULL OR (length(proposal) = 12 AND proposal GLOB 'p-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]')),\n proposal_state TEXT CHECK (proposal_state IS NULL OR proposal_state IN ('copied','taken','missing','unsafe','retry')),\n proposal_digest TEXT CHECK (proposal_digest IS NULL OR (length(proposal_digest) = 64 AND proposal_digest NOT GLOB '*[^0-9a-f]*')),\n proposal_run INTEGER,\n proposal_note TEXT,\n proposal_at TEXT,\n decided TEXT CHECK (decided IS NULL OR decided IN ('chosen','dismissed')),\n decided_at TEXT,\n decided_note TEXT,\n PRIMARY KEY (repo, request),\n UNIQUE (task_id),\n CHECK ((kind = 'options') = (proposal IS NOT NULL)),\n CHECK (proposal_state IS NULL OR kind = 'options'),\n CHECK (decided IS NULL OR coalesce(proposal_state = 'copied' AND proposal_digest IS NOT NULL, 0))\n);\n";
|
|
142
155
|
/** A project's "Show commands in the live view" (live-detail.ts): absent means off. */
|
|
143
156
|
export declare const LIVE_DETAIL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS live_detail_setting (\n repo TEXT PRIMARY KEY,\n commands INTEGER NOT NULL CHECK (commands IN (0,1)),\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n-- Every run a steering note reached, kept as it happens (live-notes.ts): a\n-- note's own attached/mailed fields move to the next attempt, this does not.\nCREATE TABLE IF NOT EXISTS steer_note_run (\n note INTEGER NOT NULL REFERENCES task_steer(id) ON DELETE CASCADE,\n run INTEGER NOT NULL REFERENCES run(id) ON DELETE CASCADE,\n how TEXT NOT NULL CHECK (how IN ('attached','mailed','read')),\n at TEXT NOT NULL,\n PRIMARY KEY (note, run, how)\n);\nCREATE INDEX IF NOT EXISTS steer_note_run_by_run ON steer_note_run (run, note);\n";
|
|
144
157
|
export declare const CHAT_APPROVAL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS chat_approval_setting (\n approver TEXT NOT NULL,\n scope TEXT NOT NULL,\n enabled INTEGER NOT NULL CHECK (enabled IN (0,1)),\n full_access INTEGER NOT NULL DEFAULT 0 CHECK (full_access IN (0,1)),\n attempt_cap_microusd INTEGER CHECK (attempt_cap_microusd IS NULL OR attempt_cap_microusd >= 0),\n generation INTEGER NOT NULL,\n digest TEXT NOT NULL,\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (approver, scope)\n);\n";
|
|
@@ -219,7 +232,7 @@ export type SealedLedgerEntry = LedgerEntry & {
|
|
|
219
232
|
hash: string;
|
|
220
233
|
} | null;
|
|
221
234
|
};
|
|
222
|
-
export declare const SCHEMA_VERSION =
|
|
235
|
+
export declare const SCHEMA_VERSION = 143;
|
|
223
236
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
224
237
|
export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
|
|
225
238
|
/**
|
|
@@ -376,6 +389,11 @@ export declare const V140_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
376
389
|
definition: string;
|
|
377
390
|
saved: string;
|
|
378
391
|
}>>>>;
|
|
392
|
+
/** v143: optional screenshot context on result feedback; existing feedback stays unchanged (saved rows read NULL). */
|
|
393
|
+
export declare const V143_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record<string, {
|
|
394
|
+
definition: string;
|
|
395
|
+
saved: string;
|
|
396
|
+
}>>>>;
|
|
379
397
|
/**
|
|
380
398
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
381
399
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -2406,8 +2424,9 @@ export declare class Store {
|
|
|
2406
2424
|
sealScopeApproval(taskId: string, by: string, now: Date, mutation?: Mutation, basis?: {
|
|
2407
2425
|
kind: "mode" | "chat" | "grant" | "schedule" | "auto-check" | "auto-green";
|
|
2408
2426
|
modeDigest: string;
|
|
2409
|
-
/** A chat seal of a deploy gate: the release card it came from (chat-management.md §0a.5).
|
|
2410
|
-
|
|
2427
|
+
/** A chat seal of a deploy gate: the release card it came from (chat-management.md §0a.5). A chat seal of a task
|
|
2428
|
+
* filed from outside text: `terms`, and the digest of every word the card showed (chat-terms.ts). */
|
|
2429
|
+
card?: "release" | "terms";
|
|
2411
2430
|
cardDigest?: string;
|
|
2412
2431
|
}, approverKind?: ApproverKind): boolean;
|
|
2413
2432
|
/** Whether this task's scope carries a LIVE seal: approved, and the
|
|
@@ -3207,6 +3226,7 @@ export declare class Store {
|
|
|
3207
3226
|
author: string;
|
|
3208
3227
|
/** Ingested comments carry their origin id — the idempotency key (M8.17). */
|
|
3209
3228
|
sourceKey?: string;
|
|
3229
|
+
image?: FeedbackImage | null;
|
|
3210
3230
|
}, now: Date): number | null;
|
|
3211
3231
|
/** Live (unsuperseded, unconsumed) comments on a run's terminal diff. */
|
|
3212
3232
|
liveDiffComments(runId: number): DiffComment[];
|
|
@@ -3405,6 +3425,15 @@ export declare class Store {
|
|
|
3405
3425
|
entry: number;
|
|
3406
3426
|
sourceJson: string;
|
|
3407
3427
|
} | null;
|
|
3428
|
+
/** v142: where a task filed from a youiyoux change or options request came from, as kept when it was filed
|
|
3429
|
+
* (youiyoux-intake.ts; a revision reads its first version's): the request, its kind, and where the person pointed. Null for every other task. */
|
|
3430
|
+
youiyouxOriginOf(taskId: string): {
|
|
3431
|
+
request: string;
|
|
3432
|
+
kind: "change" | "options";
|
|
3433
|
+
route: string | null;
|
|
3434
|
+
component: string | null;
|
|
3435
|
+
at: string | null;
|
|
3436
|
+
} | null;
|
|
3408
3437
|
/**
|
|
3409
3438
|
* Seal a revision in ONE transaction (Codex M5-M8 audit, C-3; contract
|
|
3410
3439
|
* handoff task 2): the SOURCE BINDING is proven here, against live rows,
|
|
@@ -7376,6 +7405,7 @@ export type DiffComment = {
|
|
|
7376
7405
|
id: number;
|
|
7377
7406
|
artifact: number;
|
|
7378
7407
|
artifactSha: string;
|
|
7408
|
+
image?: FeedbackImage | null;
|
|
7379
7409
|
run: number;
|
|
7380
7410
|
path: string | null;
|
|
7381
7411
|
line: number | null;
|
|
@@ -8021,3 +8051,16 @@ export declare function sealStoreMoneyMethods(prototype: object): void;
|
|
|
8021
8051
|
* when the prototype's value is the very reference recorded at the seal. `prototype` is Store's own unless a test
|
|
8022
8052
|
* says otherwise; either way nothing but the recorded references is ever installed. */
|
|
8023
8053
|
export declare function lockStoreMoneyMethods(store: object, prototype?: object): void;
|
|
8054
|
+
/** Takes a saved note's screenshot off without losing the note or its history (v143). Fail closed, in one write
|
|
8055
|
+
* transaction: the note must be live and unconsumed on this run, its screenshot must be genuinely unusable right now
|
|
8056
|
+
* (`unusable` is result-actions.ts feedbackImageProblem, re-read inside the transaction, never the page's say-so), and
|
|
8057
|
+
* the change is recorded in the action ledger under the person who made it. The note is superseded by an exact
|
|
8058
|
+
* imageless copy; the original stays in history. The caller has already proved the person may request changes on
|
|
8059
|
+
* this run (the same authority that seals any saved note into a revision); that standing is re-proved here too. */
|
|
8060
|
+
export declare function dropFeedbackImage(store: Store, runId: number, commentId: number, actor: string, unusable: (image: FeedbackImage) => string | null, now: Date): {
|
|
8061
|
+
ok: true;
|
|
8062
|
+
id: number;
|
|
8063
|
+
} | {
|
|
8064
|
+
ok: false;
|
|
8065
|
+
reason: "not-found" | "image-usable" | "not-authorized";
|
|
8066
|
+
};
|