toolroll 0.9.55 → 0.9.57
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 +18 -0
- package/README.md +24 -8
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-adapters.d.ts +5 -0
- package/dist/assignment.d.ts +24 -3
- package/dist/assignment.js +36 -10
- package/dist/browser/workspace.js +5 -5
- package/dist/browser-workspace.d.ts +12 -12
- package/dist/browser-workspace.js +1 -1
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +30 -37
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-voice.js +1 -1
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels.d.ts +3 -0
- package/dist/check-levels.js +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -7
- package/dist/contracts/chat-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/console-api.d.ts +6 -6
- package/dist/contracts/console-api.js +7 -7
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- package/dist/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.js +0 -5
- package/dist/envelope.js +0 -1
- package/dist/export-ui.js +1 -1
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.js +20 -20
- package/dist/flows.js +19 -19
- package/dist/guides.js +4 -3
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.js +5 -5
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +2 -2
- package/dist/lead-context.js +9 -9
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/lead-status.d.ts +1 -1
- package/dist/lead-status.js +2 -2
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/limits-ui.d.ts +1 -1
- package/dist/limits-ui.js +1 -1
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/notification-rows.d.ts +2 -2
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +52 -42
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.js +1 -1
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- package/dist/proof.d.ts +9 -0
- package/dist/proof.js +12 -0
- package/dist/recipe-ui.js +1 -1
- package/dist/release-coverage.d.ts +16 -0
- package/dist/release-coverage.js +92 -0
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.js +40 -45
- package/dist/server/chat.d.ts +4 -4
- package/dist/server/chat.js +105 -103
- package/dist/server/chrome.js +3 -3
- package/dist/server/flows.js +69 -62
- package/dist/server/guards.js +3 -3
- package/dist/server/http.d.ts +3 -3
- package/dist/server/http.js +1 -1
- package/dist/server/live.js +6 -6
- package/dist/server/people-tokens.js +1 -1
- package/dist/server/remote.js +12 -10
- package/dist/server/render-chat.d.ts +30 -30
- package/dist/server/render-chat.js +33 -33
- package/dist/server/render-pages.js +2 -2
- package/dist/server/render-results.d.ts +4 -1
- package/dist/server/render-results.js +23 -9
- package/dist/server/render-settings.d.ts +7 -0
- package/dist/server/render-settings.js +3 -0
- package/dist/server/route-table.js +9 -7
- package/dist/server/runtime.d.ts +13 -13
- package/dist/server/settings.js +14 -13
- package/dist/server/tasks.js +34 -30
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/spend-ui.d.ts +3 -3
- package/dist/spend-ui.js +9 -9
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +200 -254
- package/dist/store.js +830 -3152
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/{teammates-ui.js → subagents-ui.js} +45 -45
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +7 -31
- package/dist/task-outcome-cli.js +3 -1
- package/dist/task-status.d.ts +59 -7
- package/dist/task-status.js +132 -16
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +9 -9
- package/dist/teams-chat.js +6 -6
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-status.d.ts +11 -4
- package/dist/telegram-status.js +18 -11
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +13 -34
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/dist/work-index.d.ts +6 -1
- package/dist/work-index.js +50 -8
- package/dist/workspace-ui.js +2 -1
- package/package.json +1 -2
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -21
|
@@ -24,9 +24,9 @@ import { CHAT_CONTROLS, isChatControl } from "./chat-controls.js";
|
|
|
24
24
|
import { LIMITS } from "./decision.js";
|
|
25
25
|
import { CHAT_TASK_ACTIONS, chatTaskRun, chatTaskStamp, isChatTaskAction } from "./chat-task-actions.js";
|
|
26
26
|
/**
|
|
27
|
-
* The
|
|
27
|
+
* The lead's tools (mate arc §2): reads over the approver's ceiling and
|
|
28
28
|
* proposals that become rows — never a write. Every result passes through
|
|
29
|
-
* `
|
|
29
|
+
* `leadView`, a fail-closed choke point (ruling 4; slice-1 review finding
|
|
30
30
|
* 9): repos use `r1..rN` in the principal's order, and every string
|
|
31
31
|
* that leaves is scrubbed of path-shaped text, digests, and account names
|
|
32
32
|
* — titles, questions, and reasons included, because a human typed those
|
|
@@ -36,7 +36,7 @@ import { CHAT_TASK_ACTIONS, chatTaskRun, chatTaskStamp, isChatTaskAction } from
|
|
|
36
36
|
* projects after scrubbing; full paths and arbitrary text remain redacted.
|
|
37
37
|
*/
|
|
38
38
|
import { Buffer } from "node:buffer";
|
|
39
|
-
import {
|
|
39
|
+
import { LEAD_ASK_OTHER } from "./store.js";
|
|
40
40
|
import { hasDisguisedText, hasForbiddenControls } from "./decision.js";
|
|
41
41
|
import { readVerifiedArtifact, readVerifiedReport, scanForSecrets } from "./evidence.js";
|
|
42
42
|
import { parseAcceptanceCriteria } from "./scope.js";
|
|
@@ -54,27 +54,27 @@ import { flowDecisionAt } from "./telegram-flow.js";
|
|
|
54
54
|
import { flowPersonOf } from "./flow-send.js";
|
|
55
55
|
import { flowInsights } from "./flow-insights.js";
|
|
56
56
|
import { describeTrigger, FLOW_TRIGGER_KINDS, triggerConfigOf } from "./flow-triggers.js";
|
|
57
|
-
export const
|
|
57
|
+
export const LEAD_MAX_PROPOSALS_PER_TURN = 5;
|
|
58
58
|
export { reportSummaryFor } from "./report-summary.js";
|
|
59
59
|
import { reportSummaryFor } from "./report-summary.js";
|
|
60
|
-
import { labelOf, nameOf, summaryOf, zonesOf } from "./
|
|
61
|
-
import {
|
|
62
|
-
import { callOutcome, callWords, defaultRule, ruleWords } from "./
|
|
63
|
-
import { deskOf, routinesOf } from "./
|
|
60
|
+
import { labelOf, nameOf, summaryOf, zonesOf } from "./subagent-admin.js";
|
|
61
|
+
import { SUBAGENT_TEMPLATES, withSection } from "./subagents.js";
|
|
62
|
+
import { callOutcome, callWords, defaultRule, ruleWords } from "./subagent-tools.js";
|
|
63
|
+
import { deskOf, routinesOf } from "./subagent-desk.js";
|
|
64
64
|
import { KITS } from "./kits.js";
|
|
65
65
|
import { STARTER_IDS } from "./flow-starters.js";
|
|
66
|
-
import { undoFor, weekOf, weekWords } from "./
|
|
66
|
+
import { undoFor, weekOf, weekWords } from "./subagent-week.js";
|
|
67
67
|
import { PERSON_ID, personEntry, teamRoomOf } from "./lead-people.js";
|
|
68
68
|
import { aboutYouOf, checkAboutYouLine } from "./lead-about.js";
|
|
69
69
|
const REPO_ID = /^r[0-9]{1,3}$/;
|
|
70
70
|
const TASK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
71
71
|
const ISO_STAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
72
|
-
// -------------------------------------------------------------
|
|
72
|
+
// ------------------------------------------------------------- leadView
|
|
73
73
|
/** What a mate-facing string may not carry: an absolute path, a hex digest of 32+ digits, an account name. */
|
|
74
74
|
const PATH_SHAPED = /(?:^|[\s"'`(<[=:,])(\/(?:[A-Za-z0-9._~-]+\/)*[A-Za-z0-9._~-]+)/g;
|
|
75
75
|
const HEX_DIGEST = /\b[0-9a-f]{32,}\b/gi;
|
|
76
76
|
/** Scrub one string. Exact repo paths and their basenames go first (v13: no paths, no basenames), then shapes. */
|
|
77
|
-
export function
|
|
77
|
+
export function redactForLead(text, view) {
|
|
78
78
|
let out = text;
|
|
79
79
|
for (const repo of view.repos) {
|
|
80
80
|
if (repo === "")
|
|
@@ -95,10 +95,10 @@ export function redactForMate(text, view) {
|
|
|
95
95
|
return out;
|
|
96
96
|
}
|
|
97
97
|
/** The choke point: every string inside a tool result, recursively, scrubbed. */
|
|
98
|
-
export function
|
|
98
|
+
export function leadView(value, view) {
|
|
99
99
|
const walk = (node) => {
|
|
100
100
|
if (typeof node === "string")
|
|
101
|
-
return
|
|
101
|
+
return redactForLead(node, view);
|
|
102
102
|
if (Array.isArray(node))
|
|
103
103
|
return node.map(walk);
|
|
104
104
|
if (typeof node === "object" && node !== null) {
|
|
@@ -111,14 +111,14 @@ export function mateView(value, view) {
|
|
|
111
111
|
};
|
|
112
112
|
return walk(value);
|
|
113
113
|
}
|
|
114
|
-
export function
|
|
114
|
+
export function leadViewContextFor(store, who) {
|
|
115
115
|
return { repos: who.repos, names: store.listApprovers().map(one => one.name) };
|
|
116
116
|
}
|
|
117
117
|
/** Project names are deliberate display metadata, not a general basename
|
|
118
118
|
* exemption. Invalid, sensitive or account-identifying labels stay opaque. */
|
|
119
|
-
export function
|
|
119
|
+
export function projectLabelForLead(path, index, names) {
|
|
120
120
|
const base = path.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "";
|
|
121
|
-
const label =
|
|
121
|
+
const label = redactForLead(base, { repos: [], names });
|
|
122
122
|
return honest(label, 80) && /^[\p{L}\p{N}][\p{L}\p{N} ._()-]*$/u.test(label)
|
|
123
123
|
&& !/\b\d{5,}:[A-Za-z0-9_-]{20,}\b/.test(label)
|
|
124
124
|
? label : `Project ${index + 1}`;
|
|
@@ -182,7 +182,7 @@ export function readAcceptanceArg(value) {
|
|
|
182
182
|
return null;
|
|
183
183
|
return parsed.criteria;
|
|
184
184
|
}
|
|
185
|
-
const tooMany = () => ({ ok: false, message: `this turn already holds ${
|
|
185
|
+
const tooMany = () => ({ ok: false, message: `this turn already holds ${LEAD_MAX_PROPOSALS_PER_TURN} proposals` });
|
|
186
186
|
/** How far back a task search reads: enough for a busy month, bounded. */
|
|
187
187
|
const SEARCH_FAMILIES = 400;
|
|
188
188
|
/** Words that never name work on their own. */
|
|
@@ -198,9 +198,9 @@ function searchHits(words, fields) {
|
|
|
198
198
|
}
|
|
199
199
|
const notFound = () => ({ ok: false, message: "not-found: no such task in your projects" });
|
|
200
200
|
// ------------------------------------------------- shared queries (§2)
|
|
201
|
-
// The one set of queries both the
|
|
201
|
+
// The one set of queries both the lead and the MCP gateway read. Every
|
|
202
202
|
// row names its repo by INDEX into the caller's admitted list; the view
|
|
203
|
-
// then labels it — `rN` for the
|
|
203
|
+
// then labels it — `rN` for the lead, the path for a coordinator whose
|
|
204
204
|
// allowlist is its authority. Consequences and recommendations are never
|
|
205
205
|
// read here at all.
|
|
206
206
|
/** Replace every `repoIndex` with a `repo` label, recursively. */
|
|
@@ -361,13 +361,13 @@ export function agentsOver(store, taskId, now) {
|
|
|
361
361
|
demands: route.demands.filter(reason => !/review/i.test(reason)),
|
|
362
362
|
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 })),
|
|
363
363
|
problems: routeProblems(route),
|
|
364
|
-
// Only SELECTABLE choices are offered to the
|
|
364
|
+
// Only SELECTABLE choices are offered to the lead: a current agent the
|
|
365
365
|
// configuration no longer names appears under `agents` (what runs
|
|
366
366
|
// today) and nowhere a proposal could pick it.
|
|
367
367
|
choices: Object.fromEntries(PHASES.filter(phase => phase !== "review").map(phase => [ROLE_WORD[phase], choices[phase].filter(one => one.selectable).map(one => ({ provider: one.provider, model: one.model, current: one.current }))])),
|
|
368
368
|
};
|
|
369
369
|
}
|
|
370
|
-
const
|
|
370
|
+
const LEAD_TOOL_HANDLERS = {
|
|
371
371
|
get_brief: {
|
|
372
372
|
description: 'Read current tasks, decisions, results and project knowledge from the local database. No model or mutation.',
|
|
373
373
|
handle: (ctx, args) => {
|
|
@@ -392,15 +392,15 @@ const MATE_TOOL_HANDLERS = {
|
|
|
392
392
|
get_action_status: {
|
|
393
393
|
description: "Read the saved proposal outcome after confirmation. Opening a link proves no completion.",
|
|
394
394
|
handle: (ctx, args) => {
|
|
395
|
-
const proposal = ctx.store.
|
|
396
|
-
if (!proposal || !action || ctx.store.
|
|
395
|
+
const proposal = ctx.store.getLeadProposal(Number(args['proposal'])), action = proposal?.kind === 'action' ? sharedActionPayload(proposal.payload) : null;
|
|
396
|
+
if (!proposal || !action || ctx.store.getLeadThread(proposal.thread)?.approver !== ctx.who.name || !ctx.who.repos.includes(action.repo) || !ctx.store.accountCanAccess(ctx.who.name, action.repo))
|
|
397
397
|
return { ok: false, message: 'That action is outside your access.' };
|
|
398
398
|
return { ok: true, body: { proposal: proposal.id, operation: action.operation, state: proposal.state, outcome: proposal.outcome, finishedAt: proposal.resolvedAt } };
|
|
399
399
|
},
|
|
400
400
|
},
|
|
401
401
|
get_actions: {
|
|
402
402
|
description: "List shared actions and required inputs. All channels use the same approvals.",
|
|
403
|
-
handle: () => ({ ok: true, body: { actions: Object.entries(CHAT_ACTIONS).filter(([operation]) => !operation.startsWith('flow_') && !operation.startsWith('
|
|
403
|
+
handle: () => ({ ok: true, body: { actions: Object.entries(CHAT_ACTIONS).filter(([operation]) => !operation.startsWith('flow_') && !operation.startsWith('subagent_')).map(([operation, value]) => ({ operation, label: value.label, secureReview: value.protected, inputs: CHAT_ACTION_FIELDS[operation].filter(field => field !== 'nonce' && field !== 'files') })), notice: 'Passwords and credentials never belong in a tool call or conversation. Secure review links complete protected actions.' } }),
|
|
404
404
|
},
|
|
405
405
|
propose_action: {
|
|
406
406
|
description: "Read get_actions and relevant skills/evidence first. Save an exact-state proposal only; protected or long terms require full secure review.",
|
|
@@ -410,8 +410,8 @@ const MATE_TOOL_HANDLERS = {
|
|
|
410
410
|
return { ok: false, message: 'Choose an action from get_actions.' };
|
|
411
411
|
if (operation.startsWith('flow_'))
|
|
412
412
|
return { ok: false, message: 'Use propose_flow for flows.' };
|
|
413
|
-
if (operation.startsWith('
|
|
414
|
-
return { ok: false, message: 'Use
|
|
413
|
+
if (operation.startsWith('subagent_'))
|
|
414
|
+
return { ok: false, message: 'Use propose_subagent for subagents.' };
|
|
415
415
|
const input = { ...args };
|
|
416
416
|
delete input['operation'];
|
|
417
417
|
if (operation.startsWith('skill_') || operation.startsWith('knowledge_') || operation.startsWith('tool_')) {
|
|
@@ -764,7 +764,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
764
764
|
},
|
|
765
765
|
},
|
|
766
766
|
get_flows: {
|
|
767
|
-
description: "Read the flows in the operator's projects (each a process drawn as zones that cards move through): their steps in order and their cards — where each card is, what it waits on, whether it needs the operator, its task. flow reads one flow in full. Each card has its owner and latest comments; flow with card reads that card's whole discussion; read it before acting on what
|
|
767
|
+
description: "Read the flows in the operator's projects (each a process drawn as zones that cards move through): their steps in order and their cards — where each card is, what it waits on, whether it needs the operator, its task. flow reads one flow in full. Each card has its owner and latest comments; flow with card reads that card's whole discussion; read it before acting on what subagents said. Zones: Holding, Build and Research (each files an ordinary task), Person decides, Message, Done. Triggers start cards on their own: a button with questions, a schedule, GitHub (new issues, a label being added, new pull requests, failed checks), Linear, another flow's cards reaching a zone, email arriving in the operator's mailbox (optionally only from some senders or with words in the subject; reading mail is set up in Settings → Email), or a plane review (every morning, one card per problem from Toolroll's last 24 hours; a problem that comes back joins its card). A Slack, Discord or Teams channel feeds a flow when a paired approver sends 'flow <the flow's number>' in that channel (never from here); each message there becomes a card, and an 'update' step answers in its thread. Webhook addresses and the Linear key are set on the flow's Triggers panel, never in chat. Two steps run without a model: 'check' runs one of the project's scripts (named scripts in Python, Node or shell that belong to the project, not a flow) with the card as its input; what it prints is passed to later steps, a last line 'goto: <answer>' picks where the card goes, it can run in an empty folder or a copy of the card's work, and it takes the failure path if it fails; a schedule trigger can run a script and make a card of each item it prints. 'update' comments on the GitHub or Linear issue the card came from and can close it, or answers in the chat thread it came from. A Build whose project checks fail takes its failure path too. A 'sort' step has Jev (a fast decision model on the operator's OpenRouter account) read the card and send it where the answer it picks leads, or to its not-sure step, noting scores or yes/no answers too. A 'draft' step has Claude write a reply, summary or note from the card in seconds and sends nothing itself. 'request' calls a web address, 'email' sends mail from the operator's email account (answering the sender of a card that came from email keeps the reply in that thread), 'tool' uses one of the project's tools; their secrets are saved on the canvas or Tools page, never in chat. A 'wait' step after an 'email' step waits for the person to reply (the reply moves the card on and is kept for later steps; with none in time, the card takes its no-reply step), or just waits a set time. A 'pull-request' step opens a pull request for a Build's result and follows its CI (green moves on, red goes back to the build naming the failing check); it merges only when set to, and only after a person approved the card. subagents (get_subagents) are agents on the operator's team with a soul file (who they are, how they write, what they know, what they decide on their own, what they ask first, what they never do). They work flow cards within those rules: a subagent named on an approval step decides it (and hands hard ones to that step's person), and a 'subagent' step has one pick where the card goes and write what the next steps send. They never approve code tasks or merges. Each has a memory (what people told it and facts it kept from cards) and a desk (a flow): the operator asks it through you ('ask Maya to …', or '@maya, …'): propose_subagent ask puts it on its desk once they confirm, and its answer goes back to them; its desk can send a card to a Build zone that files an ordinary task under the usual approvals. get_subagents with a subagent gives its week (what it did, cost, what the operator overrode). For where flows break, read get_flow_insights, and read a failed run's log before explaining it.",
|
|
768
768
|
handle: (ctx, args) => {
|
|
769
769
|
const reachable = (repo) => ctx.who.repos.includes(repo) && ctx.store.accountCanAccess(ctx.who.name, repo);
|
|
770
770
|
const needsYou = (flow, definition, card) => {
|
|
@@ -804,8 +804,8 @@ const MATE_TOOL_HANDLERS = {
|
|
|
804
804
|
sureAt: Math.round(stage.sort.sureAt * 100), ...(stage.sort.notes.length === 0 ? {} : { alsoNote: stage.sort.notes.map(one => ({ question: one.question, kind: one.kind, ...(one.levels === null ? {} : { levels: one.levels }) })) }) } : {}),
|
|
805
805
|
...(stage.wait === undefined ? {} : stage.wait.for === "hours" ? { waitFor: "hours", from: stage.wait.from, until: stage.wait.to } : { waitFor: stage.wait.for, wait: durationWords(stage.wait.minutes) }),
|
|
806
806
|
...(stage.merge === undefined ? {} : { merge: stage.merge }),
|
|
807
|
-
...(stage.
|
|
808
|
-
...(stage.kind === "
|
|
807
|
+
...(stage.subagent === undefined ? {} : { subagent: stage.subagent }),
|
|
808
|
+
...(stage.kind === "subagent" && stage.routes !== undefined ? { routes: stage.routes.map(one => ({ answer: one.answer, goesTo: titleOf(one.to) })) } : {}),
|
|
809
809
|
...(stage.kind === "choose" ? { options: (stage.options ?? []).map((one, index) => ({ choice: index + 1, label: one.label, goesTo: one.to === "end" ? "end" : titleOf(one.to) })) } : {}),
|
|
810
810
|
...(stage.kind === "task" && stage.repo !== undefined ? { repo: repoIdOf(ctx.who, stage.repo) ?? "another project" } : {}),
|
|
811
811
|
...(stage.limit === undefined ? {} : { remindAfter: durationWords(stage.limit.minutes), ...(stage.limit.to === null ? {} : { thenMoveTo: titleOf(stage.limit.to) }) }),
|
|
@@ -820,11 +820,11 @@ const MATE_TOOL_HANDLERS = {
|
|
|
820
820
|
: card.state !== "active" || definition.stages.find(one => one.id === card.stage)?.kind !== "approval" || card.waiting === null ? card.waiting
|
|
821
821
|
: needsYou(flow, definition, card) ? "Waiting for you to approve or send it back" : "Waiting for someone else to decide",
|
|
822
822
|
task: card.task ?? card.primaryTask, ...(card.note === null ? {} : { lastNote: card.note.slice(0, 300) }),
|
|
823
|
-
// Names never reach the model: people read as you or a
|
|
824
|
-
owner: card.owner === null ? null : card.owner === ctx.who.name ? "you" : "
|
|
823
|
+
// Names never reach the model: people read as you or a subagent.
|
|
824
|
+
owner: card.owner === null ? null : card.owner === ctx.who.name ? "you" : "another person", following: ctx.store.flowCardWatchers(card.id).includes(ctx.who.name),
|
|
825
825
|
...(() => {
|
|
826
826
|
const said = ctx.store.flowComments(card.id).filter(one => one.kind === "comment");
|
|
827
|
-
return said.length === 0 ? {} : { comments: said.length, discussion: said.slice(args["card"] === card.id ? -30 : -3).map(one => ({ by: one.author === ctx.who.name ? "you" : one.author.includes("@") ? "an email reply" : "
|
|
827
|
+
return said.length === 0 ? {} : { comments: said.length, discussion: said.slice(args["card"] === card.id ? -30 : -3).map(one => ({ by: one.author === ctx.who.name ? "you" : one.author.includes("@") ? "an email reply" : "another person", at: one.at, text: one.body.slice(0, args["card"] === card.id ? 2000 : 400) })) };
|
|
828
828
|
})(),
|
|
829
829
|
})).filter(card => args["card"] === undefined || card.card === args["card"]),
|
|
830
830
|
scripts: ctx.store.flowScripts(flow.repo).map(script => ({ name: script.name, about: script.about, version: script.version, timeoutMinutes: script.timeoutMinutes, body: script.body.slice(0, 1500) })),
|
|
@@ -855,7 +855,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
855
855
|
},
|
|
856
856
|
},
|
|
857
857
|
propose_flow: {
|
|
858
|
-
description: "Draft a flow change as a card the operator confirms. To make a flow, create with a template or the steps in plain names; leave instructions out unless the operator gave them, and use decider 'me' when they decide. To change one, edit with the whole step list, keeping existing steps by id. For cards use add_card, move_card, approve, send_back with the operator's note, or cancel_card; find the card by what it is about; comment (with @name to ping someone), assign and follow here too. For triage, spam, lead, effort or exception routing, start from the matching template, with the sort as the first step. Use remindAfter and thenMoveTo for follow-ups and decisions that stall. The Issues to PRs template runs GitHub issues labelled toolroll through build, approval, pull request and a comment on the issue. When the operator asks for something to happen every time (fix CI when it fails, turn labelled issues into tasks, queue work overnight, review what went wrong every morning), offer the matching starter; each is one yes, and none merges without a person. When someone new wants a support desk, bug triage, sales follow-up or requests handled, a kit sets up the
|
|
858
|
+
description: "Draft a flow change as a card the operator confirms. To make a flow, create with a template or the steps in plain names; leave instructions out unless the operator gave them, and use decider 'me' when they decide. To change one, edit with the whole step list, keeping existing steps by id. For cards use add_card, move_card, approve, send_back with the operator's note, or cancel_card; find the card by what it is about; comment (with @name to ping someone), assign and follow here too. For triage, spam, lead, effort or exception routing, start from the matching template, with the sort as the first step. Use remindAfter and thenMoveTo for follow-ups and decisions that stall. The Issues to PRs template runs GitHub issues labelled toolroll through build, approval, pull request and a comment on the issue. When the operator asks for something to happen every time (fix CI when it fails, turn labelled issues into tasks, queue work overnight, review what went wrong every morning), offer the matching starter; each is one yes, and none merges without a person. When someone new wants a support desk, bug triage, sales follow-up or requests handled, a kit sets up the subagent and its flow in one card. Subagents: propose_subagent adds one (from a template: support, sales, ops, triage), changes one rule section (edit_section), pauses, resumes or removes it, passes it a note, or answers its question for the operator; put it to work with a flow here. When the operator says something like 'Maya can approve refunds up to $100 now', change that section of her soul file; when it's for this week or a one-off, pass it as a note. Subagents use the project's tools under a rule per action (do it, ask first: the operator approves each exact call, never, or up to a limit on a number); propose_subagent use_tool, stop_tool and tool_rule change them, and when a rule has a limit also change the soul file so its words agree. forget or edit_memory when the operator says a subagent has something wrong; a rule change it suggests is a question for the operator, answered only as they say. propose_subagent add_routine puts a card on its desk on a schedule; propose_subagent undo undoes a tool call only when its action has an undo set (tool_rule undoWith). Actions: starter: switch on a starter flow in a project (repo, starter: ci-fix files a fix task when CI fails on the main branch, issue-task makes a task of each GitHub issue labelled toolroll, overnight holds cards added in the day until 22:00 and leaves results for the morning, plane-review reviews the plane's last 24 hours every morning and turns each problem worth fixing into a researched fix and a pull request) — its zones and trigger in one card; offer the matching one when the operator says to do something every time. kit: set up a starter kit in a project (repo, kit: support-desk, bug-triage, sales-follow-up or ops-requests) — a subagent, the flow it works and its buttons, in one card; prefer it when the operator wants a support desk, bug triage, sales follow-up or requests handled. create: a template, or the steps in order (each leads to the next; Done is added; instructions may be left out). 'request' calls a web address (method, url with its host written out, headers — {{secret.NAME}} uses a secret the operator saved on the step, never in chat — and body); 'email' sends mail (to, subject, body; {{card.email}} is the card's email address); 'tool' calls one of the project's tools (server: the tool's name from get_project_tools, tool: its function, args: an object). Put a decision before any of these when they send what a model wrote or what an outsider sent. A 'draft' step has Claude write something from the card (instructions: what to write); follow it with an approval step (decider 'owner' asks the flow's owner in their chat app, where they can approve, edit or send it back), then an 'update' or 'notify' step whose message is '{{stage.<draft id>}}'. A 'sort' step has Jev pick one of its answers; make it the first step (never a holding step before it, or new cards wait unsorted): question, answers (answer, means: a few words Jev reads, goesTo: a step), sureAt (percent, default 80), ifNotSure (a step; otherwise the card waits for a person), and up to 3 alsoNote (score with levels lowest first, or yes-no); a sort has no next, so give each branch's last step its own next. A 'pull-request' step opens a pull request for the card's built result and waits for CI: next when it passes, ifFails when it fails (only this step defaults it: to the build before it, as a revision carrying the failing check); merge ('squash', 'merge' or 'rebase') merges once checks pass, and needs an approval step before it on every path. waitFor 'hours' with from and until (like '22:00' and '06:00') holds cards until the clock is inside those hours. A 'wait' step, after an 'email' step, waits for a reply to that email from someone it went to (waitFor 'reply', the default): next is where a reply goes (the reply is {{stage.<wait id>}}), ifNoReply where the card goes when none comes within wait (like '3 days', up to 30 days); waitFor 'time' just waits, then next. Any step but wait and done can have remindAfter (like '2 days': whoever it waits on is reminded once; 'none' removes it) and, on holding and approval steps, thenMoveTo (a step the card moves to then). subagents (get_subagents): an approval step with subagent (its short name) is decided by that subagent within its rules, and it hands hard ones to the step's decider; a 'subagent' step (subagent, instructions, routes of answer and goesTo) has it read the card, pick where it goes and write what the next steps send (the email body is then {{stage.<id>}}), asking the flow's owner when its rules say to. edit: the full step list, keeping existing steps by id — what a kept step leaves out carries over. add_card (starts in the first zone unless zone is named), move_card, approve, send_back (needs a note), cancel_card, comment (note; @name pings that person), assign (owner: a name, 'me', or 'nobody'), follow, unfollow, save_script (repo, and script: name, about, language python|node|shell, and either body (short) or file (a path in the project, like scripts/enrich.py), and timeoutMinutes; scripts belong to the project, so no flow is needed; a 'check' step in any of its flows names it). A 'check' step runs its script with the card as JSON on stdin (and in $FLOW_INPUT); what it prints is its result for later steps ({{stage.<id>}}); runIn 'folder' (an empty folder: for scripts that work on data) or 'copy' (a copy of the card's work, after setup: for tests on code; the default); routes (answer, goesTo) that a last printed line 'goto: <answer>' picks; ifFails is where a failing script sends the card, such as back to the build (it has no default: without it the card waits there); secrets (names of saved secrets it gets as variables). add_trigger with settings (kind button: label, questions; schedule: schedule like 'daily 09:00 Europe/London', and title, or script (a saved script whose printed items — one per line, a title or JSON with title, description, key — each become a card, once) with secrets; github: repo owner/name, watch issues|pulls|checks, label, branch, from team|anyone; linear: team, state, label; flow: follow (another flow's id), when (its zone); email: folder (default INBOX), sender (addresses or domains), subject (words it must contain); plane-review: at (HH:MM, default 07:30), timeZone — every day it reads the plane's last 24 hours and makes one card per problem worth fixing, a returning problem joining its card); pause_trigger, resume_trigger, remove_trigger with trigger. A 'send' step ('Send to me') sends the card's owner what the step before produced (summary, links, screenshots) in their chat apps, then moves on; a 'choose' step sends the same with 2 to 4 options (label, goesTo: a step, or 'end' to ignore the card) as buttons, ifReplied (where a reply goes as {{note}}; the build before by default), and remindAfter with ifNoReply. choose (card, and choice by number or a note to reply) answers one for the operator. A 'task' step may build in another project: repo (from list_repos). Read get_flows first except to create.",
|
|
859
859
|
// An edit's kept steps may leave their kind and name to the zone they keep: they are filled in before the call is read.
|
|
860
860
|
prepare: (ctx, given) => {
|
|
861
861
|
const editing = given["operation"] === "edit" && Number.isSafeInteger(given["flow"]) ? ctx.store.getFlow(Number(given["flow"])) : null;
|
|
@@ -892,7 +892,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
892
892
|
break;
|
|
893
893
|
}
|
|
894
894
|
case "kit": {
|
|
895
|
-
// a starter kit — a
|
|
895
|
+
// a starter kit — a subagent, the flow it works, its buttons.
|
|
896
896
|
const repo = repoPathOf(ctx.who, args["repo"]);
|
|
897
897
|
if (repo === null)
|
|
898
898
|
return { ok: false, message: "Choose a project from list_repos." };
|
|
@@ -1006,46 +1006,46 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1006
1006
|
}
|
|
1007
1007
|
},
|
|
1008
1008
|
},
|
|
1009
|
-
|
|
1010
|
-
description: "The project's
|
|
1009
|
+
get_subagents: {
|
|
1010
|
+
description: "The project's subagents: who each is (its soul file: role, rules), what it works on, what it did today, and the questions it's waiting on you for. With subagent, one in full with its recent log.",
|
|
1011
1011
|
handle: (ctx, args) => {
|
|
1012
1012
|
const repos = args["repo"] === undefined ? ctx.who.repos : [repoPathOf(ctx.who, args["repo"])].filter((one) => one !== null);
|
|
1013
1013
|
if (repos.length === 0)
|
|
1014
1014
|
return { ok: false, message: "Choose a project from list_repos." };
|
|
1015
1015
|
const today = new Date(ctx.now);
|
|
1016
1016
|
today.setHours(0, 0, 0, 0);
|
|
1017
|
-
const whoIs = (name) => name === null ? null : name === ctx.who.name ? "you" : "
|
|
1018
|
-
const mates = ctx.store.
|
|
1019
|
-
if (args["
|
|
1020
|
-
return { ok: false, message: "No such
|
|
1017
|
+
const whoIs = (name) => name === null ? null : name === ctx.who.name ? "you" : "another person";
|
|
1018
|
+
const mates = ctx.store.subagents(repos).filter(one => args["subagent"] === undefined || one.id === args["subagent"]);
|
|
1019
|
+
if (args["subagent"] !== undefined && mates.length === 0)
|
|
1020
|
+
return { ok: false, message: "No such subagent in your projects." };
|
|
1021
1021
|
return { ok: true, body: {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
reportsTo: whoIs(mate.manager), soul: args["
|
|
1022
|
+
subagents: mates.map(mate => ({
|
|
1023
|
+
subagent: mate.id, name: nameOf(mate), label: labelOf(mate), handle: mate.handle, project: repoIdOf(ctx.who, mate.repo), working: mate.state === "active",
|
|
1024
|
+
reportsTo: whoIs(mate.manager), soul: args["subagent"] === undefined ? mate.soul.slice(0, 600) : mate.soul, version: mate.version,
|
|
1025
1025
|
worksOn: zonesOf(ctx.store, mate).map(one => ({ flow: one.flow, flowName: one.flowName, zone: one.title, how: one.kind })),
|
|
1026
1026
|
today: summaryOf(ctx.store, mate, today.toISOString()).said,
|
|
1027
|
-
questions: ctx.store.
|
|
1027
|
+
questions: ctx.store.openSubagentQuestions([mate.id]).map(one => ({ question: one.id, card: one.card, asks: one.question, options: one.options.map(option => option.label), askedOf: whoIs(one.askedOf), ...(one.toolCall === null ? {} : { approvesToolCall: true }), ...(one.suggestion === null ? {} : { suggestsRuleChange: true }) })),
|
|
1028
1028
|
// v96: where messages to it by name and its routines land, and its routines.
|
|
1029
1029
|
desk: deskOf(ctx.store, mate)?.id ?? null,
|
|
1030
1030
|
routines: routinesOf(ctx.store, mate).map(one => ({ routine: one.id, schedule: one.schedule, text: one.text, working: one.state === "active", last: one.lastOutcome })),
|
|
1031
1031
|
// v95: what it remembers (what people told it, and what it kept), newest first.
|
|
1032
|
-
memory: ctx.store.
|
|
1032
|
+
memory: ctx.store.subagentMemories(mate.id, { limit: args["subagent"] === undefined ? 5 : 40 }).map(one => ({ memory: one.id, text: one.text, from: one.source === "person" ? whoIs(one.createdBy) : "itself" })),
|
|
1033
1033
|
// v94: the tools it may use, and its rule for each action.
|
|
1034
|
-
tools: ctx.store.
|
|
1035
|
-
...(args["
|
|
1036
|
-
recent: ctx.store.
|
|
1037
|
-
toolCalls: ctx.store.
|
|
1034
|
+
tools: ctx.store.subagentGrants(mate.id).map(grant => ({ tool: grant.tool, actions: grant.actions.map(action => ({ action: action.name, rule: ruleWords(grant.rules[action.name] ?? defaultRule(action)) })) })),
|
|
1035
|
+
...(args["subagent"] === undefined ? {} : {
|
|
1036
|
+
recent: ctx.store.subagentEvents(mate.id, 30).map(one => ({ kind: one.kind, said: one.said, at: one.at })),
|
|
1037
|
+
toolCalls: ctx.store.subagentCallsOf(mate.id, 20).map(one => ({ call: one.id, card: one.card, what: callWords(one.tool, one.action, one.input, 200), outcome: callOutcome(one), at: one.createdAt, ...(undoFor(ctx.store, one) === null ? {} : { undoWith: undoFor(ctx.store, one) }) })),
|
|
1038
1038
|
// v97: its week, as its manager's report reads.
|
|
1039
1039
|
week: weekWords(mate, weekOf(ctx.store, mate, ctx.now)),
|
|
1040
1040
|
}),
|
|
1041
1041
|
})),
|
|
1042
|
-
templates:
|
|
1043
|
-
rule: "Change
|
|
1042
|
+
templates: SUBAGENT_TEMPLATES.map(one => ({ template: one.id, about: one.about })),
|
|
1043
|
+
rule: "Change subagents with propose_subagent. They decide flow work within their rules and never approve code tasks or merges.",
|
|
1044
1044
|
} };
|
|
1045
1045
|
},
|
|
1046
1046
|
},
|
|
1047
|
-
|
|
1048
|
-
description: "Draft a
|
|
1047
|
+
propose_subagent: {
|
|
1048
|
+
description: "Draft a subagent change as a card the operator confirms. ask: delegate to a subagent (subagent, text: what the operator wants, in their words) — whenever they say 'ask Rosa to …' or write to one by name ('@rosa, …'); once they confirm, it lands on its desk, it works it within its own rules and tools (asking them first where its rules say to), and its answer goes back to them. Don't do its work yourself or answer for it. create: a subagent in a project (repo) from a template (support, sales, ops, triage; name renames it) or a whole soul file (markdown: front matter with name and role, then sections like ## Who you are, ## How you write, ## What you know, ## Decide on your own, ## Ask first, ## Never). edit_section: replace one section of its soul file (section: its title, like 'Decide on your own'; text: the new section, as lines; a new title adds the section) — use this for changing its rules; edit_soul: the whole new soul file, when it's short (keep its name). pause, resume, remove. note: something for it to remember (note, one line); forget and edit_memory change what it remembers (memory: its id from get_subagents, text for the new words). add_routine gives it a routine (schedule like 'weekdays 09:00', 'daily 17:00 Europe/London', 'monday 09:00' or 'every 2 hours'; text: what to do each time, one line) — a card on its desk each time, its answer to its manager; stop_routine (routine: its id). answer: answer its open question (question, and choice: one of its options, or text); a tool call waiting for approval is a question too (choice approve or deny, or text to say what to do instead). Tools (get_subagents lists each subagent's tools and their actions; get_project_tools the project's): use_tool lets it use one of the project's tools (tool: the tool's name, like shop) — actions that only read start as do-it and the rest ask first; stop_tool; tool_rule sets one action's rule (tool: the tool's name, like shop; action: one of its actions, like refund_order; use: free, ask or never; with free, limitField and limitOver make it ask first above that number, like limitField amount and limitOver 100; undoWith: another action of the tool that undoes it, like remove_label for add_label, so a person can press Undo on its receipts). undo: undo one of its tool calls (call: its id from get_subagents) with the action its rule names. Put a subagent to work with propose_flow (an approval step's subagent, or a 'subagent' step).",
|
|
1049
1049
|
handle: (ctx, args) => {
|
|
1050
1050
|
const given = args;
|
|
1051
1051
|
const pick = (keys) => Object.fromEntries(keys.filter(key => given[key] !== undefined).map(key => [key, given[key]]));
|
|
@@ -1056,44 +1056,52 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1056
1056
|
const repo = repoPathOf(ctx.who, args["repo"]);
|
|
1057
1057
|
if (repo === null)
|
|
1058
1058
|
return { ok: false, message: "Choose a project from list_repos." };
|
|
1059
|
-
operation = "
|
|
1059
|
+
operation = "subagent_create";
|
|
1060
1060
|
input = { repo, ...pick(["template", "name", "soul"]) };
|
|
1061
1061
|
break;
|
|
1062
1062
|
}
|
|
1063
1063
|
case "edit_soul":
|
|
1064
|
-
operation = "
|
|
1065
|
-
input = pick(["
|
|
1064
|
+
operation = "subagent_soul";
|
|
1065
|
+
input = pick(["subagent", "soul"]);
|
|
1066
1066
|
break;
|
|
1067
1067
|
case "edit_section": {
|
|
1068
|
-
const mate = Number.isSafeInteger(args["
|
|
1068
|
+
const mate = Number.isSafeInteger(args["subagent"]) ? ctx.store.getSubagent(Number(args["subagent"])) : null;
|
|
1069
1069
|
if (mate === null || !ctx.who.repos.includes(mate.repo))
|
|
1070
|
-
return { ok: false, message: "Choose a
|
|
1070
|
+
return { ok: false, message: "Choose a subagent from get_subagents." };
|
|
1071
1071
|
if (typeof args["section"] !== "string" || args["section"].trim() === "" || typeof args["text"] !== "string")
|
|
1072
1072
|
return { ok: false, message: "Give the section's title and its new text." };
|
|
1073
|
-
operation = "
|
|
1074
|
-
input = {
|
|
1073
|
+
operation = "subagent_soul";
|
|
1074
|
+
input = { subagent: mate.id, soul: withSection(mate.soul, args["section"], args["text"]) };
|
|
1075
1075
|
break;
|
|
1076
1076
|
}
|
|
1077
1077
|
case "pause":
|
|
1078
1078
|
case "resume":
|
|
1079
1079
|
case "remove":
|
|
1080
|
-
operation = "
|
|
1081
|
-
input = { ...pick(["
|
|
1080
|
+
operation = "subagent_state";
|
|
1081
|
+
input = { ...pick(["subagent"]), state: args["operation"] === "pause" ? "paused" : args["operation"] === "resume" ? "active" : "removed" };
|
|
1082
1082
|
break;
|
|
1083
1083
|
case "note":
|
|
1084
|
-
operation = "
|
|
1085
|
-
input = pick(["
|
|
1084
|
+
operation = "subagent_note";
|
|
1085
|
+
input = pick(["subagent", "note"]);
|
|
1086
1086
|
break;
|
|
1087
|
+
case "ask": {
|
|
1088
|
+
const mate = Number.isSafeInteger(args["subagent"]) ? ctx.store.getSubagent(Number(args["subagent"])) : null;
|
|
1089
|
+
if (mate === null || !ctx.who.repos.includes(mate.repo))
|
|
1090
|
+
return { ok: false, message: "Choose a subagent from get_subagents." };
|
|
1091
|
+
operation = "subagent_ask";
|
|
1092
|
+
input = pick(["subagent", "text"]);
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1087
1095
|
case "answer":
|
|
1088
|
-
operation = "
|
|
1096
|
+
operation = "subagent_answer";
|
|
1089
1097
|
input = pick(["question", "choice", "text"]);
|
|
1090
1098
|
break;
|
|
1091
1099
|
case "use_tool":
|
|
1092
1100
|
case "stop_tool":
|
|
1093
1101
|
case "tool_rule": {
|
|
1094
|
-
const mate = Number.isSafeInteger(args["
|
|
1102
|
+
const mate = Number.isSafeInteger(args["subagent"]) ? ctx.store.getSubagent(Number(args["subagent"])) : null;
|
|
1095
1103
|
if (mate === null || !ctx.who.repos.includes(mate.repo))
|
|
1096
|
-
return { ok: false, message: "Choose a
|
|
1104
|
+
return { ok: false, message: "Choose a subagent from get_subagents." };
|
|
1097
1105
|
// A tool's name and one of its actions: "shop" and "refund_order", also taken as "shop.refund_order", or the action alone when one tool has it.
|
|
1098
1106
|
let tool = typeof args["tool"] === "string" ? args["tool"].trim() : "", action = typeof args["action"] === "string" ? args["action"].trim() : "";
|
|
1099
1107
|
const dot = tool.search(/\.|→/);
|
|
@@ -1103,7 +1111,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1103
1111
|
}
|
|
1104
1112
|
if (action.includes("."))
|
|
1105
1113
|
action = action.slice(action.lastIndexOf(".") + 1);
|
|
1106
|
-
const grants = ctx.store.
|
|
1114
|
+
const grants = ctx.store.subagentGrants(mate.id);
|
|
1107
1115
|
if (args["operation"] !== "use_tool" && !grants.some(one => one.tool === tool)) {
|
|
1108
1116
|
const owners = grants.filter(one => one.actions.some(each => each.name === tool));
|
|
1109
1117
|
if (owners.length === 1 && (action === "" || action === tool)) {
|
|
@@ -1111,32 +1119,32 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1111
1119
|
tool = owners[0].tool;
|
|
1112
1120
|
}
|
|
1113
1121
|
}
|
|
1114
|
-
operation = "
|
|
1115
|
-
input = { ...pick(["
|
|
1122
|
+
operation = "subagent_tools";
|
|
1123
|
+
input = { ...pick(["subagent", "use", "limitField", "limitOver", "undoWith"]), tool, ...(action === "" ? {} : { action }), change: args["operation"] === "use_tool" ? "grant" : args["operation"] === "stop_tool" ? "revoke" : "rule" };
|
|
1116
1124
|
break;
|
|
1117
1125
|
}
|
|
1118
1126
|
case "forget":
|
|
1119
1127
|
case "edit_memory":
|
|
1120
|
-
operation = "
|
|
1121
|
-
input = { ...pick(["
|
|
1128
|
+
operation = "subagent_memory";
|
|
1129
|
+
input = { ...pick(["subagent", "memory", "text"]), change: args["operation"] === "forget" ? "forget" : "edit" };
|
|
1122
1130
|
break;
|
|
1123
1131
|
case "undo":
|
|
1124
|
-
operation = "
|
|
1125
|
-
input = pick(["
|
|
1132
|
+
operation = "subagent_undo";
|
|
1133
|
+
input = pick(["subagent", "call"]);
|
|
1126
1134
|
break;
|
|
1127
1135
|
case "add_routine":
|
|
1128
1136
|
case "stop_routine":
|
|
1129
|
-
operation = "
|
|
1130
|
-
input = { ...pick(["
|
|
1137
|
+
operation = "subagent_routine";
|
|
1138
|
+
input = { ...pick(["subagent", "schedule", "text", "routine"]), change: args["operation"] === "add_routine" ? "add" : "remove" };
|
|
1131
1139
|
break;
|
|
1132
|
-
default: return { ok: false, message: "Choose create, edit_section, edit_soul, pause, resume, remove, note, answer, use_tool, stop_tool, tool_rule, forget, edit_memory, add_routine, stop_routine or undo." };
|
|
1140
|
+
default: return { ok: false, message: "Choose ask, create, edit_section, edit_soul, pause, resume, remove, note, answer, use_tool, stop_tool, tool_rule, forget, edit_memory, add_routine, stop_routine or undo." };
|
|
1133
1141
|
}
|
|
1134
1142
|
const action = prepareSharedAction(ctx.store, ctx.who, operation, input, ctx.evidenceRoot, ctx.now);
|
|
1135
1143
|
const id = ctx.draft("action", { ...action });
|
|
1136
1144
|
return id === null ? tooMany() : { ok: true, body: { proposal: id, label: action.title, awaiting: sharedActionNeedsReview(action) ? "human review in the secure confirmation screen" : "human confirmation", executed: false } };
|
|
1137
1145
|
}
|
|
1138
1146
|
catch (error) {
|
|
1139
|
-
return { ok: false, message: error instanceof Error ? error.message : "That
|
|
1147
|
+
return { ok: false, message: error instanceof Error ? error.message : "That subagent change couldn't be drafted." };
|
|
1140
1148
|
}
|
|
1141
1149
|
},
|
|
1142
1150
|
},
|
|
@@ -1212,10 +1220,10 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1212
1220
|
const family = ctx.store.taskFamilyOf(taskId, ctx.who.repos, false);
|
|
1213
1221
|
const root = family?.root.id ?? taskId;
|
|
1214
1222
|
const limit = Number.isSafeInteger(args["limit"]) ? Math.max(1, Math.min(30, Number(args["limit"]))) : 12;
|
|
1215
|
-
const thread = ctx.store.
|
|
1223
|
+
const thread = ctx.store.liveLeadThreadFor(ctx.who.name, { kind: "task", key: root });
|
|
1216
1224
|
if (thread === null || thread.ceilingDigest !== ctx.who.ceilingDigest)
|
|
1217
1225
|
return { ok: true, body: { task: root, messages: [], notice: "No conversation about this task yet." } };
|
|
1218
|
-
const messages = ctx.store.
|
|
1226
|
+
const messages = ctx.store.listLeadMessages(thread.id, limit).map(one => ({ from: one.role === "operator" ? "operator" : "lead", text: one.text.length > 1_200 ? `${one.text.slice(0, 1_200)}…` : one.text, at: one.createdAt }));
|
|
1219
1227
|
return { ok: true, body: { task: root, title: family?.root.title ?? null, messages, notice: messages.length === limit ? "Only the most recent messages are shown." : null } };
|
|
1220
1228
|
},
|
|
1221
1229
|
},
|
|
@@ -1317,7 +1325,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1317
1325
|
return { ok: false, message: "Say the correction in one sentence." };
|
|
1318
1326
|
const text = String(args["text"]).trim();
|
|
1319
1327
|
// One card per correction: a card still waiting in this conversation for the same change is the one to confirm.
|
|
1320
|
-
const waiting = ctx.thread === undefined ? [] : ctx.store.
|
|
1328
|
+
const waiting = ctx.thread === undefined ? [] : ctx.store.listLeadProposals(ctx.thread, ["drafting", "pending", "confirming"])
|
|
1321
1329
|
.filter(one => one.kind === "action" && one.payload["repo"] === repo);
|
|
1322
1330
|
const same = (words) => typeof words === "string" && words.trim().toLowerCase() === text.toLowerCase();
|
|
1323
1331
|
let operation, input;
|
|
@@ -1363,7 +1371,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1363
1371
|
},
|
|
1364
1372
|
},
|
|
1365
1373
|
get_person: {
|
|
1366
|
-
description: "One person,
|
|
1374
|
+
description: "One person, subagent or team chat from your catch-up's people index, in full, with their open tasks: its id as the index shows it (p3fa91c2e, t5, c07b1d9a4) or a name. Read it before answering a question about that person, subagent or team. Read-only.",
|
|
1367
1375
|
handle: (ctx, args) => {
|
|
1368
1376
|
const id = typeof args["id"] === "string" && PERSON_ID.test(args["id"]) ? args["id"] : undefined;
|
|
1369
1377
|
const name = typeof args["name"] === "string" && args["name"].trim() !== "" ? args["name"] : undefined;
|
|
@@ -1410,7 +1418,7 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1410
1418
|
},
|
|
1411
1419
|
},
|
|
1412
1420
|
ask_owner: {
|
|
1413
|
-
description: `Ask the owner one question as tappable buttons: 2-4 short options; "${
|
|
1421
|
+
description: `Ask the owner one question as tappable buttons: 2-4 short options; "${LEAD_ASK_OTHER}" is added. The tapped option comes back as their next message. Only when the answer changes the work; at most once per reply. Then finish with a short reply that leads into it; the question and buttons follow it.`,
|
|
1414
1422
|
handle: (ctx, args) => {
|
|
1415
1423
|
if (ctx.ask === undefined)
|
|
1416
1424
|
return { ok: false, message: "Questions with buttons are not available here. Ask in your reply instead." };
|
|
@@ -1422,11 +1430,11 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1422
1430
|
if (options.length < 2 || options.length > 4 || options.some(one => one === "" || !honest(one, 40)))
|
|
1423
1431
|
return { ok: false, message: "Give 2 to 4 short options of up to 40 characters each." };
|
|
1424
1432
|
const seen = new Set(options.map(one => one.toLowerCase()));
|
|
1425
|
-
if (seen.size !== options.length || seen.has(
|
|
1426
|
-
return { ok: false, message: `Each option must differ, and "${
|
|
1433
|
+
if (seen.size !== options.length || seen.has(LEAD_ASK_OTHER.toLowerCase()))
|
|
1434
|
+
return { ok: false, message: `Each option must differ, and "${LEAD_ASK_OTHER}" is added for you.` };
|
|
1427
1435
|
if (!ctx.ask(question, options))
|
|
1428
1436
|
return { ok: false, message: "This reply already asks a question. Ask one at a time." };
|
|
1429
|
-
return { ok: true, body: { asked: question, options: [...options,
|
|
1437
|
+
return { ok: true, body: { asked: question, options: [...options, LEAD_ASK_OTHER], shown: "as buttons after your reply; the tapped option arrives as the owner's next message" } };
|
|
1430
1438
|
},
|
|
1431
1439
|
},
|
|
1432
1440
|
search_project_memory: {
|
|
@@ -1918,13 +1926,13 @@ const MATE_TOOL_HANDLERS = {
|
|
|
1918
1926
|
},
|
|
1919
1927
|
};
|
|
1920
1928
|
/** The lead's tools, in the order the model is shown them: each one's inputSchema is its contract, derived. */
|
|
1921
|
-
export const
|
|
1929
|
+
export const LEAD_TOOLS = Object.keys(LEAD_TOOL_INPUTS).map((name) => {
|
|
1922
1930
|
const input = LEAD_TOOL_INPUTS[name];
|
|
1923
1931
|
// Flow proposals already used their strict contract in 0.9.36; keep that validation unchanged. A shared action's
|
|
1924
1932
|
// unknown keys reach prepareSharedAction, whose per-operation check refused them in 0.9.36 and still does. Every other
|
|
1925
1933
|
// tool ignored unknown arguments and still strips them.
|
|
1926
1934
|
const reader = name === "propose_flow" ? LEAD_TOOL_READ_INPUTS[name] : name === "propose_action" ? LEAD_TOOL_READ_INPUTS[name].loose() : LEAD_TOOL_READ_INPUTS[name].strip();
|
|
1927
|
-
const handler =
|
|
1935
|
+
const handler = LEAD_TOOL_HANDLERS[name];
|
|
1928
1936
|
return {
|
|
1929
1937
|
name,
|
|
1930
1938
|
description: handler.description,
|
|
@@ -1974,7 +1982,7 @@ function integrationIoOf(ctx) {
|
|
|
1974
1982
|
const dir = file === null ? null : dirname(file);
|
|
1975
1983
|
return { store: ctx.store, dir, telegramTokenFile: dir === null ? null : join(dir, "telegram-token"), env: process.env, repos: ctx.who.repos };
|
|
1976
1984
|
}
|
|
1977
|
-
/** The admitted project a proposal is about, when its arguments name one: the project, task, result, flow, card,
|
|
1985
|
+
/** The admitted project a proposal is about, when its arguments name one: the project, task, result, flow, card, subagent or decision. */
|
|
1978
1986
|
function proposalRepoOf(ctx, args) {
|
|
1979
1987
|
const { store } = ctx;
|
|
1980
1988
|
const id = (key) => Number.isSafeInteger(args[key]) && Number(args[key]) > 0 ? Number(args[key]) : null;
|
|
@@ -1987,7 +1995,7 @@ function proposalRepoOf(ctx, args) {
|
|
|
1987
1995
|
: id("run") !== null ? ofRun(id("run"))
|
|
1988
1996
|
: id("flow") !== null ? store.getFlow(id("flow"))?.repo ?? null
|
|
1989
1997
|
: id("card") !== null ? (() => { const card = store.getFlowCard(id("card")); return card === null ? null : store.getFlow(card.flow)?.repo ?? null; })()
|
|
1990
|
-
: id("
|
|
1998
|
+
: id("subagent") !== null ? store.getSubagent(id("subagent"))?.repo ?? null
|
|
1991
1999
|
: id("decision") !== null ? ofRun(store.getDecision(id("decision"))?.run ?? null)
|
|
1992
2000
|
: null;
|
|
1993
2001
|
return found !== null && ctx.who.repos.includes(found) ? found : null;
|
|
@@ -2043,7 +2051,7 @@ function rememberAboutYou(ctx, args) {
|
|
|
2043
2051
|
if (args["replaces"] !== undefined && Number(args["replaces"]) > lines.length)
|
|
2044
2052
|
return { ok: false, message: `replaces is a line number from your catch-up's aboutYou (1 to ${lines.length}).` };
|
|
2045
2053
|
const replaces = args["replaces"] !== undefined ? Number(args["replaces"]) : 0;
|
|
2046
|
-
const waiting = ctx.thread === undefined ? [] : ctx.store.
|
|
2054
|
+
const waiting = ctx.thread === undefined ? [] : ctx.store.listLeadProposals(ctx.thread, ["drafting", "pending", "confirming"])
|
|
2047
2055
|
.filter(one => one.kind === "action" && one.payload["operation"] === "lead_about_you");
|
|
2048
2056
|
const twin = waiting.find(one => String(one.payload["request"]?.["line"] ?? "").toLowerCase() === checked.line.toLowerCase());
|
|
2049
2057
|
if (twin !== undefined)
|
|
@@ -2060,7 +2068,7 @@ function rememberAboutYou(ctx, args) {
|
|
|
2060
2068
|
...(replaces === 0 ? {} : { replaces: { line: replaces, was: lines[replaces - 1] } }),
|
|
2061
2069
|
next: "Once confirmed it is in your next catch-up's aboutYou." } };
|
|
2062
2070
|
}
|
|
2063
|
-
/** get_person names people,
|
|
2071
|
+
/** get_person names people, subagents and team chat members on purpose; the scrub took them out with the rest. */
|
|
2064
2072
|
function personNamesBack(raw, scrubbed) {
|
|
2065
2073
|
if (!raw.ok || !scrubbed.ok)
|
|
2066
2074
|
return scrubbed;
|
|
@@ -2077,17 +2085,17 @@ function personNamesBack(raw, scrubbed) {
|
|
|
2077
2085
|
listed[index].name = one.name; });
|
|
2078
2086
|
return scrubbed;
|
|
2079
2087
|
}
|
|
2080
|
-
export const
|
|
2081
|
-
export function
|
|
2082
|
-
return
|
|
2088
|
+
export const LEAD_TOOL_SCHEMAS = LEAD_TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
2089
|
+
export function isLeadTool(name) {
|
|
2090
|
+
return LEAD_TOOLS.some(one => one.name === name);
|
|
2083
2091
|
}
|
|
2084
2092
|
/** Scrub every result. Only list_repos then adds validated display metadata
|
|
2085
2093
|
* from the same admitted project list; no user-authored result gets an exemption. */
|
|
2086
|
-
export function
|
|
2087
|
-
const tool =
|
|
2088
|
-
const scrub = view ??
|
|
2094
|
+
export function executeLeadTool(ctx, name, args, view) {
|
|
2095
|
+
const tool = LEAD_TOOLS.find(one => one.name === name);
|
|
2096
|
+
const scrub = view ?? leadViewContextFor(ctx.store, ctx.who);
|
|
2089
2097
|
if (tool === undefined)
|
|
2090
|
-
return { ok: false, message: `no tool named ${
|
|
2098
|
+
return { ok: false, message: `no tool named ${redactForLead(name, scrub)}` };
|
|
2091
2099
|
let result;
|
|
2092
2100
|
try {
|
|
2093
2101
|
// The call is read by the contract's compatible reader, before any check or handler: a refusal is every path-named
|
|
@@ -2107,10 +2115,10 @@ export function executeMateTool(ctx, name, args, view) {
|
|
|
2107
2115
|
// A result is checked against its output schema before it is scrubbed; a disagreement is reported, never refused.
|
|
2108
2116
|
if (result.ok)
|
|
2109
2117
|
reportToolOutput("lead", name, LEAD_TOOL_OUTPUTS[name], result.body);
|
|
2110
|
-
const sanitized = name === "get_person" ? personNamesBack(result,
|
|
2118
|
+
const sanitized = name === "get_person" ? personNamesBack(result, leadView(result, scrub)) : leadView(result, scrub);
|
|
2111
2119
|
if (name === "list_repos" && sanitized.ok) {
|
|
2112
2120
|
return { ok: true, body: { repos: ctx.who.repos.map((path, index) => ({
|
|
2113
|
-
repo: `r${index + 1}`, name:
|
|
2121
|
+
repo: `r${index + 1}`, name: projectLabelForLead(path, index, scrub.names),
|
|
2114
2122
|
})) } };
|
|
2115
2123
|
}
|
|
2116
2124
|
return sanitized;
|