toolroll 0.9.55 → 0.9.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +24 -8
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.js +2 -2
- package/dist/browser/workspace.js +5 -5
- package/dist/browser-workspace.d.ts +12 -12
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +27 -34
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -7
- package/dist/contracts/chat-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/console-api.d.ts +6 -6
- package/dist/contracts/console-api.js +7 -7
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- package/dist/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +0 -1
- package/dist/export-ui.js +1 -1
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.js +20 -20
- package/dist/flows.js +19 -19
- package/dist/guides.js +4 -3
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.js +5 -5
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +2 -2
- package/dist/lead-context.js +9 -9
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/limits-ui.d.ts +1 -1
- package/dist/limits-ui.js +1 -1
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/notification-rows.d.ts +2 -2
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +35 -34
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.js +1 -1
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- package/dist/recipe-ui.js +1 -1
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.js +40 -45
- package/dist/server/chat.d.ts +4 -4
- package/dist/server/chat.js +97 -97
- package/dist/server/chrome.js +3 -3
- package/dist/server/flows.js +69 -62
- package/dist/server/guards.js +3 -3
- package/dist/server/http.d.ts +3 -3
- package/dist/server/http.js +1 -1
- package/dist/server/live.js +6 -6
- package/dist/server/people-tokens.js +1 -1
- package/dist/server/remote.js +12 -10
- package/dist/server/render-chat.d.ts +30 -30
- package/dist/server/render-chat.js +33 -33
- package/dist/server/render-pages.js +2 -2
- package/dist/server/render-settings.d.ts +7 -0
- package/dist/server/render-settings.js +3 -0
- package/dist/server/route-table.js +9 -7
- package/dist/server/runtime.d.ts +13 -13
- package/dist/server/settings.js +14 -13
- package/dist/server/tasks.js +6 -5
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/spend-ui.d.ts +3 -3
- package/dist/spend-ui.js +9 -9
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/{teammates-ui.js → subagents-ui.js} +45 -45
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +7 -31
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/teams-chat.js +6 -6
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-status.js +1 -1
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/package.json +1 -2
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -21
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Subscription-backed transport for the
|
|
2
|
+
* Subscription-backed transport for the lead.
|
|
3
3
|
*
|
|
4
4
|
* The direct API transport owns dollar reservations and native function
|
|
5
5
|
* calls. A cached Codex or Claude login has neither an API key nor a
|
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
* temporary directory and asks for the same tool-call envelope as strict
|
|
8
8
|
* structured JSON. The harness receives no repository path, no project
|
|
9
9
|
* instructions, no MCP servers, and no executable tool surface. Tool calls
|
|
10
|
-
* are still interpreted and executed by
|
|
10
|
+
* are still interpreted and executed by lead.ts, where every act remains a
|
|
11
11
|
* proposal that needs a human confirmation card.
|
|
12
12
|
*/
|
|
13
13
|
import { Buffer } from "node:buffer";
|
|
14
14
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
-
import {
|
|
17
|
+
import { LEAD_MAX_CALLS_PER_STEP, LEAD_STEP_TEXT_CAP_BYTES, LEAD_TOOL_CALL_CAP_BYTES, LEAD_TOOL_CALL_ID_CAP_BYTES, strictJsonParse, tokenCount, } from "./converse.js";
|
|
18
18
|
import { run } from "./exec.js";
|
|
19
|
-
import { claudeStreamReader } from "./
|
|
19
|
+
import { claudeStreamReader } from "./lead-progress.js";
|
|
20
20
|
import { ALL_CREDENTIAL_ENV } from "./provider.js";
|
|
21
21
|
import { claudeLimitsOf, noteLimits } from "./provider-limits.js";
|
|
22
22
|
import { claudeBillingFrom } from "./spend.js";
|
|
@@ -25,18 +25,18 @@ function outputSchema(tools) {
|
|
|
25
25
|
return {
|
|
26
26
|
type: "object",
|
|
27
27
|
properties: {
|
|
28
|
-
text: { type: "string", maxLength:
|
|
28
|
+
text: { type: "string", maxLength: LEAD_STEP_TEXT_CAP_BYTES },
|
|
29
29
|
// With no tools offered there is nothing to name: an empty enum is not
|
|
30
30
|
// a valid schema, so the calls array is simply required to be empty.
|
|
31
31
|
calls: tools.length === 0 ? { type: "array", maxItems: 0 } : {
|
|
32
32
|
type: "array",
|
|
33
|
-
maxItems:
|
|
33
|
+
maxItems: LEAD_MAX_CALLS_PER_STEP,
|
|
34
34
|
items: {
|
|
35
35
|
type: "object",
|
|
36
36
|
properties: {
|
|
37
|
-
id: { type: "string", minLength: 1, maxLength:
|
|
37
|
+
id: { type: "string", minLength: 1, maxLength: LEAD_TOOL_CALL_ID_CAP_BYTES },
|
|
38
38
|
name: { type: "string", enum: tools.map(tool => tool.name) },
|
|
39
|
-
argumentsJson: { type: "string", maxLength:
|
|
39
|
+
argumentsJson: { type: "string", maxLength: LEAD_TOOL_CALL_CAP_BYTES },
|
|
40
40
|
},
|
|
41
41
|
required: ["id", "name", "argumentsJson"],
|
|
42
42
|
additionalProperties: false,
|
|
@@ -47,8 +47,8 @@ function outputSchema(tools) {
|
|
|
47
47
|
additionalProperties: false,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
/** The model-visible request. Tool results are already redacted by
|
|
51
|
-
export function
|
|
50
|
+
/** The model-visible request. Tool results are already redacted by leadView. */
|
|
51
|
+
export function composeSubscriptionLeadPrompt(request) {
|
|
52
52
|
return [
|
|
53
53
|
request.system,
|
|
54
54
|
"SUBSCRIPTION HARNESS PROTOCOL:",
|
|
@@ -68,7 +68,7 @@ function exactKeys(value, keys) {
|
|
|
68
68
|
return actual.length === keys.length && [...keys].sort().every((key, index) => actual[index] === key);
|
|
69
69
|
}
|
|
70
70
|
/** Strictly turn the harness's structured output into the provider-neutral answer. */
|
|
71
|
-
export function
|
|
71
|
+
export function parseSubscriptionLeadAnswer(text, usage = {}) {
|
|
72
72
|
const parsed = strictJsonParse(Buffer.from(text, "utf8"), RESPONSE_CAP_BYTES, 8);
|
|
73
73
|
if (!parsed.ok || typeof parsed.value !== "object" || parsed.value === null || Array.isArray(parsed.value)) {
|
|
74
74
|
return { ok: false, problem: parsed.ok ? "not-an-object" : parsed.problem };
|
|
@@ -77,7 +77,7 @@ export function parseSubscriptionMateAnswer(text, usage = {}) {
|
|
|
77
77
|
if (!exactKeys(body, ["text", "calls"]) || typeof body["text"] !== "string" || !Array.isArray(body["calls"])) {
|
|
78
78
|
return { ok: false, problem: "wrong-shape" };
|
|
79
79
|
}
|
|
80
|
-
if (Buffer.byteLength(body["text"], "utf8") >
|
|
80
|
+
if (Buffer.byteLength(body["text"], "utf8") > LEAD_STEP_TEXT_CAP_BYTES || body["calls"].length > LEAD_MAX_CALLS_PER_STEP) {
|
|
81
81
|
return { ok: false, problem: "over-cap" };
|
|
82
82
|
}
|
|
83
83
|
const calls = [];
|
|
@@ -87,20 +87,20 @@ export function parseSubscriptionMateAnswer(text, usage = {}) {
|
|
|
87
87
|
const call = raw;
|
|
88
88
|
if (!exactKeys(call, ["id", "name", "argumentsJson"]))
|
|
89
89
|
return { ok: false, problem: "bad-tool-call" };
|
|
90
|
-
if (typeof call["id"] !== "string" || call["id"] === "" || Buffer.byteLength(call["id"], "utf8") >
|
|
90
|
+
if (typeof call["id"] !== "string" || call["id"] === "" || Buffer.byteLength(call["id"], "utf8") > LEAD_TOOL_CALL_ID_CAP_BYTES) {
|
|
91
91
|
return { ok: false, problem: "bad-tool-call" };
|
|
92
92
|
}
|
|
93
93
|
if (typeof call["name"] !== "string" || call["name"] === "" || call["name"].length > 64 || typeof call["argumentsJson"] !== "string") {
|
|
94
94
|
return { ok: false, problem: "bad-tool-call" };
|
|
95
95
|
}
|
|
96
96
|
const argumentsBytes = Buffer.from(call["argumentsJson"], "utf8");
|
|
97
|
-
if (argumentsBytes.byteLength >
|
|
97
|
+
if (argumentsBytes.byteLength > LEAD_TOOL_CALL_CAP_BYTES)
|
|
98
98
|
return { ok: false, problem: "bad-tool-call" };
|
|
99
|
-
const args = strictJsonParse(argumentsBytes,
|
|
99
|
+
const args = strictJsonParse(argumentsBytes, LEAD_TOOL_CALL_CAP_BYTES, 6);
|
|
100
100
|
if (!args.ok || typeof args.value !== "object" || args.value === null || Array.isArray(args.value))
|
|
101
101
|
return { ok: false, problem: "bad-tool-call" };
|
|
102
102
|
const normalized = { id: call["id"], name: call["name"], args: args.value };
|
|
103
|
-
if (Buffer.byteLength(JSON.stringify(normalized), "utf8") >
|
|
103
|
+
if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > LEAD_TOOL_CALL_CAP_BYTES)
|
|
104
104
|
return { ok: false, problem: "bad-tool-call" };
|
|
105
105
|
calls.push(normalized);
|
|
106
106
|
}
|
|
@@ -186,10 +186,10 @@ function lastResultLine(stdout) {
|
|
|
186
186
|
return "";
|
|
187
187
|
}
|
|
188
188
|
/** Production runner; injectable so tests never consume a subscription turn. */
|
|
189
|
-
export async function
|
|
189
|
+
export async function performSubscriptionLeadRequest(request, runner = run) {
|
|
190
190
|
const dir = mkdtempSync(join(tmpdir(), "standing-orders-mate-"));
|
|
191
191
|
try {
|
|
192
|
-
const prompt =
|
|
192
|
+
const prompt = composeSubscriptionLeadPrompt(request);
|
|
193
193
|
const streaming = request.provider !== "codex-subscription" && request.onText !== undefined;
|
|
194
194
|
const schema = request.outputSchema ?? outputSchema(request.tools);
|
|
195
195
|
let command;
|
|
@@ -274,7 +274,7 @@ export async function performSubscriptionMateRequest(request, runner = run) {
|
|
|
274
274
|
if (request.outputSchema !== undefined) {
|
|
275
275
|
return { ok: true, answer: { text: output.text, calls: [], tokensIn: output.tokensIn, tokensOut: output.tokensOut, reportedCostMicrousd: null } };
|
|
276
276
|
}
|
|
277
|
-
return
|
|
277
|
+
return parseSubscriptionLeadAnswer(output.text, output);
|
|
278
278
|
}
|
|
279
279
|
finally {
|
|
280
280
|
rmSync(dir, { recursive: true, force: true });
|
package/dist/surface.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declared command guide (arc 5): the agent-facing surface as data,
|
|
3
|
+
* dumped by `contract --commands`. This is DOCUMENTATION with a stable
|
|
4
|
+
* shape, not authority — legacy verbs do not execute from it,
|
|
5
|
+
* and the runtime envelope remains the truth. Its honest limits ride the
|
|
6
|
+
* envelope itself as SURFACE_NOTES, because a source comment is invisible
|
|
7
|
+
* to consumers (arc-5 review, finding 1).
|
|
8
|
+
*
|
|
9
|
+
* What the tests DO hold to the code: every root here is a routed verb
|
|
10
|
+
* and every routed verb appears here; every declared flag lives in the
|
|
11
|
+
* parser's global vocabulary with the declared arity; the subcommand
|
|
12
|
+
* inventories for task/publish/config/approver match the
|
|
13
|
+
* exported action lists their dispatchers consult; keyed mutations
|
|
14
|
+
* declare --key; notableReasons come from DOCUMENTED_REASONS. What they
|
|
15
|
+
* CANNOT prove — that a given flag is read by a given handler — is why
|
|
16
|
+
* SURFACE_NOTES.flags says "intended".
|
|
17
|
+
*/
|
|
1
18
|
import { type CommandFlag, type CommandRow, type RemotePolicy } from './contracts/cli.js';
|
|
2
19
|
/** A guide row and its flags are derived from their schemas in src/contracts/cli.ts. */
|
|
3
20
|
export type { CommandFlag, CommandRow, RemotePolicy };
|
|
4
21
|
/** The guide's limits, stated machine-readably in every dump. */
|
|
5
22
|
export declare const SURFACE_NOTES: {
|
|
6
|
-
readonly authority: "documentation is not permission;
|
|
7
|
-
readonly flags: "
|
|
23
|
+
readonly authority: "documentation is not permission; legacy entries remain advisory";
|
|
24
|
+
readonly flags: "flags are intended per command and may use a global parser vocabulary";
|
|
8
25
|
readonly reasons: "curated, not exhaustive — the runtime `reason` field is the truth; ignore tokens you do not recognize";
|
|
9
26
|
readonly database: "local database commands wait up to 15 seconds for another writer; an exhausted wait returns database-busy and names the selected file";
|
|
10
27
|
};
|
package/dist/surface.js
CHANGED
|
@@ -19,13 +19,11 @@ import { FLOWS_DESCRIPTORS } from "./flows-cli.js";
|
|
|
19
19
|
* CANNOT prove — that a given flag is read by a given handler — is why
|
|
20
20
|
* SURFACE_NOTES.flags says "intended".
|
|
21
21
|
*/
|
|
22
|
-
import { SESSION_DESCRIPTORS } from './session-contract.js';
|
|
23
|
-
import { sessionCliFlags } from './session-cli.js';
|
|
24
22
|
import { commandEntries } from './contracts/cli.js';
|
|
25
23
|
/** The guide's limits, stated machine-readably in every dump. */
|
|
26
24
|
export const SURFACE_NOTES = {
|
|
27
|
-
authority: "documentation is not permission;
|
|
28
|
-
flags: "
|
|
25
|
+
authority: "documentation is not permission; legacy entries remain advisory",
|
|
26
|
+
flags: "flags are intended per command and may use a global parser vocabulary",
|
|
29
27
|
reasons: "curated, not exhaustive — the runtime `reason` field is the truth; ignore tokens you do not recognize",
|
|
30
28
|
database: "local database commands wait up to 15 seconds for another writer; an exhausted wait returns database-busy and names the selected file",
|
|
31
29
|
};
|
|
@@ -66,22 +64,9 @@ const GUIDE_ROWS = [
|
|
|
66
64
|
{ invocation: "graph", synopsis: "report which work graph is already here", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag] },
|
|
67
65
|
{ invocation: "repos", synopsis: "list connected repositories", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag] },
|
|
68
66
|
operator("proposals", "what coordinators proposed over the MCP gateway — list, confirm <id> [--yes], dismiss <id>; confirming runs the plane's own door under your password"),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
operator('lead create', 'create a central lead with --name, --instructions and repeatable --project paths admitted by the server'),
|
|
73
|
-
operator('lead update', 'change --name, --instructions or --status for --lead at --revision; projects remain fixed'),
|
|
74
|
-
operator('lead member', 'set --account membership on --lead with --role, --active true|false and the current --revision'),
|
|
75
|
-
operator('lead transfer', 'move ownership of --task to --lead at --revision; existing execution authority remains unchanged'),
|
|
76
|
-
operatorRead('conversation list', 'list admitted conversations, optionally within --lead; attachment never starts model work'),
|
|
77
|
-
operator('conversation create', 'create a conversation with --lead, --title, --visibility private|team and repeatable --project paths'),
|
|
78
|
-
operatorRead('conversation show', 'read --conversation messages and saved work; no model call or read-marker mutation'),
|
|
79
|
-
operator('conversation member', 'set --account membership on --conversation with --role, --active true|false and the current --revision; lead membership must already permit the role'),
|
|
80
|
-
operator('conversation edit', 'edit your queued --message in --conversation at --revision using --text; running messages cannot be edited'),
|
|
81
|
-
operator('conversation withdraw', 'withdraw your queued --message in --conversation at --revision; an active turn requires stop'),
|
|
82
|
-
operator('conversation read', 'explicitly record your read position at --message within --conversation'),
|
|
83
|
-
operator('conversation follow', 'enable or pause automatic lead summaries with --conversation and --enabled true|false, within the existing paid chat authorization; passive chat --follow only reads updates'),
|
|
84
|
-
operator('conversation stop', 'request stopping the exact --message in --conversation; retain uncertain work until its owner settles it'),
|
|
67
|
+
// D5: the central team service (connect, the central lead verbs, conversation …) and native coding sessions are
|
|
68
|
+
// deprecated: they still run this release, with a warning, but are no longer declared here.
|
|
69
|
+
operator("chat", "talk to your lead: one conversation across every project; --say sends one message; --follow enables crew updates and --no-follow pauses them; proposals keep their approval requirements"),
|
|
85
70
|
{ invocation: "repos add", synopsis: "add a project: a Git repository, in the console at once; the lead adds with its token", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
86
71
|
positionals: [{ name: "path", required: false, meaning: "the repository (default: the one you are in)" }],
|
|
87
72
|
flags: [jsonFlag, { name: "token", takesValue: true, meaning: "the lead token (or TOOLROLL_LEAD_TOKEN)" }],
|
|
@@ -126,12 +111,6 @@ const GUIDE_ROWS = [
|
|
|
126
111
|
{ name: "yes", takesValue: false, meaning: "with --release-gate: lift that one pause once it is proved" }],
|
|
127
112
|
notableReasons: ["usage", "ambiguous", "drift", "timeout", "check-failed", "ci-failed", "tree-mismatch", "conflict", "mixed-runtime", "gate-held", "after-swap"] },
|
|
128
113
|
operator("demo", "a seeded throwaway sandbox"),
|
|
129
|
-
{ invocation: 'session capabilities', synopsis: 'Show executable session schemas and operator authority requirements; no credentials needed', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag] },
|
|
130
|
-
...SESSION_DESCRIPTORS.map(spec => ({
|
|
131
|
-
invocation: `session ${spec.operation}`, synopsis: spec.synopsis, audience: spec.audience, agentMayInvoke: spec.agentMayInvoke,
|
|
132
|
-
mutation: spec.mutation ? 'keyed' : 'none', inputSchema: spec.inputSchema,
|
|
133
|
-
flags: Object.entries(sessionCliFlags(spec)).map(([name, arity]) => ({ name, takesValue: arity === 'value', meaning: name === 'key' ? 'keep the same key when reconciling; inspect after unknown delivery, never blind-retry' : `see session ${spec.operation} --help` })),
|
|
134
|
-
})),
|
|
135
114
|
...KNOWLEDGE_DESCRIPTORS.map(spec => ({ invocation: `knowledge ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text or source file for impact" }] } : {}) })),
|
|
136
115
|
...MEMORY_DESCRIPTORS.map(spec => ({ invocation: `memory ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text, a decision id, or the decision sentence" }] } : {}) })),
|
|
137
116
|
// Flow writes carry an approver's credential: an agent runs them only as the person asked, after showing the preview.
|
|
@@ -144,7 +123,7 @@ const GUIDE_ROWS = [
|
|
|
144
123
|
{ invocation: "integrations", synopsis: "every integration as Connected, Not set up or Broken, with its last success and error, what uses it and what to do; --saved skips new checks; never a secret", audience: "agent", agentMayInvoke: true, mutation: "none",
|
|
145
124
|
flags: [jsonFlag, dbFlag, { name: "saved", takesValue: false, meaning: "show the last checks without checking again" }] },
|
|
146
125
|
{ invocation: "ready", synopsis: "what could be dispatched right now (rows carry reservedFor)", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
147
|
-
{ invocation: "brief", synopsis: "
|
|
126
|
+
{ invocation: "brief", synopsis: "the lead's catch-up from the local database; --history selects the older operational report", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
148
127
|
{ invocation: "gaps", synopsis: "requirement gaps blocking dispatch", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag, repoFlag] },
|
|
149
128
|
{ invocation: "grants", synopsis: "list authority grants", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
150
129
|
{ invocation: "audit", synopsis: "what people did on this server with their API tokens, newest first: when, who, which token, from the CLI (api) or an agent (mcp), the command or tool, the project and the outcome (ok, refused, error); an approver sees everyone in their projects, anyone else only themselves",
|
|
@@ -439,10 +418,7 @@ const GUIDE_ROWS = [
|
|
|
439
418
|
const REMOTE_NO = [
|
|
440
419
|
// Answered by the local CLI, or a client of a central server itself.
|
|
441
420
|
"", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github", "contract", "skills list", "skills get", "skills install",
|
|
442
|
-
"link", "unlink", "update", "release", "demo", "chat",
|
|
443
|
-
"conversation list", "conversation create", "conversation show", "conversation member", "conversation edit", "conversation withdraw",
|
|
444
|
-
"conversation read", "conversation follow", "conversation stop",
|
|
445
|
-
"session capabilities", "session list", "session show", "session changes", "session start", "session send", "session stop", "session resume", "session recover",
|
|
421
|
+
"link", "unlink", "update", "release", "demo", "chat",
|
|
446
422
|
// This machine's checkout, saved profile, credentials and catch-up.
|
|
447
423
|
"project show", "project use", "brief", "lead token", "lead say", "health",
|
|
448
424
|
"assignment show", "assignment updates", "assignment claim", "assignment check", "assignment brief", "assignment inbox", "assignment ack",
|
package/dist/team-cli.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type TeamCliOptions = {
|
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
sleep?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
12
12
|
};
|
|
13
|
+
/** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
|
|
13
14
|
export declare const TEAM_CLI_ACTIONS: readonly ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
|
|
14
15
|
export declare class UsageError extends Error {
|
|
15
16
|
}
|
package/dist/team-cli.js
CHANGED
|
@@ -9,6 +9,10 @@ import { sessionServiceOrigin } from './session-cli.js';
|
|
|
9
9
|
import { databasePath } from './store.js';
|
|
10
10
|
import { configBase, namedPath } from './names.js';
|
|
11
11
|
import { parseApiToken } from './api-tokens.js';
|
|
12
|
+
import { deprecationWarning } from './deprecations.js';
|
|
13
|
+
/** The central service's own lead verbs; every other `lead …` (token, say, or none) is the local lead's. */
|
|
14
|
+
const CENTRAL_LEAD_ACTIONS = ['list', 'create', 'update', 'member', 'transfer'];
|
|
15
|
+
/** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
|
|
12
16
|
export const TEAM_CLI_ACTIONS = ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
|
|
13
17
|
const HELP = `toolroll connect <HTTPS-origin> --as <account> --token-stdin
|
|
14
18
|
Pipe your API token (so_…, from Settings → API tokens) or use --token-file <private-file>;
|
|
@@ -335,8 +339,9 @@ function render(reply, listing) {
|
|
|
335
339
|
/** null means preserve the existing local command path. Nothing here opens a database. */
|
|
336
340
|
export async function maybeRunTeamCommand(argv, write, options = {}) {
|
|
337
341
|
const command = argv[0];
|
|
338
|
-
// `lead token`
|
|
339
|
-
|
|
342
|
+
// `lead token` and `lead say` are local: the credential a lead agent acts with on this computer, and its voice. A bare
|
|
343
|
+
// `lead` is the local lead's too ("lead" means only the lead); only the central service's verbs go on from here.
|
|
344
|
+
if (command === 'lead' && (argv[1] === 'token' || argv[1] === 'say' || !argv.some(arg => arg === '--profile' || CENTRAL_LEAD_ACTIONS.includes(arg))))
|
|
340
345
|
return null;
|
|
341
346
|
if (!['connect', 'lead', 'conversation', 'chat', 'brief'].includes(command ?? '') && !argv.includes('--profile'))
|
|
342
347
|
return null;
|
|
@@ -357,6 +362,11 @@ export async function maybeRunTeamCommand(argv, write, options = {}) {
|
|
|
357
362
|
return null;
|
|
358
363
|
}
|
|
359
364
|
const explicit = ['connect', 'lead', 'conversation'].includes(command) || argv.some(arg => ['--profile', '--lead', '--conversation', '--authorize', '--terms-digest', '--request-id'].includes(arg));
|
|
365
|
+
// D5: the central team service is deprecated. It still runs this release; each use says so once, on stderr.
|
|
366
|
+
const verb = command === 'lead' || command === 'conversation' ? `${command} ${argv.slice(1).find(arg => !arg.startsWith('--')) ?? ''}`.trim() : command === 'connect' ? command : `${command} --conversation`;
|
|
367
|
+
const warn = () => stderr(deprecationWarning(verb, 'team'));
|
|
368
|
+
if (explicit)
|
|
369
|
+
warn();
|
|
360
370
|
if (explicit && (argv.includes('--help') || ['lead', 'conversation'].includes(command) && argv.length === 1)) {
|
|
361
371
|
write(HELP);
|
|
362
372
|
return 0;
|
|
@@ -364,6 +374,8 @@ export async function maybeRunTeamCommand(argv, write, options = {}) {
|
|
|
364
374
|
const saved = profiles(options);
|
|
365
375
|
if (!explicit && !saved)
|
|
366
376
|
return null;
|
|
377
|
+
if (!explicit)
|
|
378
|
+
warn();
|
|
367
379
|
const parsed = parse(argv), { flags, action } = parsed;
|
|
368
380
|
if (flags.help || ((command === 'lead' || command === 'conversation') && !action)) {
|
|
369
381
|
write(HELP);
|
package/dist/team-leads.js
CHANGED
|
@@ -185,7 +185,7 @@ export class TeamLeads {
|
|
|
185
185
|
}
|
|
186
186
|
const selected = conversationId ? this.access(actor, conversationId).conversation : conversations[0] ?? null;
|
|
187
187
|
const access = selected ? this.access(actor, selected.id) : null;
|
|
188
|
-
const messages = selected ? this.db.prepare(`SELECT * FROM (SELECT m.*,q.author,q.request_id,q.status,q.revision AS message_revision,q.error FROM
|
|
188
|
+
const messages = selected ? this.db.prepare(`SELECT * FROM (SELECT m.*,q.author,q.request_id,q.status,q.revision AS message_revision,q.error FROM lead_message m LEFT JOIN team_message q ON q.message=m.id WHERE m.thread=? ORDER BY m.id DESC LIMIT 201) ORDER BY id`).all(selected.threadId).map(r => ({ id: Number(r['id']), author: r['role'] === 'assistant' ? access.lead.name : String(r['author'] ?? selected.createdBy), role: String(r['role']), text: String(r['text']), status: (r['status'] ?? 'answered'), revision: Number(r['message_revision'] ?? 1), createdAt: String(r['created_at']), requestId: r['request_id'] == null ? null : String(r['request_id']), turnId: r['turn'] == null ? null : Number(r['turn']), error: r['error'] == null ? null : String(r['error']) })) : [];
|
|
189
189
|
const participants = selected ? this.db.prepare('SELECT account,role,active FROM team_participant WHERE conversation=? ORDER BY account').all(selected.id).map(r => ({ account: String(r['account']), role: String(r['role']), active: Number(r['active']) === 1 })) : [];
|
|
190
190
|
const canManage = account.role === 'approver' && (access ? access.role === 'manager' : leadId ? this.leadAccess(actor, leadId).role === 'manager' : false);
|
|
191
191
|
return { leads, conversations, selected, participants, messages: messages.slice(-200), canManage, canCreateLead: account.role === 'approver' && this.enrolled().some(p => projectAccessAllows(account.projects, p)), canSend: account.role === 'approver' && access !== null && rank(access.role) >= 1 && access.lead.status === 'active', cursor: this.cursor(actor, selected?.id) ?? 0, truncated: messages.length > 200, projects: this.enrolled().filter(p => projectAccessAllows(account.projects, p)), accounts: canManage ? this.store.accountFacts().filter(a => a.revokedAt === null && (selected?.projects ?? []).every(p => projectAccessAllows(a.projects, p))).map(a => a.name) : [] };
|
|
@@ -345,14 +345,14 @@ export class TeamLeads {
|
|
|
345
345
|
return ok('Message already saved.', { messageId: Number(seen['message']), status: String(seen['status']) });
|
|
346
346
|
}
|
|
347
347
|
const revisions = this.memberRevisions(conversationId, actor.name);
|
|
348
|
-
const messageId = this.store.
|
|
348
|
+
const messageId = this.store.appendLeadMessage({ thread: conversation.threadId, turn: null, role: 'operator', text }, now);
|
|
349
349
|
this.db.prepare("INSERT INTO team_message(message,conversation,author,author_generation,lead_member_revision,participant_revision,request_id,payload_hash,status) VALUES(?,?,?,?,?,?,?,?,'queued')").run(messageId, conversationId, actor.name, actor.generation, revisions.lead, revisions.participant, requestId, digest);
|
|
350
350
|
this.event(lead.id, conversationId, 'message-queued', actor.name, now);
|
|
351
351
|
return ok('Message saved.', { messageId, status: 'queued' });
|
|
352
352
|
}
|
|
353
353
|
if (request.operation === 'read') {
|
|
354
354
|
const id = integer(a['messageId'], 'Message');
|
|
355
|
-
if (id > 0 && !this.db.prepare('SELECT 1 FROM
|
|
355
|
+
if (id > 0 && !this.db.prepare('SELECT 1 FROM lead_message WHERE id=? AND thread=?').get(id, conversation.threadId))
|
|
356
356
|
refuse('not-found', 'Message not available.');
|
|
357
357
|
this.db.prepare('INSERT INTO team_read(conversation,account,message) VALUES(?,?,?) ON CONFLICT(conversation,account) DO UPDATE SET message=MAX(team_read.message,excluded.message)').run(conversationId, actor.name, id);
|
|
358
358
|
return ok('Read position saved.');
|
|
@@ -374,9 +374,9 @@ export class TeamLeads {
|
|
|
374
374
|
if (message['status'] === 'running') {
|
|
375
375
|
this.db.prepare('UPDATE team_message SET stop_requested=1 WHERE message=?').run(id);
|
|
376
376
|
if (message['turn_id'] !== null) {
|
|
377
|
-
const turn = this.store.
|
|
377
|
+
const turn = this.store.getLeadTurn(Number(message['turn_id']));
|
|
378
378
|
if (turn)
|
|
379
|
-
this.store.
|
|
379
|
+
this.store.finalizeLeadTurn(turn.id, turn.generation, { state: 'failed', settledMicrousd: turn.reservedMicrousd, unknownSpend: true, tokensIn: 0, tokensOut: 0, failureReason: 'stopped' }, now);
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
else if (message['status'] === 'queued' || message['status'] === 'uncertain')
|
|
@@ -393,7 +393,7 @@ export class TeamLeads {
|
|
|
393
393
|
refuse('conflict', 'This message changed or has started. Send a follow-up instead.');
|
|
394
394
|
if (request.operation === 'edit') {
|
|
395
395
|
const text = messageText(a['text']);
|
|
396
|
-
this.db.prepare('UPDATE
|
|
396
|
+
this.db.prepare('UPDATE lead_message SET text=? WHERE id=?').run(text, id);
|
|
397
397
|
this.db.prepare('UPDATE team_message SET revision=revision+1 WHERE message=?').run(id);
|
|
398
398
|
}
|
|
399
399
|
else
|
|
@@ -404,9 +404,9 @@ export class TeamLeads {
|
|
|
404
404
|
refuse('unsupported', 'This action is not available here.');
|
|
405
405
|
}
|
|
406
406
|
revokeMemberSessions(leadId, conversationId, account, now) {
|
|
407
|
-
this.db.prepare(`UPDATE
|
|
407
|
+
this.db.prepare(`UPDATE lead_session SET ended_at=?,ended_by='membership-changed' WHERE approver=? AND ended_at IS NULL
|
|
408
408
|
AND EXISTS(SELECT 1 FROM team_mate_session ms JOIN team_conversation c ON c.thread=ms.thread
|
|
409
|
-
WHERE ms.session=
|
|
409
|
+
WHERE ms.session=lead_session.id AND c.lead=? AND (? IS NULL OR c.id=?))`).run(now.toISOString(), account, leadId, conversationId, conversationId);
|
|
410
410
|
}
|
|
411
411
|
memberRevisions(conversationId, account) {
|
|
412
412
|
const row = this.db.prepare(`SELECT lm.revision AS lr,p.revision AS pr FROM team_conversation c JOIN team_lead_member lm ON lm.lead=c.lead AND lm.account=? AND lm.active=1 JOIN team_participant p ON p.conversation=c.id AND p.account=lm.account AND p.active=1 WHERE c.id=?`).get(account, conversationId);
|
|
@@ -427,7 +427,7 @@ export class TeamLeads {
|
|
|
427
427
|
claimNext(runner, now = new Date(), eligible) {
|
|
428
428
|
str(runner, 'Runner', 180);
|
|
429
429
|
return this.store.transact(() => {
|
|
430
|
-
const candidates = this.db.prepare(`SELECT q.*,m.text,c.lead,c.thread FROM team_message q JOIN
|
|
430
|
+
const candidates = this.db.prepare(`SELECT q.*,m.text,c.lead,c.thread FROM team_message q JOIN lead_message m ON m.id=q.message JOIN team_conversation c ON c.id=q.conversation JOIN team_lead l ON l.id=c.lead AND l.status='active'
|
|
431
431
|
WHERE q.status='queued' AND NOT EXISTS(SELECT 1 FROM team_message live WHERE live.conversation=q.conversation AND live.status IN ('running','uncertain'))
|
|
432
432
|
AND NOT EXISTS(SELECT 1 FROM team_message earlier WHERE earlier.conversation=q.conversation AND earlier.status='queued' AND earlier.message<q.message)
|
|
433
433
|
AND (SELECT COUNT(*) FROM team_message running JOIN team_conversation other ON other.id=running.conversation WHERE running.status='running' AND other.lead=c.lead)<2
|
|
@@ -453,7 +453,7 @@ export class TeamLeads {
|
|
|
453
453
|
current(claim) { const row = this.claimed(claim); return row !== undefined && Number(row['stop_requested']) === 0 && this.authorStillAllowed(row); }
|
|
454
454
|
bindTurn(claim, turnId) {
|
|
455
455
|
return this.store.transact(() => { if (!this.current(claim))
|
|
456
|
-
return false; const turn = this.store.
|
|
456
|
+
return false; const turn = this.store.getLeadTurn(turnId); if (!turn || turn.thread !== claim.threadId || turn.approver !== claim.actor.name)
|
|
457
457
|
return false; const changed = this.db.prepare('UPDATE team_message SET turn_id=? WHERE message=? AND turn_id IS NULL').run(turnId, claim.messageId); return Number(changed.changes) === 1; });
|
|
458
458
|
}
|
|
459
459
|
finish(claim, result, now = new Date()) {
|
|
@@ -467,7 +467,7 @@ export class TeamLeads {
|
|
|
467
467
|
return false;
|
|
468
468
|
this.db.prepare('UPDATE team_message SET status=?,error=?,turn_id=COALESCE(turn_id,?),revision=revision+1 WHERE message=?').run(status, result.error ?? (!allowed ? 'Access changed while this message was running.' : stopped ? 'Stopped.' : null), result.turnId ?? null, claim.messageId);
|
|
469
469
|
if (allowed && !stopped && result.text)
|
|
470
|
-
this.store.
|
|
470
|
+
this.store.appendLeadMessage({ thread: claim.threadId, turn: result.turnId ?? (row['turn_id'] === null ? null : Number(row['turn_id'])), role: 'assistant', text: result.text }, now);
|
|
471
471
|
this.event(claim.leadId, claim.conversationId, 'message-finished', claim.actor.name, now);
|
|
472
472
|
return true;
|
|
473
473
|
});
|
|
@@ -478,7 +478,7 @@ export class TeamLeads {
|
|
|
478
478
|
const row = this.claimed(claim);
|
|
479
479
|
if (!row || row['turn_id'] !== null || !this.current(claim))
|
|
480
480
|
return false;
|
|
481
|
-
if (this.db.prepare('SELECT turn FROM
|
|
481
|
+
if (this.db.prepare('SELECT turn FROM lead_message WHERE id=?').get(claim.messageId)?.['turn'] !== null)
|
|
482
482
|
return false;
|
|
483
483
|
const changed = this.db.prepare("UPDATE team_message SET status='queued',generation=generation+1,revision=revision+1,runner=NULL,claimed_at=NULL,error=? WHERE message=? AND status='running' AND turn_id IS NULL AND generation=?").run(error, claim.messageId, claim.generation);
|
|
484
484
|
if (Number(changed.changes) !== 1)
|
|
@@ -520,7 +520,7 @@ export class TeamLeads {
|
|
|
520
520
|
return this.store.transact(() => {
|
|
521
521
|
const rows = this.db.prepare("SELECT q.*,c.lead FROM team_message q JOIN team_conversation c ON c.id=q.conversation WHERE runner=? AND status='running'").all(runner);
|
|
522
522
|
for (const row of rows) {
|
|
523
|
-
const turn = row['turn_id'] === null ? null : this.store.
|
|
523
|
+
const turn = row['turn_id'] === null ? null : this.store.getLeadTurn(Number(row['turn_id']));
|
|
524
524
|
const status = turn?.state === 'answered' ? 'answered' : turn?.state === 'failed' && !this.deliveryUncertain(turn.id) ? 'failed' : 'uncertain';
|
|
525
525
|
const error = status === 'uncertain' ? 'Delivery unconfirmed after the previous worker stopped. Inspect activity before continuing.' : status === 'failed' ? savedFailure(turn?.failureReason) : null;
|
|
526
526
|
this.db.prepare('UPDATE team_message SET status=?,generation=generation+1,revision=revision+1,error=? WHERE message=?').run(status, error, Number(row['message']));
|
package/dist/team-runtime.d.ts
CHANGED
|
@@ -2,14 +2,14 @@ import type { Store } from './store.js';
|
|
|
2
2
|
import type { WorkspaceRevision } from './workspace-revision.js';
|
|
3
3
|
import { TeamLeads } from './team-leads.js';
|
|
4
4
|
import { type TeamActor, type TeamExecute } from './team-contract.js';
|
|
5
|
-
import { type
|
|
5
|
+
import { type LeadTurnInput } from './lead.js';
|
|
6
6
|
import { type TeamChatProviderResolver } from './team-chat-authorization.js';
|
|
7
7
|
export type TeamRuntimeOptions = {
|
|
8
8
|
store: Store;
|
|
9
9
|
repos: () => readonly string[];
|
|
10
10
|
evidenceRoot: string;
|
|
11
11
|
provider: TeamChatProviderResolver;
|
|
12
|
-
subscriptionRunner?:
|
|
12
|
+
subscriptionRunner?: LeadTurnInput['subscriptionRunner'];
|
|
13
13
|
fetcher?: typeof fetch;
|
|
14
14
|
clock?: () => Date;
|
|
15
15
|
capacity?: number;
|
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/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/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;
|