toolroll 0.9.58 → 0.9.60
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 +18 -0
- package/README.md +8 -6
- package/dist/action-ledger.js +10 -1
- package/dist/assignment-adapters.d.ts +1 -1
- package/dist/assignment-adapters.js +1 -1
- package/dist/assignment-delivery.d.ts +16 -0
- package/dist/assignment-delivery.js +54 -28
- package/dist/assignment-status.d.ts +13 -4
- package/dist/assignment-status.js +70 -39
- package/dist/assignment.d.ts +6 -2
- package/dist/assignment.js +13 -8
- package/dist/bin.js +11 -2
- package/dist/brief-fence.d.ts +9 -0
- package/dist/brief-fence.js +22 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +19 -19
- package/dist/builder.js +78 -27
- package/dist/chat-channel.d.ts +1 -0
- package/dist/chat-channel.js +4 -1
- package/dist/chat-delivery.js +2 -0
- package/dist/cli-fast.d.ts +8 -0
- package/dist/cli-fast.js +18 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.js +91 -0
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +17 -96
- package/dist/contract-changes.d.ts +52 -0
- package/dist/contract-changes.js +61 -0
- package/dist/decision.d.ts +4 -0
- package/dist/decision.js +8 -0
- package/dist/deploy-lock.d.ts +8 -0
- package/dist/deploy-lock.js +45 -0
- package/dist/design/tokens.css +234 -0
- package/dist/discord.js +2 -1
- package/dist/dispatch.js +1 -1
- package/dist/evidence.d.ts +22 -0
- package/dist/evidence.js +76 -17
- package/dist/flow-actions.js +2 -1
- package/dist/guides.js +14 -11
- package/dist/lead-cli.js +4 -1
- package/dist/lead-contract.js +1 -1
- package/dist/lead-doors.js +8 -2
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +138 -88
- package/dist/lead-tools.js +11 -3
- package/dist/mailbox.js +3 -2
- package/dist/mcp-core.js +1 -1
- package/dist/needs-you.js +3 -1
- package/dist/operate.js +38 -17
- package/dist/password-bearer.d.ts +4 -4
- package/dist/password-bearer.js +2 -2
- package/dist/planner-source.d.ts +23 -53
- package/dist/planner-source.js +43 -68
- package/dist/provider.d.ts +8 -0
- package/dist/provider.js +12 -1
- package/dist/release-gate.d.ts +2 -3
- package/dist/release-gate.js +44 -16
- package/dist/scope.js +14 -0
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +6 -3
- package/dist/server/chrome.js +8 -4
- package/dist/server/console.css +9 -64
- package/dist/server/render-chat.js +12 -2
- package/dist/server/render-tasks.d.ts +2 -0
- package/dist/server/render-tasks.js +30 -22
- package/dist/server/task-views.js +1 -0
- package/dist/server/tasks.js +3 -1
- package/dist/slack-socket.d.ts +12 -0
- package/dist/slack-socket.js +29 -0
- package/dist/slack.d.ts +0 -7
- package/dist/slack.js +2 -24
- package/dist/steer-mailbox.d.ts +39 -0
- package/dist/steer-mailbox.js +207 -0
- package/dist/steer-status.d.ts +19 -0
- package/dist/steer-status.js +30 -0
- package/dist/store.d.ts +85 -8
- package/dist/store.js +203 -15
- package/dist/surface.js +2 -2
- package/dist/sweep-gate.d.ts +10 -0
- package/dist/sweep-gate.js +18 -0
- package/dist/task-status.js +3 -1
- package/dist/team-updates.js +3 -3
- package/dist/telegram-lead.js +6 -0
- package/dist/telegram.js +9 -7
- package/dist/toolroll-update.js +20 -1
- package/package.json +21 -21
package/dist/serve.js
CHANGED
|
@@ -7,7 +7,7 @@ import { CodingWorkspace } from './coding-workspace.js';
|
|
|
7
7
|
import { CONSOLE_FORMS, readForm } from "./contracts/console-api.js";
|
|
8
8
|
import { createFlowRooms, flowFingerprint } from "./flow-live.js";
|
|
9
9
|
import {} from "./google-mail.js";
|
|
10
|
-
import { configureLeadFollow, runLeadFollowPass } from './lead-follow.js';
|
|
10
|
+
import { configureLeadFollow, recordLeadFollowFailure, runLeadFollowPass } from './lead-follow.js';
|
|
11
11
|
import { leadNameOf } from "./lead-identity.js";
|
|
12
12
|
import { createLiveBus, followWorkspace } from "./live-bus.js";
|
|
13
13
|
import { startMaintenance } from './maintenance.js';
|
|
@@ -1570,7 +1570,8 @@ export function createDecisionServer(options) {
|
|
|
1570
1570
|
run: () => runLeadFollowPass({ store, repos: managedRepos, evidenceRoot, clock,
|
|
1571
1571
|
provider: () => { const enabled = chatEnablement(); return enabled.ok ? { config: enabled.config, key: enabled.key } : null; },
|
|
1572
1572
|
fetcher: chatFetcher, ...(options.subscriptionChatRunner ? { subscriptionRunner: options.subscriptionChatRunner } : {}) }),
|
|
1573
|
-
|
|
1573
|
+
// Pending delivery is durable and is read again on the next pass; a failure is said where follow status is read.
|
|
1574
|
+
onError: error => recordLeadFollowFailure(store, error, clock()),
|
|
1574
1575
|
});
|
|
1575
1576
|
});
|
|
1576
1577
|
// Keep native process custody until shutdown has verified agent/tool exit.
|
package/dist/server/chrome.d.ts
CHANGED
|
@@ -42,9 +42,12 @@ export declare const DEMO_BANNER_SHORT = "Demo: sample projects. Nothing calls a
|
|
|
42
42
|
* inset into it. Semantic names only; every surface (server pages, the
|
|
43
43
|
* React workspace, the pre-script fallback) reads these.
|
|
44
44
|
*
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* The palette above, in light, dark and pinned themes, is Signal's generated
|
|
46
|
+
* tokens.css (src/design, built by youi from the design-language repo); the
|
|
47
|
+
* console's own rules follow in console.css, shipped beside this module.
|
|
48
|
+
* Their bytes are the served sheet's (WORKSPACE_STYLE hashes them). The React
|
|
49
|
+
* workspace's stylesheet loads after this one and never redefines a token, so
|
|
50
|
+
* a person's accent (accentHead) still wins over the generated default. */
|
|
48
51
|
export declare const STYLE: string;
|
|
49
52
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
50
53
|
export declare const THEME_CONTROLS_CSS = ".task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles a>svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed=\"true\"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}";
|
package/dist/server/chrome.js
CHANGED
|
@@ -127,10 +127,14 @@ export const DEMO_BANNER_SHORT = "Demo: sample projects. Nothing calls a model o
|
|
|
127
127
|
* inset into it. Semantic names only; every surface (server pages, the
|
|
128
128
|
* React workspace, the pre-script fallback) reads these.
|
|
129
129
|
*
|
|
130
|
-
* The
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
|
|
130
|
+
* The palette above, in light, dark and pinned themes, is Signal's generated
|
|
131
|
+
* tokens.css (src/design, built by youi from the design-language repo); the
|
|
132
|
+
* console's own rules follow in console.css, shipped beside this module.
|
|
133
|
+
* Their bytes are the served sheet's (WORKSPACE_STYLE hashes them). The React
|
|
134
|
+
* workspace's stylesheet loads after this one and never redefines a token, so
|
|
135
|
+
* a person's accent (accentHead) still wins over the generated default. */
|
|
136
|
+
export const STYLE = readFileSync(join(import.meta.dirname, "..", "design", "tokens.css"), "utf8") +
|
|
137
|
+
readFileSync(join(import.meta.dirname, "console.css"), "utf8");
|
|
134
138
|
/** Appearance: a three-way segmented switch, one tap per choice. */
|
|
135
139
|
export const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;gap:1.25rem;margin:0 0 2rem}.settings-tiles h2{margin:0 0 .5rem;font-size:.875rem;font-weight:600;color:var(--so-muted)}.settings-tiles section>div{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem}.settings-tiles a>span{display:grid;gap:.1rem;min-width:0}.settings-tiles .provider-status{font-weight:400;font-size:.8125rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles a>svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
136
140
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
package/dist/server/console.css
CHANGED
|
@@ -30,83 +30,28 @@
|
|
|
30
30
|
font-family: "Geist Mono"; font-style: normal; font-weight: 600;
|
|
31
31
|
font-display: swap; src: url("/fonts/geist-mono-600.woff2") format("woff2");
|
|
32
32
|
}
|
|
33
|
-
/*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
/* Signal's colour, shadow, radius, type and spacing tokens (--so-*) are
|
|
34
|
+
* generated by youi into src/design/tokens.css, light and dark, and the
|
|
35
|
+
* server prepends that sheet to this one (chrome.ts STYLE). Edit them in the
|
|
36
|
+
* design-language repo, never here. The device decides the scheme unless the
|
|
37
|
+
* person pins one (html[data-theme], set from their cookie). What follows are
|
|
38
|
+
* the console's older names: views onto those tokens, never a second ramp. */
|
|
38
39
|
:root {
|
|
39
|
-
color-scheme: light;
|
|
40
|
-
|
|
41
|
-
--so-ground: #efefef; --so-paper: #ffffff; --so-sidebar: #efefef; --so-raised: #f5f5f5;
|
|
42
|
-
--so-ink: #171717; --so-muted: #666666; --so-line: #e6e6e6; --so-input-line: #d4d4d4;
|
|
43
|
-
--so-accent: #171717; --so-accent-hover: #383838; --so-accent-text: #171717; --so-on-accent: #ffffff; --so-soft: #f2f2f2;
|
|
44
|
-
--so-nav-ink: #525252; --so-nav-hover: #e4e4e4; --so-nav-current: #ffffff; --so-nav-current-ink: #171717;
|
|
45
|
-
--so-signal: #171717; --so-signal-hover: #383838; --so-on-signal: #ffffff; --so-signal-soft: #e8e8e8;
|
|
46
|
-
--so-danger: #c4320a; --so-danger-soft: #feebe7; --so-success: #218358; --so-success-soft: #e6f6eb;
|
|
47
|
-
--so-warning: #ab6400; --so-warning-soft: #fff4d5; --so-info: #0d74ce; --so-info-soft: #e6f4fe;
|
|
48
|
-
--so-neutral-ink: #525252; --so-neutral-soft: #f0f0f0; --so-live: #0d74ce;
|
|
49
|
-
--so-attention: var(--so-signal); --so-on-attention: var(--so-on-signal); --so-attention-soft: var(--so-signal-soft);
|
|
50
|
-
--so-sheet-shadow: 0 0 0 1px rgb(0 0 0 / .06), 0 1px 2px rgb(0 0 0 / .04), 0 4px 12px -6px rgb(0 0 0 / .06);
|
|
51
|
-
--so-pill-shadow: 0 0 0 1px rgb(0 0 0 / .06), 0 1px 2px rgb(0 0 0 / .06);
|
|
52
|
-
--so-overlay: rgb(0 0 0 / .32); --so-shadow-overlay: 0 0 0 1px rgb(0 0 0 / .08), 0 24px 48px -12px rgb(0 0 0 / .22);
|
|
53
|
-
--so-selection: #d4d4d4; --so-scroll: #cfcfcf; --so-code-bg: #fafafa; --so-user-bubble: #f2f2f2;
|
|
54
|
-
|
|
55
40
|
--background: var(--so-ground); --foreground: var(--so-ink); --card: var(--so-paper);
|
|
56
41
|
--muted: var(--so-raised); --muted-foreground: var(--so-muted); --border: var(--so-line); --input: var(--so-input-line);
|
|
57
42
|
--primary: var(--so-accent); --primary-foreground: var(--so-on-accent); --secondary: var(--so-raised); --secondary-foreground: var(--so-ink);
|
|
58
43
|
--accent: var(--so-soft); --destructive: var(--so-danger); --destructive-strong: var(--so-danger); --destructive-soft: var(--so-danger-soft);
|
|
59
44
|
--success: var(--so-success); --success-soft: var(--so-success-soft); --warning: var(--so-warning); --warning-soft: var(--so-warning-soft);
|
|
60
45
|
--running: var(--so-info); --running-soft: var(--so-info-soft); --ring: var(--so-signal);
|
|
61
|
-
/*
|
|
46
|
+
/* The accent means one thing: a person is needed (approve, answer, decide). */
|
|
62
47
|
--brand: var(--so-attention); --brand-foreground: var(--so-on-attention); --brand-soft: var(--so-attention-soft);
|
|
63
48
|
--glass: var(--so-paper); --glass-strong: var(--so-paper); --glass-border: var(--so-line); --glass-highlight: transparent;
|
|
64
49
|
--ambient-one: transparent; --ambient-two: transparent; --user-message: var(--so-user-bubble);
|
|
65
50
|
--surface: var(--so-paper); --ok: var(--so-success); --danger: var(--so-danger); --fg-muted: var(--so-muted);
|
|
66
51
|
--shadow: none; --shadow-overlay: var(--so-shadow-overlay);
|
|
67
52
|
--radius: 0.625rem;
|
|
68
|
-
--
|
|
69
|
-
--font-
|
|
70
|
-
--font-mono: "Geist Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
71
|
-
/* Phone spacing steps (760px and narrower): side gutter, gap under the
|
|
72
|
-
header, gap between page blocks, between rows, and inside a card. */
|
|
73
|
-
--so-phone-gutter: 16px; --so-phone-gap: 12px; --so-phone-block: 14px; --so-phone-row: 8px; --so-phone-card: 12px;
|
|
74
|
-
}
|
|
75
|
-
@media (prefers-color-scheme: dark) {
|
|
76
|
-
:root:not([data-theme="light"]) {
|
|
77
|
-
color-scheme: dark;
|
|
78
|
-
|
|
79
|
-
--so-ground: #0b0b0b; --so-paper: #161616; --so-sidebar: #0b0b0b; --so-raised: #1c1c1c;
|
|
80
|
-
--so-ink: #ededed; --so-muted: #a1a1a1; --so-line: #262626; --so-input-line: #363636;
|
|
81
|
-
--so-accent: #ededed; --so-accent-hover: #ffffff; --so-accent-text: #ededed; --so-on-accent: #0a0a0a; --so-soft: #1f1f1f;
|
|
82
|
-
--so-nav-ink: #a1a1a1; --so-nav-hover: #171717; --so-nav-current: #1f1f1f; --so-nav-current-ink: #ededed;
|
|
83
|
-
--so-signal: #ededed; --so-signal-hover: #ffffff; --so-on-signal: #0a0a0a; --so-signal-soft: #2e2e2e;
|
|
84
|
-
--so-danger: #ff977d; --so-danger-soft: rgb(255 151 125 / .12); --so-success: #3dd68c; --so-success-soft: rgb(61 214 140 / .12);
|
|
85
|
-
--so-warning: #ffca16; --so-warning-soft: rgb(255 202 22 / .12); --so-info: #70b8ff; --so-info-soft: rgb(112 184 255 / .12);
|
|
86
|
-
--so-neutral-ink: #b4b4b4; --so-neutral-soft: #1f1f1f; --so-live: #70b8ff;
|
|
87
|
-
--so-attention: var(--so-signal); --so-on-attention: var(--so-on-signal); --so-attention-soft: var(--so-signal-soft);
|
|
88
|
-
--so-sheet-shadow: 0 0 0 1px #262626, 0 1px 2px rgb(0 0 0 / .4);
|
|
89
|
-
--so-pill-shadow: 0 0 0 1px #2a2a2a, 0 1px 2px rgb(0 0 0 / .5);
|
|
90
|
-
--so-overlay: rgb(0 0 0 / .6); --so-shadow-overlay: 0 0 0 1px #2e2e2e, 0 24px 48px -12px rgb(0 0 0 / .7);
|
|
91
|
-
--so-selection: #3a3a3a; --so-scroll: #333333; --so-code-bg: #111111; --so-user-bubble: #202020;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
:root[data-theme="dark"] {
|
|
95
|
-
color-scheme: dark;
|
|
96
|
-
|
|
97
|
-
--so-ground: #0b0b0b; --so-paper: #161616; --so-sidebar: #0b0b0b; --so-raised: #1c1c1c;
|
|
98
|
-
--so-ink: #ededed; --so-muted: #a1a1a1; --so-line: #262626; --so-input-line: #363636;
|
|
99
|
-
--so-accent: #ededed; --so-accent-hover: #ffffff; --so-accent-text: #ededed; --so-on-accent: #0a0a0a; --so-soft: #1f1f1f;
|
|
100
|
-
--so-nav-ink: #a1a1a1; --so-nav-hover: #171717; --so-nav-current: #1f1f1f; --so-nav-current-ink: #ededed;
|
|
101
|
-
--so-signal: #ededed; --so-signal-hover: #ffffff; --so-on-signal: #0a0a0a; --so-signal-soft: #2e2e2e;
|
|
102
|
-
--so-danger: #ff977d; --so-danger-soft: rgb(255 151 125 / .12); --so-success: #3dd68c; --so-success-soft: rgb(61 214 140 / .12);
|
|
103
|
-
--so-warning: #ffca16; --so-warning-soft: rgb(255 202 22 / .12); --so-info: #70b8ff; --so-info-soft: rgb(112 184 255 / .12);
|
|
104
|
-
--so-neutral-ink: #b4b4b4; --so-neutral-soft: #1f1f1f; --so-live: #70b8ff;
|
|
105
|
-
--so-attention: var(--so-signal); --so-on-attention: var(--so-on-signal); --so-attention-soft: var(--so-signal-soft);
|
|
106
|
-
--so-sheet-shadow: 0 0 0 1px #262626, 0 1px 2px rgb(0 0 0 / .4);
|
|
107
|
-
--so-pill-shadow: 0 0 0 1px #2a2a2a, 0 1px 2px rgb(0 0 0 / .5);
|
|
108
|
-
--so-overlay: rgb(0 0 0 / .6); --so-shadow-overlay: 0 0 0 1px #2e2e2e, 0 24px 48px -12px rgb(0 0 0 / .7);
|
|
109
|
-
--so-selection: #3a3a3a; --so-scroll: #333333; --so-code-bg: #111111; --so-user-bubble: #202020;
|
|
53
|
+
--font-sans: var(--so-font-sans);
|
|
54
|
+
--font-mono: var(--so-font-mono);
|
|
110
55
|
}
|
|
111
56
|
* { box-sizing: border-box; }
|
|
112
57
|
/* Touch: no grey flash on a tap, no double-tap zoom wait on a control,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { assignmentSummaryHtml } from "../assignment-ui.js";
|
|
3
3
|
import {} from "../assignment.js";
|
|
4
4
|
import {} from "../browser-workspace.js";
|
|
5
|
+
import { closedQuestionReason } from "../decision.js";
|
|
5
6
|
import { CHAT_ACTIONS, sharedActionNeedsReview, sharedActionPayload, sharedActionReviewPath } from "../chat-actions.js";
|
|
6
7
|
import { CHAT_CONTINUITY_SCRIPT } from "../chat-continuity.js";
|
|
7
8
|
import { CHAT_CONTROLS, chatControlHref, isChatControl } from "../chat-controls.js";
|
|
@@ -478,13 +479,22 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
478
479
|
const text = (key) => (typeof payload[key] === "string" ? payload[key] : "");
|
|
479
480
|
const task = text("task");
|
|
480
481
|
const repoId = text("repoId");
|
|
482
|
+
if (view.kind === "answer" && decision?.supersededReason != null) {
|
|
483
|
+
// The proposal remains in history, but its question no longer needs an answer.
|
|
484
|
+
// Both the server page and browser card use the same closed view, without stale consent controls.
|
|
485
|
+
const body = html `<p><a href="/d/${decision.id}">${decision.question}</a></p><p class="meta">${closedQuestionReason(decision.supersededReason)}</p>`;
|
|
486
|
+
return {
|
|
487
|
+
html: html `<article class="card proposal proposal-answer" data-card-kind="answer"><header class="proposal-head"><strong>Question closed</strong></header><div class="proposal-body">${body}</div></article>`,
|
|
488
|
+
card: { id: view.id, kind: view.kind, label: "Question closed", state: view.state, body: htmlString(body), said: null, links: [], primary: null, dismissable: false, note: null },
|
|
489
|
+
};
|
|
490
|
+
}
|
|
481
491
|
const presentations = {
|
|
482
492
|
task: { label: payload["report"] === true ? "Scout investigation" : "New task", action: "file task", icon: html `<path d="M12 5v14"/><path d="M5 12h14"/>` },
|
|
483
493
|
next: { label: "Queue priority", action: "move to front", icon: html `<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>` },
|
|
484
494
|
reserve: { label: "Worker assignment", action: payload["worker"] === null ? "release" : "reserve", icon: html `<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>` },
|
|
485
495
|
hold: { label: "Pause work", action: "hold", icon: html `<rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/>` },
|
|
486
496
|
unhold: { label: "Resume work", action: "release hold", icon: html `<path d="m7 4 13 8-13 8z"/>` },
|
|
487
|
-
steer: { label: "Guidance for
|
|
497
|
+
steer: { label: "Guidance for the agent", action: "add guidance", icon: html `<path d="M5 12h14"/><path d="m13 6 6 6-6 6"/>` },
|
|
488
498
|
scope: { label: "Scope revision", action: "save scope", icon: html `<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4z"/>` },
|
|
489
499
|
answer: { label: "Decision answer", action: "confirm answer", icon: html `<path d="M9.1 9a3 3 0 1 1 5.8 1c0 2-3 2-3 4"/><path d="M12 18h.01"/><circle cx="12" cy="12" r="9"/>` },
|
|
490
500
|
cancel: { label: "Cancel task", action: "open task", icon: html `<path d="m15 9-6 6"/><path d="m9 9 6 6"/><circle cx="12" cy="12" r="9"/>` },
|
|
@@ -562,7 +572,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
562
572
|
}
|
|
563
573
|
else if (view.kind === "steer") {
|
|
564
574
|
const taskTitle = text("taskTitle") || task;
|
|
565
|
-
what = html `<h3>
|
|
575
|
+
what = html `<h3>Guidance for <a href="${taskHref(task)}">${taskTitle}</a></h3><p class="proposal-summary">${text("note")}</p>${facts(["project", mono(repoId)])}<p class="meta proposal-disclosure">A running agent reads it when it next checks for notes; otherwise the next attempt does. The task’s scope doesn’t change.</p>`;
|
|
566
576
|
}
|
|
567
577
|
else if (view.kind === "agents") {
|
|
568
578
|
// The agents card (v48): exactly what changes, in the same words the
|
|
@@ -570,6 +570,8 @@ export declare function taskBodyParts(data: {
|
|
|
570
570
|
} | null;
|
|
571
571
|
/** Operator steering notes (arc 1), delivery state included. */
|
|
572
572
|
steering?: SteerNote[];
|
|
573
|
+
/** A build's agent is running now and reads new notes when it next checks for them (v120). */
|
|
574
|
+
steeringLive?: boolean;
|
|
573
575
|
/** The publication grant the publisher would act under — from
|
|
574
576
|
* publicationGrantFor(repo) only; null when none, or no project. */
|
|
575
577
|
grant?: PublicationGrant | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
2
|
import { html, htmlString, joinHtml, postForm, replaceMarkup } from "../html.js";
|
|
3
|
+
import { closedQuestionReason, closedQuestionWords } from "../decision.js";
|
|
3
4
|
import {} from "../activity-line.js";
|
|
4
5
|
import {} from "../agentconfig.js";
|
|
5
6
|
import { gateWords } from "../approval-policy.js";
|
|
@@ -35,6 +36,7 @@ import { assignmentStageOf, demoChecksOf, stageOfCode, stageOfDispatch, STATUS_M
|
|
|
35
36
|
import { buildProgressOf, dispatchActionLabel, earlierAttemptsWords, receiptPublicationWords, resultStatusOf, REVIEW_TOKENS, workStatusOf } from "../workspace-ui.js";
|
|
36
37
|
import { createHash } from "node:crypto";
|
|
37
38
|
import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SENSITIVE_INPUT, sentenceCase, shortDigest, transcriptScript, whenTime } from "./chrome.js";
|
|
39
|
+
import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
|
|
38
40
|
import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
39
41
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
40
42
|
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, terminalDiffCard, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
@@ -1369,24 +1371,21 @@ export function taskBodyParts(data) {
|
|
|
1369
1371
|
}
|
|
1370
1372
|
return box("problem", "Dispatch unknown", "Refresh this task before relying on its scheduler state.", "unknown");
|
|
1371
1373
|
})();
|
|
1372
|
-
// Operator steering (arc 1): notes for the agent, each wearing
|
|
1373
|
-
//
|
|
1374
|
+
// Operator steering (arc 1, v120): notes for the agent, each wearing the
|
|
1375
|
+
// same words the CLI and the lead use — read (and when), waiting for the
|
|
1376
|
+
// running agent, bound for the next attempt, or ended unread.
|
|
1374
1377
|
const steering = data.steering ?? [];
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
? `reached build #${one.attachedRun}`
|
|
1381
|
-
: one.attachedRun !== null
|
|
1382
|
-
? `attached to build #${one.attachedRun} — delivery not yet proven`
|
|
1383
|
-
: "waiting for the next attempt";
|
|
1378
|
+
const steeringLive = data.steeringLive === true;
|
|
1379
|
+
const steerState = (one) => {
|
|
1380
|
+
const { words, readAt } = steerStandingParts(one, steeringLive);
|
|
1381
|
+
return readAt === null ? html `${words}` : html `${words} ${whenTime(readAt)}`;
|
|
1382
|
+
};
|
|
1384
1383
|
const steerRows = steering.length === 0 ? "" : joinHtml(steering.map(one => html `<p class="row"><span class="meta">${one.author} · ${whenTime(one.createdAt)} · ${steerState(one)}</span> ${one.note}</p>`), "\n");
|
|
1385
1384
|
const steerForm = data.csrf === "" || task.state === "done" || task.state === "cancelled"
|
|
1386
1385
|
? ""
|
|
1387
1386
|
: joinHtml([
|
|
1388
|
-
postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the
|
|
1389
|
-
html `<p class="meta"
|
|
1387
|
+
postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem"><button type="submit">Steer</button>`, { attrs: { class: "row" } }),
|
|
1388
|
+
html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved scope.</p>`,
|
|
1390
1389
|
]);
|
|
1391
1390
|
const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
|
|
1392
1391
|
const holds = data.holds.length === 0
|
|
@@ -1469,9 +1468,12 @@ export function taskBodyParts(data) {
|
|
|
1469
1468
|
!(data.revision != null && "problem" in data.revision) && scope.profileState !== "unresolved" && consentDoorOf(scope, data.route).open;
|
|
1470
1469
|
const planCard = data.planDocument === null
|
|
1471
1470
|
? data.plan === "requested"
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1471
|
+
// A request the planner cannot be given waits on a person; the status card above already says what to shorten.
|
|
1472
|
+
? data.dispatch?.code === "planner-source"
|
|
1473
|
+
? ""
|
|
1474
|
+
: data.revision != null && !("problem" in data.revision)
|
|
1475
|
+
? html `<div class="card planner-status"><span class="planner-orb" aria-hidden="true"></span><p><strong>Updating the plan</strong><span class="meta">Adding your notes to the plan. You approve any change before it builds.</span></p></div>`
|
|
1476
|
+
: html `<div class="card planner-status"><span class="planner-orb" aria-hidden="true"></span><p><strong>Planning requested</strong><span class="meta">${data.planAuto ? "Automatic approval is enabled for a verified plan that preserves your filed contract. Amendments and unanswered questions still pause." : "The agent is inspecting the repository and drafting the goal, acceptance criteria, and approach. It will ask only if a missing answer changes the work."}</span></p></div>`
|
|
1475
1477
|
: ""
|
|
1476
1478
|
: joinHtml([
|
|
1477
1479
|
approval.approved ? html `<details class="card planner-plan planner-plan-collapsed"><summary class="execution-plan-head">` : html `<section class="card planner-plan"><div class="execution-plan-head">`,
|
|
@@ -1788,7 +1790,7 @@ export function taskBodyParts(data) {
|
|
|
1788
1790
|
})();
|
|
1789
1791
|
const decisions = data.decisions.length === 0
|
|
1790
1792
|
? ""
|
|
1791
|
-
: html `<h2>Decisions</h2>${joinHtml(data.decisions.map(decision => html `<p class="row"><a href="/d/${decision.id}">${decision.question}</a> <span class="meta">${decision.state}${isOverdue(decision, data.now) ? " · overdue" : ""}</span></p>`), "\n")}`;
|
|
1793
|
+
: html `<h2>Decisions</h2>${joinHtml(data.decisions.map(decision => html `<p class="row"><a href="/d/${decision.id}">${decision.question}</a> <span class="meta">${decision.supersededReason !== null ? closedQuestionWords(decision.supersededReason) : decision.state}${isOverdue(decision, data.now) ? " · overdue" : ""}</span></p>`), "\n")}`;
|
|
1792
1794
|
const incidents = data.incidents.length === 0
|
|
1793
1795
|
? ""
|
|
1794
1796
|
: html `<h2>Incidents</h2>${joinHtml(data.incidents.map(one => one.resolvedAt === null
|
|
@@ -2068,7 +2070,11 @@ export function taskBodyParts(data) {
|
|
|
2068
2070
|
continue;
|
|
2069
2071
|
thread.push({ key: `question-${decision.id}`, at: decision.createdAt, kind: "question", who: "agent", author: "", title: "Asked",
|
|
2070
2072
|
text: decision.question, link: { label: "Question", href: `/d/${decision.id}` }, html: "", more: null });
|
|
2071
|
-
if (decision.
|
|
2073
|
+
if (decision.supersededReason !== null) {
|
|
2074
|
+
thread.push({ key: `answer-${decision.id}`, at: decision.answeredAt ?? decision.createdAt, kind: "progress", who: "agent", author: "", title: "Closed without an answer",
|
|
2075
|
+
text: closedQuestionReason(decision.supersededReason), link: null, html: "", more: null });
|
|
2076
|
+
}
|
|
2077
|
+
else if (decision.answeredAt !== null) {
|
|
2072
2078
|
const chosen = decision.options.find(one => one.id === decision.choice)?.label ?? decision.choice;
|
|
2073
2079
|
thread.push({ key: `answer-${decision.id}`, at: decision.answeredAt, kind: "reply", who: "person", author: decision.answeredBy ?? "You", title: "Answered",
|
|
2074
2080
|
text: [chosen, decision.note].filter((one) => one !== null && one !== "").join(" — ") || null, link: null, html: "", more: null });
|
|
@@ -2320,9 +2326,11 @@ export function decisionOptionForms(decision, returnTo) {
|
|
|
2320
2326
|
}
|
|
2321
2327
|
export function decisionPage(chrome, decision, taskId, artifacts, who, now, returnTo = null) {
|
|
2322
2328
|
const options = decisionOptionForms(decision, returnTo);
|
|
2323
|
-
const answered = decision.
|
|
2324
|
-
? html `<
|
|
2325
|
-
: ""
|
|
2329
|
+
const answered = decision.supersededReason !== null
|
|
2330
|
+
? html `<p class="meta">${closedQuestionReason(decision.supersededReason)}</p>`
|
|
2331
|
+
: decision.state === "answered"
|
|
2332
|
+
? html `<div class="answered">Answered: <strong>${decision.choice ?? ""}</strong> by ${decision.answeredBy ?? ""}${decision.note === null ? "" : ` — ${decision.note}`}</div>`
|
|
2333
|
+
: "";
|
|
2326
2334
|
const evidence = artifacts.length === 0
|
|
2327
2335
|
? ""
|
|
2328
2336
|
: joinHtml([
|
|
@@ -2331,7 +2339,7 @@ export function decisionPage(chrome, decision, taskId, artifacts, who, now, retu
|
|
|
2331
2339
|
html `</div>`,
|
|
2332
2340
|
]);
|
|
2333
2341
|
return screen(`decide · ${taskId}`, joinHtml([
|
|
2334
|
-
html `<h1>${taskId} <span class="badge badge-${decision.state}">${decision.state}</span>${isOverdue(decision, now) ? html ` <span class="badge badge-overdue">Overdue</span>` : ""}${decision.deadline === null ? "" : html ` <span class="meta">deadline ${decision.deadline}</span>`}</h1>`,
|
|
2342
|
+
html `<h1>${taskId} <span class="badge badge-${decision.state}">${decision.supersededReason !== null ? "closed" : decision.state}</span>${isOverdue(decision, now) ? html ` <span class="badge badge-overdue">Overdue</span>` : ""}${decision.deadline === null ? "" : html ` <span class="meta">deadline ${decision.deadline}</span>`}</h1>`,
|
|
2335
2343
|
html `<div class="recap">${decision.recap}</div>`,
|
|
2336
2344
|
html `<div class="question">${decision.question}</div>`,
|
|
2337
2345
|
decision.state === "answered" ? answered : options,
|
|
@@ -473,6 +473,7 @@ export function createTaskViews(runtime) {
|
|
|
473
473
|
return { rows, decisions: decisionsFor(runtime.store, rows), now };
|
|
474
474
|
})(),
|
|
475
475
|
steering: ref === null ? [] : runtime.store.listSteerNotes(ref.id),
|
|
476
|
+
steeringLive: ref !== null && runtime.store.steeringLiveBuild(ref.id, now) !== null,
|
|
476
477
|
// "publishes as" reads ONLY publicationGrantFor(repo) — the grant
|
|
477
478
|
// the publisher would act under — never listGrants(), which is
|
|
478
479
|
// dispatch authority (slice 1c).
|
package/dist/server/tasks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { handlersOf } from './handler-registry.js';
|
|
2
|
+
import { closedQuestionWords } from "../decision.js";
|
|
2
3
|
import { DEPRECATED_PAGE } from "../deprecations.js";
|
|
3
4
|
/** tasks handlers, moved without changing their route bodies. */
|
|
4
5
|
import { html, htmlString, joinHtml, postForm, textHtml } from "../html.js";
|
|
@@ -1118,7 +1119,8 @@ export function createTasksHandlers(runtime) {
|
|
|
1118
1119
|
}, now);
|
|
1119
1120
|
if (!answered.ok) {
|
|
1120
1121
|
const status = answered.reason === "bad-option" || answered.reason === "bad-note" ? 400 : 409;
|
|
1121
|
-
const
|
|
1122
|
+
const closed = answered.reason === "closed" ? store.getDecision(id)?.supersededReason ?? null : null;
|
|
1123
|
+
const why = closed !== null ? closedQuestionWords(closed) : answered.reason === "already-answered" ? "already answered — somebody got there first" : answered.reason;
|
|
1122
1124
|
return refuse(response, who, status, why, requestedReturn === null ? `/d/${id}` : decisionBack);
|
|
1123
1125
|
}
|
|
1124
1126
|
return redirect(response, decisionBack);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Socket Mode client, apart from the follower so the Slack SDK loads only
|
|
3
|
+
* once Slack is set up (slack.ts imports this when it finds credentials).
|
|
4
|
+
*/
|
|
5
|
+
import { SocketModeClient } from "@slack/socket-mode";
|
|
6
|
+
export { LogLevel } from "@slack/socket-mode";
|
|
7
|
+
/** The bot and socket tokens must belong to the same app, including after reconnect. */
|
|
8
|
+
export declare class ToolrollSlackSocket extends SocketModeClient {
|
|
9
|
+
expectedApp: string;
|
|
10
|
+
wrongApp: () => void;
|
|
11
|
+
protected onWebSocketMessage(data: string | ArrayBuffer, isBinary: boolean): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Socket Mode client, apart from the follower so the Slack SDK loads only
|
|
3
|
+
* once Slack is set up (slack.ts imports this when it finds credentials).
|
|
4
|
+
*/
|
|
5
|
+
import { SocketModeClient } from "@slack/socket-mode";
|
|
6
|
+
export { LogLevel } from "@slack/socket-mode";
|
|
7
|
+
/** The bot and socket tokens must belong to the same app, including after reconnect. */
|
|
8
|
+
export class ToolrollSlackSocket extends SocketModeClient {
|
|
9
|
+
expectedApp = "";
|
|
10
|
+
wrongApp = () => { };
|
|
11
|
+
async onWebSocketMessage(data, isBinary) {
|
|
12
|
+
let packet;
|
|
13
|
+
try {
|
|
14
|
+
packet = JSON.parse(typeof data === "string" ? data : new TextDecoder().decode(data));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (packet.type === "hello") {
|
|
20
|
+
const info = packet.connection_info;
|
|
21
|
+
if (info?.app_id !== this.expectedApp) {
|
|
22
|
+
this.wrongApp();
|
|
23
|
+
await this.disconnect();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return super.onWebSocketMessage(data, isBinary);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/slack.d.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import { SocketModeClient } from "@slack/socket-mode";
|
|
2
1
|
import { type SlackChatOptions } from "./slack-chat.js";
|
|
3
|
-
/** The bot and socket tokens must belong to the same app, including after reconnect. */
|
|
4
|
-
export declare class ToolrollSlackSocket extends SocketModeClient {
|
|
5
|
-
expectedApp: string;
|
|
6
|
-
wrongApp: () => void;
|
|
7
|
-
protected onWebSocketMessage(data: string | ArrayBuffer, isBinary: boolean): Promise<void>;
|
|
8
|
-
}
|
|
9
2
|
export declare function followSlack(options: Omit<SlackChatOptions, "identity" | "api" | "owner" | "current"> & {
|
|
10
3
|
dir: string;
|
|
11
4
|
signal: AbortSignal;
|
package/dist/slack.js
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
1
|
/** One leased Socket Mode connection, hosted by the ordinary worker. */
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
3
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4
|
-
import { SocketModeClient, LogLevel } from "@slack/socket-mode";
|
|
5
4
|
import { loadSlackCredentials, slackApi } from "./slack-api.js";
|
|
6
5
|
import { ChatState } from "./chat-delivery-state.js";
|
|
7
6
|
import { receiveSlack, processSlackEvent, deliverSlackPart, planSlackNotifications, planSlackRooms, } from "./slack-chat.js";
|
|
8
|
-
/** The bot and socket tokens must belong to the same app, including after reconnect. */
|
|
9
|
-
export class ToolrollSlackSocket extends SocketModeClient {
|
|
10
|
-
expectedApp = "";
|
|
11
|
-
wrongApp = () => { };
|
|
12
|
-
async onWebSocketMessage(data, isBinary) {
|
|
13
|
-
let packet;
|
|
14
|
-
try {
|
|
15
|
-
packet = JSON.parse(typeof data === "string" ? data : new TextDecoder().decode(data));
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (packet.type === "hello") {
|
|
21
|
-
const info = packet.connection_info;
|
|
22
|
-
if (info?.app_id !== this.expectedApp) {
|
|
23
|
-
this.wrongApp();
|
|
24
|
-
await this.disconnect();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return super.onWebSocketMessage(data, isBinary);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
7
|
export async function followSlack(options) {
|
|
32
8
|
const owner = randomBytes(16).toString("hex"), state = new ChatState(options.store, "slack");
|
|
33
9
|
const pause = async (ms) => {
|
|
@@ -56,6 +32,8 @@ export async function followSlack(options) {
|
|
|
56
32
|
state.owns(credentials.installation, owner));
|
|
57
33
|
};
|
|
58
34
|
const problem = (text) => state.setProblem(credentials.installation, owner, text);
|
|
35
|
+
// The SDK loads once Slack is set up, not with every command that could host the follower.
|
|
36
|
+
const { ToolrollSlackSocket, LogLevel } = await import("./slack-socket.js");
|
|
59
37
|
// SDK log output can include envelopes/tokens. Only fixed status phrases are saved.
|
|
60
38
|
const client = new ToolrollSlackSocket({
|
|
61
39
|
appToken: credentials.appToken,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
export declare const STEER_HANDLED = "handled";
|
|
3
|
+
export type SteerMailbox = {
|
|
4
|
+
runId: number;
|
|
5
|
+
taskRef: number;
|
|
6
|
+
/** Worktree-relative, as the brief names it. */
|
|
7
|
+
folder: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
/** The folder's real path, resolved once when it was made inside the worktree. */
|
|
10
|
+
realDir: string;
|
|
11
|
+
/** This attempt's note key: in the brief and in every genuine note, nowhere else. */
|
|
12
|
+
key: string;
|
|
13
|
+
/** The highest number this pulse has written. */
|
|
14
|
+
last: number;
|
|
15
|
+
/** Mailed and not yet moved to handled/: number → note id. */
|
|
16
|
+
waiting: Map<number, number>;
|
|
17
|
+
};
|
|
18
|
+
export declare const steerNoteName: (number: number) => string;
|
|
19
|
+
/** Make the folder and its handled/ inside the worktree; null when it cannot be made. */
|
|
20
|
+
export declare function openSteerMailbox(worktree: string, folder: string, runId: number, taskRef: number, key: string): SteerMailbox | null;
|
|
21
|
+
/** One beat: record what the agent read, then mail what is new while the agent runs. */
|
|
22
|
+
export declare function pulseSteerMailbox(store: Store, box: SteerMailbox, now: Date, agentRunning: boolean): void;
|
|
23
|
+
/** The attempt is over: one last read, then the folder goes. Unread notes stay pending for the next attempt. */
|
|
24
|
+
export declare function closeSteerMailbox(store: Store, box: SteerMailbox, now: Date): void;
|
|
25
|
+
/**
|
|
26
|
+
* Claude's doorbell: a synchronous PostToolUse hook that lists the folder and,
|
|
27
|
+
* while a note is unread, hands the agent one line of context between tool
|
|
28
|
+
* calls. It only says notes are waiting — it never quotes one, and its firing
|
|
29
|
+
* is never an acknowledgement. Rendered into the run's own --settings.
|
|
30
|
+
*/
|
|
31
|
+
export declare function claudeSteerHooks(dir: string, folder: string): {
|
|
32
|
+
PostToolUse: {
|
|
33
|
+
matcher: string;
|
|
34
|
+
hooks: {
|
|
35
|
+
type: "command";
|
|
36
|
+
command: string;
|
|
37
|
+
}[];
|
|
38
|
+
}[];
|
|
39
|
+
};
|