toolroll 0.9.26 → 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 +11 -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/desktop-update.d.ts +1 -1
- 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/result-shots.d.ts +5 -1
- package/dist/result-shots.js +33 -3
- package/dist/scout.js +1 -1
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +16 -13
- package/dist/setup-guide.d.ts +2 -2
- package/dist/telegram.js +37 -6
- package/dist/tools-ui.d.ts +1 -3
- package/dist/tools-ui.js +5 -4
- package/package.json +3 -1
package/dist/result-shots.js
CHANGED
|
@@ -19,6 +19,7 @@ import { imageDimensions, readVerifiedProofForRun, readVerifiedReport, RETENTION
|
|
|
19
19
|
import { resultImageFileName, verifyResultImage } from "./chat-evidence.js";
|
|
20
20
|
import { publicChatText } from "./chat-display.js";
|
|
21
21
|
import { chatTitle } from "./chat-voice.js";
|
|
22
|
+
import { itemCaption, readFlowItems } from "./flow-items.js";
|
|
22
23
|
import { FLOW_SHOTS_KIND, RESULT_SHOTS_KIND } from "./store.js";
|
|
23
24
|
/** "Up to 4": the most screenshots one result ever sends. */
|
|
24
25
|
export const RESULT_SHOTS_MAX = 4;
|
|
@@ -58,6 +59,28 @@ function reportCaptions(store, root, taskRef, run) {
|
|
|
58
59
|
}
|
|
59
60
|
return captions;
|
|
60
61
|
}
|
|
62
|
+
/** A flow visit's item screenshots from this run, in item order, each with its numbered caption (flow-items.ts). */
|
|
63
|
+
function itemCaptions(store, dedupeKey, run) {
|
|
64
|
+
const captions = new Map();
|
|
65
|
+
const visit = /^flow-shots:([1-9][0-9]{0,14}):([1-9][0-9]{0,9})$/.exec(dedupeKey ?? "");
|
|
66
|
+
const kept = visit === null ? null : store.flowSend(Number(visit[1]), Number(visit[2]));
|
|
67
|
+
if (kept === null)
|
|
68
|
+
return captions;
|
|
69
|
+
let content;
|
|
70
|
+
try {
|
|
71
|
+
content = JSON.parse(kept.contentJson);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return captions;
|
|
75
|
+
}
|
|
76
|
+
if (content.shots?.run !== run)
|
|
77
|
+
return captions;
|
|
78
|
+
readFlowItems(content.items).forEach((item, index) => {
|
|
79
|
+
if (item.shot !== null && !captions.has(item.shot))
|
|
80
|
+
captions.set(item.shot, publicChatText(itemCaption(index + 1, item), 200).trim());
|
|
81
|
+
});
|
|
82
|
+
return captions;
|
|
83
|
+
}
|
|
61
84
|
function isPhoto(bytes, format) {
|
|
62
85
|
const size = imageDimensions(bytes, format);
|
|
63
86
|
if (size === null || size.width === 0 || size.height === 0)
|
|
@@ -92,7 +115,13 @@ export function resultShotsFor(store, evidenceRoot, repos, row, sent = new Set()
|
|
|
92
115
|
const latest = store.runsFor(row.taskRef).find(one => one.finishedAt !== null && ["builder", "repair", "scout"].includes(one.role));
|
|
93
116
|
if (family?.current.id !== row.taskId || latest?.id !== row.run)
|
|
94
117
|
return line("a newer result replaced this one");
|
|
95
|
-
|
|
118
|
+
// After research, a flow's message numbers the report's items (flow-items.ts): their screenshots go first, in that
|
|
119
|
+
// order, each captioned with its item's number, so "2." here is item 2 in the text.
|
|
120
|
+
const items = row.kind === FLOW_SHOTS_KIND ? itemCaptions(store, row.dedupeKey, row.run) : new Map();
|
|
121
|
+
const order = [...items.keys()];
|
|
122
|
+
const rank = (id) => { const at = order.indexOf(id); return at < 0 ? order.length : at; };
|
|
123
|
+
const chosen = store.artifactsFor(row.run).filter(one => one.kind === "screenshot")
|
|
124
|
+
.map((one, at) => ({ one, at })).sort((a, b) => rank(a.one.id) - rank(b.one.id) || a.at - b.at).map(({ one }) => one).slice(0, limit);
|
|
96
125
|
if (chosen.length === 0)
|
|
97
126
|
return { kind: "none", why: "none" };
|
|
98
127
|
const captions = ownCaptions(store, evidenceRoot, row.run);
|
|
@@ -107,11 +136,12 @@ export function resultShotsFor(store, evidenceRoot, repos, row, sent = new Set()
|
|
|
107
136
|
return line(verified.problem);
|
|
108
137
|
}
|
|
109
138
|
const own = captions.get(SCREENSHOT_CAPTURE.exec(artifact.capture)?.[1] ?? "") ?? reported.get(artifact.id) ?? null;
|
|
139
|
+
const item = items.get(artifact.id);
|
|
110
140
|
shots.push({
|
|
111
141
|
artifact: artifact.id, sha256: artifact.sha256, bytes: verified.bytes, format: verified.format,
|
|
112
142
|
fileName: resultImageFileName(row.taskId, row.run, artifact.id, verified.format),
|
|
113
|
-
caption: index === 0 ? (own === null ? title : `${title} · ${own}`) : own ?? `${title} · screenshot ${index + 1}`,
|
|
114
|
-
photo: isPhoto(verified.bytes, verified.format),
|
|
143
|
+
caption: item !== undefined ? item : index === 0 ? (own === null ? title : `${title} · ${own}`) : own ?? `${title} · screenshot ${index + 1}`,
|
|
144
|
+
photo: isPhoto(verified.bytes, verified.format), ...(item === undefined ? {} : { numbered: true }),
|
|
115
145
|
});
|
|
116
146
|
}
|
|
117
147
|
const remaining = shots.filter(one => !sent.has(one.artifact));
|
package/dist/scout.js
CHANGED
|
@@ -163,7 +163,7 @@ function scoutBrief(title, goal, outOfScope, mailbox, reportFile, answers, struc
|
|
|
163
163
|
' "summary": "one paragraph the operator reads first",',
|
|
164
164
|
' "report": "the report as markdown: what you found, the evidence, the risks",',
|
|
165
165
|
' "followUps": [{ "title": "one line", "goal": "what success looks like" }],',
|
|
166
|
-
' "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" }],',
|
|
167
167
|
' "images": [{ "file": "home.png", "caption": "one line", "url": "the page it shows" }]',
|
|
168
168
|
"}",
|
|
169
169
|
`Caps: title ${REPORT_LIMITS.title}, summary ${REPORT_LIMITS.summary}, report ${REPORT_LIMITS.document} bytes,`,
|
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
|
@@ -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";
|
|
@@ -15932,7 +15933,7 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
|
|
|
15932
15933
|
}
|
|
15933
15934
|
`;
|
|
15934
15935
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
15935
|
-
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}`;
|
|
15936
15937
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
15937
15938
|
export const PAGE_CSS = STYLE + THEME_CONTROLS_CSS;
|
|
15938
15939
|
/** The Inbox tabs (console v2): a segmented track of real links; the dot marks a tab with something new, on phones only. */
|
|
@@ -15944,7 +15945,7 @@ const INBOX_TABS_CSS = '.inbox-tabs{display:inline-flex;gap:2px;max-width:100%;o
|
|
|
15944
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}' +
|
|
15945
15946
|
'.inbox-unread{display:none;position:absolute;top:4px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--so-signal)}' +
|
|
15946
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}}';
|
|
15947
|
-
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}');
|
|
15948
15949
|
/**
|
|
15949
15950
|
* The board's liveness: fetch this page's own fragment on a timer and swap
|
|
15950
15951
|
* it in place — no flicker, no scroll reset, no long-lived stream to manage,
|
|
@@ -24322,9 +24323,9 @@ async function latestReleaseFor(latest) {
|
|
|
24322
24323
|
latestSeen = { at: Date.now(), value };
|
|
24323
24324
|
return value;
|
|
24324
24325
|
}
|
|
24325
|
-
/** Settings destinations under short headings: an icon and a name each, and a chat app's state. */
|
|
24326
|
-
function settingsTiles(groups) {
|
|
24327
|
-
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 ? "" :
|
|
24328
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>`;
|
|
24329
24330
|
}
|
|
24330
24331
|
/** Each destination once, in order: a group's heading, then its [href, label, icon] tiles. */
|
|
@@ -24342,10 +24343,10 @@ const SETTINGS_GROUPS = [
|
|
|
24342
24343
|
["/settings/integrations", "Integrations", `<path d="M9 2v6M15 2v6"/><path d="M6 8h12v4a6 6 0 0 1-12 0z"/><path d="M12 18v4"/>`],
|
|
24343
24344
|
]],
|
|
24344
24345
|
["Chat apps", [
|
|
24345
|
-
["/settings/telegram", "Telegram",
|
|
24346
|
-
["/settings/slack", "Slack",
|
|
24347
|
-
["/settings/discord", "Discord",
|
|
24348
|
-
["/settings/teams", "Teams",
|
|
24346
|
+
["/settings/telegram", "Telegram", ""],
|
|
24347
|
+
["/settings/slack", "Slack", ""],
|
|
24348
|
+
["/settings/discord", "Discord", ""],
|
|
24349
|
+
["/settings/teams", "Teams", ""],
|
|
24349
24350
|
]],
|
|
24350
24351
|
["Access and rules", [
|
|
24351
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"/>`],
|
|
@@ -24376,13 +24377,15 @@ export function settingsGroups(messaging, telegramFailing = false) {
|
|
|
24376
24377
|
title,
|
|
24377
24378
|
tiles: tiles.map(([href, label]) => {
|
|
24378
24379
|
const app = CHAT_APP_TILES[href];
|
|
24379
|
-
if (app === undefined
|
|
24380
|
+
if (app === undefined)
|
|
24380
24381
|
return { href, label };
|
|
24382
|
+
if (messaging === null)
|
|
24383
|
+
return { href, label, brand: app };
|
|
24381
24384
|
if (!messaging.configured.includes(app))
|
|
24382
|
-
return { href, label, status: { tone: "off", words: "Not set up" } };
|
|
24385
|
+
return { href, label, brand: app, status: { tone: "off", words: "Not set up" } };
|
|
24383
24386
|
if (app === "telegram" && telegramFailing)
|
|
24384
|
-
return { href, label, status: { tone: "warn", words: "Has a problem" } };
|
|
24385
|
-
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" } };
|
|
24386
24389
|
}),
|
|
24387
24390
|
}));
|
|
24388
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;
|
package/dist/tools-ui.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
2
|
* entered here and nowhere else, and never shown again. */
|
|
3
|
+
import { brandMarkHtml, TILE_PHONE } from "./brand-mark.js";
|
|
3
4
|
import { toolCommandLine, toolStanding } from "./project-tools.js";
|
|
4
5
|
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5
6
|
const hidden = (data) => Object.entries(data).map(([k, v]) => `<input type="hidden" name="${e(k)}" value="${e(v)}">`).join("");
|
|
6
|
-
export 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:
|
|
7
|
+
export 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}${TILE_PHONE}{.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}}`;
|
|
7
8
|
/** The project chooser: it opens the chosen project at once; without scripts, its button does. */
|
|
8
9
|
export function toolsProjectPicker(projects, chosen, keep = {}) {
|
|
9
10
|
if (projects.length < 2)
|
|
@@ -26,7 +27,7 @@ function toolCard(one, base, manage, project) {
|
|
|
26
27
|
const details = `<details><summary>Details</summary><div class="tool-facts"><p>Starts: <code>${e(toolCommandLine(tool.spec))}</code></p><p>From ${e(tool.source)} · added by ${e(tool.createdBy)}</p>${tested}</div></details>`;
|
|
27
28
|
const failed = tool.lastTest !== null && !tool.lastTest.ok ? `<p class="problem" role="status">${e(tool.lastTest.problem ?? "The last test failed.")}</p>` : "";
|
|
28
29
|
const controls = manage ? `<div class="tool-controls"><form method="post" action="/settings/tools/change">${hidden({ ...base, action: "test", name: tool.name })}<button aria-label="Test ${e(tool.name)} in ${e(project)}">Test</button></form><form method="post" action="/settings/tools/change">${hidden({ ...base, action: "remove", name: tool.name })}<button class="secondary">Remove from ${e(project)}</button></form></div>` : "";
|
|
29
|
-
return `<article class="card" id="tool-${e(tool.name)}"><div class="tool-heading"
|
|
30
|
+
return `<article class="card" id="tool-${e(tool.name)}"><div class="tool-heading">${brandMarkHtml(tool.source === "custom" ? null : tool.name, tool.name, standing.ready)}<h2>${e(tool.name)}</h2><span class="tool-state" data-ready="${standing.ready}">${e(standing.words)}</span></div><p>${e(tool.spec.about)}</p>${failed}${secrets}${details}${controls}</article>`;
|
|
30
31
|
}
|
|
31
32
|
/** A service connected here, offered to the person's other projects: each button signs in again for that project (tokens are never copied). */
|
|
32
33
|
function elsewhere(view, base) {
|
|
@@ -46,8 +47,8 @@ export function toolsHtml(view, csrf, canManage, message = {}) {
|
|
|
46
47
|
const custom = `<details><summary>Add your own</summary><form method="post" action="/settings/tools/change">${hidden({ ...base, action: "add-custom" })}<label>Name<input name="name" pattern="[a-z0-9][a-z0-9_\\-]{0,39}" maxlength="40" required placeholder="for example: postgres"></label><label>It runs as<select name="transport"><option value="stdio">A program on this computer</option><option value="http">A web address</option></select></label><label>Command or address<input name="target" required placeholder="npx -y some-mcp-server@1.2.3, or https://…"></label><label>Secrets it needs (names, comma separated)<input name="secrets" placeholder="for example: DATABASE_URL"></label><p class="meta">For a web address, the first secret is sent as its sign-in token. Set each secret's value after adding.</p>${password}<button>Add${to}</button></form></details>`;
|
|
47
48
|
// One click: sign in on the service's own page. The password is the same as for adding any tool.
|
|
48
49
|
const tiles = view.connections.map(one => one.state === "taken"
|
|
49
|
-
? `<button type="button" class="connect-tile" id="connect-${e(one.id)}" data-state="taken" disabled
|
|
50
|
-
: `<button name="service" value="${e(one.id)}" class="connect-tile${one.local ? " connect-local" : ""}" id="connect-${e(one.id)}" data-state="${one.state}" aria-label="${one.state === "connected" ? `${e(one.label)}, connected${to}. ${one.local ? "Connect again" : "Sign in again"}` : `Connect ${e(one.label)}${to}`}"
|
|
50
|
+
? `<button type="button" class="connect-tile" id="connect-${e(one.id)}" data-state="taken" disabled>${brandMarkHtml(one.id, one.label, false)}<strong>${e(one.label)}</strong><span>Added another way</span></button>`
|
|
51
|
+
: `<button name="service" value="${e(one.id)}" class="connect-tile${one.local ? " connect-local" : ""}" id="connect-${e(one.id)}" data-state="${one.state}" aria-label="${one.state === "connected" ? `${e(one.label)}, connected${to}. ${one.local ? "Connect again" : "Sign in again"}` : `Connect ${e(one.label)}${to}`}">${brandMarkHtml(one.id.replace(/-desktop$/, ""), one.label, one.state === "connected")}<strong>${e(one.label)}</strong><span>${one.state === "connected" ? "Connected" : e(one.about)}</span></button>`).join("");
|
|
51
52
|
const wanted = view.connections.find(one => one.id === view.wanted && one.state !== "taken");
|
|
52
53
|
const first = wanted === undefined ? "" : `<button name="service" value="${e(wanted.id)}" class="connect-wanted">${wanted.state === "connected" && !wanted.local ? `Sign in to ${e(wanted.label)} again for ${e(view.project)}` : `Connect ${e(wanted.label)}${to}`}</button>`;
|
|
53
54
|
const connecting = manage && view.connections.length > 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
"@types/react-dom": "19.3.0",
|
|
71
71
|
"esbuild": "0.28.2",
|
|
72
72
|
"happy-dom": "20.12.0",
|
|
73
|
+
"paper": "0.12.18",
|
|
74
|
+
"simple-icons": "16.34.0",
|
|
73
75
|
"tailwindcss": "4.3.3",
|
|
74
76
|
"tsx": "4.23.12",
|
|
75
77
|
"typescript": "7.0.2",
|