toolroll 0.9.65 → 0.9.68
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 +93 -0
- package/README.md +20 -2
- 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 +69 -2
- package/dist/agent-fence.js +294 -11
- package/dist/agentconfig.js +4 -1
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
- package/dist/browser/terminal.js +134 -23
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +56 -37
- 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/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 +18 -3
- package/dist/invoke.js +142 -17
- 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.d.ts +24 -13
- package/dist/live-terminal.js +81 -43
- 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 +2 -0
- package/dist/operate.js +436 -745
- 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 +6 -0
- package/dist/project-delete-ui.js +20 -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 +9 -0
- package/dist/release-adapters.js +81 -4
- package/dist/release.d.ts +7 -1
- package/dist/release.js +20 -7
- 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 +4 -8
- 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 +13 -0
- package/dist/scope.js +11 -18
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +10 -1
- package/dist/server/chrome.js +45 -4
- package/dist/server/console.css +60 -137
- package/dist/server/flows.js +2 -2
- package/dist/server/http.d.ts +2 -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-pages.d.ts +28 -3
- package/dist/server/render-pages.js +78 -9
- package/dist/server/render-results.d.ts +0 -6
- package/dist/server/render-results.js +3 -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 +43 -22
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +10 -7
- 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 +11 -74
- package/dist/store.js +36 -179
- 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/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/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- 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-broker.d.ts +12 -0
- package/dist/tool-broker.js +66 -2
- 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/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- 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/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +5 -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/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. */
|
|
@@ -80,6 +80,7 @@ import { policyHtml } from "../policy-ui.js";
|
|
|
80
80
|
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
|
+
import { isWritesMode } from "../writes-mode.js";
|
|
83
84
|
import { projectDeleteConfirmHtml, projectSettingsHtml } from "../project-delete-ui.js";
|
|
84
85
|
import { deleteProject, holdingsWords } from "../project-delete.js";
|
|
85
86
|
import { applySavedKnowledge, changeKnowledge, knowledgeVersion, knowledgeView } from "../project-knowledge.js";
|
|
@@ -126,7 +127,7 @@ import { personChip, ROLE_TITLES } from "./chrome.js";
|
|
|
126
127
|
import { chatReturnWithSaid, goOutside } from "./http.js";
|
|
127
128
|
import { latestReleaseFor, leadSettingsHtml, notificationProjects, SETTINGS_AUTOSAVE_SCRIPT, settingsPage, settingsWorkers, telegramDeliveryWords, telegramTrouble } from "./render-settings.js";
|
|
128
129
|
export function createSettingsHandlers(runtime) {
|
|
129
|
-
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,
|
|
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, projectOf, providerFor, admissionList, toolHome, connectVisits, mintApprovalNonce, consumeApprovalNonce, defaultProject, googleVisits } = runtime;
|
|
130
131
|
// v105: what agent work cost, by month, and the monthly budgets. An instance operator's page.
|
|
131
132
|
async function spendPage(ctx) {
|
|
132
133
|
const { url, who, response, now } = ctx;
|
|
@@ -450,7 +451,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
450
451
|
// v105: Settings → Data, where an instance operator downloads everything. The download is a POST with the password.
|
|
451
452
|
async function dataPage(ctx) {
|
|
452
453
|
const { url, response, project } = ctx;
|
|
453
|
-
|
|
454
|
+
browserCaller(ctx.who);
|
|
454
455
|
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") }));
|
|
455
456
|
}
|
|
456
457
|
// v100: Settings → Sign-in, the identity provider people sign in with. An instance operator's page.
|
|
@@ -485,7 +486,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
|
|
|
485
486
|
const { response, now, posted } = ctx;
|
|
486
487
|
const who = browserCaller(ctx.who);
|
|
487
488
|
const body = readForm(posted, CONSOLE_FORMS.passwordless);
|
|
488
|
-
const back = (
|
|
489
|
+
const back = sayTo(response, "/settings/passwordless");
|
|
489
490
|
const action = body.get("action");
|
|
490
491
|
if (action === "off") {
|
|
491
492
|
setPasswordless(store, false, who.name, now);
|
|
@@ -811,7 +812,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
811
812
|
async function spendBudget(ctx) {
|
|
812
813
|
const { who, response, now, posted } = ctx;
|
|
813
814
|
const body = readForm(posted, CONSOLE_FORMS.spendBudget);
|
|
814
|
-
const back = (
|
|
815
|
+
const back = sayTo(response, "/spend");
|
|
815
816
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
816
817
|
return back("problem", "Enter your Toolroll password to change a budget.");
|
|
817
818
|
const target = /^(installation|project|person|subagent):(.+)$/.exec(body.get("target") ?? "");
|
|
@@ -847,7 +848,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
847
848
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
848
849
|
if (who.via !== "cookie" || who.role !== "approver" || !store.accountCanAccess(who.name, repo))
|
|
849
850
|
return refuse(response, who, 403, "An approver for this project sets how many tasks build at once.", "/settings/project");
|
|
850
|
-
const back = (
|
|
851
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`);
|
|
851
852
|
const n = parseProjectConcurrency(body.get("concurrency") ?? "");
|
|
852
853
|
if (n === null)
|
|
853
854
|
return back("problem", "Builds at once is a whole number from 1 to 64. Nothing changed.");
|
|
@@ -869,7 +870,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
869
870
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
870
871
|
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
871
872
|
return refuse(response, who, 403, "An approver for this project changes the live view.", "/settings/project");
|
|
872
|
-
const back = (
|
|
873
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`);
|
|
873
874
|
const act = body.get("act");
|
|
874
875
|
if (act === "off")
|
|
875
876
|
return back("said", setLiveCommands(store, { repo, commands: false, by: who.name, via: "the console" }, now).said);
|
|
@@ -892,7 +893,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
892
893
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
893
894
|
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
894
895
|
return refuse(response, who, 403, "An approver for this project changes what its builds may reach.", "/settings/project");
|
|
895
|
-
const back = (
|
|
896
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#network");
|
|
896
897
|
const hosts = (body.get("hosts") ?? "").split(/[\s,]+/).filter(Boolean).map(normalizeHost);
|
|
897
898
|
if (hosts.some(one => one === null))
|
|
898
899
|
return back("problem", "A host is a name like api.stripe.com or *.stripe.com: no address, port or path. Nothing changed.");
|
|
@@ -941,7 +942,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
941
942
|
async function policySave(ctx) {
|
|
942
943
|
const { who, response, now, posted } = ctx;
|
|
943
944
|
const body = readForm(posted, CONSOLE_FORMS.policy);
|
|
944
|
-
const back = (
|
|
945
|
+
const back = sayTo(response, "/settings/policy");
|
|
945
946
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
946
947
|
return back("problem", "That password didn't match. Nothing changed.");
|
|
947
948
|
const chosen = body.getAll("provider");
|
|
@@ -961,7 +962,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
961
962
|
const known = consoleProjects();
|
|
962
963
|
if (!known.includes(repo))
|
|
963
964
|
return refuse(response, who, 403, "That project is outside your access.", "/settings/approval");
|
|
964
|
-
const back = (
|
|
965
|
+
const back = sayTo(response, `/settings/approval?repo=${encodeURIComponent(repo)}`);
|
|
965
966
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
966
967
|
return back("problem", "That password didn't match. Nothing changed.");
|
|
967
968
|
const protect = body.get("protect") ?? "none";
|
|
@@ -979,7 +980,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
979
980
|
async function requestLimitsSave(ctx) {
|
|
980
981
|
const { who, response, now, posted } = ctx;
|
|
981
982
|
const body = readForm(posted, CONSOLE_FORMS.requestLimits);
|
|
982
|
-
const back = (
|
|
983
|
+
const back = sayTo(response, "/settings/sessions");
|
|
983
984
|
const target = body.get("target") ?? "";
|
|
984
985
|
const token = target === "*" ? null : store.apiTokens(null).find(one => one.id === target && one.revokedAt === null) ?? null;
|
|
985
986
|
if (target !== "*" && token === null)
|
|
@@ -1003,7 +1004,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1003
1004
|
const body = readForm(posted, CONSOLE_FORMS.signIn);
|
|
1004
1005
|
if (!options.configDir)
|
|
1005
1006
|
return refuse(response, who, 403, "An instance operator sets up sign-in.", "/settings");
|
|
1006
|
-
const back = (
|
|
1007
|
+
const back = sayTo(response, "/settings/sign-in");
|
|
1007
1008
|
const action = body.get("action") ?? "";
|
|
1008
1009
|
const before = ssoSettings();
|
|
1009
1010
|
if (action === "test") {
|
|
@@ -1045,7 +1046,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1045
1046
|
if (databaseFile === null)
|
|
1046
1047
|
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
1047
1048
|
const stateDir = dirname(databaseFile);
|
|
1048
|
-
const back = (
|
|
1049
|
+
const back = sayTo(response, "/settings/updates");
|
|
1049
1050
|
if (op === "seen") {
|
|
1050
1051
|
markWhatsNewSeen(stateDir);
|
|
1051
1052
|
return redirect(response, "/settings/updates");
|
|
@@ -1089,7 +1090,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1089
1090
|
async function retentionSave(ctx) {
|
|
1090
1091
|
const { who, response, now, posted } = ctx;
|
|
1091
1092
|
const body = readForm(posted, CONSOLE_FORMS.retention);
|
|
1092
|
-
const back = (
|
|
1093
|
+
const back = sayTo(response, "/settings/retention");
|
|
1093
1094
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1094
1095
|
return back("problem", "Enter your Toolroll password to change retention.");
|
|
1095
1096
|
const before = store.retentionPeriods();
|
|
@@ -1111,7 +1112,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1111
1112
|
const databaseFile = store.databaseFile();
|
|
1112
1113
|
if (databaseFile === null)
|
|
1113
1114
|
return refuse(response, who, 403, "An instance operator looks after storage.", "/settings");
|
|
1114
|
-
const back = (
|
|
1115
|
+
const back = sayTo(response, "/settings/storage");
|
|
1115
1116
|
const what = op === "clean" ? "clean up" : op === "discard" ? "discard a checkout" : "change checkout cleanup";
|
|
1116
1117
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1117
1118
|
return back("problem", `Enter your Toolroll password to ${what}.`);
|
|
@@ -1151,7 +1152,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1151
1152
|
return refuse(response, who, 404, "No such project.", "/projects");
|
|
1152
1153
|
if (who.role !== "approver" || store.isDemo())
|
|
1153
1154
|
return refuse(response, who, 403, "An approver sets up pull requests.", "/projects");
|
|
1154
|
-
const back = (
|
|
1155
|
+
const back = sayTo(response, `/settings/pull-requests?repo=${encodeURIComponent(repo)}`);
|
|
1155
1156
|
const act = body.get("act");
|
|
1156
1157
|
if (!authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
1157
1158
|
return back("problem", "Enter your Toolroll password to change pull requests.");
|
|
@@ -1192,7 +1193,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1192
1193
|
return refuse(response, who, 404, "No such project.", "/projects");
|
|
1193
1194
|
if (who.role !== "approver" || store.isDemo())
|
|
1194
1195
|
return refuse(response, who, 403, "An approver sets a project's checks.", "/projects");
|
|
1195
|
-
const back = (
|
|
1196
|
+
const back = sayTo(response, `/settings/checks?repo=${encodeURIComponent(repo)}`);
|
|
1196
1197
|
if (!authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
1197
1198
|
return back("problem", "Enter your Toolroll password to change checks.");
|
|
1198
1199
|
const act = body.get("act");
|
|
@@ -1255,7 +1256,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1255
1256
|
const databaseFile = store.databaseFile();
|
|
1256
1257
|
if (databaseFile === null)
|
|
1257
1258
|
return refuse(response, who, 403, "An instance operator looks after backups.", "/settings");
|
|
1258
|
-
const back = (
|
|
1259
|
+
const back = sayTo(response, "/settings/backups");
|
|
1259
1260
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1260
1261
|
return back("problem", "Enter your Toolroll password to change backups.");
|
|
1261
1262
|
const every = body.get("every") ?? "";
|
|
@@ -1295,7 +1296,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1295
1296
|
const { who, response, posted } = ctx;
|
|
1296
1297
|
const body = readForm(posted, CONSOLE_FORMS.flowsOn);
|
|
1297
1298
|
const repo = body.get("repo") ?? "";
|
|
1298
|
-
const back = (
|
|
1299
|
+
const back = sayTo(response, `/settings/flows?repo=${encodeURIComponent(repo)}`);
|
|
1299
1300
|
const known = [...new Set([...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()])];
|
|
1300
1301
|
if (store.isDemo())
|
|
1301
1302
|
return refuse(response, who, 403, "An approver switches starter flows on.", "/settings/flows");
|
|
@@ -1314,7 +1315,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1314
1315
|
if (!options.configDir)
|
|
1315
1316
|
return refuse(response, who, 403, "An installation approver tests integrations.", "/settings");
|
|
1316
1317
|
const key = body.get("key") ?? "";
|
|
1317
|
-
const back = (
|
|
1318
|
+
const back = sayTo(response, "/settings/integrations");
|
|
1318
1319
|
const checked = (await integrations.check([key])).find(one => one.key === key);
|
|
1319
1320
|
if (checked === undefined)
|
|
1320
1321
|
return back("problem", "That integration isn't here any more.");
|
|
@@ -1327,7 +1328,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1327
1328
|
const body = readForm(posted, CONSOLE_FORMS.monitoring);
|
|
1328
1329
|
if (!options.configDir)
|
|
1329
1330
|
return refuse(response, who, 403, "An instance operator sets up monitoring.", "/settings");
|
|
1330
|
-
const back = (
|
|
1331
|
+
const back = sayTo(response, "/settings/monitoring");
|
|
1331
1332
|
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
1332
1333
|
return back("problem", "Enter your Toolroll password to change monitoring.");
|
|
1333
1334
|
const before = readMonitoring(options.configDir);
|
|
@@ -1428,7 +1429,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
|
|
|
1428
1429
|
const repo = body.get("repo") ?? "";
|
|
1429
1430
|
if (!visible(repo) || !consoleProjects().includes(repo))
|
|
1430
1431
|
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
1431
|
-
const back = (
|
|
1432
|
+
const back = sayTo(response, `/settings/tools?repo=${encodeURIComponent(repo)}`);
|
|
1432
1433
|
// As for Connect: a post from a page that showed another project is refused, never applied here.
|
|
1433
1434
|
const shown = body.get("shown") ?? "";
|
|
1434
1435
|
if (shown !== repo) {
|
|
@@ -2483,6 +2484,7 @@ ${modeWords(terms)
|
|
|
2483
2484
|
"settings.project-concurrency": projectConcurrency,
|
|
2484
2485
|
"settings.project-live-view": projectLiveView,
|
|
2485
2486
|
"settings.project-network": projectNetwork,
|
|
2487
|
+
"settings.project-writes": projectWrites,
|
|
2486
2488
|
"settings.project-delete": projectDelete,
|
|
2487
2489
|
"settings.policy-send": policySave,
|
|
2488
2490
|
"settings.approval-send": approvalSave,
|
|
@@ -2566,5 +2568,24 @@ ${modeWords(terms)
|
|
|
2566
2568
|
"edge.google-callback": googleCallback,
|
|
2567
2569
|
"edge.connect-callback": connectCallback,
|
|
2568
2570
|
});
|
|
2571
|
+
// Settings → Project → Where builds write (writes-mode.ts). An approver for the project changes it with their password
|
|
2572
|
+
// typed now; the ledger keeps before → after. New scopes use it; approved ones keep theirs.
|
|
2573
|
+
async function projectWrites(ctx) {
|
|
2574
|
+
const { who, response, now, posted } = ctx;
|
|
2575
|
+
const body = readForm(posted, CONSOLE_FORMS.projectWrites);
|
|
2576
|
+
const repo = body.get("repo") ?? "";
|
|
2577
|
+
if (!consoleProjects().includes(repo) || !visible(repo))
|
|
2578
|
+
return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
|
|
2579
|
+
if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
|
|
2580
|
+
return refuse(response, who, 403, "An approver for this project changes where its builds may write.", "/settings/project");
|
|
2581
|
+
const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#writes");
|
|
2582
|
+
const wanted = body.get("writes");
|
|
2583
|
+
if (!isWritesMode(wanted))
|
|
2584
|
+
return back("problem", "Choose where builds may write. Nothing changed.");
|
|
2585
|
+
if (!authenticateCredential(who, body.get("password") ?? "").ok)
|
|
2586
|
+
return back("problem", "Enter your Toolroll password to change where builds may write.");
|
|
2587
|
+
const changed = store.setProjectWrites(repo, wanted, who.name, now);
|
|
2588
|
+
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
|
+
}
|
|
2569
2590
|
return { registrations, modelsView };
|
|
2570
2591
|
}
|
|
@@ -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";
|
package/dist/server/tasks.js
CHANGED
|
@@ -11,7 +11,7 @@ import { handlersOf } from './handler-registry.js';
|
|
|
11
11
|
import { closedQuestionWords } from "../decision.js";
|
|
12
12
|
import { DEPRECATED_PAGE } from "../deprecations.js";
|
|
13
13
|
/** tasks handlers, moved without changing their route bodies. */
|
|
14
|
-
import { html, htmlString, joinHtml,
|
|
14
|
+
import { html, htmlString, joinHtml, textHtml } from "../html.js";
|
|
15
15
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
16
16
|
import { lstatSync, realpathSync } from "node:fs";
|
|
17
17
|
import {} from "node:http";
|
|
@@ -40,7 +40,7 @@ import { isTaskSize, PHASES as ROUTE_PHASES } from "../phase-routing.js";
|
|
|
40
40
|
import { applyModeToNewFiling, authorizePlanUnderMode } from "../plan-auto.js";
|
|
41
41
|
import { milestonesOf, parseExecutionPlanDocument } from "../plan.js";
|
|
42
42
|
import { levelOfProfile } from "../policy.js";
|
|
43
|
-
import { authorizedProject, canonicalProject, projectName,
|
|
43
|
+
import { authorizedProject, canonicalProject, projectName, } from "../project.js";
|
|
44
44
|
import { fileTaskProposal } from "../proposal.js";
|
|
45
45
|
import { isProviderId, PROVIDER_IDS, validateSpec } from "../provider.js";
|
|
46
46
|
import { completeAndOpenPullRequest, mergeAsPerson } from '../pull-request-flow.js';
|
|
@@ -61,15 +61,15 @@ import { agentQuestion, answerAgentQuestion, markAgentSeen } from "../agent-boar
|
|
|
61
61
|
import { WorkIndexCursorError, workIndexPage } from "../work-index.js";
|
|
62
62
|
import { buildProgressOf, parseWorkView } from "../workspace-ui.js";
|
|
63
63
|
import { requestContext } from "./request-context.js";
|
|
64
|
-
import { oneLineOf, QUEUE_VIEW, refuse, regionScript, screen, transcriptScript, whenTime } from "./chrome.js";
|
|
64
|
+
import { boardTickScript, oneLineOf, QUEUE_VIEW, refuse, regionScript, screen, transcriptScript, whenTime } from "./chrome.js";
|
|
65
65
|
import { chatReturnWithSaid, matchTaskPath, redirect, respond, reviewHref, safeChatReturn, safeReturn, SAFETY, taskChatHref, taskHref } from "./http.js";
|
|
66
|
-
import { boardBody, donePage, inboxFingerprints, inboxPage, newTaskPage, nextPage, parseInboxTab, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, tasksPage, workPage } from "./render-pages.js";
|
|
66
|
+
import { boardBody, boardWorkers, donePage, inboxFingerprints, inboxPage, newTaskPage, nextPage, parseInboxTab, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, tasksPage, workPage } from "./render-pages.js";
|
|
67
67
|
import { runEfficiencyOf } from "../run-efficiency.js";
|
|
68
68
|
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";
|
|
69
69
|
import { approvalFormDigest, approveRefusalWords, checkProgressHtml, consentDoorOf, decisionPage, nonceHashOf, PermissionsWouldChange, requirementsFromEditor, resumeDigestOf, taskOf } from "./render-tasks.js";
|
|
70
70
|
import {} from "./session.js";
|
|
71
71
|
export function createTasksHandlers(runtime) {
|
|
72
|
-
const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, leadPrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView,
|
|
72
|
+
const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, leadPrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
|
|
73
73
|
/** A row admitted the address but its path parameter does not parse: the console's unknown page. */
|
|
74
74
|
function noPage(who, response) {
|
|
75
75
|
return refuse(response, who, 404, "There's no page at this address.", "/chat");
|
|
@@ -399,7 +399,10 @@ export function createTasksHandlers(runtime) {
|
|
|
399
399
|
});
|
|
400
400
|
});
|
|
401
401
|
const laneCards = [...cards, ...unverifiedCards];
|
|
402
|
-
|
|
402
|
+
// The worker strip: the workers this board's work can go to — bound to
|
|
403
|
+
// this project, or (rolled up) to any project the ceiling admits.
|
|
404
|
+
const workers = boardWorkers(store.listRunners(), { all, project, visible });
|
|
405
|
+
const body = boardBody({ cards: laneCards, workers, done: verifiedDone, saturated: snapshot.saturated, now, all, project, delta }, pr => store.ciFailureObserved(pr));
|
|
403
406
|
if (url.searchParams.get("fragment") === "1") {
|
|
404
407
|
// The live region alone — the in-page swapper's diet. Same auth,
|
|
405
408
|
// same ceiling, no shell, no scripts (finding 2).
|
|
@@ -423,7 +426,7 @@ export function createTasksHandlers(runtime) {
|
|
|
423
426
|
const regionBody = html `<div id="board-region">${body}</div><p class="meta" id="board-region-stamp"></p>`;
|
|
424
427
|
return sendScreen(response, 200, screen("board", regionBody, {
|
|
425
428
|
chrome: chromeFor(project, "board", undefined, all ? "board-all" : undefined),
|
|
426
|
-
functional: { script: regionScript("board-region", "1"), fetches: true },
|
|
429
|
+
functional: { script: regionScript("board-region", "1", undefined, { animate: true }) + boardTickScript(), fetches: true },
|
|
427
430
|
}));
|
|
428
431
|
}
|
|
429
432
|
/** A completed (or attempted) row as the review list ranks it: its proof reasons, CI standing, and the assignment
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
-
content?: never;
|
|
19
18
|
ok: false;
|
|
20
19
|
message: string;
|
|
21
20
|
plan?: never;
|
|
21
|
+
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
-
content?: never;
|
|
34
33
|
ok: false;
|
|
35
34
|
message: string;
|
|
36
35
|
plan?: never;
|
|
36
|
+
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/skills.d.ts
CHANGED
|
@@ -33,12 +33,6 @@ export declare const CONTEXT_END = "<!-- standing-orders:end -->";
|
|
|
33
33
|
export declare function skillContent(): string;
|
|
34
34
|
/** The AGENTS.md block, marker-fenced so reinstalls replace only themselves. */
|
|
35
35
|
export declare function contextBlock(): string;
|
|
36
|
-
/**
|
|
37
|
-
* Whether a repository has the Toolroll skill: a SKILL.md in the current
|
|
38
|
-
* folder, or in the one installs before the rename used. The setup guide
|
|
39
|
-
* and the console's first-run checklist both ask this, so they agree.
|
|
40
|
-
*/
|
|
41
|
-
export declare function projectSkillInstalled(repo: string): boolean;
|
|
42
36
|
export type InstallPlan = {
|
|
43
37
|
skillPath: string;
|
|
44
38
|
skillAction: "create" | "replace" | "refuse-foreign";
|
package/dist/skills.js
CHANGED
|
@@ -72,14 +72,6 @@ in the plane: read \`skills get knowledge\`, search it with
|
|
|
72
72
|
merge anything yourself.
|
|
73
73
|
${CONTEXT_END}`;
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Whether a repository has the Toolroll skill: a SKILL.md in the current
|
|
77
|
-
* folder, or in the one installs before the rename used. The setup guide
|
|
78
|
-
* and the console's first-run checklist both ask this, so they agree.
|
|
79
|
-
*/
|
|
80
|
-
export function projectSkillInstalled(repo) {
|
|
81
|
-
return [SKILL_DIR, LEGACY_SKILL_DIR].some(dir => existsSync(join(repo, dir, SKILL_FILE)));
|
|
82
|
-
}
|
|
83
75
|
/** What install would do, computed without writing anything. */
|
|
84
76
|
export function planInstall(repo, writeContext) {
|
|
85
77
|
const skillPath = join(repo, SKILL_DIR, SKILL_FILE);
|
package/dist/slack-chat.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatState
|
|
1
|
+
import { ChatState } from "./chat-delivery-state.js";
|
|
2
2
|
import { uploadSlackBytes, type SlackApi, type SlackIdentity } from "./slack-api.js";
|
|
3
3
|
import { type Store } from "./store.js";
|
|
4
4
|
import type { SubscriptionLeadRunner } from "./subscription-chat.js";
|
|
@@ -19,12 +19,6 @@ export type SlackChatOptions = {
|
|
|
19
19
|
/** Synchronous receipt before ACK. Never stores response_url, raw tokens, or a pairing code. */
|
|
20
20
|
export declare function receiveSlack(state: ChatState, identity: SlackIdentity, type: string, raw: unknown, now: Date): boolean;
|
|
21
21
|
export declare const processSlackEvent: (options: SlackChatOptions) => Promise<boolean>;
|
|
22
|
-
export declare const applySlackAction: (options: SlackChatOptions, event: ChatEvent, binding: ChatBinding, repos: readonly string[]) => {
|
|
23
|
-
merge: import("./chat-decide.js").ChatMerge;
|
|
24
|
-
part: number;
|
|
25
|
-
shown: string;
|
|
26
|
-
content: ChatContent;
|
|
27
|
-
} | null;
|
|
28
22
|
export declare const planSlackNotifications: (options: SlackChatOptions) => Promise<void>;
|
|
29
23
|
export declare const planSlackRooms: (options: SlackChatOptions) => Promise<void>;
|
|
30
24
|
export declare function slackBlocks(text: string, buttons?: Record<string, unknown>[]): Record<string, unknown>[];
|
package/dist/slack-chat.js
CHANGED
|
@@ -2,9 +2,8 @@ import { chatFlowButtons } from "./chat-flow.js";
|
|
|
2
2
|
import { refuseResultShots, resultShotsPruned, shotWaitsUntil } from "./result-shots.js";
|
|
3
3
|
import { chatQuestionButtons } from "./subagent-question.js";
|
|
4
4
|
import { chatAskButtons } from "./chat-ask.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { processChatEvent, applyChatAction, planChatNotifications, channelAccess, planRoomMessages, heldForWatch, } from "./chat-delivery.js";
|
|
5
|
+
import { admitChatEvent } from "./chat-admission.js";
|
|
6
|
+
import { processChatEvent, planChatNotifications, channelAccess, planRoomMessages, heldForWatch, } from "./chat-delivery.js";
|
|
8
7
|
/** Slack is a transport for the same saved assistant and confirmation doors. */
|
|
9
8
|
import { proposalPreview, proposalLink, proposalOutcomeText, armedCardText, armedYesLabel, } from "./chat-channel.js";
|
|
10
9
|
import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
@@ -100,21 +99,10 @@ export function receiveSlack(state, identity, type, raw, now) {
|
|
|
100
99
|
// A channel or private group is a room: accepted only while it follows a
|
|
101
100
|
// conversation, or for the `/team` words that make it follow one.
|
|
102
101
|
const isRoom = !slackId(channel, "D");
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const inbox = isRoom && kind === "message" ? channelInbox(state.store, "slack", identity.installation, String(channel), words) : { watched: false, command: false };
|
|
106
|
-
const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
|
|
107
|
-
if (isRoom && !roomish)
|
|
108
|
-
return false;
|
|
109
|
-
const binding = state.bindingFor(identity.installation, member);
|
|
110
|
-
const open = inbox.watched && !inbox.command;
|
|
111
|
-
if (kind !== "pair" && !open &&
|
|
112
|
-
(!binding ||
|
|
113
|
-
!state.live(binding) ||
|
|
114
|
-
(binding.channel !== channel && !roomish)))
|
|
115
|
-
return false;
|
|
116
|
-
if (kind === "pair" && (binding || isRoom))
|
|
102
|
+
const admitted = admitChatEvent(state, "slack", identity.installation, { kind, member, channel, isRoom, payload });
|
|
103
|
+
if (admitted === null)
|
|
117
104
|
return false;
|
|
105
|
+
const { binding } = admitted;
|
|
118
106
|
return state.enqueue({
|
|
119
107
|
id,
|
|
120
108
|
installation: identity.installation,
|
|
@@ -139,7 +127,6 @@ const delivery = (options) => ({
|
|
|
139
127
|
}),
|
|
140
128
|
});
|
|
141
129
|
export const processSlackEvent = (options) => processChatEvent(delivery(options));
|
|
142
|
-
export const applySlackAction = (options, event, binding, repos) => applyChatAction(delivery(options), event, binding, repos);
|
|
143
130
|
export const planSlackNotifications = (options) => planChatNotifications(delivery(options));
|
|
144
131
|
export const planSlackRooms = (options) => planRoomMessages(delivery(options));
|
|
145
132
|
const access = (options, binding, ceiling) => channelAccess(delivery(options), binding, ceiling);
|
package/dist/slack-settings.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
2
|
import { type Html } from "./html.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
problem?: string;
|
|
6
|
-
now?: Date;
|
|
7
|
-
who?: string;
|
|
8
|
-
}): Html;
|
|
3
|
+
import { type ChatSettingsOptions } from "./chat-settings-page.js";
|
|
4
|
+
export declare function slackSettingsHtml(store: Store, dir: string, options?: ChatSettingsOptions): Html;
|
package/dist/slack-settings.js
CHANGED
|
@@ -1,41 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { html, postForm } from "./html.js";
|
|
1
|
+
import { html } from "./html.js";
|
|
3
2
|
import { loadSlackCredentials } from "./slack-api.js";
|
|
4
|
-
import {
|
|
3
|
+
import { chatSettingsHtml } from "./chat-settings-page.js";
|
|
5
4
|
export function slackSettingsHtml(store, dir, options = {}) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const parts = [];
|
|
15
|
-
if (!credentials) {
|
|
16
|
-
parts.push(html `<p>Manage projects and review results in a private Slack conversation.</p>`, html `<details open><summary>Create your Slack app</summary><ol><li>Download the <a href="/settings/slack/manifest">app manifest</a>, then <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer">create an app</a> in your workspace using that manifest.</li><li>Under Basic Information, create an app-level token with <code>connections:write</code>.</li><li>Under OAuth & Permissions, install the app and copy its bot token.</li></ol></details>`, post("connect", html `<label>App token<input style="min-height:44px" type="password" name="app-token" autocomplete="off" placeholder="xapp-…" required></label><label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" placeholder="xoxb-…" required></label>${password}<p class="meta">Tokens stay on this installation. Next, pair your own Slack account.</p><button type="submit">Connect Slack</button>`));
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
parts.push(html `<p><strong>${credentials.workspace}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`);
|
|
20
|
-
if (runtime?.problem)
|
|
21
|
-
parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
|
|
22
|
-
if (!live && !runtime?.problem)
|
|
23
|
-
parts.push(html `<p>Keep the Toolroll worker running to receive Slack messages.</p>`);
|
|
24
|
-
if (options.code) {
|
|
25
|
-
parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Slack app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/slack">Check connection</a>`);
|
|
26
|
-
}
|
|
27
|
-
else if (binding && state.live(binding)) {
|
|
28
|
-
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
29
|
-
parts.push(html `<p>Your Slack account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === "slack"
|
|
30
|
-
? html `<p>Task updates are sent here.</p>`
|
|
31
|
-
: post("alerts", html `<button type="submit">Send task updates here</button>`));
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired Slack account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
|
|
35
|
-
}
|
|
36
|
-
if (binding && state.live(binding) && !options.code)
|
|
37
|
-
parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
|
|
38
|
-
parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect Slack</button>`)}</details>`);
|
|
39
|
-
}
|
|
40
|
-
return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Slack</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}<details><summary>Access and setup</summary><p>Private messages and threads use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>Socket Mode connects out to Slack. It does not need a public webhook or a Tailscale account.</p></details></section>`;
|
|
5
|
+
return chatSettingsHtml(store, dir, options, {
|
|
6
|
+
provider: "slack",
|
|
7
|
+
label: "Slack",
|
|
8
|
+
credentials: loadSlackCredentials(dir),
|
|
9
|
+
setup: html `<details open><summary>Create your Slack app</summary><ol><li>Download the <a href="/settings/slack/manifest">app manifest</a>, then <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer">create an app</a> in your workspace using that manifest.</li><li>Under Basic Information, create an app-level token with <code>connections:write</code>.</li><li>Under OAuth & Permissions, install the app and copy its bot token.</li></ol></details>`,
|
|
10
|
+
connect: password => html `<label>App token<input style="min-height:44px" type="password" name="app-token" autocomplete="off" placeholder="xapp-…" required></label><label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" placeholder="xoxb-…" required></label>${password}<p class="meta">Tokens stay on this installation. Next, pair your own Slack account.</p><button type="submit">Connect Slack</button>`,
|
|
11
|
+
access: html `<p>Private messages and threads use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>Socket Mode connects out to Slack. It does not need a public webhook or a Tailscale account.</p>`,
|
|
12
|
+
});
|
|
41
13
|
}
|
package/dist/slack.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/** One leased Socket Mode connection, hosted by the ordinary worker. */
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { abortablePause, workChatQueue } from "./chat-follow.js";
|
|
4
4
|
import { loadSlackCredentials, slackApi } from "./slack-api.js";
|
|
5
5
|
import { ChatState } from "./chat-delivery-state.js";
|
|
6
6
|
import { receiveSlack, processSlackEvent, deliverSlackPart, planSlackNotifications, planSlackRooms, } from "./slack-chat.js";
|
|
7
7
|
export async function followSlack(options) {
|
|
8
8
|
const owner = randomBytes(16).toString("hex"), state = new ChatState(options.store, "slack");
|
|
9
|
-
const pause =
|
|
10
|
-
try {
|
|
11
|
-
await sleep(ms, undefined, { signal: options.signal });
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
/* shutdown */
|
|
15
|
-
}
|
|
16
|
-
};
|
|
9
|
+
const pause = abortablePause(options.signal);
|
|
17
10
|
while (!options.signal.aborted) {
|
|
18
11
|
const credentials = loadSlackCredentials(options.dir);
|
|
19
12
|
if (!credentials ||
|
|
@@ -104,20 +97,16 @@ export async function followSlack(options) {
|
|
|
104
97
|
};
|
|
105
98
|
try {
|
|
106
99
|
await client.start();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (same() && options.notifications())
|
|
118
|
-
await planSlackNotifications(chat);
|
|
119
|
-
await pause(1000);
|
|
120
|
-
}
|
|
100
|
+
await workChatQueue({
|
|
101
|
+
same,
|
|
102
|
+
ready: () => connected && !(state.retryAt(credentials.installation) > new Date().toISOString()),
|
|
103
|
+
pause,
|
|
104
|
+
notifications: () => options.notifications(),
|
|
105
|
+
process: () => processSlackEvent(chat),
|
|
106
|
+
rooms: () => planSlackRooms(chat),
|
|
107
|
+
deliver: () => deliverSlackPart(chat),
|
|
108
|
+
notify: () => planSlackNotifications(chat),
|
|
109
|
+
});
|
|
121
110
|
}
|
|
122
111
|
catch {
|
|
123
112
|
if (alive)
|
package/dist/sleep.d.ts
ADDED
package/dist/sleep.js
ADDED