toolroll 0.9.54 → 0.9.56
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 +19 -0
- package/README.md +29 -12
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +17 -22
- 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 +14 -15
- package/dist/browser-workspace.js +0 -3
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +27 -34
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- 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 +15 -10
- 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-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +7 -7
- package/dist/contracts/console-api.js +13 -12
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- 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/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +10 -1
- 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 +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +2 -1
- package/dist/flow-triggers.js +17 -15
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +38 -49
- package/dist/flows.js +21 -20
- package/dist/guides.js +112 -6
- 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/invoke.js +1 -1
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +22 -24
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +12 -2
- package/dist/lead-context.js +15 -17
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +3 -2
- package/dist/limits-ui.js +8 -8
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/mcp-oauth.js +35 -36
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +2 -2
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +46 -41
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +16 -28
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- 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/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +105 -99
- package/dist/server/chat.d.ts +7 -10
- package/dist/server/chat.js +984 -1021
- 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 +854 -773
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +8 -5
- 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 +581 -573
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +39 -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 +154 -0
- package/dist/server/render-settings.js +580 -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 +167 -77
- package/dist/server/runtime.d.ts +28 -20
- 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 +2186 -2121
- 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 +1489 -1420
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/setup-guide.d.ts +2 -2
- 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-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +5 -4
- package/dist/spend-ui.js +17 -40
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- 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-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/subagents-ui.js +137 -0
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +20 -33
- 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-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teams-chat.js +6 -6
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram-status.js +1 -1
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- 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-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -2
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -20
- package/dist/teammates-ui.js +0 -166
package/dist/ledger-view.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LedgerEntry } from "./action-ledger.js";
|
|
2
|
+
import { type Html } from "./html.js";
|
|
2
3
|
/** v103: the chain's state in one line, and (for an instance operator) a checkpoint to copy off the machine. */
|
|
3
4
|
export type LedgerChainView = {
|
|
4
5
|
ok: boolean;
|
|
@@ -21,4 +22,4 @@ export type LedgerChainView = {
|
|
|
21
22
|
/** Today (UTC, YYYY-MM-DD): the audit export's default range ends here. */
|
|
22
23
|
today: string;
|
|
23
24
|
};
|
|
24
|
-
export declare function ledgerBody(rows: LedgerEntry[], projects: readonly string[], params: URLSearchParams, chain?: LedgerChainView):
|
|
25
|
+
export declare function ledgerBody(rows: LedgerEntry[], projects: readonly string[], params: URLSearchParams, chain?: LedgerChainView): Html;
|
package/dist/ledger-view.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { projectName } from "./project.js";
|
|
2
2
|
import { exactWhenHtml } from "./when-html.js";
|
|
3
|
-
|
|
3
|
+
import { html, postForm, styleElement } from "./html.js";
|
|
4
4
|
/** The ledger's kinds of event, as the filter names them. */
|
|
5
5
|
const EVENT_KINDS = [["work", "Work"], ["request", "Requests"], ["access", "Access"], ["sign-in", "Sign-ins"], ["policy", "Policy changes"]];
|
|
6
6
|
function chainHtml(chain) {
|
|
7
|
-
const html = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
8
7
|
const status = chain.ok
|
|
9
|
-
? `<p class="ledger-chain ok" data-ledger-chain="ok"><strong>Chain verified</strong> · ${chain.entries} entries through #${chain.through ?? 0} · head <code>${
|
|
10
|
-
: `<p class="ledger-chain problem" role="alert" data-ledger-chain="broken"><strong>Chain broken</strong> · ${
|
|
11
|
-
const latest = chain.latest === null ? "" : `<p class="meta ledger-checkpoint">Latest checkpoint (${
|
|
12
|
-
const make = chain.csrf === null ? "" :
|
|
8
|
+
? html `<p class="ledger-chain ok" data-ledger-chain="ok"><strong>Chain verified</strong> · ${chain.entries} entries through #${chain.through ?? 0} · head <code>${chain.head.slice(0, 16)}…</code>${chain.checkedAt === null ? "" : ` · checked in full ${chain.checkedAt.slice(11, 16)} UTC`}</p>`
|
|
9
|
+
: html `<p class="ledger-chain problem" role="alert" data-ledger-chain="broken"><strong>Chain broken</strong> · ${chain.problem?.what ?? "the chain doesn't verify"}</p>`;
|
|
10
|
+
const latest = chain.latest === null ? "" : html `<p class="meta ledger-checkpoint">Latest checkpoint (${chain.latest.at.slice(0, 16).replace("T", " ")} UTC), copy it somewhere outside this machine: <code class="ledger-checkpoint-value">${chain.latest.through}:${chain.latest.hash}</code></p>`;
|
|
11
|
+
const make = chain.csrf === null ? "" : postForm("/ledger/checkpoint", html `<button type="submit">Make a checkpoint</button>`, { attrs: { class: "ledger-checkpoint-form" } });
|
|
13
12
|
const start = new Date(Date.parse(`${chain.today}T00:00:00Z`) - 29 * 86_400_000).toISOString().slice(0, 10);
|
|
14
|
-
const exportForm = `<details class="ledger-filter-panel"><summary>Audit export</summary><form method="get" action="/ledger/export" class="ledger-filters ledger-export" aria-label="Audit export"
|
|
15
|
-
|
|
16
|
-
`<button type="submit">Download</button></form><p class="meta">Every entry in the range with its seal, and an evidence pack for each task it names. JSON.</p></details>`;
|
|
17
|
-
return `<section class="ledger-chain-panel" aria-label="Ledger integrity">${status}${latest}${make}${exportForm}</section>`;
|
|
13
|
+
const exportForm = html `<details class="ledger-filter-panel"><summary>Audit export</summary><form method="get" action="/ledger/export" class="ledger-filters ledger-export" aria-label="Audit export"><label>From<input type="date" name="from" value="${start}" required></label><label>To<input type="date" name="to" value="${chain.today}" required></label><button type="submit">Download</button></form><p class="meta">Every entry in the range with its seal, and an evidence pack for each task it names. JSON.</p></details>`;
|
|
14
|
+
return html `<section class="ledger-chain-panel" aria-label="Ledger integrity">${status}${latest}${make}${exportForm}</section>`;
|
|
18
15
|
}
|
|
16
|
+
const LEDGER_CSS = `
|
|
17
|
+
.ledger-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.ledger-heading h1{margin:0}.ledger-caption{margin:8px 0 12px}
|
|
18
|
+
.ledger-filter-panel{margin-bottom:12px}.ledger-filter-panel summary{font-size:13px;padding:6px 0;cursor:pointer}.ledger-filters{display:grid;grid-template-columns:repeat(5,minmax(0,1fr)) auto auto;gap:8px;align-items:end;margin:0 0 12px}.ledger-filters label{display:grid;gap:3px;min-width:0;font-size:12px}.ledger-filters input,.ledger-filters select{width:100%;min-width:0;box-sizing:border-box;margin:0;padding:6px 8px;font-size:13px}.ledger-filters button{padding:7px 12px}.ledger-filters>a{padding:8px 0;font-size:13px}
|
|
19
|
+
.ledger-detail{display:block;color:var(--muted-foreground);font-size:12px}.ledger-list{border:1px solid var(--border);border-radius:8px;overflow:hidden}.ledger-event{display:grid;grid-template-columns:140px minmax(90px,1fr) minmax(160px,2fr) minmax(100px,1fr) minmax(100px,1fr);gap:8px;align-items:baseline;padding:7px 10px;border-bottom:1px solid var(--border);font-size:13px;line-height:1.4;overflow-wrap:anywhere}.ledger-event:last-child{border-bottom:0}.ledger-event .actor{color:inherit;font-weight:600;text-decoration:none}.ledger-time,.ledger-place,.ledger-result small{font-size:11px;color:var(--muted-foreground)}.ledger-time time{font-variant-numeric:tabular-nums}.ledger-place a{display:block}.ledger-result small{display:block}.ledger-labels{font-size:11px;color:var(--muted-foreground);font-weight:600}.ledger-empty{padding:12px}
|
|
20
|
+
@media(max-width:1000px){.ledger-filters{grid-template-columns:repeat(3,minmax(0,1fr)) auto}.ledger-event{grid-template-columns:122px minmax(80px,1fr) minmax(130px,2fr) minmax(85px,1fr)}.ledger-place{grid-column:2/4}.ledger-result{grid-column:4;grid-row:1/3}.ledger-labels{display:none}}
|
|
21
|
+
@media(max-width:600px){.ledger-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.ledger-event{grid-template-columns:minmax(0,1fr) auto;gap:2px 8px;padding:8px 10px}.ledger-actor{grid-column:1;grid-row:1}.ledger-action{grid-column:1;grid-row:2}.ledger-result{grid-column:2;grid-row:1/3;text-align:right}.ledger-time{grid-column:1;grid-row:3}.ledger-place{grid-column:2;grid-row:3;text-align:right}.ledger-place a{display:inline}.ledger-heading>a{font-size:13px}}
|
|
22
|
+
.ledger-chain-panel{margin:0 0 12px}.ledger-chain{margin:0 0 4px;font-size:13px}.ledger-chain.ok strong{color:var(--success)}.ledger-checkpoint code{overflow-wrap:anywhere;user-select:all}.ledger-checkpoint-form{margin:6px 0 0}.ledger-export{grid-template-columns:repeat(2,minmax(0,180px)) auto;margin-bottom:4px}
|
|
23
|
+
`;
|
|
19
24
|
export function ledgerBody(rows, projects, params, chain) {
|
|
20
|
-
const selected = (key, value) => params.get(key) === value ?
|
|
25
|
+
const selected = (key, value) => params.get(key) === value ? html ` selected` : "";
|
|
21
26
|
const more = rows.length > 50;
|
|
22
27
|
const shown = rows.slice(0, 50);
|
|
23
28
|
const fresh = new URLSearchParams();
|
|
@@ -31,29 +36,22 @@ export function ledgerBody(rows, projects, params, chain) {
|
|
|
31
36
|
next.set("before", String(shown.at(-1).id));
|
|
32
37
|
const csv = new URLSearchParams(fresh);
|
|
33
38
|
csv.set("format", "csv");
|
|
34
|
-
return
|
|
35
|
-
.ledger-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.ledger-heading h1{margin:0}.ledger-caption{margin:8px 0 12px}
|
|
36
|
-
.ledger-filter-panel{margin-bottom:12px}.ledger-filter-panel summary{font-size:13px;padding:6px 0;cursor:pointer}.ledger-filters{display:grid;grid-template-columns:repeat(5,minmax(0,1fr)) auto auto;gap:8px;align-items:end;margin:0 0 12px}.ledger-filters label{display:grid;gap:3px;min-width:0;font-size:12px}.ledger-filters input,.ledger-filters select{width:100%;min-width:0;box-sizing:border-box;margin:0;padding:6px 8px;font-size:13px}.ledger-filters button{padding:7px 12px}.ledger-filters>a{padding:8px 0;font-size:13px}
|
|
37
|
-
.ledger-detail{display:block;color:var(--muted-foreground);font-size:12px}.ledger-list{border:1px solid var(--border);border-radius:8px;overflow:hidden}.ledger-event{display:grid;grid-template-columns:140px minmax(90px,1fr) minmax(160px,2fr) minmax(100px,1fr) minmax(100px,1fr);gap:8px;align-items:baseline;padding:7px 10px;border-bottom:1px solid var(--border);font-size:13px;line-height:1.4;overflow-wrap:anywhere}.ledger-event:last-child{border-bottom:0}.ledger-event .actor{color:inherit;font-weight:600;text-decoration:none}.ledger-time,.ledger-place,.ledger-result small{font-size:11px;color:var(--muted-foreground)}.ledger-time time{font-variant-numeric:tabular-nums}.ledger-place a{display:block}.ledger-result small{display:block}.ledger-labels{font-size:11px;color:var(--muted-foreground);font-weight:600}.ledger-empty{padding:12px}
|
|
38
|
-
@media(max-width:1000px){.ledger-filters{grid-template-columns:repeat(3,minmax(0,1fr)) auto}.ledger-event{grid-template-columns:122px minmax(80px,1fr) minmax(130px,2fr) minmax(85px,1fr)}.ledger-place{grid-column:2/4}.ledger-result{grid-column:4;grid-row:1/3}.ledger-labels{display:none}}
|
|
39
|
-
@media(max-width:600px){.ledger-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.ledger-event{grid-template-columns:minmax(0,1fr) auto;gap:2px 8px;padding:8px 10px}.ledger-actor{grid-column:1;grid-row:1}.ledger-action{grid-column:1;grid-row:2}.ledger-result{grid-column:2;grid-row:1/3;text-align:right}.ledger-time{grid-column:1;grid-row:3}.ledger-place{grid-column:2;grid-row:3;text-align:right}.ledger-place a{display:inline}.ledger-heading>a{font-size:13px}}
|
|
40
|
-
.ledger-chain-panel{margin:0 0 12px}.ledger-chain{margin:0 0 4px;font-size:13px}.ledger-chain.ok strong{color:var(--success)}.ledger-checkpoint code{overflow-wrap:anywhere;user-select:all}.ledger-checkpoint-form{margin:6px 0 0}.ledger-export{grid-template-columns:repeat(2,minmax(0,180px)) auto;margin-bottom:4px}
|
|
41
|
-
</style><div class="ledger-heading"><h1>Action ledger</h1><a href="/ledger?${html(csv.toString())}" download>Export CSV</a></div>${chain === undefined ? "" : chainHtml(chain)}
|
|
39
|
+
return html `${styleElement(LEDGER_CSS)}<div class="ledger-heading"><h1>Action ledger</h1><a href="/ledger?${csv.toString()}" download>Export CSV</a></div>${chain === undefined ? "" : chainHtml(chain)}
|
|
42
40
|
<p class="meta ledger-caption">${shown.length} actions on this page · CSV includes all matching actions · Times in UTC</p>
|
|
43
41
|
<details class="ledger-filter-panel"><summary>Filters · ${fresh.size === 0 ? "all actions" : `${fresh.size} active`}</summary>
|
|
44
42
|
<form method="get" action="/ledger" class="ledger-filters" aria-label="Filter action ledger">
|
|
45
|
-
<label>Project<select name="project"><option value="">All accessible projects</option>${projects.map(repo => `<option value="${
|
|
46
|
-
<label>Person or worker<input type="text" name="actor" value="${
|
|
47
|
-
<label>Events<select name="source"><option value="">All events</option>${EVENT_KINDS.map(([value, label]) => `<option value="${value}"${selected("source", value)}>${label}</option>`)
|
|
48
|
-
<label>Outcome<input type="text" name="outcome" value="${
|
|
49
|
-
<label>Task<input type="text" name="task" value="${
|
|
43
|
+
<label>Project<select name="project"><option value="">All accessible projects</option>${projects.map(repo => html `<option value="${repo}"${selected("project", repo)}>${projectName(repo)}</option>`)}</select></label>
|
|
44
|
+
<label>Person or worker<input type="text" name="actor" value="${params.get("actor") ?? ""}" placeholder="Any actor"></label>
|
|
45
|
+
<label>Events<select name="source"><option value="">All events</option>${EVENT_KINDS.map(([value, label]) => html `<option value="${value}"${selected("source", value)}>${label}</option>`)}</select></label>
|
|
46
|
+
<label>Outcome<input type="text" name="outcome" value="${params.get("outcome") ?? ""}" placeholder="Any outcome"></label>
|
|
47
|
+
<label>Task<input type="text" name="task" value="${params.get("task") ?? ""}" placeholder="Any task"></label>
|
|
50
48
|
<button type="submit">Filter</button><a href="/ledger">Clear</a></form></details>
|
|
51
|
-
<div class="ledger-list" aria-label="Action history"><div class="ledger-event ledger-labels" aria-hidden="true"><span>Time</span><span>Actor</span><span>Action</span><span>Project / work</span><span>Outcome / event</span></div>${shown.length === 0 ? `<p class="ledger-empty">No actions match these filters yet.</p>` : shown.map(row => {
|
|
52
|
-
const subject = row.runId !== null ? `<a href="/r/${row.runId}">Run #${row.runId}</a>` : row.taskId !== null ? `<a href="/t/${encodeURIComponent(row.taskId)}">${
|
|
49
|
+
<div class="ledger-list" aria-label="Action history"><div class="ledger-event ledger-labels" aria-hidden="true"><span>Time</span><span>Actor</span><span>Action</span><span>Project / work</span><span>Outcome / event</span></div>${shown.length === 0 ? html `<p class="ledger-empty">No actions match these filters yet.</p>` : shown.map(row => {
|
|
50
|
+
const subject = row.runId !== null ? html `<a href="/r/${row.runId}">Run #${row.runId}</a>` : row.taskId !== null ? html `<a href="/t/${encodeURIComponent(row.taskId)}">${row.taskId}</a>` : "";
|
|
53
51
|
const byActor = new URLSearchParams(fresh);
|
|
54
52
|
byActor.set("actor", row.actor);
|
|
55
|
-
return `<article class="ledger-event" data-ledger-id="${row.id}"><div class="ledger-time">${exactWhenHtml(row.at)}</div><div class="ledger-actor"><a class="actor" href="/ledger?${
|
|
56
|
-
})
|
|
57
|
-
<p class="row"><a href="/ledger?${
|
|
53
|
+
return html `<article class="ledger-event" data-ledger-id="${row.id}"><div class="ledger-time">${exactWhenHtml(row.at)}</div><div class="ledger-actor"><a class="actor" href="/ledger?${byActor.toString()}" title="Filter by this actor">${row.actor}</a></div><div class="ledger-action">${row.action}${row.detail === null ? "" : html `<small class="ledger-detail">${row.detail}</small>`}</div><div class="ledger-place">${row.repo === null ? "Instance" : projectName(row.repo)} ${subject}</div><div class="ledger-result">${row.outcome}<small>${row.source}</small></div></article>`;
|
|
54
|
+
})}</div>
|
|
55
|
+
<p class="row"><a href="/ledger?${fresh.toString()}">Newest actions</a>${more ? html `<a rel="next" href="/ledger?${next.toString()}">Older actions</a>` : ""}</p>
|
|
58
56
|
<p class="meta">Accepted requests record acceptance; worker events record results. History starts from this upgrade. Review evidence is on each task or run.</p>`;
|
|
59
57
|
}
|
package/dist/limits-ui.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { BrowserLimits } from "./browser-workspace.js";
|
|
8
8
|
import { type BudgetState } from "./spend.js";
|
|
9
|
+
import { type Html } from "./html.js";
|
|
9
10
|
/** "in 25 min", "in 2 h 10 min", or a day and hour ("Fri 11 PM") beyond a day. */
|
|
10
11
|
export declare function resetWords(resetsAt: string, now: Date): string;
|
|
11
12
|
export declare function limitsView(windows: readonly {
|
|
@@ -19,8 +20,8 @@ export declare function limitsView(windows: readonly {
|
|
|
19
20
|
observedAt: string;
|
|
20
21
|
}[], budgets: readonly BudgetState[], names: {
|
|
21
22
|
project: (repo: string) => string;
|
|
22
|
-
|
|
23
|
+
subagent: (id: number) => string;
|
|
23
24
|
}, now: Date): BrowserLimits | null;
|
|
24
25
|
/** The same tiles for the page without scripts. */
|
|
25
|
-
export declare function limitsHtml(limits: BrowserLimits | null):
|
|
26
|
+
export declare function limitsHtml(limits: BrowserLimits | null): Html;
|
|
26
27
|
export declare const LIMITS_CSS: string;
|
package/dist/limits-ui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { windowLabel } from "./provider-limits.js";
|
|
2
2
|
import { usd } from "./spend.js";
|
|
3
|
+
import { html } from "./html.js";
|
|
3
4
|
const PROVIDER_NAMES = { claude: "Claude", codex: "Codex", gemini: "Gemini" };
|
|
4
5
|
/** Plan names worth showing; anything else (an internal billing name) is left off. */
|
|
5
6
|
const PLAN_NAMES = { free: "Free", plus: "Plus", pro: "Pro", max: "Max", team: "Team", business: "Business", enterprise: "Enterprise", edu: "Edu" };
|
|
@@ -37,7 +38,7 @@ export function limitsView(windows, budgets, names, now) {
|
|
|
37
38
|
for (const budget of budgets) {
|
|
38
39
|
const over = budget.spentMicrousd >= budget.limitMicrousd;
|
|
39
40
|
const name = budget.scope === "installation" ? "Everything" : budget.scope === "project" ? names.project(budget.key)
|
|
40
|
-
: budget.scope === "
|
|
41
|
+
: budget.scope === "subagent" ? names.subagent(Number(budget.key)) : budget.key;
|
|
41
42
|
tiles.push({
|
|
42
43
|
key: `budget:${budget.id}`, name, window: "Budget", value: usd(budget.spentMicrousd), unit: `of ${usd(budget.limitMicrousd)}`,
|
|
43
44
|
percent: budget.percent, tone: over ? (budget.hardStop ? "danger" : "warning") : budget.percent >= 80 ? "warning" : "neutral", marks: [50, 80], href: "/spend",
|
|
@@ -47,16 +48,15 @@ export function limitsView(windows, budgets, names, now) {
|
|
|
47
48
|
}
|
|
48
49
|
return tiles.length === 0 ? null : { tiles };
|
|
49
50
|
}
|
|
50
|
-
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
51
51
|
/** The same tiles for the page without scripts. */
|
|
52
52
|
export function limitsHtml(limits) {
|
|
53
53
|
if (limits === null)
|
|
54
|
-
return
|
|
55
|
-
return `<section class="limits" aria-label="Limits"><ul>${limits.tiles.map(tile => {
|
|
56
|
-
const body = `<p class="limit-name"><strong>${
|
|
57
|
-
|
|
58
|
-
return `<li class="limit limit-${tile.tone}" data-limit="${
|
|
59
|
-
})
|
|
54
|
+
return html ``;
|
|
55
|
+
return html `<section class="limits" aria-label="Limits"><ul>${limits.tiles.map(tile => {
|
|
56
|
+
const body = html `<p class="limit-name"><strong>${tile.name}</strong> ${tile.window}</p><p class="limit-value"><strong>${tile.value}</strong> ${tile.unit}</p>\
|
|
57
|
+
<span class="limit-bar" aria-hidden="true"><span style="width:${Math.min(100, Math.max(0, tile.percent))}%"></span></span><p class="limit-detail">${tile.detail}</p>`;
|
|
58
|
+
return html `<li class="limit limit-${tile.tone}" data-limit="${tile.key}"${tile.title === null ? "" : html ` title="${tile.title}"`}>${tile.href === null ? body : html `<a href="${tile.href}">${body}</a>`}</li>`;
|
|
59
|
+
})}</ul></section>`;
|
|
60
60
|
}
|
|
61
61
|
export const LIMITS_CSS = `.limits ul{list-style:none;margin:0 0 16px;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(176px,1fr));gap:8px}` +
|
|
62
62
|
`.limit{border:1px solid var(--border);border-radius:10px;background:var(--card);padding:12px 14px;min-width:0}.limit a{color:inherit;text-decoration:none;display:block}` +
|
package/dist/live-bus.d.ts
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
* an invalidation, never content. The page reads itself again under its
|
|
16
16
|
* own checks, so redaction stays the page's.
|
|
17
17
|
* - The safety net. Every room also checks itself about every 30 s, so a
|
|
18
|
-
* missed signal heals
|
|
18
|
+
* missed signal heals; the page's connection sends a keep-alive between,
|
|
19
|
+
* each after every room proves its viewer again.
|
|
20
|
+
* - One connection per page. A page opens one stream (GET /live) and joins
|
|
21
|
+
* every room it shows; each event names its room.
|
|
19
22
|
* - Back-pressure. Each page's stream is bounded: a page that falls behind
|
|
20
23
|
* gets one `reload` and ends so its EventSource can reconnect.
|
|
21
24
|
*
|
|
@@ -67,23 +70,62 @@ export declare class LiveStream {
|
|
|
67
70
|
send(event: string, data: unknown): void;
|
|
68
71
|
keepAlive(): void;
|
|
69
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* One page's live connection: its bounded stream, one keep-alive, and every
|
|
75
|
+
* room it joined. Each event names its room, so one EventSource serves the
|
|
76
|
+
* whole page. It ends when the page goes, when it falls behind (one
|
|
77
|
+
* `reload`), or when its last room lets it go. A keep-alive is a delivery
|
|
78
|
+
* too: every room proves its viewer again first, so a page whose session
|
|
79
|
+
* lapsed with nothing written hears its rooms go, never another frame.
|
|
80
|
+
*/
|
|
81
|
+
export declare class LiveConnection {
|
|
82
|
+
readonly response: ServerResponse;
|
|
83
|
+
readonly stream: LiveStream;
|
|
84
|
+
private readonly leaves;
|
|
85
|
+
private readonly proofs;
|
|
86
|
+
private readonly timer;
|
|
87
|
+
private closed;
|
|
88
|
+
constructor(response: ServerResponse, options?: {
|
|
89
|
+
limitBytes?: number;
|
|
90
|
+
keepAliveMs?: number;
|
|
91
|
+
});
|
|
92
|
+
private keepAlive;
|
|
93
|
+
/** Run before each keep-alive: a room proves its viewer again (and leaves if it no longer passes). Returns the undo. */
|
|
94
|
+
onKeepAlive(prove: () => void): () => void;
|
|
95
|
+
get open(): boolean;
|
|
96
|
+
/** An event for one room: its data says which. */
|
|
97
|
+
send(room: string, event: string, data?: Record<string, unknown>): void;
|
|
98
|
+
/** Run when the connection ends; returns the undo for a room that leaves first. */
|
|
99
|
+
onClose(leave: () => void): () => void;
|
|
100
|
+
close(): void;
|
|
101
|
+
/** Rooms joined and not yet left. */
|
|
102
|
+
get rooms(): number;
|
|
103
|
+
}
|
|
104
|
+
/** One page in one room: who, the room's name as the page asked for it, the page's connection, and its re-proof. */
|
|
70
105
|
export type LiveViewer = {
|
|
71
106
|
name: string;
|
|
72
|
-
|
|
107
|
+
room: string;
|
|
108
|
+
connection: LiveConnection;
|
|
73
109
|
valid: () => boolean;
|
|
74
110
|
};
|
|
75
111
|
export type LiveRooms<K, V extends LiveViewer> = {
|
|
76
|
-
/** Keep this page
|
|
112
|
+
/** Keep this page on the room until it leaves; the first events say where the room stands (and who's here). */
|
|
77
113
|
join: (key: K, viewer: V) => void;
|
|
78
|
-
/** Every
|
|
114
|
+
/** Every page leaves (the server is closing). */
|
|
79
115
|
close: () => void;
|
|
80
116
|
size: () => number;
|
|
81
117
|
};
|
|
118
|
+
/** `minIntervalMs`: a busy room says `change` at most this often — the first at once, the latest after. */
|
|
82
119
|
export type LiveRoomOptions = {
|
|
83
120
|
bus?: LiveBus | null;
|
|
84
121
|
safetyNetMs?: number;
|
|
85
|
-
|
|
86
|
-
limitBytes?: number;
|
|
122
|
+
minIntervalMs?: number;
|
|
87
123
|
};
|
|
88
|
-
/**
|
|
89
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Rooms of open pages that hear the bus. `presence` says who else is here, as
|
|
126
|
+
* each page should see it (null: the room has no presence). A viewer that no
|
|
127
|
+
* longer passes its re-proof hears `gone` and leaves; a room whose subject is
|
|
128
|
+
* gone (fingerprint null) sends everyone `gone`. A connection with no rooms
|
|
129
|
+
* left ends.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createLiveRooms<K, V extends LiveViewer>(fingerprint: (key: K) => string | null, presence: ((viewers: readonly V[], me: string) => unknown) | null, options?: LiveRoomOptions): LiveRooms<K, V>;
|
package/dist/live-bus.js
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
* an invalidation, never content. The page reads itself again under its
|
|
16
16
|
* own checks, so redaction stays the page's.
|
|
17
17
|
* - The safety net. Every room also checks itself about every 30 s, so a
|
|
18
|
-
* missed signal heals
|
|
18
|
+
* missed signal heals; the page's connection sends a keep-alive between,
|
|
19
|
+
* each after every room proves its viewer again.
|
|
20
|
+
* - One connection per page. A page opens one stream (GET /live) and joins
|
|
21
|
+
* every room it shows; each event names its room.
|
|
19
22
|
* - Back-pressure. Each page's stream is bounded: a page that falls behind
|
|
20
23
|
* gets one `reload` and ends so its EventSource can reconnect.
|
|
21
24
|
*
|
|
@@ -183,34 +186,98 @@ export class LiveStream {
|
|
|
183
186
|
this.write(": keep-alive\n\n");
|
|
184
187
|
}
|
|
185
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* One page's live connection: its bounded stream, one keep-alive, and every
|
|
191
|
+
* room it joined. Each event names its room, so one EventSource serves the
|
|
192
|
+
* whole page. It ends when the page goes, when it falls behind (one
|
|
193
|
+
* `reload`), or when its last room lets it go. A keep-alive is a delivery
|
|
194
|
+
* too: every room proves its viewer again first, so a page whose session
|
|
195
|
+
* lapsed with nothing written hears its rooms go, never another frame.
|
|
196
|
+
*/
|
|
197
|
+
export class LiveConnection {
|
|
198
|
+
response;
|
|
199
|
+
stream;
|
|
200
|
+
leaves = new Set();
|
|
201
|
+
proofs = new Set();
|
|
202
|
+
timer;
|
|
203
|
+
closed = false;
|
|
204
|
+
constructor(response, options = {}) {
|
|
205
|
+
this.response = response;
|
|
206
|
+
this.stream = new LiveStream(response, options.limitBytes, () => this.close());
|
|
207
|
+
this.timer = setInterval(() => this.keepAlive(), options.keepAliveMs ?? KEEP_ALIVE_MS);
|
|
208
|
+
this.timer.unref();
|
|
209
|
+
response.once("close", () => this.close());
|
|
210
|
+
}
|
|
211
|
+
keepAlive() {
|
|
212
|
+
for (const prove of [...this.proofs]) {
|
|
213
|
+
if (!this.open)
|
|
214
|
+
return;
|
|
215
|
+
prove();
|
|
216
|
+
}
|
|
217
|
+
if (this.open)
|
|
218
|
+
this.stream.keepAlive();
|
|
219
|
+
}
|
|
220
|
+
/** Run before each keep-alive: a room proves its viewer again (and leaves if it no longer passes). Returns the undo. */
|
|
221
|
+
onKeepAlive(prove) { this.proofs.add(prove); return () => { this.proofs.delete(prove); }; }
|
|
222
|
+
get open() { return !this.closed && this.stream.open; }
|
|
223
|
+
/** An event for one room: its data says which. */
|
|
224
|
+
send(room, event, data = {}) { this.stream.send(event, { room, ...data }); }
|
|
225
|
+
/** Run when the connection ends; returns the undo for a room that leaves first. */
|
|
226
|
+
onClose(leave) { this.leaves.add(leave); return () => { this.leaves.delete(leave); }; }
|
|
227
|
+
close() {
|
|
228
|
+
if (this.closed)
|
|
229
|
+
return;
|
|
230
|
+
this.closed = true;
|
|
231
|
+
clearInterval(this.timer);
|
|
232
|
+
for (const leave of [...this.leaves])
|
|
233
|
+
leave();
|
|
234
|
+
this.leaves.clear();
|
|
235
|
+
this.proofs.clear();
|
|
236
|
+
if (!this.response.writableEnded)
|
|
237
|
+
this.response.end();
|
|
238
|
+
}
|
|
239
|
+
/** Rooms joined and not yet left. */
|
|
240
|
+
get rooms() { return this.leaves.size; }
|
|
241
|
+
}
|
|
186
242
|
const PRESENCE_SETTLE_MS = 150;
|
|
187
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* Rooms of open pages that hear the bus. `presence` says who else is here, as
|
|
245
|
+
* each page should see it (null: the room has no presence). A viewer that no
|
|
246
|
+
* longer passes its re-proof hears `gone` and leaves; a room whose subject is
|
|
247
|
+
* gone (fingerprint null) sends everyone `gone`. A connection with no rooms
|
|
248
|
+
* left ends.
|
|
249
|
+
*/
|
|
188
250
|
export function createLiveRooms(fingerprint, presence, options = {}) {
|
|
189
251
|
const bus = options.bus ?? null;
|
|
190
|
-
const
|
|
191
|
-
const
|
|
252
|
+
const safetyNetMs = options.safetyNetMs ?? SAFETY_NET_MS;
|
|
253
|
+
const minIntervalMs = options.minIntervalMs ?? 0;
|
|
192
254
|
const rooms = new Map();
|
|
193
255
|
let unsubscribe = null;
|
|
194
|
-
const
|
|
195
|
-
|
|
256
|
+
const announce = (key, room) => {
|
|
257
|
+
if (presence === null || rooms.get(key) !== room)
|
|
258
|
+
return;
|
|
196
259
|
// A page that moves from one card to the next leaves and joins at once: say it once, after it settles.
|
|
197
260
|
if (room.settling !== null)
|
|
198
261
|
clearTimeout(room.settling);
|
|
199
262
|
room.settling = setTimeout(() => {
|
|
200
263
|
room.settling = null;
|
|
201
|
-
for (const
|
|
202
|
-
|
|
264
|
+
for (const viewer of [...room.viewers.keys()])
|
|
265
|
+
deliver(key, room, viewer, "here", { people: presence([...room.viewers.keys()], viewer.name) });
|
|
203
266
|
}, PRESENCE_SETTLE_MS);
|
|
204
267
|
room.settling.unref();
|
|
205
268
|
};
|
|
206
269
|
const leave = (key, viewer) => {
|
|
207
270
|
const room = rooms.get(key);
|
|
208
|
-
|
|
271
|
+
const forget = room?.viewers.get(viewer);
|
|
272
|
+
if (room === undefined || forget === undefined)
|
|
209
273
|
return;
|
|
210
|
-
|
|
211
|
-
|
|
274
|
+
room.viewers.delete(viewer);
|
|
275
|
+
forget();
|
|
276
|
+
// The page's last room: its connection has nothing left to say.
|
|
277
|
+
if (viewer.connection.rooms === 0)
|
|
278
|
+
viewer.connection.close();
|
|
212
279
|
if (room.viewers.size > 0) {
|
|
213
|
-
announce(room);
|
|
280
|
+
announce(key, room);
|
|
214
281
|
return;
|
|
215
282
|
}
|
|
216
283
|
clearInterval(room.timer);
|
|
@@ -218,30 +285,46 @@ export function createLiveRooms(fingerprint, presence, options = {}) {
|
|
|
218
285
|
clearTimeout(room.settling);
|
|
219
286
|
if (room.retry !== null)
|
|
220
287
|
clearTimeout(room.retry);
|
|
288
|
+
if (room.holding !== null)
|
|
289
|
+
clearTimeout(room.holding);
|
|
221
290
|
rooms.delete(key);
|
|
222
291
|
if (rooms.size === 0 && unsubscribe !== null) {
|
|
223
292
|
unsubscribe();
|
|
224
293
|
unsubscribe = null;
|
|
225
294
|
}
|
|
226
295
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
296
|
+
/** The final delivery boundary, including queued frames and sends that may overflow. */
|
|
297
|
+
const deliver = (key, room, viewer, event, data = {}) => {
|
|
298
|
+
if (rooms.get(key) !== room || !room.viewers.has(viewer))
|
|
299
|
+
return false;
|
|
300
|
+
if (!viewer.connection.open) {
|
|
301
|
+
leave(key, viewer);
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
let valid = false;
|
|
305
|
+
try {
|
|
306
|
+
valid = viewer.valid();
|
|
307
|
+
}
|
|
308
|
+
catch { /* A failed proof releases only this room. */ }
|
|
309
|
+
if (!valid || event === "gone") {
|
|
310
|
+
// A failed proof may send only the generic departure, never the pending room data.
|
|
236
311
|
try {
|
|
237
|
-
|
|
312
|
+
viewer.connection.send(viewer.room, "gone");
|
|
238
313
|
}
|
|
239
|
-
|
|
240
|
-
|
|
314
|
+
finally {
|
|
315
|
+
leave(key, viewer);
|
|
241
316
|
}
|
|
242
|
-
|
|
243
|
-
drop(key, viewer);
|
|
317
|
+
return false;
|
|
244
318
|
}
|
|
319
|
+
if (event !== undefined)
|
|
320
|
+
viewer.connection.send(viewer.room, event, data);
|
|
321
|
+
return viewer.connection.open && room.viewers.has(viewer);
|
|
322
|
+
};
|
|
323
|
+
/** Recheck who may still see the room, then whether what it shows moved. */
|
|
324
|
+
const refresh = (key, room, revision) => {
|
|
325
|
+
// A signed-out or narrowed account stops hearing about it, at the first write after.
|
|
326
|
+
for (const viewer of [...room.viewers.keys()])
|
|
327
|
+
deliver(key, room, viewer);
|
|
245
328
|
if (rooms.get(key) !== room)
|
|
246
329
|
return;
|
|
247
330
|
let now;
|
|
@@ -259,25 +342,28 @@ export function createLiveRooms(fingerprint, presence, options = {}) {
|
|
|
259
342
|
}
|
|
260
343
|
if (now === null) {
|
|
261
344
|
for (const viewer of [...room.viewers.keys()])
|
|
262
|
-
|
|
345
|
+
deliver(key, room, viewer, "gone");
|
|
263
346
|
return;
|
|
264
347
|
}
|
|
265
348
|
if (now === room.last)
|
|
266
349
|
return;
|
|
267
350
|
room.last = now;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return;
|
|
351
|
+
room.revision = revision ?? bus?.revision() ?? null;
|
|
352
|
+
if (room.holding !== null)
|
|
353
|
+
return;
|
|
354
|
+
const wait = room.saidAt + minIntervalMs - Date.now();
|
|
355
|
+
if (wait > 0) {
|
|
356
|
+
room.holding = setTimeout(() => { room.holding = null; if (rooms.get(key) === room)
|
|
357
|
+
say(key, room); }, wait);
|
|
358
|
+
room.holding.unref();
|
|
359
|
+
return;
|
|
278
360
|
}
|
|
279
|
-
|
|
280
|
-
|
|
361
|
+
say(key, room);
|
|
362
|
+
};
|
|
363
|
+
const say = (key, room) => {
|
|
364
|
+
room.saidAt = Date.now();
|
|
365
|
+
for (const viewer of [...room.viewers.keys()])
|
|
366
|
+
deliver(key, room, viewer, "change", { at: room.last, revision: room.revision });
|
|
281
367
|
};
|
|
282
368
|
const heard = (change) => {
|
|
283
369
|
for (const [key, room] of [...rooms])
|
|
@@ -292,24 +378,21 @@ export function createLiveRooms(fingerprint, presence, options = {}) {
|
|
|
292
378
|
last = fingerprint(key);
|
|
293
379
|
}
|
|
294
380
|
catch { /* the next signal or the safety net reads it */ }
|
|
295
|
-
const created = { viewers: new Map(), last, timer: setInterval(() =>
|
|
381
|
+
const created = { viewers: new Map(), last, timer: setInterval(() => refresh(key, created, null), safetyNetMs), settling: null, retry: null, saidAt: 0, holding: null, revision: null };
|
|
296
382
|
created.timer.unref();
|
|
297
383
|
room = created;
|
|
298
384
|
rooms.set(key, room);
|
|
299
385
|
if (bus !== null && unsubscribe === null)
|
|
300
386
|
unsubscribe = bus.subscribe(heard);
|
|
301
387
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
room.viewers.set(viewer,
|
|
306
|
-
viewer
|
|
307
|
-
stream.send("change", { at: room.last, revision: bus?.revision() ?? null });
|
|
308
|
-
if (!stream.open)
|
|
388
|
+
const joined = room;
|
|
389
|
+
const forget = viewer.connection.onClose(() => leave(key, viewer));
|
|
390
|
+
const unprove = viewer.connection.onKeepAlive(() => { deliver(key, joined, viewer); });
|
|
391
|
+
room.viewers.set(viewer, () => { forget(); unprove(); });
|
|
392
|
+
if (!deliver(key, room, viewer, "change", { at: room.last, revision: bus?.revision() ?? null }) || presence === null)
|
|
309
393
|
return;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
announce(room);
|
|
394
|
+
if (deliver(key, room, viewer, "here", { people: presence([...room.viewers.keys()], viewer.name) }))
|
|
395
|
+
announce(key, room);
|
|
313
396
|
},
|
|
314
397
|
close() {
|
|
315
398
|
for (const [key, room] of [...rooms])
|
package/dist/mcp-connect.d.ts
CHANGED
|
@@ -159,7 +159,7 @@ export declare function finishConnect(store: Store, visit: ConnectVisit, code: s
|
|
|
159
159
|
ok: false;
|
|
160
160
|
said: string;
|
|
161
161
|
}>;
|
|
162
|
-
/** Keep connected services signed in: any token expiring within ten minutes is refreshed (the worker's pass, and before a
|
|
162
|
+
/** Keep connected services signed in: any token expiring within ten minutes is refreshed (the worker's pass, and before a subagent's call). */
|
|
163
163
|
export declare function refreshConnections(store: Store, repos: readonly string[], now: Date, options?: {
|
|
164
164
|
fetcher?: Fetch;
|
|
165
165
|
home?: string;
|
package/dist/mcp-connect.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* with this console's callback, and sends the person to the service to sign
|
|
9
9
|
* in. The callback trades the code for tokens, which live in the tool's own
|
|
10
10
|
* secrets file (never the database, never a log), and the tool joins the
|
|
11
|
-
* project like any other: tested, listed, and given to
|
|
11
|
+
* project like any other: tested, listed, and given to subagents under rules.
|
|
12
12
|
* The worker refreshes tokens before they expire.
|
|
13
13
|
*/
|
|
14
14
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -382,10 +382,10 @@ export async function finishConnect(store, visit, code, now, options = {}) {
|
|
|
382
382
|
if (options.test === false)
|
|
383
383
|
return { ok: true, said: `${service.label} is connected to ${to}.` };
|
|
384
384
|
const tested = await testToolOf(store, visit.repo, spec.name, now, { home, ...(options.omitEnv === undefined ? {} : { omitEnv: options.omitEnv }) });
|
|
385
|
-
return tested?.ok ? { ok: true, said: `${service.label} is connected to ${to}: ${tested.tools.length} action${tested.tools.length === 1 ? "" : "s"}. Let a
|
|
385
|
+
return tested?.ok ? { ok: true, said: `${service.label} is connected to ${to}: ${tested.tools.length} action${tested.tools.length === 1 ? "" : "s"}. Let a subagent use it from its page.` }
|
|
386
386
|
: { ok: true, said: `${service.label} is signed in for ${to}, but its test didn't pass yet: ${tested?.problem ?? "no answer"}.` };
|
|
387
387
|
}
|
|
388
|
-
/** Keep connected services signed in: any token expiring within ten minutes is refreshed (the worker's pass, and before a
|
|
388
|
+
/** Keep connected services signed in: any token expiring within ten minutes is refreshed (the worker's pass, and before a subagent's call). */
|
|
389
389
|
export async function refreshConnections(store, repos, now, options = {}) {
|
|
390
390
|
const fetcher = options.fetcher ?? fetch, home = options.home ?? homedir();
|
|
391
391
|
const report = { refreshed: 0, problems: [] };
|
package/dist/mcp-core.js
CHANGED
|
@@ -17,7 +17,7 @@ import { GATEWAY_TOOL_INPUTS, GATEWAY_TOOL_OUTPUTS } from "./contracts/gateway-t
|
|
|
17
17
|
import { reportToolOutput } from "./contracts/lead-tools.js";
|
|
18
18
|
import { PACKAGE_VERSION } from "./version.js";
|
|
19
19
|
import { authenticateCoordinator, fileCoordinatorProposal, statusFor, listTasksFor, taskDetailFor, } from "./coordinator.js";
|
|
20
|
-
import { ISO_STAMP_RULE, decisionOver, decisionsOver, labelRepos, queueOver, recapOver } from "./
|
|
20
|
+
import { ISO_STAMP_RULE, decisionOver, decisionsOver, labelRepos, queueOver, recapOver } from "./lead-tools.js";
|
|
21
21
|
import { proposeAsCoordinator } from "./coordinator-proposals.js";
|
|
22
22
|
export const MODERN = "2026-07-28";
|
|
23
23
|
export const LEGACY = "2025-11-25";
|