toolroll 0.9.55 → 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 +10 -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/browser/workspace.js +5 -5
- package/dist/browser-workspace.d.ts +12 -12
- 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-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- 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/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/{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 +35 -34
- 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/recipe-ui.js +1 -1
- 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 +97 -97
- 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-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 +6 -5
- 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 +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/{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/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 +8 -8
- 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.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.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/package.json +1 -2
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -21
package/dist/chat-ask.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { chatHash, partContent, savedChatPart } from "./chat-delivery-state.js";
|
|
2
|
-
import {
|
|
2
|
+
import { LEAD_ASK_OTHER } from "./store.js";
|
|
3
3
|
/** The question as text with its options, where no buttons are drawn. */
|
|
4
4
|
export function chatAskText(ask) {
|
|
5
|
-
return [ask.question, ...ask.options.map(one => `• ${one}`), `• ${
|
|
5
|
+
return [ask.question, ...ask.options.map(one => `• ${one}`), `• ${LEAD_ASK_OTHER}: say it in your own words`].join("\n");
|
|
6
6
|
}
|
|
7
7
|
/** The live buttons on one part, in the order they were minted: the options, then "Something else". None when the part
|
|
8
8
|
* is not in its owner's own chat with Toolroll (a room), where a tap could not be their message. */
|
|
@@ -16,7 +16,7 @@ export function chatAskButtons(state, part, now) {
|
|
|
16
16
|
const options = content.ask?.options ?? [];
|
|
17
17
|
return state.prepare("SELECT token,choice FROM chat_ask_action WHERE provider=:provider AND part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
|
|
18
18
|
.map(one => {
|
|
19
|
-
const label = one.choice === null ?
|
|
19
|
+
const label = one.choice === null ? LEAD_ASK_OTHER : options[Number(one.choice)] ?? null;
|
|
20
20
|
return label === null ? null : { token: String(one.token), label: label.slice(0, 75), words: one.choice === null };
|
|
21
21
|
})
|
|
22
22
|
.filter((one) => one !== null);
|
|
@@ -51,12 +51,12 @@ export function applyChatAskTap(options, event, binding, token, now) {
|
|
|
51
51
|
}
|
|
52
52
|
const turn = Number(action["turn"]), part = Number(action["part"]);
|
|
53
53
|
// Only the person the lead asked answers it: anyone else's tap changes nothing and says nothing.
|
|
54
|
-
const asker = store.
|
|
54
|
+
const asker = store.getLeadTurn(turn)?.approver;
|
|
55
55
|
if (asker !== undefined && asker !== binding.approver) {
|
|
56
56
|
state.finish(event.id);
|
|
57
57
|
return true;
|
|
58
58
|
}
|
|
59
|
-
const found = asker === undefined ? { state: "expired" } : store.
|
|
59
|
+
const found = asker === undefined ? { state: "expired" } : store.leadAskState(turn, now);
|
|
60
60
|
const expired = found.state === "expired" || String(action["expires"]) <= now.toISOString();
|
|
61
61
|
// A question already settled was shown so once (or is being): a later tap says nothing more.
|
|
62
62
|
if (action["consumed"] !== null) {
|
package/dist/chat-channel.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { DoorOutcome } from "./
|
|
1
|
+
import type { DoorOutcome } from "./lead-doors.js";
|
|
2
2
|
import { type VerifiedApprover } from "./principal.js";
|
|
3
|
-
import type { ChatConfig,
|
|
3
|
+
import type { ChatConfig, LeadProposal, LeadSession, LeadThread, Store, SubscriptionChatProviderId } from "./store.js";
|
|
4
4
|
export declare function channelRepos(store: Store, approver: string, registry: readonly string[]): string[];
|
|
5
|
-
export type
|
|
5
|
+
export type ResolvedLead = {
|
|
6
6
|
ok: true;
|
|
7
7
|
who: VerifiedApprover;
|
|
8
|
-
session:
|
|
9
|
-
thread:
|
|
8
|
+
session: LeadSession;
|
|
9
|
+
thread: LeadThread;
|
|
10
10
|
config: ChatConfig & {
|
|
11
11
|
provider: SubscriptionChatProviderId;
|
|
12
12
|
};
|
|
@@ -25,10 +25,10 @@ export type ResolvedMate = {
|
|
|
25
25
|
* always gets an answer — except while a turn is still running there,
|
|
26
26
|
* which is "busy": the message waits its turn instead of ending it.
|
|
27
27
|
*/
|
|
28
|
-
export declare function
|
|
28
|
+
export declare function resolveChannelLead(store: Store, binding: {
|
|
29
29
|
approver: string;
|
|
30
30
|
approverGeneration: number;
|
|
31
|
-
}, repos: readonly string[], now: Date):
|
|
31
|
+
}, repos: readonly string[], now: Date): ResolvedLead;
|
|
32
32
|
/** A phone exchange about one task (a reply to that task's or result's
|
|
33
33
|
* message) stays in the lead conversation, and is kept in the task's own
|
|
34
34
|
* chat as well, so the task shows everything asked of it wherever it was
|
|
@@ -65,10 +65,10 @@ export declare function taskInCeiling(store: Store, taskId: string, repos: reado
|
|
|
65
65
|
export type ChatChannelName = "telegram" | "slack" | "discord" | "teams";
|
|
66
66
|
/** The card text once its challenge is armed: an irreversible answer or a
|
|
67
67
|
* challenge action, in the same words on every channel. */
|
|
68
|
-
export declare function armedCardText(proposal:
|
|
68
|
+
export declare function armedCardText(proposal: LeadProposal, previewText: string): string;
|
|
69
69
|
/** The "yes" button's label for an armed card. */
|
|
70
|
-
export declare function armedYesLabel(proposal:
|
|
71
|
-
export declare function proposalLink(store: Store, proposal:
|
|
70
|
+
export declare function armedYesLabel(proposal: LeadProposal): string;
|
|
71
|
+
export declare function proposalLink(store: Store, proposal: LeadProposal, repos: readonly string[], channel?: ChatChannelName | null): PhoneLink | null;
|
|
72
72
|
/**
|
|
73
73
|
* The link a CONFIRMED card carries: the exact recorded task the outcome
|
|
74
74
|
* names, where its remaining step lives — the approval control while the
|
|
@@ -76,17 +76,17 @@ export declare function proposalLink(store: Store, proposal: MateProposal, repos
|
|
|
76
76
|
* automatic approval, a staged resume). Kinds whose outcome is complete
|
|
77
77
|
* carry none.
|
|
78
78
|
*/
|
|
79
|
-
export declare function confirmedLink(store: Store, outcome: DoorOutcome, proposal:
|
|
79
|
+
export declare function confirmedLink(store: Store, outcome: DoorOutcome, proposal: LeadProposal, repos: readonly string[]): PhoneLink | null;
|
|
80
80
|
/** The card: plain words for what confirming does, the exact terms it binds to, and whether a button belongs on it. */
|
|
81
|
-
export declare function proposalPreview(store: Store, proposal:
|
|
81
|
+
export declare function proposalPreview(store: Store, proposal: LeadProposal, repos: readonly string[], channel?: ChatChannelName | null): {
|
|
82
82
|
text: string;
|
|
83
83
|
buttons: boolean;
|
|
84
84
|
};
|
|
85
85
|
/** The card after it resolves, from the recorded outcome — the same words every surface shows. */
|
|
86
|
-
export declare function proposalOutcomeText(proposal:
|
|
86
|
+
export declare function proposalOutcomeText(proposal: LeadProposal): string;
|
|
87
87
|
export type ParitySupport = "direct" | "handoff" | "missing";
|
|
88
88
|
/**
|
|
89
|
-
* One row per
|
|
89
|
+
* One row per lead tool: how the paired phone reaches it. `direct` means
|
|
90
90
|
* the same engine tool or the same confirm door runs from Telegram;
|
|
91
91
|
* `handoff` means the phone shows where the existing authenticated
|
|
92
92
|
* control lives and does nothing itself; `missing` means no phone path
|
|
@@ -108,4 +108,4 @@ export declare function handoffCardText(text: string, note: string | null): stri
|
|
|
108
108
|
* the console on the computer when no link exists). A scope approved under
|
|
109
109
|
* a signed mode asks for nothing more. A staged resume says what remains.
|
|
110
110
|
*/
|
|
111
|
-
export declare function confirmedCardText(store: Store, outcome: DoorOutcome, proposal:
|
|
111
|
+
export declare function confirmedCardText(store: Store, outcome: DoorOutcome, proposal: LeadProposal, note: string | null): string;
|
package/dist/chat-channel.js
CHANGED
|
@@ -3,8 +3,8 @@ import { FLOW_HREF } from "./flow-engine.js";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { sharedActionPayload, sharedActionNeedsReview, sharedActionReviewPath, sharedActionAllowsChallenge, CHAT_ACTIONS, } from "./chat-actions.js";
|
|
5
5
|
import { isDirectChatProvider, subscriptionCredentialKey } from "./converse.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
7
|
+
import { LEAD_TOOL_SCHEMAS } from "./lead-tools.js";
|
|
8
8
|
import { verifyApproverStanding } from "./principal.js";
|
|
9
9
|
import { canonicalProject } from "./project.js";
|
|
10
10
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
@@ -32,7 +32,7 @@ export function channelRepos(store, approver, registry) {
|
|
|
32
32
|
* always gets an answer — except while a turn is still running there,
|
|
33
33
|
* which is "busy": the message waits its turn instead of ending it.
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
35
|
+
export function resolveChannelLead(store, binding, repos, now) {
|
|
36
36
|
const config = store.getChatConfig();
|
|
37
37
|
if (config === null) {
|
|
38
38
|
return {
|
|
@@ -60,21 +60,21 @@ export function resolveChannelMate(store, binding, repos, now) {
|
|
|
60
60
|
return { ok: false, reason: "unpaired", said: null };
|
|
61
61
|
const who = verified.who;
|
|
62
62
|
const credentialKey = subscriptionCredentialKey(config.provider);
|
|
63
|
-
let session = store.
|
|
64
|
-
const liveThread = store.
|
|
63
|
+
let session = store.activeLeadSession(who.name);
|
|
64
|
+
const liveThread = store.liveLeadThreadFor(who.name);
|
|
65
65
|
const sessionMismatch = session !== null &&
|
|
66
66
|
(session.approverGeneration !== who.generation ||
|
|
67
67
|
session.credentialKey !== credentialKey ||
|
|
68
68
|
session.ceilingDigest !== who.ceilingDigest);
|
|
69
69
|
const threadMismatch = liveThread !== null && liveThread.ceilingDigest !== who.ceilingDigest;
|
|
70
70
|
// Replacing either would end a turn still running on the computer.
|
|
71
|
-
if ((sessionMismatch || threadMismatch) && store.
|
|
71
|
+
if ((sessionMismatch || threadMismatch) && store.liveLeadTurnFor(who.name) !== null)
|
|
72
72
|
return { ok: false, reason: "busy", said: null };
|
|
73
73
|
if (session === null || sessionMismatch) {
|
|
74
74
|
const termsDigest = createHash("sha256")
|
|
75
75
|
.update(`0\n${who.ceilingDigest}`)
|
|
76
76
|
.digest("hex");
|
|
77
|
-
const id = store.
|
|
77
|
+
const id = store.mintLeadSession({
|
|
78
78
|
approver: who.name,
|
|
79
79
|
approverGeneration: who.generation,
|
|
80
80
|
credentialKey,
|
|
@@ -82,13 +82,13 @@ export function resolveChannelMate(store, binding, repos, now) {
|
|
|
82
82
|
ceilingDigest: who.ceilingDigest,
|
|
83
83
|
termsDigest,
|
|
84
84
|
}, now);
|
|
85
|
-
session = store.
|
|
85
|
+
session = store.getLeadSession(id);
|
|
86
86
|
if (session === null)
|
|
87
87
|
return { ok: false, reason: "no-session", said: couldNotAnswerText("a new chat session couldn't be started") };
|
|
88
88
|
}
|
|
89
|
-
//
|
|
89
|
+
// openLeadThread closes a thread kept under another ceiling.
|
|
90
90
|
const thread = threadMismatch || liveThread === null
|
|
91
|
-
? store.
|
|
91
|
+
? store.openLeadThread(who.name, who.ceilingDigest, now).thread
|
|
92
92
|
: liveThread;
|
|
93
93
|
return {
|
|
94
94
|
ok: true,
|
|
@@ -107,9 +107,9 @@ export function mirrorToTaskChat(store, who, taskId, surface, message, reply, no
|
|
|
107
107
|
const root = store.taskFamilyOf(taskId, who.repos, false)?.root.id ?? taskId;
|
|
108
108
|
if (!taskInCeiling(store, root, who.repos))
|
|
109
109
|
return;
|
|
110
|
-
const thread = store.
|
|
111
|
-
store.
|
|
112
|
-
store.
|
|
110
|
+
const thread = store.openLeadThread(who.name, who.ceilingDigest, now, { kind: "task", key: root }).thread;
|
|
111
|
+
store.appendLeadMessage({ thread: thread.id, turn: null, role: "operator", text: `From ${surface}: ${message}` }, now);
|
|
112
|
+
store.appendLeadMessage({ thread: thread.id, turn: null, role: "assistant", text: reply }, now);
|
|
113
113
|
}
|
|
114
114
|
catch {
|
|
115
115
|
// The task chat is a copy; the phone's own conversation already has it.
|
|
@@ -120,7 +120,7 @@ export function couldNotAnswerText(reason) {
|
|
|
120
120
|
return `I couldn't answer that just now: ${reason.replace(/\.$/, "")}. Ask again, or open the console.`;
|
|
121
121
|
}
|
|
122
122
|
export function tooLongText(length) {
|
|
123
|
-
return `That message is ${length.toLocaleString("en-US")} characters; chat takes up to ${
|
|
123
|
+
return `That message is ${length.toLocaleString("en-US")} characters; chat takes up to ${LEAD_MESSAGE_MAX_CHARS.toLocaleString("en-US")}. Nothing was sent to the assistant. Send it in shorter parts, or say which part matters most.`;
|
|
124
124
|
}
|
|
125
125
|
/** A reply to a message about several tasks is never guessed: it names them and says how to choose. */
|
|
126
126
|
export function whichTaskText(titles) {
|
|
@@ -459,7 +459,7 @@ export function proposalOutcomeText(proposal) {
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
462
|
-
* One row per
|
|
462
|
+
* One row per lead tool: how the paired phone reaches it. `direct` means
|
|
463
463
|
* the same engine tool or the same confirm door runs from Telegram;
|
|
464
464
|
* `handoff` means the phone shows where the existing authenticated
|
|
465
465
|
* control lives and does nothing itself; `missing` means no phone path
|
|
@@ -518,14 +518,14 @@ export const CHAT_ACTION_PARITY = {
|
|
|
518
518
|
get_project_tools: { support: "direct", how: "Read during a turn: a project's tools, the common tools list and servers found on the computer. Adding one is a secure-review card; secrets are set only on the console's Tools page.", gap: null },
|
|
519
519
|
get_task_conversation: { support: "direct", how: "Read during a turn: what the person and the lead said in one task's own chat, including what was confirmed there.", gap: null },
|
|
520
520
|
get_flows: { support: "direct", how: "Read during a turn: a person's flows, each one's steps in order and its cards — where each card is, what it waits on, whether it needs them, its owner and its discussion.", gap: null },
|
|
521
|
-
|
|
522
|
-
|
|
521
|
+
get_subagents: { support: "direct", how: "Read during a turn: the project's subagents, their soul files, the zones they work, what they did today and the questions they're waiting on.", gap: null },
|
|
522
|
+
propose_subagent: { support: "direct", how: "Asks a subagent to do something for you (the lead delegates; its answer comes back here), adds one from a template or a soul file, changes one section of its soul file (or the whole short file), pauses, resumes or removes it, passes it a note, or answers its question for the person asked, through the shared confirm door.", gap: "Soul files longer than one message are edited on the console's subagent page; the phone changes one section at a time." },
|
|
523
523
|
get_flow_insights: { support: "direct", how: "Read during a turn: where each flow's cards pass, fail or are sent back, how long they wait, how its scripts did, and a run's log.", gap: null },
|
|
524
524
|
propose_flow: { support: "direct", how: "Creates or changes a flow, adds, moves, approves, sends back or cancels its cards, comments on them (@name pings that person), sets their owner, follows them, saves the project's scripts, and adds, pauses or removes its triggers, through the shared confirm door; a long drawing opens the secure review. Work a card files is an ordinary task under the usual approvals.", gap: "The flow canvas itself is on the console; the phone confirms cards but draws nothing. Webhook addresses and the Linear key are set on the console's Triggers panel." },
|
|
525
525
|
commit_to: { support: "direct", how: "Records what the lead promised to follow up on (a task, attempt, check or time; 7 days at most). The follow pass says one line when it is met, in the shared conversation and on the chat the promise was made on.", gap: null },
|
|
526
526
|
release_commitment: { support: "direct", how: "Stops following up on one of the person's open promises, with the reason.", gap: null },
|
|
527
527
|
remember: { support: "direct", how: "Proposes a correction or lasting preference as a decision, project instruction or about-you card, through the shared confirm door; an about-you card shows the line it replaces.", gap: null },
|
|
528
|
-
get_person: { support: "direct", how: "Read during a turn: one person,
|
|
528
|
+
get_person: { support: "direct", how: "Read during a turn: one person, subagent or team chat in full, with their open tasks.", gap: null },
|
|
529
529
|
get_integrations: { support: "direct", how: "Read during a turn: which chat apps, email, GitHub, project tools and monitoring are Connected, Not set up or Broken, as Settings → Integrations shows them.", gap: null },
|
|
530
530
|
get_capabilities: { support: "direct", how: "Read during a turn: which agents are signed in, which workers are online, which tools and skills work, which integrations are connected and what checks the project runs, each with its next step and settings link.", gap: null },
|
|
531
531
|
ask_owner: { support: "direct", how: "One question with 2-4 options and Something else, drawn as buttons under the reply; the tapped option is sent as the person's next message.", gap: null },
|
|
@@ -568,7 +568,7 @@ export const CHAT_ACTION_PARITY = {
|
|
|
568
568
|
},
|
|
569
569
|
propose_task: {
|
|
570
570
|
support: "direct",
|
|
571
|
-
how: "Card with Confirm/Dismiss through
|
|
571
|
+
how: "Card with Confirm/Dismiss through confirmLeadProposal (filed as a lead proposal, via telegram). The confirmed card links Review & start for the filed task while its scope waits; under a signed automatic mode it says the scope is approved and links the task.",
|
|
572
572
|
gap: "Under manual approval the password step happens in the console, reached from the card's button.",
|
|
573
573
|
},
|
|
574
574
|
propose_scope: {
|
|
@@ -634,7 +634,7 @@ export const CHAT_ACTION_PARITY = {
|
|
|
634
634
|
};
|
|
635
635
|
/** Every tool the model can call, mapped; a new tool must name its phone road. */
|
|
636
636
|
export function parityGaps() {
|
|
637
|
-
return
|
|
637
|
+
return LEAD_TOOL_SCHEMAS.map((tool) => tool.name).filter((name) => CHAT_ACTION_PARITY[name] === undefined);
|
|
638
638
|
}
|
|
639
639
|
export function handoffCardText(text, note) {
|
|
640
640
|
return note === null ? text : `${text}\n\n${note}`;
|
package/dist/chat-controls.d.ts
CHANGED
|
@@ -38,10 +38,6 @@ export declare const CHAT_CONTROLS: {
|
|
|
38
38
|
readonly label: "Manage projects";
|
|
39
39
|
readonly href: "/projects";
|
|
40
40
|
};
|
|
41
|
-
readonly code: {
|
|
42
|
-
readonly label: "Open coding workspace";
|
|
43
|
-
readonly href: "/code";
|
|
44
|
-
};
|
|
45
41
|
readonly flows: {
|
|
46
42
|
readonly label: "Open flows";
|
|
47
43
|
readonly href: "/flows";
|
package/dist/chat-controls.js
CHANGED
|
@@ -11,7 +11,6 @@ export const CHAT_CONTROLS = {
|
|
|
11
11
|
acceptance: { label: "Inspect result", target: "task" },
|
|
12
12
|
publish: { label: "Review publication", target: "task" },
|
|
13
13
|
projects: { label: "Manage projects", href: "/projects" },
|
|
14
|
-
code: { label: "Open coding workspace", href: "/code" },
|
|
15
14
|
flows: { label: "Open flows", href: "/flows" },
|
|
16
15
|
recipes: { label: "Browse workflows", href: "/recipes" },
|
|
17
16
|
workers: { label: "Manage workers", href: "/fleet" },
|
package/dist/chat-core.js
CHANGED
|
@@ -183,7 +183,7 @@ export class ChatCore {
|
|
|
183
183
|
const thread = this.db.prepare("SELECT thread FROM team_conversation WHERE id = ?").get(args.conversation);
|
|
184
184
|
if (thread === undefined)
|
|
185
185
|
throw new Error("no such team conversation");
|
|
186
|
-
const cursor = Number(this.db.prepare("SELECT COALESCE(MAX(id), 0) AS n FROM
|
|
186
|
+
const cursor = Number(this.db.prepare("SELECT COALESCE(MAX(id), 0) AS n FROM lead_message WHERE thread = ?").get(Number(thread["thread"]))?.["n"] ?? 0);
|
|
187
187
|
this.prepare("INSERT INTO chat_room (provider, id, installation, chat, kind, conversation, binding, bound_by, bound, cursor) VALUES (:provider, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
188
188
|
.run(this.nextId("chat_room"), args.installation, args.chat, args.kind, args.conversation, args.binding, args.by, stamp, cursor);
|
|
189
189
|
return { ok: true, room: this.room(args.installation, args.chat) };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Durable chat receipts on the shared chat core: what each app's chats sent us and what we plan to send them. */
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { LEAD_ASK_TTL_MS } from "./store.js";
|
|
4
4
|
import { ChatCore, chatHash } from "./chat-core.js";
|
|
5
5
|
import { ChatMessages } from "./chat-messages.js";
|
|
6
6
|
import { readChatPart, savedChatEventBody, savedChatPart } from "./contracts/chat-content.js";
|
|
@@ -80,7 +80,7 @@ export class ChatState extends ChatMessages {
|
|
|
80
80
|
if (part.ask)
|
|
81
81
|
for (const choice of [...part.ask.options.map((_, index) => index), null])
|
|
82
82
|
this.prepare("INSERT INTO chat_ask_action (provider, token, part, turn, choice, expires) VALUES (:provider, ?, ?, ?, ?, ?)")
|
|
83
|
-
.run(token(), id, part.ask.turn, choice, new Date(now.getTime() +
|
|
83
|
+
.run(token(), id, part.ask.turn, choice, new Date(now.getTime() + LEAD_ASK_TTL_MS).toISOString());
|
|
84
84
|
if (part.choose)
|
|
85
85
|
for (const one of part.choose.options)
|
|
86
86
|
this.prepare("INSERT INTO chat_flow_choice (provider, token, part, card, entry, choice, label, created, expires) VALUES (:provider, ?, ?, ?, ?, ?, ?, ?, ?)")
|
package/dist/chat-delivery.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type WarmHooks } from "./chat-warmth.js";
|
|
2
2
|
import { ChatState, type ChatBinding, type ChatContent, type ChatEvent, type ChatIdentity } from "./chat-delivery-state.js";
|
|
3
3
|
import { type Store } from "./store.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { SubscriptionLeadRunner } from "./subscription-chat.js";
|
|
5
5
|
import { type ChatMerge } from "./chat-decide.js";
|
|
6
6
|
export declare const chatObject: (v: unknown) => Record<string, unknown>;
|
|
7
7
|
export type ChatDeliveryOptions = {
|
|
@@ -15,7 +15,7 @@ export type ChatDeliveryOptions = {
|
|
|
15
15
|
evidenceRoot: string;
|
|
16
16
|
current: () => boolean;
|
|
17
17
|
origin: () => string | null;
|
|
18
|
-
subscriptionRunner?:
|
|
18
|
+
subscriptionRunner?: SubscriptionLeadRunner;
|
|
19
19
|
canNotify?: () => boolean;
|
|
20
20
|
clock?: () => Date;
|
|
21
21
|
partSize?: number;
|
package/dist/chat-delivery.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Shared saved replies, explicit confirmations and progress for private chat transports. */
|
|
2
|
-
import { answerChatQuestionPrompt, applyChatQuestionTap, questionParts } from "./
|
|
2
|
+
import { answerChatQuestionPrompt, applyChatQuestionTap, questionParts } from "./subagent-question.js";
|
|
3
3
|
import { applyChatAskTap, chatAskText } from "./chat-ask.js";
|
|
4
|
-
import { channelRepos,
|
|
5
|
-
import {
|
|
4
|
+
import { channelRepos, resolveChannelLead, proposalPreview, proposalOutcomeText, confirmedLink, confirmedCardText, replyContextFor, mirrorToTaskChat, focusContextFor, taskInCeiling, tooLongText, } from "./chat-channel.js";
|
|
5
|
+
import { LEAD_MESSAGE_MAX_CHARS, leadFailureText, runLeadTurn } from "./lead.js";
|
|
6
6
|
import { shapeReplyParts } from "./reply-shape.js";
|
|
7
7
|
import { warmTurn } from "./chat-warmth.js";
|
|
8
|
-
import {
|
|
8
|
+
import { confirmLeadProposal, dismissLeadProposal, } from "./lead-doors.js";
|
|
9
9
|
import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
|
|
10
10
|
import { ChatState, ChatDeliveryError, chatHash, savedChatPart, } from "./chat-delivery-state.js";
|
|
11
11
|
import { readChatActionBody, readChatMessageBody, readChatPairBody, readChatPart } from "./contracts/chat-content.js";
|
|
@@ -23,7 +23,6 @@ import { phoneText, PHONE_HELP, phoneCommand, phoneStatus, phoneTaskView, phoneT
|
|
|
23
23
|
import { applyRoomInbound, conversationRow, roomCardApprover, roomCommand, roomGrantAllowed, roomMessagesAfter, roomMessageText, teamDomain } from "./chat-rooms.js";
|
|
24
24
|
import { isTelegramProgressNotification, proposalTaskOf } from "./store.js";
|
|
25
25
|
import { isShotsKind, resultShotsFor } from "./result-shots.js";
|
|
26
|
-
import { messageTeammate } from "./teammate-desk.js";
|
|
27
26
|
import { CHAT_APP_NAMES } from "./flow-triggers.js";
|
|
28
27
|
import { answersPrompt, applyDecideFeedback, applyDecideTap, decideFallbackLink, decideOffer, decideTargetOf, hasLiveDecideTokens, linksFor, mergedText, mintDecideButtons, offerFingerprint, openPromptFor, recordChatMerge } from "./chat-decide.js";
|
|
29
28
|
import { mergePullRequest } from "./pull-request-flow.js";
|
|
@@ -198,7 +197,7 @@ export async function processChatEvent(options) {
|
|
|
198
197
|
return true;
|
|
199
198
|
}
|
|
200
199
|
const text = String(input.text ?? "");
|
|
201
|
-
// A
|
|
200
|
+
// A subagent's question asked for this person's next message (v93): it is the answer.
|
|
202
201
|
if (answerChatQuestionPrompt({ store, state, label: options.label }, event, binding, { text, ...(typeof input.originalLength === "number" ? { originalLength: input.originalLength } : {}) }, nowOf(options)))
|
|
203
202
|
return true;
|
|
204
203
|
// A flow decision asked for this person's next message (Edit, Send back): it is the draft or the note.
|
|
@@ -217,14 +216,8 @@ export async function processChatEvent(options) {
|
|
|
217
216
|
// A "Person chooses" notice's thread reply is the note; the first other message after it is asked about (chat-flow.ts).
|
|
218
217
|
if (answerChoiceMessage({ store, state, label: options.label }, event, binding, { text, ...(typeof input.originalLength === "number" ? { originalLength: input.originalLength } : {}), lead: input.lead === true }, nowOf(options), repos))
|
|
219
218
|
return true;
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
const handed = messageTeammate(store, { who: binding.approver, repos, via: CHAT_APP_NAMES[state.provider] ?? "Chat" }, text, nowOf(options));
|
|
223
|
-
if (handed !== null) {
|
|
224
|
-
state.plan(event.id, [{ text: handed.said, ...(handed.link === undefined ? {} : { link: handed.link }) }], nowOf(options));
|
|
225
|
-
return true;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
219
|
+
// D5: a message naming a subagent ("@rosa …") is the lead's to read like any other: it asks the subagent for its
|
|
220
|
+
// person with a card they confirm (propose_subagent ask), never by the transport passing it on unseen.
|
|
228
221
|
// The task this message's turn is about, once chosen (kept on the event,
|
|
229
222
|
// so a reply planned after a restart names the same task).
|
|
230
223
|
let about = typeof object(input.about).task === "string" ? { task: String(object(input.about).task), run: typeof object(input.about).run === "number" ? Number(object(input.about).run) : null } : null;
|
|
@@ -280,7 +273,7 @@ export async function processChatEvent(options) {
|
|
|
280
273
|
return true;
|
|
281
274
|
}
|
|
282
275
|
}
|
|
283
|
-
if (text.length >
|
|
276
|
+
if (text.length > LEAD_MESSAGE_MAX_CHARS) {
|
|
284
277
|
state.plan(event.id, [
|
|
285
278
|
{
|
|
286
279
|
text: tooLongText(input.originalLength || text.length),
|
|
@@ -291,7 +284,7 @@ export async function processChatEvent(options) {
|
|
|
291
284
|
const request = chatHash(`${options.state.provider}:${binding.id}:${event.id}`).slice(0, 32);
|
|
292
285
|
let receipt = event.session === null
|
|
293
286
|
? null
|
|
294
|
-
: store.
|
|
287
|
+
: store.leadRequestReceipt(event.session, request);
|
|
295
288
|
if (!receipt) {
|
|
296
289
|
if (new Date(event.created).getTime() + 600_000 < now.getTime()) {
|
|
297
290
|
state.plan(event.id, [
|
|
@@ -301,7 +294,7 @@ export async function processChatEvent(options) {
|
|
|
301
294
|
], now);
|
|
302
295
|
return true;
|
|
303
296
|
}
|
|
304
|
-
const resolved =
|
|
297
|
+
const resolved = resolveChannelLead(store, { approver: binding.approver, approverGeneration: binding.generation }, repos, now);
|
|
305
298
|
if (!resolved.ok && resolved.reason === "busy") {
|
|
306
299
|
state.defer(event.id, "Assistant is answering another message", new Date(now.getTime() + 5000));
|
|
307
300
|
return true;
|
|
@@ -332,7 +325,7 @@ export async function processChatEvent(options) {
|
|
|
332
325
|
.map((c) => {
|
|
333
326
|
if (c.task || c.proposal === undefined)
|
|
334
327
|
return c;
|
|
335
|
-
const about = proposalTaskOf(store.
|
|
328
|
+
const about = proposalTaskOf(store.getLeadProposal(c.proposal));
|
|
336
329
|
return about === null ? c : { ...c, task: about.task, ...(about.run === null ? {} : { run: about.run }) };
|
|
337
330
|
});
|
|
338
331
|
const targets = [
|
|
@@ -363,7 +356,7 @@ export async function processChatEvent(options) {
|
|
|
363
356
|
// Only ever the owner's own message in their own chat: never the bot's, never a room.
|
|
364
357
|
const warm = warmTurn(options.warm !== undefined && event.kind === "message" && event.member !== identity.bot && event.member === binding.member && event.channel === binding.channel
|
|
365
358
|
? options.warm(event, binding) : {});
|
|
366
|
-
const outcome = await
|
|
359
|
+
const outcome = await runLeadTurn({
|
|
367
360
|
store,
|
|
368
361
|
who: resolved.who,
|
|
369
362
|
session: resolved.session,
|
|
@@ -407,7 +400,7 @@ export async function processChatEvent(options) {
|
|
|
407
400
|
state.defer(event.id, "Assistant is answering another message", new Date(nowOf(options).getTime() + 5000));
|
|
408
401
|
return true;
|
|
409
402
|
}
|
|
410
|
-
receipt = store.
|
|
403
|
+
receipt = store.leadRequestReceipt(resolved.session.id, request);
|
|
411
404
|
if (!receipt) {
|
|
412
405
|
state.plan(event.id, [
|
|
413
406
|
{
|
|
@@ -419,8 +412,8 @@ export async function processChatEvent(options) {
|
|
|
419
412
|
return true;
|
|
420
413
|
}
|
|
421
414
|
}
|
|
422
|
-
store.
|
|
423
|
-
const turn = store.
|
|
415
|
+
store.sweepStaleLeadTurns(nowOf(options));
|
|
416
|
+
const turn = store.getLeadTurn(receipt.turn);
|
|
424
417
|
if (turn?.state === "running" || turn?.state === "queued") {
|
|
425
418
|
state.defer(event.id, "Reply is still running", new Date(nowOf(options).getTime() + 5000));
|
|
426
419
|
return true;
|
|
@@ -428,19 +421,19 @@ export async function processChatEvent(options) {
|
|
|
428
421
|
if (turn?.state !== "answered") {
|
|
429
422
|
state.plan(event.id, [
|
|
430
423
|
{
|
|
431
|
-
text:
|
|
424
|
+
text: leadFailureText(turn?.failureReason ?? null),
|
|
432
425
|
},
|
|
433
426
|
], nowOf(options));
|
|
434
427
|
return true;
|
|
435
428
|
}
|
|
436
|
-
const session = store.
|
|
429
|
+
const session = store.getLeadSession(turn.session);
|
|
437
430
|
if (!session) {
|
|
438
431
|
state.finish(event.id, true);
|
|
439
432
|
return true;
|
|
440
433
|
}
|
|
441
434
|
await channelAccess(options, binding, session.ceilingDigest);
|
|
442
435
|
const reply = store
|
|
443
|
-
.
|
|
436
|
+
.listLeadMessages(turn.thread, 200)
|
|
444
437
|
.find((message) => message.turn === turn.id && message.role === "assistant")?.text ?? "The reply is no longer in the saved thread.";
|
|
445
438
|
// The lead's answer about one task names it: a reply to it stays on that task.
|
|
446
439
|
// Split as written, then shape each part: no cut ever lands inside a link, code or a bold anchor.
|
|
@@ -448,11 +441,11 @@ export async function processChatEvent(options) {
|
|
|
448
441
|
const parts = (shapedParts.length > 0 ? shapedParts : splitChatText("")).map((text) => (about === null ? { text, voice: true } : { text, voice: true, task: about.task, ...(about.run === null ? {} : { run: about.run }) }));
|
|
449
442
|
// The lead's question to its owner: its options as buttons, then "Something else", in their own chat (any thread);
|
|
450
443
|
// in a room a tap is not their message, so the question and its options go out as text.
|
|
451
|
-
const ask = store.
|
|
444
|
+
const ask = store.leadAsk(turn.id);
|
|
452
445
|
if (ask !== null)
|
|
453
446
|
parts.push({ ...(event.channel === binding.channel ? { text: phoneText(ask.question, 1000), ask: { turn: ask.turn, options: ask.options } } : { text: phoneText(chatAskText(ask), 1000) }),
|
|
454
447
|
...(about === null ? {} : { task: about.task, ...(about.run === null ? {} : { run: about.run }) }) });
|
|
455
|
-
for (const image of store.
|
|
448
|
+
for (const image of store.listLeadTurnEvidence(turn.id))
|
|
456
449
|
parts.push({
|
|
457
450
|
text: image.caption,
|
|
458
451
|
image: {
|
|
@@ -465,7 +458,7 @@ export async function processChatEvent(options) {
|
|
|
465
458
|
run: image.run,
|
|
466
459
|
});
|
|
467
460
|
for (const proposal of store
|
|
468
|
-
.
|
|
461
|
+
.listLeadProposals(turn.thread, ["pending"])
|
|
469
462
|
.filter((p) => p.turn === turn.id))
|
|
470
463
|
parts.push({ text: "", proposal: proposal.id });
|
|
471
464
|
state.plan(event.id, parts, nowOf(options));
|
|
@@ -565,7 +558,7 @@ export function applyChatAction(options, event, binding, repos) {
|
|
|
565
558
|
// A flow decision's button (v88) is answered by the flow's own door.
|
|
566
559
|
if (applyChatFlowTap({ store, state, label: options.label }, event, binding, token, repos, now))
|
|
567
560
|
return;
|
|
568
|
-
// A
|
|
561
|
+
// A subagent's question's button (v93) is answered by the question's own door.
|
|
569
562
|
if (applyChatQuestionTap({ store, state, label: options.label }, event, binding, token, now))
|
|
570
563
|
return;
|
|
571
564
|
// The lead's question to its owner: the tapped option becomes their next message.
|
|
@@ -592,7 +585,7 @@ export function applyChatAction(options, event, binding, repos) {
|
|
|
592
585
|
], now);
|
|
593
586
|
return;
|
|
594
587
|
}
|
|
595
|
-
const proposal = store.
|
|
588
|
+
const proposal = store.getLeadProposal(action.proposal);
|
|
596
589
|
const verified = verifyApproverStanding(store, binding.approver, binding.generation, repos);
|
|
597
590
|
if (!proposal || !verified.ok) {
|
|
598
591
|
state.finish(event.id, true);
|
|
@@ -614,7 +607,7 @@ export function applyChatAction(options, event, binding, repos) {
|
|
|
614
607
|
state.tokens(action.part, proposal.id, ["confirm", "dismiss"], now);
|
|
615
608
|
}
|
|
616
609
|
else if (phase === "dismiss") {
|
|
617
|
-
const dismissed =
|
|
610
|
+
const dismissed = dismissLeadProposal(store, verified.who, proposal.id, now);
|
|
618
611
|
content = {
|
|
619
612
|
text: dismissed
|
|
620
613
|
? "Dismissed."
|
|
@@ -629,7 +622,7 @@ export function applyChatAction(options, event, binding, repos) {
|
|
|
629
622
|
state.tokens(action.part, proposal.id, ["yes", "cancel"], now);
|
|
630
623
|
}
|
|
631
624
|
else {
|
|
632
|
-
const outcome =
|
|
625
|
+
const outcome = confirmLeadProposal(store, verified.who, proposal.id, now, {
|
|
633
626
|
via: options.state.provider,
|
|
634
627
|
evidenceRoot: options.evidenceRoot,
|
|
635
628
|
confirm: phase === "yes",
|
|
@@ -716,7 +709,7 @@ export async function planRoomMessages(options) {
|
|
|
716
709
|
parts.push({ text, channel: room.chat });
|
|
717
710
|
}
|
|
718
711
|
if (message.role === "assistant" && message.turn !== null)
|
|
719
|
-
for (const proposal of store.
|
|
712
|
+
for (const proposal of store.listLeadProposals(row.thread, ["pending"]).filter(one => one.turn === message.turn))
|
|
720
713
|
parts.push({ text: "", proposal: proposal.id, channel: room.chat });
|
|
721
714
|
const id = chatHash(`${state.provider}:room:${room.id}:${message.id}`);
|
|
722
715
|
const target = carrier;
|
|
@@ -998,7 +991,7 @@ function offerNotice(options, notification, binding, registry) {
|
|
|
998
991
|
state.plan(id, flowSendParts(store, notification, state.provider), now);
|
|
999
992
|
}
|
|
1000
993
|
else if (notification.kind === "flow-card" && questionParts(store, notification, binding) !== null) {
|
|
1001
|
-
// A
|
|
994
|
+
// A subagent's question (v93): its options and "Answer in words" on the notice, for the person it asks.
|
|
1002
995
|
state.enqueue({ id, installation: identity.installation, binding: binding.id, kind: "notice", channel: binding.channel, member: binding.member,
|
|
1003
996
|
ts: "", thread: "", payload: {}, created: now.toISOString() });
|
|
1004
997
|
state.plan(id, questionParts(store, notification, binding), now);
|
package/dist/chat-flow.js
CHANGED
|
@@ -5,7 +5,7 @@ import { chooseFlowCard, flowChoiceAt, flowSendPaths, flowSendTail, FLOW_CHOOSE_
|
|
|
5
5
|
import { cleanFlowMessage, fitFlowMessage } from "./flow-items.js";
|
|
6
6
|
import { renderReply } from "./reply-shape.js";
|
|
7
7
|
import { deciderOf, replyTarget } from "./flows.js";
|
|
8
|
-
import {
|
|
8
|
+
import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
9
9
|
import { verifyApproverStanding } from "./principal.js";
|
|
10
10
|
import { chatHash, partContent, savedChatPart } from "./chat-delivery-state.js";
|
|
11
11
|
import { readHeldWords } from "./contracts/chat-content.js";
|
|
@@ -235,7 +235,7 @@ function replyAsNote(options, binding, said, cutShort, visit, repos, now) {
|
|
|
235
235
|
if (said === "")
|
|
236
236
|
return { ok: false, said: "Say what you'd change." };
|
|
237
237
|
if (cutShort || said.length > LIMITS.note)
|
|
238
|
-
return { ok: false, said: `That's too long to take from here. Keep it to ${Math.min(LIMITS.note,
|
|
238
|
+
return { ok: false, said: `That's too long to take from here. Keep it to ${Math.min(LIMITS.note, LEAD_MESSAGE_MAX_CHARS).toLocaleString("en-US")} characters, or reply in Toolroll.` };
|
|
239
239
|
if (!verifyApproverStanding(store, binding.approver, binding.generation, repos).ok)
|
|
240
240
|
return null;
|
|
241
241
|
const chosen = chooseFlowCard(store, { card: visit.card, entry: visit.entry, choice: null, note: said, actor: binding.approver, where: options.label, repos }, now);
|
|
@@ -390,7 +390,7 @@ export function answerChatFlowPrompt(options, event, binding, input, repos, now)
|
|
|
390
390
|
// An edit is a draft (DRAFT_LIMIT); a send-back is a note (LIMITS.note). Over it is refused with the limit, never cut.
|
|
391
391
|
const cutShort = (input.originalLength ?? 0) > input.text.length;
|
|
392
392
|
const most = mode === "edit" ? DRAFT_LIMIT : LIMITS.note;
|
|
393
|
-
const limit = cutShort ? Math.min(
|
|
393
|
+
const limit = cutShort ? Math.min(LEAD_MESSAGE_MAX_CHARS, most) : most;
|
|
394
394
|
if (cutShort || said.length > most) {
|
|
395
395
|
say(`That's too long to take from here. Keep it to ${limit.toLocaleString("en-US")} characters, or ${mode === "edit" ? "change it" : "send it back"} in Toolroll.`);
|
|
396
396
|
return true;
|
package/dist/chat-messages.d.ts
CHANGED
|
@@ -181,7 +181,7 @@ export declare class ChatMessages extends ChatCore {
|
|
|
181
181
|
/**
|
|
182
182
|
* Claim the oldest message that still needs a turn: queued, or running
|
|
183
183
|
* under a claim that lapsed (a crash mid-turn), and not deferred past now.
|
|
184
|
-
* One at a time per bot — the
|
|
184
|
+
* One at a time per bot — the lead runs one turn per approver anyway.
|
|
185
185
|
*/
|
|
186
186
|
claimMessage(botId: string, owner: string, ttlMs: number, now: Date, only?: "turns" | "replies"): TelegramConversation | null;
|
|
187
187
|
/** Extend a held claim; false means it lapsed and somebody else may hold the row. */
|
package/dist/chat-messages.js
CHANGED
|
@@ -394,7 +394,7 @@ export class ChatMessages extends ChatCore {
|
|
|
394
394
|
/**
|
|
395
395
|
* Claim the oldest message that still needs a turn: queued, or running
|
|
396
396
|
* under a claim that lapsed (a crash mid-turn), and not deferred past now.
|
|
397
|
-
* One at a time per bot — the
|
|
397
|
+
* One at a time per bot — the lead runs one turn per approver anyway.
|
|
398
398
|
*/
|
|
399
399
|
claimMessage(botId, owner, ttlMs, now, only) {
|
|
400
400
|
return this.store.transact(() => {
|
|
@@ -525,7 +525,7 @@ export class ChatMessages extends ChatCore {
|
|
|
525
525
|
}
|
|
526
526
|
replyParts(conversation) {
|
|
527
527
|
return this
|
|
528
|
-
.prepare(`SELECT p.*, EXISTS (SELECT 1 FROM
|
|
528
|
+
.prepare(`SELECT p.*, EXISTS (SELECT 1 FROM lead_proposal mp WHERE mp.id = json_extract(p.payload, '$.proposal')) AS proposal_live
|
|
529
529
|
FROM chat_part p WHERE p.provider = :provider AND p.event = ? ORDER BY p.ordinal`)
|
|
530
530
|
.all(`m${conversation}`).map(row => readTelegramConversationPart(row, conversation));
|
|
531
531
|
}
|
|
@@ -596,7 +596,7 @@ export class ChatMessages extends ChatCore {
|
|
|
596
596
|
}));
|
|
597
597
|
// Every sent part of this bot's replies on that message, oldest first: images, the lead's replies and its cards.
|
|
598
598
|
const parts = chat
|
|
599
|
-
.prepare(`SELECT p.*, c.payload AS asked, EXISTS (SELECT 1 FROM
|
|
599
|
+
.prepare(`SELECT p.*, c.payload AS asked, EXISTS (SELECT 1 FROM lead_proposal mp WHERE mp.id = json_extract(p.payload, '$.proposal')) AS proposal_live
|
|
600
600
|
FROM chat_part p JOIN chat_event c ON c.provider = p.provider AND c.id = p.event
|
|
601
601
|
WHERE p.provider = :provider AND c.kind = 'message' AND c.binding = ? AND c.channel = ? AND p.message = ? AND p.state = 'sent'
|
|
602
602
|
ORDER BY CAST(substr(p.event, 2) AS INTEGER), p.ordinal`)
|
|
@@ -610,7 +610,7 @@ export class ChatMessages extends ChatCore {
|
|
|
610
610
|
// The lead's own messages: a reply from a turn that was about one task,
|
|
611
611
|
// and a card, which names its task (or, once it filed one, the new task).
|
|
612
612
|
const lead = parts.filter(one => one.part.kind !== "image").map(({ part, asked }) => part.kind === "card"
|
|
613
|
-
? proposalTaskOf(part.proposal === null ? null : this.store.
|
|
613
|
+
? proposalTaskOf(part.proposal === null ? null : this.store.getLeadProposal(part.proposal))
|
|
614
614
|
: asked.ok && asked.value.about !== undefined ? { task: asked.value.about.task, run: asked.value.about.run } : null);
|
|
615
615
|
const shown = chat
|
|
616
616
|
.prepare("SELECT task_id, run FROM chat_message_ref WHERE provider = :provider AND kind = 'task' AND binding = ? AND chat = ? AND message = ?")
|