toolroll 0.9.55 → 0.9.57
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 +18 -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/assignment-adapters.d.ts +5 -0
- package/dist/assignment.d.ts +24 -3
- package/dist/assignment.js +36 -10
- package/dist/browser/workspace.js +5 -5
- package/dist/browser-workspace.d.ts +12 -12
- package/dist/browser-workspace.js +1 -1
- 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 +30 -37
- 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-voice.js +1 -1
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels.d.ts +3 -0
- package/dist/check-levels.js +5 -0
- 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/dispose.js +0 -5
- 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/lead-status.d.ts +1 -1
- package/dist/lead-status.js +2 -2
- 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 +52 -42
- 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/proof.d.ts +9 -0
- package/dist/proof.js +12 -0
- package/dist/recipe-ui.js +1 -1
- package/dist/release-coverage.d.ts +16 -0
- package/dist/release-coverage.js +92 -0
- 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 +105 -103
- 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-results.d.ts +4 -1
- package/dist/server/render-results.js +23 -9
- 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 +34 -30
- 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 +200 -254
- package/dist/store.js +830 -3152
- 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/task-outcome-cli.js +3 -1
- package/dist/task-status.d.ts +59 -7
- package/dist/task-status.js +132 -16
- 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 +9 -9
- 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.d.ts +11 -4
- package/dist/telegram-status.js +18 -11
- 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 +13 -34
- 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/dist/work-index.d.ts +6 -1
- package/dist/work-index.js +50 -8
- package/dist/workspace-ui.js +2 -1
- 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/task-outcome-cli.js
CHANGED
|
@@ -82,7 +82,9 @@ export async function runTaskOutcomeCommand(action, positional, flags, context)
|
|
|
82
82
|
if (!changed.ok)
|
|
83
83
|
return emit(changed);
|
|
84
84
|
const assignment = 'body' in changed ? changed.body : changed.assignment;
|
|
85
|
-
|
|
85
|
+
// Checks deliberately Off: nothing to say about them here.
|
|
86
|
+
return emit({ ok: true, result: assignment }, [`${current.rootId} · Complete`, `Result: ${current.activeTaskId} · run ${current.receipt.runId} · ${current.receipt.head ?? 'no commit'}`,
|
|
87
|
+
...(current.readiness?.checksOff ? [] : [current.receipt.checks.detail])]);
|
|
86
88
|
}
|
|
87
89
|
const feedback = flags.get('feedback'), runFlag = flags.get('run'), sourceFlag = flags.get('source'), keyFlag = flags.get('key');
|
|
88
90
|
if (typeof feedback !== 'string' || !feedback.trim())
|
package/dist/task-status.d.ts
CHANGED
|
@@ -56,6 +56,24 @@ export type ChecksFact = {
|
|
|
56
56
|
running?: "quick" | "full" | null;
|
|
57
57
|
/** A batch check (batch-checks.ts): waiting for its batch, or how it was checked. */
|
|
58
58
|
batch?: ChecksBatch | null;
|
|
59
|
+
/** Off: the passing full release check whose commit contains this one (release-coverage.ts), read at read time. */
|
|
60
|
+
release?: {
|
|
61
|
+
run: number;
|
|
62
|
+
head: string;
|
|
63
|
+
} | null;
|
|
64
|
+
};
|
|
65
|
+
/** What checks Off leaves for a release: `pending` until a passing full release check contains the commit, then
|
|
66
|
+
* `covered`. `running`: a check is waiting for its batch or running on this commit. */
|
|
67
|
+
export type ReleaseState = "pending" | "covered" | "running";
|
|
68
|
+
export declare const CHECKED_AT_RELEASE = "Checked at release";
|
|
69
|
+
export declare function releaseStateOf(checks: Pick<ChecksFact, "status" | "level" | "release"> | null | undefined): ReleaseState | undefined;
|
|
70
|
+
/** How the project check stands for requirements that rest on it, read now: `covered` once a check passed on this
|
|
71
|
+
* commit (its own, a follow-up, or a release check containing it), `pending` while checks Off wait for a release. */
|
|
72
|
+
export declare function checkBackingOf(checks: Pick<ChecksFact, "status" | "level" | "release" | "running" | "batch"> | null | undefined): ReleaseState | undefined;
|
|
73
|
+
/** What stands between a finished result and Complete (`completionBlockersOf`): each with the exact next step. */
|
|
74
|
+
export type CompletionBlocker = {
|
|
75
|
+
key: "check-failed" | "check-missing" | "criteria" | "high";
|
|
76
|
+
message: string;
|
|
59
77
|
};
|
|
60
78
|
/** `waiting`: the result waits to be checked with others. Otherwise how its check ran: `together` on the
|
|
61
79
|
* temporary batch commit `tested` with `peers`; `split` (its batch failed), `conflict` or `alone` on its own commit. */
|
|
@@ -94,14 +112,22 @@ export type TaskStatusFacts = {
|
|
|
94
112
|
checks?: ChecksFact | null;
|
|
95
113
|
pullRequest?: PullRequestFact | null;
|
|
96
114
|
/** `unverified`: the report is refuted, so no requirement counts as met however it was marked. `missed`: the ones it
|
|
97
|
-
* failed or left unanswered.
|
|
115
|
+
* failed or left unanswered. `atRelease`: checks Off, waiting for a release check. `unshown`: a required screenshot
|
|
116
|
+
* is missing. `ask`: what a person is asked to look at first (a requirement they check, or a missing screenshot). */
|
|
98
117
|
requirements?: {
|
|
99
118
|
met: number;
|
|
100
119
|
total: number;
|
|
101
120
|
yours: number;
|
|
102
121
|
missed?: number;
|
|
103
122
|
unverified?: boolean;
|
|
123
|
+
atRelease?: number;
|
|
124
|
+
unshown?: number;
|
|
125
|
+
ask?: string | null;
|
|
104
126
|
} | null;
|
|
127
|
+
/** A person accepted this result's proof: their own check and a recorded exception need nothing more. */
|
|
128
|
+
accepted?: boolean;
|
|
129
|
+
/** What refuses Complete (`completionBlockersOf`); the first one is the sentence when it holds the result back. */
|
|
130
|
+
blockers?: readonly CompletionBlocker[];
|
|
105
131
|
evidence?: {
|
|
106
132
|
shortened: number;
|
|
107
133
|
missing: number;
|
|
@@ -124,11 +150,14 @@ export type TaskStatusFacts = {
|
|
|
124
150
|
/** What the person calls their lead (Settings → Lead); "Lead" when unnamed. */
|
|
125
151
|
leadName?: string;
|
|
126
152
|
};
|
|
127
|
-
export declare function headlineOf(facts: Pick<TaskStatusFacts, "stage" | "checks" | "report">): Headline;
|
|
153
|
+
export declare function headlineOf(facts: Pick<TaskStatusFacts, "stage" | "checks" | "report" | "requirements" | "accepted" | "blockers">): Headline;
|
|
128
154
|
/** What the lead's claim says, in place of "waits for you" (lead-voice.ts). */
|
|
129
155
|
export declare const leadOnIt: (name?: string) => string;
|
|
130
156
|
export declare const leadLapsed: (name?: string) => string;
|
|
131
|
-
|
|
157
|
+
/** The one status every surface shows (console list, task and result pages, CLI, chat, phone, `status`). */
|
|
158
|
+
export declare function statusOf(facts: TaskStatusFacts): TaskStatus;
|
|
159
|
+
/** The name most surfaces already import. */
|
|
160
|
+
export declare const taskStatusOf: typeof statusOf;
|
|
132
161
|
export declare function plainReasonOf(stage: TaskStage, code: string, detail: string | null | undefined): string | null;
|
|
133
162
|
export declare function stageOfCode(code: string, options?: {
|
|
134
163
|
needsPerson?: boolean;
|
|
@@ -165,18 +194,41 @@ export declare function assignmentStageOf(assignment: Pick<AssignmentSnapshot, "
|
|
|
165
194
|
wait?: WaitKey;
|
|
166
195
|
};
|
|
167
196
|
type RequirementRow = {
|
|
197
|
+
id?: string;
|
|
198
|
+
statement?: string;
|
|
168
199
|
state: string;
|
|
200
|
+
requiredEvidence?: readonly string[];
|
|
201
|
+
answered?: readonly {
|
|
202
|
+
kind: string;
|
|
203
|
+
}[];
|
|
204
|
+
detail?: readonly string[];
|
|
169
205
|
assessment?: {
|
|
170
206
|
evidenceState?: string;
|
|
207
|
+
detail?: readonly string[];
|
|
171
208
|
} | undefined;
|
|
172
209
|
review?: unknown;
|
|
173
210
|
};
|
|
174
211
|
/** One requirement's state in words: the card's Requirements row and the Checks tab read this one source, so they
|
|
175
|
-
* can't disagree. A refuted report verifies none of its requirements, so each reads Unverified, as the card does.
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
* can't disagree. A refuted report verifies none of its requirements, so each reads Unverified, as the card does.
|
|
213
|
+
* With checks Off, a requirement that waits only on the check reads Checked at release, then Met once a passing full
|
|
214
|
+
* release check contains the commit. */
|
|
215
|
+
export type RequirementWord = "Met" | "You check" | "Not shown yet" | "Checked at release" | "Waiting for checks" | "Not met" | "Unverified";
|
|
216
|
+
export declare function requirementWordOf(row: RequirementRow, verdict?: string | null, release?: ReleaseState): RequirementWord;
|
|
178
217
|
/** Requirements from the stored matrix: met, the ones only a person confirms, and the total. */
|
|
179
|
-
export declare function requirementsOf(matrix: readonly RequirementRow[] | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
218
|
+
export declare function requirementsOf(matrix: readonly RequirementRow[] | null | undefined, release?: ReleaseState): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
219
|
+
/** What refuses Complete, for `task complete`, the console and chat completion and deploy-candidate alike: a failed
|
|
220
|
+
* check; a required check that is missing (checks deliberately Off are not missing: they are checked at release);
|
|
221
|
+
* an unresolved requirement (a person's own check is theirs to give by completing; a recorded acceptance resolves
|
|
222
|
+
* the rest); an unresolved HIGH review finding. A release check that covers the commit counts as passed. */
|
|
223
|
+
export declare function completionBlockersOf(f: {
|
|
224
|
+
report: boolean;
|
|
225
|
+
checks: Pick<ChecksFact, "status" | "exitCode" | "level" | "release" | "running" | "batch"> | null;
|
|
226
|
+
checkRequired: boolean;
|
|
227
|
+
matrix: readonly RequirementRow[] | null | undefined;
|
|
228
|
+
verdict: string | null | undefined;
|
|
229
|
+
accepted: boolean;
|
|
230
|
+
high: number;
|
|
231
|
+
}): CompletionBlocker[];
|
|
180
232
|
/** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
|
|
181
233
|
export declare function unverifiedWhenRefuted(requirements: NonNullable<TaskStatusFacts["requirements"]> | null, verdict: string | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
182
234
|
/** Saved evidence health from the receipt's artifact list and its caveats. */
|
package/dist/task-status.js
CHANGED
|
@@ -27,6 +27,17 @@ export const CHECKS_LABEL = "Project checks";
|
|
|
27
27
|
export const PR_CI_LABEL = "PR CI";
|
|
28
28
|
/** The one verb for opening a saved result, on every surface. */
|
|
29
29
|
export const OPEN_RESULT = "Open result";
|
|
30
|
+
export const CHECKED_AT_RELEASE = "Checked at release";
|
|
31
|
+
export function releaseStateOf(checks) {
|
|
32
|
+
if (checks?.level !== "off" || checks.status === "passed" || checks.status === "failed")
|
|
33
|
+
return undefined;
|
|
34
|
+
return checks.release != null ? "covered" : "pending";
|
|
35
|
+
}
|
|
36
|
+
/** How the project check stands for requirements that rest on it, read now: `covered` once a check passed on this
|
|
37
|
+
* commit (its own, a follow-up, or a release check containing it), `pending` while checks Off wait for a release. */
|
|
38
|
+
export function checkBackingOf(checks) {
|
|
39
|
+
return checks?.status === "passed" ? "covered" : checksUnderway(checks) ? "running" : releaseStateOf(checks);
|
|
40
|
+
}
|
|
30
41
|
/** "T2", "T2 and T3", "T2, T3 and 2 more": short enough for one line. The full list is on the result. */
|
|
31
42
|
export function peerWords(peers, shown = 2) {
|
|
32
43
|
if (peers.length <= shown)
|
|
@@ -43,6 +54,23 @@ const BATCH_WHY = {
|
|
|
43
54
|
conflict: "It conflicted with another result when merged for a batch check, so it was checked on its own.",
|
|
44
55
|
};
|
|
45
56
|
const short = (sha) => sha !== null && /^[a-f0-9]{7,40}$/.test(sha) ? sha.slice(0, 7) : null;
|
|
57
|
+
/** A check waiting for its batch or running on this commit: the result reads Ready meanwhile, not Needs you. */
|
|
58
|
+
const checksUnderway = (checks) => checks?.batch?.state === "waiting" || checks?.running != null;
|
|
59
|
+
/** A finished result a person still has to act on before it can be complete: an unresolved HIGH finding or unmet
|
|
60
|
+
* requirement, a missing check, or a requirement only they can confirm. Failed checks read Failed instead. */
|
|
61
|
+
function resultAsk(facts) {
|
|
62
|
+
if (facts.report || facts.checks?.status === "failed")
|
|
63
|
+
return null;
|
|
64
|
+
const blocker = facts.blockers?.find(one => one.key !== "check-failed" && !(one.key === "check-missing" && checksUnderway(facts.checks)));
|
|
65
|
+
if (blocker !== undefined)
|
|
66
|
+
return blocker.message;
|
|
67
|
+
const req = facts.requirements;
|
|
68
|
+
if (req == null || facts.accepted === true || req.unverified === true)
|
|
69
|
+
return null;
|
|
70
|
+
if ((req.yours > 0 || (req.unshown ?? 0) > 0) && req.ask != null)
|
|
71
|
+
return req.ask;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
46
74
|
export function headlineOf(facts) {
|
|
47
75
|
switch (facts.stage) {
|
|
48
76
|
case "queued": return "Queued";
|
|
@@ -57,7 +85,10 @@ export function headlineOf(facts) {
|
|
|
57
85
|
return "Ready for review";
|
|
58
86
|
if (facts.checks?.status === "failed")
|
|
59
87
|
return "Failed";
|
|
60
|
-
//
|
|
88
|
+
// A requirement a person checks, a missing screenshot, an unmet requirement or a HIGH finding: Needs you, with the ask.
|
|
89
|
+
if (resultAsk(facts) !== null)
|
|
90
|
+
return "Needs you";
|
|
91
|
+
// Off: ready like any build; the sentence and the Checks row say it is checked at release.
|
|
61
92
|
return "Ready for review";
|
|
62
93
|
case "complete": return "Complete";
|
|
63
94
|
case "failed": return "Failed";
|
|
@@ -71,15 +102,17 @@ function sentenceOf(headline, facts) {
|
|
|
71
102
|
case "Queued": return reason ?? "Waiting for a worker.";
|
|
72
103
|
case "Planning": return reason ?? "The lead is writing the plan.";
|
|
73
104
|
// One plain sentence per need (needs-you.ts); a recorded reason only where the need keeps one.
|
|
74
|
-
case "Needs you": return needSentence(facts.need ?? "other", reason, facts.needContext);
|
|
105
|
+
case "Needs you": return facts.stage === "finished" ? resultAsk(facts) ?? needSentence("review-result", reason, facts.needContext) : needSentence(facts.need ?? "other", reason, facts.needContext);
|
|
75
106
|
case "Waiting": return waitSentence(facts.wait, reason, facts.needContext);
|
|
76
107
|
case "Building": return facts.stage === "checking" ? "Checks are running on the change."
|
|
77
108
|
: facts.stage === "reviewing" ? "Reviewing the change before it reaches you." : reason ?? "An agent is working on it.";
|
|
78
109
|
case "Ready for review":
|
|
79
110
|
if (facts.report)
|
|
80
111
|
return "The report is ready to read. Read it, then mark it complete.";
|
|
112
|
+
if (releaseStateOf(facts.checks) === "covered")
|
|
113
|
+
return `Checked at release on ${short(facts.checks.release.head) ?? "its release"}. Review the change, then mark it complete.`;
|
|
81
114
|
if (facts.checks?.level === "off" && facts.checks.status !== "passed")
|
|
82
|
-
return "
|
|
115
|
+
return "Checked at release. Review the change, then mark it complete.";
|
|
83
116
|
if (facts.checks?.status === "passed") {
|
|
84
117
|
// The Checks row names the others and the batch commit; the sentence doesn't repeat them.
|
|
85
118
|
const peers = facts.checks.batch?.state === "together" ? facts.checks.batch.peers.length : 0;
|
|
@@ -135,8 +168,10 @@ function detailsOf(headline, facts) {
|
|
|
135
168
|
row("checks", CHECKS_LABEL, `${quick ? "Quick checks failed" : "Failed"}${checks.exitCode === null ? "" : ` (exit ${checks.exitCode})`}`, problem(headline), headline === "Failed" ? { href: checksHref } : { action: { label: "See what failed", href: checksHref } });
|
|
136
169
|
else if (checks.status === "running")
|
|
137
170
|
row("checks", CHECKS_LABEL, "Running", "running");
|
|
171
|
+
else if (checks.level === "off" && checks.release != null)
|
|
172
|
+
row("checks", CHECKS_LABEL, `Passed at release on ${short(checks.release.head) ?? `build #${checks.release.run}`}`, "ok", { href: checksHref });
|
|
138
173
|
else if (checks.level === "off")
|
|
139
|
-
row("checks", CHECKS_LABEL,
|
|
174
|
+
row("checks", CHECKS_LABEL, CHECKED_AT_RELEASE, "none", runChecks === null ? {} : { action: runChecks });
|
|
140
175
|
else if (checks.status === "not-run")
|
|
141
176
|
row("checks", CHECKS_LABEL, "Didn't run", "none", runChecks === null ? {} : { action: runChecks });
|
|
142
177
|
// A saved check that can't be read is run again where that is possible.
|
|
@@ -173,8 +208,9 @@ function detailsOf(headline, facts) {
|
|
|
173
208
|
else if (req != null && req.total > 0 && req.unverified === true)
|
|
174
209
|
row("requirements", "Requirements", "Unverified", "none");
|
|
175
210
|
else if (req != null && req.total > 0) {
|
|
176
|
-
const
|
|
177
|
-
const
|
|
211
|
+
const atRelease = req.atRelease ?? 0;
|
|
212
|
+
const unmet = req.total - req.met - req.yours - atRelease;
|
|
213
|
+
const text = `${req.met} of ${req.total} met${atRelease > 0 ? ` · ${atRelease} at release` : ""}${req.yours > 0 ? ` · You check ${req.yours}` : ""}`;
|
|
178
214
|
row("requirements", "Requirements", text, unmet > 0 ? problem(headline) : req.met === req.total ? "ok" : "none", unmet > 0 && headline !== "Failed" ? { action: { label: "See which", href: facts.links?.checks ?? facts.links?.result ?? null } } : {});
|
|
179
215
|
}
|
|
180
216
|
const evidence = facts.evidence;
|
|
@@ -203,19 +239,23 @@ function leadSentence(headline, facts) {
|
|
|
203
239
|
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. ${leadOnIt(facts.leadName)}`;
|
|
204
240
|
return leadOnIt(facts.leadName);
|
|
205
241
|
}
|
|
206
|
-
|
|
242
|
+
/** The one status every surface shows (console list, task and result pages, CLI, chat, phone, `status`). */
|
|
243
|
+
export function statusOf(facts) {
|
|
207
244
|
const read = headlineOf(facts);
|
|
208
245
|
const lead = facts.lead != null && LEAD_HEADLINES.has(read) ? facts.lead : null;
|
|
209
246
|
// The lead has it: nothing waits on the person, so a Needs you reads Waiting.
|
|
210
247
|
const headline = lead === "on-it" && read === "Needs you" ? "Waiting" : read;
|
|
211
|
-
// Every Needs you carries the one action that resolves it, worded the same everywhere.
|
|
212
|
-
const
|
|
248
|
+
// Every Needs you carries the one action that resolves it, worded the same everywhere; a finished result's is Open result.
|
|
249
|
+
const needKey = facts.stage === "finished" ? "review-result" : facts.need ?? "other";
|
|
250
|
+
const need = headline === "Needs you" ? { key: needKey, action: NEEDS[needKey].action } : null;
|
|
213
251
|
const ownWords = need !== null && (need.key === "other" || need.key === "review-result") && facts.action != null;
|
|
214
252
|
const primaryAction = need === null || ownWords ? facts.action ?? null : { label: need.action.label, href: facts.action?.href ?? null };
|
|
215
253
|
const sentence = lead === "on-it" ? leadSentence(headline, facts) : lead === "lapsed" ? `${leadLapsed(facts.leadName)} ${sentenceOf(headline, facts)}` : sentenceOf(headline, facts);
|
|
216
254
|
return { headline, tone: HEADLINE_TONE[headline], sentence, details: detailsOf(headline, facts),
|
|
217
255
|
primaryAction, why: [...new Set(facts.why ?? [])].filter(one => one.trim() !== ""), need };
|
|
218
256
|
}
|
|
257
|
+
/** The name most surfaces already import. */
|
|
258
|
+
export const taskStatusOf = statusOf;
|
|
219
259
|
// ---- reading the existing projections ----------------------------------------
|
|
220
260
|
/** Every dispatch or work-index code, as the stage it means. Unknown codes that
|
|
221
261
|
* wait on a person stay Needs you; everything else waits in the queue. */
|
|
@@ -316,19 +356,93 @@ export function assignmentStageOf(assignment, work, planning = false) {
|
|
|
316
356
|
}
|
|
317
357
|
/** Evidence that passed and only awaits the retired assessment step is met. */
|
|
318
358
|
const requirementMet = (row) => row.state === "pass" || (row.assessment?.evidenceState === "pass" && (row.review ?? null) === null);
|
|
319
|
-
|
|
359
|
+
/** A requirement whose only gap is the project check: every other kind it needs was shown, none is a person's check. */
|
|
360
|
+
const onlyCheckMissing = (row) => {
|
|
361
|
+
const needs = row.requiredEvidence ?? [];
|
|
362
|
+
const answered = new Set((row.answered ?? []).map(one => one.kind));
|
|
363
|
+
if (row.state !== "missing" || row.review != null || row.assessment?.evidenceState === "failed" ||
|
|
364
|
+
!needs.includes("check") || needs.includes("manual-review") || !needs.every(kind => kind === "check" || answered.has(kind)))
|
|
365
|
+
return false;
|
|
366
|
+
// `answered` contains claims, not validated evidence. In legacy matrices a missing
|
|
367
|
+
// check takes precedence over an invalid screenshot/changed path in `state`.
|
|
368
|
+
// Only the stored validator's check-only gaps establish that every other
|
|
369
|
+
// reference resolved. Direct assessment keeps these outcomes in its own detail.
|
|
370
|
+
const detail = row.assessment?.detail ?? row.detail ?? [];
|
|
371
|
+
const checkGaps = new Set([
|
|
372
|
+
`criterion "${row.id}" requires check evidence, which the proof does not reference`,
|
|
373
|
+
`criterion "${row.id}" is waiting for the final check`,
|
|
374
|
+
`criterion "${row.id}" needs a passing approved project check`,
|
|
375
|
+
]);
|
|
376
|
+
return (needs.every(kind => kind === "check") || detail.length > 0) && detail.every(one => checkGaps.has(one));
|
|
377
|
+
};
|
|
378
|
+
/** A required screenshot nobody captured. */
|
|
379
|
+
const screenshotMissing = (row) => (row.requiredEvidence ?? []).includes("screenshot") && !(row.answered ?? []).some(one => one.kind === "screenshot");
|
|
380
|
+
export function requirementWordOf(row, verdict, release) {
|
|
320
381
|
if (verdict === "refuted")
|
|
321
382
|
return "Unverified";
|
|
322
383
|
if (requirementMet(row))
|
|
323
384
|
return "Met";
|
|
324
|
-
|
|
385
|
+
if (release !== undefined && onlyCheckMissing(row))
|
|
386
|
+
return release === "covered" ? "Met" : release === "running" ? "Waiting for checks" : CHECKED_AT_RELEASE;
|
|
387
|
+
if (row.state === "manual-review")
|
|
388
|
+
return "You check";
|
|
389
|
+
return row.state === "missing" || screenshotMissing(row) ? "Not shown yet" : "Not met";
|
|
390
|
+
}
|
|
391
|
+
const ASK_STATEMENT = 140;
|
|
392
|
+
const quoted = (statement) => {
|
|
393
|
+
const one = (statement ?? "").replace(/\s+/g, " ").trim();
|
|
394
|
+
return one.length > ASK_STATEMENT ? `${one.slice(0, ASK_STATEMENT - 1)}…` : one;
|
|
395
|
+
};
|
|
396
|
+
/** The ask for one unresolved requirement: what is wrong with it, and the two ways on. */
|
|
397
|
+
function unresolvedAsk(row, word) {
|
|
398
|
+
const what = word === "Not shown yet" && screenshotMissing(row) ? "A screenshot is missing for"
|
|
399
|
+
: word === "Not shown yet" ? "Nothing shows this requirement is met" : word === "Unverified" ? "This requirement couldn't be verified" : "This requirement isn't met";
|
|
400
|
+
return `${what}: ${quoted(row.statement) || row.id || "a requirement"}. Ask for changes, or accept the result with a reason.`;
|
|
325
401
|
}
|
|
326
402
|
/** Requirements from the stored matrix: met, the ones only a person confirms, and the total. */
|
|
327
|
-
export function requirementsOf(matrix) {
|
|
403
|
+
export function requirementsOf(matrix, release) {
|
|
328
404
|
if (!matrix || matrix.length === 0)
|
|
329
405
|
return null;
|
|
330
|
-
|
|
331
|
-
|
|
406
|
+
const words = matrix.map(row => requirementWordOf(row, null, release));
|
|
407
|
+
const yours = matrix.find((_, index) => words[index] === "You check");
|
|
408
|
+
const unshown = matrix.filter((row, index) => words[index] === "Not shown yet" && screenshotMissing(row));
|
|
409
|
+
const ask = yours !== undefined ? `Check this requirement yourself, then mark it complete: ${quoted(yours.statement)}`
|
|
410
|
+
: unshown.length > 0 ? unresolvedAsk(unshown[0], "Not shown yet") : null;
|
|
411
|
+
return { met: words.filter(word => word === "Met").length, total: matrix.length, yours: words.filter(word => word === "You check").length,
|
|
412
|
+
missed: words.filter(word => word === "Not met" || word === "Not shown yet").length,
|
|
413
|
+
...(release !== "pending" ? {} : { atRelease: words.filter(word => word === CHECKED_AT_RELEASE).length }),
|
|
414
|
+
...(unshown.length === 0 ? {} : { unshown: unshown.length }),
|
|
415
|
+
...(ask === null ? {} : { ask }) };
|
|
416
|
+
}
|
|
417
|
+
/** What refuses Complete, for `task complete`, the console and chat completion and deploy-candidate alike: a failed
|
|
418
|
+
* check; a required check that is missing (checks deliberately Off are not missing: they are checked at release);
|
|
419
|
+
* an unresolved requirement (a person's own check is theirs to give by completing; a recorded acceptance resolves
|
|
420
|
+
* the rest); an unresolved HIGH review finding. A release check that covers the commit counts as passed. */
|
|
421
|
+
export function completionBlockersOf(f) {
|
|
422
|
+
const blockers = [];
|
|
423
|
+
const release = releaseStateOf(f.checks);
|
|
424
|
+
const backing = checkBackingOf(f.checks);
|
|
425
|
+
if (!f.report && f.checks?.status === "failed") {
|
|
426
|
+
blockers.push({ key: "check-failed", message: `Checks failed${f.checks.exitCode === null ? "" : ` (exit ${f.checks.exitCode})`}. Fix them and run checks again, or ask for changes.` });
|
|
427
|
+
}
|
|
428
|
+
else if (!f.report && f.checkRequired && f.checks?.status !== "passed" && release === undefined) {
|
|
429
|
+
blockers.push({ key: "check-missing", message: checksUnderway(f.checks) ? "Its project check hasn't finished yet. Mark it complete once it passes."
|
|
430
|
+
: "No passing project check is recorded for this result. Run checks on it, then mark it complete." });
|
|
431
|
+
}
|
|
432
|
+
if (!f.accepted && f.checks?.status !== "failed") {
|
|
433
|
+
const refuted = f.verdict === "refuted";
|
|
434
|
+
const unresolved = (f.matrix ?? []).map(row => ({ row, word: requirementWordOf(row, refuted ? "refuted" : null, backing) }))
|
|
435
|
+
.filter(one => one.word === "Not met" || one.word === "Not shown yet" || one.word === "Unverified");
|
|
436
|
+
if (unresolved.length === 1)
|
|
437
|
+
blockers.push({ key: "criteria", message: unresolvedAsk(unresolved[0].row, unresolved[0].word) });
|
|
438
|
+
else if (unresolved.length > 1) {
|
|
439
|
+
const ids = unresolved.map(one => one.row.id ?? "?");
|
|
440
|
+
blockers.push({ key: "criteria", message: `${unresolved.length} requirements ${unresolved.every(one => one.word === "Unverified") ? "couldn't be verified" : "aren't met yet"} (${ids.slice(0, 6).join(", ")}${ids.length > 6 ? ", …" : ""}). Ask for changes, or accept the result with a reason.` });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (f.high > 0)
|
|
444
|
+
blockers.push({ key: "high", message: `The automatic review found ${f.high === 1 ? "a high-severity problem" : `${f.high} high-severity problems`}. Ask for changes before marking it complete.` });
|
|
445
|
+
return blockers;
|
|
332
446
|
}
|
|
333
447
|
/** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
|
|
334
448
|
export function unverifiedWhenRefuted(requirements, verdict) {
|
|
@@ -355,7 +469,7 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
355
469
|
const checks = receipt === null || !withResult ? null
|
|
356
470
|
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head,
|
|
357
471
|
...(receipt.checks.level == null ? {} : { level: receipt.checks.level }), ...(receipt.checks.running == null ? {} : { running: receipt.checks.running }),
|
|
358
|
-
...(receipt.checks.batch == null ? {} : { batch: receipt.checks.batch }) };
|
|
472
|
+
...(receipt.checks.batch == null ? {} : { batch: receipt.checks.batch }), ...(receipt.checks.release == null ? {} : { release: receipt.checks.release }) };
|
|
359
473
|
const publication = assignment.publication;
|
|
360
474
|
const pullRequest = options.pullRequest !== undefined ? options.pullRequest
|
|
361
475
|
: publication === null ? (withResult && !report ? { state: "none", number: null, url: null, ci: null, error: null } : null)
|
|
@@ -368,7 +482,9 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
368
482
|
stage, ...(need === undefined ? {} : { need }), ...(wait === undefined ? {} : { wait }), ...(build === null ? {} : { needContext: { build } }), reason, report,
|
|
369
483
|
checks,
|
|
370
484
|
pullRequest: withResult ? pullRequest : null,
|
|
371
|
-
requirements: withResult ? unverifiedWhenRefuted(requirementsOf(receipt?.proof?.matrix), receipt?.proof?.verdict) : null,
|
|
485
|
+
requirements: withResult ? unverifiedWhenRefuted(requirementsOf(receipt?.proof?.matrix, checkBackingOf(checks)), assignment.readiness?.verdict ?? receipt?.proof?.verdict) : null,
|
|
486
|
+
...(withResult && receipt?.proofAcceptance != null ? { accepted: true } : {}),
|
|
487
|
+
...(withResult && (assignment.readiness?.blockers.length ?? 0) > 0 ? { blockers: assignment.readiness.blockers } : {}),
|
|
372
488
|
evidence: withResult ? options.evidence ?? evidenceOf(receipt, [...(receipt?.caveats ?? []), ...assignment.attention]) : null,
|
|
373
489
|
completedBy: assignment.completion === null ? null : assignment.completion.lead === true ? "the lead" : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
374
490
|
action: options.action ?? null,
|