toolroll 0.9.54 → 0.9.56
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 +19 -0
- package/README.md +29 -12
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +17 -22
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +14 -15
- package/dist/browser-workspace.js +0 -3
- 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 +27 -34
- 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-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +15 -10
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- 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/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +7 -7
- package/dist/contracts/console-api.js +13 -12
- 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/health.d.ts +1 -3
- 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/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- 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/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- 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/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +10 -1
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +2 -1
- package/dist/flow-triggers.js +17 -15
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +38 -49
- package/dist/flows.js +21 -20
- package/dist/guides.js +112 -6
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +22 -24
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- 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 +12 -2
- package/dist/lead-context.js +15 -17
- 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/{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/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +3 -2
- package/dist/limits-ui.js +8 -8
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/mcp-oauth.js +35 -36
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +2 -2
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +46 -41
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- 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.d.ts +4 -3
- package/dist/project-delete-ui.js +16 -28
- 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/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- 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.d.ts +11 -2
- package/dist/serve.js +105 -99
- package/dist/server/chat.d.ts +7 -10
- package/dist/server/chat.js +984 -1021
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +854 -773
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +8 -5
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +581 -573
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +39 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +154 -0
- package/dist/server/render-settings.js +580 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +167 -77
- package/dist/server/runtime.d.ts +28 -20
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2186 -2121
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1489 -1420
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +5 -4
- package/dist/spend-ui.js +17 -40
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- 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/subagents-ui.js +137 -0
- 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 +20 -33
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teams-chat.js +6 -6
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- 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-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram-status.js +1 -1
- 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 +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -2
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -20
- package/dist/teammates-ui.js +0 -166
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What a
|
|
2
|
+
* What a subagent remembers (v95), and how it learns from its people.
|
|
3
3
|
*
|
|
4
4
|
* Memory is short facts, each a line: what its people told it ("this week,
|
|
5
5
|
* offer free shipping instead of a refund" — v92's notes), and what it kept
|
|
6
6
|
* from its own turns ("Sam prefers email to phone"). Every turn reads what
|
|
7
7
|
* its people said lately, and what it kept that fits the card. People search,
|
|
8
|
-
* edit and forget any of it on the
|
|
8
|
+
* edit and forget any of it on the subagent's page or through the lead.
|
|
9
9
|
*
|
|
10
10
|
* Learning: when its manager approves the same ask-first tool action again
|
|
11
|
-
* and again (five in a row, none denied), the
|
|
11
|
+
* and again (five in a row, none denied), the subagent suggests loosening
|
|
12
12
|
* that one rule — "You approved my last 5 refunds (amount 55 to 70): may I
|
|
13
13
|
* make them on my own up to 75?" — as a question its manager accepts or
|
|
14
14
|
* not, in their chat app or on its page. Accepting changes only that rule,
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { scanForSecrets } from "./evidence.js";
|
|
18
18
|
import { notifyPeople } from "./flow-people.js";
|
|
19
|
-
import { labelOf, nameOf } from "./
|
|
20
|
-
import { defaultRule, numberFields, setToolRules } from "./
|
|
19
|
+
import { labelOf, nameOf } from "./subagent-admin.js";
|
|
20
|
+
import { defaultRule, numberFields, setToolRules } from "./subagent-tools.js";
|
|
21
21
|
export const MEMORY_CHARS = 300;
|
|
22
|
-
/** Facts a
|
|
22
|
+
/** Facts a subagent keeps on its own: past this, its oldest are forgotten. What people told it is never dropped this way. */
|
|
23
23
|
export const MEMORY_CAP = 300;
|
|
24
|
-
/** Approvals in a row of one action before the
|
|
24
|
+
/** Approvals in a row of one action before the subagent suggests loosening its rule. */
|
|
25
25
|
export const SUGGEST_AFTER = 5;
|
|
26
26
|
export const ACCEPT = "accept", DISMISS = "dismiss";
|
|
27
27
|
const STOP = new Set("about after again also been before being could does doing from have here into just like more most much must only other over same some such than that their them then there these they this those through very want were what when where which while will with would your yours please thanks thank hello".split(" "));
|
|
@@ -45,57 +45,57 @@ export function cleanMemory(text) {
|
|
|
45
45
|
if (line.length > MEMORY_CHARS)
|
|
46
46
|
return { ok: false, said: `Keep a memory to ${MEMORY_CHARS} characters; put lasting rules in its soul file.` };
|
|
47
47
|
if (scanForSecrets(line).length > 0)
|
|
48
|
-
return { ok: false, said: "That looks like a key or password. Keep secrets out of a
|
|
48
|
+
return { ok: false, said: "That looks like a key or password. Keep secrets out of a subagent's memory." };
|
|
49
49
|
return { ok: true, text: line };
|
|
50
50
|
}
|
|
51
|
-
/** Keep one thing: from a person (what they told it), or from the
|
|
51
|
+
/** Keep one thing: from a person (what they told it), or from the subagent itself on a turn. The same words twice are kept once. */
|
|
52
52
|
export function remember(store, mate, text, from, now) {
|
|
53
|
-
const clean = cleanMemory(from.source === "
|
|
53
|
+
const clean = cleanMemory(from.source === "subagent" ? text.slice(0, MEMORY_CHARS) : text);
|
|
54
54
|
if (!clean.ok)
|
|
55
55
|
return clean;
|
|
56
|
-
const same = store.
|
|
56
|
+
const same = store.subagentMemories(mate.id).find(one => one.text.toLowerCase() === clean.text.toLowerCase());
|
|
57
57
|
if (same !== undefined)
|
|
58
58
|
return { ok: true, said: `${nameOf(mate)} already remembers that.`, id: same.id };
|
|
59
59
|
// Saying nearly the same thing again on the same card (another turn of the same visit) refreshes what it kept instead of adding a second line.
|
|
60
|
-
if (from.source === "
|
|
61
|
-
const near = store.
|
|
60
|
+
if (from.source === "subagent" && from.card !== undefined && from.card !== null) {
|
|
61
|
+
const near = store.subagentMemories(mate.id, { source: "subagent" }).find(one => one.card === from.card && similar(one.text, clean.text));
|
|
62
62
|
if (near !== undefined) {
|
|
63
|
-
store.
|
|
63
|
+
store.editSubagentMemory(near.id, clean.text, from.by, now);
|
|
64
64
|
return { ok: true, said: `${nameOf(mate)} already remembers that.`, id: near.id };
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
const id = store.
|
|
68
|
-
if (from.source === "
|
|
69
|
-
for (const old of store.
|
|
70
|
-
store.
|
|
67
|
+
const id = store.addSubagentMemory({ subagent: mate.id, text: clean.text, source: from.source, card: from.card ?? null, by: from.by }, now);
|
|
68
|
+
if (from.source === "subagent")
|
|
69
|
+
for (const old of store.subagentMemories(mate.id, { source: "subagent" }).slice(MEMORY_CAP))
|
|
70
|
+
store.forgetSubagentMemory(old.id, "limit", now);
|
|
71
71
|
return { ok: true, said: `${nameOf(mate)} will remember that.`, id };
|
|
72
72
|
}
|
|
73
|
-
/** Something a person tells a
|
|
74
|
-
export function
|
|
73
|
+
/** Something a person tells a subagent (the page's "Tell Maya something", or the lead): kept in its memory, read by every turn while it's among the latest ten. */
|
|
74
|
+
export function tellSubagent(store, mate, note, by, now) {
|
|
75
75
|
const kept = remember(store, mate, note, { source: "person", by }, now);
|
|
76
76
|
return kept.ok ? { ok: true, said: `${nameOf(mate)} will keep that in mind.` } : kept;
|
|
77
77
|
}
|
|
78
78
|
export function editMemory(store, mate, id, text, by, now) {
|
|
79
|
-
const memory = store.
|
|
80
|
-
if (memory === null || memory.
|
|
79
|
+
const memory = store.subagentMemory(id);
|
|
80
|
+
if (memory === null || memory.subagent !== mate.id)
|
|
81
81
|
return { ok: false, said: "It doesn't remember that any more." };
|
|
82
82
|
const clean = cleanMemory(text);
|
|
83
83
|
if (!clean.ok)
|
|
84
84
|
return clean;
|
|
85
|
-
store.
|
|
85
|
+
store.editSubagentMemory(id, clean.text, by, now);
|
|
86
86
|
return { ok: true, said: "Saved." };
|
|
87
87
|
}
|
|
88
88
|
export function forgetMemory(store, mate, id, by, now) {
|
|
89
|
-
const memory = store.
|
|
90
|
-
if (memory === null || memory.
|
|
89
|
+
const memory = store.subagentMemory(id);
|
|
90
|
+
if (memory === null || memory.subagent !== mate.id)
|
|
91
91
|
return { ok: false, said: "It doesn't remember that any more." };
|
|
92
|
-
store.
|
|
92
|
+
store.forgetSubagentMemory(id, by, now);
|
|
93
93
|
return { ok: true, said: `${nameOf(mate)} forgot it.` };
|
|
94
94
|
}
|
|
95
95
|
/** Memories whose text holds every word searched for. */
|
|
96
96
|
export function searchMemories(store, mate, query) {
|
|
97
97
|
const words = query.toLowerCase().split(/[^\p{L}\p{N}$]+/u).filter(one => one.length >= 2);
|
|
98
|
-
return store.
|
|
98
|
+
return store.subagentMemories(mate.id, { words, limit: 100 });
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* What one turn reads: what its people told it lately (oldest first), and
|
|
@@ -103,13 +103,13 @@ export function searchMemories(store, mate, query) {
|
|
|
103
103
|
* words, then its newest — up to eight.
|
|
104
104
|
*/
|
|
105
105
|
export function memoriesFor(store, mate, cardText, now) {
|
|
106
|
-
const told = store.
|
|
107
|
-
const own = store.
|
|
106
|
+
const told = store.subagentMemories(mate.id, { source: "person", limit: 10 }).reverse();
|
|
107
|
+
const own = store.subagentMemories(mate.id, { source: "subagent", limit: MEMORY_CAP });
|
|
108
108
|
const words = wordsOf(cardText);
|
|
109
109
|
const scored = own.map(one => ({ one, score: words.filter(word => one.text.toLowerCase().includes(word)).length })).filter(one => one.score > 0)
|
|
110
110
|
.sort((a, b) => b.score - a.score || b.one.id - a.one.id).slice(0, 6).map(one => one.one);
|
|
111
111
|
const kept = [...scored, ...own.filter(one => !scored.includes(one))].slice(0, 8);
|
|
112
|
-
store.
|
|
112
|
+
store.markSubagentMemoriesUsed(kept.map(one => one.id), now);
|
|
113
113
|
return { told, kept };
|
|
114
114
|
}
|
|
115
115
|
const numberOf = (value) => typeof value === "number" && Number.isFinite(value) ? value
|
|
@@ -119,22 +119,22 @@ export const roundUp = (value) => { const step = value < 20 ? 1 : value < 100 ?
|
|
|
119
119
|
/**
|
|
120
120
|
* After a person approves an ask-first call: when they have approved this
|
|
121
121
|
* action enough times in a row since it last suggested anything about it,
|
|
122
|
-
* the
|
|
122
|
+
* the subagent suggests the rule that would have let it make them all.
|
|
123
123
|
*/
|
|
124
124
|
export function considerSuggestion(store, mate, approved, now) {
|
|
125
|
-
const grant = store.
|
|
125
|
+
const grant = store.subagentGrant(mate.id, approved.tool);
|
|
126
126
|
const info = grant?.actions.find(one => one.name === approved.action);
|
|
127
127
|
if (grant === null || info === undefined)
|
|
128
128
|
return null;
|
|
129
129
|
const rule = grant.rules[approved.action] ?? defaultRule(info);
|
|
130
130
|
if (rule.use === "never" || (rule.use === "free" && rule.limit === undefined))
|
|
131
131
|
return null;
|
|
132
|
-
const earlier = store.
|
|
132
|
+
const earlier = store.subagentSuggestionsFor(mate.id, approved.tool, approved.action);
|
|
133
133
|
if (earlier.some(one => one.state === "open"))
|
|
134
134
|
return null;
|
|
135
135
|
const since = earlier[0]?.createdAt ?? "";
|
|
136
136
|
const run = [];
|
|
137
|
-
for (const call of store.
|
|
137
|
+
for (const call of store.subagentCallsOf(mate.id, 400).filter(one => one.tool === approved.tool && one.action === approved.action && one.rule === "ask" && one.decidedBy !== null && (one.decidedAt ?? "") > since)) {
|
|
138
138
|
if (call.state === "denied")
|
|
139
139
|
break;
|
|
140
140
|
run.push(call);
|
|
@@ -162,8 +162,8 @@ export function considerSuggestion(store, mate, approved, now) {
|
|
|
162
162
|
const said = suggested.limit === undefined
|
|
163
163
|
? `You approved my last ${run.length} ${approved.action} calls on ${approved.tool}. May I make them on my own from now on?`
|
|
164
164
|
: `You approved my last ${run.length} ${approved.action} calls on ${approved.tool}${range}. May I make them on my own up to ${suggested.limit.field} ${suggested.limit.over}, and ask you above that?`;
|
|
165
|
-
const id = store.
|
|
166
|
-
const question = store.
|
|
165
|
+
const id = store.addSubagentSuggestion({ subagent: mate.id, tool: approved.tool, action: approved.action, rule: suggested, was: rule, evidence: run.map(one => one.id), said }, now);
|
|
166
|
+
const question = store.openSubagentQuestion({ subagent: mate.id, card: card.id, entry: approved.entry, question: said, options: [{ id: ACCEPT, label: "Yes, change it" }, { id: DISMISS, label: "Not now" }], askedOf: mate.manager, suggestion: id }, now);
|
|
167
167
|
if (question === null)
|
|
168
168
|
return id;
|
|
169
169
|
notifyPeople(store, card, [mate.manager], null, { key: `teammate-q:${question}`, attention: false, subject: `${labelOf(mate)} suggests a rule change`,
|
|
@@ -172,24 +172,24 @@ export function considerSuggestion(store, mate, approved, now) {
|
|
|
172
172
|
}
|
|
173
173
|
/** Its manager answers a suggestion: accept changes the one rule (if it is still what it was), anything else leaves it; their words are remembered. */
|
|
174
174
|
export function answerSuggestion(store, mate, suggestion, answer, now) {
|
|
175
|
-
const offered = store.
|
|
175
|
+
const offered = store.subagentSuggestion(suggestion);
|
|
176
176
|
if (offered === null || offered.state !== "open")
|
|
177
177
|
return { ok: false, said: "That suggestion was already answered." };
|
|
178
178
|
if (answer.choice === ACCEPT) {
|
|
179
|
-
const grant = store.
|
|
179
|
+
const grant = store.subagentGrant(mate.id, offered.tool);
|
|
180
180
|
const current = grant === null ? null : grant.rules[offered.action] ?? null;
|
|
181
181
|
const { undo: _undo, ...compared } = current ?? { use: "never" };
|
|
182
182
|
if (current === null || JSON.stringify(compared) !== JSON.stringify((({ undo: _was, ...rest }) => rest)(offered.was))) {
|
|
183
|
-
store.
|
|
183
|
+
store.decideSubagentSuggestion(offered.id, "stale", null, now);
|
|
184
184
|
return { ok: false, said: "Its rules changed since it suggested this, so nothing was changed." };
|
|
185
185
|
}
|
|
186
186
|
const changed = setToolRules(store, mate, offered.tool, { [offered.action]: { use: offered.rule.use, limit: offered.rule.limit ?? null, undo: current.undo ?? "" } }, answer.by, now);
|
|
187
187
|
if (!changed.ok)
|
|
188
188
|
return changed;
|
|
189
|
-
store.
|
|
189
|
+
store.decideSubagentSuggestion(offered.id, "accepted", answer.by, now);
|
|
190
190
|
return { ok: true, said: `Changed. ${nameOf(mate)} makes ${offered.action} calls on its own ${offered.rule.limit === undefined ? "from now on" : `up to ${offered.rule.limit.field} ${offered.rule.limit.over}`}.` };
|
|
191
191
|
}
|
|
192
|
-
store.
|
|
192
|
+
store.decideSubagentSuggestion(offered.id, "dismissed", answer.by, now);
|
|
193
193
|
if (answer.text !== null && answer.text.trim() !== "")
|
|
194
194
|
remember(store, mate, `About ${offered.action} on ${offered.tool}: ${answer.text.trim()}`.slice(0, MEMORY_CHARS), { source: "person", by: answer.by }, now);
|
|
195
195
|
return { ok: true, said: "Left as it is." };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ChatBinding, ChatContent, ChatEvent, ChatState } from "./chat-delivery-state.js";
|
|
2
|
-
import type { Store, TelegramBinding,
|
|
3
|
-
import type { InlineButton } from "./telegram-
|
|
4
|
-
/** The key notifyPeople gives a
|
|
5
|
-
export declare const
|
|
2
|
+
import type { Store, TelegramBinding, SubagentQuestionRow } from "./store.js";
|
|
3
|
+
import type { InlineButton } from "./telegram-lead.js";
|
|
4
|
+
/** The key notifyPeople gives a subagent's question: flow-card:<card>:teammate-q:<question>:<person>. */
|
|
5
|
+
export declare const SUBAGENT_Q_KEY: RegExp;
|
|
6
6
|
export declare const IN_WORDS = "Answer in words";
|
|
7
7
|
/** The question a notice is about, while it is still open. */
|
|
8
|
-
export declare function openQuestionOf(store: Store, dedupeKey: string):
|
|
8
|
+
export declare function openQuestionOf(store: Store, dedupeKey: string): SubagentQuestionRow | null;
|
|
9
9
|
/** Buttons for the question, minted before the send (for the person it asks only); `place` stamps the message they land on. */
|
|
10
|
-
export declare function telegramQuestionButtons(store: Store, binding: TelegramBinding, question:
|
|
10
|
+
export declare function telegramQuestionButtons(store: Store, binding: TelegramBinding, question: SubagentQuestionRow, now: Date): {
|
|
11
11
|
keyboard: InlineButton[][];
|
|
12
12
|
tokens: string[];
|
|
13
13
|
} | null;
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A
|
|
2
|
+
* A subagent's question, answered in the chat app (v93).
|
|
3
3
|
*
|
|
4
|
-
* When a
|
|
5
|
-
* the notice reaches them in their chat app under the
|
|
4
|
+
* When a subagent asks its person something ("Refund all $200, or half?"),
|
|
5
|
+
* the notice reaches them in their chat app under the subagent's name, with
|
|
6
6
|
* one button per option and one to answer in their own words. A tap answers
|
|
7
7
|
* at once; "Answer in words" opens a prompt: on Telegram, a reply to it; in
|
|
8
8
|
* Slack, Discord and Teams, their next message in their chat with Standing
|
|
9
9
|
* Orders ("cancel" leaves it). Either way the answer goes through the same
|
|
10
|
-
* door as the card's own panel (
|
|
10
|
+
* door as the card's own panel (answerSubagentQuestion): once, and only from
|
|
11
11
|
* the person it was asked of. Answered anywhere, every button retires.
|
|
12
12
|
*/
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
14
|
import { partContent, savedChatPart } from "./chat-delivery-state.js";
|
|
15
|
-
import {
|
|
16
|
-
import { labelOf } from "./
|
|
15
|
+
import { answerSubagentQuestion } from "./subagent-work.js";
|
|
16
|
+
import { labelOf } from "./subagent-admin.js";
|
|
17
17
|
import { telegramButton } from "./contracts/telegram-callback.js";
|
|
18
|
-
/** The key notifyPeople gives a
|
|
19
|
-
export const
|
|
18
|
+
/** The key notifyPeople gives a subagent's question: flow-card:<card>:teammate-q:<question>:<person>. */
|
|
19
|
+
export const SUBAGENT_Q_KEY = /^flow-card:[1-9][0-9]{0,14}:teammate-q:([1-9][0-9]{0,14}):/;
|
|
20
20
|
export const IN_WORDS = "Answer in words";
|
|
21
21
|
const PROMPT_MS = 30 * 60_000;
|
|
22
22
|
const ANSWER_CHARS = 2000;
|
|
23
23
|
/** The question a notice is about, while it is still open. */
|
|
24
24
|
export function openQuestionOf(store, dedupeKey) {
|
|
25
|
-
const match =
|
|
26
|
-
const question = match === null ? null : store.
|
|
25
|
+
const match = SUBAGENT_Q_KEY.exec(dedupeKey);
|
|
26
|
+
const question = match === null ? null : store.subagentQuestion(Number(match[1]));
|
|
27
27
|
return question !== null && question.state === "open" ? question : null;
|
|
28
28
|
}
|
|
29
|
-
const who = (store, question) => { const mate = store.
|
|
29
|
+
const who = (store, question) => { const mate = store.getSubagent(question.subagent); return mate === null ? "Your subagent" : labelOf(mate).split(" · ")[0]; };
|
|
30
30
|
/** Everyone else's buttons retire once a question is answered anywhere. */
|
|
31
31
|
function retireEverywhere(store, question, now, state) {
|
|
32
32
|
store.retireTelegramQuestion(question, now);
|
|
@@ -51,7 +51,7 @@ export function telegramQuestionButtons(store, binding, question, now) {
|
|
|
51
51
|
export function applyTelegramQuestionTap(store, binding, action, message, now) {
|
|
52
52
|
if (action.consumedAt !== null || action.expiresAt <= now.toISOString())
|
|
53
53
|
return [{ kind: "ack", text: "That was already answered, or these buttons are too old." }];
|
|
54
|
-
const question = store.
|
|
54
|
+
const question = store.subagentQuestion(action.question);
|
|
55
55
|
if (question === null || question.state !== "open") {
|
|
56
56
|
store.retireTelegramQuestion(action.question, now);
|
|
57
57
|
return [{ kind: "ack", text: "That was already answered." }];
|
|
@@ -59,7 +59,7 @@ export function applyTelegramQuestionTap(store, binding, action, message, now) {
|
|
|
59
59
|
if (action.choice === null)
|
|
60
60
|
return [{ kind: "ack", text: "Send your answer" }, { kind: "prompt", question: question.id, placeholder: "Your answer",
|
|
61
61
|
text: `Your answer to ${who(store, question)}: “${question.question}” Reply to this message.` }];
|
|
62
|
-
const answered =
|
|
62
|
+
const answered = answerSubagentQuestion(store, question.id, { choice: action.choice, text: null, by: binding.approver, via: "telegram" }, now);
|
|
63
63
|
if (!answered.ok)
|
|
64
64
|
return [{ kind: "ack", text: answered.said.slice(0, 190) }];
|
|
65
65
|
retireEverywhere(store, question.id, now);
|
|
@@ -75,7 +75,7 @@ export function applyTelegramQuestionReply(store, binding, prompt, text, now) {
|
|
|
75
75
|
return "Send the answer itself as a reply.";
|
|
76
76
|
if (said.length > ANSWER_CHARS)
|
|
77
77
|
return `Keep it under ${ANSWER_CHARS.toLocaleString("en-US")} characters.`;
|
|
78
|
-
const answered =
|
|
78
|
+
const answered = answerSubagentQuestion(store, prompt.question, { choice: null, text: said, by: binding.approver, via: "telegram" }, now);
|
|
79
79
|
retireEverywhere(store, prompt.question, now);
|
|
80
80
|
return answered.ok ? `✅ ${answered.said}` : answered.said;
|
|
81
81
|
}
|
|
@@ -117,7 +117,7 @@ export function applyChatQuestionTap(options, event, binding, token, now) {
|
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
119
|
const questionId = Number(action["question"]), part = Number(action["part"]);
|
|
120
|
-
const question = store.
|
|
120
|
+
const question = store.subagentQuestion(questionId);
|
|
121
121
|
if (action["consumed"] !== null || String(action["expires"]) <= now.toISOString() || question === null || question.state !== "open") {
|
|
122
122
|
retireEverywhere(store, questionId, now, state);
|
|
123
123
|
repaint(state, part, event, "This was already answered.");
|
|
@@ -132,7 +132,7 @@ export function applyChatQuestionTap(options, event, binding, token, now) {
|
|
|
132
132
|
say(`Your next message here is your answer to ${who(store, question)}: “${question.question}” Send “cancel” to leave it.`);
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
|
-
const answered =
|
|
135
|
+
const answered = answerSubagentQuestion(store, questionId, { choice: String(action["choice"]), text: null, by: binding.approver, via: options.label.toLowerCase() }, now);
|
|
136
136
|
if (!answered.ok) {
|
|
137
137
|
say(answered.said);
|
|
138
138
|
return true;
|
|
@@ -169,7 +169,7 @@ export function answerChatQuestionPrompt(options, event, binding, input, now) {
|
|
|
169
169
|
say(`That's too long to take from here. Keep it under ${ANSWER_CHARS.toLocaleString("en-US")} characters, or answer on the card.`);
|
|
170
170
|
return true;
|
|
171
171
|
}
|
|
172
|
-
const answered =
|
|
172
|
+
const answered = answerSubagentQuestion(store, questionId, { choice: null, text: said, by: binding.approver, via: options.label.toLowerCase() }, now);
|
|
173
173
|
retireEverywhere(store, questionId, now, state);
|
|
174
174
|
say(answered.ok ? `✅ ${answered.said}` : answered.said);
|
|
175
175
|
return true;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolCaller } from "./flow-actions.js";
|
|
2
2
|
import { type ProjectTool, type ToolSpec } from "./project-tools.js";
|
|
3
|
-
import type { Store,
|
|
3
|
+
import type { Store, SubagentCallRow, SubagentGrantRow, SubagentRow, ToolActionInfo, ToolRule } from "./store.js";
|
|
4
4
|
/** Lists what a tool offers; injectable so tests never start a server. */
|
|
5
5
|
export type ToolLister = (spec: ToolSpec, values: Record<string, string>) => Promise<{
|
|
6
6
|
ok: true;
|
|
@@ -32,11 +32,11 @@ export declare function withActions(rules: Record<string, ToolRule>, actions: re
|
|
|
32
32
|
rules: Record<string, ToolRule>;
|
|
33
33
|
};
|
|
34
34
|
/** The rule that applies to one call with this input, and why, in words. */
|
|
35
|
-
export declare function ruleFor(grant: Pick<
|
|
35
|
+
export declare function ruleFor(grant: Pick<SubagentGrantRow, "actions" | "rules">, action: string, input: Record<string, unknown>): {
|
|
36
36
|
use: ToolRule["use"];
|
|
37
37
|
why: string;
|
|
38
38
|
};
|
|
39
|
-
/** A rule in words, for the
|
|
39
|
+
/** A rule in words, for the subagent's turn and its page. */
|
|
40
40
|
export declare function ruleWords(rule: ToolRule): string;
|
|
41
41
|
/** Number fields an action's input declares: what a limit can be set on. */
|
|
42
42
|
export declare function numberFields(action: Pick<ToolActionInfo, "input">): string[];
|
|
@@ -51,17 +51,17 @@ export type OfferedTool = {
|
|
|
51
51
|
input: string;
|
|
52
52
|
rule: string;
|
|
53
53
|
};
|
|
54
|
-
/** What a
|
|
55
|
-
export declare function offeredTools(store: Store, mate:
|
|
54
|
+
/** What a subagent may use now: its grants' actions on tools the project still has, "never" left out. */
|
|
55
|
+
export declare function offeredTools(store: Store, mate: SubagentRow): OfferedTool[];
|
|
56
56
|
/** Before a turn: re-list tools whose listing is old (or never happened), so the turn sees what they offer today. A tool that can't be reached keeps its last listing. */
|
|
57
|
-
export declare function refreshGrants(store: Store, mate:
|
|
58
|
-
/** Let a
|
|
59
|
-
export declare function grantTool(store: Store, mate:
|
|
57
|
+
export declare function refreshGrants(store: Store, mate: SubagentRow, now: Date, io: ToolIo): Promise<void>;
|
|
58
|
+
/** Let a subagent use one of the project's tools. Its actions start at their defaults: reading is free, everything else asks first. */
|
|
59
|
+
export declare function grantTool(store: Store, mate: SubagentRow, tool: string, by: string, now: Date, io?: ToolIo): Promise<Done>;
|
|
60
60
|
/** A grant from what the tool offers (or, not reachable now, the names its last test found). */
|
|
61
|
-
export declare function grantListed(store: Store, mate:
|
|
62
|
-
export declare function revokeTool(store: Store, mate:
|
|
61
|
+
export declare function grantListed(store: Store, mate: SubagentRow, tool: ProjectTool, actions: ToolActionInfo[] | null, by: string, now: Date): Done;
|
|
62
|
+
export declare function revokeTool(store: Store, mate: SubagentRow, tool: string, by: string, now?: Date): Done;
|
|
63
63
|
/** Check one rule: an action the tool offers, a known use, and a limit on one of its number fields. */
|
|
64
|
-
export declare function checkRule(grant: Pick<
|
|
64
|
+
export declare function checkRule(grant: Pick<SubagentGrantRow, "tool" | "actions">, action: string, rule: {
|
|
65
65
|
use: unknown;
|
|
66
66
|
limit?: {
|
|
67
67
|
field: unknown;
|
|
@@ -76,7 +76,7 @@ export declare function checkRule(grant: Pick<TeammateGrantRow, "tool" | "action
|
|
|
76
76
|
said: string;
|
|
77
77
|
};
|
|
78
78
|
/** Set the rules for some of a granted tool's actions; the others keep theirs. */
|
|
79
|
-
export declare function setToolRules(store: Store, mate:
|
|
79
|
+
export declare function setToolRules(store: Store, mate: SubagentRow, tool: string, changes: Record<string, {
|
|
80
80
|
use: unknown;
|
|
81
81
|
limit?: {
|
|
82
82
|
field: unknown;
|
|
@@ -87,17 +87,17 @@ export declare function setToolRules(store: Store, mate: TeammateRow, tool: stri
|
|
|
87
87
|
/**
|
|
88
88
|
* Make one call now: a free one, or one a person approved (exactly as they
|
|
89
89
|
* saw it). What the tool said is kept on the receipt, secrets scrubbed; a
|
|
90
|
-
* tool that says it failed is an answer the
|
|
90
|
+
* tool that says it failed is an answer the subagent reads, not trouble.
|
|
91
91
|
*/
|
|
92
|
-
export declare function makeCall(store: Store, call:
|
|
92
|
+
export declare function makeCall(store: Store, call: SubagentCallRow, repo: string, io: ToolIo, now: Date, options?: {
|
|
93
93
|
byPerson?: boolean;
|
|
94
|
-
}): Promise<
|
|
94
|
+
}): Promise<SubagentCallRow>;
|
|
95
95
|
/** A receipt in words: what happened to the call. */
|
|
96
|
-
export declare function callOutcome(call:
|
|
96
|
+
export declare function callOutcome(call: SubagentCallRow): string;
|
|
97
97
|
/** A receipt as a person reads it on the card: what happened, and who decided. */
|
|
98
|
-
export declare function receiptWords(store: Store, call:
|
|
98
|
+
export declare function receiptWords(store: Store, call: SubagentCallRow): string;
|
|
99
99
|
/** The Tools section's form, read into rule changes: every action the grant knows. */
|
|
100
|
-
export declare function rulesFromForm(grant: Pick<
|
|
100
|
+
export declare function rulesFromForm(grant: Pick<SubagentGrantRow, "actions">, field: (key: string) => string | null): Record<string, {
|
|
101
101
|
use: unknown;
|
|
102
102
|
limit?: {
|
|
103
103
|
field: unknown;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The tools a
|
|
2
|
+
* The tools a subagent may use (v94), and its rule for each of their actions.
|
|
3
3
|
*
|
|
4
|
-
* A
|
|
4
|
+
* A subagent never calls a tool itself. On its turn it may ask for one call
|
|
5
5
|
* ("use_tool": which action, with what input, and why), and Toolroll
|
|
6
6
|
* checks the rule its manager set for that action:
|
|
7
7
|
*
|
|
8
|
-
* - do it: the call is made and its answer goes back to the
|
|
8
|
+
* - do it: the call is made and its answer goes back to the subagent, whose
|
|
9
9
|
* turn goes on;
|
|
10
10
|
* - ask first: the exact call goes to the zone's person, in their chat app
|
|
11
11
|
* and on the card. Approve makes exactly that call; Deny doesn't; words
|
|
12
|
-
* tell the
|
|
12
|
+
* tell the subagent what to do instead;
|
|
13
13
|
* - never: the action isn't offered at all.
|
|
14
14
|
*
|
|
15
15
|
* "Do it" can ask first above a number in the input ("refunds up to $50"),
|
|
16
16
|
* so a limit holds in code, not only in the soul file's words. Every call,
|
|
17
|
-
* made or not, is a receipt on the card and on the
|
|
17
|
+
* made or not, is a receipt on the card and on the subagent's page.
|
|
18
18
|
*/
|
|
19
19
|
import { redactSecretLines, scanForSecrets } from "./evidence.js";
|
|
20
20
|
import { scrubSecrets } from "./flow-secrets.js";
|
|
@@ -52,7 +52,7 @@ export function ruleFor(grant, action, input) {
|
|
|
52
52
|
}
|
|
53
53
|
return { use: "free", why: "" };
|
|
54
54
|
}
|
|
55
|
-
/** A rule in words, for the
|
|
55
|
+
/** A rule in words, for the subagent's turn and its page. */
|
|
56
56
|
export function ruleWords(rule) {
|
|
57
57
|
if (rule.use === "never")
|
|
58
58
|
return "never";
|
|
@@ -91,12 +91,12 @@ export function callWords(tool, action, input, cap = 300) {
|
|
|
91
91
|
const line = `${tool} → ${action}${parts.length === 0 ? "" : ` · ${parts.join(" · ")}`}`;
|
|
92
92
|
return line.length <= cap ? line : `${line.slice(0, cap - 1)}…`;
|
|
93
93
|
}
|
|
94
|
-
/** What a
|
|
94
|
+
/** What a subagent may use now: its grants' actions on tools the project still has, "never" left out. */
|
|
95
95
|
export function offeredTools(store, mate) {
|
|
96
96
|
const present = new Set(projectToolsOf(store, mate.repo).map(one => one.name));
|
|
97
97
|
// Sprint 8: a tool the organisation policy doesn't allow isn't offered.
|
|
98
98
|
const policy = store.orgPolicy();
|
|
99
|
-
return store.
|
|
99
|
+
return store.subagentGrants(mate.id).filter(grant => present.has(grant.tool) && toolRefusal(policy, grant.tool) === null).flatMap(grant => grant.actions.flatMap(action => {
|
|
100
100
|
const rule = grant.rules[action.name] ?? defaultRule(action);
|
|
101
101
|
if (rule.use === "never")
|
|
102
102
|
return [];
|
|
@@ -107,7 +107,7 @@ export function offeredTools(store, mate) {
|
|
|
107
107
|
/** Before a turn: re-list tools whose listing is old (or never happened), so the turn sees what they offer today. A tool that can't be reached keeps its last listing. */
|
|
108
108
|
export async function refreshGrants(store, mate, now, io) {
|
|
109
109
|
const tools = projectToolsOf(store, mate.repo);
|
|
110
|
-
for (const grant of store.
|
|
110
|
+
for (const grant of store.subagentGrants(mate.id)) {
|
|
111
111
|
if (grant.listedAt !== null && now.getTime() - Date.parse(grant.listedAt) < LISTING_MS)
|
|
112
112
|
continue;
|
|
113
113
|
const tool = tools.find(one => one.name === grant.tool);
|
|
@@ -116,18 +116,18 @@ export async function refreshGrants(store, mate, now, io) {
|
|
|
116
116
|
const listed = await listed_(tool, mate.repo, io);
|
|
117
117
|
if (!listed.ok)
|
|
118
118
|
continue;
|
|
119
|
-
store.
|
|
119
|
+
store.saveSubagentGrant({ subagent: mate.id, tool: grant.tool, ...withActions(grant.rules, listed.actions), listedAt: now.toISOString() }, grant.updatedBy, now);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
const listed_ = (tool, repo, io) => (io.listTools ?? ((spec, values) => listProjectToolActions(spec, values, { timeoutMs: 60_000, omitEnv: ALL_CREDENTIAL_ENV })))(tool.spec, readToolSecrets(repo, tool.name, io.toolHome));
|
|
123
123
|
/** Names only, from the tool's last test: what a grant starts with when it can't be listed right now. */
|
|
124
124
|
const namesOnly = (tool) => (tool.lastTest?.tools ?? []).map(name => ({ name, about: "", input: null, readOnly: false }));
|
|
125
|
-
/** Let a
|
|
125
|
+
/** Let a subagent use one of the project's tools. Its actions start at their defaults: reading is free, everything else asks first. */
|
|
126
126
|
export async function grantTool(store, mate, tool, by, now, io = {}) {
|
|
127
127
|
const found = projectToolsOf(store, mate.repo).find(one => one.name === tool);
|
|
128
128
|
if (found === undefined)
|
|
129
129
|
return { ok: false, said: `This project has no tool called ${tool}. Add it on the Tools page first.` };
|
|
130
|
-
if (store.
|
|
130
|
+
if (store.subagentGrant(mate.id, tool) !== null)
|
|
131
131
|
return { ok: true, said: `It can already use ${tool}.` };
|
|
132
132
|
const listed = await listed_(found, mate.repo, io);
|
|
133
133
|
return grantListed(store, mate, found, listed.ok ? listed.actions : null, by, now);
|
|
@@ -137,18 +137,18 @@ export function grantListed(store, mate, tool, actions, by, now) {
|
|
|
137
137
|
const offered = actions ?? namesOnly(tool);
|
|
138
138
|
if (offered.length === 0)
|
|
139
139
|
return { ok: false, said: `${tool.name} didn't say what it can do. Test it on the Tools page, then try again.` };
|
|
140
|
-
store.
|
|
140
|
+
store.saveSubagentGrant({ subagent: mate.id, tool: tool.name, ...withActions({}, offered), listedAt: actions === null ? null : now.toISOString() }, by, now);
|
|
141
141
|
const free = offered.filter(one => defaultRule(one).use === "free").length;
|
|
142
142
|
return { ok: true, said: `It can use ${tool.name} now: ${free} of its ${offered.length} action${offered.length === 1 ? "" : "s"} freely (the ones that only read), and the rest after a person approves each call. Change that under Tools.` };
|
|
143
143
|
}
|
|
144
144
|
export function revokeTool(store, mate, tool, by, now = new Date()) {
|
|
145
|
-
for (const call of store.
|
|
146
|
-
store.
|
|
147
|
-
const question = store.
|
|
145
|
+
for (const call of store.subagentCallsOf(mate.id, 200).filter(one => one.tool === tool && one.state === "asked")) {
|
|
146
|
+
store.moveSubagentCall(call.id, ["asked"], { state: "refused", result: `It can't use ${tool} any more.` }, new Date());
|
|
147
|
+
const question = store.subagentQuestionForCall(call.id);
|
|
148
148
|
if (question !== null)
|
|
149
|
-
store.
|
|
149
|
+
store.dropSubagentQuestion(question.id, new Date());
|
|
150
150
|
}
|
|
151
|
-
return store.
|
|
151
|
+
return store.dropSubagentGrant(mate.id, tool, by, now) ? { ok: true, said: `It can't use ${tool} any more.` } : { ok: true, said: `It wasn't using ${tool}.` };
|
|
152
152
|
}
|
|
153
153
|
/** Check one rule: an action the tool offers, a known use, and a limit on one of its number fields. */
|
|
154
154
|
export function checkRule(grant, action, rule) {
|
|
@@ -176,7 +176,7 @@ export function checkRule(grant, action, rule) {
|
|
|
176
176
|
}
|
|
177
177
|
/** Set the rules for some of a granted tool's actions; the others keep theirs. */
|
|
178
178
|
export function setToolRules(store, mate, tool, changes, by, now) {
|
|
179
|
-
const grant = store.
|
|
179
|
+
const grant = store.subagentGrant(mate.id, tool);
|
|
180
180
|
if (grant === null)
|
|
181
181
|
return { ok: false, said: `It doesn't use ${tool}. Let it use ${tool} first.` };
|
|
182
182
|
const rules = { ...grant.rules };
|
|
@@ -186,35 +186,35 @@ export function setToolRules(store, mate, tool, changes, by, now) {
|
|
|
186
186
|
return { ok: false, said: checked.said };
|
|
187
187
|
rules[action] = checked.rule;
|
|
188
188
|
}
|
|
189
|
-
store.
|
|
189
|
+
store.saveSubagentGrant({ subagent: mate.id, tool, actions: grant.actions, rules }, by, now);
|
|
190
190
|
return { ok: true, said: "Saved. Its next turn uses these rules." };
|
|
191
191
|
}
|
|
192
192
|
const blank = (text) => redactSecretLines(text, scanForSecrets(text));
|
|
193
193
|
/**
|
|
194
194
|
* Make one call now: a free one, or one a person approved (exactly as they
|
|
195
195
|
* saw it). What the tool said is kept on the receipt, secrets scrubbed; a
|
|
196
|
-
* tool that says it failed is an answer the
|
|
196
|
+
* tool that says it failed is an answer the subagent reads, not trouble.
|
|
197
197
|
*/
|
|
198
198
|
export async function makeCall(store, call, repo, io, now, options = {}) {
|
|
199
199
|
// Its rules are read again at the moment of the call: a tool taken away, or an action set to never, since it was approved stops it.
|
|
200
|
-
// (A person's own undo (v97) needs the tool still granted, not the
|
|
201
|
-
const grant = store.
|
|
200
|
+
// (A person's own undo (v97) needs the tool still granted, not the subagent's rule for that action.)
|
|
201
|
+
const grant = store.subagentGrant(call.subagent, call.tool);
|
|
202
202
|
if (grant === null || (options.byPerson !== true && ruleFor(grant, call.action, call.input).use === "never")) {
|
|
203
|
-
store.
|
|
204
|
-
return store.
|
|
203
|
+
store.moveSubagentCall(call.id, ["approved", "running"], { state: "refused", result: "Its rules changed before the call was made." }, now);
|
|
204
|
+
return store.subagentCall(call.id);
|
|
205
205
|
}
|
|
206
206
|
// Sprint 8: nor does a call to a tool the organisation policy doesn't allow (a person's undo included).
|
|
207
207
|
const disallowed = toolRefusal(store.orgPolicy(), call.tool);
|
|
208
208
|
if (disallowed !== null) {
|
|
209
|
-
store.
|
|
210
|
-
return store.
|
|
209
|
+
store.moveSubagentCall(call.id, ["approved", "running"], { state: "refused", result: disallowed }, now);
|
|
210
|
+
return store.subagentCall(call.id);
|
|
211
211
|
}
|
|
212
|
-
if (call.state !== "running" && !store.
|
|
213
|
-
return store.
|
|
212
|
+
if (call.state !== "running" && !store.moveSubagentCall(call.id, ["approved"], { state: "running" }, now))
|
|
213
|
+
return store.subagentCall(call.id);
|
|
214
214
|
const tool = projectToolsOf(store, repo).find(one => one.name === call.tool);
|
|
215
215
|
if (tool === undefined) {
|
|
216
|
-
store.
|
|
217
|
-
return store.
|
|
216
|
+
store.moveSubagentCall(call.id, ["running"], { state: "failed", result: `There's no tool called ${call.tool} in this project any more.` }, now);
|
|
217
|
+
return store.subagentCall(call.id);
|
|
218
218
|
}
|
|
219
219
|
const values = readToolSecrets(repo, tool.name, io.toolHome);
|
|
220
220
|
let answer;
|
|
@@ -226,8 +226,8 @@ export async function makeCall(store, call, repo, io, now, options = {}) {
|
|
|
226
226
|
}
|
|
227
227
|
const text = answer.ok ? blank(scrubSecrets(answer.text, values)).trim() : answer.problem;
|
|
228
228
|
const kept = text.length <= RESULT_CHARS ? text : `${text.slice(0, RESULT_CHARS - 1)}…`;
|
|
229
|
-
store.
|
|
230
|
-
return store.
|
|
229
|
+
store.moveSubagentCall(call.id, ["running"], { state: answer.ok && !answer.isError ? "done" : "failed", result: kept || (answer.ok ? "(no answer)" : "It couldn't be reached.") }, now);
|
|
230
|
+
return store.subagentCall(call.id);
|
|
231
231
|
}
|
|
232
232
|
/** A receipt in words: what happened to the call. */
|
|
233
233
|
export function callOutcome(call) {
|
|
@@ -245,15 +245,15 @@ export function callOutcome(call) {
|
|
|
245
245
|
export function receiptWords(store, call) {
|
|
246
246
|
switch (call.state) {
|
|
247
247
|
case "asked": {
|
|
248
|
-
const question = store.
|
|
248
|
+
const question = store.subagentQuestionForCall(call.id);
|
|
249
249
|
return `Waiting for ${question?.askedOf ?? "a person"} to approve`;
|
|
250
250
|
}
|
|
251
251
|
case "approved":
|
|
252
252
|
case "running": return call.decidedBy === null ? "Making it now" : `${call.decidedBy} approved · making it now`;
|
|
253
|
-
case "done": return call.undoneBy !== null ? `Done · undone by ${call.undoneBy}` : call.undoOf !== null ? `Undid ${store.
|
|
253
|
+
case "done": return call.undoneBy !== null ? `Done · undone by ${call.undoneBy}` : call.undoOf !== null ? `Undid ${store.subagentCall(call.undoOf)?.action ?? "a call"} · done` : call.decidedBy === null ? "Done" : `${call.decidedBy} approved · done`;
|
|
254
254
|
case "failed": return call.decidedBy === null ? "Failed" : `${call.decidedBy} approved · failed`;
|
|
255
255
|
case "denied": {
|
|
256
|
-
const said = store.
|
|
256
|
+
const said = store.subagentQuestionForCall(call.id)?.answer;
|
|
257
257
|
return `${call.decidedBy ?? "A person"} said no${said ? `: ${said}` : ""}`;
|
|
258
258
|
}
|
|
259
259
|
default: return `Not made: ${call.result ?? "outside its rules"}`;
|