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
package/dist/team-runtime.js
CHANGED
|
@@ -6,8 +6,8 @@ import { workIndexPage } from './work-index.js';
|
|
|
6
6
|
import { TeamLeads } from './team-leads.js';
|
|
7
7
|
import { teamScopeAllows } from './team-contract.js';
|
|
8
8
|
import { verifyApproverStanding } from './principal.js';
|
|
9
|
-
import { credentialKeyOf, isDirectChatProvider, priceForConfig, subscriptionCredentialKey,
|
|
10
|
-
import {
|
|
9
|
+
import { credentialKeyOf, isDirectChatProvider, priceForConfig, subscriptionCredentialKey, leadWorstCaseForPrice } from './converse.js';
|
|
10
|
+
import { runLeadTurn } from './lead.js';
|
|
11
11
|
import { teamChatAuthorization } from './team-chat-authorization.js';
|
|
12
12
|
import { updateAdmissionPaused } from './desktop-update-gate.js';
|
|
13
13
|
const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
@@ -34,7 +34,7 @@ export function createTeamRuntime(options) {
|
|
|
34
34
|
const stamp = hash([grant.termsDigest, grant.enabled, session?.id, session?.spentMicrousd, used, weekly, Boolean(latched), legacyBusy]);
|
|
35
35
|
let reason = grant.waitingReason ?? (!grant.enabled ? 'Enable chat using the current provider and limits.' : null);
|
|
36
36
|
if (!reason && live && session) {
|
|
37
|
-
const price = priceForConfig(live.config), minimum = isDirectChatProvider(live.config.provider) && price ?
|
|
37
|
+
const price = priceForConfig(live.config), minimum = isDirectChatProvider(live.config.provider) && price ? leadWorstCaseForPrice(price, 0, { steps: 1 }) : 0;
|
|
38
38
|
if (latched)
|
|
39
39
|
reason = 'A previous chat cost is unconfirmed. Open Settings to inspect it.';
|
|
40
40
|
else if (used >= live.config.dailyTurns)
|
|
@@ -72,7 +72,7 @@ export function createTeamRuntime(options) {
|
|
|
72
72
|
const waitingReason = selected && view.canSend && grant.enabled ? admission(actor, selected.id).reason : null;
|
|
73
73
|
return { ...view, chatAuthorization: { ...grant, ...(waitingReason ? { waitingReason } : {}) },
|
|
74
74
|
...(selected ? { tasks: tasksFor(selected),
|
|
75
|
-
proposals: store.
|
|
75
|
+
proposals: store.listLeadProposals(selected.threadId).filter(p => p.state !== 'drafting').slice(-40).map(p => ({ id: p.id, turnId: p.turn, title: typeof p.payload['title'] === 'string' ? p.payload['title'].slice(0, 160) : `Review ${p.kind.replaceAll('_', ' ')}`, state: p.state, href: `/chat?conversation=${encodeURIComponent(selected.id)}&proposal=${p.id}` })) } : {}) };
|
|
76
76
|
}
|
|
77
77
|
function authorize(actor, args) {
|
|
78
78
|
const id = typeof args['conversationId'] === 'string' ? args['conversationId'] : '';
|
|
@@ -113,7 +113,7 @@ export function createTeamRuntime(options) {
|
|
|
113
113
|
domain.finish(claim, { status: 'cancelled', error: 'Automatic updates were paused.' }, clock());
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
const provider = options.provider(), session = store.teamMateSession(claim.actor.name, claim.threadId), thread = store.
|
|
116
|
+
const provider = options.provider(), session = store.teamMateSession(claim.actor.name, claim.threadId), thread = store.getLeadThread(claim.threadId);
|
|
117
117
|
const proof = verifyApproverStanding(store, claim.actor.name, claim.actor.generation, claim.projects);
|
|
118
118
|
if (!provider || !session || !thread || !proof.ok || !grant?.enabled) {
|
|
119
119
|
domain.finish(claim, { status: 'failed', error: grant?.waitingReason ?? 'Chat authorization changed. Enable chat before sending a new message.' }, clock());
|
|
@@ -122,14 +122,14 @@ export function createTeamRuntime(options) {
|
|
|
122
122
|
const requestId = requestFor(claim);
|
|
123
123
|
// A saved terminal provider receipt wins over delivery bookkeeping.
|
|
124
124
|
// Neither failure nor an unconfirmed receipt causes another provider call.
|
|
125
|
-
const receipt = store.
|
|
125
|
+
const receipt = store.leadRequestReceipt(session.id, requestId);
|
|
126
126
|
if (receipt) {
|
|
127
|
-
const turn = store.
|
|
127
|
+
const turn = store.getLeadTurn(receipt.turn);
|
|
128
128
|
domain.finish(claim, { status: turn?.state === 'answered' ? 'answered' : turn?.state === 'failed' && !domain.deliveryUncertain(turn.id) ? 'failed' : 'uncertain',
|
|
129
129
|
turnId: receipt.turn, ...(turn?.state === 'answered' ? {} : { error: 'Inspect the saved response; this message was not sent again.' }) }, clock());
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
-
const result = await
|
|
132
|
+
const result = await runLeadTurn({ store, who: proof.who, session, thread, ...provider, message: claim.text, requestId, channel: "console",
|
|
133
133
|
...(() => { const name = view.leads.find(lead => lead.id === claim.leadId)?.name; return name === undefined ? {} : { leadName: name }; })(),
|
|
134
134
|
queuedMessageId: claim.messageId,
|
|
135
135
|
onAdmitted: turn => { if (!domain.current(claim) || !domain.bindTurn(claim, turn))
|
package/dist/team-ui.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Html } from './html.js';
|
|
1
2
|
import type { TeamSnapshot } from './team-contract.js';
|
|
2
3
|
/** Readable fallback uses the same admitted projection as the interactive UI. */
|
|
3
|
-
export declare function teamWorkspaceHtml(snapshot: TeamSnapshot):
|
|
4
|
+
export declare function teamWorkspaceHtml(snapshot: TeamSnapshot): Html;
|
package/dist/team-ui.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { html } from './html.js';
|
|
2
2
|
/** Readable fallback uses the same admitted projection as the interactive UI. */
|
|
3
3
|
export function teamWorkspaceHtml(snapshot) {
|
|
4
4
|
const selected = snapshot.selected;
|
|
5
|
-
return `<section class="team-workspace"><h1>${
|
|
6
|
-
(selected ? `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${escape(snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead')}</p>` : '') +
|
|
7
|
-
(snapshot.messages.length ? snapshot.messages.map(message => `<article><strong>${escape(message.author)}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${escape(message.text)}</p>${message.status === 'queued' || message.status === 'running' ? `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? `<p role="alert">${escape(message.error)}</p>` : ''}</article>`).join('') : '<p>Start a conversation with your team lead.</p>') +
|
|
8
|
-
'<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>';
|
|
5
|
+
return html `<section class="team-workspace"><h1>${selected?.title ?? 'Team chat'}</h1>${selected ? html `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead'}</p>` : ''}${snapshot.messages.length ? snapshot.messages.map(message => html `<article><strong>${message.author}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${message.text}</p>${message.status === 'queued' || message.status === 'running' ? html `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? html `<p role="alert">${message.error}</p>` : ''}</article>`) : html `<p>Start a conversation with your team lead.</p>`}<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>`;
|
|
9
6
|
}
|
package/dist/teams-chat.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/** Microsoft Teams on the shared chat layer: activities in, Adaptive Cards
|
|
2
2
|
* out, the same durable receipts, rooms and commands as every other channel. */
|
|
3
3
|
import { resultShotsPruned } from "./result-shots.js";
|
|
4
|
-
import { chatQuestionButtons } from "./
|
|
4
|
+
import { chatQuestionButtons } from "./subagent-question.js";
|
|
5
5
|
import { chatAskButtons } from "./chat-ask.js";
|
|
6
6
|
import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
|
|
7
7
|
import { channelAccess, chatObject as object, planChatNotifications, planRoomMessages, processChatEvent, splitChatText } from "./chat-delivery.js";
|
|
8
8
|
import { PLATFORM_LIMITS } from "./text-limits.js";
|
|
9
9
|
import { roomCommand } from "./chat-rooms.js";
|
|
10
|
-
import {
|
|
10
|
+
import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
11
11
|
import { renderReply } from "./reply-shape.js";
|
|
12
12
|
import { armedCardText, armedYesLabel, proposalLink, proposalOutcomeText, proposalPreview } from "./chat-channel.js";
|
|
13
13
|
import { chatFlowButtons } from "./chat-flow.js";
|
|
@@ -109,7 +109,7 @@ export function receiveTeams(state, identity, raw, serviceUrlClaim, now) {
|
|
|
109
109
|
const text = isRoom ? withoutMentions(activity.text) : activity.text.trim();
|
|
110
110
|
const pair = /^pair ([a-f0-9]{32})$/.exec(text);
|
|
111
111
|
kind = pair ? "pair" : "message";
|
|
112
|
-
payload = pair ? { hash: chatHash(pair[1]) } : { text: text.slice(0,
|
|
112
|
+
payload = pair ? { hash: chatHash(pair[1]) } : { text: text.slice(0, LEAD_MESSAGE_MAX_CHARS + 1), originalLength: text.length,
|
|
113
113
|
...(Array.isArray(activity.attachments) && activity.attachments.length > 0 ? { unsupported: "Incoming files are not supported yet. Describe the request in a message; saved result screenshots open from their links." } : {}) };
|
|
114
114
|
eventId = chatHash(`${identity.installation}:message:${id}`);
|
|
115
115
|
}
|
|
@@ -176,7 +176,7 @@ export async function deliverTeamsPart(options) {
|
|
|
176
176
|
return true;
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
179
|
-
const session = event.session === null ? null : store.
|
|
179
|
+
const session = event.session === null ? null : store.getLeadSession(event.session);
|
|
180
180
|
const repos = await channelAccess(shared, binding, session?.ceilingDigest);
|
|
181
181
|
if (event.kind === "notice" && options.canNotify?.() === false)
|
|
182
182
|
return false;
|
|
@@ -197,7 +197,7 @@ export async function deliverTeamsPart(options) {
|
|
|
197
197
|
actions = openUrlAction(options.origin(), { label: "Open result", path: chatResultHref(content.image.taskId, content.image.run, "checks") });
|
|
198
198
|
}
|
|
199
199
|
if (content.proposal) {
|
|
200
|
-
const proposal = store.
|
|
200
|
+
const proposal = store.getLeadProposal(content.proposal);
|
|
201
201
|
if (!proposal)
|
|
202
202
|
text = "This proposal is unavailable.";
|
|
203
203
|
else if (proposal.state !== "pending")
|
|
@@ -220,7 +220,7 @@ export async function deliverTeamsPart(options) {
|
|
|
220
220
|
else if (!content.image) {
|
|
221
221
|
// A flow decision (v88): Approve / Edit / Send back, then the link.
|
|
222
222
|
const flow = content.flow || content.choose || content.note ? chatFlowButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label, data: { so: one.token }, ...(one.action === "approve" ? { style: "positive" } : {}) })) : [];
|
|
223
|
-
// A
|
|
223
|
+
// A subagent's question (v93): its options, then "Answer in words".
|
|
224
224
|
const asked = content.question ? chatQuestionButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label.slice(0, 80), data: { so: one.token } })) : [];
|
|
225
225
|
// The lead's question to its owner: its options, then "Something else".
|
|
226
226
|
const owner = content.ask ? chatAskButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label.slice(0, 80), data: { so: one.token } })) : [];
|
package/dist/teams-settings.d.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* registration must point at is shown here; it has to be reachable from the
|
|
4
4
|
* internet, which a tailnet-only address is not. */
|
|
5
5
|
import type { Store } from "./store.js";
|
|
6
|
-
|
|
6
|
+
import { type Html } from "./html.js";
|
|
7
|
+
export declare function teamsSettingsHtml(store: Store, dir: string, options?: {
|
|
7
8
|
code?: string;
|
|
8
9
|
problem?: string;
|
|
9
10
|
now?: Date;
|
|
10
11
|
who?: string;
|
|
11
12
|
publicUrl?: string | null;
|
|
12
|
-
}):
|
|
13
|
+
}): Html;
|
package/dist/teams-settings.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
1
2
|
import { ChatState } from "./chat-delivery-state.js";
|
|
2
3
|
import { loadTeamsCredentials } from "./teams-api.js";
|
|
3
4
|
import { loadPrimary } from "./webhooks.js";
|
|
4
5
|
import { TEAMS_MESSAGES_PATH } from "./teams.js";
|
|
5
|
-
|
|
6
|
-
export function teamsSettingsHtml(store, dir, csrf, options = {}) {
|
|
6
|
+
export function teamsSettingsHtml(store, dir, options = {}) {
|
|
7
7
|
const credentials = loadTeamsCredentials(dir), state = new ChatState(store, "teams");
|
|
8
8
|
const bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [];
|
|
9
9
|
const binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null;
|
|
@@ -11,47 +11,36 @@ export function teamsSettingsHtml(store, dir, csrf, options = {}) {
|
|
|
11
11
|
const runtime = credentials ? state.runtime(credentials.installation) : null;
|
|
12
12
|
const now = options.now ?? new Date();
|
|
13
13
|
const live = runtime && runtime.leaseUntil !== null && runtime.leaseUntil > now.toISOString() && runtime.connected;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const post = (action, content) => `<form method="post" action="/settings/teams/${action}" class="card">${hidden}${content}</form>`;
|
|
14
|
+
const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
|
|
15
|
+
const post = (action, content) => postForm(`/settings/teams/${action}`, content, { attrs: { class: "card" } });
|
|
17
16
|
const endpoint = options.publicUrl ? `${options.publicUrl.replace(/\/$/, "")}${TEAMS_MESSAGES_PATH}` : null;
|
|
18
|
-
|
|
17
|
+
const parts = [];
|
|
19
18
|
if (!credentials) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'<label>Directory (tenant) id<input style="min-height:44px" type="text" name="tenant" autocomplete="off" required></label>' +
|
|
27
|
-
'<label>Client secret<input style="min-height:44px" type="password" name="secret" autocomplete="off" required></label>' +
|
|
28
|
-
password + '<p class="meta">The secret stays on this installation. Next, pair your own Teams account.</p><button type="submit">Connect Teams</button>');
|
|
19
|
+
parts.push(html `<p>Manage projects and review results in a Teams chat, and let a channel follow a team conversation.</p>`, html `<details open><summary>Register the bot</summary><ol><li>In <a href="https://portal.azure.com" target="_blank" rel="noopener noreferrer">Azure</a>, create an <em>Azure Bot</em> with a new single-tenant app registration; note the app (client) id and the directory (tenant) id, and create a client secret.</li>`, html `<li>Set its messaging endpoint to <code>${endpoint === null ? "https://your-public-address" + TEAMS_MESSAGES_PATH : endpoint}</code>. Microsoft must reach it from the internet: a tailnet-only address needs Tailscale Funnel (or another public tunnel) on that path.</li>`, html `<li>Enable the Microsoft Teams channel on the bot, then add the app to Teams (personal scope; team scope for channels).</li></ol></details>`, post("connect", joinHtml([
|
|
20
|
+
html `<label>App (client) id<input style="min-height:44px" type="text" name="app-id" autocomplete="off" required></label>`,
|
|
21
|
+
html `<label>Directory (tenant) id<input style="min-height:44px" type="text" name="tenant" autocomplete="off" required></label>`,
|
|
22
|
+
html `<label>Client secret<input style="min-height:44px" type="password" name="secret" autocomplete="off" required></label>`,
|
|
23
|
+
html `${password}<p class="meta">The secret stays on this installation. Next, pair your own Teams account.</p><button type="submit">Connect Teams</button>`
|
|
24
|
+
])));
|
|
29
25
|
}
|
|
30
26
|
else {
|
|
31
|
-
|
|
27
|
+
parts.push(html `<p><strong>Microsoft Teams</strong> · ${live ? "Signed in" : "Waiting for sign-in"}</p>`);
|
|
32
28
|
if (endpoint !== null)
|
|
33
|
-
|
|
29
|
+
parts.push(html `<p class="meta">Messaging endpoint: <code>${endpoint}</code> — must be reachable from the internet.</p>`);
|
|
34
30
|
if (runtime?.problem)
|
|
35
|
-
|
|
31
|
+
parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
|
|
36
32
|
if (!live && !runtime?.problem)
|
|
37
|
-
|
|
33
|
+
parts.push(html `<p>Keep the Toolroll worker running to answer Teams messages.</p>`);
|
|
38
34
|
if (options.code) {
|
|
39
|
-
|
|
40
|
-
`<label>Pairing message<input type="text" readonly value="pair ${escape(options.code)}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>` +
|
|
41
|
-
'<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/teams">Check connection</a>';
|
|
35
|
+
parts.push(html `<h2>Pair your account</h2><p>Send this to the Toolroll bot in a personal Teams chat. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/teams">Check connection</a>`);
|
|
42
36
|
}
|
|
43
37
|
else if (binding && state.live(binding)) {
|
|
44
|
-
|
|
45
|
-
"<p>Try “What needs my attention?” or send <code>status</code>. In a channel, mention the bot with <code>team</code> to follow a conversation.</p>" +
|
|
46
|
-
'<p><a href="/chat">Open saved chat</a></p>' +
|
|
47
|
-
(loadPrimary(process.env, dir) === "teams" ? "<p>Task updates are sent here.</p>" : post("alerts", '<button type="submit">Send task updates here</button>')) +
|
|
48
|
-
post("unpair", password + '<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>');
|
|
38
|
+
parts.push(html `<p>Your Teams account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}</p>`, html `<p>Try “What needs my attention?” or send <code>status</code>. In a channel, mention the bot with <code>team</code> to follow a conversation.</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === "teams" ? html `<p>Task updates are sent here.</p>` : post("alerts", html `<button type="submit">Send task updates here</button>`), post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
|
|
49
39
|
}
|
|
50
40
|
else {
|
|
51
|
-
|
|
52
|
-
(others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : "") + "</p>" + password + '<button type="submit">Create pairing code</button>');
|
|
41
|
+
parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired Teams account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
|
|
53
42
|
}
|
|
54
|
-
|
|
43
|
+
parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect Teams</button>`)}</details>`);
|
|
55
44
|
}
|
|
56
|
-
return `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Teams</h1>${options.problem ? `<p class="problem" role="alert">${
|
|
45
|
+
return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Teams</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}</section>`;
|
|
57
46
|
}
|
package/dist/telegram-flow.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FlowChoiceVisit, type FlowSendContent } from "./flow-send.js";
|
|
2
2
|
import { type FlowStage } from "./flows.js";
|
|
3
|
-
import { type InlineButton } from "./telegram-
|
|
3
|
+
import { type InlineButton } from "./telegram-lead.js";
|
|
4
4
|
import type { FlowCardRow, FlowRow, Store, TelegramBinding, TelegramFlowAction, TelegramFlowChoice, TelegramFlowPrompt } from "./store.js";
|
|
5
5
|
/** A flow decision's notice: one per visit, and again (`:ask:<ms>`) when the owner asks the lead to approve it. */
|
|
6
6
|
export declare const FLOW_DECIDE_KEY: RegExp;
|
|
@@ -14,8 +14,8 @@ type Waiting = {
|
|
|
14
14
|
export declare function flowDecisionAt(store: Store, cardId: number, entry: number): Waiting | null;
|
|
15
15
|
/**
|
|
16
16
|
* A flow decision as Telegram shows it: the action and where each button takes the card first, then the draft (as
|
|
17
|
-
* the card holds it now, so what is approved is what is read) and why a
|
|
18
|
-
* zone. `said` is the saved notice's body: the words before its instruction are the
|
|
17
|
+
* the card holds it now, so what is approved is what is read) and why a subagent handed it over, then the flow and
|
|
18
|
+
* zone. `said` is the saved notice's body: the words before its instruction are the subagent's handoff.
|
|
19
19
|
*/
|
|
20
20
|
export declare function flowDecisionText(waiting: Waiting, said: string | null, head?: string): string;
|
|
21
21
|
/** Buttons for one visit, minted before the send; `place` stamps the message they landed on. */
|
package/dist/telegram-flow.js
CHANGED
|
@@ -25,7 +25,7 @@ import { keptDraft } from "./flow-draft.js";
|
|
|
25
25
|
import { decideFlowCard, draftFor, flowCardHref, flowDefinitionOf } from "./flow-engine.js";
|
|
26
26
|
import { chooseFlowCard, flowChoiceAt, flowPersonOf, flowSendPaths, readFlowSend } from "./flow-send.js";
|
|
27
27
|
import { deciderOf, FLOW_END, replyTarget } from "./flows.js";
|
|
28
|
-
import { phoneLinkButton } from "./telegram-
|
|
28
|
+
import { phoneLinkButton } from "./telegram-lead.js";
|
|
29
29
|
import { telegramButton } from "./contracts/telegram-callback.js";
|
|
30
30
|
/** A flow decision's notice: one per visit, and again (`:ask:<ms>`) when the owner asks the lead to approve it. */
|
|
31
31
|
export const FLOW_DECIDE_KEY = /^flow-decide:([1-9][0-9]{0,14}):([1-9][0-9]{0,9})(?::ask:[0-9]{1,15})?$/;
|
|
@@ -49,8 +49,8 @@ function decisionTerms(waiting) {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* A flow decision as Telegram shows it: the action and where each button takes the card first, then the draft (as
|
|
52
|
-
* the card holds it now, so what is approved is what is read) and why a
|
|
53
|
-
* zone. `said` is the saved notice's body: the words before its instruction are the
|
|
52
|
+
* the card holds it now, so what is approved is what is read) and why a subagent handed it over, then the flow and
|
|
53
|
+
* zone. `said` is the saved notice's body: the words before its instruction are the subagent's handoff.
|
|
54
54
|
*/
|
|
55
55
|
export function flowDecisionText(waiting, said, head = `Approve “${waiting.card.title}”`) {
|
|
56
56
|
const at = said === null ? -1 : said.indexOf(`${waiting.stage.title}: approve`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PhoneLink } from "./chat-channel.js";
|
|
2
|
-
export { channelRepos as telegramConversationRepos,
|
|
3
|
-
import { type
|
|
4
|
-
import type {
|
|
2
|
+
export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps, type PhoneLink, type ResolvedLead, type ParitySupport } from "./chat-channel.js";
|
|
3
|
+
import { type LeadAsk, type Store, type TelegramBinding } from "./store.js";
|
|
4
|
+
import type { SubscriptionLeadRunner } from "./subscription-chat.js";
|
|
5
5
|
import type { TelegramTransport } from "./telegram.js";
|
|
6
6
|
import { type TelegramCallbackButton, type TelegramUpdate } from "./contracts/telegram-callback.js";
|
|
7
7
|
/** How long one claimed turn may go without a heartbeat before another poller may take it over. */
|
|
@@ -18,7 +18,7 @@ export type TelegramConversationOptions = {
|
|
|
18
18
|
/** Where evidence lives — the same root the console and CLI read results from. */
|
|
19
19
|
evidenceRoot: string;
|
|
20
20
|
/** Injected by tests; production invokes the isolated local harness. */
|
|
21
|
-
subscriptionRunner?:
|
|
21
|
+
subscriptionRunner?: SubscriptionLeadRunner;
|
|
22
22
|
/**
|
|
23
23
|
* The https origin a phone link may open, read again on EVERY call (a
|
|
24
24
|
* card is linked immediately before it is sent or edited, never from a
|
|
@@ -71,7 +71,7 @@ export declare function phoneLinkButton(origin: string | null, link: PhoneLink |
|
|
|
71
71
|
/** One inline button: an opaque callback token (a real in-chat act) or a url (navigation, never authority). */
|
|
72
72
|
/** The buttons under the lead's question: options two to a row, then "Something else". Each names the turn and the
|
|
73
73
|
* option's place, nothing more; a tap re-proves that the question is still open and is the tapper's own. */
|
|
74
|
-
export declare function askKeyboard(ask:
|
|
74
|
+
export declare function askKeyboard(ask: LeadAsk): CallbackKeyboard;
|
|
75
75
|
export type InlineButton = TelegramCallbackButton | {
|
|
76
76
|
text: string;
|
|
77
77
|
url: string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
|
|
2
|
-
import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos,
|
|
3
|
-
export { channelRepos as telegramConversationRepos,
|
|
2
|
+
import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
|
|
3
|
+
export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
|
|
4
4
|
/**
|
|
5
5
|
* The paired phone as one more way to use the same assistant.
|
|
6
6
|
*
|
|
7
|
-
* Transport only. An ordinary private Telegram message becomes a
|
|
8
|
-
* through `
|
|
7
|
+
* Transport only. An ordinary private Telegram message becomes a lead turn
|
|
8
|
+
* through `runLeadTurn` — the same saved thread, proposal rows, confirm
|
|
9
9
|
* doors and result actions the console and `toolroll chat` use —
|
|
10
10
|
* and every act is still a card the operator confirms. This module owns
|
|
11
11
|
* what is specific to the wire: proving the pairing (chat AND immutable
|
|
@@ -23,10 +23,10 @@ export { channelRepos as telegramConversationRepos, resolveChannelMate as resolv
|
|
|
23
23
|
* the console and CLI state), and the door re-proves everything again.
|
|
24
24
|
*/
|
|
25
25
|
import { createHash, randomBytes } from "node:crypto";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { LEAD_MESSAGE_MAX_CHARS, leadFailureText, runLeadTurn } from "./lead.js";
|
|
27
|
+
import { confirmLeadProposal, dismissLeadProposal } from "./lead-doors.js";
|
|
28
28
|
import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
|
|
29
|
-
import {
|
|
29
|
+
import { LEAD_ASK_OTHER } from "./store.js";
|
|
30
30
|
import { phoneText } from "./telegram-status.js";
|
|
31
31
|
import { shapeReplyParts, telegramReply } from "./reply-shape.js";
|
|
32
32
|
import { WARM_EMOJI, warmTurn } from "./chat-warmth.js";
|
|
@@ -112,7 +112,7 @@ export function askKeyboard(ask) {
|
|
|
112
112
|
const rows = [];
|
|
113
113
|
for (let at = 0; at < ask.options.length; at += 2)
|
|
114
114
|
rows.push(ask.options.slice(at, at + 2).map((label, offset) => telegramButton(label.slice(0, 60), `ask:${ask.turn}:${at + offset}`)));
|
|
115
|
-
rows.push([telegramButton(
|
|
115
|
+
rows.push([telegramButton(LEAD_ASK_OTHER, `ask:${ask.turn}:x`)]);
|
|
116
116
|
return rows;
|
|
117
117
|
}
|
|
118
118
|
/** Mint the card's tokens before the send, so a tap can never name a token that does not exist. */
|
|
@@ -151,7 +151,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
|
|
|
151
151
|
ack("that button is stale — send /status to see what still waits");
|
|
152
152
|
return { effects, confirmed: false, ignored: true };
|
|
153
153
|
}
|
|
154
|
-
const proposal = store.
|
|
154
|
+
const proposal = store.getLeadProposal(action.proposal);
|
|
155
155
|
if (proposal === null) {
|
|
156
156
|
store.consumeTelegramProposalAction(token, now);
|
|
157
157
|
ack("that card no longer exists");
|
|
@@ -209,10 +209,10 @@ export function applyProposalTap(store, binding, token, message, repos, options,
|
|
|
209
209
|
ack("that button was already used");
|
|
210
210
|
return { effects, confirmed: false, ignored: true };
|
|
211
211
|
}
|
|
212
|
-
const done =
|
|
212
|
+
const done = dismissLeadProposal(store, who, proposal.id, now);
|
|
213
213
|
store.consumeTelegramProposalActions(proposal.id, now);
|
|
214
214
|
ack(done ? "dismissed" : "that proposal was already acted on");
|
|
215
|
-
edit(done ? "Dismissed." : proposalOutcomeText(store.
|
|
215
|
+
edit(done ? "Dismissed." : proposalOutcomeText(store.getLeadProposal(proposal.id) ?? proposal));
|
|
216
216
|
return { effects, confirmed: false, ignored: !done };
|
|
217
217
|
}
|
|
218
218
|
if (action.phase === "cancel") {
|
|
@@ -273,7 +273,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
|
|
|
273
273
|
ack(action.phase === "yes" ? "that confirmation expired — start again from Confirm" : "that button was already used");
|
|
274
274
|
return { effects, confirmed: false, ignored: true };
|
|
275
275
|
}
|
|
276
|
-
const outcome =
|
|
276
|
+
const outcome = confirmLeadProposal(store, who, proposal.id, now, {
|
|
277
277
|
via: "telegram",
|
|
278
278
|
evidenceRoot: options.evidenceRoot,
|
|
279
279
|
confirm: action.phase === "yes",
|
|
@@ -405,7 +405,7 @@ async function runTelegramConversation(row, args) {
|
|
|
405
405
|
const deliver = async (outcomeWord) => {
|
|
406
406
|
// The row as it is NOW: the session was bound after this claim read it.
|
|
407
407
|
const bound = store.getTelegramConversation(row.id)?.session ?? null;
|
|
408
|
-
const session = bound === null ? null : store.
|
|
408
|
+
const session = bound === null ? null : store.getLeadSession(bound);
|
|
409
409
|
if (session === null) {
|
|
410
410
|
await notify("This conversation was restarted before my reply went out, so it wasn't sent. Nothing was changed. Send your message again.");
|
|
411
411
|
finish({ state: "failed", outcome: "unsent:no-session" });
|
|
@@ -421,7 +421,7 @@ async function runTelegramConversation(row, args) {
|
|
|
421
421
|
continue;
|
|
422
422
|
const now = clock();
|
|
423
423
|
if (part.kind === "card") {
|
|
424
|
-
const proposal = part.proposal === null ? null : store.
|
|
424
|
+
const proposal = part.proposal === null ? null : store.getLeadProposal(part.proposal);
|
|
425
425
|
if (proposal === null || proposal.state !== "pending") {
|
|
426
426
|
// Confirmed or dismissed from the console or the terminal before the card went out: moot, not lost.
|
|
427
427
|
if (!store.dropTelegramConversationPart(row.id, part.ordinal, owner, proposal === null ? "the proposal is gone" : `the proposal was already ${proposal.state}`, now))
|
|
@@ -523,7 +523,7 @@ async function runTelegramConversation(row, args) {
|
|
|
523
523
|
// A card's link is minted NOW, from the persisted proposal and the
|
|
524
524
|
// origin configured at this moment: a retry after the setting changed
|
|
525
525
|
// or went away carries the current truth, never a stored URL.
|
|
526
|
-
const proposal = part.kind === "card" && part.proposal !== null ? store.
|
|
526
|
+
const proposal = part.kind === "card" && part.proposal !== null ? store.getLeadProposal(part.proposal) : null;
|
|
527
527
|
const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
|
|
528
528
|
const wanted = proposal === null ? null : proposalLink(store, proposal, repos);
|
|
529
529
|
const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
|
|
@@ -597,11 +597,11 @@ async function runTelegramConversation(row, args) {
|
|
|
597
597
|
const now = clock();
|
|
598
598
|
const parts = shapeReplyParts(reply, PART_CAP, { asked: row.text, appOrigin: options.phoneOrigin?.() ?? null }, shaped => telegramReply(shaped).text.length).map((text, index) => ({ kind: "reply", text, replyTo: index === 0 ? row.messageId : null }));
|
|
599
599
|
// The lead's question to its owner: one tap per option, then "Something else".
|
|
600
|
-
const ask = store.
|
|
600
|
+
const ask = store.leadAsk(turn);
|
|
601
601
|
if (ask !== null)
|
|
602
602
|
parts.push({ kind: "reply", text: phoneText(ask.question, 1_000), keyboard: askKeyboard(ask) });
|
|
603
603
|
// Only a turn that answered may have its selection sent: a failed or revoked turn's rows were deleted with its drafts, and the state is read again here.
|
|
604
|
-
const selected = store.
|
|
604
|
+
const selected = store.getLeadTurn(turn)?.state === "answered" ? store.listLeadTurnEvidence(turn) : [];
|
|
605
605
|
for (const image of selected)
|
|
606
606
|
parts.push({ kind: "image", text: image.caption, taskId: image.taskId, run: image.run, artifact: image.artifact, sha256: image.sha256 });
|
|
607
607
|
for (const proposal of proposals) {
|
|
@@ -625,8 +625,8 @@ async function runTelegramConversation(row, args) {
|
|
|
625
625
|
const recover = async (session, turnId, repos) => {
|
|
626
626
|
// A turn that died past its deadline is swept to failed first, so a
|
|
627
627
|
// restart reports the truth instead of waiting on a ghost.
|
|
628
|
-
store.
|
|
629
|
-
const turn = store.
|
|
628
|
+
store.sweepStaleLeadTurns(clock());
|
|
629
|
+
const turn = store.getLeadTurn(turnId);
|
|
630
630
|
if (turn === null) {
|
|
631
631
|
finish({ state: "failed", outcome: "replayed:missing" });
|
|
632
632
|
report.refused++;
|
|
@@ -638,13 +638,13 @@ async function runTelegramConversation(row, args) {
|
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
if (turn.state !== "answered") {
|
|
641
|
-
await notify(
|
|
641
|
+
await notify(leadFailureText(turn.failureReason));
|
|
642
642
|
finish({ state: "failed", outcome: `replayed:${turn.failureReason ?? "failed"}` });
|
|
643
643
|
report.refused++;
|
|
644
644
|
return;
|
|
645
645
|
}
|
|
646
|
-
const reply = store.
|
|
647
|
-
const proposals = store.
|
|
646
|
+
const reply = store.listLeadMessages(turn.thread, 200).find(one => one.turn === turn.id && one.role === "assistant")?.text ?? "(the reply text is no longer in the thread)";
|
|
647
|
+
const proposals = store.listLeadProposals(turn.thread, ["pending"]).filter(one => one.turn === turn.id);
|
|
648
648
|
if (!plan(session, turnId, reply, proposals, repos) && store.listTelegramConversationParts(row.id).length === 0)
|
|
649
649
|
return;
|
|
650
650
|
await sendOrHandOff("replayed");
|
|
@@ -676,14 +676,14 @@ async function runTelegramConversation(row, args) {
|
|
|
676
676
|
// 4. A session bound before an earlier dispatch: the receipt lives there,
|
|
677
677
|
// whatever session is live today.
|
|
678
678
|
if (row.session !== null) {
|
|
679
|
-
const receipt = store.
|
|
679
|
+
const receipt = store.leadRequestReceipt(row.session, row.request);
|
|
680
680
|
if (receipt !== null) {
|
|
681
681
|
await recover(row.session, receipt.turn, repos);
|
|
682
682
|
return;
|
|
683
683
|
}
|
|
684
684
|
}
|
|
685
685
|
// 5. The session and thread — shared with the console and the CLI.
|
|
686
|
-
const resolved =
|
|
686
|
+
const resolved = resolveTelegramLead(store, binding, repos, clock());
|
|
687
687
|
if (!resolved.ok && resolved.reason === "busy") {
|
|
688
688
|
await requeue("busy");
|
|
689
689
|
return;
|
|
@@ -722,7 +722,7 @@ async function runTelegramConversation(row, args) {
|
|
|
722
722
|
});
|
|
723
723
|
let outcome;
|
|
724
724
|
try {
|
|
725
|
-
outcome = await
|
|
725
|
+
outcome = await runLeadTurn({
|
|
726
726
|
store, who, session, thread, config, key: null, message: row.text, requestId: row.request,
|
|
727
727
|
...(row.context === null ? {} : { context: row.context }),
|
|
728
728
|
...(options.subscriptionRunner === undefined ? {} : { subscriptionRunner: options.subscriptionRunner }),
|
|
@@ -733,7 +733,7 @@ async function runTelegramConversation(row, args) {
|
|
|
733
733
|
clearInterval(heartbeat);
|
|
734
734
|
warm.stop();
|
|
735
735
|
}
|
|
736
|
-
const receipt = store.
|
|
736
|
+
const receipt = store.leadRequestReceipt(session.id, row.request);
|
|
737
737
|
if (receipt !== null)
|
|
738
738
|
store.bindTelegramConversationTurn(row.id, owner, session.id, receipt.turn);
|
|
739
739
|
if (!outcome.ok && "refused" in outcome) {
|
|
@@ -761,7 +761,7 @@ async function runTelegramConversation(row, args) {
|
|
|
761
761
|
if (row.taskId !== null)
|
|
762
762
|
mirrorToTaskChat(store, who, row.taskId, "Telegram", row.text, outcome.reply, clock());
|
|
763
763
|
// 7. The reply and its cards: durable first, then sent.
|
|
764
|
-
const proposals = store.
|
|
764
|
+
const proposals = store.listLeadProposals(thread.id, ["pending"]).filter(one => one.turn === outcome.turn);
|
|
765
765
|
if (!plan(session.id, outcome.turn, outcome.reply, proposals, who.repos) && store.listTelegramConversationParts(row.id).length === 0)
|
|
766
766
|
return;
|
|
767
767
|
await sendOrHandOff("answered");
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* the settings page; this card only mints or revokes the signed-in person's
|
|
4
4
|
* pairing, never anyone else's. */
|
|
5
5
|
import type { Store } from "./store.js";
|
|
6
|
+
import { type Html } from "./html.js";
|
|
6
7
|
/** How long a written reply may wait to be sent before it shows here with a Retry. */
|
|
7
8
|
export declare const UNSENT_REPLY_MS: number;
|
|
8
|
-
export declare function telegramSettingsHtml(store: Store, botId: string | null, who: string,
|
|
9
|
+
export declare function telegramSettingsHtml(store: Store, botId: string | null, who: string, options?: {
|
|
9
10
|
code?: string;
|
|
10
11
|
problem?: string;
|
|
11
12
|
now?: Date;
|
|
12
|
-
}):
|
|
13
|
+
}): Html;
|
|
@@ -1,47 +1,36 @@
|
|
|
1
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
1
2
|
/** How long a written reply may wait to be sent before it shows here with a Retry. */
|
|
2
3
|
export const UNSENT_REPLY_MS = 2 * 60_000;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.replaceAll('"', """)
|
|
8
|
-
.replaceAll("'", "'");
|
|
9
|
-
export function telegramSettingsHtml(store, botId, who, csrf, options = {}) {
|
|
10
|
-
const hidden = `<input type="hidden" name="csrf" value="${escape(csrf)}">`;
|
|
11
|
-
const password = '<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>';
|
|
12
|
-
const post = (action, content) => `<form method="post" action="/settings/telegram/${action}" class="card">${hidden}${content}</form>`;
|
|
13
|
-
let content = "";
|
|
4
|
+
export function telegramSettingsHtml(store, botId, who, options = {}) {
|
|
5
|
+
const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
|
|
6
|
+
const post = (action, content) => postForm(`/settings/telegram/${action}`, content, { attrs: { class: "card" } });
|
|
7
|
+
let content;
|
|
14
8
|
if (botId === null) {
|
|
15
|
-
content =
|
|
9
|
+
content = html `<p>No Telegram bot is connected yet. An installation approver saves the bot token on the <a href="/settings">settings page</a> first.</p>`;
|
|
16
10
|
}
|
|
17
11
|
else {
|
|
18
12
|
const bindings = store.liveTelegramBindings(botId);
|
|
19
13
|
const mine = bindings.filter(binding => binding.approver === who);
|
|
20
14
|
const others = bindings.length - mine.length;
|
|
21
|
-
|
|
15
|
+
const parts = [html `<p><strong>${mine.length > 0 ? "Your phone is paired." : "Your phone is not paired."}</strong> ${others === 0 ? "No teammates are paired yet." : `${others} teammate${others === 1 ? " is" : "s are"} paired.`}</p>`];
|
|
22
16
|
// A reply the assistant wrote that has not reached this person's phone for two minutes.
|
|
23
17
|
const unsent = store.unsentTelegramReplies(botId, who, new Date((options.now ?? new Date()).getTime() - UNSENT_REPLY_MS));
|
|
24
18
|
if (unsent.length > 0) {
|
|
25
19
|
const oldest = unsent[0];
|
|
26
|
-
|
|
27
|
-
`<div class="card" role="status" data-unsent-replies><p><strong>${unsent.length === 1 ? "A reply hasn't reached your phone" : `${unsent.length} replies haven't reached your phone`}</strong></p>` +
|
|
28
|
-
`<p class="meta">Trying since ${escape(oldest.since.slice(11, 16))} UTC${oldest.error === null ? "" : `: ${escape(oldest.error)}`}</p>` +
|
|
29
|
-
`<form method="post" action="/settings/telegram/retry">${hidden}<button type="submit">Retry</button></form></div>`;
|
|
20
|
+
parts.push(html `<div class="card" role="status" data-unsent-replies><p><strong>${unsent.length === 1 ? html `A reply hasn't reached your phone` : html `${unsent.length} replies haven't reached your phone`}</strong></p><p class="meta">Trying since ${oldest.since.slice(11, 16)} UTC${oldest.error === null ? "" : `: ${oldest.error}`}</p>${postForm("/settings/telegram/retry", html `<button type="submit">Retry</button>`)}</div>`);
|
|
30
21
|
}
|
|
31
22
|
if (options.code !== undefined) {
|
|
32
|
-
|
|
33
|
-
"<h2>Pair your phone</h2><p>Send this to the bot in a private Telegram chat within 10 minutes:</p>" +
|
|
34
|
-
`<pre class="pairing-code">/pair ${escape(options.code)}</pre>` +
|
|
35
|
-
'<p class="meta">The code works once. That chat then answers as you, so treat it like your sign-in.</p>';
|
|
23
|
+
parts.push(html `<h2>Pair your phone</h2><p>Send this to the bot in a private Telegram chat within 10 minutes:</p><pre class="pairing-code">/pair ${options.code}</pre><p class="meta">The code works once. That chat then answers as you, so treat it like your sign-in.</p>`);
|
|
36
24
|
}
|
|
37
25
|
else if (mine.length === 0) {
|
|
38
|
-
|
|
26
|
+
parts.push(post("pair", html `${password}<button type="submit">Pair my phone</button>`));
|
|
39
27
|
}
|
|
40
28
|
else {
|
|
41
|
-
|
|
29
|
+
parts.push(post("unpair", html `${password}<p class="meta">This phone will lose access. Its existing Telegram buttons will stop working.</p><button type="submit" class="danger">Unpair my phone</button>`));
|
|
42
30
|
}
|
|
31
|
+
content = joinHtml(parts);
|
|
43
32
|
}
|
|
44
33
|
if (options.problem !== undefined)
|
|
45
|
-
content = `<p role="status">${
|
|
46
|
-
return `<h1>Telegram</h1>${content}<p class="meta">Each teammate pairs their own private chat. To connect a group, a conversation manager sends <code>/team</code> there and chooses a conversation. Everyone in the group can read its replies.</p>`;
|
|
34
|
+
content = html `<p role="status">${options.problem}</p>${content}`;
|
|
35
|
+
return html `<h1>Telegram</h1>${content}<p class="meta">Each teammate pairs their own private chat. To connect a group, a conversation manager sends <code>/team</code> there and chooses a conversation. Everyone in the group can read its replies.</p>`;
|
|
47
36
|
}
|
package/dist/telegram-status.js
CHANGED
|
@@ -39,7 +39,7 @@ export const PHONE_HELP = [
|
|
|
39
39
|
"/task <name or number> — the same, by a word from its name",
|
|
40
40
|
"/lead — back to talking about everything",
|
|
41
41
|
"/status — what's happening across your projects",
|
|
42
|
-
|
|
42
|
+
// D5: /team (the central team service's conversations) is deprecated: it still answers this release, unlisted.
|
|
43
43
|
"/help — this message",
|
|
44
44
|
"",
|
|
45
45
|
"To answer an agent's question, tap its buttons; reply to the question to add a note. Approvals that need your password, cancelling and publishing finish in the Toolroll console, and a console button only takes you there. The computer must be awake and connected to reply.",
|
package/dist/telegram-team.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* per-chat cursor read on every bridge cycle. No model runs here.
|
|
8
8
|
*/
|
|
9
9
|
import type { Store } from "./store.js";
|
|
10
|
-
import { type InlineButton } from "./telegram-
|
|
10
|
+
import { type InlineButton } from "./telegram-lead.js";
|
|
11
11
|
import type { TelegramTransport } from "./telegram.js";
|
|
12
12
|
import type { TelegramMessage } from "./contracts/telegram-callback.js";
|
|
13
13
|
export type TeamCommand = {
|