toolroll 0.9.53 → 0.9.55
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 +17 -0
- package/README.md +5 -4
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +15 -20
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +2 -3
- package/dist/browser-workspace.js +0 -3
- package/dist/chat-ask.js +8 -8
- package/dist/chat-core.d.ts +171 -0
- package/dist/chat-core.js +288 -0
- package/dist/chat-decide.d.ts +10 -8
- package/dist/chat-decide.js +26 -21
- package/dist/chat-delivery-state.d.ts +51 -75
- package/dist/chat-delivery-state.js +140 -170
- package/dist/chat-delivery.d.ts +21 -2
- package/dist/chat-delivery.js +313 -194
- package/dist/chat-flow.d.ts +29 -1
- package/dist/chat-flow.js +24 -50
- package/dist/chat-messages.d.ts +319 -0
- package/dist/chat-messages.js +841 -0
- package/dist/chat-migration.d.ts +30 -0
- package/dist/chat-migration.js +802 -0
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-quiet.d.ts +3 -0
- package/dist/chat-quiet.js +5 -0
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +9 -3
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/chat-content.d.ts +71 -2
- package/dist/contracts/chat-content.js +22 -3
- package/dist/contracts/chat-tables.d.ts +11 -4
- package/dist/contracts/chat-tables.js +165 -87
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +6 -5
- package/dist/contracts/discord-callback.js +2 -1
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/slack-callback.js +3 -2
- package/dist/contracts/store-json.d.ts +1 -5
- package/dist/contracts/store-json.js +1 -4
- package/dist/contracts/teams-callback.js +2 -1
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/discord-chat.js +19 -38
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +21 -55
- package/dist/discord.js +4 -14
- package/dist/envelope.js +10 -0
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +4 -4
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-triggers.d.ts +1 -0
- package/dist/flow-triggers.js +16 -14
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +19 -30
- package/dist/flows.js +2 -1
- package/dist/guides.js +108 -3
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +18 -20
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/lead-context.d.ts +11 -1
- package/dist/lead-context.js +6 -8
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +2 -1
- package/dist/limits-ui.js +7 -7
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mate-tools.js +3 -2
- package/dist/mcp-oauth.js +35 -36
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/modes.d.ts +13 -1
- package/dist/modes.js +17 -1
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +30 -0
- package/dist/notification-rows.js +102 -0
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.js +16 -9
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/plane-review.js +5 -11
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +15 -27
- package/dist/project-delete.js +21 -26
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/result-shots.js +2 -2
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +69 -58
- package/dist/server/chat.d.ts +3 -6
- package/dist/server/chat.js +969 -1006
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +840 -766
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +5 -2
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +580 -572
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +37 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +147 -0
- package/dist/server/render-settings.js +577 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +163 -75
- package/dist/server/runtime.d.ts +15 -7
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2182 -2117
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1486 -1418
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-api.d.ts +5 -1
- package/dist/slack-api.js +2 -2
- package/dist/slack-chat.d.ts +9 -4
- package/dist/slack-chat.js +29 -52
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +23 -57
- package/dist/slack.js +6 -16
- package/dist/spend-ui.d.ts +2 -1
- package/dist/spend-ui.js +9 -32
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store.d.ts +97 -364
- package/dist/store.js +207 -1531
- package/dist/surface.js +14 -3
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teammate-question.js +12 -12
- package/dist/teammates-ui.d.ts +5 -4
- package/dist/teammates-ui.js +61 -90
- package/dist/teams-chat.js +12 -9
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +22 -33
- package/dist/teams.js +4 -5
- package/dist/telegram-flow.js +5 -5
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram.js +4 -4
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +10 -0
- package/dist/toolroll-update.js +50 -3
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-revision.js +3 -1
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -1
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/slack-state.d.ts +0 -13
- package/dist/slack-state.js +0 -10
package/dist/control-ui.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
/** Shared setup views over the existing project configuration and approval doors. */
|
|
2
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
1
3
|
import { ASSISTANTS } from "./setup-guide.js";
|
|
2
4
|
import { openRouterPicker } from "./openrouter-models.js";
|
|
3
|
-
const escape = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4
|
-
export function hiddenFields(values) {
|
|
5
|
-
return Object.entries(values).map(([name, value]) => `<input type="hidden" name="${escape(name)}" value="${escape(value)}">`).join("");
|
|
6
|
-
}
|
|
7
5
|
export function connectionWords(connection) {
|
|
8
6
|
return {
|
|
9
7
|
connected: "Connected", "signed-out": "Not signed in", "not-installed": "CLI not installed",
|
|
@@ -12,42 +10,19 @@ export function connectionWords(connection) {
|
|
|
12
10
|
}
|
|
13
11
|
export function connectionHtml(provider, connection, task = "") {
|
|
14
12
|
const query = new URLSearchParams({ provider, ...(task ? { task } : {}) });
|
|
15
|
-
return `<div class="card assistant-account"><p><strong>${
|
|
16
|
-
`<p class="meta">${[connection.email, connection.plan, connection.method].filter(Boolean).map(one => escape(one)).join(" · ")}</p>` +
|
|
17
|
-
`<p class="meta">Checked ${escape(new Date(connection.checkedAt).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", timeZoneName: "short" }))} on the computer running this console. Sign-in does not prove model access or remaining quota.</p>` +
|
|
18
|
-
`<a class="button-link" href="/control/connection?${escape(query.toString())}">${connection.state === "connected" ? "Manage connection" : "Connect account"}</a> · ` +
|
|
19
|
-
`<a href="/control?${escape(query.toString())}&check-connection=1">Check again</a></div>`;
|
|
13
|
+
return html `<div class="card assistant-account"><p><strong>${ASSISTANTS[provider].name} · ${connectionWords(connection)}</strong></p><p class="meta">${[connection.email, connection.plan, connection.method].filter(Boolean).join(" · ")}</p><p class="meta">Checked ${new Date(connection.checkedAt).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", timeZoneName: "short" })} on the computer running this console. Sign-in does not prove model access or remaining quota.</p><a class="button-link" href="/control/connection?${query.toString()}">${connection.state === "connected" ? "Manage connection" : "Connect account"}</a> · <a href="/control?${query.toString()}&check-connection=1">Check again</a></div>`;
|
|
20
14
|
}
|
|
21
15
|
export function controlSetupHtml(data) {
|
|
22
16
|
const { provider, inputs, task = "" } = data;
|
|
23
17
|
const query = new URLSearchParams({ provider, ...(task ? { task } : {}) });
|
|
24
18
|
const model = data.catalog !== null
|
|
25
19
|
? openRouterPicker(data.catalog, inputs.model || null, `/control?${query}&refresh-models=1`)
|
|
26
|
-
: `<label>Model<input type="text" name="model" value="${
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`<p class="meta">Choose the default builder for new tasks and the preparation command for this project.</p>` +
|
|
30
|
-
`<nav class="assistant-picker row" aria-label="Choose provider">${Object.keys(ASSISTANTS).map(id => `<a class="button-link" href="/control?${escape(new URLSearchParams({ provider: id, ...(task ? { task } : {}) }).toString())}"${id === provider ? ' aria-current="page"' : ""}>${ASSISTANTS[id].name}</a>`).join(" ")}</nav>` +
|
|
31
|
-
connectionHtml(provider, data.connection, task) +
|
|
32
|
-
`<form method="post" action="/control/setup-preview" class="card">${hiddenFields({ csrf: data.csrf, repo: data.repo, provider, task })}` + model +
|
|
33
|
-
`<details class="setup-advanced"><summary>Project preparation</summary>` +
|
|
34
|
-
(data.preparation === null ? "" : `<p class="meta">Suggested from ${escape(data.preparation.evidence)}: ${escape(data.preparation.label)} — <code>${escape(data.preparation.command)}</code>. Review before approving.</p>`) +
|
|
35
|
-
`<label>Preparation command<textarea name="command" rows="2" maxlength="2000">${escape(inputs.command)}</textarea></label>` +
|
|
36
|
-
`<label>Preparation timeout in seconds<input name="seconds" type="number" min="1" max="3600" value="${escape(inputs.seconds)}" required></label>` +
|
|
37
|
-
`<p class="meta">Preparation applies to subsequent runs, including already approved tasks. Saving setup does not execute it.</p></details>` +
|
|
38
|
-
`<button type="submit">Review project setup</button></form>` +
|
|
39
|
-
`<section class="card"><h2>Agent instructions</h2>` +
|
|
40
|
-
(data.instructions.message ? `<p>${escape(data.instructions.message)}</p>` : data.instructions.installed ? `<p>Toolroll instructions are installed.</p>` :
|
|
41
|
-
`<p>Give the agent the project's Toolroll handoff instructions.</p><form method="post" action="/control/instructions-preview">${hiddenFields({ csrf: data.csrf, repo: data.repo })}<button>Review instructions</button></form>`) +
|
|
42
|
-
`</section><p><a href="/fleet">Configure planning, repair, and review agents</a> · <a href="/tasks/new">Describe a task</a></p>`;
|
|
20
|
+
: html `<label>Model<input type="text" name="model" value="${inputs.model}" list="setup-models" required autocomplete="off" placeholder="Exact model id or supported alias"></label><datalist id="setup-models">${data.models.map(one => html `<option value="${one.value}">${one.label}</option>`)}</datalist>`;
|
|
21
|
+
return html `<h1>Project setup</h1><p>${data.repo}</p><p class="meta">Choose the default builder for new tasks and the preparation command for this project.</p><nav class="assistant-picker row" aria-label="Choose provider">${joinHtml(Object.keys(ASSISTANTS).map(id => html `<a class="button-link" href="/control?${new URLSearchParams({ provider: id, ...(task ? { task } : {}) }).toString()}"${id === provider ? html ` aria-current="page"` : ""}>${ASSISTANTS[id].name}</a>`), " ")}</nav>${connectionHtml(provider, data.connection, task)}${postForm("/control/setup-preview", html `${model}<details class="setup-advanced"><summary>Project preparation</summary>${data.preparation === null ? "" : html `<p class="meta">Suggested from ${data.preparation.evidence}: ${data.preparation.label} — <code>${data.preparation.command}</code>. Review before approving.</p>`}<label>Preparation command<textarea name="command" rows="2" maxlength="2000">${inputs.command}</textarea></label><label>Preparation timeout in seconds<input name="seconds" type="number" min="1" max="3600" value="${inputs.seconds}" required></label><p class="meta">Preparation applies to subsequent runs, including already approved tasks. Saving setup does not execute it.</p></details><button type="submit">Review project setup</button>`, { attrs: { class: "card" }, hidden: { repo: data.repo, provider, task } })}<section class="card"><h2>Agent instructions</h2>${data.instructions.message ? html `<p>${data.instructions.message}</p>` : data.instructions.installed ? html `<p>Toolroll instructions are installed.</p>` :
|
|
22
|
+
html `<p>Give the agent the project's Toolroll handoff instructions.</p>${postForm("/control/instructions-preview", html `<button>Review instructions</button>`, { hidden: { repo: data.repo } })}`}</section><p><a href="/fleet">Configure planning, repair, and review agents</a> · <a href="/tasks/new">Describe a task</a></p>`;
|
|
43
23
|
}
|
|
24
|
+
/** `fields` may still carry the session's csrf (a caller's habit): postForm writes the one CSRF field. */
|
|
44
25
|
export function setupPreviewHtml(inputs, fields) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`<p><strong>Preparation:</strong></p><pre class="recap">${escape(inputs.command || "No preparation command")}</pre>` +
|
|
48
|
-
`<p>Preparation timeout: ${escape(inputs.seconds)} seconds.</p>` +
|
|
49
|
-
`<p>The builder default applies to new tasks. Preparation applies to subsequent runs, including already approved tasks. Existing signed task settings and verification rules remain in force.</p>` +
|
|
50
|
-
`<form method="post" action="/control/setup-approve">${hiddenFields({ ...fields, ...inputs })}` +
|
|
51
|
-
`<label>Your password<input type="password" name="token" autocomplete="current-password" required></label>` +
|
|
52
|
-
`<button>Approve project setup</button></form></div>`;
|
|
26
|
+
const { csrf: _csrf, ...rest } = fields;
|
|
27
|
+
return html `<h1>Approve project setup</h1><div class="card"><p>${fields.repo ?? ""}</p><p><strong>Builder:</strong> ${inputs.provider} · ${inputs.model}</p><p><strong>Preparation:</strong></p><pre class="recap">${inputs.command || "No preparation command"}</pre><p>Preparation timeout: ${inputs.seconds} seconds.</p><p>The builder default applies to new tasks. Preparation applies to subsequent runs, including already approved tasks. Existing signed task settings and verification rules remain in force.</p>${postForm("/control/setup-approve", html `<label>Your password<input type="password" name="token" autocomplete="current-password" required></label><button>Approve project setup</button>`, { hidden: { ...rest, ...inputs } })}</div>`;
|
|
53
28
|
}
|
package/dist/credentials-ui.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* can see and end everyone's.
|
|
5
5
|
*/
|
|
6
6
|
import type { ApiTokenRow, WebSessionRow } from "./store.js";
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
export declare const CREDENTIALS_CSS: string;
|
|
8
9
|
/** "Chrome on macOS", from a user agent, for a person to recognise a session. */
|
|
9
10
|
export declare function deviceWords(agent: string | null): string;
|
|
@@ -19,12 +20,12 @@ export type CredentialsView = {
|
|
|
19
20
|
/** Request limits (request-budget-ui.ts): words for each token's line, and the operator's disclosure. */
|
|
20
21
|
limits?: {
|
|
21
22
|
note: (token: ApiTokenRow) => string;
|
|
22
|
-
section:
|
|
23
|
+
section: Html;
|
|
23
24
|
};
|
|
24
25
|
};
|
|
25
|
-
export declare function credentialsHtml(view: CredentialsView,
|
|
26
|
+
export declare function credentialsHtml(view: CredentialsView, notice: {
|
|
26
27
|
said?: string | null;
|
|
27
28
|
problem?: string | null;
|
|
28
|
-
}):
|
|
29
|
+
}): Html;
|
|
29
30
|
/** The token, once: a focused page with no script. */
|
|
30
|
-
export declare function tokenShownHtml(name: string, token: string, access: "read" | "act", expiresAt: string):
|
|
31
|
+
export declare function tokenShownHtml(name: string, token: string, access: "read" | "act", expiresAt: string): Html;
|
package/dist/credentials-ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { daysLeft, expiryNoticeDue, TOKEN_DAYS, tokenLive } from "./api-tokens.js";
|
|
2
2
|
import { projectName } from "./project.js";
|
|
3
|
-
|
|
3
|
+
import { html, postForm } from "./html.js";
|
|
4
4
|
export const CREDENTIALS_CSS = `.credentials{max-width:820px;min-width:0}.credentials .card{padding:14px 16px;margin:12px 0}.credentials h2{font-size:1.05rem;margin:0 0 8px}` +
|
|
5
5
|
`.credentials .rows{display:grid;gap:0;border:1px solid var(--so-line);border-radius:10px;overflow:hidden}.credentials .row-item{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-bottom:1px solid var(--so-line)}` +
|
|
6
6
|
`.credentials .row-item:last-child{border-bottom:0}.credentials .row-item p{margin:0}.credentials .row-item .meta{font-size:.85rem}.credentials form{margin:0}.credentials button{min-height:40px;white-space:nowrap}` +
|
|
@@ -22,39 +22,38 @@ const when = (value, now) => {
|
|
|
22
22
|
const minutes = Math.round((now - at) / 60_000);
|
|
23
23
|
return minutes < 1 ? "just now" : minutes < 60 ? `${minutes} min ago` : minutes < 60 * 24 ? `${Math.round(minutes / 60)} h ago` : new Date(at).toISOString().slice(0, 10);
|
|
24
24
|
};
|
|
25
|
-
export function credentialsHtml(view,
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`${one.here ? "" : post({ action: "end-session", session: one.idHash }, "Sign out")}</div>`).join("")}</div>`;
|
|
25
|
+
export function credentialsHtml(view, notice) {
|
|
26
|
+
const post = (fields, label, style = "secondary") => postForm("/settings/sessions", html `<button class="${style}">${label}</button>`, { hidden: { ...fields, ...(view.everyone ? { everyone: "1" } : {}) } });
|
|
27
|
+
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
28
|
+
const toggle = view.canSeeEveryone ? html `<p class="meta"><a href="/settings/sessions${view.everyone ? "" : "?everyone=1"}">${view.everyone ? "Show only mine" : "Show everyone's"}</a></p>` : "";
|
|
29
|
+
const sessions = view.sessions.length === 0 ? html `<p class="meta">No one else is signed in.</p>` : html `<div class="rows">${view.sessions.map(one => html `<div class="row-item" data-session="${one.idHash.slice(0, 12)}"><div><p>${view.everyone ? html `<strong>${one.account}</strong> · ` : ""}${deviceWords(one.agent)}${one.here ? html `<span class="badge-here">This browser</span>` : ""}</p>\
|
|
30
|
+
<p class="meta">${one.ssoAt === null ? "Password" : "Identity provider"} · ${one.address ?? "unknown address"} · active ${when(one.lastSeen, view.now)} · signed in ${when(one.createdAt, view.now)}</p></div>\
|
|
31
|
+
${one.here ? "" : post({ action: "end-session", session: one.idHash }, "Sign out")}</div>`)}</div>`;
|
|
33
32
|
const others = view.sessions.filter(one => !one.here && one.account === view.who).length;
|
|
34
33
|
const endOthers = others > 0 && !view.everyone ? post({ action: "end-others" }, `Sign out everywhere else (${others})`) : "";
|
|
35
34
|
const live = view.tokens.filter(one => tokenLive(one, view.now));
|
|
36
35
|
// v111: its projects, and one state: when it stops (soon, said once and marked) or, once replaced, when it ends.
|
|
37
36
|
const ends = (one) => {
|
|
38
37
|
if (one.replacedBy !== null && one.overlapUntil !== null)
|
|
39
|
-
return `<span class="soon">Replaced · stops ${
|
|
38
|
+
return html `<span class="soon">Replaced · stops ${one.overlapUntil.slice(11, 16)} UTC</span>`;
|
|
40
39
|
if (expiryNoticeDue(one, view.now) === null)
|
|
41
|
-
return `Expires ${
|
|
40
|
+
return html `Expires ${one.expiresAt.slice(0, 10)}`;
|
|
42
41
|
const days = daysLeft(one.expiresAt, view.now);
|
|
43
|
-
return `<span class="soon">Expires ${days <= 1 ? "within a day" : `in ${days} days`}</span>`;
|
|
42
|
+
return html `<span class="soon">Expires ${days <= 1 ? "within a day" : `in ${days} days`}</span>`;
|
|
44
43
|
};
|
|
45
|
-
const scope = (one) => one.projects === null ? "All projects" : one.projects.length === 0 ? "No projects" : one.projects.map(repo =>
|
|
46
|
-
const tokens = live.length === 0 ? `<p class="meta">No API tokens.</p>` : `<div class="rows">${live.map(one => `<div class="row-item" data-token="${
|
|
47
|
-
|
|
48
|
-
const create = `<details class="card"><summary>New API token</summary
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return `<section class="credentials">${note}${toggle}<h2>Signed in</h2>${sessions}${endOthers}<h2 style="margin-top:1.5rem">API tokens</h2
|
|
54
|
-
|
|
44
|
+
const scope = (one) => one.projects === null ? "All projects" : one.projects.length === 0 ? "No projects" : one.projects.map(repo => projectName(repo)).join(", ");
|
|
45
|
+
const tokens = live.length === 0 ? html `<p class="meta">No API tokens.</p>` : html `<div class="rows">${live.map(one => html `<div class="row-item" data-token="${one.id}"><div><p>${view.everyone ? html `<strong>${one.account}</strong> · ` : ""}${one.name} · ${one.access === "act" ? "Can act" : "Read only"}</p>\
|
|
46
|
+
<p class="meta">${scope(one)} · ${ends(one)} · last used ${when(one.lastUsedAt, view.now)}${view.limits === undefined ? "" : ` · ${view.limits.note(one)}`}</p></div>${post({ action: "revoke-token", token: one.id }, "Revoke")}</div>`)}</div>`;
|
|
47
|
+
const create = html `<details class="card"><summary>New API token</summary>${postForm("/settings/sessions", html `\
|
|
48
|
+
<label>Name<input type="text" name="name" maxlength="60" required placeholder="for example: CI"></label>\
|
|
49
|
+
<label>It can<select name="access"><option value="read">Read (tasks, results, the ledger)</option><option value="act">Act as you (file and manage work; never approve)</option></select></label>\
|
|
50
|
+
<label>Expires in<select name="days">${TOKEN_DAYS.map(days => html `<option value="${days}"${days === 90 ? " selected" : ""}>${days} days</option>`)}</select></label>\
|
|
51
|
+
<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button>Make the token</button>`, { attrs: { class: "create" }, hidden: { action: "create-token" } })}</details>`;
|
|
52
|
+
return html `<section class="credentials">${note}${toggle}<h2>Signed in</h2>${sessions}${endOthers}<h2 style="margin-top:1.5rem">API tokens</h2>\
|
|
53
|
+
<p class="meta">For scripts and CI: send <code>Authorization: Bearer <token></code>. A token can't approve anything.</p>${tokens}${view.everyone ? "" : create}${view.limits?.section ?? ""}</section>`;
|
|
55
54
|
}
|
|
56
55
|
/** The token, once: a focused page with no script. */
|
|
57
56
|
export function tokenShownHtml(name, token, access, expiresAt) {
|
|
58
|
-
return `<h1>${
|
|
59
|
-
|
|
57
|
+
return html `<h1>${name}</h1><div class="card"><p>Your API token, shown once. Only a hash is kept, so copy it now.</p><p class="mono secret-value">${token}</p>\
|
|
58
|
+
<p class="meta">${access === "act" ? "It acts as you (never approves)" : "It reads"} until ${expiresAt.slice(0, 10)}. Revoke it any time on Sessions & tokens.</p></div><p class="meta"><a href="/settings/sessions">Back to Sessions & tokens</a></p>`;
|
|
60
59
|
}
|
package/dist/demo-chat.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Html } from "./html.js";
|
|
1
2
|
import type { DemoExchange } from "./demo.js";
|
|
2
3
|
/** What a finished exchange's stored result holds, read back from its evidence. */
|
|
3
4
|
export type DemoResultView = {
|
|
@@ -17,14 +18,13 @@ export type DemoResultView = {
|
|
|
17
18
|
taskHref: string;
|
|
18
19
|
};
|
|
19
20
|
/** The conversation itself; the live script swaps this region in place. */
|
|
20
|
-
export declare function demoThreadHtml(exchanges: readonly DemoExchange[],
|
|
21
|
+
export declare function demoThreadHtml(exchanges: readonly DemoExchange[], resultOf: (exchange: DemoExchange) => DemoResultView | null): Html;
|
|
21
22
|
export declare function demoChatHtml(input: {
|
|
22
23
|
exchanges: readonly DemoExchange[];
|
|
23
|
-
csrf: string;
|
|
24
24
|
version: number;
|
|
25
25
|
problem: string | null;
|
|
26
26
|
resultOf: (exchange: DemoExchange) => DemoResultView | null;
|
|
27
|
-
}):
|
|
27
|
+
}): Html;
|
|
28
28
|
/**
|
|
29
29
|
* Keeps a running build live: polls while one is working and swaps the
|
|
30
30
|
* thread in place, unless the visitor is typing inside it. Enter sends.
|
package/dist/demo-chat.js
CHANGED
|
@@ -4,48 +4,26 @@
|
|
|
4
4
|
* running build's progress live; without it the page still works by reload.
|
|
5
5
|
*/
|
|
6
6
|
import { START_COMMAND } from "./first-run.js";
|
|
7
|
+
import { html, postForm } from "./html.js";
|
|
7
8
|
import { headlineOf } from "./task-status.js";
|
|
8
9
|
const SUGGESTIONS = ["Fix the flaky refund test", "Rewrite the empty Payouts page", "Put the new invoice view behind a flag"];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function hint(csrf) {
|
|
12
|
-
return `<section class="demo-hint" aria-label="Get started">` +
|
|
13
|
-
`<p class="demo-hint-title">Ask for something, like “fix the flaky refund test”</p>` +
|
|
14
|
-
`<form method="post" action="/chat/demo/ask" class="demo-suggestions">${hidden(csrf)}` +
|
|
15
|
-
SUGGESTIONS.map(one => `<button type="submit" name="message" value="${esc(one)}">${esc(one)}</button>`).join("") +
|
|
16
|
-
`</form></section>`;
|
|
10
|
+
function hint() {
|
|
11
|
+
return html `<section class="demo-hint" aria-label="Get started"><p class="demo-hint-title">Ask for something, like “fix the flaky refund test”</p>${postForm("/chat/demo/ask", SUGGESTIONS.map(one => html `<button type="submit" name="message" value="${one}">${one}</button>`), { attrs: { class: "demo-suggestions" } })}</section>`;
|
|
17
12
|
}
|
|
18
|
-
function planCard(exchange
|
|
13
|
+
function planCard(exchange) {
|
|
19
14
|
const plan = exchange.plan;
|
|
20
15
|
const goal = exchange.note === null ? plan.goal : `${plan.goal} Also: ${exchange.note}`;
|
|
21
16
|
const actions = exchange.state === "proposed"
|
|
22
|
-
? `<div class="demo-actions">`
|
|
23
|
-
`<form method="post" action="/chat/demo/${exchange.id}/approve" class="approve-form">${hidden(csrf)}<button type="submit">Approve</button></form>` +
|
|
24
|
-
`<details class="demo-more"><summary>Change it</summary>` +
|
|
25
|
-
`<form method="post" action="/chat/demo/${exchange.id}/change">${hidden(csrf)}` +
|
|
26
|
-
`<label for="demo-change-${exchange.id}">What should change?</label>` +
|
|
27
|
-
`<textarea id="demo-change-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea>` +
|
|
28
|
-
`<button type="submit">Update plan</button></form></details></div>`
|
|
17
|
+
? html `<div class="demo-actions">${postForm(`/chat/demo/${exchange.id}/approve`, html `<button type="submit">Approve</button>`, { attrs: { class: "approve-form" } })}<details class="demo-more"><summary>Change it</summary>${postForm(`/chat/demo/${exchange.id}/change`, html `<label for="demo-change-${exchange.id}">What should change?</label><textarea id="demo-change-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea><button type="submit">Update plan</button>`)}</details></div>`
|
|
29
18
|
: exchange.state === "replaced"
|
|
30
|
-
? `<p class="meta">Replaced by the updated plan below.</p>`
|
|
31
|
-
: `<p class="meta">Approved.</p>`;
|
|
32
|
-
return `<section class="card demo-plan" aria-label="Plan">`
|
|
33
|
-
`<p class="demo-kicker">Plan · ${esc(plan.project)}</p>` +
|
|
34
|
-
`<h2>${esc(plan.title)}</h2>` +
|
|
35
|
-
`<p>${esc(goal)}</p>` +
|
|
36
|
-
`<h3>Boundaries</h3><ul>${plan.boundaries.map(one => `<li>${esc(one)}</li>`).join("")}</ul>` +
|
|
37
|
-
`<h3>Checks</h3><ul>${plan.checks.map(one => `<li>${esc(one)}</li>`).join("")}</ul>` +
|
|
38
|
-
actions + `</section>`;
|
|
19
|
+
? html `<p class="meta">Replaced by the updated plan below.</p>`
|
|
20
|
+
: html `<p class="meta">Approved.</p>`;
|
|
21
|
+
return html `<section class="card demo-plan" aria-label="Plan"><p class="demo-kicker">Plan · ${plan.project}</p><h2>${plan.title}</h2><p>${goal}</p><h3>Boundaries</h3><ul>${plan.boundaries.map(one => html `<li>${one}</li>`)}</ul><h3>Checks</h3><ul>${plan.checks.map(one => html `<li>${one}</li>`)}</ul>${actions}</section>`;
|
|
39
22
|
}
|
|
40
23
|
function buildCard(exchange) {
|
|
41
24
|
const stages = [["planning", "Planning"], ["building", "Building"], ["checking", "Running checks"]];
|
|
42
25
|
const at = stages.findIndex(([key]) => key === exchange.stage);
|
|
43
|
-
return `<section class="card demo-build" id="demo-${exchange.id}-work" aria-label="Build" aria-busy="true">`
|
|
44
|
-
`<p class="demo-kicker">${esc(exchange.plan.project)}</p>` +
|
|
45
|
-
`<h2>${esc(stages[Math.max(0, at)][1])}…</h2>` +
|
|
46
|
-
`<ol class="demo-steps">${stages.map(([, label], index) => `<li class="${index < at ? "done" : index === at ? "now" : ""}"${index === at ? ` aria-current="step"` : ""}>${esc(label)}</li>`).join("")}</ol>` +
|
|
47
|
-
(exchange.progress === null ? "" : `<p class="meta demo-progress">${esc(exchange.progress)}</p>`) +
|
|
48
|
-
`</section>`;
|
|
26
|
+
return html `<section class="card demo-build" id="demo-${exchange.id}-work" aria-label="Build" aria-busy="true"><p class="demo-kicker">${exchange.plan.project}</p><h2>${stages[Math.max(0, at)][1]}…</h2><ol class="demo-steps">${stages.map(([, label], index) => html `<li class="${index < at ? "done" : index === at ? "now" : ""}"${index === at ? html ` aria-current="step"` : ""}>${label}</li>`)}</ol>${exchange.progress === null ? "" : html `<p class="meta demo-progress">${exchange.progress}</p>`}</section>`;
|
|
49
27
|
}
|
|
50
28
|
function diffHtml(diff) {
|
|
51
29
|
return diff.replace(/\n$/, "").split("\n").map(line => {
|
|
@@ -53,71 +31,46 @@ function diffHtml(diff) {
|
|
|
53
31
|
: line.startsWith("+++") || line.startsWith("---") ? "meta"
|
|
54
32
|
: line.startsWith("@@") ? "hunk"
|
|
55
33
|
: line.startsWith("+") ? "add" : line.startsWith("-") ? "del" : "ctx";
|
|
56
|
-
return `<span class="demo-diff-${kind}">${
|
|
57
|
-
})
|
|
34
|
+
return html `<span class="demo-diff-${kind}">${line || " "}</span>`;
|
|
35
|
+
});
|
|
58
36
|
}
|
|
59
|
-
function resultCard(exchange,
|
|
37
|
+
function resultCard(exchange, result) {
|
|
60
38
|
if (result === null)
|
|
61
|
-
return `<section class="card demo-result"><p class="meta">This result's saved evidence is unavailable.</p></section>`;
|
|
39
|
+
return html `<section class="card demo-result"><p class="meta">This result's saved evidence is unavailable.</p></section>`;
|
|
62
40
|
const passed = result.checks.status === "passed";
|
|
63
41
|
// The shared headline (task-status.ts): the same words as every real task.
|
|
64
42
|
const headline = headlineOf({ stage: exchange.state === "complete" ? "complete" : exchange.state === "sent-back" ? "building" : "finished",
|
|
65
43
|
checks: { status: passed ? "passed" : "failed", exitCode: null, head: null } });
|
|
66
|
-
const status = `<span class="badge${headline === "Complete" ? " demo-complete" : headline === "Ready for review" ? " demo-ready" : ""}" data-headline="${headline}">${headline}</span>`;
|
|
67
|
-
const summary = `${result.files} file${result.files === 1 ? "" : "s"} changed · +${result.additions} −${result.deletions} ·
|
|
68
|
-
`<span class="${passed ? "demo-pass" : "demo-fail"}">${esc(result.checks.detail)}</span>`;
|
|
44
|
+
const status = html `<span class="badge${headline === "Complete" ? " demo-complete" : headline === "Ready for review" ? " demo-ready" : ""}" data-headline="${headline}">${headline}</span>`;
|
|
45
|
+
const summary = html `${result.files} file${result.files === 1 ? "" : "s"} changed · +${result.additions} −${result.deletions} · <span class="${passed ? "demo-pass" : "demo-fail"}">${result.checks.detail}</span>`;
|
|
69
46
|
const actions = exchange.state === "ready"
|
|
70
|
-
? `<div class="demo-actions">`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`<form method="post" action="/chat/demo/${exchange.id}/revise">${hidden(csrf)}` +
|
|
74
|
-
`<label for="demo-revise-${exchange.id}">What should change?</label>` +
|
|
75
|
-
`<textarea id="demo-revise-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea>` +
|
|
76
|
-
`<button type="submit">Send back</button></form></details></div>`
|
|
77
|
-
: `<p class="meta"><a href="${esc(result.taskHref)}">Open in Tasks</a></p>`;
|
|
78
|
-
return `<section class="card demo-result" id="demo-${exchange.id}-work" aria-label="Result">` +
|
|
79
|
-
`<p class="demo-state">${status}<span class="meta">${esc(exchange.plan.project)}</span></p>` +
|
|
80
|
-
`<h2>${esc(exchange.plan.title)}</h2>` +
|
|
81
|
-
`<p>${esc(exchange.plan.conclusion)}</p>` +
|
|
82
|
-
`<p class="meta">${summary}</p>` +
|
|
83
|
-
actions +
|
|
84
|
-
(result.diff === null ? "" : `<details class="demo-evidence"${exchange.state === "ready" ? " open" : ""}><summary>Changes</summary><pre class="demo-diff">${diffHtml(result.diff)}</pre></details>`) +
|
|
85
|
-
(result.checkLog === null ? "" : `<details class="demo-evidence"><summary>Check log</summary><pre class="demo-log">${esc(result.checkLog)}</pre></details>`) +
|
|
86
|
-
(result.screenshot === null ? "" : `<details class="demo-evidence"><summary>Screenshot</summary><figure><img src="${esc(result.screenshot.href)}" alt="${esc(result.screenshot.caption)}" width="960" height="600"><figcaption class="meta">${esc(result.screenshot.caption)}</figcaption></figure></details>`) +
|
|
87
|
-
`</section>`;
|
|
47
|
+
? html `<div class="demo-actions">${postForm(`/chat/demo/${exchange.id}/complete`, html `<button type="submit" class="demo-primary">Complete</button>`)}<details class="demo-more"><summary>Request changes</summary>${postForm(`/chat/demo/${exchange.id}/revise`, html `<label for="demo-revise-${exchange.id}">What should change?</label><textarea id="demo-revise-${exchange.id}" name="note" rows="2" maxlength="500" required></textarea><button type="submit">Send back</button>`)}</details></div>`
|
|
48
|
+
: html `<p class="meta"><a href="${result.taskHref}">Open in Tasks</a></p>`;
|
|
49
|
+
return html `<section class="card demo-result" id="demo-${exchange.id}-work" aria-label="Result"><p class="demo-state">${status}<span class="meta">${exchange.plan.project}</span></p><h2>${exchange.plan.title}</h2><p>${exchange.plan.conclusion}</p><p class="meta">${summary}</p>${actions}${result.diff === null ? "" : html `<details class="demo-evidence"${exchange.state === "ready" ? html ` open` : ""}><summary>Changes</summary><pre class="demo-diff">${diffHtml(result.diff)}</pre></details>`}${result.checkLog === null ? "" : html `<details class="demo-evidence"><summary>Check log</summary><pre class="demo-log">${result.checkLog}</pre></details>`}${result.screenshot === null ? "" : html `<details class="demo-evidence"><summary>Screenshot</summary><figure><img src="${result.screenshot.href}" alt="${result.screenshot.caption}" width="960" height="600"><figcaption class="meta">${result.screenshot.caption}</figcaption></figure></details>`}</section>`;
|
|
88
50
|
}
|
|
89
51
|
/** The conversation itself; the live script swaps this region in place. */
|
|
90
|
-
export function demoThreadHtml(exchanges,
|
|
52
|
+
export function demoThreadHtml(exchanges, resultOf) {
|
|
91
53
|
if (exchanges.length === 0)
|
|
92
|
-
return hint(
|
|
93
|
-
return exchanges.map(exchange => {
|
|
54
|
+
return hint();
|
|
55
|
+
return html `${exchanges.map(exchange => {
|
|
94
56
|
const parts = [
|
|
95
|
-
`<div class="demo-said demo-you"><p>${
|
|
96
|
-
`<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>${
|
|
97
|
-
planCard(exchange
|
|
57
|
+
html `<div class="demo-said demo-you"><p>${exchange.asked}</p></div>`,
|
|
58
|
+
html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>${exchange.reply}</p></div>`,
|
|
59
|
+
planCard(exchange),
|
|
98
60
|
];
|
|
99
61
|
if (exchange.state === "working")
|
|
100
62
|
parts.push(buildCard(exchange));
|
|
101
63
|
if (exchange.state === "ready" || exchange.state === "complete" || exchange.state === "sent-back")
|
|
102
|
-
parts.push(resultCard(exchange,
|
|
64
|
+
parts.push(resultCard(exchange, resultOf(exchange)));
|
|
103
65
|
if (exchange.state === "complete") {
|
|
104
|
-
parts.push(`<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2>`
|
|
105
|
-
`<p>In your repository's folder, run:</p><pre class="demo-command"><code>${esc(START_COMMAND)}</code></pre>` +
|
|
106
|
-
`<p class="meta">It opens in your browser, already signed in.</p></section>`);
|
|
66
|
+
parts.push(html `<div class="demo-said demo-lead"><p class="demo-who">Lead</p><p>Done. That's the whole loop: ask, approve, Ready, Complete. Ask for something else whenever you like.</p></div>`, html `<section class="card demo-handoff" data-demo-handoff aria-label="Your own project"><h2>Now try it on your own project</h2><p>In your repository's folder, run:</p><pre class="demo-command"><code>${START_COMMAND}</code></pre><p class="meta">It opens in your browser, already signed in.</p></section>`);
|
|
107
67
|
}
|
|
108
|
-
return `<article class="demo-turn" id="demo-${exchange.id}">${parts
|
|
109
|
-
})
|
|
68
|
+
return html `<article class="demo-turn" id="demo-${exchange.id}">${parts}</article>`;
|
|
69
|
+
})}`;
|
|
110
70
|
}
|
|
111
71
|
export function demoChatHtml(input) {
|
|
112
72
|
const working = input.exchanges.some(one => one.state === "working");
|
|
113
|
-
return `<div class="demo-chat">`
|
|
114
|
-
(input.problem === null ? "" : `<p class="problem" role="alert">${esc(input.problem)}</p>`) +
|
|
115
|
-
`<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">` +
|
|
116
|
-
demoThreadHtml(input.exchanges, input.csrf, input.resultOf) + `</div>` +
|
|
117
|
-
`<form method="post" action="/chat/demo/ask" class="demo-composer">${hidden(input.csrf)}` +
|
|
118
|
-
`<label for="demo-message" class="so-sr-only">Message the lead</label>` +
|
|
119
|
-
`<textarea id="demo-message" name="message" rows="2" maxlength="500" placeholder="Ask the lead for a change" required></textarea>` +
|
|
120
|
-
`<button type="submit" class="demo-primary">Send</button></form></div>`;
|
|
73
|
+
return html `<div class="demo-chat">${input.problem === null ? "" : html `<p class="problem" role="alert">${input.problem}</p>`}<div id="demo-thread" class="demo-thread" aria-live="polite" data-version="${input.version}" data-working="${working ? "1" : "0"}">${demoThreadHtml(input.exchanges, input.resultOf)}</div>${postForm("/chat/demo/ask", html `<label for="demo-message" class="so-sr-only">Message the lead</label><textarea id="demo-message" name="message" rows="2" maxlength="500" placeholder="Ask the lead for a change" required></textarea><button type="submit" class="demo-primary">Send</button>`, { attrs: { class: "demo-composer" } })}</div>`;
|
|
121
74
|
}
|
|
122
75
|
/**
|
|
123
76
|
* Keeps a running build live: polls while one is working and swaps the
|
package/dist/discord-chat.js
CHANGED
|
@@ -190,17 +190,13 @@ export async function deliverDiscordPart(options) {
|
|
|
190
190
|
if (!options.current() ||
|
|
191
191
|
!state.owns(identity.installation, options.owner, now))
|
|
192
192
|
return false;
|
|
193
|
-
const row = state
|
|
194
|
-
.prepare("SELECT p.* FROM chat_part p JOIN chat_event e ON e.id=p.event WHERE e.installation=? AND p.state='pending' AND (e.kind!='notice' OR ?=1) AND (p.next_at IS NULL OR p.next_at<=?) ORDER BY p.id LIMIT 1")
|
|
195
|
-
.get(identity.installation, options.canNotify?.() === false ? 0 : 1, now.toISOString());
|
|
193
|
+
const row = state.nextPart(identity.installation, options.canNotify?.() !== false, now);
|
|
196
194
|
if (!row)
|
|
197
195
|
return false;
|
|
198
196
|
const event = state.event(row.event), binding = event.binding === null ? null : state.bindingById(event.binding);
|
|
199
197
|
if (!binding ||
|
|
200
198
|
new Date(row.created).getTime() + 86_400_000 < now.getTime()) {
|
|
201
|
-
state
|
|
202
|
-
.prepare("UPDATE chat_part SET state='dropped',problem='Delivery expired or access changed; open the saved chat' WHERE id=?")
|
|
203
|
-
.run(row.id);
|
|
199
|
+
state.dropPart(row.id, "Delivery expired or access changed; open the saved chat");
|
|
204
200
|
return true;
|
|
205
201
|
}
|
|
206
202
|
try {
|
|
@@ -214,16 +210,16 @@ export async function deliverDiscordPart(options) {
|
|
|
214
210
|
throw new DiscordError("Connected projects changed");
|
|
215
211
|
let text = content.text, buttons = [], file;
|
|
216
212
|
if (content.image && content.shot && resultShotsPruned(options.evidenceRoot, content.image.run)) {
|
|
217
|
-
state.
|
|
213
|
+
state.dropPart(row.id, "Removed by retention");
|
|
218
214
|
return true;
|
|
219
215
|
}
|
|
220
216
|
// A screenshot sent with a result replies to that result's message once it is placed, and waits until then.
|
|
221
|
-
const result = content.shot?.follows == null ?
|
|
222
|
-
if (result !==
|
|
223
|
-
state.
|
|
217
|
+
const result = content.shot?.follows == null ? null : state.part(content.shot.follows);
|
|
218
|
+
if (result !== null && !discordId(result.message) && result.state === "pending") {
|
|
219
|
+
state.holdPart(row.id, shotWaitsUntil(result.next_at, now));
|
|
224
220
|
return true;
|
|
225
221
|
}
|
|
226
|
-
const follows = result?.
|
|
222
|
+
const follows = result?.message;
|
|
227
223
|
const thread = discordId(follows) ? String(follows) : event.thread;
|
|
228
224
|
if (content.image) {
|
|
229
225
|
const image = verifyResultImage(store, options.evidenceRoot, repos, content.image);
|
|
@@ -260,8 +256,7 @@ export async function deliverDiscordPart(options) {
|
|
|
260
256
|
preview.text.length <= 3400 &&
|
|
261
257
|
discordPlain(text).length <= 3900) {
|
|
262
258
|
buttons = state
|
|
263
|
-
.
|
|
264
|
-
.all(row.id, now.toISOString())
|
|
259
|
+
.partTokens(row.id, now)
|
|
265
260
|
.map((action) => ({
|
|
266
261
|
type: 2,
|
|
267
262
|
style: action.phase === "yes" ? 4 : action.phase === "confirm" ? 1 : 2,
|
|
@@ -317,6 +312,10 @@ export async function deliverDiscordPart(options) {
|
|
|
317
312
|
custom_id: `so_${one.token}`,
|
|
318
313
|
}))
|
|
319
314
|
: []),
|
|
315
|
+
// A result's, plan's, failure's or pull request's own buttons (chat-decide.ts): its acts, then its links.
|
|
316
|
+
...(content.decide?.rows.flat() ?? []).flatMap((one, index) => "token" in one
|
|
317
|
+
? [{ type: 2, style: index === 0 ? 1 : 2, label: one.label.slice(0, 80), custom_id: `so_${one.token}` }]
|
|
318
|
+
: link(options.origin(), one.link)),
|
|
320
319
|
...link(options.origin(), content.link),
|
|
321
320
|
...(content.also ?? []).flatMap(one => link(options.origin(), one)),
|
|
322
321
|
];
|
|
@@ -355,13 +354,7 @@ export async function deliverDiscordPart(options) {
|
|
|
355
354
|
// Preserve the original remote attachment; never upload it again on an edit.
|
|
356
355
|
file = undefined;
|
|
357
356
|
}
|
|
358
|
-
state
|
|
359
|
-
.prepare("UPDATE chat_part SET message=?,uploaded=? WHERE id=?")
|
|
360
|
-
.run(target, content.image &&
|
|
361
|
-
Array.isArray(found.attachments) &&
|
|
362
|
-
found.attachments.length
|
|
363
|
-
? 1
|
|
364
|
-
: 0, row.id);
|
|
357
|
+
state.setPartFile(row.id, null, Boolean(content.image && Array.isArray(found.attachments) && found.attachments.length), target);
|
|
365
358
|
}
|
|
366
359
|
}
|
|
367
360
|
await channelAccess(shared, binding, session?.ceilingDigest);
|
|
@@ -396,17 +389,13 @@ export async function deliverDiscordPart(options) {
|
|
|
396
389
|
!answer.attachments.some((v) => object(v).filename === file.name &&
|
|
397
390
|
Number(object(v).size) === file.bytes.length))))
|
|
398
391
|
throw new DiscordError("Discord did not confirm the message and file identity", 15_000, true);
|
|
399
|
-
state
|
|
400
|
-
|
|
401
|
-
.run(answer.id, row.id);
|
|
402
|
-
state
|
|
403
|
-
.prepare("UPDATE chat_runtime SET problem=NULL WHERE installation=? AND owner=?")
|
|
404
|
-
.run(identity.installation, options.owner);
|
|
392
|
+
state.partSent(row.id, String(answer.id), options.clock?.() ?? new Date());
|
|
393
|
+
state.setProblem(identity.installation, options.owner, null);
|
|
405
394
|
return true;
|
|
406
395
|
}
|
|
407
396
|
catch (error) {
|
|
408
397
|
if (error instanceof ChatDeliveryError && error.permanent) {
|
|
409
|
-
state.
|
|
398
|
+
state.dropPart(row.id, error.message);
|
|
410
399
|
return true;
|
|
411
400
|
}
|
|
412
401
|
// No permission to attach files here: one plain line instead of the result's screenshots.
|
|
@@ -420,18 +409,10 @@ export async function deliverDiscordPart(options) {
|
|
|
420
409
|
: new DiscordError("Discord delivery is waiting to retry", 15_000, true);
|
|
421
410
|
const until = new Date((options.clock?.() ?? new Date()).getTime() +
|
|
422
411
|
Math.max(problem.retryMs, [5000, 15000, 60000, 300000][Math.min(row.attempts, 3)])).toISOString();
|
|
423
|
-
state
|
|
424
|
-
|
|
425
|
-
.run(problem.uncertain ? 1 : 0, until, problem.message, row.id);
|
|
426
|
-
state
|
|
427
|
-
.prepare("UPDATE chat_runtime SET problem=? WHERE installation=? AND owner=?")
|
|
428
|
-
.run(problem.code === "ratelimited"
|
|
429
|
-
? "Discord asked us to wait. Saved replies will retry."
|
|
430
|
-
: problem.message, identity.installation, options.owner);
|
|
412
|
+
state.partFailed(row.id, { problem: problem.message, until, uncertain: problem.uncertain });
|
|
413
|
+
state.setProblem(identity.installation, options.owner, problem.code === "ratelimited" ? "Discord asked us to wait. Saved replies will retry." : problem.message);
|
|
431
414
|
if (problem.code === "ratelimited")
|
|
432
|
-
state
|
|
433
|
-
.prepare("UPDATE chat_runtime SET retry_at=? WHERE installation=?")
|
|
434
|
-
.run(until, identity.installation);
|
|
415
|
+
state.deferUntil(identity.installation, until);
|
|
435
416
|
return true;
|
|
436
417
|
}
|
|
437
418
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
|
-
|
|
2
|
+
import { type Html } from "./html.js";
|
|
3
|
+
export declare function discordSettingsHtml(store: Store, dir: string, options?: {
|
|
3
4
|
code?: string;
|
|
4
5
|
problem?: string;
|
|
5
6
|
now?: Date;
|
|
6
7
|
who?: string;
|
|
7
|
-
}):
|
|
8
|
+
}): Html;
|