toolroll 0.9.25 → 0.9.27
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 +25 -0
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/dist/brand-icons.d.ts +168 -0
- package/dist/brand-icons.js +32 -0
- package/dist/brand-mark.d.ts +27 -0
- package/dist/brand-mark.js +49 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +41 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -23
- package/dist/browser-workspace.d.ts +17 -0
- package/dist/chat-delivery.js +3 -3
- package/dist/chat-flow.d.ts +4 -2
- package/dist/chat-flow.js +26 -4
- package/dist/flow-cadence.d.ts +42 -0
- package/dist/flow-cadence.js +78 -0
- package/dist/flow-items.d.ts +46 -0
- package/dist/flow-items.js +152 -0
- package/dist/flow-send.d.ts +5 -0
- package/dist/flow-send.js +30 -4
- package/dist/flows-ui.js +5 -1
- package/dist/integrations-ui.js +10 -3
- package/dist/integrations.d.ts +2 -0
- package/dist/integrations.js +2 -2
- package/dist/mate-tools.js +1 -1
- package/dist/mcp-connect.d.ts +22 -3
- package/dist/mcp-connect.js +48 -12
- package/dist/operate.d.ts +3 -1
- package/dist/operate.js +95 -27
- package/dist/project-tools.d.ts +15 -0
- package/dist/project-tools.js +14 -1
- package/dist/result-shots.d.ts +5 -1
- package/dist/result-shots.js +33 -3
- package/dist/scout.d.ts +3 -2
- package/dist/scout.js +11 -9
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +85 -28
- package/dist/setup-guide.d.ts +2 -2
- package/dist/telegram.js +37 -6
- package/dist/tools-ui.d.ts +12 -3
- package/dist/tools-ui.js +37 -14
- package/package.json +3 -1
package/dist/scout.d.ts
CHANGED
|
@@ -25,8 +25,9 @@ export declare function scoutBrowserLaunch(tool: Pick<ToolSpec, "command" | "arg
|
|
|
25
25
|
export declare function scoutBrowser(url: string): Record<string, unknown>;
|
|
26
26
|
/** The scout's environment while its proxy runs (review 827): web fetch and the provider's own traffic go through
|
|
27
27
|
* the same public-web-only proxy as its browser, so only public addresses (and the project's demo) are reachable.
|
|
28
|
-
* The
|
|
29
|
-
|
|
28
|
+
* The direct addresses are its own browser's port, when it has one (review 828), and an app on this computer it
|
|
29
|
+
* reads (`local`: Figma's desktop server): the proxy refuses loopback. */
|
|
30
|
+
export declare function scoutProxyEnv(proxy: string, browserUrl?: string | null, local?: readonly string[]): Record<string, string>;
|
|
30
31
|
export type ScoutRequest = {
|
|
31
32
|
/** v105: what's left of a monthly budget this API-key work counts toward (the CLI's own cap), when one does. */
|
|
32
33
|
maxBudgetUsd?: number;
|
package/dist/scout.js
CHANGED
|
@@ -68,9 +68,11 @@ export function scoutBrowser(url) {
|
|
|
68
68
|
const PROXY_BYPASS_ENV = ["NO_PROXY", "no_proxy", "ALL_PROXY", "all_proxy"];
|
|
69
69
|
/** The scout's environment while its proxy runs (review 827): web fetch and the provider's own traffic go through
|
|
70
70
|
* the same public-web-only proxy as its browser, so only public addresses (and the project's demo) are reachable.
|
|
71
|
-
* The
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
* The direct addresses are its own browser's port, when it has one (review 828), and an app on this computer it
|
|
72
|
+
* reads (`local`: Figma's desktop server): the proxy refuses loopback. */
|
|
73
|
+
export function scoutProxyEnv(proxy, browserUrl = null, local = []) {
|
|
74
|
+
const hosts = [...(browserUrl === null ? [] : [new URL(browserUrl).host]), ...local].join(",");
|
|
75
|
+
const direct = hosts === "" ? {} : { NO_PROXY: hosts, no_proxy: hosts };
|
|
74
76
|
return { HTTP_PROXY: proxy, HTTPS_PROXY: proxy, http_proxy: proxy, https_proxy: proxy, ...direct };
|
|
75
77
|
}
|
|
76
78
|
/** One line of untrusted text made inert for the brief (audit IV-4). */
|
|
@@ -161,7 +163,7 @@ function scoutBrief(title, goal, outOfScope, mailbox, reportFile, answers, struc
|
|
|
161
163
|
' "summary": "one paragraph the operator reads first",',
|
|
162
164
|
' "report": "the report as markdown: what you found, the evidence, the risks",',
|
|
163
165
|
' "followUps": [{ "title": "one line", "goal": "what success looks like" }],',
|
|
164
|
-
' "items": [{ "title": "one line", "why": "why it
|
|
166
|
+
' "items": [{ "title": "one line", "why": "the pattern; why it fits us; how it plugs in", "url": "https://…", "image": "home.png" }],',
|
|
165
167
|
' "images": [{ "file": "home.png", "caption": "one line", "url": "the page it shows" }]',
|
|
166
168
|
"}",
|
|
167
169
|
`Caps: title ${REPORT_LIMITS.title}, summary ${REPORT_LIMITS.summary}, report ${REPORT_LIMITS.document} bytes,`,
|
|
@@ -172,7 +174,7 @@ function scoutBrief(title, goal, outOfScope, mailbox, reportFile, answers, struc
|
|
|
172
174
|
"its goal as the contract a builder would be held to.",
|
|
173
175
|
].join("\n");
|
|
174
176
|
}
|
|
175
|
-
const NO_RESEARCH = { services: [], allowed: [], reads: {} };
|
|
177
|
+
const NO_RESEARCH = { services: [], allowed: [], reads: {}, local: [] };
|
|
176
178
|
/** What of the project's connected services this run may read (none when they can't be read). */
|
|
177
179
|
function connectedResearch(store, taskRef, runId) {
|
|
178
180
|
try {
|
|
@@ -305,11 +307,11 @@ async function scoutWith(store, request, outputDir) {
|
|
|
305
307
|
browserServer = started.server;
|
|
306
308
|
browserProblem = started !== null && !started.ok ? started.reason : null;
|
|
307
309
|
const browser = browserServer === null ? null : scoutBrowser(browserServer.url);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// The project's signed-in services, read-only actions only: Claude allows them by name, so `dontAsk` refuses every
|
|
311
|
-
// other one; Codex launches only those servers, each with only those actions enabled.
|
|
310
|
+
// The project's signed-in services (and apps on this computer), read-only actions only: Claude allows them by name,
|
|
311
|
+
// so `dontAsk` refuses every other one; Codex launches only those servers, each with only those actions enabled.
|
|
312
312
|
const research = proxy === null ? NO_RESEARCH : connectedResearch(store, request.taskRef, request.runId);
|
|
313
|
+
const proxyEnv = proxy === null ? {} : scoutProxyEnv(proxy.url, browserServer?.url ?? null, research.local);
|
|
314
|
+
const briefBrowser = browserProblem !== null ? { problem: browserProblem } : browser === null ? null : { folder: imageFolder, demoUrl };
|
|
313
315
|
invoked = await invokeAgent(store, request.runId, { provider: request.provider ?? "claude", model: request.model ?? null }, {
|
|
314
316
|
phase: "plan",
|
|
315
317
|
brief: projectSkillContext + scoutBrief(request.taskTitle, request.goal, request.outOfScope, mailbox, reportFile, request.answers ?? [], structured, briefBrowser, proxy !== null, research.services),
|
package/dist/serve.d.ts
CHANGED
|
@@ -569,6 +569,8 @@ export declare function needActionOf(assignment: AssignmentSnapshot | null, stat
|
|
|
569
569
|
* stop marker, so an open tab quits refetching a dead build (round-4
|
|
570
570
|
* finding 15). */
|
|
571
571
|
export declare function runFactsFragment(run: Run, taskId: string, live: boolean, route?: RouteStamp | null): string;
|
|
572
|
+
/** Settings destinations under short headings: an icon and a name each, and a chat app's logo and state. */
|
|
573
|
+
export declare function settingsTiles(groups: BrowserSettingsGroup[]): string;
|
|
572
574
|
/**
|
|
573
575
|
* The settings groups, with each chat app's state from what the server already holds: not set up,
|
|
574
576
|
* connected, or getting alerts (only when that service was chosen, or is the only one).
|
package/dist/serve.js
CHANGED
|
@@ -24,13 +24,13 @@ import { SlackState } from "./slack-state.js";
|
|
|
24
24
|
import { CHAT_ACTIONS, OWNER_ACTIONS, sharedActionPayload, sharedActionNeedsReview, sharedActionReviewPath, mintSharedActionReview } from './chat-actions.js';
|
|
25
25
|
import { changeSkills, githubSkill, importSkill, readSkillsSnapshot, reviseSkillTest, skillTestResult, skillsView, testSkill } from "./project-skills.js";
|
|
26
26
|
import { skillsHtml, skillsScript, skillsSnapshotHtml, skillTestFeedbackHtml, SKILLS_CSS } from "./skills-ui.js";
|
|
27
|
-
import { toolsHtml, TOOLS_CSS } from "./tools-ui.js";
|
|
27
|
+
import { toolsHtml, toolsProjectPicker, TOOLS_CSS, TOOLS_PROJECT_SCRIPT } from "./tools-ui.js";
|
|
28
28
|
import { flowFallbackHtml, flowImportHtml, flowsListHtml, flowView, FLOW_IMPORT_SCRIPT, FLOWS_CSS } from "./flows-ui.js";
|
|
29
29
|
import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, planFlowImport } from "./flow-share.js";
|
|
30
30
|
import { BLANK_SOUL, TEAMMATE_CSS, teammatePageHtml, teammatesListHtml } from "./teammates-ui.js";
|
|
31
31
|
import { grantTool, revokeTool, rulesFromForm, setToolRules } from "./teammate-tools.js";
|
|
32
32
|
import { addKitGithubTrigger, addKitSample, kitInstalled, kitOf, setUpKit } from "./kits.js";
|
|
33
|
-
import { CONNECT_CALLBACK, connectionsOf, finishConnect, oneClickOf, startConnect } from "./mcp-connect.js";
|
|
33
|
+
import { CONNECT_CALLBACK, connectionsOf, finishConnect, localConnectOf, oneClickOf, startConnect } from "./mcp-connect.js";
|
|
34
34
|
import { KITS_CSS, kitPageHtml, kitsGalleryHtml } from "./kits-ui.js";
|
|
35
35
|
import { STARTERS_CSS, startersHtml } from "./flow-starters-ui.js";
|
|
36
36
|
import { GALLERY_CSS, galleryHtml, galleryUseHtml } from "./flow-gallery-ui.js";
|
|
@@ -53,7 +53,7 @@ import { addFlowTriggerTo, checkFlowTriggerNow, HOOK_PATH, pressFlowButton, rece
|
|
|
53
53
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, FLOW_HREF, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
54
54
|
import { chooseFlowCard } from "./flow-send.js";
|
|
55
55
|
import { FLOW_TEMPLATES, validateFlowDefinition } from "./flows.js";
|
|
56
|
-
import { TOOL_CATALOG, addToolTo, catalogTool, discoverTools, projectToolsOf, removeToolFrom, secretsSetFor, setToolSecret, splitCommandLine, testToolOf } from "./project-tools.js";
|
|
56
|
+
import { TOOL_CATALOG, addToolTo, catalogTool, discoverTools, localAppOf, projectToolsOf, removeToolFrom, secretsSetFor, setToolSecret, splitCommandLine, testToolOf } from "./project-tools.js";
|
|
57
57
|
import { changeLearning, learningView } from "./project-learning.js";
|
|
58
58
|
import { changeKnowledge, knowledgeView, knowledgeVersion, readKnowledgeSnapshot } from "./project-knowledge.js";
|
|
59
59
|
import { knowledgeHtml, knowledgeContextHtml, KNOWLEDGE_CSS, decisionsHtml, memorySearchHtml, MEMORY_INTRO, proposalsHtml } from "./knowledge-ui.js";
|
|
@@ -227,6 +227,7 @@ import { readSsoSettings, removeSsoSettings, saveSsoSettings, SSO_CALLBACK, ssoC
|
|
|
227
227
|
import { monitoringChange, readMonitoring, saveMonitoring } from "./monitoring-settings.js";
|
|
228
228
|
import { MONITORING_CSS, monitoringHtml, signingSecretHtml } from "./monitoring-ui.js";
|
|
229
229
|
import { INTEGRATIONS_CSS, integrationsHtml } from "./integrations-ui.js";
|
|
230
|
+
import { BRAND_MARK_CSS, brandIconHtml } from "./brand-mark.js";
|
|
230
231
|
import { createIntegrationMonitor } from "./integrations.js";
|
|
231
232
|
import { BACKUP_CSS, backupHtml } from "./backup-ui.js";
|
|
232
233
|
import { BACKUP_EVERY_HOURS, MAX_KEEP, backupFolderOf, backupNow, checkBackupFolder, defaultBackupFolder } from "./backup.js";
|
|
@@ -695,8 +696,8 @@ export function createDecisionServer(options) {
|
|
|
695
696
|
return {
|
|
696
697
|
repo, project: repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo,
|
|
697
698
|
tools: tools.map(tool => ({ tool, secretsSet: secretsSetFor(repo, tool.spec, toolHome) })),
|
|
698
|
-
// A service that connects by signing in is offered only that way.
|
|
699
|
-
catalog: TOOL_CATALOG.filter(one => !names.has(one.name) && oneClickOf(one.name) === null),
|
|
699
|
+
// A service that connects by signing in (or an app's own Connect) is offered only that way.
|
|
700
|
+
catalog: TOOL_CATALOG.filter(one => !names.has(one.name) && oneClickOf(one.name) === null && localConnectOf(one.name) === null),
|
|
700
701
|
connections: connectionsOf(store, repo), kit, wanted,
|
|
701
702
|
found: discoverTools(repo, codex, toolHome).filter(one => !names.has(one.spec.name)),
|
|
702
703
|
};
|
|
@@ -1113,8 +1114,9 @@ export function createDecisionServer(options) {
|
|
|
1113
1114
|
const service = oneClickOf(visit.service);
|
|
1114
1115
|
const kit = visit.kit === null ? null : kitOf(visit.kit);
|
|
1115
1116
|
const back = kit === null ? `/settings/tools?repo=${encodeURIComponent(visit.repo)}` : `/kits/${kit.id}?repo=${encodeURIComponent(visit.repo)}`;
|
|
1117
|
+
const to = projectName(visit.repo);
|
|
1116
1118
|
if (url.searchParams.has("error"))
|
|
1117
|
-
return done(back, "problem", url.searchParams.get("error") === "access_denied" ? `${service.label} wasn't connected: access was declined.` : `${service.label} wasn't connected.`);
|
|
1119
|
+
return done(back, "problem", url.searchParams.get("error") === "access_denied" ? `${service.label} wasn't connected to ${to}: access was declined.` : `${service.label} wasn't connected to ${to}.`);
|
|
1118
1120
|
const code = url.searchParams.get("code") ?? "";
|
|
1119
1121
|
if (!/^[\x21-\x7e]{4,2048}$/.test(code))
|
|
1120
1122
|
return done(back, "problem", `${service.label} didn't send a sign-in code. Connect again.`);
|
|
@@ -1127,7 +1129,7 @@ export function createDecisionServer(options) {
|
|
|
1127
1129
|
if (set !== null && store.teammateGrant(set.mate.id, service.id) === null) {
|
|
1128
1130
|
const granted = await grantTool(store, set.mate, service.id, visit.by, now, { toolHome });
|
|
1129
1131
|
if (granted.ok)
|
|
1130
|
-
return done(back, "said", `${service.label} is connected, and ${nameOf(set.mate)} can use it: reading freely, the rest after you approve each call.`);
|
|
1132
|
+
return done(back, "said", `${service.label} is connected to ${to}, and ${nameOf(set.mate)} can use it: reading freely, the rest after you approve each call.`);
|
|
1131
1133
|
}
|
|
1132
1134
|
return done(back, "said", finished.said);
|
|
1133
1135
|
}
|
|
@@ -3893,18 +3895,23 @@ export function createDecisionServer(options) {
|
|
|
3893
3895
|
const chosen = url.searchParams.get("repo") ?? project ?? projects[0] ?? "";
|
|
3894
3896
|
if (chosen && !projects.includes(chosen))
|
|
3895
3897
|
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
3896
|
-
const
|
|
3898
|
+
const kit = kitOf(url.searchParams.get("kit") ?? "")?.id ?? null, wanted = (oneClickOf(url.searchParams.get("connect") ?? "") ?? localConnectOf(url.searchParams.get("connect") ?? ""))?.id ?? null;
|
|
3899
|
+
// Choosing a project opens it at once (keeping a kit's Connect), so every form below acts on the project shown.
|
|
3900
|
+
const selector = toolsProjectPicker(projects, chosen, { ...(kit === null ? {} : { kit }), ...(wanted === null ? {} : { connect: wanted }) });
|
|
3897
3901
|
let content = "<p>Add a project to give its builds tools.</p>";
|
|
3898
3902
|
if (chosen) {
|
|
3899
3903
|
try {
|
|
3900
3904
|
const approver = who.role === "approver" && who.via === "cookie";
|
|
3901
|
-
|
|
3905
|
+
const view = toolsViewOf(chosen, approver ? await codexServers(chosen) : null, kit, wanted);
|
|
3906
|
+
if (approver)
|
|
3907
|
+
view.others = projects.filter(one => one !== chosen).map(one => ({ repo: one, project: projectName(one), open: connectionsOf(store, one).filter(c => c.state === "open").map(c => c.id) }));
|
|
3908
|
+
content = toolsHtml(view, who.via === "cookie" ? who.session.csrf : "", approver, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") });
|
|
3902
3909
|
}
|
|
3903
3910
|
catch {
|
|
3904
3911
|
content = '<p class="problem" role="alert">Tools are unavailable. Reload to retry.</p>';
|
|
3905
3912
|
}
|
|
3906
3913
|
}
|
|
3907
|
-
return sendScreen(response, 200, screen("Tools", `<p><a href="/settings">Settings</a></p><h1>Tools</h1>${selector}${content}`, { chrome: chromeFor(chosen || project, "settings") }));
|
|
3914
|
+
return sendScreen(response, 200, screen("Tools", `<p><a href="/settings">Settings</a></p><h1>Tools</h1>${selector}${content}`, { chrome: chromeFor(chosen || project, "settings"), functional: { script: SETTINGS_AUTOSAVE_SCRIPT + TOOLS_PROJECT_SCRIPT } }));
|
|
3908
3915
|
}
|
|
3909
3916
|
// Settings → Project: what Toolroll holds for a project; an instance operator deletes it here.
|
|
3910
3917
|
if (url.pathname === "/settings/project") {
|
|
@@ -7714,19 +7721,61 @@ export function createDecisionServer(options) {
|
|
|
7714
7721
|
if (who.via !== "cookie" || who.role !== "approver")
|
|
7715
7722
|
return refuse(response, who, 403, "Sign in as an approver to connect tools.", "/settings/tools");
|
|
7716
7723
|
const repo = body.get("repo") ?? "";
|
|
7717
|
-
|
|
7724
|
+
const reachable = (one) => visible(one) && [...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()].includes(one);
|
|
7725
|
+
if (!reachable(repo))
|
|
7718
7726
|
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
7719
|
-
|
|
7727
|
+
// "Also connect to …": a service connected on the shown project, signed in again for another project.
|
|
7728
|
+
const also = body.get("also");
|
|
7729
|
+
const split = also === null ? -1 : also.indexOf(":");
|
|
7730
|
+
const service = oneClickOf(also === null ? body.get("service") ?? "" : split > 0 ? also.slice(0, split) : "");
|
|
7731
|
+
// An app on this computer (Figma's desktop app) connects here, never "also" elsewhere.
|
|
7732
|
+
const local = service === null && also === null ? localConnectOf(body.get("service") ?? "") : null;
|
|
7733
|
+
const chosen = service ?? local;
|
|
7734
|
+
const target = also === null ? repo : split > 0 ? also.slice(split + 1) : "";
|
|
7720
7735
|
const kit = kitOf(body.get("kit") ?? "")?.id ?? null;
|
|
7721
|
-
|
|
7722
|
-
|
|
7736
|
+
// A Connect that fails comes back to its own button, where the reason is said (no #fragment: one stops the alert's
|
|
7737
|
+
// autofocus); an "Also connect to" refusal goes back to the Connect card.
|
|
7738
|
+
const back = (words, page = repo) => redirect(response, `/settings/tools?repo=${encodeURIComponent(page)}${kit === null ? "" : `&kit=${kit}`}${chosen === null || also !== null ? "" : `&connect=${chosen.id}`}&problem=${encodeURIComponent(words)}${also === null ? "" : "#connect"}`);
|
|
7739
|
+
// The page posts the project it showed; a post from a page showing another project is stale.
|
|
7740
|
+
const shown = body.get("shown") ?? "";
|
|
7741
|
+
if (shown !== repo)
|
|
7742
|
+
return back("The project changed; connect again.", reachable(shown) ? shown : repo);
|
|
7743
|
+
if (chosen === null)
|
|
7723
7744
|
return back("Choose a service to connect.");
|
|
7745
|
+
if (also !== null && service !== null) {
|
|
7746
|
+
if (!reachable(target))
|
|
7747
|
+
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
7748
|
+
if (connectionsOf(store, repo).find(one => one.id === service.id)?.state !== "connected")
|
|
7749
|
+
return back(`${service.label} isn't connected to ${projectName(repo)}.`);
|
|
7750
|
+
if (connectionsOf(store, target).find(one => one.id === service.id)?.state !== "open")
|
|
7751
|
+
return back(`${projectName(target)} already has ${service.label}.`);
|
|
7752
|
+
}
|
|
7724
7753
|
if (!authenticateApprover(store, who.name, body.get("password") ?? "").ok)
|
|
7725
7754
|
return back("Enter your Toolroll password to connect a tool.");
|
|
7755
|
+
// An app on this computer: added like a common tool and tested; one that can't be reached isn't kept.
|
|
7756
|
+
if (local !== null) {
|
|
7757
|
+
const had = projectToolsOf(store, repo).find(one => one.name === local.id);
|
|
7758
|
+
if (had !== undefined && localAppOf(had.spec) === null)
|
|
7759
|
+
return back(`This project already has a tool called ${local.id}, set up another way. Remove it on this page to connect ${local.label}.`);
|
|
7760
|
+
if (had === undefined) {
|
|
7761
|
+
const { label: _label, ...spec } = catalogTool(local.id);
|
|
7762
|
+
const added = addToolTo(store, repo, spec, `${local.label}, connected on this computer`, who.name, clock(), { home: toolHome });
|
|
7763
|
+
if (!added.ok)
|
|
7764
|
+
return back(added.message);
|
|
7765
|
+
}
|
|
7766
|
+
const tested = await testToolOf(store, repo, local.id, clock(), { home: toolHome, omitEnv: ALL_CREDENTIAL_ENV });
|
|
7767
|
+
if (tested?.ok)
|
|
7768
|
+
return redirect(response, `/settings/tools?repo=${encodeURIComponent(repo)}${kit === null ? "" : `&kit=${kit}`}&said=${encodeURIComponent(`${local.label} is connected: ${tested.tools.length} action${tested.tools.length === 1 ? "" : "s"}.`)}#tool-${local.id}`);
|
|
7769
|
+
if (had === undefined)
|
|
7770
|
+
removeToolFrom(store, repo, local.id, who.name, clock(), toolHome);
|
|
7771
|
+
return back(tested?.problem ?? `${local.label} didn't answer.`);
|
|
7772
|
+
}
|
|
7773
|
+
if (service === null)
|
|
7774
|
+
return back("Choose a service to connect.");
|
|
7726
7775
|
const origin = consoleOrigin(request.headers.host);
|
|
7727
7776
|
if (origin === null)
|
|
7728
7777
|
return back("Connect tools from this computer (localhost) or from your https address.");
|
|
7729
|
-
const started = await startConnect({ service: service.id, repo, by: who.name, origin, kit }, options.connectFetch ?? fetch);
|
|
7778
|
+
const started = await startConnect({ service: service.id, repo: target, by: who.name, origin, kit: also === null ? kit : null }, options.connectFetch ?? fetch);
|
|
7730
7779
|
if (!started.ok)
|
|
7731
7780
|
return back(started.said);
|
|
7732
7781
|
for (const [key, visit] of connectVisits)
|
|
@@ -7742,6 +7791,12 @@ export function createDecisionServer(options) {
|
|
|
7742
7791
|
if (!visible(repo) || ![...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()].includes(repo))
|
|
7743
7792
|
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
7744
7793
|
const back = (key, words, anchor = "") => redirect(response, `/settings/tools?repo=${encodeURIComponent(repo)}&${key}=${encodeURIComponent(words)}${anchor}`);
|
|
7794
|
+
// As for Connect: a post from a page that showed another project is refused, never applied here.
|
|
7795
|
+
const shown = body.get("shown") ?? "";
|
|
7796
|
+
if (shown !== repo) {
|
|
7797
|
+
const page = visible(shown) && [...(admissionList() ?? []), ...managedRepos(), ...store.knownRepos()].includes(shown) ? shown : repo;
|
|
7798
|
+
return redirect(response, `/settings/tools?repo=${encodeURIComponent(page)}&problem=${encodeURIComponent("The project changed; try again.")}`);
|
|
7799
|
+
}
|
|
7745
7800
|
const action = body.get("action") ?? "";
|
|
7746
7801
|
const name = body.get("name") ?? "";
|
|
7747
7802
|
// Anything that adds what a build can run or reach, or a secret, needs the password.
|
|
@@ -15878,7 +15933,7 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
|
|
|
15878
15933
|
}
|
|
15879
15934
|
`;
|
|
15880
15935
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
15881
|
-
const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
15936
|
+
const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles a>svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
15882
15937
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
15883
15938
|
export const PAGE_CSS = STYLE + THEME_CONTROLS_CSS;
|
|
15884
15939
|
/** The Inbox tabs (console v2): a segmented track of real links; the dot marks a tab with something new, on phones only. */
|
|
@@ -15890,7 +15945,7 @@ const INBOX_TABS_CSS = '.inbox-tabs{display:inline-flex;gap:2px;max-width:100%;o
|
|
|
15890
15945
|
'.inbox-ask{margin:18px 0 0}.inbox-ask>h2{display:flex;align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:600}.inbox-ask>h2 .count{font:500 12px var(--so-mono,ui-monospace,monospace);font-variant-numeric:tabular-nums;color:var(--so-muted)}.inbox-ask h3{font-size:13px;font-weight:600;margin:12px 0 4px}' +
|
|
15891
15946
|
'.inbox-unread{display:none;position:absolute;top:4px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--so-signal)}' +
|
|
15892
15947
|
'@media (max-width:760px){.inbox-tabs{display:flex;width:100%}.inbox-tabs a{flex:1;justify-content:center;min-height:44px;padding:0 6px}.inbox-unread{display:block}}';
|
|
15893
|
-
const WORKSPACE_STYLE = styleAsset(STYLE + INBOX_TABS_CSS + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + GALLERY_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + CHECK_SETTINGS_CSS + DISCLOSURE_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
15948
|
+
const WORKSPACE_STYLE = styleAsset(STYLE + BRAND_MARK_CSS + INBOX_TABS_CSS + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + GALLERY_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + CHECK_SETTINGS_CSS + DISCLOSURE_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
15894
15949
|
/**
|
|
15895
15950
|
* The board's liveness: fetch this page's own fragment on a timer and swap
|
|
15896
15951
|
* it in place — no flicker, no scroll reset, no long-lived stream to manage,
|
|
@@ -24268,9 +24323,9 @@ async function latestReleaseFor(latest) {
|
|
|
24268
24323
|
latestSeen = { at: Date.now(), value };
|
|
24269
24324
|
return value;
|
|
24270
24325
|
}
|
|
24271
|
-
/** Settings destinations under short headings: an icon and a name each, and a chat app's state. */
|
|
24272
|
-
function settingsTiles(groups) {
|
|
24273
|
-
return `<nav class="settings-tiles" aria-label="Settings sections">${groups.map(group => `<section aria-label="${escape(group.title)}"><h2>${escape(group.title)}</h2><div>${group.tiles.map(tile => `<a href="${escape(tile.href)}">${strokeIcon(SETTINGS_TILE_ICONS.get(tile.href) ?? "")}<span>${escape(tile.label)}${tile.status === undefined ? "" :
|
|
24326
|
+
/** Settings destinations under short headings: an icon and a name each, and a chat app's logo and state. */
|
|
24327
|
+
export function settingsTiles(groups) {
|
|
24328
|
+
return `<nav class="settings-tiles" aria-label="Settings sections">${groups.map(group => `<section aria-label="${escape(group.title)}"><h2>${escape(group.title)}</h2><div>${group.tiles.map(tile => `<a href="${escape(tile.href)}">${tile.brand === undefined ? strokeIcon(SETTINGS_TILE_ICONS.get(tile.href) ?? "") : brandIconHtml(tile.brand)}<span>${escape(tile.label)}${tile.status === undefined ? "" :
|
|
24274
24329
|
`<span class="provider-status provider-status--${tile.status.tone}"><i aria-hidden="true"></i>${escape(tile.status.words)}</span>`}</span></a>`).join("")}</div></section>`).join("")}</nav>`;
|
|
24275
24330
|
}
|
|
24276
24331
|
/** Each destination once, in order: a group's heading, then its [href, label, icon] tiles. */
|
|
@@ -24288,10 +24343,10 @@ const SETTINGS_GROUPS = [
|
|
|
24288
24343
|
["/settings/integrations", "Integrations", `<path d="M9 2v6M15 2v6"/><path d="M6 8h12v4a6 6 0 0 1-12 0z"/><path d="M12 18v4"/>`],
|
|
24289
24344
|
]],
|
|
24290
24345
|
["Chat apps", [
|
|
24291
|
-
["/settings/telegram", "Telegram",
|
|
24292
|
-
["/settings/slack", "Slack",
|
|
24293
|
-
["/settings/discord", "Discord",
|
|
24294
|
-
["/settings/teams", "Teams",
|
|
24346
|
+
["/settings/telegram", "Telegram", ""],
|
|
24347
|
+
["/settings/slack", "Slack", ""],
|
|
24348
|
+
["/settings/discord", "Discord", ""],
|
|
24349
|
+
["/settings/teams", "Teams", ""],
|
|
24295
24350
|
]],
|
|
24296
24351
|
["Access and rules", [
|
|
24297
24352
|
["/settings/sign-in", "Sign-in", `<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`],
|
|
@@ -24322,13 +24377,15 @@ export function settingsGroups(messaging, telegramFailing = false) {
|
|
|
24322
24377
|
title,
|
|
24323
24378
|
tiles: tiles.map(([href, label]) => {
|
|
24324
24379
|
const app = CHAT_APP_TILES[href];
|
|
24325
|
-
if (app === undefined
|
|
24380
|
+
if (app === undefined)
|
|
24326
24381
|
return { href, label };
|
|
24382
|
+
if (messaging === null)
|
|
24383
|
+
return { href, label, brand: app };
|
|
24327
24384
|
if (!messaging.configured.includes(app))
|
|
24328
|
-
return { href, label, status: { tone: "off", words: "Not set up" } };
|
|
24385
|
+
return { href, label, brand: app, status: { tone: "off", words: "Not set up" } };
|
|
24329
24386
|
if (app === "telegram" && telegramFailing)
|
|
24330
|
-
return { href, label, status: { tone: "warn", words: "Has a problem" } };
|
|
24331
|
-
return { href, label, status: { tone: "ok", words: messaging.channel === app && !messaging.implicit ? "Gets alerts" : "Connected" } };
|
|
24387
|
+
return { href, label, brand: app, status: { tone: "warn", words: "Has a problem" } };
|
|
24388
|
+
return { href, label, brand: app, status: { tone: "ok", words: messaging.channel === app && !messaging.implicit ? "Gets alerts" : "Connected" } };
|
|
24332
24389
|
}),
|
|
24333
24390
|
}));
|
|
24334
24391
|
}
|
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;
|
|
18
19
|
ok: false;
|
|
19
20
|
message: string;
|
|
20
21
|
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;
|
|
33
34
|
ok: false;
|
|
34
35
|
message: string;
|
|
35
36
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/telegram.js
CHANGED
|
@@ -35,7 +35,9 @@ import { BATCH_MS, chatText, chatTitle, factLinkLabel, mentions, nameTelegramBot
|
|
|
35
35
|
import { LEAD_SAY_KIND, enqueueLeadLapses, leadSayEarlier, leadSayText, leadSubjectOf } from "./lead-voice.js";
|
|
36
36
|
import { applyTeamInbound, deliverTeamChats, teamCommand } from "./telegram-team.js";
|
|
37
37
|
import { applyFlowChoiceTap, applyFlowReply, applyFlowTap, FLOW_DECIDE_KEY, flowButtons, flowChoiceButtons, flowDecisionAt, flowSendKeyboardRow, flowSentContent } from "./telegram-flow.js";
|
|
38
|
-
import { flowChoiceAt, FLOW_CHOOSE_KEY, FLOW_SEND_KEY } from "./flow-send.js";
|
|
38
|
+
import { flowChoiceAt, flowSendTail, FLOW_CHOOSE_KEY, FLOW_SEND_KEY } from "./flow-send.js";
|
|
39
|
+
import { cleanFlowMessage, fitFlowMessage } from "./flow-items.js";
|
|
40
|
+
import { telegramReply } from "./reply-shape.js";
|
|
39
41
|
import { connectChannel, FLOW_WORDS, takeChannelMessage, watchedChannel } from "./chat-inbox.js";
|
|
40
42
|
import { focusContextFor, taskInCeiling } from "./chat-channel.js";
|
|
41
43
|
import { answersPrompt, applyDecideFeedback, applyDecideTap, decideFallbackLink, dropDecideTokens, hasLiveDecideTokens, linksFor, placeDecidePrompt, decideOffer, decideTargetOf, isDecideToken, mergedText, mintDecideButtons, offerFingerprint, openPromptFor, placeDecideTokens, recordChatMerge, retireDecideTokens } from "./chat-decide.js";
|
|
@@ -54,6 +56,8 @@ export const BRIDGE_LEASE_MS = 2 * 60_000;
|
|
|
54
56
|
export const DELIVERY_CLAIM_MS = 2 * 60_000;
|
|
55
57
|
/** Telegram's own message ceiling, with room for our part headers. */
|
|
56
58
|
const PART_CAP = 3_900;
|
|
59
|
+
/** Telegram's own limit on one message's text (Bot API sendMessage), less room for the line a tap adds under it. */
|
|
60
|
+
const TELEGRAM_TEXT_MAX = 4_096 - 300;
|
|
57
61
|
/** Pages of getUpdates one pass will read before reporting a backlog. */
|
|
58
62
|
const PAGE_BUDGET = 10;
|
|
59
63
|
// ---- pushed updates (v98) ----------------------------------------------------
|
|
@@ -840,7 +844,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
840
844
|
let answer;
|
|
841
845
|
try {
|
|
842
846
|
answer = batch.length > 1
|
|
843
|
-
? await transport("sendMediaGroup", { chat_id: binding.chatId, media: batch.map((one, index) => ({ type: "photo", media: `attach://shot${index}`, ...(index === 0 ? { caption: one.caption } : {}) })) }, undefined, upload(batch[0], "shot0"), batch.slice(1).map((one, index) => upload(one, `shot${index + 1}`)))
|
|
847
|
+
? await transport("sendMediaGroup", { chat_id: binding.chatId, media: batch.map((one, index) => ({ type: "photo", media: `attach://shot${index}`, ...(index === 0 || one.numbered === true ? { caption: one.caption } : {}) })) }, undefined, upload(batch[0], "shot0"), batch.slice(1).map((one, index) => upload(one, `shot${index + 1}`)))
|
|
844
848
|
: batch[0].photo
|
|
845
849
|
? await transport("sendPhoto", { chat_id: binding.chatId, caption: batch[0].caption }, undefined, upload(batch[0], "photo"))
|
|
846
850
|
: await transport("sendDocument", { chat_id: binding.chatId, caption: batch[0].caption }, undefined, upload(batch[0], "document"));
|
|
@@ -1192,7 +1196,7 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
|
|
|
1192
1196
|
const target = alreadyAccepted || !current ? null : decideTargetOf(notification);
|
|
1193
1197
|
const offer = decideOfferFor(store, binding, target, projects, clock(), evidenceRoot);
|
|
1194
1198
|
const decided = offer === null ? null : mintDecideButtons(store, decideSeat(binding), target, offer, clock());
|
|
1195
|
-
|
|
1199
|
+
let parts = split(offer?.text ?? `${notificationIdentity(notification, title !== undefined && mentions(words, title) ? undefined : title)}${words}`);
|
|
1196
1200
|
const fallback = offer === null ? decideFallbackLink(target) : null;
|
|
1197
1201
|
let button = fallback !== null ? (() => { try {
|
|
1198
1202
|
return phoneLinkButton(phoneOrigin?.() ?? null, fallback);
|
|
@@ -1218,6 +1222,17 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
|
|
|
1218
1222
|
} })();
|
|
1219
1223
|
if (sentRow !== null)
|
|
1220
1224
|
button = sentRow.length === 0 ? null : sentRow;
|
|
1225
|
+
// After research (flow-items.ts): each item numbered under the summary, its words cleaned as the rest of the message's
|
|
1226
|
+
// are, within Telegram's limit with every link kept — one message, unless head, links and tail alone overflow it.
|
|
1227
|
+
let entities = [];
|
|
1228
|
+
if (sent?.items !== undefined && sent.items.length > 0 && offer === null) {
|
|
1229
|
+
const subject = chatText(leadSubjectOf(store, notification, binding.approver), task);
|
|
1230
|
+
const head = `${notificationIdentity(notification, title !== undefined && mentions(subject, title) ? undefined : title)}${subject}`;
|
|
1231
|
+
const message = cleanFlowMessage({ head, summary: sent.summary, items: sent.items, tail: flowSendTail(sent) }, words => chatText(words, task));
|
|
1232
|
+
const voiced = fitFlowMessage(message, TELEGRAM_TEXT_MAX, shaped => telegramReply(shaped).text.length).map(telegramReply);
|
|
1233
|
+
parts = voiced.map(one => one.text);
|
|
1234
|
+
entities = voiced.map(one => one.entities);
|
|
1235
|
+
}
|
|
1221
1236
|
// A teammate's question (v93): its options and "Answer in words", for the person it asks.
|
|
1222
1237
|
const asked = flowKeys === null && choiceKeys === null ? openQuestionOf(store, notification.dedupeKey) : null;
|
|
1223
1238
|
const questionKeys = asked === null ? null : telegramQuestionButtons(store, binding, asked, clock());
|
|
@@ -1227,7 +1242,7 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
|
|
|
1227
1242
|
const final = index === parts.length - 1;
|
|
1228
1243
|
const decideKeys = decided === null ? [] : decideKeyboard(phoneOrigin, decided.rows);
|
|
1229
1244
|
const keyboard = !final ? undefined : keys !== null ? [...keys.keyboard, ...(button === null ? [] : [button])] : decideKeys.length > 0 ? decideKeys : button !== null ? [button] : undefined;
|
|
1230
|
-
const sent = await sender(part, keyboard);
|
|
1245
|
+
const sent = await sender(part, keyboard, undefined, entities[index]?.length ? entities[index] : undefined);
|
|
1231
1246
|
if (!sent.ok) {
|
|
1232
1247
|
if (decided !== null)
|
|
1233
1248
|
dropDecideTokens(store, decided.tokens, clock());
|
|
@@ -1358,6 +1373,19 @@ async function editProgress(transport, chatId, messageId, text, keyboard, entiti
|
|
|
1358
1373
|
const confirmed = answer.result?.message_id;
|
|
1359
1374
|
return String(confirmed) === messageId ? { ok: true, messageId } : { ok: false, error: "Telegram did not confirm the progress message identity" };
|
|
1360
1375
|
}
|
|
1376
|
+
/** A tapped message's own formatting, read back for its edit: only bold and links that still fall inside the text. */
|
|
1377
|
+
function keptEntities(raw, length) {
|
|
1378
|
+
return (raw ?? []).flatMap((one) => {
|
|
1379
|
+
if (typeof one !== "object" || one === null)
|
|
1380
|
+
return [];
|
|
1381
|
+
const { type, offset, length: size, url } = one;
|
|
1382
|
+
if (typeof offset !== "number" || typeof size !== "number" || !Number.isSafeInteger(offset) || !Number.isSafeInteger(size) || offset < 0 || size <= 0 || offset + size > length)
|
|
1383
|
+
return [];
|
|
1384
|
+
if (type === "bold")
|
|
1385
|
+
return [{ type, offset, length: size }];
|
|
1386
|
+
return type === "text_link" && typeof url === "string" && /^https?:\/\/\S+$/.test(url) ? [{ type, offset, length: size, url }] : [];
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1361
1389
|
function receiptFor(botId, chatId, messageId) {
|
|
1362
1390
|
return `telegram:${botId}:${chatId}:${messageId ?? "?"}`;
|
|
1363
1391
|
}
|
|
@@ -2037,7 +2065,7 @@ function applyCallback(context, update, effects) {
|
|
|
2037
2065
|
});
|
|
2038
2066
|
});
|
|
2039
2067
|
};
|
|
2040
|
-
const editText = (text, keyboard) => {
|
|
2068
|
+
const editText = (text, keyboard, entities) => {
|
|
2041
2069
|
if (message === undefined)
|
|
2042
2070
|
return;
|
|
2043
2071
|
const chatId = message.chat === undefined ? null : String(message.chat.id);
|
|
@@ -2049,6 +2077,7 @@ function applyCallback(context, update, effects) {
|
|
|
2049
2077
|
chat_id: chatId,
|
|
2050
2078
|
message_id: messageId,
|
|
2051
2079
|
text,
|
|
2080
|
+
...(entities === undefined || entities.length === 0 ? {} : { entities }),
|
|
2052
2081
|
link_preview_options: { is_disabled: true },
|
|
2053
2082
|
...(keyboard === undefined ? {} : { reply_markup: { inline_keyboard: keyboard } }),
|
|
2054
2083
|
});
|
|
@@ -2166,11 +2195,13 @@ function applyCallback(context, update, effects) {
|
|
|
2166
2195
|
return;
|
|
2167
2196
|
}
|
|
2168
2197
|
const repos = context.projects === null ? null : telegramConversationRepos(store, binding.approver, context.projects);
|
|
2198
|
+
const listed = (flowSentContent(store, choiceAction.card, choiceAction.entry)?.items?.length ?? 0) > 0;
|
|
2169
2199
|
for (const effect of applyFlowChoiceTap(store, binding, choiceAction, { text: message.text ?? "" }, repos, clock())) {
|
|
2170
2200
|
if (effect.kind === "ack")
|
|
2171
2201
|
ack(effect.text);
|
|
2202
|
+
// An item list's message (flow-items.ts) keeps its bold titles and labelled links under what was chosen; any other stays plain.
|
|
2172
2203
|
else if (effect.kind === "edit")
|
|
2173
|
-
editText(effect.text);
|
|
2204
|
+
editText(effect.text, undefined, listed ? keptEntities(message.entities, effect.text.length) : undefined);
|
|
2174
2205
|
}
|
|
2175
2206
|
return;
|
|
2176
2207
|
}
|
package/dist/tools-ui.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
|
-
* entered here and nowhere else, and never shown again. */
|
|
3
1
|
import { type FoundTool, type ProjectTool, type ToolSpec } from "./project-tools.js";
|
|
4
|
-
export declare const TOOLS_CSS = ".tools{max-width:780px;min-width:0;overflow-wrap:anywhere}.tools .card{padding:16px 18px;margin:12px 0;min-width:0}.tools .card h2{font-size:1.05rem;margin:0;color:var(--so-ink);font-weight:600}.tools .tool-heading{display:flex;align-items:
|
|
2
|
+
export declare const TOOLS_CSS = ".tools{max-width:780px;min-width:0;overflow-wrap:anywhere}.tools .card{padding:16px 18px;margin:12px 0;min-width:0}.tools .card h2{font-size:1.05rem;margin:0;color:var(--so-ink);font-weight:600}.tools .tool-heading{display:flex;align-items:center;gap:10px}.tools .tool-heading h2{flex:1;min-width:0}.tools .tool-state{font-size:.85rem;flex-shrink:0;color:var(--so-muted)}.tools .tool-state[data-ready=\"false\"]{color:var(--so-warning)}.tools .card>p{margin:6px 0 0}.tools code{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-size:.85rem}.tools form{display:grid;gap:10px;margin:0}.tools label{display:grid;gap:6px}.tools input,.tools select{box-sizing:border-box;width:100%;max-width:100%;min-width:0}.tools button{justify-self:start;white-space:nowrap;min-height:44px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.tools details:not(.card){margin:6px 0 0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.tools details:not(.card)>summary{min-height:36px;display:flex;align-items:center;font-size:.9rem;color:var(--so-muted);cursor:pointer}.tools details:not(.card)[open]>summary{color:var(--so-ink)}.tools details.card>summary{cursor:pointer;min-height:44px;display:flex;align-items:center;font-weight:600}.tools summary{list-style:none;gap:10px}.tools summary::-webkit-details-marker{display:none}.tools summary::before{content:\"\";flex-shrink:0;width:6px;height:6px;margin:0 2px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .15s}.tools details[open]>summary::before{transform:rotate(45deg)}.tools details:not(.card) form{padding:4px 0 10px}.tools .tool-facts{margin:0 0 8px;display:grid;gap:4px;font-size:.9rem}.tools .tool-facts p{margin:0}.tools .tool-controls{display:flex;gap:12px;flex-wrap:wrap;margin-top:12px}.tools .tool-found{display:flex;gap:10px;align-items:flex-start}.tools .tool-found input{flex:0 0 auto;width:18px;height:18px;margin-top:3px}.tools .tool-found span{min-width:0}.tools .problem{padding:10px 12px;margin:8px 0 0}.tools .tool-connect{scroll-margin-top:72px}.tools .connect-wanted{background:var(--so-accent);color:var(--so-on-accent);border-color:var(--so-accent)}.tools .connect-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.tools .connect-tile{display:grid;grid-template-columns:auto 1fr;column-gap:10px;align-items:center;justify-self:stretch;text-align:left;white-space:normal;padding:10px 12px;border:1px solid var(--so-line);border-radius:10px;background:var(--so-paper);color:var(--so-ink);min-height:60px;font:inherit;cursor:pointer;scroll-margin-top:96px}.tools .connect-tile:hover{border-color:var(--so-input-line);background:var(--so-raised)}.tools .connect-tile:target,.tools .connect-tile:focus-visible{outline:2px solid var(--so-accent);outline-offset:2px}.tools .connect-tile .brand-mark{grid-row:span 2}.tools .connect-tile strong{font-weight:600;line-height:1.3}.tools .connect-tile>span:not(.brand-mark){font-size:.8rem;color:var(--so-muted);line-height:1.35;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.tools .connect-tile[data-state=\"connected\"]>span:not(.brand-mark){color:var(--so-success)}.tools .connect-local{grid-column:1/-1}.tools .connect-local>span:not(.brand-mark){-webkit-line-clamp:unset}.tools .connect-problem{margin:0}.tools .connect-problem:focus{outline:2px solid var(--so-accent);outline-offset:2px}.tools .connect-tile:disabled{cursor:default;opacity:.6}@media(max-width:600px){.tools .connect-grid{grid-template-columns:1fr 1fr}.tools .connect-tile:not(.connect-local)>span:not(.brand-mark){display:none}.tools .connect-local>span:not(.brand-mark){grid-column:2}.tools .connect-tile{min-height:52px;padding:8px 10px}.tools .connect-tile .brand-mark{grid-row:auto}.tools input,.tools select{font-size:16px}.tools .card{padding:14px}}";
|
|
5
3
|
export type ToolsView = {
|
|
6
4
|
repo: string;
|
|
7
5
|
project: string;
|
|
@@ -11,6 +9,7 @@ export type ToolsView = {
|
|
|
11
9
|
label: string;
|
|
12
10
|
about: string;
|
|
13
11
|
state: "connected" | "open" | "taken";
|
|
12
|
+
local?: true;
|
|
14
13
|
}[];
|
|
15
14
|
kit: string | null;
|
|
16
15
|
/** The service a link asked for (a kit's checklist, the lead): its own button under the password. */
|
|
@@ -23,7 +22,17 @@ export type ToolsView = {
|
|
|
23
22
|
label: string;
|
|
24
23
|
})[];
|
|
25
24
|
found: FoundTool[];
|
|
25
|
+
/** The person's other projects and the one-click services still open there: where a connection here can also go. */
|
|
26
|
+
others?: {
|
|
27
|
+
repo: string;
|
|
28
|
+
project: string;
|
|
29
|
+
open: string[];
|
|
30
|
+
}[];
|
|
26
31
|
};
|
|
32
|
+
/** The project chooser: it opens the chosen project at once; without scripts, its button does. */
|
|
33
|
+
export declare function toolsProjectPicker(projects: readonly string[], chosen: string, keep?: Record<string, string>): string;
|
|
34
|
+
/** Choosing another project marks every form on the page as stale before it reloads, so a tap racing the reload is refused. */
|
|
35
|
+
export declare const TOOLS_PROJECT_SCRIPT = "(function(){var s=document.querySelector('select[data-tools-project]');if(!s)return;s.addEventListener('change',function(){document.querySelectorAll('input[name=shown]').forEach(function(i){i.value=s.value;});});})();";
|
|
27
36
|
export declare function toolsHtml(view: ToolsView, csrf: string, canManage: boolean, message?: {
|
|
28
37
|
said?: string | null;
|
|
29
38
|
problem?: string | null;
|