toolroll 0.9.15 → 0.9.17
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 +36 -0
- package/dist/assignment-brief.d.ts +10 -0
- package/dist/assignment-brief.js +56 -26
- package/dist/assignment.d.ts +8 -7
- package/dist/assignment.js +13 -8
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +22 -22
- package/dist/browser-crew.js +1 -0
- package/dist/browser-workspace.d.ts +5 -0
- package/dist/chat-actions.d.ts +7 -0
- package/dist/chat-actions.js +40 -4
- package/dist/chat-ask.d.ts +28 -0
- package/dist/chat-ask.js +91 -0
- package/dist/chat-channel.js +8 -1
- package/dist/chat-controls.d.ts +8 -0
- package/dist/chat-controls.js +3 -0
- package/dist/chat-delivery-state.d.ts +8 -1
- package/dist/chat-delivery-state.js +15 -2
- package/dist/chat-delivery.d.ts +3 -0
- package/dist/chat-delivery.js +49 -17
- package/dist/chat-quiet.js +4 -2
- package/dist/chat-voice.d.ts +3 -6
- package/dist/chat-voice.js +8 -2
- package/dist/chat-warmth.d.ts +21 -0
- package/dist/chat-warmth.js +36 -0
- package/dist/discord-api.d.ts +1 -1
- package/dist/discord-api.js +1 -1
- package/dist/discord-chat.d.ts +3 -2
- package/dist/discord-chat.js +28 -3
- package/dist/lead-about.d.ts +35 -0
- package/dist/lead-about.js +62 -0
- package/dist/lead-capabilities.d.ts +64 -0
- package/dist/lead-capabilities.js +199 -0
- package/dist/lead-commitments.d.ts +84 -0
- package/dist/lead-commitments.js +214 -0
- package/dist/lead-context.d.ts +34 -3
- package/dist/lead-context.js +155 -19
- package/dist/lead-follow.d.ts +1 -1
- package/dist/lead-follow.js +6 -2
- package/dist/lead-identity.d.ts +23 -0
- package/dist/lead-identity.js +33 -0
- package/dist/lead-people.d.ts +55 -0
- package/dist/lead-people.js +128 -0
- package/dist/lead-voice.d.ts +13 -9
- package/dist/lead-voice.js +26 -16
- package/dist/ledger-view.js +3 -2
- package/dist/mate-cli.d.ts +4 -1
- package/dist/mate-cli.js +23 -16
- package/dist/mate-contract.d.ts +1 -1
- package/dist/mate-contract.js +13 -4
- package/dist/mate-progress.js +7 -0
- package/dist/mate-tools.d.ts +26 -1
- package/dist/mate-tools.js +357 -7
- package/dist/mate.d.ts +35 -1
- package/dist/mate.js +121 -61
- package/dist/operate.js +4 -1
- package/dist/phase-routing.d.ts +1 -1
- package/dist/project-delete.js +3 -1
- package/dist/provider-auth.d.ts +4 -0
- package/dist/provider-auth.js +1 -1
- package/dist/reply-shape.d.ts +96 -0
- package/dist/reply-shape.js +452 -0
- package/dist/result-acts.d.ts +14 -0
- package/dist/result-acts.js +19 -0
- package/dist/retention.js +6 -0
- package/dist/serve.js +162 -55
- package/dist/slack-chat.d.ts +2 -0
- package/dist/slack-chat.js +30 -3
- package/dist/store.d.ts +60 -0
- package/dist/store.js +152 -3
- package/dist/surface.js +2 -2
- package/dist/task-status.d.ts +5 -15
- package/dist/task-status.js +18 -5
- package/dist/team-runtime.js +3 -2
- package/dist/teams-chat.js +16 -2
- package/dist/telegram-mate.d.ts +6 -1
- package/dist/telegram-mate.js +43 -11
- package/dist/telegram-progress.js +3 -2
- package/dist/telegram-team.js +6 -3
- package/dist/telegram.js +43 -3
- package/dist/test-temp.js +1 -1
- package/dist/when-html.d.ts +8 -3
- package/dist/when-html.js +23 -3
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +9 -6
- package/dist/work-summary.d.ts +1 -1
- package/dist/workspace-ui.d.ts +2 -14
- package/dist/workspace-ui.js +4 -22
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { checkLeadIdentity, leadIdentityOf, leadNameOf, LEAD_NAME_MAX, LEAD_PERSONA_MAX } from "./lead-identity.js";
|
|
2
|
+
import { aboutYouOf, checkAboutYou, saveAboutYou, ABOUT_YOU_LINE_MAX, ABOUT_YOU_MAX_LINES } from "./lead-about.js";
|
|
1
3
|
import { withActor } from "./actor.js";
|
|
2
4
|
import { repositoryContext, repositoryContextRead } from './repository-context.js';
|
|
3
5
|
import { ADD_TESTS_ACTION, followUpChecksOf, requestFollowUpChecks, fileAddTestsTask, resultCheckLevel } from './result-follow-ups.js';
|
|
@@ -6,6 +8,7 @@ import { repositoryContextHtml } from './repository-context-ui.js';
|
|
|
6
8
|
import { browserAssetsAvailable, browserWorkspaceDocument, serveBrowserAsset } from './browser-shell.js';
|
|
7
9
|
import { browserCrewOf, browserCrewFromIndex, browserWorkActionHref, browserProjectsOf, browserNavigationOf } from './browser-workspace.js';
|
|
8
10
|
import { configureLeadFollow, leadFollowStatus, runLeadFollowPass } from './lead-follow.js';
|
|
11
|
+
import { cancelCommitment, conditionWords, openCommitments } from './lead-commitments.js';
|
|
9
12
|
import { startMaintenance } from './maintenance.js';
|
|
10
13
|
import { codingHandoffPreview, createCodingHandoff } from './coding-handoff.js';
|
|
11
14
|
import { codingShippingHtml, CODING_SHIPPING_CSS } from './coding-shipping-ui.js';
|
|
@@ -18,7 +21,7 @@ import { ChatState } from "./chat-delivery-state.js";
|
|
|
18
21
|
import { slackSettingsHtml } from "./slack-settings.js";
|
|
19
22
|
import { checkSlackCredentials, loadSlackCredentials, saveSlackCredentials, clearSlackCredentials, SLACK_MANIFEST, SlackError } from "./slack-api.js";
|
|
20
23
|
import { SlackState } from "./slack-state.js";
|
|
21
|
-
import { CHAT_ACTIONS, sharedActionPayload, sharedActionNeedsReview, sharedActionReviewPath, mintSharedActionReview } from './chat-actions.js';
|
|
24
|
+
import { CHAT_ACTIONS, OWNER_ACTIONS, sharedActionPayload, sharedActionNeedsReview, sharedActionReviewPath, mintSharedActionReview } from './chat-actions.js';
|
|
22
25
|
import { changeSkills, githubSkill, importSkill, readSkillsSnapshot, reviseSkillTest, skillTestResult, skillsView, testSkill } from "./project-skills.js";
|
|
23
26
|
import { skillsHtml, skillsScript, skillsSnapshotHtml, skillTestFeedbackHtml, SKILLS_CSS } from "./skills-ui.js";
|
|
24
27
|
import { toolsHtml, TOOLS_CSS } from "./tools-ui.js";
|
|
@@ -65,7 +68,7 @@ import { prepareWorkspaceRevision, WorkspaceValidatorCache } from "./workspace-r
|
|
|
65
68
|
import { workIndexPage, workCountsByProject, WorkIndexCursorError, WORK_INDEX_MAX_LIMIT } from "./work-index.js";
|
|
66
69
|
import { leadActivity } from "./lead-voice.js";
|
|
67
70
|
import { openWorkDecisionOf } from "./work-summary.js";
|
|
68
|
-
import { acceptAndCompleteAsOperator, assignmentOf, checkAssignmentAsOperator,
|
|
71
|
+
import { acceptAndCompleteAsOperator, assignmentOf, checkAssignmentAsOperator, owedAcceptance } from './assignment.js';
|
|
69
72
|
import { demoChatHtml, demoThreadHtml, DEMO_CHAT_SCRIPT } from "./demo-chat.js";
|
|
70
73
|
import { pullRequestSettingsHtml, PULL_REQUEST_SETTINGS_CSS } from './pull-request-ui.js';
|
|
71
74
|
import { checkSettingsHtml, CHECK_SETTINGS_CSS } from './check-levels-ui.js';
|
|
@@ -185,7 +188,7 @@ import { dirname } from "node:path";
|
|
|
185
188
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
186
189
|
import { loadOrCreateVapidKeys, validatePushEndpoint } from "./push.js";
|
|
187
190
|
import { parseGithubRepo, previewGithubRepo, cloneGithubRepo, listGithubRepos, isLargeRepo } from "./onboard.js";
|
|
188
|
-
import { verifiedAuthor, LEAD_THREAD, isDigestTime } from "./store.js";
|
|
191
|
+
import { verifiedAuthor, LEAD_THREAD, isDigestTime, MATE_ASK_OTHER } from "./store.js";
|
|
189
192
|
import { digestTimes } from "./digest-times.js";
|
|
190
193
|
import { updateRepos, addRepos, removeRepos } from "./repos.js";
|
|
191
194
|
import { run as execRun } from "./exec.js";
|
|
@@ -210,7 +213,7 @@ import { authorizedProject, canonicalProject, isGitRepo, projectName, sameRepo,
|
|
|
210
213
|
import { tally, spendLine, runCostWords } from "./summary.js";
|
|
211
214
|
import { classify, holdOwnerWords, attentionCardForUnverifiedDone } from "./board.js";
|
|
212
215
|
import { approveRoutine, describeSchedule, fireRoutine, parseSchedule, refreshRoutineAgents, routineAgentsState, routineDigestOf, validateRoutineTerms, ROUTINE_NAME } from "./routine.js";
|
|
213
|
-
import { effectivePrimary, isMessagingChannel, savePrimary } from "./webhooks.js";
|
|
216
|
+
import { effectivePrimary, isMessagingChannel, loadConsoleUrl, savePrimary } from "./webhooks.js";
|
|
214
217
|
import { resolvePhaseAgent, resolveRoutineAuthority, INSTALLATION_SCOPE, routeOfTask, agentChoicesFor } from "./agentconfig.js";
|
|
215
218
|
import { isRiskLevel, projectRoute, riskTitle, riskConsequence, chosenWords, agentsSummary, postureWords, RISK_CHOICES, RISK_LEVELS, PHASES as ROUTE_PHASES } from "./phase-routing.js";
|
|
216
219
|
import { ALL_CREDENTIAL_ENV, isProviderId, reportsCost, PROVIDER_IDS, validModelId, validateSpec } from "./provider.js";
|
|
@@ -258,6 +261,7 @@ import { handleTeamsHttp } from "./teams.js";
|
|
|
258
261
|
import { checkTeamsCredentials, clearTeamsCredentials, loadTeamsCredentials, saveTeamsCredentials, TeamsError } from "./teams-api.js";
|
|
259
262
|
import { verifyApproverByPassword, verifyApproverStanding } from "./principal.js";
|
|
260
263
|
import { runMateTurn, MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
264
|
+
import { replyHtmlInline, shapeReply } from "./reply-shape.js";
|
|
261
265
|
import { confirmCoordinatorProposal, confirmMateProposal, dismissCoordinatorProposal, dismissMateProposal } from "./mate-doors.js";
|
|
262
266
|
import { envValue } from "./names.js";
|
|
263
267
|
import { cachedRelease, isNewer, runnerVersions, setUpdateChecks, updateChecksOff } from "./releases.js";
|
|
@@ -1527,6 +1531,8 @@ export function createDecisionServer(options) {
|
|
|
1527
1531
|
actor: who.name,
|
|
1528
1532
|
csrf: who.via === "cookie" ? who.session.csrf : "",
|
|
1529
1533
|
returnTo: safeReturn(url.pathname + url.search),
|
|
1534
|
+
// This console's own addresses: a lead reply's links there read as "the task", "the result", "Settings → Lead".
|
|
1535
|
+
appOrigins: [consoleOrigin(request.headers.host), publicOrigin?.origin ?? null, options.configDir === undefined ? null : loadConsoleUrl(process.env, options.configDir)],
|
|
1530
1536
|
// Every signed-in page shares the workspace shell; pages showing a one-time secret opt out (forceSensitive).
|
|
1531
1537
|
browser: who.via === 'cookie',
|
|
1532
1538
|
// v100: signed in with the identity provider: its label, and whether it checked them recently enough to stand in for a password.
|
|
@@ -1632,7 +1638,7 @@ export function createDecisionServer(options) {
|
|
|
1632
1638
|
}
|
|
1633
1639
|
if (admitted && proposal) {
|
|
1634
1640
|
const taskId = typeof action?.request['task'] === 'string' ? action.request['task'] : typeof proposal.payload['task'] === 'string' ? proposal.payload['task'] : null;
|
|
1635
|
-
const repo = action?.repo ?? (taskId ? store.lookupRef(taskId)?.repo : null) ?? (typeof proposal.payload['repo'] === 'string' ? proposal.payload['repo'] : null);
|
|
1641
|
+
const repo = (action?.repo || null) ?? (taskId ? store.lookupRef(taskId)?.repo : null) ?? (typeof proposal.payload['repo'] === 'string' ? proposal.payload['repo'] : null);
|
|
1636
1642
|
return { repo, taskId, runId: typeof action?.request['run'] === 'number' ? action.request['run'] : null, action: action?.operation ?? `chat ${proposal.kind}` };
|
|
1637
1643
|
}
|
|
1638
1644
|
}
|
|
@@ -1697,6 +1703,9 @@ export function createDecisionServer(options) {
|
|
|
1697
1703
|
}
|
|
1698
1704
|
if (action && proposal && store.getMateThread(proposal.thread)?.approver === who.name && visible(action.repo) && store.accountCanAccess(who.name, action.repo))
|
|
1699
1705
|
return true;
|
|
1706
|
+
// A card about the person themselves (what their lead knows about them) is in no project.
|
|
1707
|
+
if (action && proposal && action.repo === '' && OWNER_ACTIONS.has(action.operation) && store.getMateThread(proposal.thread)?.approver === who.name)
|
|
1708
|
+
return true;
|
|
1700
1709
|
refuse(response, who, 404, 'No such action in your projects.', '/projects');
|
|
1701
1710
|
return false;
|
|
1702
1711
|
}
|
|
@@ -2465,6 +2474,7 @@ export function createDecisionServer(options) {
|
|
|
2465
2474
|
csrf,
|
|
2466
2475
|
canRetryReview: who.via === "cookie" && who.role === "approver",
|
|
2467
2476
|
noted: url.searchParams.get("noted") !== null,
|
|
2477
|
+
refusal: Object.hasOwn(RESULT_REFUSALS, url.searchParams.get("refused") ?? "") ? RESULT_REFUSALS[url.searchParams.get("refused")] : null,
|
|
2468
2478
|
tab: parseResultTab(url.searchParams.get("tab")),
|
|
2469
2479
|
user: who.name,
|
|
2470
2480
|
now,
|
|
@@ -2914,8 +2924,9 @@ export function createDecisionServer(options) {
|
|
|
2914
2924
|
const version = url.searchParams.get("version");
|
|
2915
2925
|
if (version !== null && !family.versions.some(one => one.id === version))
|
|
2916
2926
|
return refuse(response, who, 404, "That version is not available for this task.", taskHref(family.root.id));
|
|
2927
|
+
// A version's own address lands on its family's page, still asking for the plan editor when it did (Chat's Edit plan).
|
|
2917
2928
|
if (task.taskId !== family.root.id)
|
|
2918
|
-
return redirect(response, `${taskHref(family.root.id)}?version=${encodeURIComponent(task.taskId)}`);
|
|
2929
|
+
return redirect(response, `${taskHref(family.root.id)}?version=${encodeURIComponent(task.taskId)}${url.searchParams.get("edit") === "plan" ? "&edit=plan" : ""}`);
|
|
2919
2930
|
return taskScreen(response, who, version ?? family.current.id, null, 200, undefined, undefined, url.searchParams.get("edit") === "plan");
|
|
2920
2931
|
}
|
|
2921
2932
|
// v103: a task's evidence pack, as a printable page or JSON (the whole family, sealed ledger entries included).
|
|
@@ -3482,7 +3493,7 @@ export function createDecisionServer(options) {
|
|
|
3482
3493
|
const fragments = known === version
|
|
3483
3494
|
? null
|
|
3484
3495
|
: {
|
|
3485
|
-
thread: mateThreadHtml({ ...rows, focusTask, csrf, now, problem: takeMateNote(csrf, session.id) }),
|
|
3496
|
+
thread: mateThreadHtml({ ...rows, focusTask, csrf, now, problem: takeMateNote(csrf, session.id), chatProject }),
|
|
3486
3497
|
after: mateAfterComposerHtml({ messages: rows.messages, pending: rows.pending, focusTask, csrf }),
|
|
3487
3498
|
live: focusTask === null ? null : taskChatLiveRegion(focusTask, csrf, true, rows.pending !== null),
|
|
3488
3499
|
};
|
|
@@ -4132,8 +4143,10 @@ export function createDecisionServer(options) {
|
|
|
4132
4143
|
returnTo: "/settings/lead",
|
|
4133
4144
|
};
|
|
4134
4145
|
const signedIn = localSignIn?.states.claude === "connected" ? "Claude Code" : localSignIn?.states.codex === "connected" ? "Codex" : null;
|
|
4146
|
+
const promises = openCommitments(store, who.name, 50).map(one => ({ id: one.id, what: one.what, when: conditionWords(store, one.condition), until: one.expiresAt }));
|
|
4135
4147
|
return sendScreen(response, 200, screen("Lead", leadSettingsHtml({ config, facts, words: leadWords(), signedIn, command: agentSignInCommand(),
|
|
4136
|
-
said: url.searchParams.get("said")
|
|
4148
|
+
said: url.searchParams.get("said"), saved: url.searchParams.get("saved") === "1", identity: leadIdentityOf(store, who.name), promises,
|
|
4149
|
+
about: aboutYouOf(store, who.name), aboutSaved: url.searchParams.get("saved") === "about" }), { chrome: chromeFor(project, "settings") }));
|
|
4137
4150
|
}
|
|
4138
4151
|
if (url.pathname === "/settings/telegram") {
|
|
4139
4152
|
// Any approver pairs their OWN phone here; the bot token stays on /settings.
|
|
@@ -4432,8 +4445,16 @@ export function createDecisionServer(options) {
|
|
|
4432
4445
|
// One reply runs at a time per person; this thread shows it only when
|
|
4433
4446
|
// the reply is its own.
|
|
4434
4447
|
const live = store.liveMateTurnFor(who.name);
|
|
4448
|
+
const messages = store.listMateMessages(opened.thread.id, 40);
|
|
4449
|
+
// The lead's question with buttons, while its reply is the last word in the thread.
|
|
4450
|
+
const last = messages.at(-1);
|
|
4451
|
+
const ask = last?.role === "assistant" && last.turn !== null && store.getMateTurn(last.turn)?.approver === who.name ? store.mateAskOpen(last.turn, now) : null;
|
|
4452
|
+
// Answered questions stay readable above the answer; only their buttons go.
|
|
4453
|
+
const asks = new Map(messages.flatMap(one => { const asked = one.role === "assistant" && one.turn !== null ? store.mateAsk(one.turn) : null; return asked === null ? [] : [[asked.turn, asked]]; }));
|
|
4435
4454
|
return {
|
|
4436
|
-
messages
|
|
4455
|
+
messages,
|
|
4456
|
+
ask,
|
|
4457
|
+
asks,
|
|
4437
4458
|
proposals,
|
|
4438
4459
|
decisions: decisionsFor(store, [...proposals, ...coordinatorProposals]),
|
|
4439
4460
|
coordinatorProposals,
|
|
@@ -4508,7 +4529,7 @@ export function createDecisionServer(options) {
|
|
|
4508
4529
|
const rows = mateConversationRows(who, principal, focusTask, now, chatProject);
|
|
4509
4530
|
return {
|
|
4510
4531
|
sessionId: session.id, user: session.approver, version: mateChatVersion({ ...rows, focusTask }),
|
|
4511
|
-
messages: mateBrowserMessages(rows, who.session.csrf, back),
|
|
4532
|
+
messages: mateBrowserMessages(rows, who.session.csrf, back, { task: focusTask?.id ?? null, project: chatProject }),
|
|
4512
4533
|
pendingTurnId: rows.pending?.id ?? null, requestId: randomBytes(16).toString("hex"), maxChars: MATE_MESSAGE_MAX_CHARS,
|
|
4513
4534
|
taskId: focusTask?.id ?? null, resultRunId, project: focusTask === null ? chatProject : null,
|
|
4514
4535
|
};
|
|
@@ -4805,20 +4826,20 @@ export function createDecisionServer(options) {
|
|
|
4805
4826
|
if (result.problem.startsWith("status-")) {
|
|
4806
4827
|
// The provider ANSWERED with an error: nothing billed.
|
|
4807
4828
|
finish({ state: "failed", failureReason: "provider-error", settledMicrousd: 0 });
|
|
4808
|
-
tell(null, "
|
|
4829
|
+
tell(null, "The chat provider turned the request down. Nothing was kept or charged. Try again in a minute.");
|
|
4809
4830
|
}
|
|
4810
4831
|
else if (result.problem === "timeout") {
|
|
4811
4832
|
finish({ state: "failed", failureReason: "timeout", settledMicrousd: null, unknownSpend: true });
|
|
4812
|
-
tell(null, "
|
|
4833
|
+
tell(null, "The reply took too long and was stopped, and its cost isn't known yet, so chat is paused. Confirm the cost on this page to turn chat back on.");
|
|
4813
4834
|
}
|
|
4814
4835
|
else if (result.problem === "network") {
|
|
4815
4836
|
finish({ state: "failed", failureReason: "provider-error", settledMicrousd: null, unknownSpend: true });
|
|
4816
|
-
tell(null, "
|
|
4837
|
+
tell(null, "The chat provider stopped responding partway through, and the cost isn't known, so chat is paused. Confirm the cost on this page to turn chat back on.");
|
|
4817
4838
|
}
|
|
4818
4839
|
else {
|
|
4819
4840
|
// 200 with an unusable wrapper: billed, amount unproven.
|
|
4820
4841
|
finish({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
4821
|
-
tell(null, "
|
|
4842
|
+
tell(null, "The chat provider sent back an answer that couldn't be read, and its cost isn't known, so chat is paused. Confirm the cost on this page to turn chat back on.");
|
|
4822
4843
|
}
|
|
4823
4844
|
return;
|
|
4824
4845
|
}
|
|
@@ -4976,7 +4997,8 @@ export function createDecisionServer(options) {
|
|
|
4976
4997
|
try {
|
|
4977
4998
|
const project = s.chrome.active === 'chat' ? null : s.chrome.project;
|
|
4978
4999
|
crew = extras.team?.tasks ? browserCrewFromIndex(extras.team.tasks, extras.team.selected?.id) : requestFacts.workCrew?.project === project ? browserCrewFromIndex(requestFacts.workCrew.page)
|
|
4979
|
-
|
|
5000
|
+
// The reader's own lead's claims read "<name> is on it" here too.
|
|
5001
|
+
: browserCrewOf(store, clock(), { principal: 'operator', repos: managedRepos(), includeUnplaced: false, viewer: requestFacts.actor ?? null }, { evidenceRoot, project });
|
|
4980
5002
|
}
|
|
4981
5003
|
catch {
|
|
4982
5004
|
notices.push('Crew updates are unavailable. Open Tasks to inspect saved work.');
|
|
@@ -5018,6 +5040,7 @@ export function createDecisionServer(options) {
|
|
|
5018
5040
|
const request = requestFacts.workspaceRequest;
|
|
5019
5041
|
const workspace = {
|
|
5020
5042
|
version: 1, path: currentPath, title: s.title, user: requestFacts.actor ?? '', csrf: requestFacts.csrf, sensitive,
|
|
5043
|
+
...(requestFacts.actor ? { leadName: leadNameOf(store, requestFacts.actor) } : {}),
|
|
5021
5044
|
refreshUrl: path.pathname + path.search,
|
|
5022
5045
|
receipt: conversation !== null && request && REQUEST_TOKEN.test(request)
|
|
5023
5046
|
? { request, received: store.mateRequestReceipt(conversation.sessionId, request) !== null } : null,
|
|
@@ -5123,7 +5146,7 @@ export function createDecisionServer(options) {
|
|
|
5123
5146
|
// One line: what this person's lead is doing now and when it last acted.
|
|
5124
5147
|
const activity = leadActivity(store, who.name);
|
|
5125
5148
|
const leadTask = activity?.taskId == null ? null : store.lookupRef(activity.taskId);
|
|
5126
|
-
const lead = activity === null ? null : { doing: activity.doing, at: activity.at,
|
|
5149
|
+
const lead = activity === null ? null : { name: activity.name, doing: activity.doing, at: activity.at,
|
|
5127
5150
|
href: leadTask == null || !admitted(leadTask.repo) ? null : taskHref(familyOf(activity.taskId)?.root.id ?? activity.taskId) };
|
|
5128
5151
|
return {
|
|
5129
5152
|
agents, counts, catchUp, allHref: "/work", ...(lead === null ? {} : { lead }),
|
|
@@ -5224,7 +5247,7 @@ export function createDecisionServer(options) {
|
|
|
5224
5247
|
};
|
|
5225
5248
|
}
|
|
5226
5249
|
function workAccess() {
|
|
5227
|
-
// The reader's own lead's claims read "
|
|
5250
|
+
// The reader's own lead's claims read "<name> is on it" (lead-voice.ts); nobody else's.
|
|
5228
5251
|
return { principal: 'operator', repos: admissionList(), includeUnplaced: visible(null), viewer: requestContext.getStore()?.actor ?? null };
|
|
5229
5252
|
}
|
|
5230
5253
|
// All project counts are read together. No task artifacts or processes are
|
|
@@ -9206,6 +9229,35 @@ export function createDecisionServer(options) {
|
|
|
9206
9229
|
}
|
|
9207
9230
|
// Turn the lead on with the agent signed in on this computer: its membership spends no dollars, so like starting a
|
|
9208
9231
|
// conversation it needs no second password; the full form (Advanced) still asks for one.
|
|
9232
|
+
// Settings → Lead: the name and persona this person's lead speaks with.
|
|
9233
|
+
if (url.pathname === "/settings/lead/identity") {
|
|
9234
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
9235
|
+
return refuse(response, who, 403, "An approver names their lead.", "/settings/lead");
|
|
9236
|
+
const checked = checkLeadIdentity(body.get("name") ?? "", body.get("persona") ?? "");
|
|
9237
|
+
if (!checked.ok)
|
|
9238
|
+
return redirect(response, chatReturnWithSaid("/settings/lead", checked.message));
|
|
9239
|
+
store.setLeadConfig(who.name, checked.identity.name, checked.identity.persona, now);
|
|
9240
|
+
return redirect(response, "/settings/lead?saved=1");
|
|
9241
|
+
}
|
|
9242
|
+
// Settings → Lead: what this person's lead knows about them, one line each.
|
|
9243
|
+
if (url.pathname === "/settings/lead/about") {
|
|
9244
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
9245
|
+
return refuse(response, who, 403, "An approver edits what their lead knows about them.", "/settings/lead");
|
|
9246
|
+
const checked = checkAboutYou(body.get("about") ?? "");
|
|
9247
|
+
if (!checked.ok)
|
|
9248
|
+
return redirect(response, chatReturnWithSaid("/settings/lead", checked.message));
|
|
9249
|
+
saveAboutYou(store, who.name, checked.lines, now);
|
|
9250
|
+
return redirect(response, "/settings/lead?saved=about");
|
|
9251
|
+
}
|
|
9252
|
+
// Settings → Lead: stop the lead following up on one of your promises.
|
|
9253
|
+
if (url.pathname === "/settings/lead/promise/cancel") {
|
|
9254
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
9255
|
+
return refuse(response, who, 403, "An approver manages the lead's promises.", "/settings/lead");
|
|
9256
|
+
const id = Number(body.get("promise"));
|
|
9257
|
+
const done = Number.isSafeInteger(id) && cancelCommitment(store, who.name, id, who.name, "Cancelled in Settings.", now);
|
|
9258
|
+
// The promise leaving the list says it was cancelled; only a refusal needs words.
|
|
9259
|
+
return redirect(response, done ? "/settings/lead" : chatReturnWithSaid("/settings/lead", "That promise was already closed."));
|
|
9260
|
+
}
|
|
9209
9261
|
if (url.pathname === "/settings/lead/on") {
|
|
9210
9262
|
if (who.via !== "cookie" || who.role !== "approver")
|
|
9211
9263
|
return refuse(response, who, 403, "An approver turns the lead on.", "/settings/lead");
|
|
@@ -9519,7 +9571,7 @@ export function createDecisionServer(options) {
|
|
|
9519
9571
|
}
|
|
9520
9572
|
const opened = store.openMateThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
|
|
9521
9573
|
const onProgress = beginLiveTurn(opened.thread.id);
|
|
9522
|
-
void runMateTurn({ store, who: principal, session: mateSession, thread: opened.thread, config: enabled.config, key: enabled.key, message, onProgress, ...(requestId === null ? {} : { requestId }), ...(focusTask === null && chatProject !== null ? { context: `Current project: ${projectName(chatProject)} (${chatProject}). Keep this conversation about that project unless the operator explicitly asks to broaden it; use it as the repo for project tools.` } : {}), ...(focusTask === null ? {} : { context: `Current task: ${focusTask.id}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.${resultContext}${modeContext}` }), fetcher: chatFetcher, ...(options.subscriptionChatRunner === undefined ? {} : { subscriptionRunner: options.subscriptionChatRunner }), clock, evidenceRoot })
|
|
9574
|
+
void runMateTurn({ store, who: principal, session: mateSession, thread: opened.thread, config: enabled.config, key: enabled.key, message, onProgress, channel: "console", ...(requestId === null ? {} : { requestId }), ...(focusTask === null && chatProject !== null ? { context: `Current project: ${projectName(chatProject)} (${chatProject}). Keep this conversation about that project unless the operator explicitly asks to broaden it; use it as the repo for project tools.` } : {}), ...(focusTask === null ? {} : { context: `Current task: ${focusTask.id}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.${resultContext}${modeContext}` }), fetcher: chatFetcher, ...(options.subscriptionChatRunner === undefined ? {} : { subscriptionRunner: options.subscriptionChatRunner }), clock, evidenceRoot })
|
|
9523
9575
|
.then(outcome => {
|
|
9524
9576
|
if (!outcome.ok)
|
|
9525
9577
|
noteMate(who.session.csrf, "turn" in outcome ? outcome.turn : null, outcome.message);
|
|
@@ -11019,8 +11071,14 @@ export function createDecisionServer(options) {
|
|
|
11019
11071
|
throw error;
|
|
11020
11072
|
saved = { ok: false, status: 409, message: "Not saved: this would change what the agent may do. Change permissions in Details, under edit the scope." };
|
|
11021
11073
|
}
|
|
11022
|
-
if (!saved.ok)
|
|
11023
|
-
|
|
11074
|
+
if (!saved.ok) {
|
|
11075
|
+
// Refused because the plan moved on: the draft comes back with the version now on file, so saving it
|
|
11076
|
+
// again (having read the reason) can succeed instead of being refused the same way.
|
|
11077
|
+
const draft = new URLSearchParams(body);
|
|
11078
|
+
if (saved.status === 409)
|
|
11079
|
+
draft.set("sawDigest", store.getScope(taskId)?.digest ?? "");
|
|
11080
|
+
return taskScreen(response, who, taskId, saved.message, saved.status, draft);
|
|
11081
|
+
}
|
|
11024
11082
|
return redirect(response, taskHref(taskId));
|
|
11025
11083
|
}
|
|
11026
11084
|
case "approve": {
|
|
@@ -11135,8 +11193,13 @@ export function createDecisionServer(options) {
|
|
|
11135
11193
|
note = validated.note;
|
|
11136
11194
|
}
|
|
11137
11195
|
// A failed task's result is accepted only anyway, on purpose: with a reason.
|
|
11138
|
-
if (note === null && store.getTask(taskId)?.state === "failed")
|
|
11139
|
-
|
|
11196
|
+
if (note === null && store.getTask(taskId)?.state === "failed") {
|
|
11197
|
+
// Posted from the result page: back there, where the reason field is, with the refusal said on it.
|
|
11198
|
+
const back = body.get("return") === null ? null : safeReturn(body.get("return"));
|
|
11199
|
+
if (back !== null && back.startsWith("/review?"))
|
|
11200
|
+
return redirect(response, withRefusal(back, "reason"));
|
|
11201
|
+
return taskScreen(response, who, taskId, ACCEPT_ANYWAY_NEEDS_REASON, 400);
|
|
11202
|
+
}
|
|
11140
11203
|
store.acceptProof(latest.id, verifiedAuthor(who.name), note, now);
|
|
11141
11204
|
return redirect(response, body.get("return") === null ? taskHref(taskId) : safeReturn(body.get("return")));
|
|
11142
11205
|
}
|
|
@@ -11166,7 +11229,7 @@ export function createDecisionServer(options) {
|
|
|
11166
11229
|
const publish = body.get("publish") === "1";
|
|
11167
11230
|
const finished = acceptAndCompleteAsOperator(store, taskId, { runId: Number(namedRun), receiptDigest: digest, note }, principal, now, evidenceRoot, publish ? accepted => completeAndOpenPullRequest(store, { taskId, digest: accepted, runId: Number(namedRun), who: principal, root: evidenceRoot }, now) : undefined);
|
|
11168
11231
|
if (!finished.ok)
|
|
11169
|
-
return taskScreen(response, who, taskId, finished.message, 409);
|
|
11232
|
+
return taskScreen(response, who, taskId, finished.message, finished.reason === "needs-reason" ? 400 : 409);
|
|
11170
11233
|
return redirect(response, publish ? `${taskHref(familyOf(taskId)?.root.id ?? taskId)}#merge` : body.get("return") === null ? reviewHref(taskId, Number(namedRun)) : safeReturn(body.get("return")));
|
|
11171
11234
|
}
|
|
11172
11235
|
if (body.get("publish") === "1") {
|
|
@@ -17432,13 +17495,13 @@ function chatHeading(copy, projectCount, showProjectToggle = true) {
|
|
|
17432
17495
|
`${strokeIcon(`<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/>`)}<span>projects</span><span class="badge">${projectCount}</span></button>`
|
|
17433
17496
|
: "") + `</div></div>`);
|
|
17434
17497
|
}
|
|
17435
|
-
/** A deliberately small rich-text grammar for model copy.
|
|
17436
|
-
*
|
|
17437
|
-
*
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17498
|
+
/** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
|
|
17499
|
+
* no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
|
|
17500
|
+
* escaped before tags are introduced: bullets, numbered steps, bold, inline code and labelled http(s) links are
|
|
17501
|
+
* presentation only—never executable HTML. */
|
|
17502
|
+
function renderChatText(raw, asked) {
|
|
17503
|
+
const inline = replyHtmlInline;
|
|
17504
|
+
const text = shapeReply(raw, { appOrigin: requestContext.getStore()?.appOrigins ?? null, ...(asked === undefined ? {} : { asked }) });
|
|
17442
17505
|
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
17443
17506
|
const out = [];
|
|
17444
17507
|
let paragraph = [];
|
|
@@ -17456,15 +17519,9 @@ function renderChatText(text) {
|
|
|
17456
17519
|
list = null;
|
|
17457
17520
|
};
|
|
17458
17521
|
for (const line of lines) {
|
|
17459
|
-
const heading = /^(?:#{1,3}\s+)(.+)$/.exec(line);
|
|
17460
17522
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
17461
17523
|
const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
|
|
17462
|
-
if (
|
|
17463
|
-
flushParagraph();
|
|
17464
|
-
closeList();
|
|
17465
|
-
out.push(`<h3>${inline(heading[1] ?? "")}</h3>`);
|
|
17466
|
-
}
|
|
17467
|
-
else if (bullet !== null || numbered !== null) {
|
|
17524
|
+
if (bullet !== null || numbered !== null) {
|
|
17468
17525
|
flushParagraph();
|
|
17469
17526
|
const wanted = bullet !== null ? "ul" : "ol";
|
|
17470
17527
|
if (list !== wanted) {
|
|
@@ -17548,6 +17605,20 @@ function leadSettingsHtml(data) {
|
|
|
17548
17605
|
`<p><a href="/settings">Settings</a></p><h1>Lead</h1>`,
|
|
17549
17606
|
data.said === null ? "" : `<p class="problem" role="status">${escape(data.said)}</p>`,
|
|
17550
17607
|
`<section class="card lead-settings" data-lead-settings>${summary}</section>`,
|
|
17608
|
+
data.identity === undefined ? "" : `<form method="post" action="/settings/lead/identity" class="card lead-identity" data-lead-identity>` +
|
|
17609
|
+
`<input type="hidden" name="csrf" value="${escape(facts.csrf)}">` +
|
|
17610
|
+
`<label>Name your lead<input name="name" value="${escape(data.identity.name)}" maxlength="${LEAD_NAME_MAX}" autocomplete="off" required></label>` +
|
|
17611
|
+
`<label>Persona<textarea name="persona" rows="4" maxlength="${LEAD_PERSONA_MAX}">${escape(data.identity.persona)}</textarea></label>` +
|
|
17612
|
+
`<button type="submit">Save</button>${data.saved === true ? ` <span class="meta" role="status">Saved.</span>` : ""}</form>`,
|
|
17613
|
+
// What the lead knows about this person: lines they confirmed in chat or wrote here. Every project, every chat.
|
|
17614
|
+
data.about === undefined ? "" : `<form method="post" action="/settings/lead/about" class="card lead-about" data-lead-about>` +
|
|
17615
|
+
`<input type="hidden" name="csrf" value="${escape(facts.csrf)}">` +
|
|
17616
|
+
`<label>What your lead knows about you<textarea name="about" rows="${Math.min(10, Math.max(4, data.about.length + 1))}" maxlength="${ABOUT_YOU_MAX_LINES * (ABOUT_YOU_LINE_MAX + 1)}" placeholder="Keep copy terse. I test changes myself.">${escape(data.about.join("\n"))}</textarea></label>` +
|
|
17617
|
+
`<p class="meta">One per line, up to ${ABOUT_YOU_MAX_LINES}.</p>` +
|
|
17618
|
+
`<button type="submit">Save</button>${data.aboutSaved === true ? ` <span class="meta" role="status">Saved.</span>` : ""}</form>`,
|
|
17619
|
+
// What the lead promised to follow up on; it reports each once, here in chat, and drops it after 7 days.
|
|
17620
|
+
(data.promises ?? []).length === 0 ? "" : `<section class="card lead-promises" data-lead-promises><h2>Promises</h2><ul class="lead-promise-list">${data.promises.map(one => `<li data-promise="${one.id}"><p>${escape(one.what)}</p><p class="meta">${escape(one.when.charAt(0).toUpperCase() + one.when.slice(1))} · <span class="nowrap">until ${escape(new Date(one.until).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }))}</span></p>` +
|
|
17621
|
+
`<form method="post" action="/settings/lead/promise/cancel" class="inline">${hidden}<input type="hidden" name="promise" value="${one.id}"><button type="submit" class="secondary">Cancel</button></form></li>`).join("")}</ul></section>`,
|
|
17551
17622
|
`<details class="lead-advanced" data-lead-advanced><summary>Advanced</summary>`,
|
|
17552
17623
|
leadConfigForm(config, facts),
|
|
17553
17624
|
config === null ? "" : `<form method="post" action="/chat/config" class="inline">${hidden}<input type="hidden" name="off" value="1">` +
|
|
@@ -17646,8 +17717,8 @@ function chatPage(chrome, data) {
|
|
|
17646
17717
|
parts.push(data.coordinatorProposals);
|
|
17647
17718
|
parts.push(data.focusTask === null && !data.catchUp ? `<details class="chat-fleet-context">${chatOverviewSummaryHtml(data.projects, data.fleetSnapshot?.attentionCount)}${chatFleetOverview(data.fleetSnapshot, data.projects, data.csrf, false)}</details>` : "", chatLimitsHtml({ provider: config.provider, model: config.model, turnsToday: data.turnsToday, dailyTurns: config.dailyTurns, subscription, weekly: subscription ? null : { spent: data.weeklySpent, ceiling: config.weeklyCeilingMicrousd } }));
|
|
17648
17719
|
for (const turn of data.latched) {
|
|
17649
|
-
parts.push(`<div class="problem"><strong>
|
|
17650
|
-
`<a href="/chat/ack/${turn.id}">
|
|
17720
|
+
parts.push(`<div class="problem"><strong>Chat is paused.</strong> An earlier reply stopped before its cost was known; it may have cost up to ${chatMoney(turn.reservedMicrousd)}. ` +
|
|
17721
|
+
`<a href="/chat/ack/${turn.id}">Confirm that cost</a> to turn chat back on.</div>`);
|
|
17651
17722
|
}
|
|
17652
17723
|
if (data.pending !== null) {
|
|
17653
17724
|
parts.push(chatWorkingHtml({ details: `Turn #${data.pending.id}${subscription ? " · subscription" : ` · up to ${chatMoney(data.pending.reservedMicrousd)} reserved`}` }));
|
|
@@ -18041,13 +18112,35 @@ function coordinatorProposalsSection(proposals, decisions, csrf, now, heading =
|
|
|
18041
18112
|
proposals.map(one => coordinatorProposalCard(one, csrf, now, decisions.get(typeof one.payload["decision"] === "number" ? one.payload["decision"] : -1) ?? null, returnTo)).join("") +
|
|
18042
18113
|
`</div>`);
|
|
18043
18114
|
}
|
|
18115
|
+
/** The lead's question as buttons: each option sends itself as the next message (the same POST as a typed one);
|
|
18116
|
+
* "Something else" moves to the composer. Nothing shows while a reply is running. */
|
|
18117
|
+
function mateAskHtml(ask, csrf, target, composer, open = true) {
|
|
18118
|
+
if (!open)
|
|
18119
|
+
return `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${escape(ask.question)}</strong></p></div>`;
|
|
18120
|
+
const hidden = `<input type="hidden" name="csrf" value="${escape(csrf)}">` +
|
|
18121
|
+
(target.task !== null ? `<input type="hidden" name="task" value="${escape(target.task)}">` : target.project !== null ? `<input type="hidden" name="project" value="${escape(target.project)}">` : "");
|
|
18122
|
+
return `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${escape(ask.question)}</strong></p>` +
|
|
18123
|
+
`<div class="so-owner-ask-options" role="group" aria-label="Answer options">` +
|
|
18124
|
+
ask.options.map(option => `<form method="post" action="/chat" class="inline">${hidden}<button type="submit" name="message" value="${escape(option)}" class="so-suggestion quiet">${escape(option)}</button></form>`).join("") +
|
|
18125
|
+
`<label for="${escape(composer)}" class="so-suggestion so-owner-ask-other">${escape(MATE_ASK_OTHER)}</label></div></div>`;
|
|
18126
|
+
}
|
|
18044
18127
|
/** The thread's messages as the React conversation renders them, each
|
|
18045
18128
|
* with the proposal cards its turn produced; `back` is where a card's
|
|
18046
18129
|
* confirm or dismiss returns. */
|
|
18047
|
-
function mateBrowserMessages(rows, csrf, back) {
|
|
18130
|
+
function mateBrowserMessages(rows, csrf, back, target = { task: null, project: null }) {
|
|
18131
|
+
const open = rows.pending === null ? rows.ask?.turn ?? null : null;
|
|
18132
|
+
const asked = (turn) => {
|
|
18133
|
+
const ask = turn === null ? undefined : rows.asks?.get(turn);
|
|
18134
|
+
return ask === undefined ? "" : mateAskHtml(ask, csrf, target, target.task === null ? "lead-message" : "task-message", ask.turn === open);
|
|
18135
|
+
};
|
|
18136
|
+
// The owner's message each reply answers: when it asked for ids, the reply keeps them.
|
|
18137
|
+
const askedBy = new Map();
|
|
18138
|
+
rows.messages.reduce((last, message) => { if (message.role === 'operator')
|
|
18139
|
+
return message.text; if (last !== undefined)
|
|
18140
|
+
askedBy.set(message.id, last); return last; }, undefined);
|
|
18048
18141
|
return rows.messages.map(message => ({
|
|
18049
18142
|
id: message.id, role: message.role, text: message.text,
|
|
18050
|
-
html: message.role === 'operator' ? `<p>${escape(message.text)}</p>` : renderChatText(message.text),
|
|
18143
|
+
html: message.role === 'operator' ? `<p>${escape(message.text)}</p>` : renderChatText(message.text, askedBy.get(message.id)) + asked(message.turn),
|
|
18051
18144
|
activity: message.activity, createdAt: message.createdAt,
|
|
18052
18145
|
...(() => {
|
|
18053
18146
|
const parts = message.turn === null ? [] : rows.proposals.filter(one => one.turn === message.turn)
|
|
@@ -18070,6 +18163,7 @@ function mateChatVersion(rows) {
|
|
|
18070
18163
|
rows.coordinatorProposals.map(one => [one.id, one.state, one.outcome === null ? null : JSON.stringify(one.outcome)]),
|
|
18071
18164
|
[...rows.decisions.values()].map(one => [one.id, one.state, one.choice ?? null]),
|
|
18072
18165
|
rows.pending === null ? null : [rows.pending.id, rows.pending.steps],
|
|
18166
|
+
rows.ask?.turn ?? null,
|
|
18073
18167
|
rows.recent[0] === undefined ? null : [rows.recent[0].id, rows.recent[0].state, rows.recent[0].failureReason],
|
|
18074
18168
|
focus === null
|
|
18075
18169
|
? null
|
|
@@ -18117,14 +18211,20 @@ function mateThreadHtml(data) {
|
|
|
18117
18211
|
parts.push(`<div class="chat-empty" data-key="empty"><strong>${data.focusTask === null ? "What do you want to get done?" : "What do you want to understand or change?"}</strong>` +
|
|
18118
18212
|
`<p class="meta">${data.focusTask === null ? "Describe a task or ask about your projects." : "Ask about progress, review results, or adjust the plan."}</p></div>`);
|
|
18119
18213
|
}
|
|
18214
|
+
let asked;
|
|
18120
18215
|
for (const message of data.messages) {
|
|
18121
18216
|
if (message.role === "operator") {
|
|
18217
|
+
asked = message.text;
|
|
18122
18218
|
parts.push(`<div class="msg op" data-message-role="operator" data-key="m${message.id}"><p style="white-space:pre-wrap">${escape(message.text)}</p></div>`);
|
|
18123
18219
|
continue;
|
|
18124
18220
|
}
|
|
18125
18221
|
const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
|
|
18126
18222
|
parts.push(`<div class="msg mate" data-message-role="assistant" data-key="m${message.id}"${message.id === latestReply ? ' id="latest"' : ""}>` +
|
|
18127
|
-
renderChatText(message.text) +
|
|
18223
|
+
renderChatText(message.text, asked) +
|
|
18224
|
+
(() => {
|
|
18225
|
+
const ask = message.turn === null ? undefined : data.asks?.get(message.turn);
|
|
18226
|
+
return ask === undefined ? "" : mateAskHtml(ask, data.csrf, { task: data.focusTask?.id ?? null, project: data.chatProject ?? null }, "chat-message", data.pending === null && data.ask?.turn === ask.turn);
|
|
18227
|
+
})() +
|
|
18128
18228
|
cards.map(one => mateProposalCard(one, data.csrf, inert, data.decisions.get(typeof one.payload["decision"] === "number" ? one.payload["decision"] : -1) ?? null, data.focusTask === null ? null : returnTo)).join("") +
|
|
18129
18229
|
`<div class="chat-message-foot">${chatActivity(message.activity)}<time datetime="${escape(message.createdAt)}">${escape(relativeAge(message.createdAt, data.now))}</time></div>` +
|
|
18130
18230
|
`</div>`);
|
|
@@ -18180,17 +18280,17 @@ function matePage(chrome, data) {
|
|
|
18180
18280
|
if (data.problem !== null)
|
|
18181
18281
|
conversation.push(`<div class="problem">${escape(data.problem)}</div>`);
|
|
18182
18282
|
for (const turn of data.latched) {
|
|
18183
|
-
conversation.push(`<div class="problem"><strong>
|
|
18184
|
-
`<a href="/chat/ack/${turn.id}">
|
|
18283
|
+
conversation.push(`<div class="problem"><strong>Chat is paused.</strong> An earlier reply stopped before its cost was known; it may have cost up to ${chatMoney(turn.reservedMicrousd)}. ` +
|
|
18284
|
+
`<a href="/chat/ack/${turn.id}">Confirm that cost</a> to turn chat back on.</div>`);
|
|
18185
18285
|
}
|
|
18186
18286
|
const lastMessage = data.messages.at(-1);
|
|
18187
18287
|
const latestReply = data.pending === null && lastMessage?.role === "assistant" ? lastMessage.id : null;
|
|
18188
|
-
conversation.push(mateThreadHtml({ ...data, problem: null }));
|
|
18288
|
+
conversation.push(mateThreadHtml({ ...data, problem: null, chatProject }));
|
|
18189
18289
|
conversation.push(
|
|
18190
18290
|
// The New update action (package 2): hidden until a live update lands
|
|
18191
18291
|
// while the reader is above the latest message; a real button, so the
|
|
18192
18292
|
// keyboard reaches it. Only this act moves the reader.
|
|
18193
|
-
`<div class="chat-new-update-holder"><button type="button" class="chat-new-update" id="chat-new-update" hidden>New update ↓</button></div>`, `<form method="post" action="/chat" class="card composer" id="${latestReply === null && data.pending === null ? "latest" : "chat-composer"}" aria-label="message the mate" data-chat-session="${data.session.id}" data-chat-task="${escape(data.focusTask?.id ?? "")}" data-chat-user="${escape(data.session.approver)}" data-chat-busy="${data.pending === null ? "0" : "1"}" data-chat-version="${mateChatVersion(data)}" data-chat-approval="${escape(data.focusTask?.approval?.digest ?? "")}">`, `<input type="hidden" name="csrf" value="${escape(data.csrf)}">`, `<input type="hidden" name="request" value="${randomBytes(16).toString("hex")}"><input type="hidden" name="request-session" value="${data.session.id}">`, data.focusTask === null ? "" : `<input type="hidden" name="task" value="${escape(data.focusTask.id)}">`, chatProject === null ? "" : `<input type="hidden" name="project" value="${escape(chatProject)}">`, data.resultRunId == null ? "" : `<input type="hidden" name="result" value="${data.resultRunId}">`, `<label>Message<textarea name="message" rows="1" maxlength="${MATE_MESSAGE_MAX_CHARS}" placeholder="${data.focusTask !== null ? "Ask about this task…" : chatProject !== null ? `Ask about ${escape(projectName(chatProject))}…` : "Describe what you want done…"}"></textarea></label>`, `<button type="submit" aria-label="${data.pending === null ? "send message" : "wait for the current reply before sending"}"${data.pending === null ? "" : " disabled"}>Send</button>`, `</form>`,
|
|
18293
|
+
`<div class="chat-new-update-holder"><button type="button" class="chat-new-update" id="chat-new-update" hidden>New update ↓</button></div>`, `<form method="post" action="/chat" class="card composer" id="${latestReply === null && data.pending === null ? "latest" : "chat-composer"}" aria-label="message the mate" data-chat-session="${data.session.id}" data-chat-task="${escape(data.focusTask?.id ?? "")}" data-chat-user="${escape(data.session.approver)}" data-chat-busy="${data.pending === null ? "0" : "1"}" data-chat-version="${mateChatVersion(data)}" data-chat-approval="${escape(data.focusTask?.approval?.digest ?? "")}">`, `<input type="hidden" name="csrf" value="${escape(data.csrf)}">`, `<input type="hidden" name="request" value="${randomBytes(16).toString("hex")}"><input type="hidden" name="request-session" value="${data.session.id}">`, data.focusTask === null ? "" : `<input type="hidden" name="task" value="${escape(data.focusTask.id)}">`, chatProject === null ? "" : `<input type="hidden" name="project" value="${escape(chatProject)}">`, data.resultRunId == null ? "" : `<input type="hidden" name="result" value="${data.resultRunId}">`, `<label>Message<textarea id="chat-message" name="message" rows="1" maxlength="${MATE_MESSAGE_MAX_CHARS}" placeholder="${data.focusTask !== null ? "Ask about this task…" : chatProject !== null ? `Ask about ${escape(projectName(chatProject))}…` : "Describe what you want done…"}"></textarea></label>`, `<button type="submit" aria-label="${data.pending === null ? "send message" : "wait for the current reply before sending"}"${data.pending === null ? "" : " disabled"}>Send</button>`, `</form>`,
|
|
18194
18294
|
// Concise pass (2026-09-13): the status line speaks only when there is
|
|
18195
18295
|
// a state to report — a reply in progress here, the connection from
|
|
18196
18296
|
// the continuity script — and the composer carries no second intro.
|
|
@@ -18202,7 +18302,7 @@ function matePage(chrome, data) {
|
|
|
18202
18302
|
workspace: {
|
|
18203
18303
|
conversation: {
|
|
18204
18304
|
sessionId: data.session.id, user: data.session.approver, version: mateChatVersion(data),
|
|
18205
|
-
messages: mateBrowserMessages(data, data.csrf, data.focusTask === null && chatProject === null ? null : returnTo),
|
|
18305
|
+
messages: mateBrowserMessages(data, data.csrf, data.focusTask === null && chatProject === null ? null : returnTo, { task: data.focusTask?.id ?? null, project: chatProject }),
|
|
18206
18306
|
pendingTurnId: data.pending?.id ?? null, requestId: randomBytes(16).toString('hex'), maxChars: MATE_MESSAGE_MAX_CHARS,
|
|
18207
18307
|
taskId: data.focusTask?.id ?? null, resultRunId: data.resultRunId ?? null, project: chatProject,
|
|
18208
18308
|
},
|
|
@@ -19529,6 +19629,15 @@ function accentHead() {
|
|
|
19529
19629
|
}
|
|
19530
19630
|
const requestContext = new AsyncLocalStorage();
|
|
19531
19631
|
/** A same-site path or "/": never a scheme, a host, or a protocol-relative road. */
|
|
19632
|
+
/** The words a result page shows for a refusal its own form led to, by the fixed code a redirect carries. */
|
|
19633
|
+
const ACCEPT_ANYWAY_NEEDS_REASON = "Accepting anyway needs a reason.";
|
|
19634
|
+
const RESULT_REFUSALS = { reason: ACCEPT_ANYWAY_NEEDS_REASON };
|
|
19635
|
+
/** A same-site page address with a refusal code added, before any fragment. */
|
|
19636
|
+
function withRefusal(href, code) {
|
|
19637
|
+
const at = href.indexOf("#");
|
|
19638
|
+
const [path, hash] = at === -1 ? [href, ""] : [href.slice(0, at), href.slice(at)];
|
|
19639
|
+
return `${path}${path.includes("?") ? "&" : "?"}refused=${code}${hash}`;
|
|
19640
|
+
}
|
|
19532
19641
|
function safeReturn(raw) {
|
|
19533
19642
|
if (raw === null || raw === undefined)
|
|
19534
19643
|
return "/";
|
|
@@ -21779,12 +21888,13 @@ function reviewCockpitPage(chrome, data) {
|
|
|
21779
21888
|
capped: queue.length >= data.queueCap ? data.queueCap : null,
|
|
21780
21889
|
missing: data.missing === null ? null : `No completed task ${data.missing} is in view here — it may not be finished, or it is outside this console's projects.`,
|
|
21781
21890
|
beyond: data.beyondQueue && selected !== null,
|
|
21782
|
-
selected: detailParts === null ? null : detailParts.selected,
|
|
21891
|
+
selected: detailParts === null ? null : data.refusal == null ? detailParts.selected : { ...detailParts.selected, problem: data.refusal },
|
|
21783
21892
|
};
|
|
21784
21893
|
return screen("review", [
|
|
21785
21894
|
selected === null ? `<h1>Results</h1>` : "",
|
|
21786
21895
|
missingNote,
|
|
21787
21896
|
beyondNote,
|
|
21897
|
+
data.refusal == null || selected === null ? "" : `<p class="problem" data-result-refusal>${escape(data.refusal)}</p>`,
|
|
21788
21898
|
`<div class="cockpit">${queuePane}${detail}</div>`,
|
|
21789
21899
|
].join("\n"), {
|
|
21790
21900
|
chrome,
|
|
@@ -22011,14 +22121,11 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
22011
22121
|
},
|
|
22012
22122
|
};
|
|
22013
22123
|
}
|
|
22014
|
-
/** What Accept and finish owes before it completes, as the form asks for it: the
|
|
22015
|
-
*
|
|
22124
|
+
/** What Accept and finish owes before it completes, as the form asks for it: the server's own rule
|
|
22125
|
+
* (owedAcceptance), so the form asks for exactly what the completion requires. */
|
|
22016
22126
|
function owedAcceptanceOf(receipt) {
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
if (personCheckPending(receipt))
|
|
22020
|
-
return { note: null };
|
|
22021
|
-
return cantAcceptYetOf(receipt.proof?.verdict ?? null, receipt.proof?.reasons ?? [], false) === ACCEPT_NEEDS_REASON ? { note: "Why is this safe to accept?" } : null;
|
|
22127
|
+
const owed = owedAcceptance(receipt);
|
|
22128
|
+
return owed === null ? null : owed === "person-check" ? { note: null } : { note: "Why is this safe to accept?" };
|
|
22022
22129
|
}
|
|
22023
22130
|
function completionForm(taskId, runId, digest, csrf, pullRequestTo = null, accept = null) {
|
|
22024
22131
|
// Accept and finish, in one request: with an acceptance owed (the person's own checks, or an exception and
|
package/dist/slack-chat.d.ts
CHANGED
|
@@ -25,4 +25,6 @@ export declare const applySlackAction: (options: SlackChatOptions, event: SlackE
|
|
|
25
25
|
export declare const planSlackNotifications: (options: SlackChatOptions) => Promise<void>;
|
|
26
26
|
export declare const planSlackRooms: (options: SlackChatOptions) => Promise<void>;
|
|
27
27
|
export declare function slackBlocks(text: string, buttons?: Record<string, unknown>[]): Record<string, unknown>[];
|
|
28
|
+
/** The lead's reply as mrkdwn sections: no header block, so its first line stays an ordinary line. */
|
|
29
|
+
export declare function slackVoiceBlocks(mrkdwn: string, buttons?: Record<string, unknown>[]): Record<string, unknown>[];
|
|
28
30
|
export declare function deliverSlackPart(options: SlackChatOptions): Promise<boolean>;
|