toolroll 0.9.84 → 0.9.86
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 +66 -0
- package/README.md +94 -1193
- package/dist/agent-fence.d.ts +9 -5
- package/dist/agent-fence.js +6 -5
- package/dist/agent-onboard.d.ts +2 -0
- package/dist/agent-onboard.js +3 -0
- package/dist/assignment.js +4 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +33 -31
- package/dist/browser-workspace.d.ts +45 -0
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.d.ts +10 -1
- package/dist/chat-channel.js +36 -5
- package/dist/chat-decide.d.ts +16 -4
- package/dist/chat-decide.js +239 -31
- package/dist/chat-messages.d.ts +5 -0
- package/dist/chat-messages.js +1 -1
- package/dist/chat-quiet.js +6 -4
- package/dist/chat-review.d.ts +4 -0
- package/dist/chat-review.js +13 -4
- package/dist/chat-terms.d.ts +9 -0
- package/dist/chat-terms.js +46 -0
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/console-api.d.ts +6 -2
- package/dist/contracts/console-api.js +6 -2
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +51 -0
- package/dist/contracts/lead-tools.js +5 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/contracts/youiyoux-request.d.ts +248 -0
- package/dist/contracts/youiyoux-request.js +152 -0
- package/dist/coordinator.js +1 -0
- package/dist/deploy-gate.js +4 -0
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-cadence.d.ts +8 -2
- package/dist/flow-cadence.js +16 -3
- package/dist/flow-card-task.d.ts +9 -0
- package/dist/flow-card-task.js +15 -6
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +47 -2
- package/dist/flows-ui.js +11 -2
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.js +9 -4
- package/dist/integrations.d.ts +9 -1
- package/dist/integrations.js +43 -2
- package/dist/landed-cleanup.d.ts +4 -0
- package/dist/landed-cleanup.js +3 -2
- package/dist/lead-capabilities.js +2 -1
- package/dist/lead-cli.js +3 -1
- package/dist/lead-tools.d.ts +2 -0
- package/dist/lead-tools.js +65 -10
- package/dist/lead-voice.js +16 -5
- package/dist/lead.d.ts +2 -0
- package/dist/lead.js +3 -3
- package/dist/operate.d.ts +2 -3
- package/dist/operate.js +226 -21
- package/dist/project-activity.d.ts +20 -0
- package/dist/project-activity.js +23 -0
- package/dist/project-cli.d.ts +1 -0
- package/dist/project-cli.js +36 -19
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/project-outcomes.d.ts +119 -0
- package/dist/project-outcomes.js +79 -0
- package/dist/pull-request-flow.js +35 -4
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +41 -9
- package/dist/result-follow-ups.js +4 -0
- package/dist/result-review.d.ts +9 -0
- package/dist/result-review.js +45 -5
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +7 -0
- package/dist/server/chat.js +4 -2
- package/dist/server/console.css +41 -0
- package/dist/server/flows.js +15 -0
- package/dist/server/pages.js +11 -1
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +15 -2
- package/dist/server/render-pages.js +52 -4
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-tasks.d.ts +9 -0
- package/dist/server/render-tasks.js +32 -11
- package/dist/server/route-table.js +12 -0
- package/dist/server/settings.js +18 -1
- package/dist/server/task-views.js +11 -0
- package/dist/server/tasks.d.ts +10 -0
- package/dist/server/tasks.js +95 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/store.d.ts +116 -5
- package/dist/store.js +385 -24
- package/dist/surface.js +18 -2
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/task-source.d.ts +13 -1
- package/dist/task-source.js +17 -1
- package/dist/telegram-lead.js +7 -5
- package/dist/telegram.js +1 -1
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update.js +2 -2
- package/dist/webhooks.d.ts +6 -0
- package/dist/webhooks.js +25 -0
- package/dist/work-index.d.ts +28 -0
- package/dist/work-index.js +82 -28
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +3 -2
- package/dist/youiyoux-intake.d.ts +234 -0
- package/dist/youiyoux-intake.js +908 -0
- package/package.json +11 -8
|
@@ -198,6 +198,12 @@ export type BrowserTasksView = {
|
|
|
198
198
|
detail: string | null;
|
|
199
199
|
problem: string | null;
|
|
200
200
|
notes: string[];
|
|
201
|
+
/** Put away, or Restore in Archived (task-tidy.md): a POST to the task's own route; null for a viewer or live work. */
|
|
202
|
+
tidy?: {
|
|
203
|
+
action: string;
|
|
204
|
+
label: 'Put away' | 'Restore';
|
|
205
|
+
returnTo: string;
|
|
206
|
+
} | null;
|
|
201
207
|
}[];
|
|
202
208
|
empty: {
|
|
203
209
|
text: string;
|
|
@@ -209,6 +215,8 @@ export type BrowserTasksView = {
|
|
|
209
215
|
};
|
|
210
216
|
tools: BrowserLink[];
|
|
211
217
|
newTask: BrowserLink;
|
|
218
|
+
/** Put away old tasks (an approver's): the preview page. */
|
|
219
|
+
putAway?: BrowserLink | null;
|
|
212
220
|
/** v105: subscription windows and monthly budgets, one tile each (an instance operator's; null when there are none). */
|
|
213
221
|
limits: BrowserLimits | null;
|
|
214
222
|
/** The stored-state filter (Any state, Queued, Running, Done, Failed, Cancelled), each a real link starting on page one. */
|
|
@@ -449,6 +457,15 @@ export type BrowserTaskView = {
|
|
|
449
457
|
label: string;
|
|
450
458
|
current: BrowserLink;
|
|
451
459
|
} | null;
|
|
460
|
+
/** Put away (task-tidy.md): "Archived by tidy: <why>", when, and Restore for an approver. */
|
|
461
|
+
archived?: {
|
|
462
|
+
words: string;
|
|
463
|
+
at: string;
|
|
464
|
+
restore: {
|
|
465
|
+
action: string;
|
|
466
|
+
returnTo: string;
|
|
467
|
+
} | null;
|
|
468
|
+
} | null;
|
|
452
469
|
/** The status card; `statusHtml` stands in when no assignment projection exists. */
|
|
453
470
|
status: AssignmentCard | null;
|
|
454
471
|
statusHtml: string;
|
|
@@ -624,6 +641,10 @@ export type BrowserProjectRow = {
|
|
|
624
641
|
};
|
|
625
642
|
export type BrowserProjectsView = {
|
|
626
643
|
kind: 'projects';
|
|
644
|
+
activity?: {
|
|
645
|
+
project: string;
|
|
646
|
+
report: import('./project-outcomes.js').ProjectOutcomes | null;
|
|
647
|
+
};
|
|
627
648
|
/** Arrived from New task: choosing where the task belongs. */
|
|
628
649
|
choosing: boolean;
|
|
629
650
|
problem: string | null;
|
|
@@ -711,6 +732,7 @@ export type BrowserCheckItem = {
|
|
|
711
732
|
src: string;
|
|
712
733
|
href: string;
|
|
713
734
|
caption: string;
|
|
735
|
+
image?: import("./result-review.js").FeedbackImage;
|
|
714
736
|
}[];
|
|
715
737
|
};
|
|
716
738
|
/** The result's one decision: what Accept is called, why it isn't plain
|
|
@@ -972,6 +994,8 @@ export type BrowserFlowStage = {
|
|
|
972
994
|
}[] | undefined;
|
|
973
995
|
/** A Build zone in another project: its path. */
|
|
974
996
|
repo?: string | undefined;
|
|
997
|
+
/** v144: a script zone that starts a browser (Playwright's Chromium); it runs only once a person allows browser use for the flow. */
|
|
998
|
+
browser?: true | undefined;
|
|
975
999
|
next: string | null;
|
|
976
1000
|
onFail: string | null;
|
|
977
1001
|
};
|
|
@@ -995,6 +1019,8 @@ export type BrowserFlowCard = {
|
|
|
995
1019
|
stage: string;
|
|
996
1020
|
state: "active" | "done" | "cancelled";
|
|
997
1021
|
waiting: string | null;
|
|
1022
|
+
/** It waits because browser use isn't allowed for the flow (flow-browser.ts): the card offers Allow browser. */
|
|
1023
|
+
browserWait?: true;
|
|
998
1024
|
task: {
|
|
999
1025
|
id: string;
|
|
1000
1026
|
href: string;
|
|
@@ -1289,6 +1315,25 @@ export type BrowserFlowView = {
|
|
|
1289
1315
|
}[];
|
|
1290
1316
|
schedulesDigest: string;
|
|
1291
1317
|
};
|
|
1318
|
+
/** Browser use (flow-browser.ts browserViewOf): null when no zone says it starts a browser. */
|
|
1319
|
+
browser: {
|
|
1320
|
+
status: "allowed" | "ended" | "none";
|
|
1321
|
+
words: string;
|
|
1322
|
+
signedBy: string | null;
|
|
1323
|
+
signedAt: string | null;
|
|
1324
|
+
zones: {
|
|
1325
|
+
zone: string;
|
|
1326
|
+
title: string;
|
|
1327
|
+
script: string;
|
|
1328
|
+
version: number;
|
|
1329
|
+
digest: string;
|
|
1330
|
+
}[];
|
|
1331
|
+
terms: string[];
|
|
1332
|
+
digest: string | null;
|
|
1333
|
+
problem: string | null;
|
|
1334
|
+
/** Whether this person may allow it here: an approver whose account reaches every project (as the route requires). */
|
|
1335
|
+
canApprove: boolean;
|
|
1336
|
+
} | null;
|
|
1292
1337
|
approvers: string[];
|
|
1293
1338
|
/** The projects a Build zone may work in: the flow's own first, then others the viewer can reach. */
|
|
1294
1339
|
projects?: {
|
package/dist/chat-actions.d.ts
CHANGED
|
@@ -153,6 +153,16 @@ export declare const CHAT_ACTIONS: {
|
|
|
153
153
|
readonly protected: false;
|
|
154
154
|
readonly password: false;
|
|
155
155
|
};
|
|
156
|
+
readonly flow_browser: {
|
|
157
|
+
readonly label: "Allow browser";
|
|
158
|
+
readonly protected: true;
|
|
159
|
+
readonly password: true;
|
|
160
|
+
};
|
|
161
|
+
readonly flow_browser_off: {
|
|
162
|
+
readonly label: "Turn off browser";
|
|
163
|
+
readonly protected: false;
|
|
164
|
+
readonly password: false;
|
|
165
|
+
};
|
|
156
166
|
readonly subagent_create: {
|
|
157
167
|
readonly label: "Add subagent";
|
|
158
168
|
readonly protected: false;
|
|
@@ -266,7 +276,7 @@ export declare function sharedActionReviewPath(id: number): string;
|
|
|
266
276
|
export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
|
|
267
277
|
payload: {
|
|
268
278
|
version: 1;
|
|
269
|
-
operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
|
|
279
|
+
operation: "decision_record" | "decision_retire" | "flow_browser" | "flow_browser_off" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
|
|
270
280
|
request: {
|
|
271
281
|
[x: string]: unknown;
|
|
272
282
|
};
|
package/dist/chat-actions.js
CHANGED
|
@@ -25,6 +25,7 @@ import { saveScript, scriptDigest, validateScript } from "./flow-scripts.js";
|
|
|
25
25
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, flowCardHref, flowCardText, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
26
26
|
import { chooseFlowCard, flowPersonOf } from "./flow-send.js";
|
|
27
27
|
import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCard } from "./flow-people.js";
|
|
28
|
+
import { browserViewOf, liveBrowserGrant, revokeFlowBrowser, signFlowBrowser } from "./flow-browser.js";
|
|
28
29
|
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
29
30
|
import { describeSchedule, parseSchedule } from "./flow-schedule.js";
|
|
30
31
|
import { dirname } from "node:path";
|
|
@@ -109,6 +110,10 @@ export const CHAT_ACTIONS = {
|
|
|
109
110
|
flow_trigger_pause: { label: "Pause trigger", protected: false, password: false },
|
|
110
111
|
flow_trigger_resume: { label: "Turn trigger on", protected: false, password: false },
|
|
111
112
|
flow_trigger_remove: { label: "Remove trigger", protected: false, password: false },
|
|
113
|
+
// Browser use widens a flow's sandbox (flow-browser.ts): the lead may only propose it; a person allows it with their
|
|
114
|
+
// own password on the secure screen, never on a chat card or a paired phone's tap. Turning it off lowers authority.
|
|
115
|
+
flow_browser: { label: "Allow browser", protected: true, password: true },
|
|
116
|
+
flow_browser_off: { label: "Turn off browser", protected: false, password: false },
|
|
112
117
|
subagent_create: { label: "Add subagent", protected: false, password: false },
|
|
113
118
|
subagent_soul: { label: "Update soul file", protected: false, password: false },
|
|
114
119
|
subagent_state: { label: "Change subagent", protected: false, password: false },
|
|
@@ -710,6 +715,32 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
710
715
|
terms.push("Only what happens from now on counts; nothing already there is added.");
|
|
711
716
|
terms.push("Work a card starts still waits for your usual approvals.");
|
|
712
717
|
}
|
|
718
|
+
else if (operation === "flow_browser" || operation === "flow_browser_off") {
|
|
719
|
+
const { flow } = flowTarget;
|
|
720
|
+
const view = browserViewOf(store, flow);
|
|
721
|
+
if (view === null)
|
|
722
|
+
throw Error(`No zone in ${flow.name} says it starts a browser. Mark its script zone first (edit the flow), then propose this.`);
|
|
723
|
+
// As on the flow page (route table: instance access), a project-limited account can't allow browser use.
|
|
724
|
+
if (operation === "flow_browser" && store.accountOf(who.name)?.projects !== null)
|
|
725
|
+
throw Error("Allowing browser use needs an account with access to every project.");
|
|
726
|
+
if (operation === "flow_browser_off") {
|
|
727
|
+
if (liveBrowserGrant(store, flow.id) === null)
|
|
728
|
+
throw Error(`Browser use isn't allowed for ${flow.name}.`);
|
|
729
|
+
state = { flow: flow.id, grant: liveBrowserGrant(store, flow.id).id };
|
|
730
|
+
title = `Turn off browser use in ${flow.name}`;
|
|
731
|
+
terms.push(`${flow.name}'s browser zones stop and wait until a person allows browser use again.`);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
if (view.status === "allowed")
|
|
735
|
+
throw Error(`${view.words}.`);
|
|
736
|
+
if (view.digest === null)
|
|
737
|
+
throw Error(view.problem ?? "This flow's browser zones can't be approved as they are.");
|
|
738
|
+
// The approval the person reads and the one their password signs: the digest is re-derived when they confirm.
|
|
739
|
+
state = { flow: flow.id, revision: flow.revision, digest: view.digest };
|
|
740
|
+
title = `Allow browser use in ${flow.name}`;
|
|
741
|
+
terms.push(`Flow: ${flow.name} · project ${project}`, ...view.zones.map(zone => `${zone.title} runs ${zone.script} v${zone.version}\nScript digest ${zone.digest}`), "Each run of these zones starts a headless Chromium.", ...view.terms, `Approval digest ${view.digest}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
713
744
|
else if (operation === "flow_trigger_pause" || operation === "flow_trigger_resume" || operation === "flow_trigger_remove") {
|
|
714
745
|
const { flow, trigger } = flowTarget;
|
|
715
746
|
const config = trigger === null ? null : triggerConfigOf(trigger);
|
|
@@ -1099,6 +1130,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1099
1130
|
}
|
|
1100
1131
|
// The review nonce was minted against the saved proposal's stamp: it is checked against that, whoever reviews.
|
|
1101
1132
|
const savedStamp = payload.stamp;
|
|
1133
|
+
let personProved = null;
|
|
1102
1134
|
payload = live;
|
|
1103
1135
|
const config = CHAT_ACTIONS[live.operation], req = live.request;
|
|
1104
1136
|
const challenged = options.via !== "web" &&
|
|
@@ -1113,10 +1145,14 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1113
1145
|
// A scope approval proves its person inside approve() (one transaction with the seal, under the same freshness
|
|
1114
1146
|
// rule as the task page); here an empty field is only looked at, never spent (an explicit re-check is one-use).
|
|
1115
1147
|
const emptyApproval = payload.operation === "scope_approve" && options.review.password === "";
|
|
1148
|
+
const proved = config.password && !emptyApproval ? authenticateApprover(store, who.name, options.review.password, payload.repo) : null;
|
|
1149
|
+
// How the person was proved, for an approval that records it (browser use: flow-browser.ts refuses any but a person's).
|
|
1150
|
+
if (proved?.ok === true)
|
|
1151
|
+
personProved = proved.method;
|
|
1116
1152
|
if (config.password &&
|
|
1117
1153
|
(emptyApproval
|
|
1118
1154
|
? !passwordlessSessionFor(store, who.name) && !ssoNowAvailable(who.name) && withFreshness("approve-standard", () => freshnessFor(who.name)) === null
|
|
1119
|
-
:
|
|
1155
|
+
: proved?.ok !== true))
|
|
1120
1156
|
return refuse("needs-confirm", "Confirm with your password in the secure screen.");
|
|
1121
1157
|
}
|
|
1122
1158
|
try {
|
|
@@ -1156,6 +1192,22 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1156
1192
|
? {}
|
|
1157
1193
|
: { restore: Number(req["restore"]) }),
|
|
1158
1194
|
}, now);
|
|
1195
|
+
else if (payload.operation === "flow_browser") {
|
|
1196
|
+
// Only through the secure screen with the person's own password (protected + password above); signFlowBrowser
|
|
1197
|
+
// re-reads the flow, its zones and scripts in this transaction and refuses unless they are what the card showed.
|
|
1198
|
+
const flow = Number(payload.state["flow"]);
|
|
1199
|
+
// signFlowBrowser re-reads the account's generation and reach in its transaction (it may have narrowed since).
|
|
1200
|
+
const signed = signFlowBrowser(store, { flow, signer: actor, proof: personProved, sawDigest: String(payload.state["digest"] ?? ""), console: { generation: who.generation } }, now);
|
|
1201
|
+
if (!signed.ok)
|
|
1202
|
+
throw Error(signed.message);
|
|
1203
|
+
return { ok: true, taskId: null, said: "Browser allowed. Its browser zones can start Chromium now.", href: `/flows/${flow}` };
|
|
1204
|
+
}
|
|
1205
|
+
else if (payload.operation === "flow_browser_off") {
|
|
1206
|
+
const flow = Number(payload.state["flow"]);
|
|
1207
|
+
if (!revokeFlowBrowser(store, flow, actor, now))
|
|
1208
|
+
throw Error("Browser use was already off.");
|
|
1209
|
+
return { ok: true, taskId: null, said: "Browser turned off. Its browser zones wait until someone allows it again.", href: `/flows/${flow}` };
|
|
1210
|
+
}
|
|
1159
1211
|
else if (payload.operation.startsWith("flow_")) {
|
|
1160
1212
|
const done = runFlowAction(store, payload, actor, who.repos, options.root, now);
|
|
1161
1213
|
return { ok: true, taskId: null, said: done.said, href: done.href };
|
package/dist/chat-channel.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FeedbackImage } from "./result-review.js";
|
|
1
2
|
import type { DoorOutcome } from "./lead-doors.js";
|
|
2
3
|
import { type VerifiedApprover } from "./principal.js";
|
|
3
4
|
import type { ChatConfig, LeadProposal, LeadSession, LeadThread, Store, SubscriptionChatProviderId } from "./store.js";
|
|
@@ -40,7 +41,7 @@ export declare function tooLongText(length: number): string;
|
|
|
40
41
|
/** A reply to a message about several tasks is never guessed: it names them and says how to choose. */
|
|
41
42
|
export declare function whichTaskText(titles: readonly string[]): string;
|
|
42
43
|
/** The server-authored context a reply to a result message carries into the turn — the exact execution and run, never a guess. */
|
|
43
|
-
export declare function replyContextFor(taskId: string, run: number | null): string;
|
|
44
|
+
export declare function replyContextFor(taskId: string, run: number | null, image?: FeedbackImage): string;
|
|
44
45
|
/** The context a chosen task (/tasks, /task <name>) carries into each turn from that chat. */
|
|
45
46
|
export declare function focusContextFor(taskId: string): string;
|
|
46
47
|
/** No trusted https origin is configured: one honest line, no localhost, no promise. */
|
|
@@ -69,6 +70,14 @@ export declare function armedCardText(proposal: LeadProposal, previewText: strin
|
|
|
69
70
|
/** The "yes" button's label for an armed card. */
|
|
70
71
|
export declare function armedYesLabel(proposal: LeadProposal): string;
|
|
71
72
|
export declare function proposalLink(store: Store, proposal: LeadProposal, repos: readonly string[], channel?: ChatChannelName | null): PhoneLink | null;
|
|
73
|
+
/** The personal Telegram card: the shared preview, or a handoff to the lead conversation when it cannot be shown in full. */
|
|
74
|
+
export declare function phoneProposalPreview(store: Store, proposal: LeadProposal, repos: readonly string[]): {
|
|
75
|
+
text: string;
|
|
76
|
+
buttons: boolean;
|
|
77
|
+
closed?: true;
|
|
78
|
+
};
|
|
79
|
+
/** The personal Telegram card's link: the lead conversation holding a card too long to show, else the shared link. */
|
|
80
|
+
export declare function phoneProposalLink(store: Store, proposal: LeadProposal, repos: readonly string[], channel?: ChatChannelName | null): PhoneLink | null;
|
|
72
81
|
/**
|
|
73
82
|
* The link a CONFIRMED card carries: the exact recorded task the outcome
|
|
74
83
|
* names, where its remaining step lives — the approval control while the
|
package/dist/chat-channel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { parseFeedbackImage } from "./result-review.js";
|
|
1
2
|
/** Shared membership-chat identity, proposal previews and result links. Transport adapters never grant authority. */
|
|
2
3
|
import { FLOW_HREF } from "./flow-engine.js";
|
|
3
|
-
import { closedQuestionWords } from "./decision.js";
|
|
4
|
+
import { closedQuestionWords, LIMITS } from "./decision.js";
|
|
4
5
|
import { createHash } from "node:crypto";
|
|
5
6
|
import { sharedActionPayload, sharedActionNeedsReview, sharedActionReviewPath, sharedActionAllowsChallenge, CHAT_ACTIONS, } from "./chat-actions.js";
|
|
6
7
|
import { isDirectChatProvider, subscriptionCredentialKey } from "./converse.js";
|
|
@@ -132,12 +133,13 @@ export function whichTaskText(titles) {
|
|
|
132
133
|
].join("\n");
|
|
133
134
|
}
|
|
134
135
|
/** The server-authored context a reply to a result message carries into the turn — the exact execution and run, never a guess. */
|
|
135
|
-
export function replyContextFor(taskId, run) {
|
|
136
|
+
export function replyContextFor(taskId, run, image) {
|
|
136
137
|
return [
|
|
137
138
|
`Current task: ${taskId}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.`,
|
|
138
139
|
run === null
|
|
139
140
|
? `The operator is replying to a message about execution ${taskId}.`
|
|
140
141
|
: `The operator is replying to result #${run} from execution ${taskId}. Use get_result for that exact execution and run when responding to feedback; do not substitute another result. If the operator asks for changes, use propose_review revise; use note only when they explicitly ask to save feedback without starting work.`,
|
|
142
|
+
...(image === undefined ? [] : [`The reply targets screenshot ${image.artifact} on result #${image.run}. Retain image: ${image.artifact} in propose_review so the exact screenshot stays with new feedback; pass image: null only when the feedback is not about it. Read get_result_images for this result first.`]),
|
|
141
143
|
].join(" ");
|
|
142
144
|
}
|
|
143
145
|
/** The context a chosen task (/tasks, /task <name>) carries into each turn from that chat. */
|
|
@@ -146,6 +148,10 @@ export function focusContextFor(taskId) {
|
|
|
146
148
|
}
|
|
147
149
|
/** A step the phone cannot take itself: said once; the button (or its absence) says where. */
|
|
148
150
|
const HANDOFF = "This step finishes in Toolroll.";
|
|
151
|
+
/** One Telegram message; a card that cannot fit is never confirmed from a shortened copy. */
|
|
152
|
+
const TELEGRAM_TEXT_MAX = 4_096;
|
|
153
|
+
const PHONE_TOO_LONG = "This is too long to show in full here. It is waiting in your Toolroll chat to review and confirm.";
|
|
154
|
+
const LEAD_CHAT_LINK = { label: "Open chat", path: "/chat" };
|
|
149
155
|
/** No trusted https origin is configured: one honest line, no localhost, no promise. */
|
|
150
156
|
export const NO_PHONE_LINK = "Phone access isn't configured. Open Toolroll on your computer.";
|
|
151
157
|
/** An origin exists but the card's task is not one this phone may reach now: said as that, never as missing setup. */
|
|
@@ -229,6 +235,23 @@ export function proposalLink(store, proposal, repos, channel = null) {
|
|
|
229
235
|
}
|
|
230
236
|
return null;
|
|
231
237
|
}
|
|
238
|
+
/** A personal Telegram card fits one message, or it waits, whole, in the person's lead conversation in Toolroll. */
|
|
239
|
+
function fitsPhone(preview) {
|
|
240
|
+
return !preview.buttons || preview.text.length <= TELEGRAM_TEXT_MAX;
|
|
241
|
+
}
|
|
242
|
+
/** The personal Telegram card: the shared preview, or a handoff to the lead conversation when it cannot be shown in full. */
|
|
243
|
+
export function phoneProposalPreview(store, proposal, repos) {
|
|
244
|
+
const preview = proposalPreview(store, proposal, repos, "telegram");
|
|
245
|
+
return fitsPhone(preview)
|
|
246
|
+
? preview
|
|
247
|
+
: { text: [preview.text.split("\n")[0], PHONE_TOO_LONG, "", HANDOFF].join("\n"), buttons: false };
|
|
248
|
+
}
|
|
249
|
+
/** The personal Telegram card's link: the lead conversation holding a card too long to show, else the shared link. */
|
|
250
|
+
export function phoneProposalLink(store, proposal, repos, channel = null) {
|
|
251
|
+
return fitsPhone(proposalPreview(store, proposal, repos, "telegram"))
|
|
252
|
+
? proposalLink(store, proposal, repos, channel)
|
|
253
|
+
: LEAD_CHAT_LINK;
|
|
254
|
+
}
|
|
232
255
|
/**
|
|
233
256
|
* The link a CONFIRMED card carries: the exact recorded task the outcome
|
|
234
257
|
* names, where its remaining step lives — the approval control while the
|
|
@@ -410,20 +433,22 @@ export function proposalPreview(store, proposal, repos, channel = null) {
|
|
|
410
433
|
? payload["notes"]
|
|
411
434
|
: [];
|
|
412
435
|
const saved = (snapshot?.notes ?? []).filter((one) => selected.includes(one.id));
|
|
413
|
-
const note = t("note",
|
|
436
|
+
const note = t("note", LIMITS.note);
|
|
437
|
+
const image = parseFeedbackImage(payload["image"]);
|
|
414
438
|
const path = t("path", 300);
|
|
415
439
|
return card(`${revise ? "Request changes to" : "Save feedback on"} result #${String(payload["run"] ?? "?")} of ${taskName}`, [
|
|
416
440
|
...(note === ""
|
|
417
441
|
? []
|
|
418
442
|
: [
|
|
419
443
|
`Your feedback${path === "" ? "" : ` (${path}${typeof payload["line"] === "number" ? `:${payload["line"]}` : ""})`}:`,
|
|
420
|
-
...
|
|
444
|
+
...(image ? [`Screenshot ${image.artifact} · result #${image.run}`] : []),
|
|
445
|
+
...lines(note, LIMITS.note),
|
|
421
446
|
]),
|
|
422
447
|
...(saved.length === 0
|
|
423
448
|
? []
|
|
424
449
|
: [
|
|
425
450
|
"Saved notes included:",
|
|
426
|
-
...saved.map((one) => `• ${one.path === null ? "" : `${phoneText(one.path, 120)}${one.line === null ? "" : `:${one.line}`}: `}${phoneText(one.note,
|
|
451
|
+
...saved.map((one) => `• ${one.image ? `Screenshot ${one.image.artifact} · result #${one.image.run}: ` : ""}${one.path === null ? "" : `${phoneText(one.path, 120)}${one.line === null ? "" : `:${one.line}`}: `}${phoneText(one.note, LIMITS.note)}`),
|
|
427
452
|
]),
|
|
428
453
|
], revise
|
|
429
454
|
? "Creates a revision of the same task. Its approval follows your settings."
|
|
@@ -484,6 +509,11 @@ export const CHAT_ACTION_PARITY = {
|
|
|
484
509
|
how: "Reads current tasks, decisions, results and project knowledge from the local database through the shared engine, within the enrolled projects.",
|
|
485
510
|
gap: "Dedicated Telegram and Slack journeys for this tool and live channel rendering remain unverified.",
|
|
486
511
|
},
|
|
512
|
+
get_project_activity: {
|
|
513
|
+
support: "direct",
|
|
514
|
+
how: "Reads the project's Ready backlog, finished-to-decision elapsed time, explicit revisions and recorded cost for completed families in an accessible enrolled project through the shared engine.",
|
|
515
|
+
gap: "Dedicated Telegram and Slack journeys for this tool and live channel rendering remain unverified.",
|
|
516
|
+
},
|
|
487
517
|
get_project_context: {
|
|
488
518
|
support: "direct",
|
|
489
519
|
how: "Reads bounded source excerpts or advisory import impact in an accessible enrolled project through the shared engine, with source-search fallback when its index is unavailable.",
|
|
@@ -548,6 +578,7 @@ export const CHAT_ACTION_PARITY = {
|
|
|
548
578
|
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 },
|
|
549
579
|
search_project_memory: { support: "direct", how: "Read during a turn: one search over decisions, references, lessons and the conversations the person may read.", gap: null },
|
|
550
580
|
list_tasks: { support: "direct", how: "Read during a turn.", gap: null },
|
|
581
|
+
tidy_preview: { support: "direct", how: "Read during a turn: what Put away old tasks would put away now and why. Only a person puts tasks away, on the console or with the CLI command it names.", gap: null },
|
|
551
582
|
get_task: {
|
|
552
583
|
support: "direct",
|
|
553
584
|
how: "Read during a turn; a reply to a result message pins the exact execution.",
|
package/dist/chat-decide.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type VerifiedApprover } from "./principal.js";
|
|
|
4
4
|
import type { Scope } from "./scope.js";
|
|
5
5
|
import { type ReleaseCard } from "./release-card.js";
|
|
6
6
|
import type { Store } from "./store.js";
|
|
7
|
+
import { permissionPlainWords } from "./chat-terms.js";
|
|
8
|
+
export { permissionPlainWords };
|
|
7
9
|
/** The tokens live in chat_decide_action and the open "What should change?" in chat_decide_prompt (store.ts). */
|
|
8
10
|
export type DecideAct = (typeof DECIDE_ACTS)[number];
|
|
9
11
|
export type DecideTarget = {
|
|
@@ -20,6 +22,11 @@ export type DecideTarget = {
|
|
|
20
22
|
kind: "merge";
|
|
21
23
|
taskId: string;
|
|
22
24
|
run: number;
|
|
25
|
+
}
|
|
26
|
+
/** The tasks one youiyoux pass filed (youiyoux-intake.ts), named by any one of them. */
|
|
27
|
+
| {
|
|
28
|
+
kind: "requests";
|
|
29
|
+
taskId: string;
|
|
23
30
|
};
|
|
24
31
|
export type DecideChannel = "telegram" | "slack" | "discord" | "teams";
|
|
25
32
|
/** Who is tapping, where: one paired person's binding on one channel, and the chat the card is in. */
|
|
@@ -56,11 +63,13 @@ export declare const DECIDE_LABELS: {
|
|
|
56
63
|
readonly cancel: "Cancel";
|
|
57
64
|
};
|
|
58
65
|
export declare const WHAT_SHOULD_CHANGE = "What should change?";
|
|
66
|
+
/** `taskId`: the task the button acts on, when it isn't the card's own (a youiyoux card's one-task Approve). */
|
|
59
67
|
type ActSpec = {
|
|
60
68
|
act: DecideAct;
|
|
61
69
|
run: number | null;
|
|
62
70
|
digest: string;
|
|
63
71
|
label?: string;
|
|
72
|
+
taskId?: string;
|
|
64
73
|
};
|
|
65
74
|
/** What a card offers: an optional replacement text (the plan card) and rows of acts and links; a release card also
|
|
66
75
|
* sends its full terms as a file with it (`file`), where the chat app can. */
|
|
@@ -85,11 +94,11 @@ export declare function chatApproveMode(store: Store, repo: string | null, appro
|
|
|
85
94
|
ok: false;
|
|
86
95
|
why: string;
|
|
87
96
|
};
|
|
88
|
-
/** What a plan's yes allows an agent to do, in a few plain words (the approval sheet's "You're allowing"). */
|
|
89
|
-
export declare function permissionPlainWords(profile: Scope["profile"] | null | undefined): string | null;
|
|
90
97
|
/** A plan this person's chat may approve now: the term, the plan's exact bytes, and none of the reasons it must open
|
|
91
98
|
* Toolroll instead. `digest` binds the scope and the plan document the card shows. */
|
|
92
|
-
export declare function planInChat(store: Store, taskId: string, approver: string, now: Date, channel: DecideChannel, root?: string
|
|
99
|
+
export declare function planInChat(store: Store, taskId: string, approver: string, now: Date, channel: DecideChannel, root?: string,
|
|
100
|
+
/** A youiyoux card asking (requestsOffer): its own words are checked there, every one shown. */
|
|
101
|
+
from?: "requests"): {
|
|
93
102
|
ok: true;
|
|
94
103
|
modeDigest: string;
|
|
95
104
|
source: "setting" | "mode";
|
|
@@ -109,6 +118,10 @@ export declare function mergeInChat(store: Store, taskId: string, run: number, a
|
|
|
109
118
|
ok: false;
|
|
110
119
|
why: string;
|
|
111
120
|
};
|
|
121
|
+
/** Whether a card's words arrive as one message that shows each of them as written: within Telegram's part, Slack's and
|
|
122
|
+
* (after its escapes) Discord's limits, with room for the question a tap adds. Teams draws card text as Markdown, where
|
|
123
|
+
* a link's label hides its address, and Slack turns `:name:` into an emoji: there such words never approve. */
|
|
124
|
+
export declare function shownWhole(text: string, channel: DecideChannel): boolean;
|
|
112
125
|
/** The plan card: title, goal in two lines, what changes, done when, and what the yes allows. On Telegram the yes's
|
|
113
126
|
* terms come second, under the title, and the steps are fewer: the full plan is behind Edit. */
|
|
114
127
|
export declare function planCardText(store: Store, taskId: string, scope: Scope, root?: string, channel?: DecideChannel): string;
|
|
@@ -260,4 +273,3 @@ export declare function decideTargetOf(row: {
|
|
|
260
273
|
taskId: string | null;
|
|
261
274
|
run: number | null;
|
|
262
275
|
}): DecideTarget | null;
|
|
263
|
-
export {};
|