toolroll 0.9.67 → 0.9.69
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 +70 -0
- package/dist/acp-client.js +1 -3
- package/dist/agent-board.d.ts +0 -1
- package/dist/agent-board.js +0 -1
- package/dist/agent-fence.d.ts +58 -0
- package/dist/agent-fence.js +214 -19
- package/dist/agentconfig.d.ts +1 -1
- package/dist/agentconfig.js +6 -2
- package/dist/batch-checks.js +14 -26
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.d.ts +9 -0
- package/dist/builder.js +30 -27
- package/dist/canonical-json.d.ts +14 -0
- package/dist/canonical-json.js +36 -0
- package/dist/chat-actions.js +1 -2
- package/dist/chat-admission.d.ts +19 -0
- package/dist/chat-admission.js +22 -0
- package/dist/chat-channel.js +1 -1
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-follow.d.ts +17 -0
- package/dist/chat-follow.js +32 -0
- package/dist/chat-messages.js +0 -1
- package/dist/chat-settings-page.d.ts +23 -0
- package/dist/chat-settings-page.js +41 -0
- package/dist/check-access.d.ts +54 -0
- package/dist/check-access.js +80 -0
- package/dist/claim.js +3 -6
- package/dist/claude-print.d.ts +12 -0
- package/dist/claude-print.js +34 -0
- package/dist/cli-http.d.ts +0 -7
- package/dist/cli-http.js +0 -21
- package/dist/cli.d.ts +3 -10
- package/dist/cli.js +15 -24
- package/dist/coding-provider.js +2 -1
- package/dist/coding-workspace.js +1 -1
- package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
- package/dist/connect-cli.js +331 -0
- package/dist/containment.d.ts +0 -2
- package/dist/containment.js +2 -6
- package/dist/contracts/chat-actions.d.ts +0 -1
- package/dist/contracts/chat-content.d.ts +0 -4
- package/dist/contracts/chat-content.js +0 -2
- package/dist/contracts/chat-tables.d.ts +0 -1
- package/dist/contracts/cli.d.ts +0 -62
- package/dist/contracts/cli.js +0 -4
- package/dist/contracts/coding-workspace.js +1 -1
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/contract.js +1 -1
- package/dist/contracts/flow-card.d.ts +0 -1
- package/dist/contracts/flow-propose.d.ts +0 -1
- package/dist/contracts/flow-send.js +1 -1
- package/dist/contracts/gateway-tools.d.ts +0 -1
- package/dist/contracts/integration-metadata.js +1 -1
- package/dist/contracts/lead-tools.d.ts +0 -1
- package/dist/contracts/memory-pass.d.ts +0 -1
- package/dist/contracts/memory-pass.js +1 -1
- package/dist/contracts/plan.js +1 -1
- package/dist/contracts/project-knowledge.js +1 -1
- package/dist/contracts/project-skills.d.ts +0 -1
- package/dist/contracts/proof.js +1 -1
- package/dist/contracts/recipes.js +1 -1
- package/dist/contracts/scope.js +1 -1
- package/dist/contracts/scout-report.js +1 -1
- package/dist/contracts/teams-callback.d.ts +0 -1
- package/dist/converse.d.ts +0 -6
- package/dist/converse.js +0 -19
- package/dist/daemon.js +3 -2
- package/dist/decision.d.ts +1 -3
- package/dist/deprecations.d.ts +12 -4
- package/dist/deprecations.js +27 -4
- package/dist/desktop-update-recovery.js +1 -1
- package/dist/desktop-update.js +3 -2
- package/dist/discord-chat.js +4 -15
- package/dist/discord-settings.d.ts +2 -6
- package/dist/discord-settings.js +10 -38
- package/dist/discord.js +12 -23
- package/dist/evidence.d.ts +0 -1
- package/dist/evidence.js +0 -3
- package/dist/exec.d.ts +0 -9
- package/dist/exec.js +3 -81
- package/dist/flow-actions.js +0 -3
- package/dist/flow-draft.d.ts +1 -3
- package/dist/flow-draft.js +15 -28
- package/dist/flow-scripts.d.ts +1 -8
- package/dist/flow-scripts.js +0 -13
- package/dist/flow-send.js +1 -1
- package/dist/flow-share.d.ts +0 -2
- package/dist/flow-share.js +1 -6
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +0 -1
- package/dist/flow-triggers.js +1 -2
- package/dist/flow-unattended.js +1 -12
- package/dist/flows.js +1 -1
- package/dist/health-cli.d.ts +2 -2
- package/dist/health-cli.js +1 -1
- package/dist/invoke.d.ts +39 -5
- package/dist/invoke.js +181 -24
- package/dist/is-record.d.ts +2 -0
- package/dist/is-record.js +2 -0
- package/dist/lead-doors.d.ts +1 -5
- package/dist/lead-doors.js +0 -4
- package/dist/lead-tools.js +3 -6
- package/dist/live-detail.d.ts +0 -1
- package/dist/live-detail.js +0 -6
- package/dist/live-terminal.js +2 -1
- package/dist/live.d.ts +0 -1
- package/dist/live.js +1 -2
- package/dist/mcp-core.js +0 -2
- package/dist/memory-pass.js +1 -1
- package/dist/model-catalog.js +1 -1
- package/dist/modes.js +1 -12
- package/dist/operate.d.ts +4 -0
- package/dist/operate.js +456 -750
- package/dist/password-bearer.d.ts +0 -2
- package/dist/password-bearer.js +0 -4
- package/dist/peek-cli.js +2 -1
- package/dist/peek.d.ts +2 -26
- package/dist/peek.js +2 -168
- package/dist/phase-routing.d.ts +0 -4
- package/dist/phase-routing.js +2 -21
- package/dist/planner-source.d.ts +2 -1
- package/dist/planner-source.js +2 -12
- package/dist/planner.js +2 -14
- package/dist/process-tree.d.ts +0 -2
- package/dist/process-tree.js +0 -10
- package/dist/project-delete-ui.d.ts +16 -0
- package/dist/project-delete-ui.js +60 -1
- package/dist/project-delete.d.ts +0 -2
- package/dist/project-delete.js +1 -3
- package/dist/project-learning.d.ts +0 -14
- package/dist/project-skills.d.ts +0 -1
- package/dist/project-skills.js +0 -14
- package/dist/project-tools.js +2 -10
- package/dist/provider.d.ts +0 -17
- package/dist/provider.js +0 -26
- package/dist/pulls.d.ts +0 -2
- package/dist/pulls.js +0 -9
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +15 -5
- package/dist/remote-exec.d.ts +3 -2
- package/dist/remote-exec.js +3 -2
- package/dist/repos.d.ts +0 -5
- package/dist/repos.js +2 -10
- package/dist/repository-context.d.ts +0 -2
- package/dist/repository-context.js +0 -4
- package/dist/restart-certification.d.ts +0 -2
- package/dist/restart-certification.js +1 -5
- package/dist/result-follow-ups.d.ts +1 -4
- package/dist/result-follow-ups.js +11 -21
- package/dist/result-review.d.ts +0 -2
- package/dist/result-review.js +0 -2
- package/dist/retention.d.ts +0 -1
- package/dist/retention.js +0 -1
- package/dist/review-context.d.ts +0 -1
- package/dist/review-context.js +1 -12
- package/dist/reviewer.d.ts +0 -16
- package/dist/reviewer.js +0 -18
- package/dist/run-closeout.js +2 -1
- package/dist/scope.d.ts +9 -0
- package/dist/scope.js +13 -18
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +11 -2
- package/dist/server/console.css +5 -133
- package/dist/server/flows.js +2 -2
- package/dist/server/http.d.ts +4 -0
- package/dist/server/http.js +5 -0
- package/dist/server/pages.js +2 -2
- package/dist/server/people-tokens.js +3 -4
- package/dist/server/remote.js +1 -6
- package/dist/server/render-chat.d.ts +9 -2
- package/dist/server/render-chat.js +53 -7
- package/dist/server/render-results.d.ts +3 -6
- package/dist/server/render-results.js +4 -9
- package/dist/server/render-tasks.js +6 -6
- package/dist/server/route-table.d.ts +0 -4
- package/dist/server/route-table.js +1 -6
- package/dist/server/settings.js +60 -23
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +5 -3
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills.d.ts +0 -6
- package/dist/skills.js +0 -8
- package/dist/slack-chat.d.ts +1 -7
- package/dist/slack-chat.js +5 -18
- package/dist/slack-settings.d.ts +2 -6
- package/dist/slack-settings.js +10 -38
- package/dist/slack.js +12 -23
- package/dist/sleep.d.ts +2 -0
- package/dist/sleep.js +2 -0
- package/dist/store.d.ts +29 -75
- package/dist/store.js +89 -180
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/subagent-work.d.ts +0 -2
- package/dist/subagent-work.js +0 -2
- package/dist/subagents.d.ts +1 -3
- package/dist/subagents.js +20 -33
- package/dist/subscription-chat.d.ts +1 -3
- package/dist/subscription-chat.js +2 -3
- package/dist/surface.js +2 -1
- package/dist/task-sizing.d.ts +1 -4
- package/dist/task-sizing.js +3 -10
- package/dist/teams-chat.js +4 -11
- package/dist/teams.js +12 -19
- package/dist/telegram-lead.js +2 -2
- package/dist/telegram.js +1 -1
- package/dist/token-notices.d.ts +0 -11
- package/dist/token-notices.js +1 -5
- package/dist/tool-secret-store.d.ts +0 -3
- package/dist/tool-secret-store.js +2 -11
- package/dist/toolroll-update.d.ts +0 -2
- package/dist/toolroll-update.js +2 -2
- package/dist/verification-evidence.d.ts +0 -1
- package/dist/verification-evidence.js +0 -1
- package/dist/webhooks.d.ts +0 -1
- package/dist/webhooks.js +0 -1
- package/dist/workspace-ui.d.ts +0 -8
- package/dist/workspace-ui.js +1 -6
- package/package.json +1 -1
- package/dist/contracts/native-census.d.ts +0 -42
- package/dist/contracts/native-census.js +0 -36
- package/dist/process-recovery-anchor.d.ts +0 -75
- package/dist/process-recovery-anchor.js +0 -188
- package/dist/process-recovery-coalition.d.ts +0 -25
- package/dist/process-recovery-coalition.js +0 -105
- package/dist/process-recovery-eligibility.d.ts +0 -179
- package/dist/process-recovery-eligibility.js +0 -174
- package/dist/process-recovery-native.d.ts +0 -161
- package/dist/process-recovery-native.js +0 -595
- package/dist/process-recovery-provenance.d.ts +0 -163
- package/dist/process-recovery-provenance.js +0 -292
- package/dist/process-recovery-services.d.ts +0 -56
- package/dist/process-recovery-services.js +0 -191
- package/dist/process-recovery-settlement.d.ts +0 -32
- package/dist/process-recovery-settlement.js +0 -133
- package/dist/process-recovery.d.ts +0 -41
- package/dist/process-recovery.js +0 -91
- package/dist/session-brief.d.ts +0 -8
- package/dist/session-brief.js +0 -24
- package/dist/session-cli.d.ts +0 -42
- package/dist/session-cli.js +0 -271
- package/dist/session-contract.d.ts +0 -183
- package/dist/session-contract.js +0 -122
- package/dist/session-remote.d.ts +0 -19
- package/dist/session-remote.js +0 -86
- package/dist/session-service.d.ts +0 -22
- package/dist/session-service.js +0 -157
- package/dist/team-cli.js +0 -692
package/dist/server/remote.js
CHANGED
|
@@ -7,9 +7,7 @@ import { CodingWorkspace } from '../coding-workspace.js';
|
|
|
7
7
|
import { createMcpHttp } from "../mcp-http.js";
|
|
8
8
|
import { createOAuthHttp, resourceMetadataUrl } from "../mcp-oauth.js";
|
|
9
9
|
import { reproveRemote } from "../operate-remote.js";
|
|
10
|
-
import { rowVisible } from "../project.js";
|
|
11
10
|
import { RequestBudget, SourceAdmission } from "../request-budget.js";
|
|
12
|
-
import { createSessionRunner } from '../session-remote.js';
|
|
13
11
|
import {} from "../store.js";
|
|
14
12
|
import { handleTeamHttp, TEAM_PASSWORD_REVERIFY_MS } from '../team-http.js';
|
|
15
13
|
import { handleTeamsHttp } from "../teams.js";
|
|
@@ -17,7 +15,7 @@ import { flowHook, telegramHook } from "./remote-hooks.js";
|
|
|
17
15
|
import { respond } from "./http.js";
|
|
18
16
|
import {} from "./session.js";
|
|
19
17
|
export function createRemoteHandlers(runtime) {
|
|
20
|
-
const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options,
|
|
18
|
+
const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options, clock, evidenceRoot, consoleOrigin, managedRepos, joinSourceOf, ssoSettings, oauthTokenBudget, hookContext, teamsSourceBudget, teamsTenantBudget } = runtime;
|
|
21
19
|
/**
|
|
22
20
|
* The full principal a live `so_` API token stands for: its person and their generation, the token's own read/act
|
|
23
21
|
* scope, its project limit narrowed by the person's current access, and its id. Every route that accepts an API token
|
|
@@ -97,15 +95,12 @@ export function createRemoteHandlers(runtime) {
|
|
|
97
95
|
return who?.via === 'cookie' ? teamBrowserReply(reply, actor, who.session.csrf) : reply;
|
|
98
96
|
},
|
|
99
97
|
});
|
|
100
|
-
// D7: native session operations are remote commands too (`session …` over /api/cli), run by the session owner.
|
|
101
|
-
const sessionRunner = createSessionRunner({ store, workspace: coding, projects: codingProjects, projectAllowed: repo => rowVisible(liveCeiling(), repo), clock });
|
|
102
98
|
// Remote CLI: one live API token names the person; the shared command boundary decides everything else.
|
|
103
99
|
const cliEndpoint = (request, response) => handleCliHttp(request, response, {
|
|
104
100
|
admit: principal => requestBudget.admit(principal.tokenId, "api"),
|
|
105
101
|
authenticate: tokenPrincipalOf,
|
|
106
102
|
run: async () => options.cliRunner ?? (await import("../operate.js")).runOperateAs ?? null,
|
|
107
103
|
modeOf: options.cliModeOf,
|
|
108
|
-
session: sessionRunner,
|
|
109
104
|
store,
|
|
110
105
|
});
|
|
111
106
|
const mcpHttp = createMcpHttp({ store, clock, evidenceRoot, ...(options.requestBudgetClock === undefined ? {} : { requestBudgetClock: options.requestBudgetClock }), signedIn: request => identify(request, true, true) !== null, admit: person => requestBudget.admit(person.principal.tokenId, "mcp"),
|
|
@@ -142,9 +142,16 @@ export declare function leadPromptStarters(focus?: TaskChatFocus | null): Html;
|
|
|
142
142
|
export declare function chatHeading(copy: string, projectCount: number, showProjectToggle?: boolean): Html;
|
|
143
143
|
/** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
|
|
144
144
|
* no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
|
|
145
|
-
* escaped before tags are introduced: bullets, numbered steps, bold, inline code
|
|
146
|
-
* presentation only—never executable HTML. */
|
|
145
|
+
* escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
|
|
146
|
+
* http(s) links are presentation only—never executable HTML. */
|
|
147
147
|
export declare function renderChatText(raw: string, asked?: string): Html;
|
|
148
|
+
/** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
|
|
149
|
+
* [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
|
|
150
|
+
* chat POST has trimmed the message), always last.
|
|
151
|
+
* The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
|
|
152
|
+
* code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
|
|
153
|
+
* escaped by the html template. */
|
|
154
|
+
export declare function steerCommentHtml(text: string): Html | null;
|
|
148
155
|
export declare function chatActivity(activity: string | null): Html;
|
|
149
156
|
export declare const CHAT_UI_SCRIPT: string;
|
|
150
157
|
/** Provider, model, and limits under ONE disclosure (UI polish
|
|
@@ -33,7 +33,6 @@ import { chatMoney, oneLineOf, relativeAge, safePrUrl, screen, sentenceCase, str
|
|
|
33
33
|
import { chatResultHref, projectChatHref, taskChatHref, taskHref } from "./http.js";
|
|
34
34
|
import {} from "./render-pages.js";
|
|
35
35
|
import { completionReceiptCard } from "./render-results.js";
|
|
36
|
-
import {} from "./render-settings.js";
|
|
37
36
|
import { agentsAvailabilityHtml, agentsBadgesHtml, agentsStripHtml, agentsSummaryWords, agentsWhyHtml, approvalSheetHtml, checkProgressHtml, consentClosedHtml, consentDoorOf, decisionAnswerCard, dispatchHeadline, milestoneProgressHtml, planRevisionLedgerHtml, profileWords, sizeConsequence, taskControlDetailsHtml, taskStatusCard, taskViewSwitch } from "./render-tasks.js";
|
|
38
37
|
/** The installation fact that the lead was turned on by default, once (its value: the provider). */
|
|
39
38
|
export const LEAD_BY_DEFAULT_FACT = "lead-on-by-default";
|
|
@@ -260,8 +259,8 @@ export function chatHeading(copy, projectCount, showProjectToggle = true) {
|
|
|
260
259
|
}
|
|
261
260
|
/** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
|
|
262
261
|
* no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
|
|
263
|
-
* escaped before tags are introduced: bullets, numbered steps, bold, inline code
|
|
264
|
-
* presentation only—never executable HTML. */
|
|
262
|
+
* escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
|
|
263
|
+
* http(s) links are presentation only—never executable HTML. */
|
|
265
264
|
export function renderChatText(raw, asked) {
|
|
266
265
|
const inline = replyHtmlInline;
|
|
267
266
|
const text = shapeReply(raw, { appOrigin: requestContext.getStore()?.appOrigins ?? null, ...(asked === undefined ? {} : { asked }) });
|
|
@@ -281,7 +280,31 @@ export function renderChatText(raw, asked) {
|
|
|
281
280
|
out.push(list.tag === "ul" ? html `<ul>${list.items}</ul>` : html `<ol>${list.items}</ol>`);
|
|
282
281
|
list = null;
|
|
283
282
|
};
|
|
283
|
+
// A fenced block (shaping leaves it exactly as written) is shown as written: escaped, never parsed.
|
|
284
|
+
let fence = null;
|
|
285
|
+
const closeFence = () => {
|
|
286
|
+
const open = fence;
|
|
287
|
+
if (open === null)
|
|
288
|
+
return;
|
|
289
|
+
out.push(html `<pre class="chat-code"${open.lang === "" ? "" : html ` data-lang="${open.lang}"`}><code>${open.lines.join("\n")}</code></pre>`);
|
|
290
|
+
fence = null;
|
|
291
|
+
};
|
|
284
292
|
for (const line of lines) {
|
|
293
|
+
const open = fence;
|
|
294
|
+
if (open !== null) {
|
|
295
|
+
if (/^\s{0,3}```\s*$/.test(line))
|
|
296
|
+
closeFence();
|
|
297
|
+
else
|
|
298
|
+
open.lines.push(line);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const fenceMark = /^\s{0,3}```\s*([A-Za-z0-9_+.#-]{0,24})\s*$/.exec(line);
|
|
302
|
+
if (fenceMark !== null) {
|
|
303
|
+
flushParagraph();
|
|
304
|
+
closeList();
|
|
305
|
+
fence = { lang: fenceMark[1], lines: [] };
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
285
308
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
286
309
|
const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
|
|
287
310
|
if (bullet !== null || numbered !== null) {
|
|
@@ -304,10 +327,35 @@ export function renderChatText(raw, asked) {
|
|
|
304
327
|
paragraph.push(line);
|
|
305
328
|
}
|
|
306
329
|
}
|
|
330
|
+
// An unclosed fence still shows its lines as code: the reply may have been cut.
|
|
307
331
|
flushParagraph();
|
|
308
332
|
closeList();
|
|
333
|
+
closeFence();
|
|
309
334
|
return html `<div class="chat-copy">${out}</div>`;
|
|
310
335
|
}
|
|
336
|
+
/** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
|
|
337
|
+
* [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
|
|
338
|
+
* chat POST has trimmed the message), always last.
|
|
339
|
+
* The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
|
|
340
|
+
* code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
|
|
341
|
+
* escaped by the html template. */
|
|
342
|
+
export function steerCommentHtml(text) {
|
|
343
|
+
const steer = /^On ("(?:[^"\\\n]|\\.){1,1200}"):(\d{1,9})( \(removed line\))? \(run #(\d{1,12})\): ([\s\S]+)\n\n>(?: ([^\n]*))?$/.exec(text);
|
|
344
|
+
if (steer === null)
|
|
345
|
+
return null;
|
|
346
|
+
// A blank line's quote ("> ") reaches the store as ">": the chat POST trims the message (src/server/chat.ts).
|
|
347
|
+
const [, quoted, line, removed, run, comment, code = ""] = steer;
|
|
348
|
+
let path;
|
|
349
|
+
try {
|
|
350
|
+
path = JSON.parse(quoted);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
if (typeof path !== "string" || path === "" || /[\u0000-\u001f]/.test(path) || comment.trim() === "")
|
|
356
|
+
return null;
|
|
357
|
+
return html `<div class="chat-steer" data-steer-comment><p class="chat-steer-ref">${path}:${line}${removed === undefined ? "" : " · removed line"} · run #${run}</p>${code === "" ? "" : html `<pre class="chat-steer-code"><code>${code}</code></pre>`}<p class="chat-steer-note" style="white-space:pre-wrap">${comment}</p></div>`;
|
|
358
|
+
}
|
|
311
359
|
export function chatActivity(activity) {
|
|
312
360
|
return chatActivityDetailsHtml(activity);
|
|
313
361
|
}
|
|
@@ -351,8 +399,6 @@ export function chatLimitsHtml(facts) {
|
|
|
351
399
|
return html `<details class="chat-limits"><summary>Model & limits<span class="meta">${facts.subscription ? "membership" : facts.provider}</span></summary><div class="chat-budget">${rows}</div></details>`;
|
|
352
400
|
}
|
|
353
401
|
export function chatPage(chrome, data) {
|
|
354
|
-
const formFacts = { keyFacts: data.keyFacts, openrouterModels: data.openrouterModels, ...(data.liveModels === undefined ? {} : { liveModels: data.liveModels }), csrf: data.csrf,
|
|
355
|
-
returnTo: data.focusTask === null ? "/chat" : taskChatHref(data.focusTask.id) };
|
|
356
402
|
const parts = [
|
|
357
403
|
data.focusTask === null
|
|
358
404
|
? chatHeading("", data.projects.length, data.enabled.ok)
|
|
@@ -803,7 +849,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
|
|
|
803
849
|
return rows.messages.map(message => ({
|
|
804
850
|
id: message.id, role: message.role, text: message.text,
|
|
805
851
|
html: htmlString(message.role === 'operator'
|
|
806
|
-
? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
|
|
852
|
+
? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
|
|
807
853
|
: html `${renderChatText(message.text, askedBy.get(message.id))}${asked(message.turn)}`),
|
|
808
854
|
activity: message.activity, createdAt: message.createdAt,
|
|
809
855
|
...(() => {
|
|
@@ -882,7 +928,7 @@ export function leadThreadHtml(data) {
|
|
|
882
928
|
asked = message.text;
|
|
883
929
|
// A message that waits its turn (the lead queue) says so, with Remove while it still waits — as in the React thread.
|
|
884
930
|
const queued = data.queue?.get(message.id);
|
|
885
|
-
thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
|
|
931
|
+
thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
|
|
886
932
|
continue;
|
|
887
933
|
}
|
|
888
934
|
const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Results and runs: the review cockpit, evidence, receipts, diffs and the result panel. */
|
|
2
|
+
import type { CheckAccess } from "../check-access.js";
|
|
2
3
|
import type { RunNetworkRecord } from "../egress.js";
|
|
3
4
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
4
5
|
import { type BrowserCheckItem, type BrowserNeedAction, type BrowserResultPanel, type BrowserResultView } from "../browser-workspace.js";
|
|
@@ -95,9 +96,6 @@ export declare function withRefusal(href: string, code: keyof typeof RESULT_REFU
|
|
|
95
96
|
/** The queue reads at most this many completed tasks — the store's own
|
|
96
97
|
* page ceiling, admission bound before it. */
|
|
97
98
|
export declare const REVIEW_QUEUE_CAP = 100;
|
|
98
|
-
/** The one return road the comment endpoint honors besides its own run
|
|
99
|
-
* page: the cockpit's selected-result link, exactly. */
|
|
100
|
-
export declare const REVIEW_RETURN: RegExp;
|
|
101
99
|
/** Review priority: a deterministic, LABELED presentation aid over facts
|
|
102
100
|
* the done list already carries. Band 0 goes first; the words name why.
|
|
103
101
|
* It is never a verdict — the stored proof verdict stays the authority,
|
|
@@ -187,6 +185,8 @@ export type ReviewCockpitView = {
|
|
|
187
185
|
efficiency?: RunEfficiency | null;
|
|
188
186
|
/** What the run's network was and what its proxy saw (egress.ts): display only. */
|
|
189
187
|
network?: RunNetworkRecord | null;
|
|
188
|
+
/** How far its checks reached (check-access.ts): shown only when wider than sandboxed. */
|
|
189
|
+
checkAccess?: CheckAccess;
|
|
190
190
|
taskId: string;
|
|
191
191
|
title: string;
|
|
192
192
|
repo: string | null;
|
|
@@ -287,9 +287,6 @@ export declare function reviewCockpitPage(chrome: Chrome, data: {
|
|
|
287
287
|
user: string;
|
|
288
288
|
now: Date;
|
|
289
289
|
}): Screen;
|
|
290
|
-
/** The selected result: intent → proof → changes → publication → acts,
|
|
291
|
-
* one scan path, every fact labeled by its source. */
|
|
292
|
-
export declare function reviewCockpitDetail(view: ReviewCockpitView, csrf: string, noted: boolean, canRetryReview?: boolean, tab?: ResultTab, user?: string): Html;
|
|
293
290
|
/** The selected result's HTML and, for the rebuilt page, the same result
|
|
294
291
|
* as data: every form and panel body is the one the HTML carries. */
|
|
295
292
|
/** Where a result is shown: its own page (/review), or the Result tab of its task's page (docs/plans/one-page-task-and-agent.md),
|
|
@@ -238,9 +238,6 @@ export function withRefusal(href, code) {
|
|
|
238
238
|
/** The queue reads at most this many completed tasks — the store's own
|
|
239
239
|
* page ceiling, admission bound before it. */
|
|
240
240
|
export const REVIEW_QUEUE_CAP = 100;
|
|
241
|
-
/** The one return road the comment endpoint honors besides its own run
|
|
242
|
-
* page: the cockpit's selected-result link, exactly. */
|
|
243
|
-
export const REVIEW_RETURN = /^\/review\?result=[A-Za-z0-9._~%-]{1,200}$/;
|
|
244
241
|
export const PRIORITY_LABELS = { 0: "needs action", 1: "review", 2: "no flags" };
|
|
245
242
|
export function reviewPriorityOf(row) {
|
|
246
243
|
if (row.assignment != null && (row.assignment.receipt?.runId ?? null) === row.runId) {
|
|
@@ -568,11 +565,6 @@ export function reviewCockpitPage(chrome, data) {
|
|
|
568
565
|
functional: { script: reviewEvidenceScript() + (selected !== null && selected.detail !== null ? RESULT_REVIEW_SCRIPT : ""), fetches: false },
|
|
569
566
|
});
|
|
570
567
|
}
|
|
571
|
-
/** The selected result: intent → proof → changes → publication → acts,
|
|
572
|
-
* one scan path, every fact labeled by its source. */
|
|
573
|
-
export function reviewCockpitDetail(view, csrf, noted, canRetryReview = false, tab = "summary", user = "") {
|
|
574
|
-
return reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user).html;
|
|
575
|
-
}
|
|
576
568
|
export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user, host = { kind: "review" }) {
|
|
577
569
|
const parts = [];
|
|
578
570
|
const run = view.run;
|
|
@@ -637,6 +629,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
637
629
|
record: run === null ? null : { build: run.id, href: `/r/${run.id}?record=1`, facts: [
|
|
638
630
|
{ label: "Agent", value: [run.provider, run.model].filter(Boolean).join(" · ") },
|
|
639
631
|
{ label: "Worker", value: run.runner },
|
|
632
|
+
...(view.checkAccess === undefined || view.checkAccess === "sandboxed" ? [] : [{ label: "Checks", value: view.checkAccess === "unsandboxed" ? "Checks ran without a sandbox" : "Checks ran with model and GitHub sign-ins" }]),
|
|
640
633
|
...(run.branch === null ? [] : [{ label: "Branch", value: run.branch }]),
|
|
641
634
|
...(run.finishedAt === null ? [] : [{ label: "Finished", value: when(run.finishedAt) }]),
|
|
642
635
|
...(run.ranMinutes === null ? [] : [{ label: "Ran", value: `${run.ranMinutes} min` }]),
|
|
@@ -1268,7 +1261,9 @@ export function reviewDiffHtml(patch, stat, runId, commentable, anchors = new Ma
|
|
|
1268
1261
|
? html `<span class="diff-annotate-space"></span>`
|
|
1269
1262
|
: html `<button type="button" class="diff-annotate pick-line" data-path="${one.path}" data-line="${lineNumber}" data-side="${side}" aria-label="Annotate ${one.path}, ${side} line ${lineNumber}" title="Annotate this line">${annotateIcon}</button>`;
|
|
1270
1263
|
const marker = line.kind === "addition" ? "+" : line.kind === "deletion" ? "−" : line.kind === "context" ? " " : "·";
|
|
1271
|
-
|
|
1264
|
+
// Each real line names its file and line, so a comment (or a test) can find the exact row.
|
|
1265
|
+
const named = lineNumber === null || line.kind === "meta" ? "" : html ` data-diff-file="${one.path}" data-diff-line="${lineNumber}"`;
|
|
1266
|
+
return html `<div class="diff-line diff-${line.kind}"${named}>${annotate}<span class="diff-gutter">${line.oldLine ?? ""}</span><span class="diff-gutter">${line.newLine ?? ""}</span><code><b aria-hidden="true">${marker}</b>${line.text}</code></div>`;
|
|
1272
1267
|
})}</div></section>`;
|
|
1273
1268
|
});
|
|
1274
1269
|
const anchor = anchors.get(one.path);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
2
|
import { networkWords } from "../egress.js";
|
|
3
3
|
import { writesWords } from "../writes-mode.js";
|
|
4
|
+
import { checkAccessWords } from "../check-access.js";
|
|
4
5
|
import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
|
|
5
6
|
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
6
7
|
import { closedQuestionReason, closedQuestionWords } from "../decision.js";
|
|
@@ -22,17 +23,16 @@ import { starterForWork } from "../flow-starters.js";
|
|
|
22
23
|
import {} from "../gaps.js";
|
|
23
24
|
import { readAuthModeStrict } from "../keys.js";
|
|
24
25
|
import { failedAttemptSentence, retryNoteOf } from "../needs-you.js";
|
|
25
|
-
import {
|
|
26
|
+
import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
|
|
26
27
|
import { parseExecutionPlanDocument } from "../plan.js";
|
|
27
28
|
import {} from "../planner-source.js";
|
|
28
29
|
import { projectName } from "../project.js";
|
|
29
30
|
import { dispatchStatusToken, manualReviewOnly, plainReasonWords, reviewConflict } from "../proof.js";
|
|
30
31
|
import { providerName } from "../provider-auth.js";
|
|
31
|
-
import {
|
|
32
|
+
import {} from "../provider.js";
|
|
32
33
|
import { qualityModeTitle } from "../quality.js";
|
|
33
|
-
import {} from "../result-evidence-readers.js";
|
|
34
34
|
import { evidenceShortenedWords } from "../result-review.js";
|
|
35
|
-
import { acceptanceToLines, approvalOf,
|
|
35
|
+
import { acceptanceToLines, approvalOf, scopeAuthorityOf } from "../scope.js";
|
|
36
36
|
import {} from "../store.js";
|
|
37
37
|
import { runCostWords } from "../summary.js";
|
|
38
38
|
import {} from "../task-control.js";
|
|
@@ -44,7 +44,7 @@ import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SEN
|
|
|
44
44
|
import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
|
|
45
45
|
import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
46
46
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
47
|
-
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml,
|
|
47
|
+
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
48
48
|
import {} from "./session.js";
|
|
49
49
|
import { linkWords, originNote } from "../task-source.js";
|
|
50
50
|
export function contractChangeHtml(change) {
|
|
@@ -542,7 +542,7 @@ export function profileWords(scope) {
|
|
|
542
542
|
? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside the OS fence; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
|
|
543
543
|
: html `<p class="meta">Runs on <span class="mono">${profile.provider} · ${profile.model}</span> — ${profile.sandboxMode === "danger-full-access" ? "FULL permissions via --dangerously-bypass-approvals-and-sandbox; nothing asks" : "workspace-write sandbox"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}); repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`;
|
|
544
544
|
// What the build may reach is part of what is approved (egress.ts): said on every approval form.
|
|
545
|
-
return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p><p class="meta" data-scope-writes>Writes: ${writesWords(profile.writes)}.</p
|
|
545
|
+
return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p><p class="meta" data-scope-writes>Writes: ${writesWords(profile.writes)}.</p>${profile.checkAccess === undefined ? "" : html `<p class="meta" data-scope-checks>Checks: ${checkAccessWords(profile.checkAccess)}.</p>`}`;
|
|
546
546
|
}
|
|
547
547
|
export const ROLE_NOUN = { plan: "Planner", build: "Builder", repair: "Repair", review: "Reviewer" };
|
|
548
548
|
export function agentsStandingWords(view) {
|
|
@@ -91,9 +91,5 @@ export interface RouteDeclaration {
|
|
|
91
91
|
export declare const ROUTES: readonly RouteDeclaration[];
|
|
92
92
|
/** The first row that declares this method and path, or null: an undeclared request never reaches a handler. */
|
|
93
93
|
export declare function matchRoute(method: string, pathname: string, stage?: Stage): RouteDeclaration | null;
|
|
94
|
-
/** True when some row declares this path for another method (a wrong-method request). */
|
|
95
|
-
export declare function declaredForAnotherMethod(method: string, pathname: string, stage?: Stage): boolean;
|
|
96
94
|
/** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
|
|
97
95
|
export declare function assertRouteTable(routes?: readonly RouteDeclaration[]): void;
|
|
98
|
-
/** The effective policy of every row, as plain data — what the snapshot test compares. */
|
|
99
|
-
export declare const routePolicySnapshot: () => Array<Omit<RouteDeclaration, "sample">>;
|
|
@@ -284,6 +284,7 @@ post("settings", "settings.project-concurrency", "/settings/project/concurrency"
|
|
|
284
284
|
post("settings", "settings.project-live-view", "/settings/project/live-view", { scope: "step-up", callers: ["cookie"] });
|
|
285
285
|
post("settings", "settings.project-network", "/settings/project/network", { scope: "step-up", callers: ["cookie"] });
|
|
286
286
|
post("settings", "settings.project-writes", "/settings/project/writes", { scope: "step-up", callers: ["cookie"] });
|
|
287
|
+
post("settings", "settings.project-check-access", "/settings/project/check-access", { scope: "step-up", callers: ["cookie"] });
|
|
287
288
|
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
288
289
|
// Lead sweeps (docs/plans/lead-fan-out.md): the table, and cancelling what isn't ready. The handler shows a sweep only in
|
|
289
290
|
// a project the reader can see, and acts only for an approver.
|
|
@@ -431,10 +432,6 @@ export function matchRoute(method, pathname, stage) {
|
|
|
431
432
|
}
|
|
432
433
|
return null;
|
|
433
434
|
}
|
|
434
|
-
/** True when some row declares this path for another method (a wrong-method request). */
|
|
435
|
-
export function declaredForAnotherMethod(method, pathname, stage) {
|
|
436
|
-
return compiled.some(({ route, test }) => (stage === undefined || route.stage === stage) && !methodMatches(route.method, method) && test.test(pathname));
|
|
437
|
-
}
|
|
438
435
|
/** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
|
|
439
436
|
export function assertRouteTable(routes = ROUTES) {
|
|
440
437
|
const ids = new Set(), keys = new Set();
|
|
@@ -466,5 +463,3 @@ export function assertRouteTable(routes = ROUTES) {
|
|
|
466
463
|
}
|
|
467
464
|
}
|
|
468
465
|
assertRouteTable();
|
|
469
|
-
/** The effective policy of every row, as plain data — what the snapshot test compares. */
|
|
470
|
-
export const routePolicySnapshot = () => ROUTES.map(({ sample: _sample, ...rest }) => ({ ...rest }));
|
package/dist/server/settings.js
CHANGED
|
@@ -32,7 +32,7 @@ import { budgetLabel, monthOf } from "../spend.js";
|
|
|
32
32
|
import { PACKAGE_VERSION } from "../version.js";
|
|
33
33
|
import { handlersOf } from './handler-registry.js';
|
|
34
34
|
import { refuse, screen } from "./chrome.js";
|
|
35
|
-
import { HANDOFF_STYLE, redirect, respond, safeReturn, signInSpent, startedHere } from "./http.js";
|
|
35
|
+
import { HANDOFF_STYLE, redirect, respond, safeReturn, sayTo, signInSpent, startedHere } from "./http.js";
|
|
36
36
|
import { html, htmlString, joinHtml, postForm, reauth } from "../html.js";
|
|
37
37
|
import { browserCaller } from "./session.js";
|
|
38
38
|
/** settings handlers, moved without changing their route bodies. */
|
|
@@ -81,6 +81,7 @@ import { checkPolicy, parseList, policyParts } from "../policy.js";
|
|
|
81
81
|
import { parseProjectConcurrency, saveProjectConcurrency } from "../project-concurrency.js";
|
|
82
82
|
import { MAX_NETWORK_HOSTS, normalizeHost } from "../egress.js";
|
|
83
83
|
import { isWritesMode } from "../writes-mode.js";
|
|
84
|
+
import { isCheckAccess, signInsUnavailableWords, widerThan } from "../check-access.js";
|
|
84
85
|
import { projectDeleteConfirmHtml, projectSettingsHtml } from "../project-delete-ui.js";
|
|
85
86
|
import { deleteProject, holdingsWords } from "../project-delete.js";
|
|
86
87
|
import { applySavedKnowledge, changeKnowledge, knowledgeVersion, knowledgeView } from "../project-knowledge.js";
|
|
@@ -127,7 +128,7 @@ import { personChip, ROLE_TITLES } from "./chrome.js";
|
|
|
127
128
|
import { chatReturnWithSaid, goOutside } from "./http.js";
|
|
128
129
|
import { latestReleaseFor, leadSettingsHtml, notificationProjects, SETTINGS_AUTOSAVE_SCRIPT, settingsPage, settingsWorkers, telegramDeliveryWords, telegramTrouble } from "./render-settings.js";
|
|
129
130
|
export function createSettingsHandlers(runtime) {
|
|
130
|
-
const { store, providerHome, managedRepos, consoleProjects, sendScreen, chromeFor, connectionCheck, clock, modelCatalog, modelSeams, toolsViewOf, codexServers, projectViewOf, sessions, restricted, options, integrations, consoleOrigin, evidenceRoot, storagePool, visible, ssoSettings, checkLocalAgents, chatKeyFor, chatCatalog, leadWords, agentSignInCommand, settingsUpdates, phoneSetup, authenticateApprover, authenticateCredential, deletedRepos,
|
|
131
|
+
const { store, providerHome, managedRepos, consoleProjects, sendScreen, chromeFor, connectionCheck, clock, modelCatalog, modelSeams, toolsViewOf, codexServers, projectViewOf, sessions, restricted, options, integrations, consoleOrigin, evidenceRoot, storagePool, visible, ssoSettings, checkLocalAgents, chatKeyFor, chatCatalog, leadWords, agentSignInCommand, settingsUpdates, phoneSetup, authenticateApprover, authenticateCredential, deletedRepos, projectOf, providerFor, admissionList, toolHome, connectVisits, mintApprovalNonce, consumeApprovalNonce, defaultProject, googleVisits } = runtime;
|
|
131
132
|
// v105: what agent work cost, by month, and the monthly budgets. An instance operator's page.
|
|
132
133
|
async function spendPage(ctx) {
|
|
133
134
|
const { url, who, response, now } = ctx;
|
|
@@ -451,7 +452,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
451
452
|
// v105: Settings → Data, where an instance operator downloads everything. The download is a POST with the password.
|
|
452
453
|
async function dataPage(ctx) {
|
|
453
454
|
const { url, response, project } = ctx;
|
|
454
|
-
|
|
455
|
+
browserCaller(ctx.who);
|
|
455
456
|
return sendScreen(response, 200, screen("Data", html `<p><a href="/settings">Settings</a></p><h1>Data</h1>${(dataExportHtml({ problem: url.searchParams.get("problem") }))}`, { chrome: chromeFor(project, "settings") }));
|
|
456
457
|
}
|
|
457
458
|
// v100: Settings → Sign-in, the identity provider people sign in with. An instance operator's page.
|
|
@@ -486,7 +487,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
486
487
|
const { response, now, posted } = ctx;
|
|
487
488
|
const who = browserCaller(ctx.who);
|
|
488
489
|
const body = readForm(posted, CONSOLE_FORMS.passwordless);
|
|
489
|
-
const back = (
|
|
490
|
+
const back = sayTo(response, "/settings/passwordless");
|
|
490
491
|
const action = body.get("action");
|
|
491
492
|
if (action === "off") {
|
|
492
493
|
setPasswordless(store, false, who.name, now);
|
|
@@ -812,7 +813,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
812
813
|
async function spendBudget(ctx) {
|
|
813
814
|
const { who, response, now, posted } = ctx;
|
|
814
815
|
const body = readForm(posted, CONSOLE_FORMS.spendBudget);
|
|
815
|
-
const back = (
|
|
816
|
+
const back = sayTo(response, "/spend");
|
|
816
817
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
817
818
|
return back("problem", "Enter your Toolroll password to change a budget.");
|
|
818
819
|
const target = /^(installation|project|person|subagent):(.+)$/.exec(body.get("target") ?? "");
|
|
@@ -848,7 +849,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
848
849
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
849
850
|
if (who.via !== "cookie" || who.role !== "approver" || !store.accountCanAccess(who.name, repo))
|
|
850
851
|
return refuse(response, who, 403, "An approver for this project sets how many tasks build at once.", "/settings/project");
|
|
851
|
-
const back = (
|
|
852
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`);
|
|
852
853
|
const n = parseProjectConcurrency(body.get("concurrency") ?? "");
|
|
853
854
|
if (n === null)
|
|
854
855
|
return back("problem", "Builds at once is a whole number from 1 to 64. Nothing changed.");
|
|
@@ -870,7 +871,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
870
871
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
871
872
|
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
872
873
|
return refuse(response, who, 403, "An approver for this project changes the live view.", "/settings/project");
|
|
873
|
-
const back = (
|
|
874
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`);
|
|
874
875
|
const act = body.get("act");
|
|
875
876
|
if (act === "off")
|
|
876
877
|
return back("said", setLiveCommands(store, { repo, commands: false, by: who.name, via: "the console" }, now).said);
|
|
@@ -893,7 +894,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
893
894
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
894
895
|
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
895
896
|
return refuse(response, who, 403, "An approver for this project changes what its builds may reach.", "/settings/project");
|
|
896
|
-
const back = (
|
|
897
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#network");
|
|
897
898
|
const hosts = (body.get("hosts") ?? "").split(/[\s,]+/).filter(Boolean).map(normalizeHost);
|
|
898
899
|
if (hosts.some(one => one === null))
|
|
899
900
|
return back("problem", "A host is a name like api.stripe.com or *.stripe.com: no address, port or path. Nothing changed.");
|
|
@@ -942,7 +943,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
942
943
|
async function policySave(ctx) {
|
|
943
944
|
const { who, response, now, posted } = ctx;
|
|
944
945
|
const body = readForm(posted, CONSOLE_FORMS.policy);
|
|
945
|
-
const back = (
|
|
946
|
+
const back = sayTo(response, "/settings/policy");
|
|
946
947
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
947
948
|
return back("problem", "That password didn't match. Nothing changed.");
|
|
948
949
|
const chosen = body.getAll("provider");
|
|
@@ -962,7 +963,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
962
963
|
const known = consoleProjects();
|
|
963
964
|
if (!known.includes(repo))
|
|
964
965
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/approval");
|
|
965
|
-
const back = (
|
|
966
|
+
const back = sayTo(response, `/settings/approval?repo=${encodeURIComponent(repo)}`);
|
|
966
967
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
967
968
|
return back("problem", "That password didn't match. Nothing changed.");
|
|
968
969
|
const protect = body.get("protect") ?? "none";
|
|
@@ -980,7 +981,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
980
981
|
async function requestLimitsSave(ctx) {
|
|
981
982
|
const { who, response, now, posted } = ctx;
|
|
982
983
|
const body = readForm(posted, CONSOLE_FORMS.requestLimits);
|
|
983
|
-
const back = (
|
|
984
|
+
const back = sayTo(response, "/settings/sessions");
|
|
984
985
|
const target = body.get("target") ?? "";
|
|
985
986
|
const token = target === "*" ? null : store.apiTokens(null).find(one => one.id === target && one.revokedAt === null) ?? null;
|
|
986
987
|
if (target !== "*" && token === null)
|
|
@@ -1004,7 +1005,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1004
1005
|
const body = readForm(posted, CONSOLE_FORMS.signIn);
|
|
1005
1006
|
if (!options.configDir)
|
|
1006
1007
|
return refuse(response, who, 403, "An instance operator sets up sign-in.", "/settings");
|
|
1007
|
-
const back = (
|
|
1008
|
+
const back = sayTo(response, "/settings/sign-in");
|
|
1008
1009
|
const action = body.get("action") ?? "";
|
|
1009
1010
|
const before = ssoSettings();
|
|
1010
1011
|
if (action === "test") {
|
|
@@ -1046,7 +1047,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1046
1047
|
if (databaseFile === null)
|
|
1047
1048
|
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
1048
1049
|
const stateDir = dirname(databaseFile);
|
|
1049
|
-
const back = (
|
|
1050
|
+
const back = sayTo(response, "/settings/updates");
|
|
1050
1051
|
if (op === "seen") {
|
|
1051
1052
|
markWhatsNewSeen(stateDir);
|
|
1052
1053
|
return redirect(response, "/settings/updates");
|
|
@@ -1090,7 +1091,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1090
1091
|
async function retentionSave(ctx) {
|
|
1091
1092
|
const { who, response, now, posted } = ctx;
|
|
1092
1093
|
const body = readForm(posted, CONSOLE_FORMS.retention);
|
|
1093
|
-
const back = (
|
|
1094
|
+
const back = sayTo(response, "/settings/retention");
|
|
1094
1095
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1095
1096
|
return back("problem", "Enter your Toolroll password to change retention.");
|
|
1096
1097
|
const before = store.retentionPeriods();
|
|
@@ -1112,7 +1113,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1112
1113
|
const databaseFile = store.databaseFile();
|
|
1113
1114
|
if (databaseFile === null)
|
|
1114
1115
|
return refuse(response, who, 403, "An instance operator looks after storage.", "/settings");
|
|
1115
|
-
const back = (
|
|
1116
|
+
const back = sayTo(response, "/settings/storage");
|
|
1116
1117
|
const what = op === "clean" ? "clean up" : op === "discard" ? "discard a checkout" : "change checkout cleanup";
|
|
1117
1118
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1118
1119
|
return back("problem", `Enter your Toolroll password to ${what}.`);
|
|
@@ -1152,7 +1153,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1152
1153
|
return refuse(response, who, 404, "No such project.", "/projects");
|
|
1153
1154
|
if (who.role !== "approver" || store.isDemo())
|
|
1154
1155
|
return refuse(response, who, 403, "An approver sets up pull requests.", "/projects");
|
|
1155
|
-
const back = (
|
|
1156
|
+
const back = sayTo(response, `/settings/pull-requests?repo=${encodeURIComponent(repo)}`);
|
|
1156
1157
|
const act = body.get("act");
|
|
1157
1158
|
if (!authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
1158
1159
|
return back("problem", "Enter your Toolroll password to change pull requests.");
|
|
@@ -1193,7 +1194,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1193
1194
|
return refuse(response, who, 404, "No such project.", "/projects");
|
|
1194
1195
|
if (who.role !== "approver" || store.isDemo())
|
|
1195
1196
|
return refuse(response, who, 403, "An approver sets a project's checks.", "/projects");
|
|
1196
|
-
const back = (
|
|
1197
|
+
const back = sayTo(response, `/settings/checks?repo=${encodeURIComponent(repo)}`);
|
|
1197
1198
|
if (!authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
1198
1199
|
return back("problem", "Enter your Toolroll password to change checks.");
|
|
1199
1200
|
const act = body.get("act");
|
|
@@ -1256,7 +1257,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1256
1257
|
const databaseFile = store.databaseFile();
|
|
1257
1258
|
if (databaseFile === null)
|
|
1258
1259
|
return refuse(response, who, 403, "An instance operator looks after backups.", "/settings");
|
|
1259
|
-
const back = (
|
|
1260
|
+
const back = sayTo(response, "/settings/backups");
|
|
1260
1261
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1261
1262
|
return back("problem", "Enter your Toolroll password to change backups.");
|
|
1262
1263
|
const every = body.get("every") ?? "";
|
|
@@ -1296,7 +1297,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1296
1297
|
const { who, response, posted } = ctx;
|
|
1297
1298
|
const body = readForm(posted, CONSOLE_FORMS.flowsOn);
|
|
1298
1299
|
const repo = body.get("repo") ?? "";
|
|
1299
|
-
const back = (
|
|
1300
|
+
const back = sayTo(response, `/settings/flows?repo=${encodeURIComponent(repo)}`);
|
|
1300
1301
|
const known = [...new Set([...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()])];
|
|
1301
1302
|
if (store.isDemo())
|
|
1302
1303
|
return refuse(response, who, 403, "An approver switches starter flows on.", "/settings/flows");
|
|
@@ -1315,7 +1316,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1315
1316
|
if (!options.configDir)
|
|
1316
1317
|
return refuse(response, who, 403, "An installation approver tests integrations.", "/settings");
|
|
1317
1318
|
const key = body.get("key") ?? "";
|
|
1318
|
-
const back = (
|
|
1319
|
+
const back = sayTo(response, "/settings/integrations");
|
|
1319
1320
|
const checked = (await integrations.check([key])).find(one => one.key === key);
|
|
1320
1321
|
if (checked === undefined)
|
|
1321
1322
|
return back("problem", "That integration isn't here any more.");
|
|
@@ -1328,7 +1329,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1328
1329
|
const body = readForm(posted, CONSOLE_FORMS.monitoring);
|
|
1329
1330
|
if (!options.configDir)
|
|
1330
1331
|
return refuse(response, who, 403, "An instance operator sets up monitoring.", "/settings");
|
|
1331
|
-
const back = (
|
|
1332
|
+
const back = sayTo(response, "/settings/monitoring");
|
|
1332
1333
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1333
1334
|
return back("problem", "Enter your Toolroll password to change monitoring.");
|
|
1334
1335
|
const before = readMonitoring(options.configDir);
|
|
@@ -1429,7 +1430,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1429
1430
|
const repo = body.get("repo") ?? "";
|
|
1430
1431
|
if (!visible(repo) || !consoleProjects().includes(repo))
|
|
1431
1432
|
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
1432
|
-
const back = (
|
|
1433
|
+
const back = sayTo(response, `/settings/tools?repo=${encodeURIComponent(repo)}`);
|
|
1433
1434
|
// As for Connect: a post from a page that showed another project is refused, never applied here.
|
|
1434
1435
|
const shown = body.get("shown") ?? "";
|
|
1435
1436
|
if (shown !== repo) {
|
|
@@ -2485,6 +2486,7 @@ ${modeWords(terms)
|
|
|
2485
2486
|
"settings.project-live-view": projectLiveView,
|
|
2486
2487
|
"settings.project-network": projectNetwork,
|
|
2487
2488
|
"settings.project-writes": projectWrites,
|
|
2489
|
+
"settings.project-check-access": projectCheckAccess,
|
|
2488
2490
|
"settings.project-delete": projectDelete,
|
|
2489
2491
|
"settings.policy-send": policySave,
|
|
2490
2492
|
"settings.approval-send": approvalSave,
|
|
@@ -2578,7 +2580,7 @@ ${modeWords(terms)
|
|
|
2578
2580
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
2579
2581
|
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
2580
2582
|
return refuse(response, who, 403, "An approver for this project changes where its builds may write.", "/settings/project");
|
|
2581
|
-
const back = (
|
|
2583
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#writes");
|
|
2582
2584
|
const wanted = body.get("writes");
|
|
2583
2585
|
if (!isWritesMode(wanted))
|
|
2584
2586
|
return back("problem", "Choose where builds may write. Nothing changed.");
|
|
@@ -2587,5 +2589,40 @@ ${modeWords(terms)
|
|
|
2587
2589
|
const changed = store.setProjectWrites(repo, wanted, who.name, now);
|
|
2588
2590
|
return back("said", changed.before === changed.after ? "Nothing changed." : "Saved. Tasks scoped from now on use it; tasks already approved keep what they were approved with.");
|
|
2589
2591
|
}
|
|
2592
|
+
// Settings → Project → Check access (check-access.ts). Widening is an instance operator's act with a password typed now
|
|
2593
|
+
// (an identity-provider re-check doesn't count); any approver for the project narrows it. The level is compared again
|
|
2594
|
+
// inside the write, so a change made meanwhile can't turn a narrowing into a widening. The ledger keeps before → after.
|
|
2595
|
+
async function projectCheckAccess(ctx) {
|
|
2596
|
+
const { who, response, now, posted } = ctx;
|
|
2597
|
+
const body = readForm(posted, CONSOLE_FORMS.projectCheckAccess);
|
|
2598
|
+
const repo = body.get("repo") ?? "";
|
|
2599
|
+
if (!consoleProjects().includes(repo) || !visible(repo))
|
|
2600
|
+
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
2601
|
+
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
2602
|
+
return refuse(response, who, 403, "An approver for this project changes how far its checks reach.", "/settings/project");
|
|
2603
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#check-access");
|
|
2604
|
+
const wanted = body.get("level");
|
|
2605
|
+
if (!isCheckAccess(wanted))
|
|
2606
|
+
return back("problem", "Choose how far checks reach. Nothing changed.");
|
|
2607
|
+
// Not available yet (check-access.ts signInsAvailable): refused before the password, whatever the form sent.
|
|
2608
|
+
const unavailable = wanted === "sign-ins" ? signInsUnavailableWords(options.platform ?? process.platform) : null;
|
|
2609
|
+
if (unavailable !== null)
|
|
2610
|
+
return back("problem", `${unavailable} Nothing changed.`);
|
|
2611
|
+
const widening = widerThan(wanted, store.projectCheckAccess(repo));
|
|
2612
|
+
// Sign-ins with an open network: a second, explicit yes, said before the password.
|
|
2613
|
+
if (wanted === "sign-ins" && widening && store.projectNetwork(repo).mode === "open" && body.get("confirm-open") !== "yes") {
|
|
2614
|
+
return back("problem", "This project's network is open: confirm that its checks could send your sign-ins to any host. Nothing changed.");
|
|
2615
|
+
}
|
|
2616
|
+
const proved = authenticateCredential(who, body.get("password") ?? "");
|
|
2617
|
+
if (!proved.ok)
|
|
2618
|
+
return back("problem", "Enter your Toolroll password to change how far checks reach.");
|
|
2619
|
+
if (widening && (proved.method !== "password-now" || !store.isInstanceOperator(who.name))) {
|
|
2620
|
+
return back("problem", "Only an instance operator widens how far checks reach, with their password typed now. Nothing changed.");
|
|
2621
|
+
}
|
|
2622
|
+
const changed = store.setProjectCheckAccess(repo, wanted, who.name, now, { may: widening ? "widen" : "narrow" });
|
|
2623
|
+
if (changed === null)
|
|
2624
|
+
return back("problem", "Check access changed meanwhile, and this would now widen it. Nothing changed.");
|
|
2625
|
+
return back("said", changed.before === changed.after ? "Nothing changed." : "Saved. Tasks scoped from now on use it; tasks already approved keep what they were approved with.");
|
|
2626
|
+
}
|
|
2590
2627
|
return { registrations, modelsView };
|
|
2591
2628
|
}
|
|
@@ -3,7 +3,7 @@ import { liveAttemptOf, steerControlsFor } from "../steer-controls.js";
|
|
|
3
3
|
import { originOf } from "../task-source.js";
|
|
4
4
|
import { taskBaseOf } from "../task-base.js";
|
|
5
5
|
import { assignmentPresentationOf, historicalAssessmentReason } from '../assignment-presentation.js';
|
|
6
|
-
import { html,
|
|
6
|
+
import { html, joinHtml } from "../html.js";
|
|
7
7
|
import { assignmentStatusOf, assignmentWithEvidence } from '../assignment-ui.js';
|
|
8
8
|
import { assignmentOf } from '../assignment.js';
|
|
9
9
|
import { knowledgeContextHtml } from "../knowledge-ui.js";
|