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/mate.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type MateProgress } from "./mate-progress.js";
|
|
2
2
|
import type { ChatConfig, MateSession, MateThread, Store } from "./store.js";
|
|
3
|
+
import type { Integration } from "./integrations.js";
|
|
3
4
|
import type { VerifiedApprover } from "./principal.js";
|
|
4
5
|
import { type MateHistoryMessage } from "./converse.js";
|
|
5
6
|
import { type SubscriptionMateRunner } from "./subscription-chat.js";
|
|
7
|
+
import { type LeadChannel } from './lead-context.js';
|
|
6
8
|
export declare const MATE_MESSAGE_MAX_CHARS = 2000;
|
|
7
9
|
/** The thread's recent history the model sees, most recent first until the cap. */
|
|
8
10
|
export declare const MATE_HISTORY_CAP_BYTES = 16384;
|
|
@@ -39,8 +41,14 @@ export type MateTurnInput = {
|
|
|
39
41
|
clock?: () => Date;
|
|
40
42
|
/** Where evidence lives — get_task reads a scout's report from here. */
|
|
41
43
|
evidenceRoot?: string;
|
|
44
|
+
/** Where this conversation happens, so the lead fits its replies to it. */
|
|
45
|
+
channel?: LeadChannel;
|
|
46
|
+
/** A shared team conversation's own lead name, in place of this person's (Settings → Lead). */
|
|
47
|
+
leadName?: string;
|
|
42
48
|
/** How this surface delivers the images a turn selects: Telegram sends them as documents after the reply; absent means identity only. */
|
|
43
49
|
mediaDelivery?: "documents";
|
|
50
|
+
/** The integrations as Settings → Integrations shows them (get_integrations); absent: read from the files beside the database. */
|
|
51
|
+
integrations?: () => readonly Integration[];
|
|
44
52
|
/**
|
|
45
53
|
* A channel's own standing, re-proved where the approver's is: before
|
|
46
54
|
* admission, before every provider dispatch, after every provider wait,
|
|
@@ -54,9 +62,11 @@ export type MateTurnInput = {
|
|
|
54
62
|
ok: true;
|
|
55
63
|
} | {
|
|
56
64
|
ok: false;
|
|
57
|
-
reason:
|
|
65
|
+
reason: MateChannelProblem;
|
|
58
66
|
}>;
|
|
59
67
|
};
|
|
68
|
+
/** Why a chat's standing changed under a turn: each has its own plain words (MATE_CHANNEL_COPY). */
|
|
69
|
+
export type MateChannelProblem = "unpaired" | "not-approver" | "projects-changed" | "projects-unreadable" | "member-changed" | "access-changed";
|
|
60
70
|
export type MateRefusal = "empty-message" | "secret-in-message" | "secret-in-context" | "standing" | "unpriced" | "ceiling-changed" | "not-yours" | "thread-closed" | "latched" | "concurrent" | "daily-cap" | "session-exhausted" | "session-ended" | "over-budget" | "monthly-budget" | "policy" | "invalid-request" | "request-changed" | "channel";
|
|
61
71
|
export type MateFailure = "provider-error" | "timeout" | "malformed-reply" | "secret-refused" | "latched" | "revoked" | "superseded";
|
|
62
72
|
export type MateTurnOutcome = {
|
|
@@ -84,7 +94,31 @@ export type MateTurnOutcome = {
|
|
|
84
94
|
message: string;
|
|
85
95
|
unknownSpend: boolean;
|
|
86
96
|
};
|
|
97
|
+
/** What happened, what it means, and one next step: plain words a person can act on, never internal terms. */
|
|
87
98
|
export declare const MATE_REFUSAL_COPY: Record<MateRefusal, string>;
|
|
99
|
+
/** A turn stopped because its chat's standing changed: what happened, what it means, one next step. */
|
|
100
|
+
export declare const MATE_CHANNEL_COPY: Record<MateChannelProblem, string>;
|
|
101
|
+
/** Why a reply did not finish, in the same three parts: what happened, what it means, one next step. */
|
|
102
|
+
export declare const MATE_FAILURE_COPY: {
|
|
103
|
+
readonly stopped: "This reply was stopped before it finished, from another window or by a newer message. Nothing it proposed was kept. Send your message again if you still need an answer.";
|
|
104
|
+
readonly ended: "Your sign-in or this conversation ended while the lead was answering. Nothing it proposed was kept. Sign in again or start a new chat, then send your message again.";
|
|
105
|
+
readonly tooLong: "The reply took too long and was stopped. Nothing it proposed was kept. Send your message again, or ask for less at once.";
|
|
106
|
+
readonly secretRead: "Something the lead read looked like a password or key, so it stopped before sending it anywhere. Nothing was kept. Remove the password or key from that task or note, then ask again.";
|
|
107
|
+
readonly secretReply: "The lead's reply contained something that looked like a password or key, so it was thrown away. Nothing was kept. Ask again, without asking for a password or key.";
|
|
108
|
+
readonly paused: "An earlier reply stopped before its cost was known, so chat is paused. This message wasn't answered. Confirm that earlier reply on the Chat page, then send again.";
|
|
109
|
+
readonly notStarted: "The reply couldn't be started. Nothing was kept or charged. Send your message again.";
|
|
110
|
+
readonly notInstalled: "The chat app the lead uses isn't installed on this computer, so it couldn't answer. Nothing was kept. Install it, or choose another provider in Settings → Lead.";
|
|
111
|
+
readonly unreadable: "The chat provider sent back an answer that couldn't be read, so it was thrown away. Nothing was kept. Send your message again.";
|
|
112
|
+
readonly signIn: "The chat provider couldn't answer; its sign-in may have expired. Nothing was kept. Sign in to it again on this computer, then send your message again.";
|
|
113
|
+
readonly refused: "The chat provider turned the request down. Nothing was kept or charged. Try again in a minute; if it keeps happening, check the provider in Settings → Lead.";
|
|
114
|
+
readonly tooLongUnknownCost: "The reply took too long and was stopped, and its cost isn't known yet, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.";
|
|
115
|
+
readonly lostUnknownCost: "The chat provider stopped responding partway through, and the cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.";
|
|
116
|
+
readonly unreadableUnknownCost: "The chat provider sent back an answer that couldn't be read, and its cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.";
|
|
117
|
+
readonly empty: "The lead sent back an empty answer. Nothing was kept. Send your message again.";
|
|
118
|
+
readonly unusable: "The lead's answer couldn't be used, so it was thrown away. Nothing was kept. Send your message again.";
|
|
119
|
+
};
|
|
120
|
+
/** A failed turn's saved reason as plain words, for a surface that reads the turn back later (a restart, a delivery retry). */
|
|
121
|
+
export declare function mateFailureText(reason: string | null): string;
|
|
88
122
|
/** The last messages of the thread as provider-neutral history, newest kept first until the byte cap. */
|
|
89
123
|
export declare function historyFor(store: Store, thread: number, queuedMessageId?: number): MateHistoryMessage[];
|
|
90
124
|
/** "read 3 · proposed 1 · 2 steps" — counts only, never results (ruling 11). */
|
package/dist/mate.js
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The mate's turn engine (mate arc §3): one reservation for the whole
|
|
3
|
-
* loop, one `chat_turn` step per provider request, tools executed
|
|
4
|
-
* in-process under the branded approver, results through `mateView`, and
|
|
5
|
-
* only operator text and assistant text kept (ruling 11). The console
|
|
6
|
-
* and the CLI drive this and render from the rows it writes.
|
|
7
|
-
*
|
|
8
|
-
* Slice-1 review, folded in: the session, thread, and credential are
|
|
9
|
-
* bound to the principal inside the store's admission (finding 2); the
|
|
10
|
-
* principal is re-proved after every network wait and the turn's own
|
|
11
|
-
* row is re-read, so a revocation mid-flight ends the loop before any
|
|
12
|
-
* tool runs (finding 5); a step whose cost is unknown charges the whole
|
|
13
|
-
* reservation (finding 1); the latch is re-checked before every dispatch
|
|
14
|
-
* (finding 11); usage that cannot be true is malformed, never a discount
|
|
15
|
-
* (finding 8); and everything the model sees passed `mateView` (finding 9).
|
|
16
|
-
*/
|
|
17
1
|
import { Buffer } from "node:buffer";
|
|
18
2
|
import { mateToolLabel } from "./mate-progress.js";
|
|
19
3
|
import { createHash } from "node:crypto";
|
|
@@ -23,35 +7,82 @@ import { MATE_MAX_CALLS_PER_STEP, MATE_MAX_STEPS, MATE_TOOL_RESULT_CAP_BYTES, TU
|
|
|
23
7
|
import { redactSecretLines, scanForSecrets } from "./evidence.js";
|
|
24
8
|
import { redactSecretAssignments } from "./builder.js";
|
|
25
9
|
import { MATE_CONTRACT } from "./mate-contract.js";
|
|
26
|
-
import { MATE_MAX_PROPOSALS_PER_TURN, MATE_TOOL_SCHEMAS, executeMateTool, isMateTool, mateViewContextFor, redactForMate, toolResultBytes } from "./mate-tools.js";
|
|
10
|
+
import { MATE_MAX_PROPOSALS_PER_TURN, MATE_TOOL_SCHEMAS, executeMateTool, isMateTool, mateViewContextFor, projectLabelForMate, redactForMate, toolResultBytes } from "./mate-tools.js";
|
|
27
11
|
import { composeSubscriptionMatePrompt, performSubscriptionMateRequest } from "./subscription-chat.js";
|
|
28
12
|
import { leadContext } from './lead-context.js';
|
|
29
13
|
import { envValue } from "./names.js";
|
|
14
|
+
import { deliverableClaim, deliverableRepair, dropDeliverableClaims, replyCarriesDeliverable } from "./reply-shape.js";
|
|
30
15
|
export const MATE_MESSAGE_MAX_CHARS = 2_000;
|
|
16
|
+
/** Where a promise made this turn is reported: the chat app it arrived on, or this conversation for the console and terminal. */
|
|
17
|
+
function promiseChannelOfTurn(channel) {
|
|
18
|
+
return channel === undefined ? undefined : channel === "console" || channel === "terminal" ? "chat" : channel;
|
|
19
|
+
}
|
|
31
20
|
/** The thread's recent history the model sees, most recent first until the cap. */
|
|
32
21
|
export const MATE_HISTORY_CAP_BYTES = 16_384;
|
|
33
22
|
export const MATE_HISTORY_MAX_MESSAGES = 40;
|
|
23
|
+
/** What happened, what it means, and one next step: plain words a person can act on, never internal terms. */
|
|
34
24
|
export const MATE_REFUSAL_COPY = {
|
|
35
|
-
"empty-message": `
|
|
36
|
-
"secret-in-message": "
|
|
37
|
-
"secret-in-context": "
|
|
38
|
-
standing: "
|
|
39
|
-
unpriced: "no
|
|
40
|
-
"ceiling-changed": "
|
|
41
|
-
"not-yours": "
|
|
42
|
-
"thread-closed": "
|
|
43
|
-
latched: "
|
|
44
|
-
concurrent: "
|
|
45
|
-
"daily-cap": "
|
|
46
|
-
"session-exhausted": "
|
|
47
|
-
"session-ended": "
|
|
48
|
-
"over-budget": "the weekly chat
|
|
49
|
-
"monthly-budget": "
|
|
50
|
-
policy: "
|
|
51
|
-
"invalid-request": "This message
|
|
25
|
+
"empty-message": `That message was empty or too long, so it wasn't sent. Write 1 to ${MATE_MESSAGE_MAX_CHARS} characters and send it again.`,
|
|
26
|
+
"secret-in-message": "That message looks like it contains a password or key, so it wasn't sent or saved. Remove it and send the message again.",
|
|
27
|
+
"secret-in-context": "Something saved in your projects looks like a password or key, so the lead can't read your projects safely. Remove it from the task or note it's in, then try again.",
|
|
28
|
+
standing: "Your sign-in changed, so the lead can't act for you right now. Sign in again.",
|
|
29
|
+
unpriced: "The chat model has no saved price, so its cost can't be tracked. Save the chat settings again to fix it.",
|
|
30
|
+
"ceiling-changed": "Your projects changed since this chat started, so it can't go on. Start a new chat.",
|
|
31
|
+
"not-yours": "This chat belongs to someone else, so you can't continue it. Start your own chat.",
|
|
32
|
+
"thread-closed": "This conversation is closed. Start a new one to keep going.",
|
|
33
|
+
latched: "An earlier reply stopped before its cost was known, so chat is paused. Acknowledge that reply on the Chat page, then send again.",
|
|
34
|
+
concurrent: "The lead is still answering your last message. Wait for that reply, then send this one.",
|
|
35
|
+
"daily-cap": "You've reached today's limit on chat replies, so this one wasn't sent. Try again tomorrow.",
|
|
36
|
+
"session-exhausted": "This chat has used its spending limit. Start a new chat to keep going.",
|
|
37
|
+
"session-ended": "This chat has ended. Start a new chat to keep going.",
|
|
38
|
+
"over-budget": "This reply would go over this week's chat spending limit, so it wasn't sent. Raise the weekly limit in chat settings to keep going.",
|
|
39
|
+
"monthly-budget": "A monthly budget this chat counts toward is used up, or its cost can't be worked out yet. Open the Spend page to see which.",
|
|
40
|
+
policy: "Your organisation's policy doesn't allow this chat's provider or model. Open Settings → Policy to see what's allowed.",
|
|
41
|
+
"invalid-request": "This message couldn't be matched to your conversation. Reload the conversation, then send it again.",
|
|
52
42
|
"request-changed": "That send was already received with different text or task context. Reload the conversation before sending a new message.",
|
|
53
|
-
channel: "
|
|
43
|
+
channel: "This conversation's connection changed, so the message wasn't sent. Reconnect it, then send again.",
|
|
44
|
+
};
|
|
45
|
+
/** A turn stopped because its chat's standing changed: what happened, what it means, one next step. */
|
|
46
|
+
export const MATE_CHANNEL_COPY = {
|
|
47
|
+
unpaired: "This chat is no longer connected to your account, so the reply was stopped. Nothing it proposed was kept. Connect the chat again in Settings, then send your message again.",
|
|
48
|
+
"not-approver": "Your account can no longer approve work here, so the reply was stopped. Nothing it proposed was kept. Ask an owner to restore your access, then send your message again.",
|
|
49
|
+
"projects-changed": "The projects this chat can see changed while the lead was answering, so the reply was stopped. Nothing it proposed was kept. Send your message again.",
|
|
50
|
+
"projects-unreadable": "Your project list couldn't be read just now, so the reply was stopped. Nothing it proposed was kept. Send your message again in a minute.",
|
|
51
|
+
"member-changed": "Your account in this chat app no longer has access, so the reply was stopped. Nothing it proposed was kept. Ask the app's admin to add you back, then send your message again.",
|
|
52
|
+
"access-changed": "This chat's access changed while the lead was answering, so the reply was stopped. Nothing it proposed was kept. Reconnect the chat, then send your message again.",
|
|
54
53
|
};
|
|
54
|
+
/** Why a reply did not finish, in the same three parts: what happened, what it means, one next step. */
|
|
55
|
+
export const MATE_FAILURE_COPY = {
|
|
56
|
+
stopped: "This reply was stopped before it finished, from another window or by a newer message. Nothing it proposed was kept. Send your message again if you still need an answer.",
|
|
57
|
+
ended: "Your sign-in or this conversation ended while the lead was answering. Nothing it proposed was kept. Sign in again or start a new chat, then send your message again.",
|
|
58
|
+
tooLong: "The reply took too long and was stopped. Nothing it proposed was kept. Send your message again, or ask for less at once.",
|
|
59
|
+
secretRead: "Something the lead read looked like a password or key, so it stopped before sending it anywhere. Nothing was kept. Remove the password or key from that task or note, then ask again.",
|
|
60
|
+
secretReply: "The lead's reply contained something that looked like a password or key, so it was thrown away. Nothing was kept. Ask again, without asking for a password or key.",
|
|
61
|
+
paused: "An earlier reply stopped before its cost was known, so chat is paused. This message wasn't answered. Confirm that earlier reply on the Chat page, then send again.",
|
|
62
|
+
notStarted: "The reply couldn't be started. Nothing was kept or charged. Send your message again.",
|
|
63
|
+
notInstalled: "The chat app the lead uses isn't installed on this computer, so it couldn't answer. Nothing was kept. Install it, or choose another provider in Settings → Lead.",
|
|
64
|
+
unreadable: "The chat provider sent back an answer that couldn't be read, so it was thrown away. Nothing was kept. Send your message again.",
|
|
65
|
+
signIn: "The chat provider couldn't answer; its sign-in may have expired. Nothing was kept. Sign in to it again on this computer, then send your message again.",
|
|
66
|
+
refused: "The chat provider turned the request down. Nothing was kept or charged. Try again in a minute; if it keeps happening, check the provider in Settings → Lead.",
|
|
67
|
+
tooLongUnknownCost: "The reply took too long and was stopped, and its cost isn't known yet, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.",
|
|
68
|
+
lostUnknownCost: "The chat provider stopped responding partway through, and the cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.",
|
|
69
|
+
unreadableUnknownCost: "The chat provider sent back an answer that couldn't be read, and its cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.",
|
|
70
|
+
empty: "The lead sent back an empty answer. Nothing was kept. Send your message again.",
|
|
71
|
+
unusable: "The lead's answer couldn't be used, so it was thrown away. Nothing was kept. Send your message again.",
|
|
72
|
+
};
|
|
73
|
+
/** A failed turn's saved reason as plain words, for a surface that reads the turn back later (a restart, a delivery retry). */
|
|
74
|
+
export function mateFailureText(reason) {
|
|
75
|
+
switch (reason) {
|
|
76
|
+
case "timeout": return MATE_FAILURE_COPY.tooLong;
|
|
77
|
+
case "malformed-reply": return MATE_FAILURE_COPY.unreadable;
|
|
78
|
+
case "secret-refused": return MATE_FAILURE_COPY.secretReply;
|
|
79
|
+
case "latched": return MATE_FAILURE_COPY.paused;
|
|
80
|
+
case "revoked": return MATE_FAILURE_COPY.ended;
|
|
81
|
+
case "superseded": return MATE_FAILURE_COPY.stopped;
|
|
82
|
+
case "provider-error": return MATE_FAILURE_COPY.refused;
|
|
83
|
+
default: return "The lead's reply didn't finish. Nothing it proposed was kept. Send your message again.";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
55
86
|
const READ_TOOLS = new Set(["get_brief", "get_project_context", "get_actions", "get_action_status", "get_skills", "get_acceptance_evidence", "recap", "list_repos", "list_tasks", "get_task", "get_result", "get_result_images", "get_controls", "get_agents", "get_project_knowledge", "get_task_conversation", "get_diff", "get_check_log", "get_project_tools", "get_flows", "get_flow_insights", "list_decisions", "get_decision", "queue"]);
|
|
56
87
|
/** The last messages of the thread as provider-neutral history, newest kept first until the byte cap. */
|
|
57
88
|
export function historyFor(store, thread, queuedMessageId) {
|
|
@@ -149,7 +180,10 @@ export async function runMateTurn(input) {
|
|
|
149
180
|
let now = clock();
|
|
150
181
|
store.sweepStaleMateTurns(now);
|
|
151
182
|
const view = mateViewContextFor(store, who);
|
|
152
|
-
|
|
183
|
+
// Names in the bundle are deliberate (the lead's, the person's first name, project labels); everything else is scrubbed.
|
|
184
|
+
const document = leadContext(store, who.repos, now, { owner: who.name, thread: thread.id, redact: text => redactForMate(text, view),
|
|
185
|
+
projectName: (path, index) => projectLabelForMate(path, index, view.names),
|
|
186
|
+
...(input.evidenceRoot === undefined ? {} : { evidenceRoot: input.evidenceRoot }), ...(input.channel === undefined ? {} : { channel: input.channel }), ...(input.leadName === undefined ? {} : { leadName: input.leadName }) });
|
|
153
187
|
const authoredMessage = input.queuedMessageId === undefined ? message : `From ${who.name}:\n${message}`;
|
|
154
188
|
const historyMessage = input.context === undefined ? authoredMessage : `${redactForMate(input.context, view)}\n\n${authoredMessage}`;
|
|
155
189
|
const history = [...historyFor(store, thread.id, input.queuedMessageId), { role: "operator", text: historyMessage }];
|
|
@@ -229,6 +263,10 @@ export async function runMateTurn(input) {
|
|
|
229
263
|
let steps = 0;
|
|
230
264
|
const readDecisions = new Map();
|
|
231
265
|
const readResults = new Map();
|
|
266
|
+
const searchedMemory = new Map();
|
|
267
|
+
const checkedCapabilities = new Map();
|
|
268
|
+
/** The turn's one question to its owner: shown with the reply only once the turn answers; a failed turn drops it. */
|
|
269
|
+
const ask = (question, options) => store.recordMateAsk({ turn: turnId, thread: thread.id, question, options }, clock());
|
|
232
270
|
let tokensIn = 0;
|
|
233
271
|
let tokensOut = 0;
|
|
234
272
|
let settled = 0;
|
|
@@ -249,6 +287,7 @@ export async function runMateTurn(input) {
|
|
|
249
287
|
const fail = (failed, message, unknownSpend) => {
|
|
250
288
|
now = clock();
|
|
251
289
|
store.finalizeMateTurn(turnId, started.generation, { state: "failed", settledMicrousd: settled, unknownSpend, tokensIn, tokensOut, failureReason: failed }, now);
|
|
290
|
+
store.dropMateAsk(turnId);
|
|
252
291
|
return { ok: false, turn: turnId, failed, message, unknownSpend };
|
|
253
292
|
};
|
|
254
293
|
/** The turn's own row, re-read: still running under our generation, or someone ended it under us. */
|
|
@@ -260,20 +299,30 @@ export async function runMateTurn(input) {
|
|
|
260
299
|
// AFTER it resolves, immediately before sending context or using a tool.
|
|
261
300
|
const guard = (channel) => {
|
|
262
301
|
if (!stillOurs())
|
|
263
|
-
return { ok: false, turn: turnId, failed: "superseded", message:
|
|
302
|
+
return { ok: false, turn: turnId, failed: "superseded", message: MATE_FAILURE_COPY.stopped, unknownSpend: false };
|
|
264
303
|
if (!channel.ok)
|
|
265
|
-
return fail("revoked",
|
|
304
|
+
return fail("revoked", MATE_CHANNEL_COPY[channel.reason] ?? MATE_CHANNEL_COPY["access-changed"], false);
|
|
266
305
|
const standing = reproveApprover(store, who);
|
|
267
306
|
const liveSession = store.getMateSession(session.id);
|
|
268
307
|
const liveThread = store.getMateThread(thread.id);
|
|
269
308
|
if (!standing.ok || liveSession === null || liveSession.endedAt !== null || liveThread === null || liveThread.closedAt !== null) {
|
|
270
|
-
return fail("revoked",
|
|
309
|
+
return fail("revoked", MATE_FAILURE_COPY.ended, false);
|
|
271
310
|
}
|
|
272
311
|
return null;
|
|
273
312
|
};
|
|
274
313
|
let reply = null;
|
|
275
314
|
let stoppedAtCap = false;
|
|
276
315
|
let lastText = "";
|
|
316
|
+
/** A page shown this turn (show_control): a reply may say it links one. */
|
|
317
|
+
let shownControl = false;
|
|
318
|
+
/** The turn's one repair step for a reply that claims an attachment it does not carry. */
|
|
319
|
+
let repaired = false;
|
|
320
|
+
const unbacked = (text) => {
|
|
321
|
+
const claim = deliverableClaim(text);
|
|
322
|
+
if (claim === null || shownControl || replyCarriesDeliverable(text) || store.listMateTurnEvidence(turnId).length > 0)
|
|
323
|
+
return null;
|
|
324
|
+
return claim;
|
|
325
|
+
};
|
|
277
326
|
while (steps < maxSteps) {
|
|
278
327
|
const blocked = guard(input.revalidate === undefined ? { ok: true } : await input.revalidate());
|
|
279
328
|
if (blocked !== null)
|
|
@@ -281,22 +330,22 @@ export async function runMateTurn(input) {
|
|
|
281
330
|
now = clock();
|
|
282
331
|
const remainingMs = TURN_WALL_CLOCK_MS - (now.getTime() - turnStartedAt);
|
|
283
332
|
if (remainingMs <= 0)
|
|
284
|
-
return fail("timeout",
|
|
333
|
+
return fail("timeout", MATE_FAILURE_COPY.tooLong, false);
|
|
285
334
|
const request = direct ? composeDirect(input.key) : composeSubscription();
|
|
286
335
|
// Tool results join the outbound body: scanned again before every dispatch.
|
|
287
336
|
const outbound = typeof request === "string" ? request : request.body;
|
|
288
337
|
if (scanForSecrets(outbound).length > 0) {
|
|
289
|
-
return fail("secret-refused",
|
|
338
|
+
return fail("secret-refused", MATE_FAILURE_COPY.secretRead, false);
|
|
290
339
|
}
|
|
291
340
|
const step = store.openMateStep({ mateTurn: turnId, generation: started.generation, approver: who.name, credentialKey, provider: config.provider, model: config.model, deadlineMs: remainingMs + 10_000 }, now);
|
|
292
341
|
if (!step.ok) {
|
|
293
342
|
if (step.reason === "latched")
|
|
294
|
-
return fail("latched",
|
|
295
|
-
return { ok: false, turn: turnId, failed: "superseded", message:
|
|
343
|
+
return fail("latched", MATE_FAILURE_COPY.paused, false);
|
|
344
|
+
return { ok: false, turn: turnId, failed: "superseded", message: MATE_FAILURE_COPY.stopped, unknownSpend: false };
|
|
296
345
|
}
|
|
297
346
|
const stepStarted = store.startChatTurn(step.id, now);
|
|
298
347
|
if (!stepStarted.ok)
|
|
299
|
-
return fail("provider-error",
|
|
348
|
+
return fail("provider-error", MATE_FAILURE_COPY.notStarted, false);
|
|
300
349
|
steps++;
|
|
301
350
|
progress({ kind: "step", turn: turnId, step: steps });
|
|
302
351
|
const requestBytes = Buffer.byteLength(outbound, "utf8");
|
|
@@ -341,36 +390,36 @@ export async function runMateTurn(input) {
|
|
|
341
390
|
if (!direct) {
|
|
342
391
|
finishStep({ state: "failed", failureReason: result.problem === "timeout" ? "timeout" : result.problem === "malformed-reply" ? "malformed-reply" : "provider-error", settledMicrousd: 0 });
|
|
343
392
|
const message = result.problem === "not-found"
|
|
344
|
-
?
|
|
393
|
+
? MATE_FAILURE_COPY.notInstalled
|
|
345
394
|
: result.problem === "timeout"
|
|
346
|
-
?
|
|
395
|
+
? MATE_FAILURE_COPY.tooLong
|
|
347
396
|
: result.problem === "malformed-reply"
|
|
348
|
-
?
|
|
349
|
-
:
|
|
397
|
+
? MATE_FAILURE_COPY.unreadable
|
|
398
|
+
: MATE_FAILURE_COPY.signIn;
|
|
350
399
|
return fail(result.problem === "timeout" ? "timeout" : result.problem === "malformed-reply" ? "malformed-reply" : "provider-error", message, false);
|
|
351
400
|
}
|
|
352
401
|
if (result.problem.startsWith("status-")) {
|
|
353
402
|
// The provider ANSWERED with an error: nothing billed for this step.
|
|
354
403
|
finishStep({ state: "failed", failureReason: "provider-error", settledMicrousd: 0 });
|
|
355
|
-
return fail("provider-error",
|
|
404
|
+
return fail("provider-error", MATE_FAILURE_COPY.refused, false);
|
|
356
405
|
}
|
|
357
406
|
if (result.problem === "timeout") {
|
|
358
407
|
finishStep({ state: "failed", failureReason: "timeout", settledMicrousd: null, unknownSpend: true });
|
|
359
|
-
return fail("timeout",
|
|
408
|
+
return fail("timeout", MATE_FAILURE_COPY.tooLongUnknownCost, true);
|
|
360
409
|
}
|
|
361
410
|
if (result.problem === "network") {
|
|
362
411
|
finishStep({ state: "failed", failureReason: "provider-error", settledMicrousd: null, unknownSpend: true });
|
|
363
|
-
return fail("provider-error",
|
|
412
|
+
return fail("provider-error", MATE_FAILURE_COPY.lostUnknownCost, true);
|
|
364
413
|
}
|
|
365
414
|
finishStep({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
366
|
-
return fail("malformed-reply",
|
|
415
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unreadableUnknownCost, true);
|
|
367
416
|
}
|
|
368
417
|
const answer = result.answer;
|
|
369
418
|
// Usage that cannot be true — more input tokens than bytes sent — is a
|
|
370
419
|
// malformed reply with unknown cost, never a number to settle by.
|
|
371
420
|
if (direct && answer.tokensIn > requestBytes) {
|
|
372
421
|
finishStep({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
373
|
-
return fail("malformed-reply",
|
|
422
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unreadableUnknownCost, true);
|
|
374
423
|
}
|
|
375
424
|
// The pinned math, or the provider's own reported charge when HIGHER.
|
|
376
425
|
const stepSettled = direct
|
|
@@ -389,15 +438,23 @@ export async function runMateTurn(input) {
|
|
|
389
438
|
return changed;
|
|
390
439
|
if (answer.calls.length === 0) {
|
|
391
440
|
if (answer.text.trim() === "")
|
|
392
|
-
return fail("malformed-reply",
|
|
393
|
-
|
|
441
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.empty, false);
|
|
442
|
+
// "Here's the screenshot" with nothing attached or linked: one repair step to attach it or drop the claim; never the claim alone.
|
|
443
|
+
const claim = unbacked(answer.text);
|
|
444
|
+
if (claim !== null && !repaired && steps < maxSteps) {
|
|
445
|
+
repaired = true;
|
|
446
|
+
history.push({ role: "assistant", text: answer.text, calls: [] });
|
|
447
|
+
history.push({ role: "operator", text: deliverableRepair(claim) });
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
reply = claim === null ? answer.text : dropDeliverableClaims(answer.text);
|
|
394
451
|
break;
|
|
395
452
|
}
|
|
396
453
|
if (answer.calls.length > MATE_MAX_CALLS_PER_STEP)
|
|
397
|
-
return fail("malformed-reply",
|
|
454
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unusable, false);
|
|
398
455
|
for (const call of answer.calls) {
|
|
399
456
|
if (!isMateTool(call.name))
|
|
400
|
-
return fail("malformed-reply",
|
|
457
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unusable, false);
|
|
401
458
|
}
|
|
402
459
|
history.push({ role: "assistant", text: answer.text, calls: answer.calls });
|
|
403
460
|
for (const [index, call] of answer.calls.entries()) {
|
|
@@ -407,9 +464,11 @@ export async function runMateTurn(input) {
|
|
|
407
464
|
return changed;
|
|
408
465
|
}
|
|
409
466
|
progress({ kind: "tool", turn: turnId, step: steps, label: mateToolLabel(call.name) });
|
|
410
|
-
const outcome = executeMateTool({ store, who, now: clock(), draft, selectEvidence, step: steps, readDecisions, readResults, ...(input.evidenceRoot === undefined ? {} : { evidenceRoot: input.evidenceRoot }), ...(input.mediaDelivery === undefined ? {} : { mediaDelivery: input.mediaDelivery }) }, call.name, call.args, view);
|
|
467
|
+
const outcome = executeMateTool({ store, who, now: clock(), draft, selectEvidence, step: steps, readDecisions, readResults, searchedMemory, checkedCapabilities, ask, ...(input.integrations === undefined ? {} : { integrations: input.integrations }), thread: thread.id, turn: turnId, ...(promiseChannelOfTurn(input.channel) === undefined ? {} : { channel: promiseChannelOfTurn(input.channel) }), ...(input.evidenceRoot === undefined ? {} : { evidenceRoot: input.evidenceRoot }), ...(input.mediaDelivery === undefined ? {} : { mediaDelivery: input.mediaDelivery }) }, call.name, call.args, view);
|
|
411
468
|
if (READ_TOOLS.has(call.name))
|
|
412
469
|
reads++;
|
|
470
|
+
if (call.name === "show_control" && outcome.ok)
|
|
471
|
+
shownControl = true;
|
|
413
472
|
// Opt-in, local diagnostics for end-to-end runs: what the lead asked of each tool and what came back (its start), keys
|
|
414
473
|
// blanked. Each line stays whole JSON so a run can assert on what a tool returned rather than on the model's words.
|
|
415
474
|
if (envValue(process.env, "MATE_TRACE") === "1") {
|
|
@@ -423,16 +482,17 @@ export async function runMateTurn(input) {
|
|
|
423
482
|
}
|
|
424
483
|
if (reply === null) {
|
|
425
484
|
stoppedAtCap = true;
|
|
426
|
-
|
|
485
|
+
const last = lastText.trim() === "" || unbacked(lastText) === null ? lastText.trim() : dropDeliverableClaims(lastText);
|
|
486
|
+
reply = `${last === "" ? "" : `${last}\n\n`}(I stopped here: this answer needed more steps than ${maxSteps < MATE_MAX_STEPS ? "this conversation's remaining spending allows" : "one reply allows"}. Ask me to carry on.)`;
|
|
427
487
|
}
|
|
428
488
|
// Ruling 11: model text is scanned before it becomes durable.
|
|
429
489
|
if (scanForSecrets(reply).length > 0)
|
|
430
|
-
return fail("secret-refused",
|
|
490
|
+
return fail("secret-refused", MATE_FAILURE_COPY.secretReply, false);
|
|
431
491
|
now = clock();
|
|
432
492
|
const activity = activitySummary(reads, proposals, steps);
|
|
433
493
|
// One write (finding 12): settle, debit, promote the drafts, append the assistant text.
|
|
434
494
|
const finalized = store.finalizeMateTurn(turnId, started.generation, { state: "answered", settledMicrousd: settled, tokensIn, tokensOut, message: { text: reply, activity } }, now);
|
|
435
495
|
if (!finalized)
|
|
436
|
-
return { ok: false, turn: turnId, failed: "superseded", message:
|
|
496
|
+
return { ok: false, turn: turnId, failed: "superseded", message: MATE_FAILURE_COPY.stopped, unknownSpend: false };
|
|
437
497
|
return { ok: true, turn: turnId, reply, activity, proposals, steps, stoppedAtCap, settledMicrousd: settled };
|
|
438
498
|
}
|
package/dist/operate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UNSENT_REPLY_MS } from "./telegram-settings.js";
|
|
2
|
+
import { leadNameOf } from "./lead-identity.js";
|
|
2
3
|
import { maybeTriggerRepair } from "./dispose.js";
|
|
3
4
|
import { CHECK_LEVEL_HINTS, CHECK_LEVEL_WORDS, isCheckLevel, liveQuickCommand, projectCheckLevel, quickVerifyKey, setProjectCheckLevel, setTaskCheckLevel, suggestQuickCommand } from "./check-levels.js";
|
|
4
5
|
import { fileAddTestsTask, followUpChecksOf, requestFollowUpChecks, runFollowUpCheck, runWaitingChecks } from "./result-follow-ups.js";
|
|
@@ -10698,7 +10699,7 @@ function leadClaimCommand(positional, flags, context) {
|
|
|
10698
10699
|
const claimed = leadClaim(store, actor, id, context.clock());
|
|
10699
10700
|
if (!claimed.ok)
|
|
10700
10701
|
return fail(write, json, command, claimed.reason, claimed.message, claimed.reason === "usage" ? EXIT.usage : EXIT.refused);
|
|
10701
|
-
return succeed(write, json, command, { task: id, root: claimed.root, lead: claimed.claim }, () => [`${id} is yours: it reads "
|
|
10702
|
+
return succeed(write, json, command, { task: id, root: claimed.root, lead: claimed.claim }, () => [`${id} is yours: it reads "${leadNameOf(store, actor.account)} is on it" until you complete it, hand it on with task ask, or go two hours without acting on it.`]);
|
|
10702
10703
|
}
|
|
10703
10704
|
function blockTask(positional, flags, context) {
|
|
10704
10705
|
const { store, write, json } = context;
|
|
@@ -11642,6 +11643,8 @@ async function chatCommand(flags, context) {
|
|
|
11642
11643
|
ceilingUsd: ceilingGiven === undefined ? undefined : Number(ceilingGiven),
|
|
11643
11644
|
...(context.mateSeams === undefined ? {} : { seams: { ...context.mateSeams, clock: context.mateSeams.clock ?? context.clock } }),
|
|
11644
11645
|
...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }),
|
|
11646
|
+
// The console's own address (and its public one): links there read as "the task", "the result", "Settings → Lead".
|
|
11647
|
+
appOrigin: [loadConsoleUrl(process.env, dirname(context.databaseFile)), phoneOrigin(process.env, dirname(context.databaseFile))],
|
|
11645
11648
|
});
|
|
11646
11649
|
return result.code;
|
|
11647
11650
|
}
|
package/dist/phase-routing.d.ts
CHANGED
|
@@ -234,7 +234,7 @@ export declare function parseOverrides(raw: unknown): RouteOverride[] | null;
|
|
|
234
234
|
* nobody had overridden anything. */
|
|
235
235
|
export declare function overridesFromJson(json: string | null): RouteOverride[] | null;
|
|
236
236
|
export declare function canonicalOverridesJson(overrides: readonly RouteOverride[]): string | null;
|
|
237
|
-
export type ReadinessLookup = (provider: ProviderId) => Pick<ReadinessObservation, "state" | "reason" | "runner" | "observedAt"> | null;
|
|
237
|
+
export type ReadinessLookup = (provider: ProviderId) => (Pick<ReadinessObservation, "state" | "reason" | "runner" | "observedAt"> & Partial<Pick<ReadinessObservation, "probe">>) | null;
|
|
238
238
|
export type RouteLegProjection = RouteLeg & {
|
|
239
239
|
readiness: ReadinessState;
|
|
240
240
|
readinessReason: string | null;
|
package/dist/project-delete.js
CHANGED
|
@@ -218,6 +218,7 @@ function deleteRows(store, repo, d, now) {
|
|
|
218
218
|
del(`${surface}_flow_prompt`, IN("card"), C);
|
|
219
219
|
del(`${surface}_question_action`, IN("question"), questions);
|
|
220
220
|
del(`${surface}_question_prompt`, IN("question"), questions);
|
|
221
|
+
del(`${surface}_ask_action`, IN("turn"), U);
|
|
221
222
|
}
|
|
222
223
|
del("telegram_action", IN("decision"), decisions);
|
|
223
224
|
del("telegram_decision_message", IN("decision"), decisions);
|
|
@@ -242,6 +243,7 @@ function deleteRows(store, repo, d, now) {
|
|
|
242
243
|
del("team_mate_session", IN("thread"), H);
|
|
243
244
|
del("mate_turn_evidence", `${IN("turn")} OR ${IN("run")} OR ${IN("task_ref")} OR ${IN("artifact")}`, U, R, T, A);
|
|
244
245
|
del("chat_turn", IN("mate_turn"), U);
|
|
246
|
+
del("mate_ask", IN("turn"), U);
|
|
245
247
|
del("mate_turn", IN("id"), U);
|
|
246
248
|
del("mate_proposal", IN("id"), proposals);
|
|
247
249
|
del("mate_message", IN("thread"), H);
|
|
@@ -318,7 +320,7 @@ function deleteRows(store, repo, d, now) {
|
|
|
318
320
|
for (const [table, column] of SETTINGS)
|
|
319
321
|
del(table, `${column} = ?`, repo);
|
|
320
322
|
del("project_mute", "repo = ?", repo);
|
|
321
|
-
for (const table of ["knowledge_change", "project_skill_change", "memory_gap", "memory_proposal", "memory_rejection", "memory_session", "memory_search", "mode_rail", "side_spend", "watch_episode", "worktree", "coordinator_proposal"])
|
|
323
|
+
for (const table of ["knowledge_change", "project_skill_change", "memory_gap", "memory_proposal", "memory_rejection", "memory_session", "memory_search", "mode_rail", "side_spend", "watch_episode", "worktree", "coordinator_proposal", "lead_commitment"])
|
|
322
324
|
del(table, "repo = ?", repo);
|
|
323
325
|
// A builder watching the project keeps its lease until it stops; an ended one goes.
|
|
324
326
|
del("watch_lease", "repo = ? AND expires_at <= ?", repo, now.toISOString());
|
package/dist/provider-auth.d.ts
CHANGED
|
@@ -34,6 +34,10 @@ export declare function providerName(provider: string): string;
|
|
|
34
34
|
export declare function signInReason(pause: Pick<AuthPause, "provider" | "authMode">): string;
|
|
35
35
|
/** What to run, as a command. */
|
|
36
36
|
export declare function signInCommand(pause: Pick<AuthPause, "provider" | "authMode">): string;
|
|
37
|
+
/** The same command for a chat message: the phone scrub hides anything shaped
|
|
38
|
+
* like a path (`/login` included), so a message names Claude's equivalent
|
|
39
|
+
* `claude auth login`. */
|
|
40
|
+
export declare function messageCommand(pause: Pick<AuthPause, "provider" | "authMode">): string;
|
|
37
41
|
/** One line for status, ready and the console. */
|
|
38
42
|
export declare function signInWords(pause: AuthPause): string;
|
|
39
43
|
/** The open pause for a provider, or null. */
|
package/dist/provider-auth.js
CHANGED
|
@@ -44,7 +44,7 @@ export function signInCommand(pause) {
|
|
|
44
44
|
/** The same command for a chat message: the phone scrub hides anything shaped
|
|
45
45
|
* like a path (`/login` included), so a message names Claude's equivalent
|
|
46
46
|
* `claude auth login`. */
|
|
47
|
-
function messageCommand(pause) {
|
|
47
|
+
export function messageCommand(pause) {
|
|
48
48
|
const command = signInCommand(pause);
|
|
49
49
|
return command === "claude /login" ? "claude auth login" : command;
|
|
50
50
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lead's voice, enforced: every chat surface (console, terminal, Telegram, Slack, Discord, Teams) runs a reply
|
|
3
|
+
* through here before it is sent. The contract asks for plain text; this makes it so, without changing what the reply
|
|
4
|
+
* says. Shaping is channel-neutral and idempotent — it yields a small canonical form (`**anchor**`, `[label](url)`,
|
|
5
|
+
* `code`) — and each channel renders that form in its own format.
|
|
6
|
+
*
|
|
7
|
+
* - Markdown headers become plain lines.
|
|
8
|
+
* - Bold stays on at most three short anchors; the rest is plain.
|
|
9
|
+
* - Bare URLs become labelled links, named from their target: "the task", "the result", "Settings → Lead", else the host.
|
|
10
|
+
* - Internal ids (run #12, digests, r1-style project ids) are removed unless the owner asked for them. When a reply names
|
|
11
|
+
* several of one kind (two runs, two projects), they stay: two items must never read the same.
|
|
12
|
+
* - Runs of blank lines collapse to one.
|
|
13
|
+
* Code (inline or fenced) is left exactly as written.
|
|
14
|
+
*/
|
|
15
|
+
export type ReplyChannel = "console" | "terminal" | "telegram" | "slack" | "discord" | "teams";
|
|
16
|
+
export type ShapeOptions = {
|
|
17
|
+
/** The owner's message this reply answers: when it asks for ids, the ids stay. */
|
|
18
|
+
asked?: string;
|
|
19
|
+
/** This app's own origin (or origins: the console's address and its public one). Only a link there is named as the
|
|
20
|
+
* app's page ("the task"); every other link is named by its real host, whatever label the model wrote, so a reply can
|
|
21
|
+
* never dress a foreign link up as ours. */
|
|
22
|
+
appOrigin?: string | readonly (string | null)[] | null;
|
|
23
|
+
};
|
|
24
|
+
export declare const BOLD_ANCHORS = 3;
|
|
25
|
+
/** A bold anchor is a few words, not a sentence. */
|
|
26
|
+
export declare const BOLD_ANCHOR_CHARS = 40;
|
|
27
|
+
/** Did the owner ask for ids (an id, a digest, a hash, a run number or a project's r-number)? */
|
|
28
|
+
export declare function askedForIds(asked: string | undefined): boolean;
|
|
29
|
+
/** What a link is called, from where it goes: the app's own task, result and settings pages by name (only on `appOrigin`); anything else by host. */
|
|
30
|
+
export declare function linkLabel(url: string, appOrigin?: ShapeOptions["appOrigin"]): string;
|
|
31
|
+
/**
|
|
32
|
+
* The canonical shaped reply: plain lines, at most three bold anchors, labelled links, no internal ids unless asked
|
|
33
|
+
* (or unless several of a kind would otherwise read the same), single blank lines. Idempotent: shaping a shaped reply
|
|
34
|
+
* changes nothing.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shapeReply(text: string, options?: ShapeOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* A long reply split into parts of at most `size` characters, then each part shaped: the cut is made on the reply as
|
|
39
|
+
* written, at a paragraph or line where possible and never inside code, a link or a bold anchor, so no part carries
|
|
40
|
+
* half of one. The parts share one bold budget and one rule for ids, as if the reply were shaped whole. `measure` is
|
|
41
|
+
* how long a shaped part is once the channel renders it (its own link and escape format): a part that renders past
|
|
42
|
+
* `size` is cut again, smaller. Only a single link longer than a part can exceed it, and it is never cut.
|
|
43
|
+
*/
|
|
44
|
+
export declare function shapeReplyParts(text: string, size: number, options?: ShapeOptions, measure?: (shaped: string) => number): string[];
|
|
45
|
+
export type ReplyPiece = {
|
|
46
|
+
kind: "text";
|
|
47
|
+
text: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "bold";
|
|
50
|
+
text: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: "code";
|
|
53
|
+
text: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "link";
|
|
56
|
+
label: string;
|
|
57
|
+
url: string;
|
|
58
|
+
};
|
|
59
|
+
/** A shaped reply as pieces: plain text, bold anchors, inline code and labelled links. Fenced code stays text. */
|
|
60
|
+
export declare function replyPieces(shaped: string): ReplyPiece[];
|
|
61
|
+
/** Discord's presentation syntax, escaped; mentions are broken so nothing pings. */
|
|
62
|
+
export declare const discordPlain: (text: string) => string;
|
|
63
|
+
/** One line of a shaped reply as console HTML (the console lays out paragraphs and lists itself). */
|
|
64
|
+
export declare function replyHtmlInline(shapedLine: string): string;
|
|
65
|
+
/** A shaped reply in a channel's own text format (Telegram's formatting rides entities: see `telegramReply`). */
|
|
66
|
+
export declare function renderReply(shaped: string, channel: ReplyChannel): string;
|
|
67
|
+
export type TelegramEntity = {
|
|
68
|
+
type: "bold" | "code";
|
|
69
|
+
offset: number;
|
|
70
|
+
length: number;
|
|
71
|
+
} | {
|
|
72
|
+
type: "text_link";
|
|
73
|
+
offset: number;
|
|
74
|
+
length: number;
|
|
75
|
+
url: string;
|
|
76
|
+
};
|
|
77
|
+
/** A shaped reply for Telegram: plain text plus entities (bold anchors, code, labelled links). Offsets are UTF-16, as JS strings are. */
|
|
78
|
+
export declare function telegramReply(shaped: string): {
|
|
79
|
+
text: string;
|
|
80
|
+
entities: TelegramEntity[];
|
|
81
|
+
};
|
|
82
|
+
/** Shape and render in one step, for a surface that sends the reply as text. */
|
|
83
|
+
export declare function voiceReply(text: string, channel: ReplyChannel, options?: ShapeOptions): string;
|
|
84
|
+
/**
|
|
85
|
+
* The words in a reply that say something is being sent or attached ("I've attached the log", "here's the
|
|
86
|
+
* screenshot"), or null. A claim needs an attachment noun and a send verb; one whose content the reply lists itself
|
|
87
|
+
* ("I've included the files I changed: a.ts") is not a claim.
|
|
88
|
+
*/
|
|
89
|
+
export declare function deliverableClaim(text: string): string | null;
|
|
90
|
+
/** Does the reply carry the thing itself: a link, or content quoted in a code block? */
|
|
91
|
+
export declare function replyCarriesDeliverable(text: string): boolean;
|
|
92
|
+
export declare const NOTHING_ATTACHED = "I couldn't attach that to this reply.";
|
|
93
|
+
/** The reply without its claims of an attachment, said plainly once; whatever a claim listed stays; never the claim alone. */
|
|
94
|
+
export declare function dropDeliverableClaims(text: string): string;
|
|
95
|
+
/** The one repair step's words to the lead: attach it, or drop the claim. */
|
|
96
|
+
export declare function deliverableRepair(claim: string): string;
|