toolroll 0.9.72 → 0.9.74
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 +108 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-fence.d.ts +5 -0
- package/dist/agent-fence.js +42 -7
- package/dist/agent-turns.js +5 -0
- package/dist/agents-live.d.ts +31 -0
- package/dist/agents-live.js +43 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +40 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -30
- package/dist/browser-workspace.d.ts +6 -0
- package/dist/builder.js +1 -1
- package/dist/chat-actions.js +13 -8
- package/dist/chat-continuity.js +10 -0
- package/dist/chat-decide.d.ts +47 -1
- package/dist/chat-decide.js +170 -4
- package/dist/chat-quiet.js +3 -3
- package/dist/check-report.js +2 -1
- package/dist/coding-provider.js +4 -2
- package/dist/commit-extract.d.ts +17 -0
- package/dist/commit-extract.js +82 -4
- package/dist/contracts/console-api.d.ts +3 -3
- package/dist/contracts/console-api.js +4 -4
- package/dist/contracts/release-terms.d.ts +7 -1
- package/dist/contracts/release-terms.js +6 -2
- package/dist/deploy-gate.js +5 -2
- package/dist/desktop-update.d.ts +1 -1
- package/dist/dispose.js +35 -2
- package/dist/exec.d.ts +1 -1
- package/dist/exec.js +20 -19
- package/dist/first-run.d.ts +17 -1
- package/dist/first-run.js +38 -0
- package/dist/flow-gallery-ui.js +4 -2
- package/dist/flow-gallery.d.ts +3 -0
- package/dist/flow-gallery.js +6 -2
- package/dist/flow-triggers.d.ts +8 -0
- package/dist/flow-triggers.js +15 -0
- package/dist/flows-cli.js +13 -3
- package/dist/guides.js +10 -1
- package/dist/invoke.d.ts +13 -0
- package/dist/invoke.js +21 -12
- package/dist/kits-ui.js +4 -2
- package/dist/kits.d.ts +6 -4
- package/dist/kits.js +9 -11
- package/dist/knowledge-ui.d.ts +4 -1
- package/dist/knowledge-ui.js +10 -5
- package/dist/lead-progress.d.ts +4 -1
- package/dist/lead-progress.js +3 -1
- package/dist/lead.js +4 -0
- package/dist/live-bus.d.ts +2 -0
- package/dist/live-bus.js +1 -0
- package/dist/live-children-read.d.ts +52 -0
- package/dist/live-children-read.js +333 -0
- package/dist/live-children.d.ts +73 -0
- package/dist/live-children.js +318 -0
- package/dist/live-detail.d.ts +4 -3
- package/dist/live-detail.js +9 -5
- package/dist/live-input.js +7 -8
- package/dist/live-main-read.d.ts +19 -0
- package/dist/live-main-read.js +91 -0
- package/dist/live-open.d.ts +28 -0
- package/dist/live-open.js +125 -0
- package/dist/live-records.d.ts +10 -1
- package/dist/live-records.js +16 -5
- package/dist/live.d.ts +54 -22
- package/dist/live.js +218 -69
- package/dist/mcp-connect.d.ts +10 -0
- package/dist/mcp-connect.js +2 -0
- package/dist/operate.js +3 -2
- package/dist/peek-cli.d.ts +10 -3
- package/dist/peek-cli.js +17 -8
- package/dist/planner.js +1 -1
- package/dist/project-tools.js +3 -1
- package/dist/protected-storage.d.ts +67 -0
- package/dist/protected-storage.js +398 -0
- package/dist/provider-limits.d.ts +3 -1
- package/dist/provider-limits.js +5 -2
- package/dist/provider.js +6 -12
- package/dist/release-adapters.d.ts +8 -1
- package/dist/release-adapters.js +84 -14
- package/dist/release-authority.d.ts +6 -0
- package/dist/release-authority.js +9 -0
- package/dist/release-card.d.ts +76 -0
- package/dist/release-card.js +252 -0
- package/dist/release-carry.d.ts +3 -0
- package/dist/release-carry.js +23 -4
- package/dist/release-cli.js +36 -3
- package/dist/release-git.d.ts +43 -0
- package/dist/release-git.js +106 -0
- package/dist/release.d.ts +11 -2
- package/dist/release.js +82 -6
- package/dist/repository-context-ui.js +2 -1
- package/dist/run-efficiency.d.ts +3 -3
- package/dist/run-efficiency.js +20 -8
- package/dist/scope.d.ts +7 -0
- package/dist/scope.js +22 -5
- package/dist/scout.d.ts +3 -0
- package/dist/scout.js +5 -3
- package/dist/serve.js +34 -23
- package/dist/server/agents-live-lead.d.ts +47 -0
- package/dist/server/agents-live-lead.js +116 -0
- package/dist/server/agents-live-room.d.ts +23 -0
- package/dist/server/agents-live-room.js +88 -0
- package/dist/server/chat.js +84 -30
- package/dist/server/chrome.js +2 -2
- package/dist/server/console.css +68 -40
- package/dist/server/flows.js +40 -10
- package/dist/server/live.d.ts +4 -0
- package/dist/server/live.js +42 -2
- package/dist/server/pages.js +81 -1
- package/dist/server/render-chat.d.ts +15 -1
- package/dist/server/render-chat.js +14 -7
- package/dist/server/render-pages.d.ts +8 -4
- package/dist/server/render-pages.js +55 -50
- package/dist/server/render-settings.js +1 -1
- package/dist/server/render-tasks.js +10 -3
- package/dist/server/route-table.js +5 -1
- package/dist/server/runtime.d.ts +1 -1
- package/dist/server/settings.js +29 -8
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +59 -41
- package/dist/setup-guide.d.ts +2 -2
- package/dist/store.d.ts +34 -1
- package/dist/store.js +81 -2
- package/dist/stream-scope.d.ts +18 -0
- package/dist/stream-scope.js +28 -0
- package/dist/subagent-memory.d.ts +1 -0
- package/dist/subagent-memory.js +1 -1
- package/dist/subagent-tools.d.ts +22 -6
- package/dist/subagent-tools.js +44 -8
- package/dist/subagent-work.d.ts +2 -0
- package/dist/subagent-work.js +5 -2
- package/dist/subagents-ui.js +6 -4
- package/dist/subscription-chat.js +5 -3
- package/dist/telegram.d.ts +6 -1
- package/dist/telegram.js +58 -6
- package/package.json +1 -1
package/dist/server/chat.js
CHANGED
|
@@ -39,7 +39,7 @@ import { html, htmlString, postForm, reauth } from "../html.js";
|
|
|
39
39
|
import { requestContext } from "./request-context.js";
|
|
40
40
|
import { refuse, screen, whenTime } from "./chrome.js";
|
|
41
41
|
import { chatResultHref, chatReturnWithLatest, chatReturnWithSaid, projectChatHref, redirect, respond, safeChatReturn, safeReturn, taskChatHref, taskHref } from "./http.js";
|
|
42
|
-
import { chatAckPage, chatPage, coordinatorProposalsSection, decisionsFor, leadAfterComposerHtml, leadChatVersion, leadMintCard, leadPage, leadThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
|
|
42
|
+
import { chatAckPage, chatPage, NOT_STARTED_SESSION, coordinatorProposalsSection, decisionsFor, leadAfterComposerHtml, leadChatVersion, leadMintCard, leadPage, leadThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
|
|
43
43
|
import {} from "./render-pages.js";
|
|
44
44
|
import { completionForm, hardBlockerOf, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
|
|
45
45
|
import { browserCaller, oneLineUa } from "./session.js";
|
|
@@ -54,25 +54,31 @@ export function createChatHandlers(runtime) {
|
|
|
54
54
|
async function mateStatus(ctx) {
|
|
55
55
|
const { url, response, now } = ctx;
|
|
56
56
|
const who = browserCaller(ctx.who);
|
|
57
|
-
const
|
|
57
|
+
const found = store.activeLeadSession(who.name);
|
|
58
58
|
const principal = leadPrincipal(who);
|
|
59
|
-
|
|
59
|
+
// Quiet page visits: a membership conversation not started yet is still a page to keep current (its task's
|
|
60
|
+
// progress and approval); it reads as session 0, and nothing here starts it.
|
|
61
|
+
const enabled = chatEnablement();
|
|
62
|
+
const notStarted = principal !== null && enabled.ok && enabled.billing === "subscription" && (found === null || found.ceilingDigest !== principal.ceilingDigest);
|
|
63
|
+
const session = notStarted ? null : found;
|
|
64
|
+
if (principal === null || (!notStarted && (session === null || session.ceilingDigest !== principal.ceilingDigest || session.approverGeneration !== principal.generation))) {
|
|
60
65
|
return respond(response, 200, "application/json", JSON.stringify({ session: null }));
|
|
61
66
|
}
|
|
67
|
+
const sessionId = session?.id ?? NOT_STARTED_SESSION;
|
|
62
68
|
store.sweepStaleLeadTurns(now);
|
|
63
69
|
const request = url.searchParams.get("request") ?? "";
|
|
64
70
|
// Received: the engine took it, or the lead queue holds it (a queued acknowledgement lost on the way still frees the draft).
|
|
65
|
-
const received = /^[a-f0-9]{32}$/.test(request) && store.leadRequestReceived(session.id, request);
|
|
71
|
+
const received = session !== null && /^[a-f0-9]{32}$/.test(request) && store.leadRequestReceived(session.id, request);
|
|
66
72
|
const requestedTask = url.searchParams.get("task");
|
|
67
73
|
const focusTask = requestedTask === null || requestedTask === "" ? null : taskChatFocus(requestedTask, now, who, { mintNonce: false });
|
|
68
74
|
if (requestedTask !== null && requestedTask !== "" && focusTask === null) {
|
|
69
75
|
// The lens's task is gone or no longer admitted: the page keeps its
|
|
70
76
|
// draft and says so; it never falls back to the unified thread.
|
|
71
|
-
return respond(response, 200, "application/json", JSON.stringify({ session:
|
|
77
|
+
return respond(response, 200, "application/json", JSON.stringify({ session: sessionId, task: requestedTask, unavailable: true, received }));
|
|
72
78
|
}
|
|
73
79
|
const chatProject = focusTask !== null ? null : chatProjectOf(url.searchParams.getAll("project").filter(one => one !== ""));
|
|
74
80
|
if (chatProject === undefined)
|
|
75
|
-
return respond(response, 200, "application/json", JSON.stringify({ session:
|
|
81
|
+
return respond(response, 200, "application/json", JSON.stringify({ session: sessionId, unavailable: true, received }));
|
|
76
82
|
const rows = leadConversationRows(who, principal, focusTask, now, chatProject);
|
|
77
83
|
const version = leadChatVersion({ ...rows, focusTask });
|
|
78
84
|
const known = url.searchParams.get("version") ?? "";
|
|
@@ -80,12 +86,12 @@ export function createChatHandlers(runtime) {
|
|
|
80
86
|
const fragments = known === version
|
|
81
87
|
? null
|
|
82
88
|
: {
|
|
83
|
-
thread: htmlString(leadThreadHtml({ ...rows, focusTask, csrf, now, problem: takeLeadNote(csrf, session
|
|
89
|
+
thread: htmlString(leadThreadHtml({ ...rows, focusTask, csrf, now, problem: takeLeadNote(csrf, session?.id ?? null), chatProject })),
|
|
84
90
|
after: htmlString(leadAfterComposerHtml({ messages: rows.messages, pending: rows.pending, focusTask })),
|
|
85
91
|
live: focusTask === null ? null : htmlString(taskChatLiveRegion(focusTask, csrf, true, rows.pending !== null)),
|
|
86
92
|
};
|
|
87
93
|
return respond(response, 200, "application/json", JSON.stringify({
|
|
88
|
-
session:
|
|
94
|
+
session: sessionId,
|
|
89
95
|
task: focusTask?.id ?? "",
|
|
90
96
|
version,
|
|
91
97
|
pending: rows.pending !== null,
|
|
@@ -237,24 +243,22 @@ export function createChatHandlers(runtime) {
|
|
|
237
243
|
// The mate (mate arc §5): while a mate session is live, /chat IS the
|
|
238
244
|
// thread — the same rows the CLI reads. Without one, fleet chat as
|
|
239
245
|
// before, plus the card that mints a session.
|
|
240
|
-
|
|
246
|
+
const leadSession = enabled.ok && who.role === "approver" ? store.activeLeadSession(who.name) : null;
|
|
241
247
|
const principal = enabled.ok && who.role === "approver" ? leadPrincipal(who) : null;
|
|
242
248
|
// Chat opens ready to talk (2026-09-23): an approver in good standing
|
|
243
|
-
// needs no second password to start their own conversation.
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// the
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
+
// needs no second password to start their own conversation. Quiet page
|
|
250
|
+
// visits (2026-10-27): a visit only shows it. With no membership
|
|
251
|
+
// conversation yet (or only one under another ceiling), the page shows
|
|
252
|
+
// the composer and the first message starts one (chatSend); nothing
|
|
253
|
+
// here writes. Direct-API chat keeps a one-tap Start with its visible
|
|
254
|
+
// spending limit (no password).
|
|
249
255
|
const settingsView = url.searchParams.get("settings") === "1";
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
leadSession = store.activeLeadSession(who.name);
|
|
253
|
-
}
|
|
256
|
+
const notStarted = !settingsView && enabled.ok && enabled.billing === "subscription" && principal !== null &&
|
|
257
|
+
(leadSession === null || leadSession.ceilingDigest !== principal.ceilingDigest);
|
|
254
258
|
// A session under another ceiling is not continuable from here; a GET
|
|
255
259
|
// writes nothing (slice-2 review, finding 7) — the mint card below
|
|
256
260
|
// starts a new conversation, and minting ends the old session.
|
|
257
|
-
const ceilingStale = enabled.ok && leadSession !== null && principal !== null && leadSession.ceilingDigest !== principal.ceilingDigest;
|
|
261
|
+
const ceilingStale = !notStarted && enabled.ok && leadSession !== null && principal !== null && leadSession.ceilingDigest !== principal.ceilingDigest;
|
|
258
262
|
const catchUp = leadBriefHtml(assignmentCatchUp(store, now, { principal: "operator", repos }, { limit: 6 }, evidenceRoot));
|
|
259
263
|
const chatProjects = repos.map((repo, index) => {
|
|
260
264
|
let peek = null;
|
|
@@ -288,11 +292,12 @@ export function createChatHandlers(runtime) {
|
|
|
288
292
|
const withFirstRun = (shown) => firstRun === undefined && phone === undefined && home === null ? shown
|
|
289
293
|
: { ...shown, ...(home === null ? {} : { refreshSeconds: liveRefreshSeconds() }),
|
|
290
294
|
workspace: { ...shown.workspace, ...(firstRun === undefined ? {} : { firstRun }), ...(phone === undefined ? {} : { phone }), ...(home === null ? {} : { home }) } };
|
|
291
|
-
if (enabled.ok &&
|
|
295
|
+
if (enabled.ok && principal !== null && (notStarted || (leadSession !== null && !ceilingStale))) {
|
|
292
296
|
{
|
|
293
|
-
const said = takeLeadNote(who.session.csrf, leadSession
|
|
297
|
+
const said = takeLeadNote(who.session.csrf, notStarted ? null : leadSession?.id ?? null);
|
|
294
298
|
return sendScreen(response, 200, withFirstRun(leadPage(chromeFor(null, "chat", undefined, "all"), {
|
|
295
|
-
session: leadSession,
|
|
299
|
+
session: notStarted ? null : leadSession,
|
|
300
|
+
approver: who.name,
|
|
296
301
|
...leadConversationRows(who, principal, focusTask, now, chatProject),
|
|
297
302
|
chatProject,
|
|
298
303
|
latched,
|
|
@@ -663,7 +668,9 @@ export function createChatHandlers(runtime) {
|
|
|
663
668
|
const body = readForm(posted, CONSOLE_FORMS.leadFollow);
|
|
664
669
|
if (who.via !== "cookie")
|
|
665
670
|
return refuse(response, who, 403, "Open the conversation to change automatic updates.");
|
|
666
|
-
const principal = leadPrincipal(who), session = store.activeLeadSession(who.name)
|
|
671
|
+
const principal = leadPrincipal(who), session = store.activeLeadSession(who.name);
|
|
672
|
+
// A conversation its first message started from a task or project page has no main thread yet: this act opens it.
|
|
673
|
+
const thread = principal && session && session.ceilingDigest === principal.ceilingDigest ? store.openLeadThread(who.name, principal.ceilingDigest, now).thread : null;
|
|
667
674
|
if (!principal || !session || !thread || !configureLeadFollow(store, principal, session, thread, body.get("enabled") === "yes", now))
|
|
668
675
|
return refuse(response, who, 409, "Conversation access changed. Start chat again.", "/chat");
|
|
669
676
|
return redirect(response, safeChatReturn(body.get("return")));
|
|
@@ -875,10 +882,52 @@ export function createChatHandlers(runtime) {
|
|
|
875
882
|
// A live mate session: the message is a mate turn — no password, the
|
|
876
883
|
// session's ceremony already covered it (§1); the engine refuses on
|
|
877
884
|
// its own terms and the thread shows why.
|
|
878
|
-
|
|
885
|
+
let leadSession = who.role === "approver" ? store.activeLeadSession(who.name) : null;
|
|
886
|
+
// Quiet page visits: a membership conversation starts with its first message, never with a page view. A page
|
|
887
|
+
// shown before one existed binds its sends to session 0; that send starts it (the same approver standing a visit
|
|
888
|
+
// used to need), and a retry or double submit of the same request lands in the conversation it started.
|
|
889
|
+
let startedHere = false;
|
|
890
|
+
const binding = body.getAll("request-session");
|
|
891
|
+
const unbound = binding.length === 0 || (binding.length === 1 && binding[0] === String(NOT_STARTED_SESSION));
|
|
892
|
+
if (who.role === "approver" && enabled.billing === "subscription" && unbound) {
|
|
893
|
+
const principal = leadPrincipal(who);
|
|
894
|
+
if (principal === null)
|
|
895
|
+
return wantsJson ? said(403, "your approver standing changed — sign in again") : refuse(response, who, 403, "your approver standing changed — sign in again", back);
|
|
896
|
+
const sentRequest = body.get("request");
|
|
897
|
+
if (sentRequest !== null && (body.getAll("request").length !== 1 || !/^[a-f0-9]{32}$/.test(sentRequest)))
|
|
898
|
+
return said(409, "This conversation changed. Reload it before sending your message.");
|
|
899
|
+
const usable = leadSession !== null && leadSession.ceilingDigest === principal.ceilingDigest && leadSession.approverGeneration === principal.generation;
|
|
900
|
+
// The conversation this very request started, if it started one: a retry or double submit lands only there.
|
|
901
|
+
const startedBy = sentRequest === null ? null : store.leadStartReceipt(who.name, sentRequest);
|
|
902
|
+
if (startedBy !== null) {
|
|
903
|
+
if (!usable || leadSession === null || leadSession.id !== startedBy.session)
|
|
904
|
+
return said(409, "This conversation ended or changed. Reload it before sending your message.", leadSession?.id ?? null);
|
|
905
|
+
startedHere = true;
|
|
906
|
+
}
|
|
907
|
+
else if (usable && leadSession !== null) {
|
|
908
|
+
// A page that showed no conversation never sends into one it did not start.
|
|
909
|
+
if (binding.length === 1)
|
|
910
|
+
return said(409, "This conversation changed. Reload it before sending your message.", leadSession.id);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
// Nothing starts for a message that would be refused anyway.
|
|
914
|
+
const message = (body.get("message") ?? "").trim();
|
|
915
|
+
if (message === "" || message.length > LEAD_MESSAGE_MAX_CHARS)
|
|
916
|
+
return said(400, `a message is 1 to ${LEAD_MESSAGE_MAX_CHARS} characters`);
|
|
917
|
+
if (scanForSecrets(message).length > 0)
|
|
918
|
+
return said(400, "that looks like a credential — chat never forwards or stores those");
|
|
919
|
+
const scope = chatScopeOf(focusTask, chatProject);
|
|
920
|
+
// The start and its record are one write: a retry finds it, and never starts a second conversation.
|
|
921
|
+
const started = startLeadConversation(who, principal, enabled, 0, false, now, scope, sentRequest ?? undefined);
|
|
922
|
+
leadSession = store.activeLeadSession(who.name);
|
|
923
|
+
if (leadSession === null || leadSession.id !== started)
|
|
924
|
+
return said(409, "This conversation changed. Reload it before sending your message.");
|
|
925
|
+
startedHere = true;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
879
928
|
if (leadSession !== null) {
|
|
880
929
|
const sentRequest = body.get("request");
|
|
881
|
-
if (sentRequest !== null && (body.getAll("request").length !== 1 || body.getAll("request-session").length !== 1 || body.get("request-session") !== String(leadSession.id))) {
|
|
930
|
+
if (sentRequest !== null && !startedHere && (body.getAll("request").length !== 1 || body.getAll("request-session").length !== 1 || body.get("request-session") !== String(leadSession.id))) {
|
|
882
931
|
return said(409, "This conversation changed. Reload it before sending your message.");
|
|
883
932
|
}
|
|
884
933
|
// Every send has one identity (a form without one gets it here): a message that waits is known by it.
|
|
@@ -1136,16 +1185,18 @@ export function createChatHandlers(runtime) {
|
|
|
1136
1185
|
const previous = liveTurns.get(thread);
|
|
1137
1186
|
if (previous?.expiry)
|
|
1138
1187
|
clearTimeout(previous.expiry);
|
|
1139
|
-
const live = { steps: [], done: false, ok: false, listeners: previous?.listeners ?? new Set() };
|
|
1188
|
+
const live = { steps: [], done: false, ok: false, listeners: previous?.listeners ?? new Set(), startedAt: clock().getTime(), stepAt: [] };
|
|
1140
1189
|
liveTurns.set(thread, live);
|
|
1141
1190
|
// A page that has this conversation open follows the reply (GET /live, its chat room).
|
|
1142
1191
|
runtime.liveTurnStarted(thread);
|
|
1143
|
-
const
|
|
1192
|
+
const newStep = () => { live.steps.push({ tools: [], toolCalls: [], text: "" }); live.stepAt.push(clock().getTime()); };
|
|
1193
|
+
const current = () => live.steps.at(-1) ?? (newStep(), live.steps.at(-1));
|
|
1144
1194
|
const progress = (event) => {
|
|
1145
1195
|
if (live.done)
|
|
1146
1196
|
return;
|
|
1197
|
+
live.turn ??= event.turn;
|
|
1147
1198
|
if (event.kind === "step")
|
|
1148
|
-
|
|
1199
|
+
newStep();
|
|
1149
1200
|
else if (event.kind === "tool") {
|
|
1150
1201
|
current().tools.push(event.label);
|
|
1151
1202
|
current().toolCalls.push({ id: event.id, label: event.label, state: "running" });
|
|
@@ -1159,8 +1210,10 @@ export function createChatHandlers(runtime) {
|
|
|
1159
1210
|
}
|
|
1160
1211
|
}
|
|
1161
1212
|
}
|
|
1162
|
-
else if (event.kind === "text")
|
|
1213
|
+
else if (event.kind === "text") {
|
|
1163
1214
|
current().text = event.text;
|
|
1215
|
+
(live.confirmed ??= [])[live.steps.length - 1] = event.final === true;
|
|
1216
|
+
}
|
|
1164
1217
|
for (const listener of live.listeners)
|
|
1165
1218
|
listener();
|
|
1166
1219
|
};
|
|
@@ -1175,6 +1228,7 @@ export function createChatHandlers(runtime) {
|
|
|
1175
1228
|
return;
|
|
1176
1229
|
live.done = true;
|
|
1177
1230
|
live.ok = ok;
|
|
1231
|
+
live.endedAt = clock().getTime();
|
|
1178
1232
|
for (const listener of live.listeners)
|
|
1179
1233
|
listener();
|
|
1180
1234
|
live.expiry = setTimeout(() => { if (liveTurns.get(thread) === live)
|
package/dist/server/chrome.js
CHANGED
|
@@ -396,7 +396,7 @@ export function chromeScript() {
|
|
|
396
396
|
`if(ev.key==="?"){toggleHelp();ev.preventDefault();return;}` +
|
|
397
397
|
`if(ev.key==="j"||ev.key==="k"){rove(ev.key==="j"?1:-1,ev);return;}` +
|
|
398
398
|
`if(pending==="g"){pending=null;` +
|
|
399
|
-
`var map={b:"/board",i:"/",w:"/workbench",d:"/done",q:"/board?view=order",f:"/fleet",t:"/
|
|
399
|
+
`var map={b:"/board",i:"/",w:"/workbench",d:"/done",q:"/board?view=order",f:"/fleet",t:"/work",a:"/activity",p:"/projects"};` +
|
|
400
400
|
`if(map[ev.key]){go(map[ev.key]);ev.preventDefault();}return;}` +
|
|
401
401
|
`if(ev.key==="g"){pending="g";setTimeout(function(){pending=null;},800);}});` +
|
|
402
402
|
`})();`);
|
|
@@ -717,7 +717,7 @@ export function workToolRows(scoped = false, _offersChat = false, offersCode = f
|
|
|
717
717
|
...(offersCode ? [{ key: "code", href: "/term", label: "Live", hint: "your own terminals: Claude Code, Codex or a shell" }] : []),
|
|
718
718
|
{ key: "inbox", href: "/inbox", label: "Inbox", hint: "questions, approvals, and repairs to act on" },
|
|
719
719
|
{ key: "board", href: "/board", label: "Board", hint: "lanes by state, with the order view" },
|
|
720
|
-
{ key: "tasks", href: "/
|
|
720
|
+
{ key: "tasks", href: "/work", label: "Task list", hint: "everything, filterable by state" },
|
|
721
721
|
{ key: "recipes", href: "/recipes", label: "Recipes", hint: "choose, customize, and reuse a workflow" },
|
|
722
722
|
{ key: "workbench", href: "/workbench", label: "Portfolio", hint: "every project and live build in one place" },
|
|
723
723
|
{ key: "ledger", href: "/ledger", label: "Action ledger", hint: "who acted, what happened, and the result" },
|
package/dist/server/console.css
CHANGED
|
@@ -172,6 +172,30 @@
|
|
|
172
172
|
color: color-mix(in srgb, var(--running) 72%, var(--foreground)); border-color: var(--border);
|
|
173
173
|
}
|
|
174
174
|
.badge-cut { background: var(--muted); }
|
|
175
|
+
/* The status chip (components/ui/badge.tsx `size="chip"`, after ReUI's light badge with a dot): 20px, 6px
|
|
176
|
+
corners, a soft wash, 11.5px words and a 6px dot. Needs you is the amber wash with its words leaned toward ink
|
|
177
|
+
(4.5:1); the dot is decoration and the word carries the meaning. */
|
|
178
|
+
.chip {
|
|
179
|
+
display: inline-flex; align-items: center; gap: .375rem; height: 1.25rem; padding: 0 .375rem; border-radius: var(--so-radius-sm);
|
|
180
|
+
font-family: var(--font-sans); font-size: .71875rem; font-weight: 500; line-height: 1; white-space: nowrap; vertical-align: middle;
|
|
181
|
+
background: var(--so-neutral-soft); color: var(--so-ink);
|
|
182
|
+
}
|
|
183
|
+
/* Ink words, the tone in the dot (the Agents → Live chip); Needs you keeps amber words leaned toward ink. */
|
|
184
|
+
.chip-dot { width: .375rem; height: .375rem; flex-shrink: 0; border-radius: var(--so-radius-full); background: var(--so-neutral-ink); }
|
|
185
|
+
.chip-needs { background: var(--so-warning-soft); color: color-mix(in oklab, var(--so-warning) 85%, var(--so-ink)); }
|
|
186
|
+
.chip-needs .chip-dot { background: var(--so-warning); }
|
|
187
|
+
.chip-live { background: var(--so-info-soft); }
|
|
188
|
+
.chip-live .chip-dot { background: var(--so-info); }
|
|
189
|
+
.chip-done { background: var(--so-success-soft); }
|
|
190
|
+
.chip-done .chip-dot { background: var(--so-success); }
|
|
191
|
+
.chip-failed { background: var(--so-danger-soft); }
|
|
192
|
+
.chip-failed .chip-dot { background: var(--so-danger); }
|
|
193
|
+
/* The frame (components/ui/frame.tsx, after ReUI's Frame): a raised well with a hairline, its heading on the
|
|
194
|
+
well's fill, and paper panels whose corners sit concentric inside it. No shadow on a panel. */
|
|
195
|
+
.frame { display: flex; flex-direction: column; gap: .25rem; padding: .25rem; border: 1px solid var(--so-line); border-radius: var(--so-radius-card); background: var(--so-raised); }
|
|
196
|
+
.frame-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; min-height: 2rem; padding: .25rem .625rem; }
|
|
197
|
+
.frame-panel { position: relative; border: 1px solid var(--so-line); border-radius: calc(var(--so-radius-card) - .25rem - 1px); background: var(--so-paper); padding: .75rem .875rem; }
|
|
198
|
+
.frame-panel.waits { border-color: var(--so-warning); }
|
|
175
199
|
|
|
176
200
|
.card {
|
|
177
201
|
border: 1px solid var(--glass-border); border-radius: var(--radius); background: var(--glass);
|
|
@@ -1749,13 +1773,13 @@
|
|
|
1749
1773
|
says which way it folds. */
|
|
1750
1774
|
@media (max-width: 760px) {
|
|
1751
1775
|
.board { display: flex; flex-direction: column; align-items: stretch; gap: .5rem; padding-bottom: 0; }
|
|
1752
|
-
.board .lane { min-height: 0; padding: 0 .
|
|
1776
|
+
.board .lane { min-height: 0; padding: 0 .25rem .25rem; }
|
|
1753
1777
|
.board .lane > summary {
|
|
1754
1778
|
cursor: pointer; display: flex; align-items: center; min-height: 2.75rem; margin: 0;
|
|
1755
1779
|
-webkit-tap-highlight-color: transparent;
|
|
1756
1780
|
}
|
|
1757
|
-
.board .lane > summary
|
|
1758
|
-
.board .lane > summary
|
|
1781
|
+
.board .lane > summary { padding: 0 .375rem; }
|
|
1782
|
+
.board .lane > summary h2 { flex: 1; }
|
|
1759
1783
|
.board .lane > summary::after {
|
|
1760
1784
|
content: ""; width: .5rem; height: .5rem; flex: none; margin-right: .25rem;
|
|
1761
1785
|
border-right: 1.5px solid var(--muted-foreground); border-bottom: 1.5px solid var(--muted-foreground);
|
|
@@ -1763,63 +1787,66 @@
|
|
|
1763
1787
|
}
|
|
1764
1788
|
.board .lane[open] > summary::after { transform: rotate(225deg) translateY(-.125rem); }
|
|
1765
1789
|
.board .lane .hint { margin-bottom: .25rem; }
|
|
1766
|
-
.board .lane .lane-card:last-of-type { margin-bottom:
|
|
1790
|
+
.board .lane .lane-card:last-of-type { margin-bottom: 0; }
|
|
1767
1791
|
.board .lane-attention { order: 0; }
|
|
1768
1792
|
.board .lane-building { order: 1; }
|
|
1769
1793
|
.board .lane-queued { order: 2; }
|
|
1770
1794
|
.board .lane-waiting { order: 3; }
|
|
1771
1795
|
.board .lane-done { order: 4; }
|
|
1772
1796
|
}
|
|
1797
|
+
/* A lane is a frame (components/ui/frame.tsx, after ReUI's Frame): a raised well with a hairline, its head a
|
|
1798
|
+
status chip and a mono count on the well's fill, its cards paper panels at concentric corners. Only the lane
|
|
1799
|
+
that waits on a person is amber: its chip, and each card's thin full outline. No fill, no stripe, no shadow. */
|
|
1773
1800
|
.lane {
|
|
1774
|
-
background:
|
|
1775
|
-
border-radius: var(--radius); padding: .
|
|
1801
|
+
background: var(--so-raised); border: 1px solid var(--so-line);
|
|
1802
|
+
border-radius: var(--so-radius-card); padding: .25rem; min-height: 12rem;
|
|
1776
1803
|
}
|
|
1777
|
-
.lane > summary { list-style: none; cursor: default; margin: 0
|
|
1804
|
+
.lane > summary { list-style: none; cursor: default; margin: 0; padding: .375rem .375rem .125rem; }
|
|
1778
1805
|
.lane > summary::-webkit-details-marker { display: none; }
|
|
1779
|
-
.lane h2 { display: flex; align-items: center; gap: .
|
|
1806
|
+
.lane h2 { display: flex; align-items: center; gap: .5rem; margin: 0; font-size: .8125rem; font-weight: 600; }
|
|
1780
1807
|
.lane h2 a { color: inherit; text-decoration: none; }
|
|
1781
1808
|
@media (hover: hover) and (pointer: fine) { .lane h2 a:hover { text-decoration: underline; } }
|
|
1782
|
-
.lane .hint { margin
|
|
1783
|
-
.lane-count { color: var(--muted-foreground); font-weight:
|
|
1784
|
-
.lane h2::before {
|
|
1785
|
-
content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full); flex: none;
|
|
1786
|
-
background: var(--muted-foreground); opacity: .55;
|
|
1787
|
-
}
|
|
1788
|
-
.lane-attention h2::before { background: var(--brand); opacity: 1; }
|
|
1789
|
-
.lane-building h2::before { background: var(--running); opacity: 1; }
|
|
1790
|
-
.lane-done h2::before { background: var(--muted-foreground); opacity: 1; }
|
|
1809
|
+
.lane .hint { margin: 0; padding: .125rem .375rem .375rem; }
|
|
1810
|
+
.lane-count { color: var(--muted-foreground); font-family: var(--font-mono); font-size: .6875rem; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
1791
1811
|
.lane-card {
|
|
1792
|
-
display:
|
|
1793
|
-
background: var(--
|
|
1794
|
-
border-radius: calc(var(--radius) -
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
.lane-
|
|
1812
|
+
display: flex; flex-direction: column; text-decoration: none; color: inherit;
|
|
1813
|
+
background: var(--so-paper); border: 1px solid var(--so-line);
|
|
1814
|
+
border-radius: calc(var(--so-radius-card) - .25rem - 1px); padding: .625rem .75rem; margin-top: .25rem;
|
|
1815
|
+
}
|
|
1816
|
+
.lane-attention .lane-card { border-color: var(--so-warning); }
|
|
1817
|
+
/* The order view's columns are frames too, with roomier insides (a form, a note) and a quiet dot by the name. */
|
|
1818
|
+
.lane.queue-column { padding: .5rem; }
|
|
1819
|
+
.queue-column > h2::before { content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full); flex: none; background: var(--muted-foreground); opacity: .55; }
|
|
1820
|
+
@media (hover: hover) and (pointer: fine) { .lane-card:hover { background: var(--so-raised); } }
|
|
1821
|
+
/* The title leads; the task id is a machine fact under it, in mono. */
|
|
1822
|
+
.lane-card .t { display: block; font-size: .84375rem; font-weight: 500; line-height: 1.35; }
|
|
1823
|
+
.lane-card .why { display: block; margin-top: .125rem; font-size: .75rem; color: var(--muted-foreground); }
|
|
1824
|
+
.lane-card .id { display: block; margin-top: .375rem; font-family: var(--font-mono); font-size: .75rem; color: var(--muted-foreground); overflow-wrap: anywhere; }
|
|
1825
|
+
.lane-card .who { display: block; margin-top: .125rem; font-size: .75rem; color: var(--muted-foreground); overflow-wrap: anywhere; }
|
|
1826
|
+
.lane-card .who .mono { display: inline; margin: 0; font-family: var(--font-mono); font-size: .6875rem; }
|
|
1799
1827
|
.lane-card .dot { margin-right: .4rem; }
|
|
1800
1828
|
.lane-card .meta, .lane-card .mono { display: block; margin-top: .125rem; font-size: .75rem; }
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
.lane-card .facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .0625rem .625rem; margin-top: .4rem; font-size: .6875rem; line-height: 1.5; }
|
|
1829
|
+
/* The facts: a muted word, then the value; only a machine's own words (a name, a model, a branch, a clock) are mono. */
|
|
1830
|
+
.lane-card .facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .0625rem .625rem; margin-top: .25rem; font-size: .75rem; line-height: 1.5; }
|
|
1804
1831
|
.lane-card .fact { display: contents; }
|
|
1805
|
-
.lane-card .fact .k { color: var(--muted-foreground);
|
|
1806
|
-
.lane-card .fact .v {
|
|
1807
|
-
.lane-card .
|
|
1832
|
+
.lane-card .fact .k { color: var(--muted-foreground); }
|
|
1833
|
+
.lane-card .fact .v { color: var(--foreground); overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
|
|
1834
|
+
.lane-card .fact .v.mono { display: inline; margin: 0; font-family: var(--font-mono); font-size: .6875rem; }
|
|
1835
|
+
.lane-card .chips { display: flex; flex-wrap: wrap; gap: .25rem; margin-top: .5rem; }
|
|
1808
1836
|
.lane-card .chips .badge { margin: 0; }
|
|
1809
|
-
/* The live strip on a building card: stage
|
|
1810
|
-
the run's own facts, never a percent. */
|
|
1837
|
+
/* The live strip on a building card: the stage in words, the clock in mono, on a sunken well. */
|
|
1811
1838
|
.lane-card .live-line {
|
|
1812
1839
|
display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-top: .375rem;
|
|
1813
|
-
padding: .3rem .5rem; border-radius:
|
|
1814
|
-
font-
|
|
1840
|
+
padding: .3rem .5rem; border-radius: var(--so-radius-sm); background: var(--so-raised);
|
|
1841
|
+
font-size: .75rem; color: var(--so-info);
|
|
1815
1842
|
}
|
|
1816
|
-
.lane-card .live-line .clock { color: var(--muted-foreground); }
|
|
1843
|
+
.lane-card .live-line .clock { color: var(--muted-foreground); font-family: var(--font-mono); font-size: .6875rem; font-variant-numeric: tabular-nums; }
|
|
1817
1844
|
/* The build's steps — set up, writing, checking and committing: three quiet
|
|
1818
1845
|
ticks, the current one in the running hue. A stage, never a percent. */
|
|
1819
1846
|
.lane-card .steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .1875rem; margin-top: .5rem; }
|
|
1820
|
-
.lane-card .steps i { height: .1875rem; border-radius:
|
|
1821
|
-
.lane-card .steps i.on { background: color-mix(in srgb, var(--
|
|
1822
|
-
.lane-card .steps i.now { background: var(--
|
|
1847
|
+
.lane-card .steps i { height: .1875rem; border-radius: var(--so-radius-full); background: var(--so-line); }
|
|
1848
|
+
.lane-card .steps i.on { background: color-mix(in srgb, var(--so-info) 45%, var(--so-line)); }
|
|
1849
|
+
.lane-card .steps i.now { background: var(--so-info); }
|
|
1823
1850
|
|
|
1824
1851
|
/* The worker strip: one quiet tile per worker above the lanes — what it is
|
|
1825
1852
|
building here, the stage, the clock. Ink and lines only; the running dot
|
|
@@ -2613,8 +2640,9 @@ button { min-height: 44px; }
|
|
|
2613
2640
|
.chat-workspace.projects-open::before { animation: fade 180ms ease-out; }
|
|
2614
2641
|
.switcher[open] .switcher-menu { animation: rise 160ms ease-out; }
|
|
2615
2642
|
@media (hover: hover) and (pointer: fine) {
|
|
2616
|
-
.lane-card, .decide-card, .menu-row, .chat-overview-item, .proposal { transition: border-color .15s, transform .15s, box-shadow .15s; }
|
|
2617
|
-
|
|
2643
|
+
.lane-card, .decide-card, .menu-row, .chat-overview-item, .proposal { transition: border-color .15s, background-color .15s, transform .15s, box-shadow .15s; }
|
|
2644
|
+
/* Board cards are panels in a frame: a hover fills them raised, never a shadow. */
|
|
2645
|
+
.decide-card:hover, .menu-row:hover {
|
|
2618
2646
|
|
|
2619
2647
|
box-shadow: 0 2px 8px -2px rgb(0 0 0 / .35);
|
|
2620
2648
|
}
|
package/dist/server/flows.js
CHANGED
|
@@ -12,13 +12,13 @@ import { run as execRun } from "../exec.js";
|
|
|
12
12
|
import { setFlowSecret } from "../flow-actions.js";
|
|
13
13
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, flowDefinitionOf, moveCardInFlow } from "../flow-engine.js";
|
|
14
14
|
import { galleryHtml, galleryUseHtml } from "../flow-gallery-ui.js";
|
|
15
|
-
import { galleryDefaults, galleryDiagram, galleryTemplateOf, galleryToolsOf, previewGallery, useGalleryTemplate } from "../flow-gallery.js";
|
|
15
|
+
import { galleryAdmitsAnyone, galleryDefaults, galleryDiagram, galleryTemplateOf, galleryToolsOf, previewGallery, useGalleryTemplate } from "../flow-gallery.js";
|
|
16
16
|
import { flowInsights } from "../flow-insights.js";
|
|
17
17
|
import { assignFlowCard, commentOnFlowCard, watchFlowCard } from "../flow-people.js";
|
|
18
18
|
import { saveScript } from "../flow-scripts.js";
|
|
19
19
|
import { chooseFlowCard } from "../flow-send.js";
|
|
20
20
|
import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, planFlowImport } from "../flow-share.js";
|
|
21
|
-
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveSigningSecret, setUpFlowHook, changeFlowHookSender, setFlowHookScope, shareFlowButton, stopSharingFlowButton, rawTakesDeliveries } from "../flow-triggers.js";
|
|
21
|
+
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveSigningSecret, setUpFlowHook, changeFlowHookSender, setFlowHookScope, shareFlowButton, stopSharingFlowButton, rawTakesDeliveries, rawAdmitsAnyone } from "../flow-triggers.js";
|
|
22
22
|
import { FLOW_IMPORT_SCRIPT, flowFallbackHtml, flowImportHtml, flowsListHtml, flowView } from "../flows-ui.js";
|
|
23
23
|
import { FLOW_TEMPLATES, FlowContractError, validateFlowDefinition, withZoneNames } from "../flows.js";
|
|
24
24
|
import { prepareUnattendedCandidates, parseUnattendedAsk, revokeUnattended, signUnattended, untilWords } from "../flow-unattended.js";
|
|
@@ -32,7 +32,7 @@ import { createWorkflowPreview, exportRecipe, findRecipe, importRecipe, launchWo
|
|
|
32
32
|
import { isAlive as runnerAlive } from "../runner.js";
|
|
33
33
|
import { createSubagentFrom, labelOf, nameOf, saveSoul, sendSubagentSummaries, setSubagentState, subagentSettings } from "../subagent-admin.js";
|
|
34
34
|
import { addRoutine, removeRoutine, runRoutine } from "../subagent-desk.js";
|
|
35
|
-
import { editMemory, forgetMemory, tellSubagent } from "../subagent-memory.js";
|
|
35
|
+
import { ACCEPT as ACCEPT_SUGGESTION, editMemory, forgetMemory, tellSubagent } from "../subagent-memory.js";
|
|
36
36
|
import { grantTool, revokeTool, rulesFromForm, setToolRules } from "../subagent-tools.js";
|
|
37
37
|
import { sendSubagentWeeklies, undoCall } from "../subagent-week.js";
|
|
38
38
|
import { answerSubagentQuestion } from "../subagent-work.js";
|
|
@@ -301,11 +301,16 @@ export function createFlowsHandlers(runtime) {
|
|
|
301
301
|
return redirect(response, `/kits?problem=${encodeURIComponent("Choose one of your projects.")}`);
|
|
302
302
|
const page = (key, words) => redirect(response, `/kits/${kit.id}?repo=${encodeURIComponent(repo)}&${key}=${encodeURIComponent(words)}`);
|
|
303
303
|
if (step === "setup") {
|
|
304
|
-
|
|
304
|
+
// Its tools come with it only on the person's proof (a password, or the session under Passwordless): a grant widens.
|
|
305
|
+
const proved = authenticateApprover(who, body.get("password") ?? "", repo).ok;
|
|
306
|
+
const made = await setUpKit(store, kit, repo, who.name, now, options.configDir ?? null, { toolHome }, proved ? { widening: "proved" } : {});
|
|
305
307
|
return page(made.ok ? "said" : "problem", made.said);
|
|
306
308
|
}
|
|
307
309
|
if (step === "github") {
|
|
308
|
-
|
|
310
|
+
// Issues from anyone on GitHub come into a flow: a widening, so it takes a password (chat-management.md §0a.3).
|
|
311
|
+
if (!authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
312
|
+
return page("problem", "Enter your Toolroll password to bring in GitHub issues. Nothing changed.");
|
|
313
|
+
const made = addKitGithubTrigger(store, kit, repo, who.name, now, options.configDir ?? null, "proved");
|
|
309
314
|
return page(made.ok ? "said" : "problem", made.said);
|
|
310
315
|
}
|
|
311
316
|
const tried = addKitSample(store, kit, repo, who.name, now);
|
|
@@ -330,7 +335,10 @@ export function createFlowsHandlers(runtime) {
|
|
|
330
335
|
const card = question === null ? null : store.getFlowCard(question.card);
|
|
331
336
|
const flow = card === null ? null : store.getFlow(card.flow);
|
|
332
337
|
const done = question === null || flow === null || !visible(flow.repo) ? { ok: false, said: "No such question." }
|
|
333
|
-
|
|
338
|
+
// Accepting a looser rule it suggested takes the password (chat-management.md §0a.3); every other answer doesn't.
|
|
339
|
+
: question.suggestion !== null && body.get("choice") === ACCEPT_SUGGESTION && !authenticateApprover(who, body.get("password") ?? "").ok
|
|
340
|
+
? { ok: false, said: "Enter your Toolroll password to change the rule." }
|
|
341
|
+
: answerSubagentQuestion(store, question.id, { choice: body.get("choice"), text: body.get("text"), by: who.name, via: "web", ...(question.suggestion !== null ? { widening: "proved" } : {}) }, now);
|
|
334
342
|
if (wantsJson)
|
|
335
343
|
return respond(response, done.ok ? 200 : 409, "application/json; charset=utf-8", JSON.stringify(done));
|
|
336
344
|
const back = question === null ? "/settings/lead/subagents" : `/settings/lead/subagents/${question.subagent}`;
|
|
@@ -403,9 +411,23 @@ export function createFlowsHandlers(runtime) {
|
|
|
403
411
|
// v94: which project tools it may use, and its rule for each action.
|
|
404
412
|
const tool = body.get("tool") ?? "", op = body.get("op");
|
|
405
413
|
const grant = store.subagentGrant(mate.id, tool);
|
|
406
|
-
|
|
414
|
+
// Giving a subagent a tool widens what it does unasked: a password (chat-management.md §0a.3). Revoking and
|
|
415
|
+
// tightening a rule don't need one.
|
|
416
|
+
if (op === "grant" && !authenticateApprover(who, body.get("password") ?? "", mate.repo).ok) {
|
|
417
|
+
return redirect(response, `${back}?problem=${encodeURIComponent(`Enter your Toolroll password to let ${nameOf(mate)} use ${tool}. Nothing changed.`)}#tools`);
|
|
418
|
+
}
|
|
419
|
+
const changed = op === "grant" ? await grantTool(store, mate, tool, who.name, now, { toolHome }, { widening: "proved" })
|
|
407
420
|
: op === "revoke" ? revokeTool(store, mate, tool, who.name, now)
|
|
408
|
-
: grant === null ? { ok: false, said: `${nameOf(mate)} doesn't use ${tool}.` } :
|
|
421
|
+
: grant === null ? { ok: false, said: `${nameOf(mate)} doesn't use ${tool}.` } : (() => {
|
|
422
|
+
// Loosening a rule widens what it does unasked: a password (chat-management.md §0a.3); tightening needs none.
|
|
423
|
+
const changes = rulesFromForm(grant, key => body.sent.get(key));
|
|
424
|
+
const tried = setToolRules(store, mate, tool, changes, who.name, now);
|
|
425
|
+
if (tried.ok || !/takes your password/.test(tried.said))
|
|
426
|
+
return tried;
|
|
427
|
+
if (!authenticateApprover(who, body.get("password") ?? "", mate.repo).ok)
|
|
428
|
+
return { ok: false, said: `Enter your Toolroll password to let ${nameOf(mate)} do more on its own. Nothing changed.` };
|
|
429
|
+
return setToolRules(store, mate, tool, changes, who.name, now, { widening: "proved" });
|
|
430
|
+
})();
|
|
409
431
|
return redirect(response, `${back}?${changed.ok ? "said" : "problem"}=${encodeURIComponent(changed.said)}#tools`);
|
|
410
432
|
}
|
|
411
433
|
const done = part === "state" ? setSubagentState(store, mate, body.get("state") === "removed" ? "removed" : body.get("state") === "paused" ? "paused" : "active", who.name, now)
|
|
@@ -445,7 +467,11 @@ export function createFlowsHandlers(runtime) {
|
|
|
445
467
|
}
|
|
446
468
|
if (digest === null || digest !== body.get("previewed"))
|
|
447
469
|
return sendGalleryUse(response, who, template, projects, repo, answers, name, digest === null ? null : "Your answers changed. Check the preview, then create the flow.");
|
|
448
|
-
|
|
470
|
+
// A template whose trigger brings in items from anyone takes the password (chat-management.md §0a.3).
|
|
471
|
+
const anyone = galleryAdmitsAnyone(template);
|
|
472
|
+
if (anyone && !authenticateApprover(who, body.get("password") ?? "", repo).ok)
|
|
473
|
+
return sendGalleryUse(response, who, template, projects, repo, answers, name, "Enter your Toolroll password: this flow brings in items from anyone.");
|
|
474
|
+
const used = useGalleryTemplate(store, template, repo, answers, { name, by: who.name, now: clock(), dir: options.configDir ?? null, ...(anyone ? { widening: "proved" } : {}) });
|
|
449
475
|
if (!used.ok)
|
|
450
476
|
return sendGalleryUse(response, who, template, projects, repo, answers, name, used.said);
|
|
451
477
|
return redirect(response, `/flows/${used.flow}`);
|
|
@@ -576,7 +602,11 @@ export function createFlowsHandlers(runtime) {
|
|
|
576
602
|
const ingress = rawTakesDeliveries(raw);
|
|
577
603
|
if (ingress && !authenticateCredential(who, body.get("password") ?? "").ok)
|
|
578
604
|
return answer(403, { ok: false, said: "Enter your Toolroll password to make a webhook address." });
|
|
579
|
-
|
|
605
|
+
// Items from anyone: a widening, an ordinary step-up (chat-management.md §0a.3).
|
|
606
|
+
const anyone = !ingress && rawAdmitsAnyone(raw);
|
|
607
|
+
if (anyone && !authenticateApprover(who, body.get("password") ?? "", flow.repo).ok)
|
|
608
|
+
return answer(403, { ok: false, said: "Enter your Toolroll password to bring in items from anyone." });
|
|
609
|
+
const made = addFlowTriggerTo(store, flow, raw, who.name, now, dir, ingress ? { ingress: "proved" } : anyone ? { widening: "proved" } : {});
|
|
580
610
|
if (made.ok)
|
|
581
611
|
await prepareUnattendedCandidates(store, flow.id);
|
|
582
612
|
return made.ok ? settle(made.said, made.reveal === null ? {} : { reveal: made.reveal }) : answer(400, { ok: false, said: made.message });
|
package/dist/server/live.d.ts
CHANGED
|
@@ -45,6 +45,10 @@ export type LiveRoom = {
|
|
|
45
45
|
} | {
|
|
46
46
|
kind: "agent";
|
|
47
47
|
run: number;
|
|
48
|
+
}
|
|
49
|
+
/** The Agents view's Live grid (agents-live-room.ts): per viewer, watching only. */
|
|
50
|
+
| {
|
|
51
|
+
kind: "agents-live";
|
|
48
52
|
};
|
|
49
53
|
/** A room's name, read strictly: anything else is not a room. */
|
|
50
54
|
export declare function parseLiveRoom(name: string): LiveRoom | null;
|