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
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { labelOf, nameOf, summaryOf,
|
|
2
|
-
import {
|
|
1
|
+
import { labelOf, nameOf, summaryOf, SUBAGENT_MODELS, zonesOf } from "./subagent-admin.js";
|
|
2
|
+
import { SUBAGENT_TEMPLATES } from "./subagents.js";
|
|
3
3
|
import { projectToolsOf } from "./project-tools.js";
|
|
4
|
-
import { callWords, defaultRule, numberFields, receiptWords } from "./
|
|
5
|
-
import { MEMORY_CHARS, searchMemories } from "./
|
|
6
|
-
import { deskOf, localZone, routinesOf } from "./
|
|
7
|
-
import { weekOf, weekWords } from "./
|
|
4
|
+
import { callWords, defaultRule, numberFields, receiptWords } from "./subagent-tools.js";
|
|
5
|
+
import { MEMORY_CHARS, searchMemories } from "./subagent-memory.js";
|
|
6
|
+
import { deskOf, localZone, routinesOf } from "./subagent-desk.js";
|
|
7
|
+
import { weekOf, weekWords } from "./subagent-week.js";
|
|
8
8
|
import { describeSchedule, parseSchedule } from "./flow-schedule.js";
|
|
9
9
|
import { html, joinHtml, postForm } from "./html.js";
|
|
10
10
|
/** v94: the Tools section's rule rows; a limit's fields show only while "Up to a limit" is chosen. */
|
|
11
|
-
export const
|
|
11
|
+
export const SUBAGENT_CSS = `.subagent-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.subagent-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.subagent-tools h3{font-size:1rem;margin:0 0 4px}.subagent-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.subagent-tools .tool-rule:last-of-type{border-bottom:0}.subagent-tools .tool-rule .meta{display:block}.subagent-tools select,.subagent-tools input{max-width:100%;box-sizing:border-box}.subagent-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.subagent-tools .tool-rule:has(option[value="limit"]:checked) .tool-limit{display:flex}.subagent-tools .tool-rule>select{width:auto;min-width:16em}.subagent-tools .tool-limit select{width:auto}.subagent-tools .tool-limit input{width:7em}.subagent-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.subagent-tools button{min-height:44px}.subagent-activity li{overflow-wrap:anywhere}.subagent-memory .memory-tell,.subagent-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.subagent-memory .memory-tell textarea{flex:1 1 280px}.subagent-memory .memory-search input{flex:1 1 220px}.subagent-memory button{min-height:44px}.subagent-memories{list-style:none;padding:0;margin:0}.subagent-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.subagent-memories li:first-child{border-top:0}.subagent-memories .meta{display:block;font-size:.85rem}.subagent-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.subagent-overrides,.subagent-undo{padding-left:18px}.subagent-undo li{margin:6px 0}.subagent-undo form{display:inline}.subagent-week h3{font-size:1rem;margin:14px 0 4px}.subagent-week button{min-height:44px}.subagent-routines{list-style:none;padding:0;margin:8px 0}.subagent-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.subagent-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.subagent-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.subagent-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.subagent-tools select,.subagent-tools input{font-size:16px}}`;
|
|
12
12
|
const USES = [["free", "Do it"], ["limit", "Do it, up to a limit"], ["ask", "Ask first"], ["never", "Never"]];
|
|
13
13
|
/** v94: which project tools it may use, and its rule for each action. */
|
|
14
14
|
function toolsSection(store, mate, canManage) {
|
|
15
15
|
const name = nameOf(mate);
|
|
16
16
|
const tools = projectToolsOf(store, mate.repo);
|
|
17
|
-
const grants = store.
|
|
17
|
+
const grants = store.subagentGrants(mate.id);
|
|
18
18
|
const toolsPage = `/settings/tools?repo=${encodeURIComponent(mate.repo)}`;
|
|
19
19
|
const granted = grants.map(grant => {
|
|
20
20
|
const gone = !tools.some(one => one.name === grant.tool);
|
|
@@ -38,41 +38,41 @@ function toolsSection(store, mate, canManage) {
|
|
|
38
38
|
});
|
|
39
39
|
const undoBox = undoing.length > 0 && html `<details class="tool-undo"${grant.actions.some(one => grant.rules[one.name]?.undo !== undefined) && html ` open`}><summary>Undo</summary><p class="meta">When an action has an opposite, a person can press Undo on its receipts: the opposite is called with the same input.</p>${undoing}</details>`;
|
|
40
40
|
return canManage
|
|
41
|
-
? html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}${postForm(`/
|
|
41
|
+
? html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}${postForm(`/settings/lead/subagents/${mate.id}/tools`, html `${rows}${undoBox}<div class="tool-buttons"><button name="op" value="rules">Save rules</button><button name="op" value="revoke" class="secondary">Stop using ${grant.tool}</button></div>`, { hidden: { tool: grant.tool } })}</div>`
|
|
42
42
|
: html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}<ul>${rows}</ul></div>`;
|
|
43
43
|
});
|
|
44
44
|
const open = tools.filter(one => !grants.some(grant => grant.tool === one.name));
|
|
45
45
|
const add = !canManage ? null : tools.length === 0 ? html `<p class="meta">This project has no tools yet. <a href="${toolsPage}">Add one</a>, then let ${name} use it here.</p>`
|
|
46
|
-
: open.length === 0 ? null : postForm(`/
|
|
46
|
+
: open.length === 0 ? null : postForm(`/settings/lead/subagents/${mate.id}/tools`, html `<label>Let ${name} use<select name="tool">${open.map(one => html `<option value="${one.name}">${one.name}${one.spec.about === "" ? "" : ` — ${one.spec.about.slice(0, 80)}`}</option>`)}</select></label><button>Add tool</button>`, { attrs: { class: "tool-add" }, hidden: { op: "grant" } });
|
|
47
47
|
if (grants.length === 0 && add === null)
|
|
48
48
|
return "";
|
|
49
|
-
return html `<section class="card
|
|
49
|
+
return html `<section class="card subagent-tools" id="tools"><h2>Tools</h2>${grants.length === 0 && html `<p class="meta">${name} doesn't use any tools yet. Reading starts as “Do it”; everything else asks you first.</p>`}${granted}${add}</section>`;
|
|
50
50
|
}
|
|
51
51
|
const when = (at) => new Date(at).toLocaleString("en", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
|
|
52
52
|
const startOfDay = () => { const day = new Date(); day.setHours(0, 0, 0, 0); return day.toISOString(); };
|
|
53
53
|
/** Text whose line breaks show as breaks. */
|
|
54
54
|
const lines = (text) => joinHtml(text.split("\n"), html `<br>`);
|
|
55
|
-
/** A blank soul file: the sections a
|
|
55
|
+
/** A blank soul file: the sections a subagent reads, to fill in. */
|
|
56
56
|
export const BLANK_SOUL = `---\nname: \nrole: \n---\n\n## Who you are\n\n\n## How you write\n\n\n## What you know\n\n\n## Decide on your own\n- \n\n## Ask first\n- \n\n## Never\n- \n`;
|
|
57
|
-
export function
|
|
57
|
+
export function subagentsListHtml(store, mates, projects, projectName, canCreate, notice) {
|
|
58
58
|
const rows = mates.map(mate => {
|
|
59
|
-
const today = store.
|
|
60
|
-
const open = store.
|
|
61
|
-
return html `<article class="card" data-
|
|
59
|
+
const today = store.subagentTurnsSince(mate.id, startOfDay());
|
|
60
|
+
const open = store.openSubagentQuestions([mate.id]).length;
|
|
61
|
+
return html `<article class="card" data-subagent="${mate.handle}"><div class="flow-row"><h2><a href="/settings/lead/subagents/${mate.id}">${labelOf(mate)}</a></h2><span class="flow-counts">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused"} · ${today} today${open > 0 ? ` · ${open} question${open === 1 ? "" : "s"} for you` : ""}</span></div></article>`;
|
|
62
62
|
});
|
|
63
63
|
const create = canCreate && projects.length > 0
|
|
64
|
-
? html `<details class="card"${mates.length === 0 && html ` open`}><summary>
|
|
64
|
+
? html `<details class="card" id="add"${(mates.length === 0 || notice.adding === true) && html ` open`}><summary>Add subagent</summary>${postForm("/settings/lead/subagents/new", html `<label>Start from<select name="template">${SUBAGENT_TEMPLATES.map(one => html `<option value="${one.id}">${one.label}: ${one.about}</option>`)}<option value="blank">Blank: write its soul file yourself</option></select></label><label>Name<input name="name" maxlength="40" placeholder="keep the template's name"></label>${projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`}<button>Add subagent</button>`, { attrs: { "data-new-subagent": true } })}</details>`
|
|
65
65
|
: "";
|
|
66
|
-
const intro = html `<p class="meta">
|
|
67
|
-
return html `<section class="
|
|
66
|
+
const intro = html `<p class="meta">Your lead's helpers. Each works within rules you write and asks you when they say to. The quickest start is a <a href="/kits">starter kit</a>.</p>`;
|
|
67
|
+
return html `<section class="subagents">${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}${intro}${rows.length > 0 ? rows : html `<p class="meta">No subagents yet.</p>`}${create}</section>`;
|
|
68
68
|
}
|
|
69
69
|
/** v97: its week: what it did and cost, what people overrode, and calls that can be undone. */
|
|
70
70
|
function weekSection(store, mate, canManage) {
|
|
71
71
|
const week = weekOf(store, mate, new Date());
|
|
72
|
-
const overrides = week.overrides.length > 0 && html `<h3>What you overrode</h3><ul class="
|
|
73
|
-
const undo = week.undoable.length > 0 && html `<h3>Undo</h3><ul class="
|
|
74
|
-
const send = canManage && postForm(`/
|
|
75
|
-
return html `<details class="card
|
|
72
|
+
const overrides = week.overrides.length > 0 && html `<h3>What you overrode</h3><ul class="subagent-overrides">${week.overrides.map(one => html `<li>${one.said}${one.href !== null && html ` · <a href="${one.href}">open</a>`} <span class="meta">${when(one.at)}</span></li>`)}</ul>`;
|
|
73
|
+
const undo = week.undoable.length > 0 && html `<h3>Undo</h3><ul class="subagent-undo">${week.undoable.map(one => html `<li data-undo="${one.call}"><span>${one.words}</span> <span class="meta">${when(one.at)}${one.href !== null && html ` · <a href="${one.href}">open</a>`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="undo" class="secondary">Undo with ${one.undo}</button>`, { hidden: { id: one.call } })}</li>`)}</ul>`;
|
|
74
|
+
const send = canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="send" class="secondary">Send the week's report</button>`);
|
|
75
|
+
return html `<details class="card subagent-week" id="week" open><summary>This week</summary><p class="subagent-week-said">${lines(weekWords(mate, week))}</p>${overrides}${undo}${send}</details>`;
|
|
76
76
|
}
|
|
77
77
|
/** v96: its desk — where messages to it by name and its routines land — and its routines. */
|
|
78
78
|
function deskSection(store, mate, canManage) {
|
|
@@ -80,58 +80,58 @@ function deskSection(store, mate, canManage) {
|
|
|
80
80
|
const desk = deskOf(store, mate);
|
|
81
81
|
const routines = routinesOf(store, mate);
|
|
82
82
|
const intro = html `<p class="meta">Message ${name} by name in your chat app, like “@${mate.handle} where's order 1042?”. It lands on ${desk === null ? "its desk" : html `<a href="/flows/${desk.id}">${name}'s desk</a>`}, and the answer comes back to you.</p>`;
|
|
83
|
-
const list = routines.length > 0 && html `<ul class="
|
|
84
|
-
const add = canManage && postForm(`/
|
|
85
|
-
return html `<section class="card
|
|
83
|
+
const list = routines.length > 0 && html `<ul class="subagent-routines">${routines.map(one => html `<li data-routine="${one.id}"><span><strong>${scheduleWords(one.schedule)}</strong> · ${one.text}</span><span class="meta">${one.state === "paused" ? "Paused" : one.nextAt === null ? "" : `Next ${when(one.nextAt)}`}${one.lastAt !== null && ` · last ${when(one.lastAt)}${one.lastOutcome === null ? "" : `: ${one.lastOutcome}`}`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<button name="op" value="run" class="secondary">Run now</button><button name="op" value="remove" class="secondary">Remove</button>`, { attrs: { class: "routine-buttons" }, hidden: { id: one.id } })}</li>`)}</ul>`;
|
|
84
|
+
const add = canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<label>When<input type="text" name="schedule" maxlength="80" placeholder="weekdays 09:00" required></label><label>What to do<input type="text" name="text" maxlength="200" placeholder="Look up yesterday's refunds and tell me the total." required></label><button>Add routine</button>`, { attrs: { class: "routine-add" }, hidden: { op: "add" } });
|
|
85
|
+
return html `<section class="card subagent-desk" id="desk"><h2>Desk and routines</h2>${intro}${list}${add}${canManage && html `<p class="meta">A routine puts a card on its desk on that schedule (“weekdays 09:00”, “daily 17:00”, “monday 09:00”, “every 2 hours”; times are this computer's, ${localZone()}, unless you name one); the answer goes to ${mate.manager}.</p>`}</section>`;
|
|
86
86
|
}
|
|
87
87
|
/** A schedule's stored form ("weekdays:09:00@Europe/London") in words. */
|
|
88
88
|
const scheduleWords = (text) => { const parsed = parseSchedule(text); return parsed === null ? text : describeSchedule(parsed); };
|
|
89
89
|
/** v95: what it remembers — what its people told it, and what it kept from cards — to search, edit and forget. */
|
|
90
90
|
function memorySection(store, mate, canManage, query) {
|
|
91
91
|
const name = nameOf(mate);
|
|
92
|
-
const all = store.
|
|
92
|
+
const all = store.subagentMemories(mate.id, { limit: 500 });
|
|
93
93
|
const shown = query === null || query.trim() === "" ? all.slice(0, 30) : searchMemories(store, mate, query);
|
|
94
|
-
const tell = canManage && postForm(`/
|
|
95
|
-
const search = (all.length > 8 || (query ?? "") !== "") && html `<form method="get" action="/
|
|
94
|
+
const tell = canManage && postForm(`/settings/lead/subagents/${mate.id}/note`, html `<label class="sr-only" for="subagent-note">Tell ${name} something</label><textarea id="subagent-note" name="note" rows="2" maxlength="${MEMORY_CHARS}" placeholder="For example: this week, offer free shipping instead of a refund when you can."></textarea><button>Tell ${name}</button>`, { attrs: { class: "memory-tell" } });
|
|
95
|
+
const search = (all.length > 8 || (query ?? "") !== "") && html `<form method="get" action="/settings/lead/subagents/${mate.id}#memory" class="memory-search" role="search"><label class="sr-only" for="memory-q">Search what ${name} remembers</label><input id="memory-q" type="search" name="q" value="${query ?? ""}" placeholder="Search what ${name} remembers"><button class="secondary">Search</button></form>`;
|
|
96
96
|
const items = shown.map(one => {
|
|
97
97
|
const card = one.card === null ? null : store.getFlowCard(one.card);
|
|
98
98
|
const whence = one.source === "person" ? `${one.createdBy} told it` : card === null ? "it kept this" : html `it kept this from <a href="/flows/${card.flow}?card=${card.id}">${card.title.slice(0, 60)}</a>`;
|
|
99
|
-
const edit = canManage && html `<details class="memory-edit"><summary>Edit</summary>${postForm(`/
|
|
99
|
+
const edit = canManage && html `<details class="memory-edit"><summary>Edit</summary>${postForm(`/settings/lead/subagents/${mate.id}/memory`, html `<label class="sr-only" for="memory-${one.id}">Memory</label><textarea id="memory-${one.id}" name="text" rows="2" maxlength="${MEMORY_CHARS}">${one.text}</textarea><div class="memory-buttons"><button name="op" value="edit">Save</button><button name="op" value="forget" class="secondary">Forget</button></div>`, { hidden: { id: one.id } })}</details>`;
|
|
100
100
|
return html `<li data-memory="${one.id}" data-source="${one.source}"><span>${one.text}</span> <span class="meta">${whence} · ${when(one.updatedAt)}</span>${edit}</li>`;
|
|
101
101
|
});
|
|
102
102
|
const empty = (query ?? "") !== "" ? html `<p class="meta">Nothing ${name} remembers matches that.</p>` : html `<p class="meta">Nothing yet. What you tell ${name} is kept here, and it keeps short facts from the cards it works.</p>`;
|
|
103
|
-
return html `<section class="card
|
|
103
|
+
return html `<section class="card subagent-memory" id="memory"><h2>Memory</h2>${tell}${search}${shown.length === 0 ? empty : html `<ul class="subagent-memories">${items}</ul>`}${query === null && all.length > shown.length && html `<p class="meta">${all.length - shown.length} older ${all.length - shown.length === 1 ? "memory" : "memories"}: search to find them.</p>`}</section>`;
|
|
104
104
|
}
|
|
105
|
-
export function
|
|
105
|
+
export function subagentPageHtml(store, mate, viewer, projectName, canManage, approvers, notice) {
|
|
106
106
|
const name = nameOf(mate);
|
|
107
|
-
const questions = store.
|
|
107
|
+
const questions = store.openSubagentQuestions([mate.id]);
|
|
108
108
|
const asked = questions.map(question => {
|
|
109
109
|
const card = store.getFlowCard(question.card);
|
|
110
110
|
const flow = card === null ? null : store.getFlow(card.flow);
|
|
111
111
|
const mine = question.askedOf === viewer;
|
|
112
112
|
const cardLink = card === null || flow === null ? "a card" : html `<a href="/flows/${flow.id}?card=${card.id}">${card.title}</a>`;
|
|
113
|
-
const answer = mine && html `<div class="question-options">${question.options.map(one => postForm(`/
|
|
114
|
-
return html `<article class="card
|
|
113
|
+
const answer = mine && html `<div class="question-options">${question.options.map(one => postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<button>${one.label}</button>`, { hidden: { choice: one.id } }))}</div>${postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<label>Or answer in your words<textarea name="text" rows="2" maxlength="2000"></textarea></label><button>Answer</button>`, { attrs: { class: "question-reply" } })}`;
|
|
114
|
+
return html `<article class="card subagent-question" data-question="${question.id}"${question.suggestion !== null && html ` data-suggestion`}><p class="meta">${question.suggestion === null ? html `About ${cardLink}` : "Suggests a rule change"} · ${when(question.createdAt)}${!mine && ` · for ${question.askedOf}`}</p><p><strong>${question.question}</strong></p>${answer}</article>`;
|
|
115
115
|
});
|
|
116
116
|
const zones = zonesOf(store, mate);
|
|
117
117
|
const where = zones.length === 0
|
|
118
|
-
? html `<p class="meta">Not on any zone yet. In a flow, choose ${name} under “Who decides” on a “Person decides” zone, or add a “
|
|
118
|
+
? html `<p class="meta">Not on any zone yet. In a flow, choose ${name} under “Who decides” on a “Person decides” zone, or add a “Subagent handles it” zone.</p>`
|
|
119
119
|
: html `<ul>${zones.map(one => html `<li><a href="/flows/${one.flow}">${one.flowName}</a> · ${one.kind === "decides" ? "decides" : "handles"} ${one.title}</li>`)}</ul>`;
|
|
120
120
|
// What it did and why, with every tool call it made or asked to make (v94), newest first.
|
|
121
121
|
const events = [
|
|
122
|
-
...store.
|
|
123
|
-
...store.
|
|
122
|
+
...store.subagentEvents(mate.id, 40).filter(one => one.kind !== "note").map(one => ({ kind: one.kind, said: one.said, card: one.card, at: one.at })),
|
|
123
|
+
...store.subagentCallsOf(mate.id, 40).map(one => ({ kind: "call", said: `Used ${callWords(one.tool, one.action, one.input, 200)} · ${receiptWords(store, one)}`, card: one.card, at: one.createdAt })),
|
|
124
124
|
].sort((a, b) => b.at.localeCompare(a.at)).slice(0, 40);
|
|
125
|
-
const activity = events.length === 0 ? html `<p class="meta">Nothing yet.</p>` : html `<ul class="
|
|
125
|
+
const activity = events.length === 0 ? html `<p class="meta">Nothing yet.</p>` : html `<ul class="subagent-activity">${events.map(one => {
|
|
126
126
|
const card = one.card === null ? null : store.getFlowCard(one.card);
|
|
127
127
|
return html `<li data-event="${one.kind}">${one.said}${card !== null && html ` · <a href="/flows/${card.flow}?card=${card.id}">open</a>`} <span class="meta">${when(one.at)}</span></li>`;
|
|
128
128
|
})}</ul>`;
|
|
129
|
-
const versions = store.
|
|
129
|
+
const versions = store.subagentVersions(mate.id);
|
|
130
130
|
const latest = versions[0];
|
|
131
|
-
const manage = canManage && html `<div class="
|
|
132
|
-
const soul = html `<details class="card"${canManage && html ` open`}><summary>Soul file</summary><p class="meta">Who ${name} is and its rules, read every turn. Version ${mate.version}${latest !== undefined && ` · saved by ${latest.savedBy}, ${when(latest.savedAt)}`} · <a href="/
|
|
133
|
-
? postForm(`/
|
|
131
|
+
const manage = canManage && html `<div class="subagent-actions">${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button${mate.state === "active" && html ` class="secondary"`}>${mate.state === "active" ? `Pause ${name}` : `Resume ${name}`}</button>`, { hidden: { state: mate.state === "active" ? "paused" : "active" } })}${postForm(`/settings/lead/subagents/${mate.id}/summary`, html `<button class="secondary">Send today's summary</button>`)}</div>`;
|
|
132
|
+
const soul = html `<details class="card"${canManage && html ` open`}><summary>Soul file</summary><p class="meta">Who ${name} is and its rules, read every turn. Version ${mate.version}${latest !== undefined && ` · saved by ${latest.savedBy}, ${when(latest.savedAt)}`} · <a href="/settings/lead/subagents/${mate.id}/soul.md" download>Download</a></p>${canManage
|
|
133
|
+
? postForm(`/settings/lead/subagents/${mate.id}/soul`, html `<label class="sr-only" for="subagent-soul">Soul file</label><textarea id="subagent-soul" name="soul" rows="24" class="mono" maxlength="12000" spellcheck="false">${notice.soulDraft ?? mate.soul}</textarea><button>Save</button>`, { attrs: { "data-soul-form": true } })
|
|
134
134
|
: html `<pre class="mono">${mate.soul}</pre>`}</details>`;
|
|
135
|
-
const settings = canManage && html `<details class="card"><summary>Settings</summary>${postForm(`/
|
|
136
|
-
return html `${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}<p class="meta">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused: its decisions go to people"} · reports to ${mate.manager}</p>${manage}${questions.length > 0 && html `<section><h2>Questions</h2>${asked}</section>`}<section class="card"><h2>Today</h2><p class="
|
|
135
|
+
const settings = canManage && html `<details class="card"><summary>Settings</summary>${postForm(`/settings/lead/subagents/${mate.id}/settings`, html `<label>Model<select name="model">${SUBAGENT_MODELS.map(one => html `<option value="${one}"${(mate.model ?? "default") === one && html ` selected`}>${one === "default" ? "The lead chat's model" : one[0].toUpperCase() + one.slice(1)}</option>`)}</select></label><label>Turns a day, at most<input type="number" name="dailyTurns" min="1" max="2000" value="${mate.dailyTurns}"></label><label>Reports to<select name="manager">${[...new Set([mate.manager, ...approvers])].map(one => html `<option value="${one}"${one === mate.manager && html ` selected`}>${one}</option>`)}</select></label><button>Save settings</button>`)}${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button class="danger">Remove ${name} from the team</button>`, { attrs: { class: "danger-zone" }, hidden: { state: "removed" } })}</details>`;
|
|
136
|
+
return html `${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}<p class="meta">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused: its decisions go to people"} · reports to ${mate.manager}</p>${manage}${questions.length > 0 && html `<section><h2>Questions</h2>${asked}</section>`}<section class="card"><h2>Today</h2><p class="subagent-summary">${lines(summaryOf(store, mate, startOfDay()).said)}</p></section>${weekSection(store, mate, canManage)}<section class="card"><h2>Works on</h2>${where}</section>${toolsSection(store, mate, canManage)}${deskSection(store, mate, canManage)}${memorySection(store, mate, canManage, notice.query ?? null)}${soul}<details class="card"><summary>What ${name} did</summary>${activity}</details>${settings}`;
|
|
137
137
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { run, type ExecResult } from "./exec.js";
|
|
2
2
|
import type { ContractResult } from "./contracts/contract.js";
|
|
3
|
-
import { type TurnAnswer } from "./contracts/
|
|
3
|
+
import { type TurnAnswer } from "./contracts/subagent-turn.js";
|
|
4
4
|
import { type Overrun } from "./text-limits.js";
|
|
5
5
|
export declare const SOUL_CHARS = 12000;
|
|
6
6
|
export type Soul = {
|
|
@@ -24,17 +24,17 @@ export declare function parseSoul(text: string): {
|
|
|
24
24
|
};
|
|
25
25
|
/** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
|
|
26
26
|
export declare function withSection(soul: string, title: string, text: string): string;
|
|
27
|
-
/** Starters to copy and make your own. Every rule is plain words the
|
|
28
|
-
export declare const
|
|
27
|
+
/** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
|
|
28
|
+
export declare const SUBAGENT_TEMPLATES: readonly {
|
|
29
29
|
id: string;
|
|
30
30
|
label: string;
|
|
31
31
|
about: string;
|
|
32
32
|
soul: string;
|
|
33
33
|
}[];
|
|
34
|
-
export type { TurnAction, TurnAnswer } from "./contracts/
|
|
34
|
+
export type { TurnAction, TurnAnswer } from "./contracts/subagent-turn.js";
|
|
35
35
|
/** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
|
|
36
|
-
* asked once to shorten (
|
|
37
|
-
* "note" is a decision or send-back note. Not in the turn's schema (src/contracts/
|
|
36
|
+
* asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
|
|
37
|
+
* "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
|
|
38
38
|
* refuse an answer a few characters over one whole. */
|
|
39
39
|
export declare const TURN_LIMITS: {
|
|
40
40
|
readonly answer: 60;
|
|
@@ -53,7 +53,7 @@ export type TurnContext = {
|
|
|
53
53
|
role: string;
|
|
54
54
|
flow: string;
|
|
55
55
|
zone: string;
|
|
56
|
-
/** decide: a "Person decides" zone; handle: a "
|
|
56
|
+
/** decide: a "Person decides" zone; handle: a "Subagent handles it" zone. */
|
|
57
57
|
kind: "decide" | "handle";
|
|
58
58
|
instructions: string | null;
|
|
59
59
|
/** decide: the draft being decided on (null: none), whether it can be sent back, and to whom hard ones go. */
|
|
@@ -111,7 +111,7 @@ export type TurnContext = {
|
|
|
111
111
|
};
|
|
112
112
|
export declare function turnPrompt(context: TurnContext): string;
|
|
113
113
|
/**
|
|
114
|
-
* A turn's answer, read through its contract (src/contracts/
|
|
114
|
+
* A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
|
|
115
115
|
* allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
|
|
116
116
|
* (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
|
|
117
117
|
* each cut to an answer's length, and a tool name is cut to 140, as they always were.
|
|
@@ -142,7 +142,7 @@ export declare const TURN_TIMEOUT_MS = 180000;
|
|
|
142
142
|
type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
|
|
143
143
|
/** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
|
|
144
144
|
export declare function claudeTurnRunner(runner?: CommandRunner): TurnRunner;
|
|
145
|
-
/** How a
|
|
146
|
-
export declare const
|
|
145
|
+
/** How a subagent is named to people: "Maya · Support". */
|
|
146
|
+
export declare const subagentLabel: (soul: Pick<Soul, "name" | "role">) => string;
|
|
147
147
|
/** How its acts read in a card's history: "Maya (AI)". */
|
|
148
|
-
export declare const
|
|
148
|
+
export declare const subagentActor: (soul: Pick<Soul, "name">) => string;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* subagents (v92): agents that work a flow's cards the way an employee
|
|
3
3
|
* would, within rules a person wrote for them.
|
|
4
4
|
*
|
|
5
|
-
* A
|
|
5
|
+
* A subagent is a soul file: who it is, how it writes, what it knows, what it
|
|
6
6
|
* decides on its own, what it asks about first, and what it never does. It is
|
|
7
7
|
* staffed on zones: a "Person decides" zone it decides (approve, send back,
|
|
8
|
-
* or hand it to a person), or a "
|
|
8
|
+
* or hand it to a person), or a "Subagent handles it" zone where it picks
|
|
9
9
|
* where the card goes and writes what the next zones send. It never acts
|
|
10
10
|
* itself: each turn answers with one decision as JSON, through Claude with no
|
|
11
11
|
* tools, no repository and no MCP servers, and Toolroll carries it out
|
|
12
12
|
* within the zone's choices. The card is data, never instructions. (v94: it
|
|
13
13
|
* may ask for a project tool call on its turn; Toolroll makes it, or
|
|
14
|
-
* asks a person first, by the rules in
|
|
14
|
+
* asks a person first, by the rules in subagent-tools.ts.)
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Subagents do flow work only: they never approve a code task, a merge or
|
|
17
17
|
* spending — those stay with people (or a signed hands-off mode).
|
|
18
18
|
*/
|
|
19
19
|
import { Buffer } from "node:buffer";
|
|
@@ -24,13 +24,13 @@ import { strictJsonParse } from "./converse.js";
|
|
|
24
24
|
import { scanForSecrets } from "./evidence.js";
|
|
25
25
|
import { run } from "./exec.js";
|
|
26
26
|
import { ALL_CREDENTIAL_ENV } from "./provider.js";
|
|
27
|
-
import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/
|
|
27
|
+
import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/subagent-turn.js";
|
|
28
28
|
import { LIMITS } from "./decision.js";
|
|
29
29
|
import { overruns, TEXT_LIMITS } from "./text-limits.js";
|
|
30
30
|
export const SOUL_CHARS = 12_000;
|
|
31
31
|
const HANDLE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
32
32
|
/** A handle from a name: "Maya" → "maya". */
|
|
33
|
-
export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "
|
|
33
|
+
export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "subagent";
|
|
34
34
|
export const isHandle = (value) => typeof value === "string" && HANDLE.test(value);
|
|
35
35
|
/** A soul file read: its name and role (front matter) and its sections. */
|
|
36
36
|
export function parseSoul(text) {
|
|
@@ -47,9 +47,9 @@ export function parseSoul(text) {
|
|
|
47
47
|
const field = (key) => new RegExp(`^${key}:\\s*(.+)$`, "im").exec(front[1])?.[1]?.trim() ?? "";
|
|
48
48
|
const name = field("name"), role = field("role");
|
|
49
49
|
if (!/^[\p{L}][\p{L}\p{N} .'-]{0,39}$/u.test(name))
|
|
50
|
-
return { ok: false, problem: "Give the
|
|
50
|
+
return { ok: false, problem: "Give the subagent a name of up to 40 letters, like “name: Maya”." };
|
|
51
51
|
if (role === "" || role.length > 40)
|
|
52
|
-
return { ok: false, problem: "Say what the
|
|
52
|
+
return { ok: false, problem: "Say what the subagent does in up to 40 characters, like “role: Support”." };
|
|
53
53
|
const body = normal.slice(front[0].length);
|
|
54
54
|
const sections = [];
|
|
55
55
|
for (const part of body.split(/^## /m).slice(1)) {
|
|
@@ -57,7 +57,7 @@ export function parseSoul(text) {
|
|
|
57
57
|
sections.push({ title: title.trim(), body: rest.join("\n").trim() });
|
|
58
58
|
}
|
|
59
59
|
if (sections.length === 0 || sections.every(one => one.body === ""))
|
|
60
|
-
return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the
|
|
60
|
+
return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the subagent is." };
|
|
61
61
|
return { ok: true, soul: { name, role, sections } };
|
|
62
62
|
}
|
|
63
63
|
/** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
|
|
@@ -73,8 +73,8 @@ export function withSection(soul, title, text) {
|
|
|
73
73
|
return `${parts.join("").trimEnd()}\n`;
|
|
74
74
|
}
|
|
75
75
|
const soul = (name, role, parts) => `---\nname: ${name}\nrole: ${role}\n---\n\n${Object.entries(parts).map(([title, lines]) => `## ${title}\n${lines.join("\n")}`).join("\n\n")}\n`;
|
|
76
|
-
/** Starters to copy and make your own. Every rule is plain words the
|
|
77
|
-
export const
|
|
76
|
+
/** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
|
|
77
|
+
export const SUBAGENT_TEMPLATES = [
|
|
78
78
|
{
|
|
79
79
|
id: "support", label: "Support rep", about: "Answers customers, handles refunds and replacements within limits, and hands angry or costly cases to you.",
|
|
80
80
|
soul: soul("Maya", "Support", {
|
|
@@ -121,10 +121,10 @@ export const TEAMMATE_TEMPLATES = [
|
|
|
121
121
|
},
|
|
122
122
|
];
|
|
123
123
|
/** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
|
|
124
|
-
* asked once to shorten (
|
|
125
|
-
* "note" is a decision or send-back note. Not in the turn's schema (src/contracts/
|
|
124
|
+
* asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
|
|
125
|
+
* "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
|
|
126
126
|
* refuse an answer a few characters over one whole. */
|
|
127
|
-
export const TURN_LIMITS = { answer: TEXT_LIMITS.
|
|
127
|
+
export const TURN_LIMITS = { answer: TEXT_LIMITS.subagentAnswer, text: TEXT_LIMITS.stageOutput, note: LIMITS.note, question: TEXT_LIMITS.subagentQuestion, reason: TEXT_LIMITS.subagentReason, remember: TEXT_LIMITS.subagentRemember };
|
|
128
128
|
/** The fields of an answer over their limits. */
|
|
129
129
|
export function turnOverruns(answer) {
|
|
130
130
|
return overruns({ answer: answer.answer, text: answer.text, note: answer.note, question: answer.question, reason: answer.reason, remember: answer.remember }, TURN_LIMITS);
|
|
@@ -186,7 +186,7 @@ export function turnPrompt(context) {
|
|
|
186
186
|
/** How many options an ask offers, and the longest tool name and input a tool call may give. */
|
|
187
187
|
const ASK_OPTIONS = 4, TOOL_NAME_CHARS = 140, TOOL_INPUT_CHARS = 8000;
|
|
188
188
|
/**
|
|
189
|
-
* A turn's answer, read through its contract (src/contracts/
|
|
189
|
+
* A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
|
|
190
190
|
* allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
|
|
191
191
|
* (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
|
|
192
192
|
* each cut to an answer's length, and a tool name is cut to 140, as they always were.
|
|
@@ -204,7 +204,7 @@ export function readTurn(value, context) {
|
|
|
204
204
|
const refuse = (path, what) => ({ ok: false, issues: [{ path, kind: "bad-value", line: `${path}: ${what}` }] });
|
|
205
205
|
if (!allowed.includes(answer.action))
|
|
206
206
|
return refuse("action", `"${answer.action}" isn't one this zone allows (${allowed.map(one => `"${one}"`).join(", ")})`);
|
|
207
|
-
// A tool call is checked against the
|
|
207
|
+
// A tool call is checked against the subagent's rules where it's carried out; here only that it names one and fits.
|
|
208
208
|
if (answer.action === "use_tool" && answer.tool === "")
|
|
209
209
|
return refuse("tool", "required for use_tool");
|
|
210
210
|
if (answer.action === "use_tool" && answer.input.length > TOOL_INPUT_CHARS)
|
|
@@ -221,7 +221,7 @@ export const TURN_TIMEOUT_MS = 180_000;
|
|
|
221
221
|
/** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
|
|
222
222
|
export function claudeTurnRunner(runner = run) {
|
|
223
223
|
return async (request) => {
|
|
224
|
-
const dir = mkdtempSync(join(tmpdir(), "standing-orders-
|
|
224
|
+
const dir = mkdtempSync(join(tmpdir(), "standing-orders-subagent-"));
|
|
225
225
|
const started = Date.now();
|
|
226
226
|
try {
|
|
227
227
|
const result = await runner("claude", [
|
|
@@ -259,7 +259,7 @@ function costOf(body) {
|
|
|
259
259
|
const tokensIn = count("input_tokens") + count("cache_creation_input_tokens") + count("cache_read_input_tokens"), tokensOut = count("output_tokens");
|
|
260
260
|
return { ...(cost === undefined ? {} : { costUsd: cost }), ...(tokensIn > 0 ? { tokensIn } : {}), ...(tokensOut > 0 ? { tokensOut } : {}) };
|
|
261
261
|
}
|
|
262
|
-
/** How a
|
|
263
|
-
export const
|
|
262
|
+
/** How a subagent is named to people: "Maya · Support". */
|
|
263
|
+
export const subagentLabel = (soul) => `${soul.name} · ${soul.role}`;
|
|
264
264
|
/** How its acts read in a card's history: "Maya (AI)". */
|
|
265
|
-
export const
|
|
265
|
+
export const subagentActor = (soul) => `${soul.name} (AI)`;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LeadHistoryMessage, type LeadProviderAnswer, type LeadToolSchema } from "./converse.js";
|
|
2
2
|
import { run, type ExecResult } from "./exec.js";
|
|
3
3
|
import type { SubscriptionChatProviderId } from "./store.js";
|
|
4
|
-
export type
|
|
4
|
+
export type SubscriptionLeadRequest = {
|
|
5
5
|
provider: SubscriptionChatProviderId;
|
|
6
6
|
/** `default` lets the authenticated harness choose its current default. */
|
|
7
7
|
model: string;
|
|
8
8
|
system: string;
|
|
9
9
|
dataDocument: string;
|
|
10
|
-
history: readonly
|
|
11
|
-
tools: readonly
|
|
10
|
+
history: readonly LeadHistoryMessage[];
|
|
11
|
+
tools: readonly LeadToolSchema[];
|
|
12
12
|
/** A structured answer of another shape (the memory pass's verdict): this JSON Schema is the harness's
|
|
13
|
-
* `--json-schema` (Codex's `--output-schema`) in place of the
|
|
13
|
+
* `--json-schema` (Codex's `--output-schema`) in place of the lead envelope, and its JSON text comes back as the
|
|
14
14
|
* answer's text, unread, for the caller's own schema to parse. */
|
|
15
15
|
outputSchema?: Record<string, unknown>;
|
|
16
16
|
timeoutMs: number;
|
|
@@ -20,31 +20,31 @@ export type SubscriptionMateRequest = {
|
|
|
20
20
|
* (Claude). Display only: the finished answer is still parsed whole. */
|
|
21
21
|
onText?: (text: string) => void;
|
|
22
22
|
};
|
|
23
|
-
export type
|
|
23
|
+
export type SubscriptionLeadRunner = (request: SubscriptionLeadRequest) => Promise<{
|
|
24
24
|
ok: true;
|
|
25
|
-
answer:
|
|
25
|
+
answer: LeadProviderAnswer;
|
|
26
26
|
} | {
|
|
27
27
|
ok: false;
|
|
28
28
|
problem: string;
|
|
29
29
|
}>;
|
|
30
30
|
type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
|
|
31
|
-
/** The model-visible request. Tool results are already redacted by
|
|
32
|
-
export declare function
|
|
31
|
+
/** The model-visible request. Tool results are already redacted by leadView. */
|
|
32
|
+
export declare function composeSubscriptionLeadPrompt(request: Omit<SubscriptionLeadRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
|
|
33
33
|
/** Strictly turn the harness's structured output into the provider-neutral answer. */
|
|
34
|
-
export declare function
|
|
34
|
+
export declare function parseSubscriptionLeadAnswer(text: string, usage?: {
|
|
35
35
|
tokensIn?: unknown;
|
|
36
36
|
tokensOut?: unknown;
|
|
37
37
|
}): {
|
|
38
38
|
ok: true;
|
|
39
|
-
answer:
|
|
39
|
+
answer: LeadProviderAnswer;
|
|
40
40
|
} | {
|
|
41
41
|
ok: false;
|
|
42
42
|
problem: string;
|
|
43
43
|
};
|
|
44
44
|
/** Production runner; injectable so tests never consume a subscription turn. */
|
|
45
|
-
export declare function
|
|
45
|
+
export declare function performSubscriptionLeadRequest(request: SubscriptionLeadRequest, runner?: CommandRunner): Promise<{
|
|
46
46
|
ok: true;
|
|
47
|
-
answer:
|
|
47
|
+
answer: LeadProviderAnswer;
|
|
48
48
|
} | {
|
|
49
49
|
ok: false;
|
|
50
50
|
problem: string;
|