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/surface.js
CHANGED
|
@@ -114,6 +114,17 @@ const GUIDE_ROWS = [
|
|
|
114
114
|
operator("link", "put toolroll on PATH"),
|
|
115
115
|
operator("unlink", "take it off PATH"),
|
|
116
116
|
operator("update", "update to the latest release: verified provenance, drained work, backup, rehearsal, health check, automatic restore; --rollback undoes it"),
|
|
117
|
+
{ invocation: "release", synopsis: "release Toolroll itself from a branch: gate the exact commit, wait for the owner's approval, check, complete, deploy, merge, tag, publish, Homebrew; rerun to resume",
|
|
118
|
+
audience: "operator", agentMayInvoke: false, mutation: "unkeyed",
|
|
119
|
+
positionals: [{ name: "branch", required: true, meaning: "the pushed branch with an open pull request" }],
|
|
120
|
+
flags: [jsonFlag,
|
|
121
|
+
{ name: "repo", takesValue: true, meaning: "the gate checkout the release check runs in (default: this checkout)" },
|
|
122
|
+
{ name: "new", takesValue: false, meaning: "release a new commit on the branch, setting the earlier unfinished release aside" },
|
|
123
|
+
{ name: "limit", takesValue: true, meaning: "<step>=<minutes>: one waiting step's time limit" },
|
|
124
|
+
{ name: "tap", takesValue: true, meaning: "the Homebrew tap, owner/name" },
|
|
125
|
+
{ name: "release-gate", takesValue: true, meaning: "<id>: show why an update pause left before a deployment's swap is safe to lift" },
|
|
126
|
+
{ name: "yes", takesValue: false, meaning: "with --release-gate: lift that one pause once it is proved" }],
|
|
127
|
+
notableReasons: ["usage", "ambiguous", "drift", "timeout", "check-failed", "ci-failed", "tree-mismatch", "conflict", "mixed-runtime", "gate-held", "after-swap"] },
|
|
117
128
|
operator("demo", "a seeded throwaway sandbox"),
|
|
118
129
|
{ invocation: 'session capabilities', synopsis: 'Show executable session schemas and operator authority requirements; no credentials needed', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag] },
|
|
119
130
|
...SESSION_DESCRIPTORS.map(spec => ({
|
|
@@ -315,7 +326,7 @@ const GUIDE_ROWS = [
|
|
|
315
326
|
{ invocation: "heartbeat", synopsis: "still working; extends the lease", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
316
327
|
positionals: [{ name: "lease", required: true, meaning: "the lease id" }],
|
|
317
328
|
flags: [jsonFlag, dbFlag], notableReasons: ["not-leased", "fenced"] },
|
|
318
|
-
{ invocation: "release", synopsis: "done with it; fenced if superseded", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
329
|
+
{ invocation: "worker release", synopsis: "done with it; fenced if superseded", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
319
330
|
positionals: [{ name: "lease", required: true, meaning: "the lease id" }],
|
|
320
331
|
flags: [jsonFlag, dbFlag], notableReasons: ["not-leased", "fenced"] },
|
|
321
332
|
{ invocation: "reap", synopsis: "release every lease that ran out", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent", flags: [jsonFlag, dbFlag] },
|
|
@@ -428,7 +439,7 @@ const GUIDE_ROWS = [
|
|
|
428
439
|
const REMOTE_NO = [
|
|
429
440
|
// Answered by the local CLI, or a client of a central server itself.
|
|
430
441
|
"", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github", "contract", "skills list", "skills get", "skills install",
|
|
431
|
-
"link", "unlink", "update", "demo", "chat", "connect", "lead list", "lead create", "lead update", "lead member", "lead transfer",
|
|
442
|
+
"link", "unlink", "update", "release", "demo", "chat", "connect", "lead list", "lead create", "lead update", "lead member", "lead transfer",
|
|
432
443
|
"conversation list", "conversation create", "conversation show", "conversation member", "conversation edit", "conversation withdraw",
|
|
433
444
|
"conversation read", "conversation follow", "conversation stop",
|
|
434
445
|
"session capabilities", "session list", "session show", "session changes", "session start", "session send", "session stop", "session resume", "session recover",
|
|
@@ -436,7 +447,7 @@ const REMOTE_NO = [
|
|
|
436
447
|
"project show", "project use", "brief", "lead token", "lead say", "health",
|
|
437
448
|
"assignment show", "assignment updates", "assignment claim", "assignment check", "assignment brief", "assignment inbox", "assignment ack",
|
|
438
449
|
// Workers, the loop and the services that run here.
|
|
439
|
-
"claim", "heartbeat", "release", "reap", "tick", "build", "reconcile",
|
|
450
|
+
"claim", "heartbeat", "worker release", "reap", "tick", "build", "reconcile",
|
|
440
451
|
"runner register", "runner retire", "runner bind", "runner capacity", "coordinator mint", "coordinator revoke", "mcp", "outbox deliver", "peek",
|
|
441
452
|
"serve", "serve check-public", "watch", "up", "onboard", "daemon", "bridge", "setup show", "setup clear",
|
|
442
453
|
// Provider keys and model tools on this machine.
|
package/dist/task-composer.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** UI choices translate into the same digest-bound schedules as the CLI. */
|
|
2
2
|
import { type Schedule } from "./flow-schedule.js";
|
|
3
|
+
import { type Html } from "./html.js";
|
|
3
4
|
export declare function composerSchedule(fields: URLSearchParams): {
|
|
4
5
|
ok: true;
|
|
5
6
|
schedule: string | null;
|
|
@@ -9,5 +10,5 @@ export declare function composerSchedule(fields: URLSearchParams): {
|
|
|
9
10
|
};
|
|
10
11
|
export declare function scheduleFields(schedule: Schedule): Record<string, string>;
|
|
11
12
|
/** A normal form works without JavaScript; enhancement only folds unused fields. */
|
|
12
|
-
export declare function scheduleEditorHtml(raw: string | null, submitted?: URLSearchParams):
|
|
13
|
+
export declare function scheduleEditorHtml(raw: string | null, submitted?: URLSearchParams): Html;
|
|
13
14
|
export declare function scheduleEditorScript(): string;
|
package/dist/task-composer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** UI choices translate into the same digest-bound schedules as the CLI. */
|
|
2
2
|
import { parseSchedule, scheduleText, validTimezone } from "./flow-schedule.js";
|
|
3
|
+
import { html, joinHtml } from "./html.js";
|
|
3
4
|
export function composerSchedule(fields) {
|
|
4
5
|
const repeat = fields.get("repeat") ?? "once";
|
|
5
6
|
if (repeat === "once")
|
|
@@ -29,18 +30,17 @@ export function scheduleFields(schedule) {
|
|
|
29
30
|
}
|
|
30
31
|
return { repeat: schedule.kind, time: schedule.hhmm, timezone: schedule.timezone ?? "UTC", ...(schedule.kind === "weekly" ? { weekday: String(schedule.day) } : {}) };
|
|
31
32
|
}
|
|
32
|
-
const escape = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
33
33
|
/** A normal form works without JavaScript; enhancement only folds unused fields. */
|
|
34
34
|
export function scheduleEditorHtml(raw, submitted) {
|
|
35
35
|
const parsed = raw === null ? null : parseSchedule(raw);
|
|
36
36
|
const fields = submitted?.has("repeat") ? Object.fromEntries(submitted) : parsed === null ? {} : scheduleFields(parsed);
|
|
37
37
|
const repeat = fields.repeat ?? "daily";
|
|
38
|
-
const value = (name, fallback) =>
|
|
39
|
-
return `<fieldset data-schedule-editor${raw === null && submitted === undefined ?
|
|
40
|
-
`<label>Repeat<select name="repeat">${[["daily", "Daily"], ["weekly", "Weekly"], ["custom", "Custom interval"]].map(([id, label]) => `<option value="${id}"${repeat === id ?
|
|
41
|
-
`<div data-schedule-kind="weekly"><label>Day<select name="weekday">${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].map((day, index) => `<option value="${index}"${String(index) === (fields.weekday ?? "1") ?
|
|
42
|
-
`<div data-schedule-kind="calendar"><label>Time<input type="time" name="time" value="${value("time", "09:00")}"></label><label>Timezone<input type="text" name="timezone" value="${value("timezone", "UTC")}" autocomplete="off" placeholder="America/Los_Angeles"></label><p class="meta">Daily and weekly times follow this timezone. A skipped clock time skips that occurrence; a repeated time runs once.</p></div
|
|
43
|
-
`<div data-schedule-kind="custom"><label>Every<input type="number" name="interval" min="1" value="${value("interval", "60")}"></label><label>Unit<select name="interval-unit">${["minutes", "hours", "days"].map(unit => `<option${unit === (fields["interval-unit"] ?? "minutes") ?
|
|
38
|
+
const value = (name, fallback) => fields[name] ?? fallback;
|
|
39
|
+
return joinHtml([html `<fieldset data-schedule-editor${raw === null && submitted === undefined ? html ` data-detect-timezone="true"` : ""}><legend>When to run</legend>`,
|
|
40
|
+
html `<label>Repeat<select name="repeat">${[["daily", "Daily"], ["weekly", "Weekly"], ["custom", "Custom interval"]].map(([id, label]) => html `<option value="${id}"${repeat === id ? html ` selected` : ""}>${label}</option>`)}</select></label>`,
|
|
41
|
+
html `<div data-schedule-kind="weekly"><label>Day<select name="weekday">${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].map((day, index) => html `<option value="${index}"${String(index) === (fields.weekday ?? "1") ? html ` selected` : ""}>${day}</option>`)}</select></label></div>`,
|
|
42
|
+
html `<div data-schedule-kind="calendar"><label>Time<input type="time" name="time" value="${value("time", "09:00")}"></label><label>Timezone<input type="text" name="timezone" value="${value("timezone", "UTC")}" autocomplete="off" placeholder="America/Los_Angeles"></label><p class="meta">Daily and weekly times follow this timezone. A skipped clock time skips that occurrence; a repeated time runs once.</p></div>`,
|
|
43
|
+
html `<div data-schedule-kind="custom"><label>Every<input type="number" name="interval" min="1" value="${value("interval", "60")}"></label><label>Unit<select name="interval-unit">${["minutes", "hours", "days"].map(unit => html `<option${unit === (fields["interval-unit"] ?? "minutes") ? html ` selected` : ""}>${unit}</option>`)}</select></label><p class="meta">Intervals range from 5 minutes to 7 days.</p></div></fieldset>`]);
|
|
44
44
|
}
|
|
45
45
|
export function scheduleEditorScript() {
|
|
46
46
|
return `(function(){document.querySelectorAll('[data-schedule-editor]').forEach(function(root){
|
package/dist/task-status.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Html } from "./html.js";
|
|
1
2
|
import type { AssignmentSnapshot } from "./assignment.js";
|
|
2
3
|
import { type NeedAction, type NeedContext, type NeedKey, type WaitKey } from "./needs-you.js";
|
|
3
4
|
export type { NeedKey, WaitKey } from "./needs-you.js";
|
|
@@ -200,9 +201,9 @@ export declare function workToneOf(headline: Headline): "attention" | "problem"
|
|
|
200
201
|
export declare function statusDetailLines(status: TaskStatus): string[];
|
|
201
202
|
/** The headline's emoji for chat cards (colour is never the only signal: the words follow). */
|
|
202
203
|
export declare function headlineEmoji(headline: Headline): string;
|
|
203
|
-
export declare const statusIconSvg: (mark: DetailMark) =>
|
|
204
|
+
export declare const statusIconSvg: (mark: DetailMark) => Html;
|
|
204
205
|
/** The detail rows as server HTML: quiet rows, colour only on the icon. */
|
|
205
|
-
export declare function statusDetailsHtml(status: TaskStatus):
|
|
206
|
+
export declare function statusDetailsHtml(status: TaskStatus): Html;
|
|
206
207
|
/** The status card's fold of exact reasons: "More", never a third thing called Details. */
|
|
207
208
|
export declare const STATUS_MORE = "More";
|
|
208
209
|
/** The technical reasons, one tap away. */
|
|
@@ -210,7 +211,7 @@ export declare function statusWhyHtml(status: TaskStatus, extra?: readonly strin
|
|
|
210
211
|
token: string;
|
|
211
212
|
label: string;
|
|
212
213
|
detail: string;
|
|
213
|
-
}[]):
|
|
214
|
+
}[]): Html;
|
|
214
215
|
/** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
|
|
215
216
|
export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}";
|
|
216
217
|
/** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
|
package/dist/task-status.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* output) is an amber note on its own row with one action; the exact
|
|
12
12
|
* technical reason stays one tap away in `why`. */
|
|
13
13
|
import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
|
|
14
|
+
import { html } from "./html.js";
|
|
14
15
|
import { CODE_NEED, NEEDS, needSentence, waitSentence } from "./needs-you.js";
|
|
15
16
|
/** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
|
|
16
17
|
export const replacedWords = (successor) => `Replaced by ${successor}`;
|
|
@@ -398,24 +399,23 @@ export function statusDetailLines(status) {
|
|
|
398
399
|
export function headlineEmoji(headline) {
|
|
399
400
|
return { Queued: "🕓", Planning: "📝", "Needs you": "👋", Waiting: "⏸", Building: "⏳", "Ready for review": "✅", Complete: "✅", Failed: "❌", Stopped: "⏹" }[headline];
|
|
400
401
|
}
|
|
401
|
-
const escapeHtml = (value) => value.replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
402
402
|
const ICON_PATHS = {
|
|
403
|
-
ok: `<path d="M20 6 9 17l-5-5"/>`,
|
|
404
|
-
running: `<circle cx="12" cy="12" r="4"/>`,
|
|
405
|
-
none: `<circle cx="12" cy="12" r="3"/>`,
|
|
406
|
-
note: `<path d="M12 8v5"/><path d="M12 16.5h.01"/><path d="M10.3 3.9 2.4 18a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>`,
|
|
407
|
-
failed: `<path d="M18 6 6 18M6 6l12 12"/>`,
|
|
403
|
+
ok: html `<path d="M20 6 9 17l-5-5"/>`,
|
|
404
|
+
running: html `<circle cx="12" cy="12" r="4"/>`,
|
|
405
|
+
none: html `<circle cx="12" cy="12" r="3"/>`,
|
|
406
|
+
note: html `<path d="M12 8v5"/><path d="M12 16.5h.01"/><path d="M10.3 3.9 2.4 18a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>`,
|
|
407
|
+
failed: html `<path d="M18 6 6 18M6 6l12 12"/>`,
|
|
408
408
|
};
|
|
409
|
-
export const statusIconSvg = (mark) => `<svg class="status-icon status-icon--${mark}" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS[mark]}</svg>`;
|
|
409
|
+
export const statusIconSvg = (mark) => html `<svg class="status-icon status-icon--${mark}" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS[mark]}</svg>`;
|
|
410
410
|
/** The detail rows as server HTML: quiet rows, colour only on the icon. */
|
|
411
411
|
export function statusDetailsHtml(status) {
|
|
412
412
|
if (status.details.length === 0)
|
|
413
|
-
return
|
|
414
|
-
return `<ul class="status-details" aria-label="Status"
|
|
415
|
-
const text = one.href === null ?
|
|
416
|
-
const action = one.action === null ? "" : one.action.href === null ? `<span class="status-detail-act">${
|
|
417
|
-
return `<li class="status-detail status-detail--${one.mark}" data-status-detail="${one.key}" data-mark="${one.mark}">${statusIconSvg(one.mark)}<span class="status-detail-label">${
|
|
418
|
-
})
|
|
413
|
+
return html ``;
|
|
414
|
+
return html `<ul class="status-details" aria-label="Status">${status.details.map(one => {
|
|
415
|
+
const text = one.href === null ? one.text : html `<a href="${one.href}">${one.text}</a>`;
|
|
416
|
+
const action = one.action === null ? "" : one.action.href === null ? html `<span class="status-detail-act">${one.action.label}</span>` : html `<a class="status-detail-act" href="${one.action.href}">${one.action.label}</a>`;
|
|
417
|
+
return html `<li class="status-detail status-detail--${one.mark}" data-status-detail="${one.key}" data-mark="${one.mark}">${statusIconSvg(one.mark)}<span class="status-detail-label">${one.label}</span><span class="status-detail-text"><span>${text}</span>${action}</span></li>`;
|
|
418
|
+
})}</ul>`;
|
|
419
419
|
}
|
|
420
420
|
/** The status card's fold of exact reasons: "More", never a third thing called Details. */
|
|
421
421
|
export const STATUS_MORE = "More";
|
|
@@ -423,9 +423,8 @@ export const STATUS_MORE = "More";
|
|
|
423
423
|
export function statusWhyHtml(status, extra = [], diagnostics = []) {
|
|
424
424
|
const lines = [...new Set([...status.details.flatMap(one => one.why === null ? [] : [`${one.label}: ${one.why}`]), ...status.why, ...extra])];
|
|
425
425
|
if (lines.length === 0 && diagnostics.length === 0)
|
|
426
|
-
return
|
|
427
|
-
return `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => `<p class="meta">${
|
|
428
|
-
diagnostics.map(one => `<p class="meta" data-work-diagnostic="${escapeHtml(one.token)}">${escapeHtml(one.label)} · ${escapeHtml(one.detail)}</p>`).join("") + `</details>`;
|
|
426
|
+
return html ``;
|
|
427
|
+
return html `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => html `<p class="meta">${one}</p>`)}${diagnostics.map(one => html `<p class="meta" data-work-diagnostic="${one.token}">${one.label} · ${one.detail}</p>`)}</details>`;
|
|
429
428
|
}
|
|
430
429
|
/** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
|
|
431
430
|
export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}`;
|
package/dist/team-http.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
2
2
|
import { type TeamActor, type TeamExecute } from './team-contract.js';
|
|
3
3
|
import { type Admission } from './request-budget.js';
|
|
4
4
|
export declare const TEAM_REQUEST_BYTES: number;
|
|
5
|
-
/** How long a password bearer's proof stands on a long
|
|
6
|
-
* The account, its revocation and its credential generation are still rechecked on every
|
|
5
|
+
/** How long a password bearer's proof stands on a long live team stream (GET /live?room=team) before its password is
|
|
6
|
+
* checked again. The account, its revocation and its credential generation are still rechecked on every signal. */
|
|
7
7
|
export declare const TEAM_PASSWORD_REVERIFY_MS: number;
|
|
8
8
|
/** Account role belongs to HTTP authentication; API tokens carry their own scope. */
|
|
9
9
|
export type TeamHttpActor = TeamActor & {
|
|
@@ -15,15 +15,11 @@ export type TeamHttpOptions = {
|
|
|
15
15
|
revalidate: (request: IncomingMessage, actor: TeamActor) => boolean;
|
|
16
16
|
authorizeMutation: (request: IncomingMessage, actor: TeamActor) => boolean;
|
|
17
17
|
execute: TeamExecute;
|
|
18
|
-
/**
|
|
19
|
-
cursor?: (actor: TeamActor, conversationId?: string) => number | null;
|
|
20
|
-
/** Source admission before credential verification. Streams are charged once when opened. */
|
|
18
|
+
/** Source admission before credential verification. */
|
|
21
19
|
admit?: (request: IncomingMessage) => Admission;
|
|
22
20
|
/** Proved account/token admission, before reading the body or executing any operation. */
|
|
23
21
|
admitAuthenticated?: (request: IncomingMessage, actor: TeamActor) => Admission;
|
|
24
|
-
streamIntervalMs?: number;
|
|
25
|
-
streams?: Set<ServerResponse>;
|
|
26
22
|
};
|
|
27
|
-
/** Authenticated browser and remote CLI share one operation boundary.
|
|
28
|
-
*
|
|
23
|
+
/** Authenticated browser and remote CLI share one operation boundary. Live
|
|
24
|
+
* updates come from GET /live?room=team (server/live.ts). */
|
|
29
25
|
export declare function handleTeamHttp(request: IncomingMessage, response: ServerResponse, options: TeamHttpOptions): Promise<boolean>;
|
package/dist/team-http.js
CHANGED
|
@@ -2,8 +2,8 @@ import { adapterPolicy } from "./server/route-policy.js";
|
|
|
2
2
|
import { TEAM_OPERATIONS, TEAM_MUTATIONS, teamScopeAllows } from './team-contract.js';
|
|
3
3
|
import { limitWords } from './request-budget.js';
|
|
4
4
|
export const TEAM_REQUEST_BYTES = 64 * 1024;
|
|
5
|
-
/** How long a password bearer's proof stands on a long
|
|
6
|
-
* The account, its revocation and its credential generation are still rechecked on every
|
|
5
|
+
/** How long a password bearer's proof stands on a long live team stream (GET /live?room=team) before its password is
|
|
6
|
+
* checked again. The account, its revocation and its credential generation are still rechecked on every signal. */
|
|
7
7
|
export const TEAM_PASSWORD_REVERIFY_MS = 5 * 60_000;
|
|
8
8
|
const failure = (code, message) => ({ version: 1, ok: false, code, message });
|
|
9
9
|
function send(response, status, value, headers = {}) {
|
|
@@ -33,11 +33,11 @@ function valid(value) {
|
|
|
33
33
|
!!row.args && typeof row.args === 'object' && !Array.isArray(row.args) &&
|
|
34
34
|
!['actor', 'generation', 'authenticatedActor'].some(key => key in row.args);
|
|
35
35
|
}
|
|
36
|
-
/** Authenticated browser and remote CLI share one operation boundary.
|
|
37
|
-
*
|
|
36
|
+
/** Authenticated browser and remote CLI share one operation boundary. Live
|
|
37
|
+
* updates come from GET /live?room=team (server/live.ts). */
|
|
38
38
|
export async function handleTeamHttp(request, response, options) {
|
|
39
39
|
const url = new URL(request.url ?? '/', 'http://standing-orders.local');
|
|
40
|
-
if (url.pathname !== '/api/team'
|
|
40
|
+
if (url.pathname !== '/api/team')
|
|
41
41
|
return false;
|
|
42
42
|
const reject = (status, code, message) => { send(response, status, failure(code, message)); request.resume(); return true; };
|
|
43
43
|
if ([...url.searchParams.keys()].some(key => /token|password|credential|authorization/i.test(key)))
|
|
@@ -71,64 +71,6 @@ export async function handleTeamHttp(request, response, options) {
|
|
|
71
71
|
if (!adapterPolicy(policyPrincipal()).ok)
|
|
72
72
|
return reject(403, 'read-only', 'Your token reads only. Use an act token for this.');
|
|
73
73
|
const conversationId = url.searchParams.get('conversation') ?? undefined;
|
|
74
|
-
if (url.pathname === '/api/team/events') {
|
|
75
|
-
if (request.method !== 'GET')
|
|
76
|
-
return reject(405, 'method-not-allowed', 'Use GET for conversation updates.');
|
|
77
|
-
let initial;
|
|
78
|
-
try {
|
|
79
|
-
initial = await options.execute(actor, { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} });
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return reject(503, 'unavailable', 'Conversation updates are unavailable.');
|
|
83
|
-
}
|
|
84
|
-
if (!initial.ok || !initial.snapshot) {
|
|
85
|
-
send(response, 403, initial);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
response.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', 'x-accel-buffering': 'no' });
|
|
89
|
-
options.streams?.add(response);
|
|
90
|
-
let cursor = initial.snapshot.cursor;
|
|
91
|
-
response.write(`id: ${cursor}\nevent: change\ndata: {"cursor":${cursor}}\n\n`);
|
|
92
|
-
let active = false;
|
|
93
|
-
const close = () => { clearInterval(timer); options.streams?.delete(response); response.end(); };
|
|
94
|
-
const revoked = () => { response.write('event: revoked\ndata: {}\n\n'); close(); };
|
|
95
|
-
const tick = async () => {
|
|
96
|
-
if (active || response.destroyed || response.writableEnded)
|
|
97
|
-
return;
|
|
98
|
-
active = true;
|
|
99
|
-
try {
|
|
100
|
-
if (!options.revalidate(request, actor))
|
|
101
|
-
return revoked();
|
|
102
|
-
let next;
|
|
103
|
-
if (options.cursor)
|
|
104
|
-
next = options.cursor(actor, conversationId);
|
|
105
|
-
else {
|
|
106
|
-
const reply = await options.execute(actor, { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} });
|
|
107
|
-
next = reply.ok ? reply.snapshot?.cursor ?? null : null;
|
|
108
|
-
}
|
|
109
|
-
if (next === null)
|
|
110
|
-
return revoked();
|
|
111
|
-
if (next !== cursor) {
|
|
112
|
-
cursor = next;
|
|
113
|
-
// A slow client receives a fresh scoped snapshot on reconnect, never an unbounded replay buffer.
|
|
114
|
-
if (!response.write(`id: ${cursor}\nevent: change\ndata: {"cursor":${cursor}}\n\n`))
|
|
115
|
-
close();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
response.write('event: unavailable\ndata: {}\n\n');
|
|
120
|
-
close();
|
|
121
|
-
}
|
|
122
|
-
finally {
|
|
123
|
-
active = false;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const timer = setInterval(() => { void tick(); }, options.streamIntervalMs ?? 1_000);
|
|
127
|
-
timer.unref();
|
|
128
|
-
request.once('close', close);
|
|
129
|
-
response.once('close', close);
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
74
|
let input;
|
|
133
75
|
if (request.method === 'GET')
|
|
134
76
|
input = { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} };
|
package/dist/team-ui.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Html } from './html.js';
|
|
1
2
|
import type { TeamSnapshot } from './team-contract.js';
|
|
2
3
|
/** Readable fallback uses the same admitted projection as the interactive UI. */
|
|
3
|
-
export declare function teamWorkspaceHtml(snapshot: TeamSnapshot):
|
|
4
|
+
export declare function teamWorkspaceHtml(snapshot: TeamSnapshot): Html;
|
package/dist/team-ui.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { html } from './html.js';
|
|
2
2
|
/** Readable fallback uses the same admitted projection as the interactive UI. */
|
|
3
3
|
export function teamWorkspaceHtml(snapshot) {
|
|
4
4
|
const selected = snapshot.selected;
|
|
5
|
-
return `<section class="team-workspace"><h1>${
|
|
6
|
-
(selected ? `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${escape(snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead')}</p>` : '') +
|
|
7
|
-
(snapshot.messages.length ? snapshot.messages.map(message => `<article><strong>${escape(message.author)}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${escape(message.text)}</p>${message.status === 'queued' || message.status === 'running' ? `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? `<p role="alert">${escape(message.error)}</p>` : ''}</article>`).join('') : '<p>Start a conversation with your team lead.</p>') +
|
|
8
|
-
'<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>';
|
|
5
|
+
return html `<section class="team-workspace"><h1>${selected?.title ?? 'Team chat'}</h1>${selected ? html `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead'}</p>` : ''}${snapshot.messages.length ? snapshot.messages.map(message => html `<article><strong>${message.author}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${message.text}</p>${message.status === 'queued' || message.status === 'running' ? html `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? html `<p role="alert">${message.error}</p>` : ''}</article>`) : html `<p>Start a conversation with your team lead.</p>`}<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>`;
|
|
9
6
|
}
|
|
@@ -30,8 +30,8 @@ const who = (store, question) => { const mate = store.getTeammate(question.teamm
|
|
|
30
30
|
/** Everyone else's buttons retire once a question is answered anywhere. */
|
|
31
31
|
function retireEverywhere(store, question, now, state) {
|
|
32
32
|
store.retireTelegramQuestion(question, now);
|
|
33
|
-
state?.prepare("UPDATE chat_question_action SET consumed=? WHERE question=? AND consumed IS NULL").run(now.toISOString(), question);
|
|
34
|
-
state?.prepare("UPDATE chat_question_prompt SET consumed=? WHERE question=? AND consumed IS NULL").run(now.toISOString(), question);
|
|
33
|
+
state?.prepare("UPDATE chat_question_action SET consumed=? WHERE provider=:provider AND question=? AND consumed IS NULL").run(now.toISOString(), question);
|
|
34
|
+
state?.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND question=? AND consumed IS NULL").run(now.toISOString(), question);
|
|
35
35
|
}
|
|
36
36
|
// ---- Telegram ----------------------------------------------------------------------
|
|
37
37
|
/** Buttons for the question, minted before the send (for the person it asks only); `place` stamps the message they land on. */
|
|
@@ -91,23 +91,23 @@ export function questionParts(store, notification, binding) {
|
|
|
91
91
|
}
|
|
92
92
|
/** The live buttons on one part, in the order they were minted. */
|
|
93
93
|
export function chatQuestionButtons(state, part, now) {
|
|
94
|
-
const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
|
|
94
|
+
const row = state.prepare("SELECT payload FROM chat_part WHERE provider=:provider AND id=?").get(part);
|
|
95
95
|
const content = row === undefined ? null : partContent(String(row["payload"]));
|
|
96
96
|
const labels = new Map((content?.question?.choices ?? []).map(one => [one.choice ?? "", one.label]));
|
|
97
|
-
return state.prepare("SELECT token,choice FROM chat_question_action WHERE part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
|
|
97
|
+
return state.prepare("SELECT token,choice FROM chat_question_action WHERE provider=:provider AND part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
|
|
98
98
|
.map(one => ({ token: String(one.token), label: (labels.get(one.choice ?? "") ?? IN_WORDS).slice(0, 75), words: one.choice === null }));
|
|
99
99
|
}
|
|
100
100
|
/** Show the tapped notice again with the answer, and without its buttons. */
|
|
101
101
|
function repaint(state, part, event, line) {
|
|
102
|
-
const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
|
|
102
|
+
const row = state.prepare("SELECT payload FROM chat_part WHERE provider=:provider AND id=?").get(part);
|
|
103
103
|
const before = row === undefined ? { text: "" } : partContent(String(row["payload"]));
|
|
104
104
|
const content = { text: `${before.text}\n\n${line}`.slice(0, 3400), edit: event.ts, ...(before.link === undefined ? {} : { link: before.link }), ...(before.channel === undefined ? {} : { channel: before.channel }) };
|
|
105
|
-
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(savedChatPart(content), part);
|
|
105
|
+
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE provider=:provider AND id=?").run(savedChatPart(content), part);
|
|
106
106
|
}
|
|
107
107
|
/** A tapped question button, inside the action's transaction; false when the token isn't one. */
|
|
108
108
|
export function applyChatQuestionTap(options, event, binding, token, now) {
|
|
109
109
|
const { store, state } = options;
|
|
110
|
-
const action = state.prepare("SELECT a
|
|
110
|
+
const action = state.prepare("SELECT a.token,a.part,a.question,a.choice,a.expires,a.consumed,p.message,e.binding AS owner,e.channel FROM chat_question_action a JOIN chat_part p ON p.provider=a.provider AND p.id=a.part JOIN chat_event e ON e.provider=p.provider AND e.id=p.event WHERE a.provider=:provider AND a.token=?").get(token);
|
|
111
111
|
if (action === undefined)
|
|
112
112
|
return false;
|
|
113
113
|
const say = (text) => state.plan(event.id, [{ text }], now);
|
|
@@ -126,9 +126,9 @@ export function applyChatQuestionTap(options, event, binding, token, now) {
|
|
|
126
126
|
}
|
|
127
127
|
if (action["choice"] === null) {
|
|
128
128
|
// One open prompt per person, across flow decisions and questions: the newest is the one the next message answers.
|
|
129
|
-
state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
|
|
130
|
-
state.prepare("UPDATE chat_flow_prompt SET consumed=? WHERE binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
|
|
131
|
-
state.prepare("INSERT INTO chat_question_prompt(binding,question,created,expires) VALUES(
|
|
129
|
+
state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
|
|
130
|
+
state.prepare("UPDATE chat_flow_prompt SET consumed=? WHERE provider=:provider AND binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
|
|
131
|
+
state.prepare("INSERT INTO chat_question_prompt(provider,id,binding,question,created,expires) VALUES(:provider,(SELECT COALESCE(MAX(id),0)+1 FROM chat_question_prompt WHERE provider=:provider),?,?,?,?)").run(binding.id, questionId, now.toISOString(), new Date(now.getTime() + PROMPT_MS).toISOString());
|
|
132
132
|
say(`Your next message here is your answer to ${who(store, question)}: “${question.question}” Send “cancel” to leave it.`);
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
@@ -149,14 +149,14 @@ export function answerChatQuestionPrompt(options, event, binding, input, now) {
|
|
|
149
149
|
if (event.channel !== binding.channel)
|
|
150
150
|
return false;
|
|
151
151
|
return store.transact(() => {
|
|
152
|
-
const prompt = state.prepare("SELECT * FROM chat_question_prompt WHERE binding=? AND consumed IS NULL AND expires>? ORDER BY id DESC LIMIT 1").get(binding.id, now.toISOString());
|
|
152
|
+
const prompt = state.prepare("SELECT * FROM chat_question_prompt WHERE provider=:provider AND binding=? AND consumed IS NULL AND expires>? ORDER BY id DESC LIMIT 1").get(binding.id, now.toISOString());
|
|
153
153
|
if (prompt === undefined)
|
|
154
154
|
return false;
|
|
155
155
|
const id = Number(prompt["id"]), questionId = Number(prompt["question"]);
|
|
156
156
|
const say = (text) => state.plan(event.id, [{ text }], now);
|
|
157
157
|
const said = input.text.trim();
|
|
158
158
|
if (/^cancel\.?$/i.test(said)) {
|
|
159
|
-
state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE id=?").run(now.toISOString(), id);
|
|
159
|
+
state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND id=?").run(now.toISOString(), id);
|
|
160
160
|
say("Left it unanswered. The buttons on the message still work.");
|
|
161
161
|
return true;
|
|
162
162
|
}
|
package/dist/teammates-ui.d.ts
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
* and its settings. Server-rendered forms, like the other settings pages.
|
|
5
5
|
*/
|
|
6
6
|
import type { Store, TeammateRow } from "./store.js";
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
/** v94: the Tools section's rule rows; a limit's fields show only while "Up to a limit" is chosen. */
|
|
8
9
|
export declare const TEAMMATE_CSS = ".teammate-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.teammate-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.teammate-tools h3{font-size:1rem;margin:0 0 4px}.teammate-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.teammate-tools .tool-rule:last-of-type{border-bottom:0}.teammate-tools .tool-rule .meta{display:block}.teammate-tools select,.teammate-tools input{max-width:100%;box-sizing:border-box}.teammate-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.teammate-tools .tool-rule:has(option[value=\"limit\"]:checked) .tool-limit{display:flex}.teammate-tools .tool-rule>select{width:auto;min-width:16em}.teammate-tools .tool-limit select{width:auto}.teammate-tools .tool-limit input{width:7em}.teammate-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.teammate-tools button{min-height:44px}.teammate-activity li{overflow-wrap:anywhere}.teammate-memory .memory-tell,.teammate-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.teammate-memory .memory-tell textarea{flex:1 1 280px}.teammate-memory .memory-search input{flex:1 1 220px}.teammate-memory button{min-height:44px}.teammate-memories{list-style:none;padding:0;margin:0}.teammate-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.teammate-memories li:first-child{border-top:0}.teammate-memories .meta{display:block;font-size:.85rem}.teammate-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.teammate-overrides,.teammate-undo{padding-left:18px}.teammate-undo li{margin:6px 0}.teammate-undo form{display:inline}.teammate-week h3{font-size:1rem;margin:14px 0 4px}.teammate-week button{min-height:44px}.teammate-routines{list-style:none;padding:0;margin:8px 0}.teammate-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.teammate-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.teammate-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.teammate-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.teammate-tools select,.teammate-tools input{font-size:16px}}";
|
|
9
10
|
/** A blank soul file: the sections a teammate reads, to fill in. */
|
|
10
11
|
export declare const BLANK_SOUL = "---\nname: \nrole: \n---\n\n## Who you are\n\n\n## How you write\n\n\n## What you know\n\n\n## Decide on your own\n- \n\n## Ask first\n- \n\n## Never\n- \n";
|
|
11
|
-
export declare function teammatesListHtml(store: Store, mates: readonly TeammateRow[], projects: readonly string[], projectName: (repo: string) => string,
|
|
12
|
+
export declare function teammatesListHtml(store: Store, mates: readonly TeammateRow[], projects: readonly string[], projectName: (repo: string) => string, canCreate: boolean, notice: {
|
|
12
13
|
said?: string | null;
|
|
13
14
|
problem?: string | null;
|
|
14
|
-
}):
|
|
15
|
-
export declare function teammatePageHtml(store: Store, mate: TeammateRow, viewer: string, projectName: (repo: string) => string,
|
|
15
|
+
}): Html;
|
|
16
|
+
export declare function teammatePageHtml(store: Store, mate: TeammateRow, viewer: string, projectName: (repo: string) => string, canManage: boolean, approvers: readonly string[], notice: {
|
|
16
17
|
said?: string | null;
|
|
17
18
|
problem?: string | null;
|
|
18
19
|
soulDraft?: string | null;
|
|
19
20
|
query?: string | null;
|
|
20
|
-
}):
|
|
21
|
+
}): Html;
|