toolroll 0.9.16 → 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 +17 -0
- package/dist/assignment-brief.js +11 -4
- package/dist/chat-actions.d.ts +7 -0
- package/dist/chat-actions.js +40 -4
- package/dist/chat-ask.js +29 -15
- package/dist/chat-channel.js +3 -1
- package/dist/chat-delivery-state.d.ts +2 -0
- package/dist/chat-delivery.d.ts +3 -0
- package/dist/chat-delivery.js +31 -16
- 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 +18 -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.js +13 -7
- package/dist/lead-context.d.ts +5 -3
- package/dist/lead-context.js +29 -7
- package/dist/lead-follow.d.ts +1 -1
- package/dist/lead-follow.js +3 -2
- package/dist/lead-identity.js +1 -1
- package/dist/lead-people.d.ts +55 -0
- package/dist/lead-people.js +128 -0
- package/dist/lead-voice.d.ts +3 -2
- package/dist/lead-voice.js +4 -3
- package/dist/mate-cli.d.ts +3 -0
- package/dist/mate-cli.js +15 -14
- package/dist/mate-contract.d.ts +1 -1
- package/dist/mate-contract.js +6 -4
- package/dist/mate-progress.js +2 -0
- package/dist/mate-tools.d.ts +8 -0
- package/dist/mate-tools.js +112 -6
- package/dist/mate.d.ts +26 -1
- package/dist/mate.js +89 -25
- package/dist/operate.js +2 -0
- package/dist/phase-routing.d.ts +1 -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/serve.js +53 -28
- package/dist/slack-chat.d.ts +2 -0
- package/dist/slack-chat.js +20 -3
- package/dist/store.d.ts +12 -3
- package/dist/store.js +44 -9
- package/dist/team-runtime.js +1 -1
- package/dist/teams-chat.js +12 -1
- package/dist/telegram-mate.d.ts +2 -0
- package/dist/telegram-mate.js +27 -11
- package/dist/telegram-team.js +6 -3
- package/dist/telegram.js +1 -1
- package/dist/test-temp.js +1 -1
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
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";
|
|
2
3
|
import { withActor } from "./actor.js";
|
|
3
4
|
import { repositoryContext, repositoryContextRead } from './repository-context.js';
|
|
4
5
|
import { ADD_TESTS_ACTION, followUpChecksOf, requestFollowUpChecks, fileAddTestsTask, resultCheckLevel } from './result-follow-ups.js';
|
|
@@ -20,7 +21,7 @@ import { ChatState } from "./chat-delivery-state.js";
|
|
|
20
21
|
import { slackSettingsHtml } from "./slack-settings.js";
|
|
21
22
|
import { checkSlackCredentials, loadSlackCredentials, saveSlackCredentials, clearSlackCredentials, SLACK_MANIFEST, SlackError } from "./slack-api.js";
|
|
22
23
|
import { SlackState } from "./slack-state.js";
|
|
23
|
-
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';
|
|
24
25
|
import { changeSkills, githubSkill, importSkill, readSkillsSnapshot, reviseSkillTest, skillTestResult, skillsView, testSkill } from "./project-skills.js";
|
|
25
26
|
import { skillsHtml, skillsScript, skillsSnapshotHtml, skillTestFeedbackHtml, SKILLS_CSS } from "./skills-ui.js";
|
|
26
27
|
import { toolsHtml, TOOLS_CSS } from "./tools-ui.js";
|
|
@@ -212,7 +213,7 @@ import { authorizedProject, canonicalProject, isGitRepo, projectName, sameRepo,
|
|
|
212
213
|
import { tally, spendLine, runCostWords } from "./summary.js";
|
|
213
214
|
import { classify, holdOwnerWords, attentionCardForUnverifiedDone } from "./board.js";
|
|
214
215
|
import { approveRoutine, describeSchedule, fireRoutine, parseSchedule, refreshRoutineAgents, routineAgentsState, routineDigestOf, validateRoutineTerms, ROUTINE_NAME } from "./routine.js";
|
|
215
|
-
import { effectivePrimary, isMessagingChannel, savePrimary } from "./webhooks.js";
|
|
216
|
+
import { effectivePrimary, isMessagingChannel, loadConsoleUrl, savePrimary } from "./webhooks.js";
|
|
216
217
|
import { resolvePhaseAgent, resolveRoutineAuthority, INSTALLATION_SCOPE, routeOfTask, agentChoicesFor } from "./agentconfig.js";
|
|
217
218
|
import { isRiskLevel, projectRoute, riskTitle, riskConsequence, chosenWords, agentsSummary, postureWords, RISK_CHOICES, RISK_LEVELS, PHASES as ROUTE_PHASES } from "./phase-routing.js";
|
|
218
219
|
import { ALL_CREDENTIAL_ENV, isProviderId, reportsCost, PROVIDER_IDS, validModelId, validateSpec } from "./provider.js";
|
|
@@ -260,6 +261,7 @@ import { handleTeamsHttp } from "./teams.js";
|
|
|
260
261
|
import { checkTeamsCredentials, clearTeamsCredentials, loadTeamsCredentials, saveTeamsCredentials, TeamsError } from "./teams-api.js";
|
|
261
262
|
import { verifyApproverByPassword, verifyApproverStanding } from "./principal.js";
|
|
262
263
|
import { runMateTurn, MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
264
|
+
import { replyHtmlInline, shapeReply } from "./reply-shape.js";
|
|
263
265
|
import { confirmCoordinatorProposal, confirmMateProposal, dismissCoordinatorProposal, dismissMateProposal } from "./mate-doors.js";
|
|
264
266
|
import { envValue } from "./names.js";
|
|
265
267
|
import { cachedRelease, isNewer, runnerVersions, setUpdateChecks, updateChecksOff } from "./releases.js";
|
|
@@ -1529,6 +1531,8 @@ export function createDecisionServer(options) {
|
|
|
1529
1531
|
actor: who.name,
|
|
1530
1532
|
csrf: who.via === "cookie" ? who.session.csrf : "",
|
|
1531
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)],
|
|
1532
1536
|
// Every signed-in page shares the workspace shell; pages showing a one-time secret opt out (forceSensitive).
|
|
1533
1537
|
browser: who.via === 'cookie',
|
|
1534
1538
|
// v100: signed in with the identity provider: its label, and whether it checked them recently enough to stand in for a password.
|
|
@@ -1634,7 +1638,7 @@ export function createDecisionServer(options) {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
if (admitted && proposal) {
|
|
1636
1640
|
const taskId = typeof action?.request['task'] === 'string' ? action.request['task'] : typeof proposal.payload['task'] === 'string' ? proposal.payload['task'] : null;
|
|
1637
|
-
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);
|
|
1638
1642
|
return { repo, taskId, runId: typeof action?.request['run'] === 'number' ? action.request['run'] : null, action: action?.operation ?? `chat ${proposal.kind}` };
|
|
1639
1643
|
}
|
|
1640
1644
|
}
|
|
@@ -1699,6 +1703,9 @@ export function createDecisionServer(options) {
|
|
|
1699
1703
|
}
|
|
1700
1704
|
if (action && proposal && store.getMateThread(proposal.thread)?.approver === who.name && visible(action.repo) && store.accountCanAccess(who.name, action.repo))
|
|
1701
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;
|
|
1702
1709
|
refuse(response, who, 404, 'No such action in your projects.', '/projects');
|
|
1703
1710
|
return false;
|
|
1704
1711
|
}
|
|
@@ -4138,7 +4145,8 @@ export function createDecisionServer(options) {
|
|
|
4138
4145
|
const signedIn = localSignIn?.states.claude === "connected" ? "Claude Code" : localSignIn?.states.codex === "connected" ? "Codex" : null;
|
|
4139
4146
|
const promises = openCommitments(store, who.name, 50).map(one => ({ id: one.id, what: one.what, when: conditionWords(store, one.condition), until: one.expiresAt }));
|
|
4140
4147
|
return sendScreen(response, 200, screen("Lead", leadSettingsHtml({ config, facts, words: leadWords(), signedIn, command: agentSignInCommand(),
|
|
4141
|
-
said: url.searchParams.get("said"), saved: url.searchParams.get("saved") === "1", identity: leadIdentityOf(store, who.name), promises
|
|
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") }));
|
|
4142
4150
|
}
|
|
4143
4151
|
if (url.pathname === "/settings/telegram") {
|
|
4144
4152
|
// Any approver pairs their OWN phone here; the bot token stays on /settings.
|
|
@@ -4818,20 +4826,20 @@ export function createDecisionServer(options) {
|
|
|
4818
4826
|
if (result.problem.startsWith("status-")) {
|
|
4819
4827
|
// The provider ANSWERED with an error: nothing billed.
|
|
4820
4828
|
finish({ state: "failed", failureReason: "provider-error", settledMicrousd: 0 });
|
|
4821
|
-
tell(null, "
|
|
4829
|
+
tell(null, "The chat provider turned the request down. Nothing was kept or charged. Try again in a minute.");
|
|
4822
4830
|
}
|
|
4823
4831
|
else if (result.problem === "timeout") {
|
|
4824
4832
|
finish({ state: "failed", failureReason: "timeout", settledMicrousd: null, unknownSpend: true });
|
|
4825
|
-
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.");
|
|
4826
4834
|
}
|
|
4827
4835
|
else if (result.problem === "network") {
|
|
4828
4836
|
finish({ state: "failed", failureReason: "provider-error", settledMicrousd: null, unknownSpend: true });
|
|
4829
|
-
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.");
|
|
4830
4838
|
}
|
|
4831
4839
|
else {
|
|
4832
4840
|
// 200 with an unusable wrapper: billed, amount unproven.
|
|
4833
4841
|
finish({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
4834
|
-
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.");
|
|
4835
4843
|
}
|
|
4836
4844
|
return;
|
|
4837
4845
|
}
|
|
@@ -9231,6 +9239,16 @@ export function createDecisionServer(options) {
|
|
|
9231
9239
|
store.setLeadConfig(who.name, checked.identity.name, checked.identity.persona, now);
|
|
9232
9240
|
return redirect(response, "/settings/lead?saved=1");
|
|
9233
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
|
+
}
|
|
9234
9252
|
// Settings → Lead: stop the lead following up on one of your promises.
|
|
9235
9253
|
if (url.pathname === "/settings/lead/promise/cancel") {
|
|
9236
9254
|
if (who.via !== "cookie" || who.role !== "approver")
|
|
@@ -17477,13 +17495,13 @@ function chatHeading(copy, projectCount, showProjectToggle = true) {
|
|
|
17477
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>`
|
|
17478
17496
|
: "") + `</div></div>`);
|
|
17479
17497
|
}
|
|
17480
|
-
/** A deliberately small rich-text grammar for model copy.
|
|
17481
|
-
*
|
|
17482
|
-
*
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
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 }) });
|
|
17487
17505
|
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
17488
17506
|
const out = [];
|
|
17489
17507
|
let paragraph = [];
|
|
@@ -17501,15 +17519,9 @@ function renderChatText(text) {
|
|
|
17501
17519
|
list = null;
|
|
17502
17520
|
};
|
|
17503
17521
|
for (const line of lines) {
|
|
17504
|
-
const heading = /^(?:#{1,3}\s+)(.+)$/.exec(line);
|
|
17505
17522
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
17506
17523
|
const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
|
|
17507
|
-
if (
|
|
17508
|
-
flushParagraph();
|
|
17509
|
-
closeList();
|
|
17510
|
-
out.push(`<h3>${inline(heading[1] ?? "")}</h3>`);
|
|
17511
|
-
}
|
|
17512
|
-
else if (bullet !== null || numbered !== null) {
|
|
17524
|
+
if (bullet !== null || numbered !== null) {
|
|
17513
17525
|
flushParagraph();
|
|
17514
17526
|
const wanted = bullet !== null ? "ul" : "ol";
|
|
17515
17527
|
if (list !== wanted) {
|
|
@@ -17598,6 +17610,12 @@ function leadSettingsHtml(data) {
|
|
|
17598
17610
|
`<label>Name your lead<input name="name" value="${escape(data.identity.name)}" maxlength="${LEAD_NAME_MAX}" autocomplete="off" required></label>` +
|
|
17599
17611
|
`<label>Persona<textarea name="persona" rows="4" maxlength="${LEAD_PERSONA_MAX}">${escape(data.identity.persona)}</textarea></label>` +
|
|
17600
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>`,
|
|
17601
17619
|
// What the lead promised to follow up on; it reports each once, here in chat, and drops it after 7 days.
|
|
17602
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>` +
|
|
17603
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>`,
|
|
@@ -17699,8 +17717,8 @@ function chatPage(chrome, data) {
|
|
|
17699
17717
|
parts.push(data.coordinatorProposals);
|
|
17700
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 } }));
|
|
17701
17719
|
for (const turn of data.latched) {
|
|
17702
|
-
parts.push(`<div class="problem"><strong>
|
|
17703
|
-
`<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>`);
|
|
17704
17722
|
}
|
|
17705
17723
|
if (data.pending !== null) {
|
|
17706
17724
|
parts.push(chatWorkingHtml({ details: `Turn #${data.pending.id}${subscription ? " · subscription" : ` · up to ${chatMoney(data.pending.reservedMicrousd)} reserved`}` }));
|
|
@@ -18115,9 +18133,14 @@ function mateBrowserMessages(rows, csrf, back, target = { task: null, project: n
|
|
|
18115
18133
|
const ask = turn === null ? undefined : rows.asks?.get(turn);
|
|
18116
18134
|
return ask === undefined ? "" : mateAskHtml(ask, csrf, target, target.task === null ? "lead-message" : "task-message", ask.turn === open);
|
|
18117
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);
|
|
18118
18141
|
return rows.messages.map(message => ({
|
|
18119
18142
|
id: message.id, role: message.role, text: message.text,
|
|
18120
|
-
html: message.role === 'operator' ? `<p>${escape(message.text)}</p>` : renderChatText(message.text) + asked(message.turn),
|
|
18143
|
+
html: message.role === 'operator' ? `<p>${escape(message.text)}</p>` : renderChatText(message.text, askedBy.get(message.id)) + asked(message.turn),
|
|
18121
18144
|
activity: message.activity, createdAt: message.createdAt,
|
|
18122
18145
|
...(() => {
|
|
18123
18146
|
const parts = message.turn === null ? [] : rows.proposals.filter(one => one.turn === message.turn)
|
|
@@ -18188,14 +18211,16 @@ function mateThreadHtml(data) {
|
|
|
18188
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>` +
|
|
18189
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>`);
|
|
18190
18213
|
}
|
|
18214
|
+
let asked;
|
|
18191
18215
|
for (const message of data.messages) {
|
|
18192
18216
|
if (message.role === "operator") {
|
|
18217
|
+
asked = message.text;
|
|
18193
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>`);
|
|
18194
18219
|
continue;
|
|
18195
18220
|
}
|
|
18196
18221
|
const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
|
|
18197
18222
|
parts.push(`<div class="msg mate" data-message-role="assistant" data-key="m${message.id}"${message.id === latestReply ? ' id="latest"' : ""}>` +
|
|
18198
|
-
renderChatText(message.text) +
|
|
18223
|
+
renderChatText(message.text, asked) +
|
|
18199
18224
|
(() => {
|
|
18200
18225
|
const ask = message.turn === null ? undefined : data.asks?.get(message.turn);
|
|
18201
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);
|
|
@@ -18255,8 +18280,8 @@ function matePage(chrome, data) {
|
|
|
18255
18280
|
if (data.problem !== null)
|
|
18256
18281
|
conversation.push(`<div class="problem">${escape(data.problem)}</div>`);
|
|
18257
18282
|
for (const turn of data.latched) {
|
|
18258
|
-
conversation.push(`<div class="problem"><strong>
|
|
18259
|
-
`<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>`);
|
|
18260
18285
|
}
|
|
18261
18286
|
const lastMessage = data.messages.at(-1);
|
|
18262
18287
|
const latestReply = data.pending === null && lastMessage?.role === "assistant" ? lastMessage.id : null;
|
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>;
|
package/dist/slack-chat.js
CHANGED
|
@@ -7,6 +7,7 @@ import { processChatEvent, applyChatAction, planChatNotifications, channelAccess
|
|
|
7
7
|
/** Slack is a transport for the same saved assistant and confirmation doors. */
|
|
8
8
|
import { proposalPreview, proposalLink, proposalOutcomeText, armedCardText, armedYesLabel, } from "./chat-channel.js";
|
|
9
9
|
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
10
|
+
import { renderReply } from "./reply-shape.js";
|
|
10
11
|
import {} from "./mate-doors.js";
|
|
11
12
|
import { SlackState, slackHash, } from "./slack-state.js";
|
|
12
13
|
import { object, slackId, slackTs, slackMember, SlackError, uploadSlackBytes, } from "./slack-api.js";
|
|
@@ -131,6 +132,10 @@ const delivery = (options) => ({
|
|
|
131
132
|
state: new SlackState(options.store),
|
|
132
133
|
label: "Slack",
|
|
133
134
|
member: (member, channel) => slackMember(options.api, options.identity, member, channel),
|
|
135
|
+
// A 👍 on the owner's message where the app has reactions:write; Slack shows bots no typing indicator.
|
|
136
|
+
warm: (event) => ({
|
|
137
|
+
react: () => options.api("reactions.add", { channel: event.channel, timestamp: event.ts, name: "thumbsup" }),
|
|
138
|
+
}),
|
|
134
139
|
});
|
|
135
140
|
export const processSlackEvent = (options) => processChatEvent(delivery(options));
|
|
136
141
|
export const applySlackAction = (options, event, binding, repos) => applyChatAction(delivery(options), event, binding, repos);
|
|
@@ -156,6 +161,16 @@ export function slackBlocks(text, buttons = []) {
|
|
|
156
161
|
blocks.push({ type: "actions", elements: buttons });
|
|
157
162
|
return blocks;
|
|
158
163
|
}
|
|
164
|
+
/** The lead's reply as mrkdwn sections: no header block, so its first line stays an ordinary line. */
|
|
165
|
+
export function slackVoiceBlocks(mrkdwn, buttons = []) {
|
|
166
|
+
const blocks = split(mrkdwn).map((part) => ({
|
|
167
|
+
type: "section",
|
|
168
|
+
text: { type: "mrkdwn", text: part },
|
|
169
|
+
}));
|
|
170
|
+
if (buttons.length)
|
|
171
|
+
blocks.push({ type: "actions", elements: buttons });
|
|
172
|
+
return blocks;
|
|
173
|
+
}
|
|
159
174
|
function linkButton(origin, link) {
|
|
160
175
|
if (!origin ||
|
|
161
176
|
!link ||
|
|
@@ -374,14 +389,16 @@ export async function deliverSlackPart(options) {
|
|
|
374
389
|
...(content.also ?? []).flatMap((one, index) => linkButton(options.origin(), one).map(button => ({ ...button, action_id: `toolroll_link_${index + 2}` }))),
|
|
375
390
|
];
|
|
376
391
|
const target = content.edit ?? row.message;
|
|
392
|
+
// The lead's own reply goes out in Slack's mrkdwn (bold anchors, labelled links); everything else stays plain.
|
|
393
|
+
const voiced = content.voice === true && !content.proposal && !content.image ? renderReply(text, "slack") : null;
|
|
377
394
|
const args = {
|
|
378
395
|
channel: destination,
|
|
379
|
-
text: text
|
|
396
|
+
text: voiced ?? text
|
|
380
397
|
.replaceAll("&", "&")
|
|
381
398
|
.replaceAll("<", "<")
|
|
382
399
|
.replaceAll(">", ">"),
|
|
383
|
-
blocks: slackBlocks(text, buttons),
|
|
384
|
-
mrkdwn:
|
|
400
|
+
blocks: voiced === null ? slackBlocks(text, buttons) : slackVoiceBlocks(voiced, buttons),
|
|
401
|
+
mrkdwn: voiced !== null,
|
|
385
402
|
parse: "none",
|
|
386
403
|
unfurl_links: false,
|
|
387
404
|
unfurl_media: false,
|
package/dist/store.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export type MateAsk = {
|
|
|
57
57
|
export declare const MATE_ASK_OTHER = "Something else";
|
|
58
58
|
/** How long the question's buttons work, on every channel. */
|
|
59
59
|
export declare const MATE_ASK_TTL_MS: number;
|
|
60
|
+
/** What a turn the lead starts itself for automatic crew updates says in the owner's place (lead-follow.ts): never their answer. */
|
|
61
|
+
export declare const LEAD_FOLLOW_MESSAGE = "Automatic crew update: inspect the saved results or decisions and tell me what needs attention. Do not rerun work.";
|
|
60
62
|
/** Settings → Integrations (no version bump): each integration's last check. `outcome` is ok, failed, or absent (the check found
|
|
61
63
|
* nothing set up, such as no `gh` sign-in). `ok_at` and `error`/`error_at` keep the last success and the last
|
|
62
64
|
* failure across checks, so a row can say both. `account` is a display name only, never a credential. */
|
|
@@ -6702,8 +6704,9 @@ export declare class Store {
|
|
|
6702
6704
|
dropMateAsk(turn: number): void;
|
|
6703
6705
|
/** The question an ANSWERED turn asked; null when it asked none or did not answer. */
|
|
6704
6706
|
mateAsk(turn: number): MateAsk | null;
|
|
6705
|
-
/** What a tap on the question finds: open; answered (
|
|
6706
|
-
*
|
|
6707
|
+
/** What a tap on the question finds: open; answered (its owner has written in its thread since the reply that asked
|
|
6708
|
+
* it, typed or tapped; a turn the lead started itself, or another person's message, is no answer); or expired (older
|
|
6709
|
+
* than MATE_ASK_TTL_MS, or the question or the reply that asked it is gone, as after a retention purge). */
|
|
6707
6710
|
mateAskState(turn: number, now: Date): {
|
|
6708
6711
|
state: "open";
|
|
6709
6712
|
ask: MateAsk;
|
|
@@ -7501,13 +7504,19 @@ export declare class Store {
|
|
|
7501
7504
|
}[];
|
|
7502
7505
|
/** Claim one local day's digest for one person: true once, false if it was already considered. */
|
|
7503
7506
|
claimEveningDigestDay(account: string, day: string): boolean;
|
|
7504
|
-
/** What this person named their lead and its persona, or null when they never saved one.
|
|
7507
|
+
/** What this person named their lead and its persona, or null when they never saved one. An empty name or persona
|
|
7508
|
+
* (a row saved only for what the lead knows about them) reads as the default. */
|
|
7505
7509
|
leadConfig(account: string): {
|
|
7506
7510
|
name: string;
|
|
7507
7511
|
persona: string;
|
|
7508
7512
|
} | null;
|
|
7509
7513
|
/** Save this person's lead name and persona (checked by the caller: lead-identity.ts). */
|
|
7510
7514
|
setLeadConfig(account: string, name: string, persona: string, now: Date): void;
|
|
7515
|
+
/** What this person's lead knows about them: their confirmed lines, in order (none when they never saved any). */
|
|
7516
|
+
leadAbout(account: string): string[];
|
|
7517
|
+
/** Save what this person's lead knows about them (checked by the caller: lead-about.ts). A person who never named
|
|
7518
|
+
* their lead gets an empty name and persona, so they keep following the defaults. */
|
|
7519
|
+
setLeadAbout(account: string, lines: readonly string[], now: Date): void;
|
|
7511
7520
|
/** A lead token for one person, shown once. A new one ends their earlier ones; the ledger names it "lead for <owner>". */
|
|
7512
7521
|
mintLeadCredential(owner: string, by: string, now: Date): {
|
|
7513
7522
|
id: string;
|
package/dist/store.js
CHANGED
|
@@ -184,13 +184,15 @@ CREATE TABLE IF NOT EXISTS chat_batch_item (
|
|
|
184
184
|
);
|
|
185
185
|
`;
|
|
186
186
|
/** Settings → Lead (no version bump: additive only): what one person calls their lead and the short persona it
|
|
187
|
-
* speaks with
|
|
187
|
+
* speaks with, and what their lead knows about them (`about_json`: the confirmed lines, lead-about.ts). No row reads
|
|
188
|
+
* as the defaults (lead-identity.ts). */
|
|
188
189
|
const LEAD_CONFIG_SCHEMA = `
|
|
189
190
|
CREATE TABLE IF NOT EXISTS lead_config (
|
|
190
191
|
account TEXT PRIMARY KEY,
|
|
191
192
|
name TEXT NOT NULL,
|
|
192
193
|
persona TEXT NOT NULL,
|
|
193
|
-
updated_at TEXT NOT NULL
|
|
194
|
+
updated_at TEXT NOT NULL,
|
|
195
|
+
about_json TEXT NOT NULL DEFAULT '[]'
|
|
194
196
|
);
|
|
195
197
|
`;
|
|
196
198
|
/** Pings follow responsibility (no version bump: additive only). `lead_credential` is a lead token: an agent acting
|
|
@@ -296,6 +298,8 @@ CREATE INDEX IF NOT EXISTS mate_ask_thread ON mate_ask (thread, turn);
|
|
|
296
298
|
export const MATE_ASK_OTHER = "Something else";
|
|
297
299
|
/** How long the question's buttons work, on every channel. */
|
|
298
300
|
export const MATE_ASK_TTL_MS = 7 * 86_400_000;
|
|
301
|
+
/** What a turn the lead starts itself for automatic crew updates says in the owner's place (lead-follow.ts): never their answer. */
|
|
302
|
+
export const LEAD_FOLLOW_MESSAGE = "Automatic crew update: inspect the saved results or decisions and tell me what needs attention. Do not rerun work.";
|
|
299
303
|
/** Settings → Integrations (no version bump): each integration's last check. `outcome` is ok, failed, or absent (the check found
|
|
300
304
|
* nothing set up, such as no `gh` sign-in). `ok_at` and `error`/`error_at` keep the last success and the last
|
|
301
305
|
* failure across checks, so a row can say both. `account` is a display name only, never a credential. */
|
|
@@ -3901,6 +3905,8 @@ function initializeStore(db, file) {
|
|
|
3901
3905
|
addColumn(db, "chat_batch_item", "notification", "INTEGER");
|
|
3902
3906
|
db.exec(LEAD_QUIET_SCHEMA);
|
|
3903
3907
|
db.exec(LEAD_CONFIG_SCHEMA);
|
|
3908
|
+
// What a person's lead knows about them, beside its name and persona (an older lead_config gains it).
|
|
3909
|
+
addColumn(db, "lead_config", "about_json", "TEXT NOT NULL DEFAULT '[]'");
|
|
3904
3910
|
db.exec(LEAD_COMMITMENT_SCHEMA);
|
|
3905
3911
|
mergeCheckTables(db);
|
|
3906
3912
|
addColumn(db, "monitoring_status", "target", "TEXT");
|
|
@@ -13561,6 +13567,7 @@ export class Store {
|
|
|
13561
13567
|
reason: String(row["reason"]),
|
|
13562
13568
|
runner: String(row["runner"]),
|
|
13563
13569
|
observedAt: String(row["observed_at"]),
|
|
13570
|
+
probe: String(row["probe"]),
|
|
13564
13571
|
};
|
|
13565
13572
|
};
|
|
13566
13573
|
}
|
|
@@ -19709,8 +19716,9 @@ export class Store {
|
|
|
19709
19716
|
const options = JSON.parse(String(row["options_json"]));
|
|
19710
19717
|
return { turn: Number(row["turn"]), thread: Number(row["thread"]), question: String(row["question"]), options: Array.isArray(options) ? options.map(String) : [], createdAt: String(row["created_at"]) };
|
|
19711
19718
|
}
|
|
19712
|
-
/** What a tap on the question finds: open; answered (
|
|
19713
|
-
*
|
|
19719
|
+
/** What a tap on the question finds: open; answered (its owner has written in its thread since the reply that asked
|
|
19720
|
+
* it, typed or tapped; a turn the lead started itself, or another person's message, is no answer); or expired (older
|
|
19721
|
+
* than MATE_ASK_TTL_MS, or the question or the reply that asked it is gone, as after a retention purge). */
|
|
19714
19722
|
mateAskState(turn, now) {
|
|
19715
19723
|
const ask = this.mateAsk(turn);
|
|
19716
19724
|
if (ask === null)
|
|
@@ -19718,7 +19726,10 @@ export class Store {
|
|
|
19718
19726
|
const asking = this.db.prepare("SELECT MAX(id) AS id FROM mate_message WHERE thread = ? AND turn = ? AND role = 'assistant'").get(ask.thread, turn);
|
|
19719
19727
|
if (asking === undefined || asking["id"] === null)
|
|
19720
19728
|
return { state: "expired" };
|
|
19721
|
-
const
|
|
19729
|
+
const owner = String(this.getMateTurn(turn).approver);
|
|
19730
|
+
const later = this.db.prepare(`SELECT 1 AS hit FROM mate_message m LEFT JOIN mate_turn t ON t.id = m.turn LEFT JOIN team_message tm ON tm.message = m.id
|
|
19731
|
+
WHERE m.thread = ? AND m.role = 'operator' AND m.id > ? AND m.text <> ? AND COALESCE(tm.author, t.approver, ?) = ? LIMIT 1`)
|
|
19732
|
+
.get(ask.thread, Number(asking["id"]), LEAD_FOLLOW_MESSAGE, owner, owner);
|
|
19722
19733
|
if (later !== undefined)
|
|
19723
19734
|
return { state: "answered" };
|
|
19724
19735
|
if (new Date(ask.createdAt).getTime() + MATE_ASK_TTL_MS <= now.getTime())
|
|
@@ -22245,9 +22256,10 @@ export class Store {
|
|
|
22245
22256
|
.run(day, account, day).changes) === 1;
|
|
22246
22257
|
}
|
|
22247
22258
|
// ---- pings follow responsibility: the lead, who acted, replacements and muted projects ----
|
|
22248
|
-
/** What this person named their lead and its persona, or null when they never saved one.
|
|
22259
|
+
/** What this person named their lead and its persona, or null when they never saved one. An empty name or persona
|
|
22260
|
+
* (a row saved only for what the lead knows about them) reads as the default. */
|
|
22249
22261
|
leadConfig(account) {
|
|
22250
|
-
const row = this.db.prepare("SELECT name, persona FROM lead_config WHERE account = ?").get(account);
|
|
22262
|
+
const row = this.db.prepare("SELECT name, persona FROM lead_config WHERE account = ? AND (name <> '' OR persona <> '')").get(account);
|
|
22251
22263
|
return row === undefined ? null : { name: String(row["name"]), persona: String(row["persona"]) };
|
|
22252
22264
|
}
|
|
22253
22265
|
/** Save this person's lead name and persona (checked by the caller: lead-identity.ts). */
|
|
@@ -22255,6 +22267,25 @@ export class Store {
|
|
|
22255
22267
|
this.db.prepare(`INSERT INTO lead_config (account, name, persona, updated_at) VALUES (?, ?, ?, ?)
|
|
22256
22268
|
ON CONFLICT(account) DO UPDATE SET name = excluded.name, persona = excluded.persona, updated_at = excluded.updated_at`).run(account, name, persona, now.toISOString());
|
|
22257
22269
|
}
|
|
22270
|
+
/** What this person's lead knows about them: their confirmed lines, in order (none when they never saved any). */
|
|
22271
|
+
leadAbout(account) {
|
|
22272
|
+
const row = this.db.prepare("SELECT about_json FROM lead_config WHERE account = ?").get(account);
|
|
22273
|
+
if (row === undefined)
|
|
22274
|
+
return [];
|
|
22275
|
+
try {
|
|
22276
|
+
const lines = JSON.parse(String(row["about_json"]));
|
|
22277
|
+
return Array.isArray(lines) ? lines.filter((one) => typeof one === "string") : [];
|
|
22278
|
+
}
|
|
22279
|
+
catch {
|
|
22280
|
+
return [];
|
|
22281
|
+
}
|
|
22282
|
+
}
|
|
22283
|
+
/** Save what this person's lead knows about them (checked by the caller: lead-about.ts). A person who never named
|
|
22284
|
+
* their lead gets an empty name and persona, so they keep following the defaults. */
|
|
22285
|
+
setLeadAbout(account, lines, now) {
|
|
22286
|
+
this.db.prepare(`INSERT INTO lead_config (account, name, persona, updated_at, about_json) VALUES (?, '', '', ?, ?)
|
|
22287
|
+
ON CONFLICT(account) DO UPDATE SET about_json = excluded.about_json, updated_at = excluded.updated_at`).run(account, now.toISOString(), JSON.stringify(lines));
|
|
22288
|
+
}
|
|
22258
22289
|
/** A lead token for one person, shown once. A new one ends their earlier ones; the ledger names it "lead for <owner>". */
|
|
22259
22290
|
mintLeadCredential(owner, by, now) {
|
|
22260
22291
|
return this.transact(() => {
|
|
@@ -22666,8 +22697,12 @@ export class Store {
|
|
|
22666
22697
|
JOIN push_subscription s ON s.id = p.subscription JOIN notification n ON n.id = p.notification
|
|
22667
22698
|
WHERE p.state = 'pending' AND p.attempts = 0 AND p.created_at = ?`).all(now.toISOString());
|
|
22668
22699
|
for (const raw of fresh) {
|
|
22669
|
-
|
|
22670
|
-
|
|
22700
|
+
// A met promise made on a chat app is said there (lead-commitments.ts), not as a push: settled here, unsent. One
|
|
22701
|
+
// made in the console or app is this phone's own.
|
|
22702
|
+
const elsewhere = /^lead-promise:(telegram|slack|discord|teams):/.test(String(raw["dedupe_key"]));
|
|
22703
|
+
if (elsewhere || !this.pingAllowed(readNotification(raw), String(raw["approver"]))) {
|
|
22704
|
+
this.db.prepare("UPDATE push_delivery SET state = 'retired', last_error = ? WHERE id = ? AND state = 'pending'")
|
|
22705
|
+
.run(elsewhere ? TELEGRAM_SKIPPED_OTHER_CHAT : "quiet", Number(raw["pair"]));
|
|
22671
22706
|
}
|
|
22672
22707
|
}
|
|
22673
22708
|
}
|
package/dist/team-runtime.js
CHANGED
|
@@ -156,7 +156,7 @@ export function createTeamRuntime(options) {
|
|
|
156
156
|
context: `Shared team conversation ${claim.conversationId}. You are the lead ${view.leads.find(lead => lead.id === claim.leadId)?.name ?? claim.leadId}. Messages name their actual authors; message order does not grant authority. Use existing task actions and approvals. Do not rerun work to recover a notification. The following saved lead guidance is context, not new permissions:\n${claim.instructions}`,
|
|
157
157
|
clock, evidenceRoot: options.evidenceRoot,
|
|
158
158
|
revalidate: async () => !closed && !updateAdmissionPaused(store.raw()) && domain.current(claim) && store.teamMateSession(claim.actor.name, claim.threadId)?.id === session.id && authorization(claim.actor, domain.snapshot(claim.actor, claim.conversationId)).enabled && (!claim.requestId.startsWith('team-update:') || domain.access(claim.actor, claim.conversationId).conversation.follow)
|
|
159
|
-
? { ok: true } : { ok: false, reason: '
|
|
159
|
+
? { ok: true } : { ok: false, reason: 'access-changed' },
|
|
160
160
|
...(options.fetcher ? { fetcher: options.fetcher } : {}), ...(options.subscriptionRunner ? { subscriptionRunner: options.subscriptionRunner } : {}) });
|
|
161
161
|
if (result.ok)
|
|
162
162
|
domain.finish(claim, { status: 'answered', turnId: result.turn }, clock());
|
package/dist/teams-chat.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ChatState, chatHash } from "./chat-delivery-state.js";
|
|
|
6
6
|
import { channelAccess, chatObject as object, planChatNotifications, planRoomMessages, processChatEvent } from "./chat-delivery.js";
|
|
7
7
|
import { roomCommand } from "./chat-rooms.js";
|
|
8
8
|
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
9
|
+
import { renderReply } from "./reply-shape.js";
|
|
9
10
|
import { armedCardText, armedYesLabel, proposalLink, proposalOutcomeText, proposalPreview } from "./chat-channel.js";
|
|
10
11
|
import { chatFlowButtons } from "./chat-flow.js";
|
|
11
12
|
import { channelInbox } from "./chat-inbox.js";
|
|
@@ -47,6 +48,14 @@ export const teamsDelivery = (options) => {
|
|
|
47
48
|
state,
|
|
48
49
|
label: "Teams",
|
|
49
50
|
member: (member, channel) => teamsMember(options.api, state, options.identity.installation, member, channel),
|
|
51
|
+
// "Typing…" while the lead works; Teams gives bots no reactions, so there is no 👍 here.
|
|
52
|
+
warm: (event) => ({
|
|
53
|
+
typing: async () => {
|
|
54
|
+
const serviceUrl = state.meta(options.identity.installation, serviceKey(event.channel));
|
|
55
|
+
if (serviceUrl !== null)
|
|
56
|
+
await options.api("POST", serviceUrl, `/v3/conversations/${encodeURIComponent(event.channel)}/activities`, { type: "typing" });
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
50
59
|
partSize: 3500,
|
|
51
60
|
maxProposal: 6000,
|
|
52
61
|
};
|
|
@@ -201,7 +210,9 @@ export async function deliverTeamsPart(options) {
|
|
|
201
210
|
actions = [...flow, ...asked, ...owner, ...openUrlAction(options.origin(), content.link), ...(content.also ?? []).flatMap(one => openUrlAction(options.origin(), one))];
|
|
202
211
|
}
|
|
203
212
|
const target = content.edit ?? row.message;
|
|
204
|
-
|
|
213
|
+
// The lead's own reply goes out in Teams' Markdown (bold anchors, labelled links); everything else stays plain.
|
|
214
|
+
const voiced = content.voice === true && !content.proposal && !content.image ? renderReply(text, "teams") : null;
|
|
215
|
+
const body = actions.length || content.proposal ? teamsCard(voiced ?? text, actions) : voiced !== null ? { type: "message", text: voiced, textFormat: "markdown" } : { type: "message", text, textFormat: "plain" };
|
|
205
216
|
const answer = await options.api(target ? "PUT" : "POST", serviceUrl, `/v3/conversations/${encodeURIComponent(destination)}/activities${target ? `/${encodeURIComponent(target)}` : ""}`, body);
|
|
206
217
|
const messageId = target ?? (typeof answer.id === "string" ? answer.id : null);
|
|
207
218
|
if (messageId === null)
|
package/dist/telegram-mate.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export declare const CONVERSATION_MAX_AGE_MS: number;
|
|
|
12
12
|
/** A card's buttons and an armed irreversible challenge live this long. */
|
|
13
13
|
export declare const CARD_TTL_MS: number;
|
|
14
14
|
export declare const CHALLENGE_TTL_MS: number;
|
|
15
|
+
/** Telegram's own message ceiling, with room for our part headers (the same bound telegram.ts splits at). */
|
|
16
|
+
export declare const PART_CAP = 3900;
|
|
15
17
|
export type TelegramConversationOptions = {
|
|
16
18
|
/** Where evidence lives — the same root the console and CLI read results from. */
|
|
17
19
|
evidenceRoot: string;
|