toolroll 0.9.84 → 0.9.86
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 +66 -0
- package/README.md +94 -1193
- package/dist/agent-fence.d.ts +9 -5
- package/dist/agent-fence.js +6 -5
- package/dist/agent-onboard.d.ts +2 -0
- package/dist/agent-onboard.js +3 -0
- package/dist/assignment.js +4 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +33 -31
- package/dist/browser-workspace.d.ts +45 -0
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.d.ts +10 -1
- package/dist/chat-channel.js +36 -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 +6 -4
- 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/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/console-api.d.ts +6 -2
- package/dist/contracts/console-api.js +6 -2
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +51 -0
- package/dist/contracts/lead-tools.js +5 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/contracts/youiyoux-request.d.ts +248 -0
- package/dist/contracts/youiyoux-request.js +152 -0
- package/dist/coordinator.js +1 -0
- package/dist/deploy-gate.js +4 -0
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -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/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +47 -2
- package/dist/flows-ui.js +11 -2
- package/dist/flows.js +5 -2
- 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 +65 -10
- package/dist/lead-voice.js +16 -5
- package/dist/lead.d.ts +2 -0
- package/dist/lead.js +3 -3
- package/dist/operate.d.ts +2 -3
- package/dist/operate.js +226 -21
- 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-delete.js +7 -3
- 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/pull-request-flow.js +35 -4
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +41 -9
- package/dist/result-follow-ups.js +4 -0
- package/dist/result-review.d.ts +9 -0
- package/dist/result-review.js +45 -5
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +7 -0
- package/dist/server/chat.js +4 -2
- package/dist/server/console.css +41 -0
- package/dist/server/flows.js +15 -0
- package/dist/server/pages.js +11 -1
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +15 -2
- package/dist/server/render-pages.js +52 -4
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-tasks.d.ts +9 -0
- package/dist/server/render-tasks.js +32 -11
- package/dist/server/route-table.js +12 -0
- package/dist/server/settings.js +18 -1
- package/dist/server/task-views.js +11 -0
- package/dist/server/tasks.d.ts +10 -0
- package/dist/server/tasks.js +95 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/store.d.ts +116 -5
- package/dist/store.js +385 -24
- package/dist/surface.js +18 -2
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- 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/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- 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 +28 -0
- package/dist/work-index.js +82 -28
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +3 -2
- package/dist/youiyoux-intake.d.ts +234 -0
- package/dist/youiyoux-intake.js +908 -0
- package/package.json +11 -8
|
@@ -127,6 +127,9 @@ get("pages", "projects.page", "/projects", { limited: "unscoped", ...free });
|
|
|
127
127
|
get("pages", "home", "/", { limited: "collection", ...free });
|
|
128
128
|
get("tasks", "inbox", "/inbox", { limited: "collection", ...free });
|
|
129
129
|
get("tasks", "work", "/work", { limited: "collection", ...free });
|
|
130
|
+
// Put away old tasks (task-tidy.md): the preview reads only; putting away is an approver's step-up in a browser session.
|
|
131
|
+
get("tasks", "work.put-away", "/work/put-away", { limited: "collection", ...free, callers: ["cookie"], ...approver("Only an approver can put tasks away.", "/work") });
|
|
132
|
+
post("tasks", "work.put-away.apply", "/work/put-away", { scope: "step-up", limited: "collection", ...approver("Only an approver can put tasks away.", "/work") });
|
|
130
133
|
get("tasks", "next", "/next", { limited: "collection" });
|
|
131
134
|
get("pages", "morning", "/morning");
|
|
132
135
|
get("pages", "workbench", "/workbench", free);
|
|
@@ -270,6 +273,11 @@ for (const one of ["linear-key", "hooks-address", "secrets"])
|
|
|
270
273
|
// "Runs on its own": signing raises authority (the owner's password, a step-up); revoking is one click for any approver.
|
|
271
274
|
post("flows", "flow.unattended", `^\\/flows\\/${FLOW}\\/unattended$`, { scope: "step-up", project: "flow", sample: "/flows/1/unattended", ...EDIT_FLOWS });
|
|
272
275
|
post("flows", "flow.unattended-off", `^\\/flows\\/${FLOW}\\/unattended-off$`, { project: "flow", sample: "/flows/1/unattended-off", ...EDIT_FLOWS });
|
|
276
|
+
// Browser use in a flow's script zones (flow-browser.ts): allowing it widens the sandbox, so a person's own step-up from
|
|
277
|
+
// their browser, carrying the approval digest they read; turning it off lowers authority, one click. Browser only: a
|
|
278
|
+
// lead's or remote run's token never reaches either (the CLI says the same).
|
|
279
|
+
post("flows", "flow.browser", `^\\/flows\\/${FLOW}\\/browser$`, { scope: "step-up", project: "flow", sample: "/flows/1/browser", ...EDIT_FLOWS });
|
|
280
|
+
post("flows", "flow.browser-off", `^\\/flows\\/${FLOW}\\/browser-off$`, { callers: ["cookie"], project: "flow", limited: "resource", sample: "/flows/1/browser-off", ...EDIT_FLOWS });
|
|
273
281
|
post("flows", "flow.triggers", `^\\/flows\\/${FLOW}\\/triggers$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers", ...EDIT_FLOWS });
|
|
274
282
|
for (const one of ["move", "decide", "cancel", "comment", "assign", "watch"])
|
|
275
283
|
post("flows", `flow.card.${one}`, `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/cards/2/${one}`, ...EDIT_FLOWS });
|
|
@@ -336,6 +344,7 @@ for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in"
|
|
|
336
344
|
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
345
|
}
|
|
338
346
|
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
|
|
347
|
+
post("settings", "settings.integrations-youiyoux", "/settings/integrations/youiyoux", { limited: "collection", ...approver("An approver turns youiyoux requests on or off.", "/settings/integrations") });
|
|
339
348
|
const MODELS = approver("Sign in as an approver to change models.", "/settings/models");
|
|
340
349
|
for (const one of ["check", "watch", "update"])
|
|
341
350
|
post("settings", `settings.models-${one}`, `/settings/models/${one}`, MODELS);
|
|
@@ -407,6 +416,9 @@ post("tasks", "task.act.merge", `^\\/t\\/${TASK}\\/merge$`, { project: "task", l
|
|
|
407
416
|
post("tasks", "task.act.confirm-stopped", `^\\/t\\/${TASK}\\/confirm-stopped$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/confirm-stopped", ...approver("Only an approver can confirm a build stopped.") });
|
|
408
417
|
post("tasks", "task.act.stop-carrying", `^\\/t\\/${TASK}\\/stop-carrying$`, { project: "task", limited: "resource", sample: "/t/one/stop-carrying", ...approver("Only an approver can stop carrying a release.") });
|
|
409
418
|
post("tasks", "task.act.stop", `^\\/t\\/${TASK}\\/stop$`, { project: "task", limited: "resource", sample: "/t/one/stop" });
|
|
419
|
+
// Putting one task away and bringing it back (task-tidy.md): a person's act, so only a browser session.
|
|
420
|
+
post("tasks", "task.act.archive", `^\\/t\\/${TASK}\\/archive$`, { project: "task", limited: "resource", sample: "/t/one/archive", callers: ["cookie"], ...approver("Only an approver can put a task away.") });
|
|
421
|
+
post("tasks", "task.act.restore", `^\\/t\\/${TASK}\\/restore$`, { project: "task", limited: "resource", sample: "/t/one/restore", callers: ["cookie"], ...approver("Only an approver can restore a task.") });
|
|
410
422
|
post("tasks", "task.act.pause", `^\\/t\\/${TASK}\\/pause$`, { project: "task", limited: "resource", sample: "/t/one/pause" });
|
|
411
423
|
post("tasks", "task.act.resume-arm", `^\\/t\\/${TASK}\\/resume-arm$`, { project: "task", limited: "resource", sample: "/t/one/resume-arm" });
|
|
412
424
|
post("tasks", "task.act.resume", `^\\/t\\/${TASK}\\/resume$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/resume" });
|
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";
|
|
@@ -19,6 +21,7 @@ import { newestPullRequestOf, publishingOf, pullRequestBlocker } from '../pull-r
|
|
|
19
21
|
import { structuredHandoffView, terminalDiffView } from "../result-evidence-readers.js";
|
|
20
22
|
import { resultCheckLevel } from '../result-follow-ups.js';
|
|
21
23
|
import { skillsSnapshotHtml, skillTestFeedbackHtml } from "../skills-ui.js";
|
|
24
|
+
import { archiveOf } from "../store.js";
|
|
22
25
|
import { runActivityOf } from "../task-activity.js";
|
|
23
26
|
import { taskFingerprint } from "../task-live.js";
|
|
24
27
|
import { pullRequestFactOf } from '../task-status.js';
|
|
@@ -229,6 +232,7 @@ export function createTaskViews(runtime) {
|
|
|
229
232
|
line: one.line === null || one.line === undefined ? null : Number(one.line),
|
|
230
233
|
note: String(one.note ?? ""),
|
|
231
234
|
author: String(one.author ?? "?"),
|
|
235
|
+
image: parseFeedbackImage(one.image) || null,
|
|
232
236
|
})),
|
|
233
237
|
};
|
|
234
238
|
}
|
|
@@ -789,6 +793,10 @@ export function createTaskViews(runtime) {
|
|
|
789
793
|
const presentedData = { ...data, rootId: family?.root.id ?? taskId, rootTitle: family?.root.title ?? data.task.title,
|
|
790
794
|
...(family === null || data.assignment != null ? {} : { history: familyHistory(family) }),
|
|
791
795
|
versionLabel: family !== null && family.current.id !== taskId ? `Viewing ${family.versions.findIndex(one => one.id === taskId) === 0 ? "Original" : `Revision ${family.versions.findIndex(one => one.id === taskId)}`} · ${family.current.state === "running" ? "A newer revision is running" : "A newer revision is current"}` : null };
|
|
796
|
+
// Put away (v145, task-tidy.md): who or what put it away and why, and whether this person can put it away or restore it.
|
|
797
|
+
const archived = archiveOf(runtime.store, taskId);
|
|
798
|
+
presentedData.archived = archived;
|
|
799
|
+
presentedData.canPutAway = who.via === "cookie" && who.role === "approver";
|
|
792
800
|
if (scopeDraft !== undefined)
|
|
793
801
|
presentedData.scopeDraft = scopeDraft;
|
|
794
802
|
if (cancelDraft !== undefined)
|
|
@@ -1119,6 +1127,9 @@ export function createTaskViews(runtime) {
|
|
|
1119
1127
|
outsideTouches: files.filter(one => one.outsideTouches).map(one => one.path),
|
|
1120
1128
|
fileAnchors,
|
|
1121
1129
|
comments: runtime.store.liveDiffComments(run.id).filter(isRevisionFeedback),
|
|
1130
|
+
// The same check that refuses a saved note's screenshot at revision time decides when it can be removed.
|
|
1131
|
+
unusableImageNotes: runtime.store.liveDiffComments(run.id).filter(one => isRevisionFeedback(one) && one.image != null
|
|
1132
|
+
&& feedbackImageProblem(runtime.store, runtime.evidenceRoot, run.id, one.image) !== null).map(one => one.id),
|
|
1122
1133
|
pastComments: runtime.store.allDiffComments(run.id).filter(one => one.reviewerRun === null && (one.consumedBy !== null || one.supersededBy !== null)),
|
|
1123
1134
|
reviewerFindings: runtime.store.allDiffComments(run.id).filter(one => one.reviewerRun !== null),
|
|
1124
1135
|
automaticReview: buildReviewOf(runtime.store, run.id),
|
package/dist/server/tasks.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type Html } from "../html.js";
|
|
3
3
|
import { type ServerResponse } from "node:http";
|
|
4
4
|
import { type Artifact, type Run, type Store } from "../store.js";
|
|
5
|
+
import type { HandlerContext } from './handler-context.js';
|
|
5
6
|
import type { ServerRuntime } from './runtime.js';
|
|
6
7
|
import { type PeekAdmission } from "./render-pages.js";
|
|
7
8
|
import { type CompletedWorkRow, type RankedReviewRow, type ReviewCockpitView } from "./render-results.js";
|
|
@@ -36,3 +37,12 @@ export declare function createTasksHandlers(runtime: ServerRuntime): {
|
|
|
36
37
|
};
|
|
37
38
|
liveRunIds: (rows: readonly (Pick<Run, "id" | "outcome" | "leaseId" | "taskRef">)[]) => Set<number>;
|
|
38
39
|
};
|
|
40
|
+
/** Put away and Restore one task, and Put away old tasks (docs/plans/task-tidy.md): the preview reads only (GET-quiet);
|
|
41
|
+
* putting away many is an approver's step-up bound to the digest of exactly what the preview showed. Each write
|
|
42
|
+
* re-proves the person's standing inside its own transaction. */
|
|
43
|
+
export declare function createTidyHandlers(runtime: ServerRuntime): {
|
|
44
|
+
"work.put-away": (ctx: HandlerContext) => Promise<void>;
|
|
45
|
+
"work.put-away.apply": (ctx: HandlerContext) => Promise<void>;
|
|
46
|
+
"task.act.archive": (ctx: HandlerContext) => Promise<void>;
|
|
47
|
+
"task.act.restore": (ctx: HandlerContext) => Promise<void>;
|
|
48
|
+
};
|
package/dist/server/tasks.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseFeedbackImage, sameFeedbackImage } from "../result-review.js";
|
|
2
|
+
import { archiveTaskFamily, dropFeedbackImage, releaseAuthorityOfTask, restoreTaskFamily, revokeRelease, runLiveCommands } from "../store.js";
|
|
3
|
+
import { applyTidy, approverStandsNow, familyRepoOf, releaseFactsOf, tidyDays, tidyDigest, tidyPreview, TIDY_DEFAULT_DAYS } from "../tidy.js";
|
|
2
4
|
import { steerCapabilityOf } from "../steer-capabilities.js";
|
|
3
5
|
import { canReveal, localCheckoutOf, revealInFinder } from "../checkout-proof.js";
|
|
4
6
|
/** A form's way back to a flow card ("flow:<flow>:<card>"), read strictly: numbers only, never a path. */
|
|
@@ -48,7 +50,7 @@ import { fileTaskProposal } from "../proposal.js";
|
|
|
48
50
|
import { isProviderId, PROVIDER_IDS, validateSpec } from "../provider.js";
|
|
49
51
|
import { completeAndOpenPullRequest, mergeAsPerson } from '../pull-request-flow.js';
|
|
50
52
|
import { isQualityMode } from "../quality.js";
|
|
51
|
-
import { createResultRevision, requestResultChanges } from "../result-actions.js";
|
|
53
|
+
import { createResultRevision, requestResultChanges, feedbackImageProblem } from "../result-actions.js";
|
|
52
54
|
import { structuredHandoffView, terminalDiffView } from "../result-evidence-readers.js";
|
|
53
55
|
import { fileAddTestsTask, requestFollowUpChecks } from '../result-follow-ups.js';
|
|
54
56
|
import { commentSourceKey, isRevisionFeedback, parseResultTab, resultReturnTarget } from "../result-review.js";
|
|
@@ -66,7 +68,7 @@ import { buildProgressOf, parseWorkView } from "../workspace-ui.js";
|
|
|
66
68
|
import { requestContext } from "./request-context.js";
|
|
67
69
|
import { boardTickScript, oneLineOf, QUEUE_VIEW, refuse, regionScript, screen, transcriptScript, whenTime } from "./chrome.js";
|
|
68
70
|
import { chatReturnWithSaid, matchTaskPath, redirect, respond, reviewHref, safeChatReturn, safeReturn, SAFETY, taskChatHref, taskHref } from "./http.js";
|
|
69
|
-
import { boardBody, boardWorkers, donePage, inboxFingerprints, inboxPage, newTaskPage, nextPage, parseInboxTab, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, workPage } from "./render-pages.js";
|
|
71
|
+
import { putAwayPage, boardBody, boardWorkers, donePage, inboxFingerprints, inboxPage, newTaskPage, nextPage, parseInboxTab, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, workPage } from "./render-pages.js";
|
|
70
72
|
import { runEfficiencyOf } from "../run-efficiency.js";
|
|
71
73
|
import { timingFor } from "../latency-timeline.js";
|
|
72
74
|
import { ACCEPT_ANYWAY_NEEDS_REASON, editorFileHref, proofBundleView, rankReviewQueue, RESULT_REFUSALS, REVIEW_QUEUE_CAP, reviewCockpitDetailParts, reviewCockpitPage, reviewPriorityOf, runFactsFragment, runOutcomeBadge, runPage, RUNS_PAGE, runsPage, withRefusal } from "./render-results.js";
|
|
@@ -277,6 +279,7 @@ export function createTasksHandlers(runtime) {
|
|
|
277
279
|
multiProject: new Set(work.items.map(task => task.repo ?? "")).size > 1,
|
|
278
280
|
now,
|
|
279
281
|
state,
|
|
282
|
+
canPutAway: who.via === "cookie" && who.role === "approver",
|
|
280
283
|
// v105: subscription windows and monthly budgets, for whoever runs the installation.
|
|
281
284
|
limits: store.isInstanceOperator(who.name)
|
|
282
285
|
? limitsView(store.providerLimits(), store.budgets().length === 0 ? [] : store.monthSpendCached(now).budgets, { project: projectName, subagent: id => subagentNamesOf(store.handle).get(id) ?? `Subagent ${id}` }, now)
|
|
@@ -1402,15 +1405,40 @@ export function createTasksHandlers(runtime) {
|
|
|
1402
1405
|
const viewParam = returnTo.startsWith("/t/") ? "section" : "tab";
|
|
1403
1406
|
const back = selectedTab === "summary" ? returnTo
|
|
1404
1407
|
: `${returnTo.replace(/§ion=(?:summary|changes|checks)$/, "")}${returnTo.includes("?") ? "&" : "?"}${viewParam}=${selectedTab}`;
|
|
1408
|
+
if (body.get("intent") === "drop-image") {
|
|
1409
|
+
// A saved note whose screenshot is gone or changed would block every revision that seals it. Its words stay:
|
|
1410
|
+
// the note is superseded by an exact imageless copy, and the original remains in history.
|
|
1411
|
+
// Who: a signed-in browser approver admitted to this run's project (checked above) — the same authority that
|
|
1412
|
+
// seals any saved note into a revision. What: only a screenshot that is unusable right now, re-proved inside
|
|
1413
|
+
// dropFeedbackImage's transaction, and recorded in the ledger under this person.
|
|
1414
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
1415
|
+
return refuse(response, who, 403, "Only a signed-in approver can change saved feedback.", back);
|
|
1416
|
+
const comment = Number(body.get("comment") ?? "");
|
|
1417
|
+
if (!Number.isSafeInteger(comment) || comment < 1)
|
|
1418
|
+
return refuse(response, who, 400, "Choose a saved note.", back);
|
|
1419
|
+
const dropped = dropFeedbackImage(store, id, comment, who.name, one => feedbackImageProblem(store, evidenceRoot, id, one), now);
|
|
1420
|
+
if (!dropped.ok && dropped.reason === "not-authorized")
|
|
1421
|
+
return refuse(response, who, 403, "Only a signed-in approver on this project can change saved feedback.", back);
|
|
1422
|
+
if (!dropped.ok)
|
|
1423
|
+
return refuse(response, who, 409, dropped.reason === "image-usable"
|
|
1424
|
+
? "That screenshot still verifies, so the note keeps it. Reload the result." : "That saved note changed or was already used. Reload the result.", back);
|
|
1425
|
+
return redirect(response, `${back}#request-changes`);
|
|
1426
|
+
}
|
|
1427
|
+
const image = parseFeedbackImage(body.get("image"));
|
|
1428
|
+
if (image === false)
|
|
1429
|
+
return refuse(response, who, 400, "Choose a screenshot from this result again. Your draft is unchanged.", back);
|
|
1405
1430
|
if (body.get("intent") === "revise") {
|
|
1406
1431
|
const result = requestResultChanges(store, evidenceRoot, {
|
|
1407
1432
|
run: id, batch: body.get("batch"), source: body.get("source"), actor: who.name,
|
|
1408
1433
|
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"),
|
|
1434
|
+
note: body.get("note") ?? "", path: body.get("path") ?? "", line: body.get("line") ?? "", request: body.get("request"), image,
|
|
1410
1435
|
}, now);
|
|
1411
1436
|
if (!result.ok) {
|
|
1412
1437
|
const request = body.get("request") ?? "";
|
|
1413
|
-
|
|
1438
|
+
// The queue's link can stop resolving when a newer attempt finishes. The exact
|
|
1439
|
+
// run record still hosts this account/run's recoverable feedback draft.
|
|
1440
|
+
const retryBase = image === null ? back : `/r/${id}?record=1`;
|
|
1441
|
+
const retryBack = /^[a-f0-9]{32}$/.test(request) ? `${retryBase}${retryBase.includes("?") ? "&" : "?"}conflict=${request}#request-changes` : retryBase;
|
|
1414
1442
|
return refuse(response, who, result.status, result.message, retryBack);
|
|
1415
1443
|
}
|
|
1416
1444
|
return redirect(response, revisionDestination(result.id, returnTo));
|
|
@@ -1445,7 +1473,17 @@ export function createTasksHandlers(runtime) {
|
|
|
1445
1473
|
const request = body.get("request");
|
|
1446
1474
|
const sourceKey = commentSourceKey(who.name, request);
|
|
1447
1475
|
const path = rawPath === "" ? null : rawPath;
|
|
1448
|
-
|
|
1476
|
+
// Freshness, custody and the insert in one write transaction: a newer attempt finishing in between can't leave a
|
|
1477
|
+
// stale screenshot attached (check and write together).
|
|
1478
|
+
const saved = store.transact(() => {
|
|
1479
|
+
const imageProblem = feedbackImageProblem(store, evidenceRoot, id, image);
|
|
1480
|
+
if (imageProblem !== null)
|
|
1481
|
+
return { problem: imageProblem };
|
|
1482
|
+
return { inserted: store.addDiffComment({ artifactId: terminal.id, runId: id, path, line, note: note.note, author: who.name, image, ...(sourceKey === undefined ? {} : { sourceKey }) }, now) };
|
|
1483
|
+
});
|
|
1484
|
+
if ("problem" in saved)
|
|
1485
|
+
return refuse(response, who, 409, saved.problem, `/r/${id}?record=1#request-changes`);
|
|
1486
|
+
const inserted = saved.inserted;
|
|
1449
1487
|
if (inserted === null && sourceKey !== undefined) {
|
|
1450
1488
|
// The token was used before (repair 2026-09-14, finding 1). A
|
|
1451
1489
|
// request identity is IMMUTABLE: it names exactly one note on
|
|
@@ -1459,7 +1497,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1459
1497
|
if (earlier === null || earlier.run !== id) {
|
|
1460
1498
|
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
1499
|
}
|
|
1462
|
-
if (earlier.path !== path || earlier.line !== line || earlier.note !== note.note) {
|
|
1500
|
+
if (earlier.path !== path || earlier.line !== line || earlier.note !== note.note || !sameFeedbackImage(earlier.image, image)) {
|
|
1463
1501
|
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
1502
|
}
|
|
1465
1503
|
}
|
|
@@ -3021,7 +3059,9 @@ export function createTasksHandlers(runtime) {
|
|
|
3021
3059
|
live.add(run.id);
|
|
3022
3060
|
return live;
|
|
3023
3061
|
}
|
|
3062
|
+
const tidyHandlers = createTidyHandlers(runtime);
|
|
3024
3063
|
const registrations = handlersOf("tasks", {
|
|
3064
|
+
...tidyHandlers,
|
|
3025
3065
|
"code.page": showCode("workspace"),
|
|
3026
3066
|
"code.session": showCode("session"),
|
|
3027
3067
|
"code.state": showCode("state"),
|
|
@@ -3118,3 +3158,51 @@ export function createTasksHandlers(runtime) {
|
|
|
3118
3158
|
}
|
|
3119
3159
|
return { registrations, runListPane, peekFragment, taskMutation, queueRegionFor, ciFailingFor, completedRowFor, attemptRowFor, resultRowOf, reviewCockpitViewOf, decisionEvidence, runEvidence, sendArtifact, peekGuards, liveRunIds };
|
|
3120
3160
|
}
|
|
3161
|
+
/** Put away and Restore one task, and Put away old tasks (docs/plans/task-tidy.md): the preview reads only (GET-quiet);
|
|
3162
|
+
* putting away many is an approver's step-up bound to the digest of exactly what the preview showed. Each write
|
|
3163
|
+
* re-proves the person's standing inside its own transaction. */
|
|
3164
|
+
export function createTidyHandlers(runtime) {
|
|
3165
|
+
const { store, sendScreen, chromeFor, authenticateApprover, workAccess, bustBadge } = runtime;
|
|
3166
|
+
async function showPutAway(ctx) {
|
|
3167
|
+
const { url, who, response, now, project } = ctx;
|
|
3168
|
+
const days = tidyDays(url.searchParams.get("days"));
|
|
3169
|
+
if (days === null)
|
|
3170
|
+
return refuse(response, who, 400, "Choose a number of days from 1 to 365.", "/work/put-away");
|
|
3171
|
+
const releases = releaseFactsOf(store);
|
|
3172
|
+
const items = tidyPreview(store, now, { releases, days, access: workAccess() });
|
|
3173
|
+
return sendScreen(response, 200, putAwayPage(chromeFor(project, "work"), { items, days, digest: tidyDigest(items, days), now,
|
|
3174
|
+
problem: url.searchParams.get("problem"), releasesSkipped: releases.problem }));
|
|
3175
|
+
}
|
|
3176
|
+
async function applyPutAway(ctx) {
|
|
3177
|
+
const { who, response, now, posted } = ctx;
|
|
3178
|
+
const body = readForm(posted, CONSOLE_FORMS.tasksPutAway);
|
|
3179
|
+
const days = tidyDays(body.get("days"));
|
|
3180
|
+
const back = (problem) => redirect(response, `/work/put-away?${new URLSearchParams({ ...(days === null || days === TIDY_DEFAULT_DAYS ? {} : { days: String(days) }), problem })}`);
|
|
3181
|
+
if (days === null)
|
|
3182
|
+
return back("Choose a number of days from 1 to 365.");
|
|
3183
|
+
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
3184
|
+
return back("Enter your Toolroll password to put these away.");
|
|
3185
|
+
const done = applyTidy(store, now, { by: who.name, days, digest: body.get("digest") ?? "", only: body.getAll("task"), releases: releaseFactsOf(store), access: workAccess() });
|
|
3186
|
+
if (!done.ok)
|
|
3187
|
+
return back(done.message);
|
|
3188
|
+
bustBadge();
|
|
3189
|
+
return redirect(response, "/work?view=archived");
|
|
3190
|
+
}
|
|
3191
|
+
const oneTask = (kind) => async (ctx) => {
|
|
3192
|
+
const { url, who, response, now, posted } = ctx;
|
|
3193
|
+
const act = matchTaskPath(url.pathname, `/${kind}$`);
|
|
3194
|
+
if (act === null)
|
|
3195
|
+
return refuse(response, who, 404, "There's no page at this address.", "/work");
|
|
3196
|
+
const body = readForm(posted, kind === "archive" ? CONSOLE_FORMS.taskArchive : CONSOLE_FORMS.taskRestore);
|
|
3197
|
+
const standing = (refs) => approverStandsNow(store, who.name, familyRepoOf(store, refs));
|
|
3198
|
+
const done = kind === "archive"
|
|
3199
|
+
? archiveTaskFamily(store, act.taskId, { by: who.name, byKind: "person", rule: null, reason: (body.get("reason") ?? "").trim() || "Put away by hand" }, now, { guard: standing })
|
|
3200
|
+
: restoreTaskFamily(store, act.taskId, who.name, now, standing);
|
|
3201
|
+
if (!done.ok)
|
|
3202
|
+
return refuse(response, who, done.reason === "unknown-task" ? 404 : 409, done.message, taskHref(act.taskId));
|
|
3203
|
+
bustBadge();
|
|
3204
|
+
const to = safeReturn(body.get("return"));
|
|
3205
|
+
return redirect(response, to === "/" ? taskHref(act.taskId) : to);
|
|
3206
|
+
};
|
|
3207
|
+
return { "work.put-away": showPutAway, "work.put-away.apply": applyPutAway, "task.act.archive": oneTask("archive"), "task.act.restore": oneTask("restore") };
|
|
3208
|
+
}
|
package/dist/settings-proofs.js
CHANGED
|
@@ -56,6 +56,7 @@ export const PROOF_INVENTORY = [
|
|
|
56
56
|
{ id: "updates", action: "Update Toolroll", routes: ["/settings/updates", "/settings/updates/seen", "/settings/updates/cancel"], form: "updates", field: "password", proof: "step-up", ...ACCEPT, role: "operator", secrets: [], terms: [] },
|
|
57
57
|
{ id: "storage-cleanup", action: "Choose when checkouts are removed", routes: ["/settings/storage"], form: "storage", field: "password", proof: "step-up", ...ACCEPT, role: "operator", secrets: [], terms: [] },
|
|
58
58
|
{ id: "storage-clean", action: "Clean up checkouts", routes: ["/settings/storage/clean"], form: "storage", field: "password", proof: "step-up", ...ACCEPT, role: "operator", secrets: [], terms: ["preview"], note: "`preview` is the digest of what the page showed; a changed list is refused" },
|
|
59
|
+
{ id: "tasks-put-away", action: "Put away old tasks", routes: ["/work/put-away"], form: "tasksPutAway", field: "password", proof: "step-up", ...ACCEPT, role: "approver", secrets: [], terms: ["digest", "days", "task"], note: "`digest` is the preview the page showed; a changed list, or a ticked task outside it, is refused" },
|
|
59
60
|
{ id: "storage-discard", action: "Discard a checkout", routes: ["/settings/storage/discard"], form: "storage", field: "password", proof: "step-up", ...ACCEPT, role: "operator", secrets: [], terms: ["path"] },
|
|
60
61
|
{ id: "project-live-view", action: "Show a project's commands in the live view", routes: ["/settings/project/live-view"], form: "projectLiveView", field: "password", proof: "step-up", ...ACCEPT, role: "any", handlerRole: "an approver of the project", secrets: [], terms: ["repo", "nonce"] },
|
|
61
62
|
{ id: "project-delete", action: "Delete a project", routes: ["/settings/project/delete"], form: "projectDelete", field: "password", proof: "step-up", ...ACCEPT, role: "operator", secrets: [], terms: ["repo", "step"], note: "the project's name typed too" },
|
package/dist/store.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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";
|
|
4
5
|
import type { FencePolicy } from "./agent-fence.js";
|
|
5
6
|
import { type NetworkProfile, type ProjectNetwork, type RunNetworkRecord } from "./egress.js";
|
|
6
7
|
import { type AcpLaunch } from "./acp-attest.js";
|
|
8
|
+
export { refArchived, taskArchived } from "./task-archive.js";
|
|
7
9
|
import { type ChatBinding } from "./chat-core.js";
|
|
8
10
|
import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET } from "./chat-messages.js";
|
|
9
11
|
import { isLifecycleNotification, isTelegramProgressNotification, LIFECYCLE_KEY_PREFIX, LIFECYCLE_KINDS, proposalTaskOf, type LifecycleKind } from "./notification-rows.js";
|
|
@@ -139,6 +141,18 @@ export declare const V141_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
139
141
|
definition: string;
|
|
140
142
|
saved: string;
|
|
141
143
|
}>>>>;
|
|
144
|
+
/** v142 (youiyoux-intake.ts): youiyoux change requests as tasks. `youiyoux_intake` is one row per project the intake
|
|
145
|
+
* has looked at: a person's on/off choice (`enabled` NULL: automatic, on while the project uses youiyoux) and the last
|
|
146
|
+
* pass's answer (whether the project uses youiyoux, how many request files couldn't be read, and the problem, if any).
|
|
147
|
+
* `youiyoux_request` is one row per request taken, keyed by project and request id: the task it became (NULL: left
|
|
148
|
+
* out, `note` says why), never filed again, the status last written beside it, with the console address its links
|
|
149
|
+
* named ('' for none), and where the person pointed (route, component, file:line), cleaned, for the task page. An
|
|
150
|
+
* options request (`kind`) also keeps the options file its agent writes (`proposal`, p-<its hex>), set when filed and
|
|
151
|
+
* never read from the request again: whether it went to the project's main checkout from which result (`proposal_state`,
|
|
152
|
+
* `proposal_run`, and why not in `proposal_note`; `taken`: a file was already there, so it was left and is never read
|
|
153
|
+
* for a decision), the digest of what was copied apart from what a decision changes (`proposal_digest`), and what a
|
|
154
|
+
* person decided on it in youiyoux's hub (`decided`, `decided_note`: what Toolroll did about it). New tables only. */
|
|
155
|
+
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
156
|
/** A project's "Show commands in the live view" (live-detail.ts): absent means off. */
|
|
143
157
|
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
158
|
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 +233,7 @@ export type SealedLedgerEntry = LedgerEntry & {
|
|
|
219
233
|
hash: string;
|
|
220
234
|
} | null;
|
|
221
235
|
};
|
|
222
|
-
export declare const SCHEMA_VERSION =
|
|
236
|
+
export declare const SCHEMA_VERSION = 145;
|
|
223
237
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
224
238
|
export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
|
|
225
239
|
/**
|
|
@@ -376,6 +390,21 @@ export declare const V140_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
376
390
|
definition: string;
|
|
377
391
|
saved: string;
|
|
378
392
|
}>>>>;
|
|
393
|
+
/** v143: optional screenshot context on result feedback; existing feedback stays unchanged (saved rows read NULL). */
|
|
394
|
+
export declare const V143_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record<string, {
|
|
395
|
+
definition: string;
|
|
396
|
+
saved: string;
|
|
397
|
+
}>>>>;
|
|
398
|
+
/** v145 (docs/plans/task-tidy.md): `task_archive` is a put-away task version. A row hides it from every list and count
|
|
399
|
+
* and refuses every door that would dispatch, claim or approve it; restore deletes the row. Its history, results,
|
|
400
|
+
* approvals and ledger stay exactly as they were. `merge_in_flight` is a direct merge between its last check and GitHub's
|
|
401
|
+
* answer, owned by its token: a task can't be put away under it (a row older than 15 minutes is a crash, and no
|
|
402
|
+
* longer counts). `deploy_in_flight` is a deployment whose candidate passed its gate (30 minutes, refreshed by every
|
|
403
|
+
* phase that proves it again). `flow_step_task` is the tasks a flow step was admitted for, kept whatever the card does
|
|
404
|
+
* next, so a step still running holds them. `branch_check_run` keeps each completed GitHub workflow run (pushed to
|
|
405
|
+
* the watched branch of the watched repository) the "checks failed" trigger saw, green or red, so tidy can tell a
|
|
406
|
+
* failure notice was fixed by a later commit. */
|
|
407
|
+
export declare const V145_SCHEMA = "\nCREATE TABLE IF NOT EXISTS task_archive (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n by TEXT NOT NULL,\n by_kind TEXT NOT NULL CHECK (by_kind IN ('person','tidy')),\n rule TEXT,\n reason TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS branch_check_run (\n repo TEXT NOT NULL,\n run_id INTEGER NOT NULL,\n branch TEXT NOT NULL,\n workflow TEXT NOT NULL,\n head_sha TEXT NOT NULL,\n conclusion TEXT NOT NULL CHECK (conclusion IN ('success','failure')),\n run_created_at TEXT NOT NULL,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (repo, run_id)\n);\nCREATE INDEX IF NOT EXISTS branch_check_run_by_workflow ON branch_check_run (repo, branch, workflow, run_created_at);\nCREATE TABLE IF NOT EXISTS merge_in_flight (\n publication INTEGER PRIMARY KEY REFERENCES publication(id),\n task_ref INTEGER NOT NULL REFERENCES task_ref(id),\n token TEXT NOT NULL,\n by TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS deploy_in_flight (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n run INTEGER NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS flow_step_task (\n card INTEGER NOT NULL,\n entry INTEGER NOT NULL,\n task_id TEXT NOT NULL,\n PRIMARY KEY (card, entry, task_id)\n);\n";
|
|
379
408
|
/**
|
|
380
409
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
381
410
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -2406,8 +2435,9 @@ export declare class Store {
|
|
|
2406
2435
|
sealScopeApproval(taskId: string, by: string, now: Date, mutation?: Mutation, basis?: {
|
|
2407
2436
|
kind: "mode" | "chat" | "grant" | "schedule" | "auto-check" | "auto-green";
|
|
2408
2437
|
modeDigest: string;
|
|
2409
|
-
/** A chat seal of a deploy gate: the release card it came from (chat-management.md §0a.5).
|
|
2410
|
-
|
|
2438
|
+
/** A chat seal of a deploy gate: the release card it came from (chat-management.md §0a.5). A chat seal of a task
|
|
2439
|
+
* filed from outside text: `terms`, and the digest of every word the card showed (chat-terms.ts). */
|
|
2440
|
+
card?: "release" | "terms";
|
|
2411
2441
|
cardDigest?: string;
|
|
2412
2442
|
}, approverKind?: ApproverKind): boolean;
|
|
2413
2443
|
/** Whether this task's scope carries a LIVE seal: approved, and the
|
|
@@ -3207,6 +3237,7 @@ export declare class Store {
|
|
|
3207
3237
|
author: string;
|
|
3208
3238
|
/** Ingested comments carry their origin id — the idempotency key (M8.17). */
|
|
3209
3239
|
sourceKey?: string;
|
|
3240
|
+
image?: FeedbackImage | null;
|
|
3210
3241
|
}, now: Date): number | null;
|
|
3211
3242
|
/** Live (unsuperseded, unconsumed) comments on a run's terminal diff. */
|
|
3212
3243
|
liveDiffComments(runId: number): DiffComment[];
|
|
@@ -3405,6 +3436,15 @@ export declare class Store {
|
|
|
3405
3436
|
entry: number;
|
|
3406
3437
|
sourceJson: string;
|
|
3407
3438
|
} | null;
|
|
3439
|
+
/** v142: where a task filed from a youiyoux change or options request came from, as kept when it was filed
|
|
3440
|
+
* (youiyoux-intake.ts; a revision reads its first version's): the request, its kind, and where the person pointed. Null for every other task. */
|
|
3441
|
+
youiyouxOriginOf(taskId: string): {
|
|
3442
|
+
request: string;
|
|
3443
|
+
kind: "change" | "options";
|
|
3444
|
+
route: string | null;
|
|
3445
|
+
component: string | null;
|
|
3446
|
+
at: string | null;
|
|
3447
|
+
} | null;
|
|
3408
3448
|
/**
|
|
3409
3449
|
* Seal a revision in ONE transaction (Codex M5-M8 audit, C-3; contract
|
|
3410
3450
|
* handoff task 2): the SOURCE BINDING is proven here, against live rows,
|
|
@@ -3716,7 +3756,7 @@ export declare class Store {
|
|
|
3716
3756
|
rejectedRun: number | null;
|
|
3717
3757
|
} | {
|
|
3718
3758
|
ok: false;
|
|
3719
|
-
reason: "unknown-task" | "not-stalled" | "claimed" | "accepted-result" | "published" | "moved";
|
|
3759
|
+
reason: "unknown-task" | "not-stalled" | "claimed" | "accepted-result" | "published" | "moved" | "archived";
|
|
3720
3760
|
};
|
|
3721
3761
|
/**
|
|
3722
3762
|
* Cancel, aware of who might be building right now. A live claim wins:
|
|
@@ -6863,7 +6903,7 @@ export declare class Store {
|
|
|
6863
6903
|
stop: RunStop;
|
|
6864
6904
|
} | {
|
|
6865
6905
|
ok: false;
|
|
6866
|
-
reason: "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review";
|
|
6906
|
+
reason: "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review" | "archived";
|
|
6867
6907
|
detail: string;
|
|
6868
6908
|
};
|
|
6869
6909
|
/** Every stop recorded on a task, newest request first. */
|
|
@@ -7376,6 +7416,7 @@ export type DiffComment = {
|
|
|
7376
7416
|
id: number;
|
|
7377
7417
|
artifact: number;
|
|
7378
7418
|
artifactSha: string;
|
|
7419
|
+
image?: FeedbackImage | null;
|
|
7379
7420
|
run: number;
|
|
7380
7421
|
path: string | null;
|
|
7381
7422
|
line: number | null;
|
|
@@ -8021,3 +8062,73 @@ export declare function sealStoreMoneyMethods(prototype: object): void;
|
|
|
8021
8062
|
* when the prototype's value is the very reference recorded at the seal. `prototype` is Store's own unless a test
|
|
8022
8063
|
* says otherwise; either way nothing but the recorded references is ever installed. */
|
|
8023
8064
|
export declare function lockStoreMoneyMethods(store: object, prototype?: object): void;
|
|
8065
|
+
/** Takes a saved note's screenshot off without losing the note or its history (v143). Fail closed, in one write
|
|
8066
|
+
* transaction: the note must be live and unconsumed on this run, its screenshot must be genuinely unusable right now
|
|
8067
|
+
* (`unusable` is result-actions.ts feedbackImageProblem, re-read inside the transaction, never the page's say-so), and
|
|
8068
|
+
* the change is recorded in the action ledger under the person who made it. The note is superseded by an exact
|
|
8069
|
+
* imageless copy; the original stays in history. The caller has already proved the person may request changes on
|
|
8070
|
+
* this run (the same authority that seals any saved note into a revision); that standing is re-proved here too. */
|
|
8071
|
+
export declare function dropFeedbackImage(store: Store, runId: number, commentId: number, actor: string, unusable: (image: FeedbackImage) => string | null, now: Date): {
|
|
8072
|
+
ok: true;
|
|
8073
|
+
id: number;
|
|
8074
|
+
} | {
|
|
8075
|
+
ok: false;
|
|
8076
|
+
reason: "not-found" | "image-usable" | "not-authorized";
|
|
8077
|
+
};
|
|
8078
|
+
/** A put-away task version (v145, docs/plans/task-tidy.md). */
|
|
8079
|
+
export type TaskArchive = {
|
|
8080
|
+
taskRef: number;
|
|
8081
|
+
taskId: string;
|
|
8082
|
+
by: string;
|
|
8083
|
+
byKind: "person" | "tidy";
|
|
8084
|
+
rule: string | null;
|
|
8085
|
+
reason: string;
|
|
8086
|
+
at: string;
|
|
8087
|
+
};
|
|
8088
|
+
/** Who put a task away, and why: a person by name, or tidy by its rule. */
|
|
8089
|
+
export type ArchiveBy = {
|
|
8090
|
+
by: string;
|
|
8091
|
+
byKind: "person" | "tidy";
|
|
8092
|
+
rule: string | null;
|
|
8093
|
+
reason: string;
|
|
8094
|
+
};
|
|
8095
|
+
export type ArchiveRefusal = "unknown-task" | "live" | "archived" | "not-archived" | "blocked";
|
|
8096
|
+
/** The put-away record of this task, or null when it is not put away. */
|
|
8097
|
+
export declare function archiveOf(store: Store, taskId: string): TaskArchive | null;
|
|
8098
|
+
/** Every version of a task's family, by the same proven lineage the task page reads (`taskFamilyOf`: same project, each
|
|
8099
|
+
* revision bound to its parent's own run by its brief). A family whose lineage doesn't prove is just this task. */
|
|
8100
|
+
export declare function taskFamilyRefs(store: Store, taskId: string): {
|
|
8101
|
+
id: number;
|
|
8102
|
+
taskId: string;
|
|
8103
|
+
}[];
|
|
8104
|
+
/** The first family version with something still going on, or null: running, a live newest claim, an unfinished run,
|
|
8105
|
+
* a process Toolroll hasn't seen exit, a build whose processes were never recorded, a stop still settling, a follow-up
|
|
8106
|
+
* check asked for and not finished, a flow step running for its card, a release in flight (a live release approval
|
|
8107
|
+
* that names it, or a deploy or merge reserved, running or unknown), or a pull request merging (an intent claimed or
|
|
8108
|
+
* firing, or a person's merge between its last check and GitHub's answer). */
|
|
8109
|
+
export declare function liveFamilyAttempt(store: Store, refs: readonly number[], now: Date): string | null;
|
|
8110
|
+
/** Puts a task away (v145): every version of its family, in one write transaction that re-checks it has no live
|
|
8111
|
+
* attempt, that its versions are exactly `expect` when the caller showed a person a family (a preview), and that
|
|
8112
|
+
* `guard` (the caller's own rule, re-read under the lock) still allows it. Its state, history, results, approvals and
|
|
8113
|
+
* ledger are untouched; its open notifications are resolved; one ledger row records who and why. */
|
|
8114
|
+
export declare function archiveTaskFamily(store: Store, taskId: string, by: ArchiveBy, now: Date, options?: {
|
|
8115
|
+
expect?: readonly number[];
|
|
8116
|
+
guard?: (refs: readonly number[]) => string | null;
|
|
8117
|
+
}): {
|
|
8118
|
+
ok: true;
|
|
8119
|
+
refs: number[];
|
|
8120
|
+
} | {
|
|
8121
|
+
ok: false;
|
|
8122
|
+
reason: ArchiveRefusal;
|
|
8123
|
+
message: string;
|
|
8124
|
+
};
|
|
8125
|
+
/** Brings a put-away task back (v145): every version of its family, with a ledger row. Tidy never puts a restored
|
|
8126
|
+
* task away again (tidy.ts reads the `task restored` row). */
|
|
8127
|
+
export declare function restoreTaskFamily(store: Store, taskId: string, by: string, now: Date, guard?: (refs: readonly number[]) => string | null): {
|
|
8128
|
+
ok: true;
|
|
8129
|
+
refs: number[];
|
|
8130
|
+
} | {
|
|
8131
|
+
ok: false;
|
|
8132
|
+
reason: ArchiveRefusal;
|
|
8133
|
+
message: string;
|
|
8134
|
+
};
|