toolroll 0.9.26 → 0.9.28
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 +25 -0
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/dist/agentconfig.js +71 -3
- package/dist/brand-icons.d.ts +168 -0
- package/dist/brand-icons.js +32 -0
- package/dist/brand-mark.d.ts +27 -0
- package/dist/brand-mark.js +49 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +41 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -23
- package/dist/browser-workspace.d.ts +17 -0
- package/dist/build-review.js +3 -2
- package/dist/builder.js +10 -8
- package/dist/chat-actions.js +7 -4
- package/dist/chat-delivery.js +3 -3
- package/dist/chat-flow.d.ts +4 -2
- package/dist/chat-flow.js +36 -10
- package/dist/converse.js +3 -2
- package/dist/coordinator-proposals.js +2 -2
- package/dist/decision.d.ts +3 -3
- package/dist/decision.js +4 -3
- package/dist/desktop-update.d.ts +2 -2
- package/dist/evidence.js +1 -1
- package/dist/flow-actions.d.ts +1 -0
- package/dist/flow-actions.js +6 -7
- package/dist/flow-code.d.ts +3 -2
- package/dist/flow-code.js +6 -4
- package/dist/flow-draft.d.ts +10 -4
- package/dist/flow-draft.js +19 -8
- package/dist/flow-engine.d.ts +0 -3
- package/dist/flow-engine.js +17 -10
- package/dist/flow-items.d.ts +46 -0
- package/dist/flow-items.js +152 -0
- package/dist/flow-people.d.ts +0 -1
- package/dist/flow-people.js +9 -1
- package/dist/flow-send.d.ts +5 -0
- package/dist/flow-send.js +33 -6
- package/dist/flow-steps.js +33 -10
- package/dist/flows-ui.js +5 -1
- package/dist/flows.d.ts +23 -8
- package/dist/flows.js +106 -52
- package/dist/guides.js +1 -1
- package/dist/integrations-ui.js +10 -3
- package/dist/integrations.d.ts +2 -0
- package/dist/integrations.js +2 -2
- package/dist/lead-context.d.ts +3 -9
- package/dist/lead-context.js +21 -5
- package/dist/mate-doors.js +8 -2
- package/dist/mate-tools.js +27 -10
- package/dist/mate.js +13 -2
- package/dist/operate.d.ts +3 -0
- package/dist/operate.js +122 -8
- package/dist/phase-routing.d.ts +86 -5
- package/dist/phase-routing.js +228 -16
- package/dist/project-skills.d.ts +10 -0
- package/dist/proposal.d.ts +10 -2
- package/dist/proposal.js +25 -10
- package/dist/recipe-ui.js +2 -1
- package/dist/result-shots.d.ts +5 -1
- package/dist/result-shots.js +33 -3
- package/dist/scout-report.d.ts +10 -4
- package/dist/scout-report.js +23 -6
- package/dist/scout.d.ts +2 -0
- package/dist/scout.js +84 -11
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +76 -23
- package/dist/store.d.ts +64 -5
- package/dist/store.js +192 -7
- package/dist/task-sizing.d.ts +78 -0
- package/dist/task-sizing.js +228 -0
- package/dist/task-text.d.ts +4 -4
- package/dist/task-text.js +10 -6
- package/dist/teammate-work.js +31 -7
- package/dist/teammates.d.ts +20 -1
- package/dist/teammates.js +18 -5
- package/dist/teams-chat.d.ts +4 -1
- package/dist/teams-chat.js +17 -6
- package/dist/telegram-flow.js +6 -2
- package/dist/telegram.js +37 -6
- package/dist/test-temp.js +1 -1
- package/dist/text-limits.d.ts +67 -0
- package/dist/text-limits.js +73 -0
- package/dist/tier-report.d.ts +44 -0
- package/dist/tier-report.js +71 -0
- package/dist/tools-ui.d.ts +1 -3
- package/dist/tools-ui.js +5 -4
- package/package.json +3 -1
package/dist/flows.js
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
* The engine is deterministic and model-free: it runs in the worker's pass
|
|
48
48
|
* beside routines, and no card ever skips an approval the task itself needs.
|
|
49
49
|
*/
|
|
50
|
+
import { TEXT_LIMITS } from "./text-limits.js";
|
|
50
51
|
import { createHash } from "node:crypto";
|
|
51
52
|
export const FLOW_STAGE_KINDS = ["inbox", "task", "report", "approval", "check", "pull-request", "update", "notify", "sort", "draft", "request", "email", "tool", "wait", "teammate", "send", "choose", "done"];
|
|
52
53
|
export const FLOW_COLORS = ["slate", "blue", "violet", "amber", "green", "rose"];
|
|
@@ -310,7 +311,8 @@ export function validateFlowDefinition(input, options = {}) {
|
|
|
310
311
|
return {
|
|
311
312
|
id, title, kind: kind,
|
|
312
313
|
zone: { x: coordinate(zone["x"], -20000, 20000, index * 320), y: coordinate(zone["y"], -20000, 20000, 0), w: coordinate(zone["w"], 220, 1200, 280), h: coordinate(zone["h"], 160, 1600, 360), color },
|
|
313
|
-
|
|
314
|
+
// Reading a saved flow never re-checks length: a limit is for writing.
|
|
315
|
+
instructions: text(stage["instructions"], options.stored ? Number.POSITIVE_INFINITY : TEXT_LIMITS.flowInstructions),
|
|
314
316
|
planning: kind === "task" ? planning ?? "auto" : null,
|
|
315
317
|
approver: kind === "approval" && stage["toOwner"] !== true ? text(stage["approver"], 64) : null,
|
|
316
318
|
...(kind === "approval" && stage["toOwner"] === true ? { toOwner: true } : {}),
|
|
@@ -356,11 +358,6 @@ export function validateFlowDefinition(input, options = {}) {
|
|
|
356
358
|
throw new Error(`Zone ${stage.title}: an answer can't send cards back into the same zone.`);
|
|
357
359
|
if ((stage.kind === "task" || stage.kind === "report") && stage.instructions === null)
|
|
358
360
|
throw new Error(`Zone ${stage.title}: say what the agent should do.`);
|
|
359
|
-
// A work zone's goal must fit a task: its own words whole, with room left for the card's details. Refused here, when the flow
|
|
360
|
-
// is saved, never when a card arrives. A flow saved before this rule is still read, and its card says why it can't file.
|
|
361
|
-
const over = !options.stored && (stage.kind === "task" || stage.kind === "report") && stage.instructions !== null ? flowGoalOverflow(stage.instructions) : 0;
|
|
362
|
-
if (over > 0)
|
|
363
|
-
throw new Error(`Zone ${stage.title}: its instructions leave no room for the card's details. Shorten them by ${over} characters.`);
|
|
364
361
|
if (stage.kind === "draft" && stage.instructions === null)
|
|
365
362
|
throw new Error(`Zone ${stage.title}: say what Claude should write.`);
|
|
366
363
|
if (stage.kind === "notify" && stage.message === null)
|
|
@@ -381,6 +378,59 @@ export function validateFlowDefinition(input, options = {}) {
|
|
|
381
378
|
}
|
|
382
379
|
return { version: 1, start, stages };
|
|
383
380
|
}
|
|
381
|
+
/** The instructions 0.9.26 and earlier read from a saved zone: they re-check this length on every read, and a flow with a
|
|
382
|
+
* longer one won't load. A saved zone keeps its first part under it, and the rest in `instructionsMore`, which they ignore. */
|
|
383
|
+
export const FLOW_INSTRUCTIONS_STORED = 4000;
|
|
384
|
+
/** A flow's definition as the store saves it: any zone's instructions over FLOW_INSTRUCTIONS_STORED are split, so a
|
|
385
|
+
* person who goes back a version still opens the flow (with its instructions' first part). Unchanged otherwise. */
|
|
386
|
+
export function flowDefinitionForStore(json) {
|
|
387
|
+
let raw;
|
|
388
|
+
try {
|
|
389
|
+
raw = JSON.parse(json);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return json;
|
|
393
|
+
}
|
|
394
|
+
const stages = raw?.stages;
|
|
395
|
+
if (!Array.isArray(stages))
|
|
396
|
+
return json;
|
|
397
|
+
let split = false;
|
|
398
|
+
const kept = stages.map(one => {
|
|
399
|
+
const stage = one;
|
|
400
|
+
const words = stage?.["instructions"];
|
|
401
|
+
if (stage === null || typeof stage !== "object" || typeof words !== "string" || words.length <= FLOW_INSTRUCTIONS_STORED)
|
|
402
|
+
return one;
|
|
403
|
+
let at = FLOW_INSTRUCTIONS_STORED;
|
|
404
|
+
if (/[\uD800-\uDBFF]/.test(words[at - 1]))
|
|
405
|
+
at--;
|
|
406
|
+
split = true;
|
|
407
|
+
return { ...stage, instructions: words.slice(0, at), instructionsMore: words.slice(at) };
|
|
408
|
+
});
|
|
409
|
+
return split ? JSON.stringify({ ...raw, stages: kept }) : json;
|
|
410
|
+
}
|
|
411
|
+
/** A saved flow's definition with every zone's instructions whole again (flowDefinitionForStore). */
|
|
412
|
+
export function flowDefinitionFromStore(json) {
|
|
413
|
+
if (!json.includes("\"instructionsMore\""))
|
|
414
|
+
return json;
|
|
415
|
+
let raw;
|
|
416
|
+
try {
|
|
417
|
+
raw = JSON.parse(json);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return json;
|
|
421
|
+
}
|
|
422
|
+
const stages = raw?.stages;
|
|
423
|
+
if (!Array.isArray(stages))
|
|
424
|
+
return json;
|
|
425
|
+
const whole = stages.map(one => {
|
|
426
|
+
const stage = one;
|
|
427
|
+
if (stage === null || typeof stage !== "object" || typeof stage["instructionsMore"] !== "string")
|
|
428
|
+
return one;
|
|
429
|
+
const { instructionsMore, ...rest } = stage;
|
|
430
|
+
return { ...rest, instructions: `${typeof stage["instructions"] === "string" ? stage["instructions"] : ""}${instructionsMore}` };
|
|
431
|
+
});
|
|
432
|
+
return JSON.stringify({ ...raw, stages: whole });
|
|
433
|
+
}
|
|
384
434
|
/** A Pull request zone's merge: true is squash, the default. */
|
|
385
435
|
function validateMerge(value, title) {
|
|
386
436
|
if (value === true)
|
|
@@ -498,12 +548,10 @@ const FILLED = /\{\{\s*(card\.title|card\.description|card\.email|note|stage\.([
|
|
|
498
548
|
export function fillFlowText(template, card, encode = value => value) {
|
|
499
549
|
return template.replace(FILLED, (_match, key, stage) => encode(fillOne(key, stage, card))).trim();
|
|
500
550
|
}
|
|
501
|
-
/** The task door's goal limit (task-text.ts). A work zone's goal is kept
|
|
502
|
-
export const FLOW_GOAL_LIMIT =
|
|
503
|
-
/** What stands in
|
|
504
|
-
export const
|
|
505
|
-
/** The least room each filled-in value keeps: the cut mark and 20 characters from each end. */
|
|
506
|
-
const VALUE_ROOM = FLOW_CUT.length + 40;
|
|
551
|
+
/** The task door's goal limit (task-text.ts). A work zone's goal is kept within it. */
|
|
552
|
+
export const FLOW_GOAL_LIMIT = TEXT_LIMITS.goal;
|
|
553
|
+
/** What stands in a goal for a filled-in value too long to hold whole: the agent is given it whole, attached (flowGoalCuts). */
|
|
554
|
+
export const flowAttachedMark = (label) => `[${label}: attached in full below]`;
|
|
507
555
|
/** A work zone's words, plus the card and any send-back note when the words leave them out. "Fix the bug on the card"
|
|
508
556
|
* says nothing on its own (found in the real e2e: the builder saw only the branch name). */
|
|
509
557
|
export function flowWorkTemplate(instructions, card) {
|
|
@@ -515,50 +563,56 @@ export function flowWorkTemplate(instructions, card) {
|
|
|
515
563
|
];
|
|
516
564
|
return [instructions, ...extra].join("\n\n");
|
|
517
565
|
}
|
|
518
|
-
/** Characters a work zone's instructions leave no room for, with every filled-in value kept to its least room. 0 when they fit. */
|
|
519
|
-
function flowGoalOverflow(instructions) {
|
|
520
|
-
const widest = flowWorkTemplate(instructions, { description: "x", note: "x" });
|
|
521
|
-
const values = [...widest.matchAll(FILLED)].length;
|
|
522
|
-
return Math.max(0, widest.replace(FILLED, "").length + values * VALUE_ROOM - (FLOW_GOAL_LIMIT - 1));
|
|
523
|
-
}
|
|
524
566
|
/** Script output can carry carriage returns, colour codes and hidden marks a task goal refuses; the goal drops them. */
|
|
525
567
|
const goalSafe = (value) => value.replace(/\r\n?/g, "\n").replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u2028\u2029\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g, "");
|
|
526
|
-
/**
|
|
527
|
-
function
|
|
528
|
-
if (
|
|
529
|
-
return
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
|
|
568
|
+
/** How a filled-in value is named where it is attached: the same words flowGoalCuts labels it with. */
|
|
569
|
+
export function flowValueLabel(key, stageTitle = id => id) {
|
|
570
|
+
if (key === "card.title")
|
|
571
|
+
return "The card's title";
|
|
572
|
+
if (key === "card.description")
|
|
573
|
+
return "The card's description";
|
|
574
|
+
if (key === "card.email")
|
|
575
|
+
return "The card's email address";
|
|
576
|
+
if (key === "note")
|
|
577
|
+
return "The note it was sent back with";
|
|
578
|
+
const match = /^stage\.([a-z0-9-]+)(?:\.(items|report))?$/.exec(key);
|
|
579
|
+
if (match === null)
|
|
580
|
+
return key;
|
|
581
|
+
return `What ${stageTitle(match[1])} ${match[2] === "items" ? "listed" : match[2] === "report" ? "reported in full" : "found"}`;
|
|
537
582
|
}
|
|
538
|
-
/** A work zone's goal, filled from the card and kept
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (each < FLOW_CUT.length + 2)
|
|
558
|
-
return null;
|
|
583
|
+
/** A work zone's goal, filled from the card and kept within `limit`: the zone's own words stay whole, and every filled-in
|
|
584
|
+
* value goes in whole. When they don't all fit, the longest values are attached instead (each named in the goal by
|
|
585
|
+
* flowAttachedMark, and given to the agent whole by flowGoalCuts) until the rest fits: nothing is cut. Words that fill
|
|
586
|
+
* the limit to the last character leave attached values a shorter mark, then none (they are still attached). Null only
|
|
587
|
+
* when the template's own words are over the limit: the caller then files the zone's words alone (flowGoal). */
|
|
588
|
+
export function fitFlowText(template, card, limit = FLOW_GOAL_LIMIT, stageTitle) {
|
|
589
|
+
const matches = [...template.matchAll(FILLED)];
|
|
590
|
+
const values = matches.map(match => goalSafe(fillOne(match[1], match[2], card)));
|
|
591
|
+
const marks = matches.map(match => flowAttachedMark(flowValueLabel(match[1], stageTitle)));
|
|
592
|
+
const attached = new Set();
|
|
593
|
+
const build = () => { let at = 0; return template.replace(FILLED, () => { const i = at++; return attached.has(i) ? marks[i] : values[i]; }).trim(); };
|
|
594
|
+
let goal = build();
|
|
595
|
+
// The longest first, so the fewest values leave the goal.
|
|
596
|
+
const order = values.map((value, i) => i).filter(i => values[i].length > marks[i].length).sort((a, b) => values[b].length - values[a].length);
|
|
597
|
+
for (const i of order) {
|
|
598
|
+
if (goal.length <= limit)
|
|
599
|
+
break;
|
|
600
|
+
attached.add(i);
|
|
601
|
+
goal = build();
|
|
559
602
|
}
|
|
560
|
-
|
|
561
|
-
|
|
603
|
+
for (const shorter of ["[attached]", ""]) {
|
|
604
|
+
if (goal.length <= limit)
|
|
605
|
+
break;
|
|
606
|
+
for (const i of attached)
|
|
607
|
+
marks[i] = shorter;
|
|
608
|
+
goal = build();
|
|
609
|
+
}
|
|
610
|
+
return goal.length <= limit ? goal : null;
|
|
611
|
+
}
|
|
612
|
+
/** A work zone's goal for a card: its words, the card and any note, fitted (fitFlowText); when the zone's words leave no
|
|
613
|
+
* room for the card's own lines, its words alone, with the card attached. Null only for words over the limit. */
|
|
614
|
+
export function flowGoal(instructions, card, stageTitle) {
|
|
615
|
+
return fitFlowText(flowWorkTemplate(instructions, card), card, FLOW_GOAL_LIMIT, stageTitle) ?? fitFlowText(instructions, card, FLOW_GOAL_LIMIT, stageTitle);
|
|
562
616
|
}
|
|
563
617
|
/** The first email address a card mentions, or "" — what {{card.email}} holds. */
|
|
564
618
|
export function cardEmailOf(card) {
|
package/dist/guides.js
CHANGED
|
@@ -207,7 +207,7 @@ const steering = {
|
|
|
207
207
|
${AUTHORITY_LINE}
|
|
208
208
|
|
|
209
209
|
Steering is an OPERATOR ceremony. \`task steer <id> --note "..." --as
|
|
210
|
-
<you> --token <t>\` files one
|
|
210
|
+
<you> --token <t>\` files one note (≤4000 characters) for the
|
|
211
211
|
task's NEXT attempt, and the credential is the point: the note appears
|
|
212
212
|
in the agent's brief under an OPERATOR STEERING heading, so only a
|
|
213
213
|
verified person may put words there. Anonymous or agent-authored notes
|
package/dist/integrations-ui.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { STATE_WORDS } from "./integrations.js";
|
|
8
8
|
import { whenUtc } from "./when-html.js";
|
|
9
|
+
import { brandMarkHtml } from "./brand-mark.js";
|
|
9
10
|
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
10
11
|
const when = (at) => whenUtc(at);
|
|
11
12
|
export const INTEGRATIONS_CSS = `.integrations{max-width:760px;min-width:0}.integrations h2{margin:24px 0 6px;font-size:.9375rem}` +
|
|
12
13
|
`.integrations .integration{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 16px;align-items:start;padding:12px 0;border-top:1px solid var(--so-line);min-width:0}` +
|
|
13
14
|
`.integrations .integration>:not(.integration-action),.integrations .integration-body>*{grid-column:1}.integrations .integration-body{display:contents}.integrations .integration:first-of-type{border-top:0}.integrations .integration-head{margin:0;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;min-width:0}` +
|
|
14
|
-
`.integrations .integration-head strong{overflow-wrap:anywhere}.integrations .integration p{margin:2px 0 0;min-width:0;overflow-wrap:anywhere}` +
|
|
15
|
+
`.integrations .integration .integration-body>*{margin-left:calc(var(--brand-mark) + 10px)}.integrations .integration-head strong{overflow-wrap:anywhere}.integrations .integration p{margin:2px 0 0;min-width:0;overflow-wrap:anywhere}` +
|
|
15
16
|
`.integrations .integration-action{grid-column:2;grid-row:1 / span 3;align-self:center;margin:0}.integrations .integration-action button,.integrations .integration-action .button-link{min-height:44px;white-space:nowrap}` +
|
|
16
17
|
`.integrations .integration .integration-action button{width:auto}.integrations .integration .integration-action a.integration-quiet{background:var(--so-paper);color:var(--so-ink);border:1px solid var(--so-input-line)}` +
|
|
17
18
|
`.integrations .integration-fix{color:var(--so-danger)}.integrations code{overflow-wrap:anywhere}.integrations details{margin-top:4px;font-size:.8125rem}.integrations summary{cursor:pointer;padding:4px 0}` +
|
|
@@ -21,7 +22,7 @@ export const INTEGRATIONS_CSS = `.integrations{max-width:760px;min-width:0}.inte
|
|
|
21
22
|
`.integration-state--not-set-up,.integration-state--checking{color:var(--so-muted);background:var(--so-neutral-soft)}` +
|
|
22
23
|
// A phone: the action sits at the right of the name line, and the facts under it run on as one line.
|
|
23
24
|
`@media (max-width:760px){.integrations h2{margin:16px 0 0}.integrations .integration{gap:0 12px;padding:6px 0 8px}.integrations .integration-head{min-height:44px}` +
|
|
24
|
-
`.integrations .integration-action{grid-row:1;align-self:center}.integrations .integration-body{display:block;grid-column:1 / -1;line-height:1.35}` +
|
|
25
|
+
`.integrations .integration-action{grid-row:1;align-self:center}.integrations .integration-body{display:block;grid-column:1 / -1;line-height:1.35;padding-left:calc(var(--brand-mark) + 10px)}.integrations .integration .integration-body>*{margin-left:0}` +
|
|
25
26
|
`.integrations .integration-body>p{margin:0}.integrations .integration-body>p.meta{display:inline}.integrations .integration-body>p.meta+p.meta::before{content:" · "}` +
|
|
26
27
|
`.integrations .integration-body>:not(.meta){margin-top:4px}.integrations .integration-body>:not(.meta)+p.meta{display:block;margin-top:2px}.integrations details{margin-top:2px}}`;
|
|
27
28
|
const GROUPS = [
|
|
@@ -39,6 +40,12 @@ function action(one, csrf) {
|
|
|
39
40
|
return `<p class="integration-action"><a class="button-link${a.kind === "setup" ? " integration-quiet" : ""}" href="${e(a.href)}">${a.label}</a></p>`;
|
|
40
41
|
return "";
|
|
41
42
|
}
|
|
43
|
+
/** Which mark a row shows: email is Gmail's only when the account is a Gmail address; a custom tool is a letter. */
|
|
44
|
+
function markFor(one) {
|
|
45
|
+
if (one.custom)
|
|
46
|
+
return null;
|
|
47
|
+
return one.key === "email" && /@(gmail|googlemail)\.com$/i.test(one.account ?? "") ? "gmail" : one.key;
|
|
48
|
+
}
|
|
42
49
|
function row(one, csrf) {
|
|
43
50
|
const badge = !one.checked && one.state === "connected"
|
|
44
51
|
? `<span class="integration-state integration-state--checking"><i aria-hidden="true"></i>Checking</span>`
|
|
@@ -53,7 +60,7 @@ function row(one, csrf) {
|
|
|
53
60
|
? `<p class="meta">Failed ${when(one.lastErrorAt ?? "")}</p>`
|
|
54
61
|
: `<details><summary>Last error ${one.lastErrorAt === null ? "" : when(one.lastErrorAt)}</summary><p class="meta">${e(one.lastError)}</p></details>`;
|
|
55
62
|
return `<div class="integration" data-integration="${e(one.key)}" data-state="${one.state}">` +
|
|
56
|
-
`<p class="integration-head"
|
|
63
|
+
`<p class="integration-head">${brandMarkHtml(markFor(one), one.name, one.state === "connected")}<strong>${e(one.name)}</strong> ${badge}</p>` +
|
|
57
64
|
action(one, csrf) +
|
|
58
65
|
`<div class="integration-body">${facts === "" ? "" : `<p class="meta">${facts}</p>`}${fix}${run}${history}${lastError}</div></div>`;
|
|
59
66
|
}
|
package/dist/integrations.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export type Integration = {
|
|
|
36
36
|
lastErrorAt: string | null;
|
|
37
37
|
usedBy: string[];
|
|
38
38
|
action: IntegrationAction;
|
|
39
|
+
/** A project's own MCP tool: it shows a letter, never a logo matched by name. */
|
|
40
|
+
custom?: true;
|
|
39
41
|
};
|
|
40
42
|
export declare const STATE_WORDS: Record<IntegrationState, string>;
|
|
41
43
|
export type ProbeResult = {
|
package/dist/integrations.js
CHANGED
|
@@ -383,7 +383,7 @@ function targets(io) {
|
|
|
383
383
|
const href = `/settings/tools?repo=${encodeURIComponent(repo)}#tool-${encodeURIComponent(tool.name)}`;
|
|
384
384
|
const test = tool.lastTest;
|
|
385
385
|
list.push({
|
|
386
|
-
key: `mcp:${repo}:${tool.name}`, group: "tools", name: tool.name, configured: true, account: basename(repo),
|
|
386
|
+
key: `mcp:${repo}:${tool.name}`, group: "tools", name: tool.name, configured: true, account: basename(repo), ...(tool.source === "custom" ? { custom: true } : {}),
|
|
387
387
|
setup: place(href), fix: place(href), usedBy: [`Tasks in ${basename(repo)}`],
|
|
388
388
|
activity: test === null ? NO_ACTIVITY : test.ok ? { okAt: test.at, error: null, errorAt: null, failing: false } : { okAt: null, error: test.problem, errorAt: test.at, failing: false },
|
|
389
389
|
probe: async (check) => {
|
|
@@ -432,7 +432,7 @@ function targets(io) {
|
|
|
432
432
|
}
|
|
433
433
|
// ---------------------------------------------------------------- the list
|
|
434
434
|
function combine(target, row) {
|
|
435
|
-
const base = { key: target.key, group: target.group, name: target.name, usedBy: target.usedBy };
|
|
435
|
+
const base = { key: target.key, group: target.group, name: target.name, usedBy: target.usedBy, ...(target.custom ? { custom: true } : {}) };
|
|
436
436
|
const checked = target.probe === null || row !== undefined;
|
|
437
437
|
const lastSuccessAt = later(row?.okAt ?? null, target.activity.okAt);
|
|
438
438
|
const checkError = row?.error ?? null, checkErrorAt = row?.errorAt ?? null;
|
package/dist/lead-context.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
/** Bounded DB memory shared by browser and terminal lead turns. No provider,
|
|
2
|
-
* repository scan, mutation, or hidden approval happens while catching up.
|
|
3
|
-
*
|
|
4
|
-
* The bundle is ordered by importance and built fresh each turn: who the lead
|
|
5
|
-
* is (name, persona), who it is talking to (first name, time zone, today), what
|
|
6
|
-
* it knows about them (their confirmed lines), the people, AI teammates and team
|
|
7
|
-
* chats it works with (one line each), the channel, what needs them now,
|
|
8
|
-
* projects by name with their active decisions, then the rest. Over 8 KB, the
|
|
9
|
-
* least important goes first: the rest, then people, then decisions. */
|
|
10
1
|
import type { Store } from './store.js';
|
|
11
2
|
import { type AssignmentCatchUp } from './assignment-brief.js';
|
|
12
3
|
export declare const LEAD_CONTEXT_MAX_BYTES = 8000;
|
|
13
4
|
/** Where this turn's conversation happens. */
|
|
14
5
|
export type LeadChannel = 'console' | 'terminal' | 'telegram' | 'slack' | 'discord' | 'teams';
|
|
6
|
+
/** One message on the channel, in characters: the lead writes a reply within it (it is told before it writes and asked
|
|
7
|
+
* once to shorten one over it); a longer reply is split across messages, never cut. Null: no platform limit. */
|
|
8
|
+
export declare const LEAD_REPLY_LIMITS: Record<LeadChannel, number | null>;
|
|
15
9
|
export type LeadContextOptions = {
|
|
16
10
|
evidenceRoot?: string;
|
|
17
11
|
/** The person this turn talks to (their account name). */
|
package/dist/lead-context.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** Bounded DB memory shared by browser and terminal lead turns. No provider,
|
|
2
|
+
* repository scan, mutation, or hidden approval happens while catching up.
|
|
3
|
+
*
|
|
4
|
+
* The bundle is ordered by importance and built fresh each turn: who the lead
|
|
5
|
+
* is (name, persona), who it is talking to (first name, time zone, today), what
|
|
6
|
+
* it knows about them (their confirmed lines), the people, AI teammates and team
|
|
7
|
+
* chats it works with (one line each), the channel, what needs them now,
|
|
8
|
+
* projects by name with their active decisions, then the rest. Over 8 KB, the
|
|
9
|
+
* least important goes first: the rest, then people, then decisions. */
|
|
10
|
+
import { PLATFORM_LIMITS } from './text-limits.js';
|
|
1
11
|
import { activeDecisionsOf, assignmentCatchUp } from './assignment-brief.js';
|
|
2
12
|
import { publicChatText } from './chat-display.js';
|
|
3
13
|
import { leadIdentityOf } from './lead-identity.js';
|
|
@@ -37,13 +47,19 @@ function followThrough(store, owner, thread) {
|
|
|
37
47
|
...(corrections.length === 0 ? {} : { followThrough: 'The operator confirmed these corrections since your last reply. Re-check the open proposals and promises listed here; release or replace any they affect and say in one line what you changed.' }) };
|
|
38
48
|
}
|
|
39
49
|
export const LEAD_CONTEXT_MAX_BYTES = 8_000;
|
|
50
|
+
/** One message on the channel, in characters: the lead writes a reply within it (it is told before it writes and asked
|
|
51
|
+
* once to shorten one over it); a longer reply is split across messages, never cut. Null: no platform limit. */
|
|
52
|
+
export const LEAD_REPLY_LIMITS = {
|
|
53
|
+
console: null, terminal: null, telegram: PLATFORM_LIMITS.telegram, slack: PLATFORM_LIMITS.slack, discord: PLATFORM_LIMITS.discord, teams: PLATFORM_LIMITS.teams,
|
|
54
|
+
};
|
|
55
|
+
const within = (channel) => { const limit = LEAD_REPLY_LIMITS[channel]; return limit === null ? '' : ` Keep each reply within ${limit.toLocaleString('en-US')} characters, one message; a longer one is split across messages.`; };
|
|
40
56
|
const CHANNEL_WORDS = {
|
|
41
57
|
console: 'The Toolroll console in a browser: cards and links show beside your reply.',
|
|
42
58
|
terminal: 'The Toolroll CLI in a terminal: plain text only.',
|
|
43
|
-
telegram:
|
|
44
|
-
slack:
|
|
45
|
-
discord:
|
|
46
|
-
teams:
|
|
59
|
+
telegram: `Telegram on their phone: a few short lines, the most important first.${within('telegram')}`,
|
|
60
|
+
slack: `A Slack thread: a few short lines; teammates may read it.${within('slack')}`,
|
|
61
|
+
discord: `A Discord thread: a few short lines; teammates may read it.${within('discord')}`,
|
|
62
|
+
teams: `A Microsoft Teams thread: a few short lines; teammates may read it.${within('teams')}`,
|
|
47
63
|
};
|
|
48
64
|
/** "alex.pelletier@example.com" → "Alex". */
|
|
49
65
|
export function firstNameOf(account) {
|
|
@@ -114,7 +130,7 @@ export function leadContext(store, repos, now, options = {}) {
|
|
|
114
130
|
you: { firstName, ...localNow(now, options.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone) },
|
|
115
131
|
aboutYou,
|
|
116
132
|
people: { people: [], teammates: [], teams: [] },
|
|
117
|
-
channel: options.channel === undefined ? null : { id: options.channel, fit: CHANNEL_WORDS[options.channel] },
|
|
133
|
+
channel: options.channel === undefined ? null : { id: options.channel, fit: CHANNEL_WORDS[options.channel], replyLimit: LEAD_REPLY_LIMITS[options.channel] },
|
|
118
134
|
needsYou: brief.assignments.filter(needs).map(task),
|
|
119
135
|
...(options.owner === undefined || options.thread === undefined ? {} : followThrough(store, options.owner, options.thread)),
|
|
120
136
|
projects,
|
package/dist/mate-doors.js
CHANGED
|
@@ -20,7 +20,7 @@ import { isVerifiedApprover, reproveApprover } from "./principal.js";
|
|
|
20
20
|
import { TeamLeads } from './team-leads.js';
|
|
21
21
|
import { fileTaskProposal } from "./proposal.js";
|
|
22
22
|
import { proposeGuarded } from "./scope.js";
|
|
23
|
-
import { isRiskLevel, PHASES, riskTitle, specWords } from "./phase-routing.js";
|
|
23
|
+
import { isRiskLevel, isTaskSize, PHASES, riskTitle, specWords } from "./phase-routing.js";
|
|
24
24
|
import { isProviderId } from "./provider.js";
|
|
25
25
|
import { applyChatReview } from "./chat-review.js";
|
|
26
26
|
import { applyChatTaskAction } from "./chat-task-actions.js";
|
|
@@ -454,11 +454,15 @@ function executeProposal(store, actor, kind, payload, now, options) {
|
|
|
454
454
|
const clear = payload["clear"] === true;
|
|
455
455
|
const provider = payloadString(payload, "provider");
|
|
456
456
|
const model = payloadString(payload, "model");
|
|
457
|
+
const size = payload["size"];
|
|
458
|
+
const risky = payload["risky"];
|
|
457
459
|
if (risk !== undefined && !isRiskLevel(risk))
|
|
458
460
|
return refuse("refused", "this proposal carries an unknown risk level");
|
|
461
|
+
if (size !== undefined && (!isTaskSize(size) || typeof risky !== "boolean"))
|
|
462
|
+
return refuse("refused", "this proposal carries an unknown size");
|
|
459
463
|
if (phase !== undefined && (typeof phase !== "string" || !PHASES.includes(phase)))
|
|
460
464
|
return refuse("refused", "this proposal names an unknown role");
|
|
461
|
-
if (risk === undefined && phase === undefined)
|
|
465
|
+
if (risk === undefined && phase === undefined && size === undefined)
|
|
462
466
|
return refuse("refused", "this proposal changes nothing about the agents");
|
|
463
467
|
let override;
|
|
464
468
|
if (typeof phase === "string") {
|
|
@@ -478,6 +482,7 @@ function executeProposal(store, actor, kind, payload, now, options) {
|
|
|
478
482
|
return standing.ok ? { ok: true } : { ok: false, reason: `your approver standing changed (${standing.reason}) — sign in again` };
|
|
479
483
|
},
|
|
480
484
|
...(isRiskLevel(risk) ? { risk } : {}),
|
|
485
|
+
...(isTaskSize(size) && typeof risky === "boolean" ? { size: { size, risky } } : {}),
|
|
481
486
|
...(override === undefined ? {} : { override }),
|
|
482
487
|
expectDigest: payloadString(payload, "sawDigest"),
|
|
483
488
|
// The pair is re-proved against the role's configured choices
|
|
@@ -508,6 +513,7 @@ function executeProposal(store, actor, kind, payload, now, options) {
|
|
|
508
513
|
const roleWord = typeof phase === "string" ? { plan: "planner", build: "builder", repair: "repair", review: "reviewer" }[phase] ?? phase : null;
|
|
509
514
|
const changed = [
|
|
510
515
|
...(isRiskLevel(risk) ? [`risk is now ${riskTitle(risk).toLowerCase()}`] : []),
|
|
516
|
+
...(isTaskSize(size) ? [`it is now a ${size}${risky === true ? ", risky" : ""} change`] : []),
|
|
511
517
|
...(roleWord === null ? [] : clear ? [`the ${roleWord} choice was cleared — the recommendation stands again`] : [`the ${roleWord} is now ${specWords({ provider: provider, model: model })}`]),
|
|
512
518
|
];
|
|
513
519
|
return {
|
package/dist/mate-tools.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TEXT_LIMITS } from "./text-limits.js";
|
|
1
2
|
import { connectionsOf, oneClickOf } from "./mcp-connect.js";
|
|
2
3
|
import { checkLevelFromWords, isCheckLevel } from "./check-levels.js";
|
|
3
4
|
import { policyParts } from "./policy.js";
|
|
@@ -42,7 +43,7 @@ import { parseAcceptanceCriteria, ACCEPTANCE_LIMITS, EVIDENCE_KINDS } from "./sc
|
|
|
42
43
|
import { diagnoseTaskDispatch, withDispatchDiagnoses } from "./dispatch.js";
|
|
43
44
|
import { agentChoicesFor, routeOfTask, INSTALLATION_SCOPE } from "./agentconfig.js";
|
|
44
45
|
import { isNewModel, modelWords, priceWords, runtimeStates, seenModels } from "./model-catalog.js";
|
|
45
|
-
import { agentsSummary, chosenWords, isRiskLevel, PHASES, postureWords, RISK_CHOICES, riskConsequence, riskTitle, routeProblems, sameSpec, specWords } from "./phase-routing.js";
|
|
46
|
+
import { agentsSummary, chosenWords, isRiskLevel, isTaskSize, PHASES, postureWords, RISK_CHOICES, riskConsequence, riskTitle, routeProblems, sameSpec, sizeSourceWords, sizeWords, specWords } from "./phase-routing.js";
|
|
46
47
|
import { TOOL_CATALOG, discoverTools, projectToolsOf, secretsSetFor, toolCommandLine, toolStanding } from "./project-tools.js";
|
|
47
48
|
import { deciderOf, durationWords, FLOW_KIND_WORDS, FLOW_STAGE_KINDS, FLOW_TEMPLATES, flowFromSteps, stepsFor } from "./flows.js";
|
|
48
49
|
import { flowDefinitionOf } from "./flow-engine.js";
|
|
@@ -359,6 +360,7 @@ export function agentsOver(store, taskId, now) {
|
|
|
359
360
|
const base = {
|
|
360
361
|
task: taskId,
|
|
361
362
|
risk: { level: risk, title: riskTitle(risk), consequence: riskConsequence(risk) },
|
|
363
|
+
size: ref.sizing == null ? null : { size: ref.sizing.size, risky: ref.sizing.risky, reason: ref.sizing.reason, source: sizeSourceWords(ref.sizing.source) },
|
|
362
364
|
riskChoices: RISK_CHOICES.map(one => ({ risk: one.risk, title: one.title, consequence: one.consequence })),
|
|
363
365
|
editable,
|
|
364
366
|
editableWhy,
|
|
@@ -378,6 +380,7 @@ export function agentsOver(store, taskId, now) {
|
|
|
378
380
|
standing: routed.source === "approved" ? "approved" : routed.source === "proposed" ? "awaiting approval" : "recommended",
|
|
379
381
|
summary: agentsSummary(route),
|
|
380
382
|
posture: postureWords(route),
|
|
383
|
+
sizeWords: sizeWords(route),
|
|
381
384
|
demands: route.demands.filter(reason => !/review/i.test(reason)),
|
|
382
385
|
agents: route.legs.filter(leg => leg.phase !== "review").map(leg => ({ role: ROLE_WORD[leg.phase], provider: leg.provider, model: leg.model, chosen: chosenWords(leg), reasons: leg.reasons.map(reason => reason.replace("builder and reviewer", "builder")), problem: leg.problem })),
|
|
383
386
|
problems: routeProblems(route),
|
|
@@ -433,7 +436,7 @@ export const MATE_TOOLS = [
|
|
|
433
436
|
{
|
|
434
437
|
name: "propose_action",
|
|
435
438
|
description: "Read get_actions and relevant skills/evidence first. Save an exact-state proposal only; protected or long terms require full secure review.",
|
|
436
|
-
inputSchema: schema({ operation: { type: 'string', enum: Object.keys(CHAT_ACTIONS).filter(one => !one.startsWith('flow_') && !one.startsWith('teammate_')) }, repo: { type: 'string' }, task: TASK_ARG, version: { type: 'string' }, restore: { type: 'integer', minimum: 1 }, sample: { type: 'string', maxLength: 800 }, content: { type: 'string', maxLength: 12000 }, instructions: { type: 'string', maxLength:
|
|
439
|
+
inputSchema: schema({ operation: { type: 'string', enum: Object.keys(CHAT_ACTIONS).filter(one => !one.startsWith('flow_') && !one.startsWith('teammate_')) }, repo: { type: 'string' }, task: TASK_ARG, version: { type: 'string' }, restore: { type: 'integer', minimum: 1 }, sample: { type: 'string', maxLength: 800 }, content: { type: 'string', maxLength: 12000 }, instructions: { type: 'string', maxLength: TEXT_LIMITS.flowInstructions }, title: { type: 'string', maxLength: 120 }, id: { type: 'string' }, run: { type: 'integer', minimum: 1 }, note: { type: 'string', maxLength: LIMITS.note, description: `At most ${LIMITS.note} characters; longer is refused with its length, not cut.` }, catalog: { type: 'string', maxLength: 40 }, name: { type: 'string', maxLength: 40 }, command: { type: 'string', maxLength: 400 }, args: { type: 'array', items: { type: 'string', maxLength: 400 }, maxItems: 40 }, url: { type: 'string', maxLength: 500 }, secrets: { type: 'array', items: { type: 'string', maxLength: 64 }, maxItems: 12 }, about: { type: 'string', maxLength: 240 } }, ['operation']),
|
|
437
440
|
handle: (ctx, args) => {
|
|
438
441
|
const operation = args['operation'];
|
|
439
442
|
if (!isChatAction(operation))
|
|
@@ -894,7 +897,7 @@ export const MATE_TOOLS = [
|
|
|
894
897
|
name: { type: "string", maxLength: 80 }, template: { type: "string", enum: FLOW_TEMPLATES.map(one => one.id) },
|
|
895
898
|
steps: { type: "array", minItems: 1, maxItems: 24, items: { type: "object", additionalProperties: false, properties: {
|
|
896
899
|
id: { type: "string", maxLength: 32 }, title: { type: "string", maxLength: 60 }, kind: { type: "string", enum: [...FLOW_STAGE_KINDS] },
|
|
897
|
-
instructions: { type: "string", maxLength:
|
|
900
|
+
instructions: { type: "string", maxLength: TEXT_LIMITS.flowInstructions }, planning: { type: "string", enum: ["auto", "required", "skip"] },
|
|
898
901
|
decider: { type: "string", maxLength: 64 }, message: { type: "string", maxLength: 1000 },
|
|
899
902
|
script: { type: "string", maxLength: 40 }, close: { type: "boolean" },
|
|
900
903
|
question: { type: "string", maxLength: 300 }, sureAt: { type: "integer", minimum: 50, maximum: 99 },
|
|
@@ -913,7 +916,7 @@ export const MATE_TOOLS = [
|
|
|
913
916
|
ifReplied: { type: "string", maxLength: 60 }, repo: REPO_ARG,
|
|
914
917
|
next: { type: "string", maxLength: 60 }, ifFails: { type: "string", maxLength: 60 }, ifNotSure: { type: "string", maxLength: 60 },
|
|
915
918
|
} } },
|
|
916
|
-
title: { type: "string", maxLength: 200 }, description: { type: "string", maxLength: 4000 }, zone: { type: "string", maxLength: 60 }, note: { type: "string", maxLength:
|
|
919
|
+
title: { type: "string", maxLength: 200 }, description: { type: "string", maxLength: 4000 }, zone: { type: "string", maxLength: 60 }, note: { type: "string", maxLength: LIMITS.note, description: `At most ${LIMITS.note} characters; longer is refused with its length, not cut.` },
|
|
917
920
|
choice: { type: "integer", minimum: 1, maximum: 4 },
|
|
918
921
|
trigger: { type: "integer", minimum: 1 }, owner: { type: "string", maxLength: 64 },
|
|
919
922
|
script: { type: "object", additionalProperties: false, properties: { name: { type: "string", maxLength: 40 }, about: { type: "string", maxLength: 160 }, body: { type: "string", maxLength: 1600 }, timeoutMinutes: { type: "integer", minimum: 1, maximum: 60 },
|
|
@@ -1829,15 +1832,17 @@ export const MATE_TOOLS = [
|
|
|
1829
1832
|
{
|
|
1830
1833
|
name: "propose_steer",
|
|
1831
1834
|
description: "Guide the next attempt within existing scope; never interrupt work.",
|
|
1832
|
-
inputSchema: schema({ task: TASK_ARG, note: { type: "string", maxLength:
|
|
1835
|
+
inputSchema: schema({ task: TASK_ARG, note: { type: "string", maxLength: LIMITS.note, description: `At most ${LIMITS.note} characters; longer is refused with its length, not cut.` } }, ["task", "note"]),
|
|
1833
1836
|
handle: (ctx, args) => {
|
|
1834
1837
|
const taskId = taskIdOf(args);
|
|
1835
1838
|
const ref = taskId === null ? null : admittedRef(ctx, taskId);
|
|
1836
1839
|
const task = taskId === null ? null : ctx.store.getTask(taskId);
|
|
1837
1840
|
if (taskId === null || ref === null || task === null)
|
|
1838
1841
|
return notFound();
|
|
1839
|
-
if (
|
|
1840
|
-
return { ok: false, message:
|
|
1842
|
+
if (typeof args["note"] === "string" && args["note"].length > LIMITS.note)
|
|
1843
|
+
return { ok: false, message: `guidance is ${args["note"].length} characters; the limit is ${LIMITS.note}. Shorten it and propose again.` };
|
|
1844
|
+
if (!honest(args["note"], LIMITS.note))
|
|
1845
|
+
return { ok: false, message: `guidance is plain text up to ${LIMITS.note} characters` };
|
|
1841
1846
|
if (task.state === "done" || task.state === "cancelled")
|
|
1842
1847
|
return { ok: false, message: "that task is finished, so guidance has no next attempt to reach" };
|
|
1843
1848
|
if (ctx.store.openContestFor(ref.id) !== null)
|
|
@@ -1945,10 +1950,12 @@ export const MATE_TOOLS = [
|
|
|
1945
1950
|
},
|
|
1946
1951
|
{
|
|
1947
1952
|
name: "propose_agents",
|
|
1948
|
-
description: "Read get_agents. Change risk
|
|
1953
|
+
description: "Read get_agents. Change risk, size (small: fast model, no plan; large or risky: strongest agents), configured role model, or clear override; stales approval, refuses running work.",
|
|
1949
1954
|
inputSchema: schema({
|
|
1950
1955
|
task: TASK_ARG,
|
|
1951
1956
|
risk: { type: "string", enum: ["routine", "elevated", "high"] },
|
|
1957
|
+
size: { type: "string", enum: ["small", "medium", "large"] },
|
|
1958
|
+
risky: { type: "boolean" },
|
|
1952
1959
|
role: { type: "string", enum: ["planner", "builder", "repair"] },
|
|
1953
1960
|
agent: schema({ provider: { type: "string", maxLength: 20 }, model: { type: "string", maxLength: 120 } }, ["provider", "model"]),
|
|
1954
1961
|
clear: { type: "boolean" },
|
|
@@ -1969,8 +1976,16 @@ export const MATE_TOOLS = [
|
|
|
1969
1976
|
return { ok: false, message: "role is planner, builder, or repair" };
|
|
1970
1977
|
const clear = args["clear"] === true;
|
|
1971
1978
|
const agent = args["agent"];
|
|
1972
|
-
|
|
1973
|
-
|
|
1979
|
+
const size = args["size"];
|
|
1980
|
+
const risky = args["risky"];
|
|
1981
|
+
if (size !== undefined && !isTaskSize(size))
|
|
1982
|
+
return { ok: false, message: "size is small, medium, or large" };
|
|
1983
|
+
if (risky !== undefined && typeof risky !== "boolean")
|
|
1984
|
+
return { ok: false, message: "risky is true or false" };
|
|
1985
|
+
const sized = ctx.store.refForId(ref.id)?.sizing ?? null;
|
|
1986
|
+
const sizeChange = size === undefined && risky === undefined ? null : { size: size ?? sized?.size ?? "medium", risky: risky ?? sized?.risky ?? false };
|
|
1987
|
+
if (risk === undefined && phase === null && sizeChange === null)
|
|
1988
|
+
return { ok: false, message: "say what changes: a risk, a size, or a role with an agent (or clear: true)" };
|
|
1974
1989
|
if (phase !== null && !clear && (agent === null || typeof agent !== "object"))
|
|
1975
1990
|
return { ok: false, message: "a role change names an agent from get_agents, or clear: true" };
|
|
1976
1991
|
if (phase === null && (clear || agent !== undefined))
|
|
@@ -2014,6 +2029,7 @@ export const MATE_TOOLS = [
|
|
|
2014
2029
|
taskTitle: task.title,
|
|
2015
2030
|
repoId: ref.repoId,
|
|
2016
2031
|
...(risk === undefined ? {} : { risk, riskConsequence: riskConsequence(risk) }),
|
|
2032
|
+
...(sizeChange === null ? {} : { size: sizeChange.size, risky: sizeChange.risky }),
|
|
2017
2033
|
...(phase === null ? {} : { phase, role: ROLE_WORD[phase] }),
|
|
2018
2034
|
...(chosen === null ? {} : { provider: chosen.provider, model: chosen.model }),
|
|
2019
2035
|
...(clear ? { clear: true } : {}),
|
|
@@ -2031,6 +2047,7 @@ export const MATE_TOOLS = [
|
|
|
2031
2047
|
kind: "agents",
|
|
2032
2048
|
task: taskId,
|
|
2033
2049
|
...(risk === undefined ? {} : { risk }),
|
|
2050
|
+
...(sizeChange === null ? {} : { size: sizeChange.size, risky: sizeChange.risky }),
|
|
2034
2051
|
...(phase === null ? {} : { role: ROLE_WORD[phase], ...(chosen === null ? { clear: true } : { agent: chosen }) }),
|
|
2035
2052
|
awaiting: view["approval"] === "approved" ? "the operator's confirmation — the current approval will then need renewing" : "the operator's confirmation",
|
|
2036
2053
|
},
|
package/dist/mate.js
CHANGED
|
@@ -9,7 +9,8 @@ import { redactSecretAssignments } from "./builder.js";
|
|
|
9
9
|
import { MATE_CONTRACT } from "./mate-contract.js";
|
|
10
10
|
import { MATE_MAX_PROPOSALS_PER_TURN, MATE_TOOL_SCHEMAS, executeMateTool, isMateTool, mateViewContextFor, projectLabelForMate, redactForMate, toolResultBytes } from "./mate-tools.js";
|
|
11
11
|
import { composeSubscriptionMatePrompt, performSubscriptionMateRequest } from "./subscription-chat.js";
|
|
12
|
-
import { leadContext } from './lead-context.js';
|
|
12
|
+
import { LEAD_REPLY_LIMITS, leadContext } from './lead-context.js';
|
|
13
|
+
import { shortenAsk } from './text-limits.js';
|
|
13
14
|
import { envValue } from "./names.js";
|
|
14
15
|
import { deliverableClaim, deliverableRepair, dropDeliverableClaims, replyCarriesDeliverable } from "./reply-shape.js";
|
|
15
16
|
export const MATE_MESSAGE_MAX_CHARS = 2_000;
|
|
@@ -317,6 +318,9 @@ export async function runMateTurn(input) {
|
|
|
317
318
|
let shownControl = false;
|
|
318
319
|
/** The turn's one repair step for a reply that claims an attachment it does not carry. */
|
|
319
320
|
let repaired = false;
|
|
321
|
+
/** The turn's one shorten step for a reply over its channel's message limit (LEAD_REPLY_LIMITS); still over, it is split. */
|
|
322
|
+
let shortened = false;
|
|
323
|
+
const replyLimit = input.channel === undefined ? null : LEAD_REPLY_LIMITS[input.channel];
|
|
320
324
|
const unbacked = (text) => {
|
|
321
325
|
const claim = deliverableClaim(text);
|
|
322
326
|
if (claim === null || shownControl || replyCarriesDeliverable(text) || store.listMateTurnEvidence(turnId).length > 0)
|
|
@@ -447,7 +451,14 @@ export async function runMateTurn(input) {
|
|
|
447
451
|
history.push({ role: "operator", text: deliverableRepair(claim) });
|
|
448
452
|
continue;
|
|
449
453
|
}
|
|
450
|
-
|
|
454
|
+
const candidate = claim === null ? answer.text : dropDeliverableClaims(answer.text);
|
|
455
|
+
if (replyLimit !== null && candidate.length > replyLimit && !shortened && steps < maxSteps) {
|
|
456
|
+
shortened = true;
|
|
457
|
+
history.push({ role: "assistant", text: answer.text, calls: [] });
|
|
458
|
+
history.push({ role: "operator", text: shortenAsk([{ field: "Your reply", limit: replyLimit, length: candidate.length }]) });
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
reply = candidate;
|
|
451
462
|
break;
|
|
452
463
|
}
|
|
453
464
|
if (answer.calls.length > MATE_MAX_CALLS_PER_STEP)
|
package/dist/operate.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IntegrationIo } from "./integrations.js";
|
|
2
2
|
import { type OnboardIo } from "./agent-onboard.js";
|
|
3
|
+
import { type Sizer } from "./task-sizing.js";
|
|
3
4
|
import { type TriggerIo } from "./flow-triggers.js";
|
|
4
5
|
import type { StepIo } from "./flow-steps.js";
|
|
5
6
|
import type { FetchLike } from "./flow-share.js";
|
|
@@ -83,6 +84,8 @@ export type OperateOptions = {
|
|
|
83
84
|
env?: Record<string, string | undefined>;
|
|
84
85
|
openBrowser?: (url: string) => void;
|
|
85
86
|
};
|
|
87
|
+
/** Injected by tests: the classifier that sizes filed tasks (none by default under test). */
|
|
88
|
+
filingSizer?: Sizer;
|
|
86
89
|
};
|
|
87
90
|
export type OnboardSeams = Partial<Pick<OnboardIo, "home" | "env" | "cwd" | "interactive" | "confirm" | "findRepo" | "checkConnection" | "pullRequests">>;
|
|
88
91
|
export declare const OPERATE_HELP: string;
|