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/mate.js
CHANGED
|
@@ -11,6 +11,7 @@ import { MATE_MAX_PROPOSALS_PER_TURN, MATE_TOOL_SCHEMAS, executeMateTool, isMate
|
|
|
11
11
|
import { composeSubscriptionMatePrompt, performSubscriptionMateRequest } from "./subscription-chat.js";
|
|
12
12
|
import { leadContext } from './lead-context.js';
|
|
13
13
|
import { envValue } from "./names.js";
|
|
14
|
+
import { deliverableClaim, deliverableRepair, dropDeliverableClaims, replyCarriesDeliverable } from "./reply-shape.js";
|
|
14
15
|
export const MATE_MESSAGE_MAX_CHARS = 2_000;
|
|
15
16
|
/** Where a promise made this turn is reported: the chat app it arrived on, or this conversation for the console and terminal. */
|
|
16
17
|
function promiseChannelOfTurn(channel) {
|
|
@@ -41,6 +42,47 @@ export const MATE_REFUSAL_COPY = {
|
|
|
41
42
|
"request-changed": "That send was already received with different text or task context. Reload the conversation before sending a new message.",
|
|
42
43
|
channel: "This conversation's connection changed, so the message wasn't sent. Reconnect it, then send again.",
|
|
43
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.",
|
|
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
|
+
}
|
|
44
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"]);
|
|
45
87
|
/** The last messages of the thread as provider-neutral history, newest kept first until the byte cap. */
|
|
46
88
|
export function historyFor(store, thread, queuedMessageId) {
|
|
@@ -222,6 +264,7 @@ export async function runMateTurn(input) {
|
|
|
222
264
|
const readDecisions = new Map();
|
|
223
265
|
const readResults = new Map();
|
|
224
266
|
const searchedMemory = new Map();
|
|
267
|
+
const checkedCapabilities = new Map();
|
|
225
268
|
/** The turn's one question to its owner: shown with the reply only once the turn answers; a failed turn drops it. */
|
|
226
269
|
const ask = (question, options) => store.recordMateAsk({ turn: turnId, thread: thread.id, question, options }, clock());
|
|
227
270
|
let tokensIn = 0;
|
|
@@ -256,20 +299,30 @@ export async function runMateTurn(input) {
|
|
|
256
299
|
// AFTER it resolves, immediately before sending context or using a tool.
|
|
257
300
|
const guard = (channel) => {
|
|
258
301
|
if (!stillOurs())
|
|
259
|
-
return { ok: false, turn: turnId, failed: "superseded", message:
|
|
302
|
+
return { ok: false, turn: turnId, failed: "superseded", message: MATE_FAILURE_COPY.stopped, unknownSpend: false };
|
|
260
303
|
if (!channel.ok)
|
|
261
|
-
return fail("revoked",
|
|
304
|
+
return fail("revoked", MATE_CHANNEL_COPY[channel.reason] ?? MATE_CHANNEL_COPY["access-changed"], false);
|
|
262
305
|
const standing = reproveApprover(store, who);
|
|
263
306
|
const liveSession = store.getMateSession(session.id);
|
|
264
307
|
const liveThread = store.getMateThread(thread.id);
|
|
265
308
|
if (!standing.ok || liveSession === null || liveSession.endedAt !== null || liveThread === null || liveThread.closedAt !== null) {
|
|
266
|
-
return fail("revoked",
|
|
309
|
+
return fail("revoked", MATE_FAILURE_COPY.ended, false);
|
|
267
310
|
}
|
|
268
311
|
return null;
|
|
269
312
|
};
|
|
270
313
|
let reply = null;
|
|
271
314
|
let stoppedAtCap = false;
|
|
272
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
|
+
};
|
|
273
326
|
while (steps < maxSteps) {
|
|
274
327
|
const blocked = guard(input.revalidate === undefined ? { ok: true } : await input.revalidate());
|
|
275
328
|
if (blocked !== null)
|
|
@@ -277,22 +330,22 @@ export async function runMateTurn(input) {
|
|
|
277
330
|
now = clock();
|
|
278
331
|
const remainingMs = TURN_WALL_CLOCK_MS - (now.getTime() - turnStartedAt);
|
|
279
332
|
if (remainingMs <= 0)
|
|
280
|
-
return fail("timeout",
|
|
333
|
+
return fail("timeout", MATE_FAILURE_COPY.tooLong, false);
|
|
281
334
|
const request = direct ? composeDirect(input.key) : composeSubscription();
|
|
282
335
|
// Tool results join the outbound body: scanned again before every dispatch.
|
|
283
336
|
const outbound = typeof request === "string" ? request : request.body;
|
|
284
337
|
if (scanForSecrets(outbound).length > 0) {
|
|
285
|
-
return fail("secret-refused",
|
|
338
|
+
return fail("secret-refused", MATE_FAILURE_COPY.secretRead, false);
|
|
286
339
|
}
|
|
287
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);
|
|
288
341
|
if (!step.ok) {
|
|
289
342
|
if (step.reason === "latched")
|
|
290
|
-
return fail("latched",
|
|
291
|
-
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 };
|
|
292
345
|
}
|
|
293
346
|
const stepStarted = store.startChatTurn(step.id, now);
|
|
294
347
|
if (!stepStarted.ok)
|
|
295
|
-
return fail("provider-error",
|
|
348
|
+
return fail("provider-error", MATE_FAILURE_COPY.notStarted, false);
|
|
296
349
|
steps++;
|
|
297
350
|
progress({ kind: "step", turn: turnId, step: steps });
|
|
298
351
|
const requestBytes = Buffer.byteLength(outbound, "utf8");
|
|
@@ -337,36 +390,36 @@ export async function runMateTurn(input) {
|
|
|
337
390
|
if (!direct) {
|
|
338
391
|
finishStep({ state: "failed", failureReason: result.problem === "timeout" ? "timeout" : result.problem === "malformed-reply" ? "malformed-reply" : "provider-error", settledMicrousd: 0 });
|
|
339
392
|
const message = result.problem === "not-found"
|
|
340
|
-
?
|
|
393
|
+
? MATE_FAILURE_COPY.notInstalled
|
|
341
394
|
: result.problem === "timeout"
|
|
342
|
-
?
|
|
395
|
+
? MATE_FAILURE_COPY.tooLong
|
|
343
396
|
: result.problem === "malformed-reply"
|
|
344
|
-
?
|
|
345
|
-
:
|
|
397
|
+
? MATE_FAILURE_COPY.unreadable
|
|
398
|
+
: MATE_FAILURE_COPY.signIn;
|
|
346
399
|
return fail(result.problem === "timeout" ? "timeout" : result.problem === "malformed-reply" ? "malformed-reply" : "provider-error", message, false);
|
|
347
400
|
}
|
|
348
401
|
if (result.problem.startsWith("status-")) {
|
|
349
402
|
// The provider ANSWERED with an error: nothing billed for this step.
|
|
350
403
|
finishStep({ state: "failed", failureReason: "provider-error", settledMicrousd: 0 });
|
|
351
|
-
return fail("provider-error",
|
|
404
|
+
return fail("provider-error", MATE_FAILURE_COPY.refused, false);
|
|
352
405
|
}
|
|
353
406
|
if (result.problem === "timeout") {
|
|
354
407
|
finishStep({ state: "failed", failureReason: "timeout", settledMicrousd: null, unknownSpend: true });
|
|
355
|
-
return fail("timeout",
|
|
408
|
+
return fail("timeout", MATE_FAILURE_COPY.tooLongUnknownCost, true);
|
|
356
409
|
}
|
|
357
410
|
if (result.problem === "network") {
|
|
358
411
|
finishStep({ state: "failed", failureReason: "provider-error", settledMicrousd: null, unknownSpend: true });
|
|
359
|
-
return fail("provider-error",
|
|
412
|
+
return fail("provider-error", MATE_FAILURE_COPY.lostUnknownCost, true);
|
|
360
413
|
}
|
|
361
414
|
finishStep({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
362
|
-
return fail("malformed-reply",
|
|
415
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unreadableUnknownCost, true);
|
|
363
416
|
}
|
|
364
417
|
const answer = result.answer;
|
|
365
418
|
// Usage that cannot be true — more input tokens than bytes sent — is a
|
|
366
419
|
// malformed reply with unknown cost, never a number to settle by.
|
|
367
420
|
if (direct && answer.tokensIn > requestBytes) {
|
|
368
421
|
finishStep({ state: "failed", failureReason: "malformed-reply", settledMicrousd: null, unknownSpend: true });
|
|
369
|
-
return fail("malformed-reply",
|
|
422
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unreadableUnknownCost, true);
|
|
370
423
|
}
|
|
371
424
|
// The pinned math, or the provider's own reported charge when HIGHER.
|
|
372
425
|
const stepSettled = direct
|
|
@@ -385,15 +438,23 @@ export async function runMateTurn(input) {
|
|
|
385
438
|
return changed;
|
|
386
439
|
if (answer.calls.length === 0) {
|
|
387
440
|
if (answer.text.trim() === "")
|
|
388
|
-
return fail("malformed-reply",
|
|
389
|
-
|
|
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);
|
|
390
451
|
break;
|
|
391
452
|
}
|
|
392
453
|
if (answer.calls.length > MATE_MAX_CALLS_PER_STEP)
|
|
393
|
-
return fail("malformed-reply",
|
|
454
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unusable, false);
|
|
394
455
|
for (const call of answer.calls) {
|
|
395
456
|
if (!isMateTool(call.name))
|
|
396
|
-
return fail("malformed-reply",
|
|
457
|
+
return fail("malformed-reply", MATE_FAILURE_COPY.unusable, false);
|
|
397
458
|
}
|
|
398
459
|
history.push({ role: "assistant", text: answer.text, calls: answer.calls });
|
|
399
460
|
for (const [index, call] of answer.calls.entries()) {
|
|
@@ -403,9 +464,11 @@ export async function runMateTurn(input) {
|
|
|
403
464
|
return changed;
|
|
404
465
|
}
|
|
405
466
|
progress({ kind: "tool", turn: turnId, step: steps, label: mateToolLabel(call.name) });
|
|
406
|
-
const outcome = executeMateTool({ store, who, now: clock(), draft, selectEvidence, step: steps, readDecisions, readResults, searchedMemory, 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);
|
|
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);
|
|
407
468
|
if (READ_TOOLS.has(call.name))
|
|
408
469
|
reads++;
|
|
470
|
+
if (call.name === "show_control" && outcome.ok)
|
|
471
|
+
shownControl = true;
|
|
409
472
|
// Opt-in, local diagnostics for end-to-end runs: what the lead asked of each tool and what came back (its start), keys
|
|
410
473
|
// blanked. Each line stays whole JSON so a run can assert on what a tool returned rather than on the model's words.
|
|
411
474
|
if (envValue(process.env, "MATE_TRACE") === "1") {
|
|
@@ -419,16 +482,17 @@ export async function runMateTurn(input) {
|
|
|
419
482
|
}
|
|
420
483
|
if (reply === null) {
|
|
421
484
|
stoppedAtCap = true;
|
|
422
|
-
|
|
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.)`;
|
|
423
487
|
}
|
|
424
488
|
// Ruling 11: model text is scanned before it becomes durable.
|
|
425
489
|
if (scanForSecrets(reply).length > 0)
|
|
426
|
-
return fail("secret-refused",
|
|
490
|
+
return fail("secret-refused", MATE_FAILURE_COPY.secretReply, false);
|
|
427
491
|
now = clock();
|
|
428
492
|
const activity = activitySummary(reads, proposals, steps);
|
|
429
493
|
// One write (finding 12): settle, debit, promote the drafts, append the assistant text.
|
|
430
494
|
const finalized = store.finalizeMateTurn(turnId, started.generation, { state: "answered", settledMicrousd: settled, tokensIn, tokensOut, message: { text: reply, activity } }, now);
|
|
431
495
|
if (!finalized)
|
|
432
|
-
return { ok: false, turn: turnId, failed: "superseded", message:
|
|
496
|
+
return { ok: false, turn: turnId, failed: "superseded", message: MATE_FAILURE_COPY.stopped, unknownSpend: false };
|
|
433
497
|
return { ok: true, turn: turnId, reply, activity, proposals, steps, stoppedAtCap, settledMicrousd: settled };
|
|
434
498
|
}
|
package/dist/operate.js
CHANGED
|
@@ -11643,6 +11643,8 @@ async function chatCommand(flags, context) {
|
|
|
11643
11643
|
ceilingUsd: ceilingGiven === undefined ? undefined : Number(ceilingGiven),
|
|
11644
11644
|
...(context.mateSeams === undefined ? {} : { seams: { ...context.mateSeams, clock: context.mateSeams.clock ?? context.clock } }),
|
|
11645
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))],
|
|
11646
11648
|
});
|
|
11647
11649
|
return result.code;
|
|
11648
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/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;
|