@bli-cockpit/cli 0.2.249 → 0.2.251
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/dist/adapters/raw-evidence-collection-state.js +2 -0
- package/dist/adapters/raw-evidence-prepare-memo.js +212 -0
- package/dist/adapters/raw-evidence-transcript-file.js +122 -9
- package/dist/adapters/raw-evidence.js +20 -0
- package/dist/autostart-darwin-plist.js +21 -5
- package/dist/autostart.js +1 -1
- package/dist/commands/backfill-live-window.js +41 -0
- package/dist/commands/backfill-report.js +11 -1
- package/dist/commands/backfill-scan.js +8 -4
- package/dist/commands/doctor-disk-words.js +1 -1
- package/dist/commands/local-args-tower-manager.js +28 -17
- package/dist/commands/local-args-tower-notes.js +1 -1
- package/dist/commands/local-args-tower-settings.js +24 -3
- package/dist/commands/local-help-commands-tower-ops.js +20 -2
- package/dist/commands/local-help-commands-tower.js +2 -2
- package/dist/commands/local-help-commands.js +1 -1
- package/dist/commands/local-help-project.js +1 -1
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/manager-decisions.js +189 -0
- package/dist/commands/manager-hints.js +2 -0
- package/dist/commands/manager-limits.json +42 -1
- package/dist/commands/manager-meetings.js +184 -0
- package/dist/commands/manager-requests.js +15 -0
- package/dist/commands/manager.js +16 -2
- package/dist/commands/notes-accounts.js +1 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/settings.js +1 -1
- package/dist/evidence-upload-terminal.js +47 -5
- package/dist/image-description-session.js +20 -9
- package/dist/raw-evidence-staging.js +2 -1
- package/dist/upload-evidence-delivery-offer.js +3 -0
- package/package.json +2 -2
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* cockpit manager approve <id> --version <v> [--timeline] [--restore] (--restore brings a dismissed item back)
|
|
19
19
|
* cockpit manager dismiss|archive|unarchive <id> --version <v> [--timeline]
|
|
20
20
|
* cockpit manager proposal <id> <proposal-id> accept|dismiss|add-new --version <v> [--timeline]
|
|
21
|
+
* cockpit manager meetings | tie | decide-tie ... (meetings tied to clients: usage in `manager-meetings.ts`)
|
|
22
|
+
* cockpit manager decisions <client> [--status open|approved|dismissed|all] (BLI-22187, `manager-decisions.ts`)
|
|
23
|
+
* cockpit manager add-decision <client> --title <t> [--why <t>] [--owners 'A, B'] [--due YYYY-MM-DD] [--request-id <uuid>]
|
|
24
|
+
* cockpit manager decide <decision-id> approve|dismiss|reopen --version <v> [--note <t>]
|
|
21
25
|
*
|
|
22
26
|
* Free text goes in a JSON file or on stdin with `--fields-file <path>|-` on add, edit and summary (no shell quoting at all,
|
|
23
27
|
* `manager-fields-file.ts`); on a flag it goes in SINGLE quotes (the shell leaves `$` alone) and the help says how to write
|
|
@@ -27,33 +31,35 @@
|
|
|
27
31
|
* `help` and `--help` are the help, never a list and never another command.
|
|
28
32
|
*/
|
|
29
33
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs, rejectSecretLikeValue } from "./local-arg-values.js";
|
|
34
|
+
import { readDecisionFields } from "./manager-decisions.js";
|
|
30
35
|
import { mergeFieldsFile } from "./manager-fields-file.js";
|
|
31
36
|
import { runIdRefusal } from "./manager-hints.js";
|
|
37
|
+
import { readMeetingTieArgs, readTieId } from "./manager-meetings.js";
|
|
32
38
|
export const MANAGER_VERBS = [
|
|
33
39
|
"clients", "client", "home", "summary", "run", "list", "queue", "show", "add", "edit", "done", "drop", "reopen", "link",
|
|
34
|
-
"approve", "dismiss", "proposal", "archive", "unarchive",
|
|
40
|
+
"approve", "dismiss", "proposal", "archive", "unarchive", "meetings", "tie", "decide-tie", "decisions", "add-decision", "decide",
|
|
35
41
|
];
|
|
36
42
|
export const MANAGER_ACTION_STATUSES = ["open", "done", "dropped", "closed", "all"];
|
|
37
43
|
export const MANAGER_TIMELINE_KINDS = ["meeting", "document", "milestone", "internal"];
|
|
38
44
|
export const MANAGER_SUMMARY_HEALTHS = ["on_track", "at_risk", "blocked"];
|
|
39
45
|
/** The verbs whose one positional word is a client (its id or its exact name). */
|
|
40
|
-
const CLIENT_VERBS = new Set(["client", "home", "summary", "list", "queue", "add"]);
|
|
46
|
+
const CLIENT_VERBS = new Set(["client", "home", "summary", "list", "queue", "add", "tie", "decisions", "add-decision"]);
|
|
41
47
|
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
42
48
|
const VERSION = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
43
49
|
const ONE_VERB = `manager takes one verb: ${MANAGER_VERBS.join(", ")}. Example: \`cockpit manager clients\` or \`cockpit manager list <client>\`.`;
|
|
44
50
|
const OWN_FLAGS = [
|
|
45
51
|
"--timeline", "--needs-review", "--status", "--owner", "--owners", "--no-owner", "--kind", "--search", "--archived", "--review",
|
|
46
52
|
"--title", "--detail", "--due", "--date", "--meeting", "--none", "--version", "--request-id", "--restore", "--overdue", "--source-url", "--source-note",
|
|
47
|
-
"--health", "--headline", "--bullet", "--next", "--blocker", "--fields-file",
|
|
53
|
+
"--health", "--headline", "--bullet", "--next", "--blocker", "--fields-file", "--client", "--event", "--note", "--occurrence", "--cursor", "--page-size", "--why",
|
|
48
54
|
];
|
|
49
55
|
const COMMON_FLAGS = ["--home", "--dashboard-url", "--json", "--help"];
|
|
50
56
|
const VALUE_FLAGS = [
|
|
51
57
|
"--status", "--owner", "--owners", "--kind", "--search", "--review", "--title", "--detail", "--due", "--date", "--meeting",
|
|
52
58
|
"--version", "--request-id", "--source-url", "--source-note", "--home", "--dashboard-url",
|
|
53
|
-
"--health", "--headline", "--bullet", "--next", "--blocker", "--fields-file",
|
|
59
|
+
"--health", "--headline", "--bullet", "--next", "--blocker", "--fields-file", "--client", "--event", "--note", "--occurrence", "--cursor", "--page-size", "--why",
|
|
54
60
|
];
|
|
55
61
|
/** The flags whose value is a person's own words (not an id, a version or a choice). */
|
|
56
|
-
const TEXT_FLAGS = ["--title", "--detail", "--headline", "--bullet", "--next", "--blocker", "--owners", "--owner", "--source-note", "--source-url"];
|
|
62
|
+
const TEXT_FLAGS = ["--title", "--detail", "--headline", "--bullet", "--next", "--blocker", "--owners", "--owner", "--source-note", "--source-url", "--why", "--note"];
|
|
57
63
|
/** `summary --bullet` is given once per bullet: the one value flag that may repeat. */
|
|
58
64
|
const REPEATABLE_FLAGS = new Set(["--bullet"]);
|
|
59
65
|
/** Which flags each verb owns, so a flag on the wrong verb is refused by name rather than ignored. */
|
|
@@ -77,6 +83,8 @@ export const FLAGS_BY_VERB = {
|
|
|
77
83
|
proposal: ["--timeline", "--version"],
|
|
78
84
|
archive: ["--timeline", "--version"],
|
|
79
85
|
unarchive: ["--timeline", "--version"],
|
|
86
|
+
meetings: ["--client", "--event", "--note", "--page-size", "--cursor"], tie: ["--event", "--note", "--occurrence", "--request-id"], "decide-tie": ["--version"],
|
|
87
|
+
decisions: ["--status"], "add-decision": ["--title", "--why", "--owners", "--due", "--request-id"], decide: ["--version", "--note"],
|
|
80
88
|
};
|
|
81
89
|
/** Positional words each verb takes, named for the refusal. */
|
|
82
90
|
const POSITIONALS = {
|
|
@@ -90,15 +98,9 @@ const POSITIONALS = {
|
|
|
90
98
|
show: ["id"],
|
|
91
99
|
add: ["client"],
|
|
92
100
|
edit: ["id"],
|
|
93
|
-
done: ["action id"],
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
link: ["action id"],
|
|
97
|
-
approve: ["id"],
|
|
98
|
-
dismiss: ["id"],
|
|
99
|
-
proposal: ["id", "proposal id", "accept, dismiss or add-new"],
|
|
100
|
-
archive: ["id"],
|
|
101
|
-
unarchive: ["id"],
|
|
101
|
+
done: ["action id"], drop: ["action id"], reopen: ["action id"], link: ["action id"],
|
|
102
|
+
approve: ["id"], dismiss: ["id"], proposal: ["id", "proposal id", "accept, dismiss or add-new"], archive: ["id"], unarchive: ["id"],
|
|
103
|
+
meetings: [], tie: ["client"], "decide-tie": ["tie id", "accept, dismiss or remove"], decisions: ["client"], "add-decision": ["client"], decide: ["decision id", "approve, dismiss or reopen"],
|
|
102
104
|
};
|
|
103
105
|
/** A value flag given twice would silently keep only the last; say so instead. */
|
|
104
106
|
function refuseRepeatedFlags(args) {
|
|
@@ -151,13 +153,20 @@ export function parseManagerArgs(args, options = {}) {
|
|
|
151
153
|
shape.client = rest[0];
|
|
152
154
|
else if (verb === "run")
|
|
153
155
|
shape.runId = readRunId(rest[0]);
|
|
154
|
-
else if (verb
|
|
155
|
-
shape.id =
|
|
156
|
+
else if (verb === "decide-tie")
|
|
157
|
+
shape.id = readTieId(rest[0]);
|
|
158
|
+
else if (verb !== "clients" && verb !== "meetings")
|
|
159
|
+
shape.id = readId(rest[0], verb === "done" || verb === "drop" || verb === "reopen" || verb === "link" ? "An action" : verb === "decide" ? "A decision" : "An item");
|
|
156
160
|
if (verb === "proposal") {
|
|
157
161
|
shape.proposalId = rest[1];
|
|
158
162
|
shape.decision = readDecision(rest[2]);
|
|
159
163
|
}
|
|
160
|
-
|
|
164
|
+
// BLI-22187: the decision verbs read their own flags (`manager-decisions.ts`); `decide`'s version comes from `decisions <client>`.
|
|
165
|
+
if (verb === "decisions" || verb === "add-decision" || verb === "decide") {
|
|
166
|
+
readDecisionFields(verb, values, shape, rest[1], { readId, readDate, readOwnersList, isBlank });
|
|
167
|
+
return shape;
|
|
168
|
+
}
|
|
169
|
+
const needsVersion = !["clients", "client", "home", "summary", "run", "list", "queue", "show", "add", "meetings", "tie"].includes(verb);
|
|
161
170
|
if (needsVersion)
|
|
162
171
|
shape.version = readVersion(flag("--version"), verb);
|
|
163
172
|
let fileBullets;
|
|
@@ -172,6 +181,8 @@ export function parseManagerArgs(args, options = {}) {
|
|
|
172
181
|
readSummaryFields(values, shape, fileBullets);
|
|
173
182
|
if (verb === "list")
|
|
174
183
|
readListFilters(values, shape);
|
|
184
|
+
if (verb === "meetings" || verb === "tie" || verb === "decide-tie")
|
|
185
|
+
readMeetingTieArgs(verb, values, rest, shape);
|
|
175
186
|
if (verb === "add")
|
|
176
187
|
readAddFields(values, shape);
|
|
177
188
|
if (verb === "edit")
|
|
@@ -16,7 +16,7 @@ import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs,
|
|
|
16
16
|
* `otter` and `granola` are deliberately absent: Otter's public API is
|
|
17
17
|
* Enterprise-only and Granola still runs on its own server-side cron.
|
|
18
18
|
*/
|
|
19
|
-
const CONNECTABLE_PROVIDERS = ["fellow", "circleback", "wisprflow"];
|
|
19
|
+
const CONNECTABLE_PROVIDERS = ["fellow", "circleback", "wisprflow", "fireflies"];
|
|
20
20
|
/**
|
|
21
21
|
* BLI-12926. Providers with no API key at all: the person signs in through
|
|
22
22
|
* their browser and `connect` runs that sign-in itself. `--key-stdin` is
|
|
@@ -27,7 +27,14 @@ const SETTINGS_ALLOWED_FLAGS = [
|
|
|
27
27
|
"--id",
|
|
28
28
|
"--content-stdin",
|
|
29
29
|
"--yes",
|
|
30
|
+
"--clear",
|
|
30
31
|
];
|
|
32
|
+
/**
|
|
33
|
+
* BLI-22344: the switches a person may set to EMPTY with `--clear`. Emptying the meeting bot's pilot list is the
|
|
34
|
+
* fail-closed "pause the bot" act, so it needs a spelling a shell cannot swallow (an empty `""` vanishes in some
|
|
35
|
+
* shells). Every other switch still needs a value; the server keeps its own validation either way.
|
|
36
|
+
*/
|
|
37
|
+
const CLEARABLE_SWITCH_KEYS = ["notes.review_gate.pilot_user_ids"];
|
|
31
38
|
const SETTINGS_VALUE_FLAGS = [
|
|
32
39
|
"--home",
|
|
33
40
|
"--dashboard-url",
|
|
@@ -50,6 +57,10 @@ export function parseSettingsArgs(args) {
|
|
|
50
57
|
yes: values.booleans.has("--yes"),
|
|
51
58
|
json: values.booleans.has("--json"),
|
|
52
59
|
};
|
|
60
|
+
const clear = values.booleans.has("--clear");
|
|
61
|
+
if (clear && rawSection !== "switches") {
|
|
62
|
+
throw new Error("--clear belongs to `settings switches set <key> --clear` only.");
|
|
63
|
+
}
|
|
53
64
|
if (!rawSection) {
|
|
54
65
|
if (rest.length > 0)
|
|
55
66
|
throw new Error("settings does not accept that argument.");
|
|
@@ -62,7 +73,7 @@ export function parseSettingsArgs(args) {
|
|
|
62
73
|
case "personal":
|
|
63
74
|
return parsePersonalSection(rest, values.flags, common);
|
|
64
75
|
case "switches":
|
|
65
|
-
return parseSwitchesSection(rest, common);
|
|
76
|
+
return parseSwitchesSection(rest, clear, common);
|
|
66
77
|
case "cli-floor":
|
|
67
78
|
return parseCliFloorSection(rest, common);
|
|
68
79
|
case "models":
|
|
@@ -86,15 +97,25 @@ function parsePersonalSection(rest, flags, common) {
|
|
|
86
97
|
...common,
|
|
87
98
|
};
|
|
88
99
|
}
|
|
89
|
-
/** `settings switches` or `settings switches set <key>
|
|
90
|
-
function parseSwitchesSection(rest, common) {
|
|
100
|
+
/** `settings switches`, `settings switches set <key> <value>`, or `settings switches set <key> --clear` for a list. */
|
|
101
|
+
function parseSwitchesSection(rest, clear, common) {
|
|
91
102
|
if (rest.length === 0) {
|
|
103
|
+
if (clear)
|
|
104
|
+
throw new Error("--clear belongs to `settings switches set <key> --clear` only.");
|
|
92
105
|
return { kind: "settings", section: "switches", action: "show", ...common };
|
|
93
106
|
}
|
|
94
107
|
if (rest[0] !== "set") {
|
|
95
108
|
throw new Error("settings switches takes no verb, or `set <key> <value>`.");
|
|
96
109
|
}
|
|
97
110
|
const [, key, value, ...extra] = rest;
|
|
111
|
+
if (clear) {
|
|
112
|
+
if (!key || value !== undefined)
|
|
113
|
+
throw new Error("settings switches set <key> --clear takes no value.");
|
|
114
|
+
if (!CLEARABLE_SWITCH_KEYS.includes(key)) {
|
|
115
|
+
throw new Error(`--clear only empties a list switch: ${CLEARABLE_SWITCH_KEYS.join(", ")}. Every other switch needs a value.`);
|
|
116
|
+
}
|
|
117
|
+
return { kind: "settings", section: "switches", action: "set", switchKey: key, switchValue: "", ...common };
|
|
118
|
+
}
|
|
98
119
|
if (!key || !value || extra.length > 0) {
|
|
99
120
|
throw new Error("settings switches set needs exactly a key and a value.");
|
|
100
121
|
}
|
|
@@ -212,7 +212,7 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
212
212
|
[
|
|
213
213
|
"manager",
|
|
214
214
|
[
|
|
215
|
-
"Usage: cockpit manager <clients|client|home|summary|run|list|queue|show|add|edit|done|drop|reopen|link|approve|dismiss|proposal|archive|unarchive> ... [--dashboard-url <url>] [--json]",
|
|
215
|
+
"Usage: cockpit manager <clients|client|home|summary|run|list|queue|show|add|edit|done|drop|reopen|link|approve|dismiss|proposal|archive|unarchive|meetings|tie|decide-tie|decisions|add-decision|decide> ... [--dashboard-url <url>] [--json]",
|
|
216
216
|
"",
|
|
217
217
|
"Tower Manager's clients, from the terminal: each client's Home and \"Where we are\", its actions and its timeline items, the same things the manager page does.",
|
|
218
218
|
" cockpit manager clients every client, with its id",
|
|
@@ -253,6 +253,24 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
253
253
|
" cockpit manager dismiss <id> --version <v> [--timeline] the AI's item is wrong: dismiss it (kept, hidden from the lists)",
|
|
254
254
|
" cockpit manager proposal <id> <proposal-id> accept|dismiss|add-new --version <v> [--timeline] decide one suggested change; add-new makes it an item of its own",
|
|
255
255
|
" cockpit manager archive <id> --version <v> [--timeline] delete an item: it moves to the Deleted list (list --archived), kept; unarchive <id> --version <v> brings it back",
|
|
256
|
+
" cockpit manager meetings [--client <client>] [--event <id> | --note <id>] [--page-size <n>] [--cursor <c>]",
|
|
257
|
+
" meetings tied to clients, newest tie first: one client's (--client), the clients one meeting is tied to (--event or --note), or every client's.",
|
|
258
|
+
" Each shows the meeting's title and time only if YOU can open that event or note in Tower; any other reads \"a meeting you cannot open\" (Tower never stores a meeting's title on the tie).",
|
|
259
|
+
" Suggested and confirmed ties only; dismissed and removed ones are kept but not listed. Ties per page: 50 by default, up to 200 with --page-size; the next page is --cursor <nextCursor> with the same --client, --event or --note, and the command prints it.",
|
|
260
|
+
" cockpit manager tie <client> --event <id> [--occurrence <timestamp>] | --note <id> [--request-id <uuid>]",
|
|
261
|
+
" tie a calendar event (--occurrence: one occurrence of a recurring one, its start with an offset) or a meeting note to a client. A meeting can be tied to several clients.",
|
|
262
|
+
" From the terminal or an agent a tie is a SUGGESTION, shown as waiting for a person until someone accepts or dismisses it (the manager page, or decide-tie); a meeting already tied there answers that tie and writes nothing.",
|
|
263
|
+
" A suggestion a person dismissed is never suggested again (refused tie_dismissed). It sends a request id and prints it: rerun with that --request-id to retry without a duplicate.",
|
|
264
|
+
" cockpit manager decide-tie <tie-id> accept|dismiss|remove --version <v>",
|
|
265
|
+
" accept or dismiss a suggested tie, or remove a confirmed one (kept, no longer listed). --version is the tie's version as `meetings` prints it; a changed tie is refused (tie_changed) and nothing is written.",
|
|
266
|
+
" cockpit manager decisions <client> [--status open|approved|dismissed|all] the choices someone has to make for this client (its own kind, not timeline items), soonest due first, undated last,",
|
|
267
|
+
" each with why it is asked, who decides, its id and its version; open (waiting) is the default, all lists the decided ones too. A list holds at most 500 decisions.",
|
|
268
|
+
" cockpit manager add-decision <client> --title <text> [--why <text>] [--owners 'A, B'] [--due YYYY-MM-DD] [--request-id <uuid>]",
|
|
269
|
+
" add a decision to make: --why says why it is asked, --owners who decides (kept as written), --due the day to decide by. It is stored as added by your agent and waits until someone decides it. Text tagged [HR] is refused.",
|
|
270
|
+
" Limits: a title at most 300 characters, a why at most 2,000 characters, a note at most 500 characters. Like add, it always sends a request id and prints it, so a retry never makes a second one.",
|
|
271
|
+
" cockpit manager decide <decision-id> approve|dismiss|reopen --version <v> [--note <text>]",
|
|
272
|
+
" approve or dismiss a decision as YOUR AGENT (your name, marked as your agent, with the day), with --note saying in a line what was decided; reopen undoes either and clears the note. An approved decision is dismissed (or a dismissed one approved) only after a reopen.",
|
|
273
|
+
" --version is the decision's version as `decisions <client>` prints it; a stale one is refused and nothing is written. Deciding it the same way again writes nothing.",
|
|
256
274
|
"approve, dismiss and proposal decide the AI's suggestion as YOU: your name is recorded as the reviewer, and the item leaves the review queue unless a suggested change still waits on it.",
|
|
257
275
|
"Marking reviewed sets the review state to reviewed, with you as the reviewer and the time of the write: link always does it, and edit does whenever it changes something, even a detail-only one (an edit whose fields all equal the stored ones prints \"No change; nothing was written.\", with no new version and no reviewed mark); done, drop and reopen do it only when the status changes (on an action already in that status nothing is written); approve and an accepted proposal do it too. Dismiss sets it to dismissed with you as the reviewer; archive, unarchive and a proposal you dismiss or add-new leave the item's review state as it was.",
|
|
258
276
|
"Free text without shell quoting: add, edit and summary take --fields-file <path> (or - for stdin), one JSON object whose keys are the flags in snake_case, so a $12k, an apostrophe, a double quote or an = reaches Tower exactly as written. The file is UTF-8 (a UTF-16 file with a BOM is read too; any other encoding is refused): in Windows PowerShell write it with `-Encoding utf8` (a `>` redirect makes UTF-16 and Set-Content makes the ANSI code page, which breaks a ’), and for stdin on 5.1 set `$OutputEncoding = [System.Text.UTF8Encoding]::new()` first.",
|
|
@@ -272,7 +290,7 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
272
290
|
"--json writes the door's body to stdout, verbatim, and nothing else (an add's body also carries requestId); a refusal is { ok: false, error, detail, refusal? } and exits 1.",
|
|
273
291
|
"Progress lines on stderr start with [tag]; stdout is the answer (2>/dev/null hides them). Creating a client, syncing, Rebuild brief and merging owners are on the manager page only. Teaching: docs/architecture/tower-manager.md.",
|
|
274
292
|
"The bli-tower MCP server has the twins: manager_clients, manager_client, manager_home, manager_set_summary (summary), manager_run, manager_list, manager_queue, manager_show, manager_add, manager_edit,",
|
|
275
|
-
"manager_done, manager_drop, manager_reopen, manager_link, manager_approve, manager_dismiss, manager_proposal, manager_archive, manager_unarchive.",
|
|
293
|
+
"manager_done, manager_drop, manager_reopen, manager_link, manager_approve, manager_dismiss, manager_proposal, manager_archive, manager_unarchive, manager_meetings, manager_tie, manager_decide_tie (decide-tie), manager_decisions, manager_add_decision, manager_decide.",
|
|
276
294
|
],
|
|
277
295
|
],
|
|
278
296
|
[
|
|
@@ -426,14 +426,14 @@ export const TOWER_COMMAND_HELP = [
|
|
|
426
426
|
"move <id> (--to \"<shelf>\"|--clear-shelf) — put it on a different shelf, or take the shelf off.",
|
|
427
427
|
"place <id> [--apply] — say which shelf this note belongs on and why, from the meeting it is part of, who was in the room and its own name. Moves nothing without --apply, and never overrules a shelf somebody typed.",
|
|
428
428
|
"",
|
|
429
|
-
"Your own notetaker (BLI-4383). Fellow, Circleback and Wispr Flow today; Otter needs an Enterprise plan and Granola still runs on its own cron.",
|
|
429
|
+
"Your own notetaker (BLI-4383). Fellow, Circleback, Fireflies and Wispr Flow today; Otter needs an Enterprise plan and Granola still runs on its own cron.",
|
|
430
430
|
"accounts [--json] — the notetakers you have connected, with health and when each was last read, and under it who on the team feeds Tower with which notetaker (a member with none is named: that is the gap).",
|
|
431
431
|
"recordings <note-id> [--json] — every recording of the meeting this note is. When several notetakers or people recorded one meeting, the library shows ONE note, the recording with the longest transcript; the others are alternates kept by id (BLI-12926).",
|
|
432
432
|
"recording <recording-id> [--json] — one recording, text and all, shown or alternate.",
|
|
433
433
|
"accounts --show-webhook <account-id> — that account's webhook URL, where to paste it at the vendor, and whether a signing secret is stored (BLI-4394). Until one is, every request to that URL is refused, which is the right way to be off.",
|
|
434
434
|
"webhook <account-id> --secret-stdin — store the VENDOR's webhook signing secret. THE SECRET IS READ FROM STDIN, never a flag: printf \"%s\" \"<signing secret>\" | cockpit notes webhook <account-id> --secret-stdin",
|
|
435
435
|
"connect --provider wisprflow — Wispr Flow has no API key: this asks Tower to start the sign-in (the same door the Settings button, the MCP tool notes_connect_wisprflow and JARVIS use), prints and opens Wispr's approval page (Google, Apple, Microsoft or company single sign-on), and waits up to 10 minutes for Tower to report it connected, then Tower reads your Notetaker meetings from then on. Needs Notetaker and MCP access on your Wispr account. Exits 1 with a reason if nothing connects (BLI-12926, BLI-17973).",
|
|
436
|
-
"connect --provider fellow|circleback [--workspace <subdomain>] --key-stdin [--name <n>] — connect one. Fellow needs --workspace, its API is per workspace. THE API KEY IS READ FROM STDIN, never a flag: printf \"%s\" \"<key>\" | cockpit notes connect --provider fellow --workspace <subdomain> --key-stdin",
|
|
436
|
+
"connect --provider fellow|circleback|fireflies [--workspace <subdomain>] --key-stdin [--name <n>] — connect one. Fellow needs --workspace, its API is per workspace. THE API KEY IS READ FROM STDIN, never a flag: printf \"%s\" \"<key>\" | cockpit notes connect --provider fellow --workspace <subdomain> --key-stdin",
|
|
437
437
|
"detach <account-id> — take it off. The stored key goes with it; the meetings it already filed stay.",
|
|
438
438
|
"sync <account-id> — read that notetaker now. Prints the run's own counts and reason label, and exits 1 when the pass failed.",
|
|
439
439
|
"sync <account-id> --repair-truncated — re-read Wispr Flow notes that were stored with only the first part of a long transcript (the \"truncated, N chars remaining\" line), and rewrite them whole. Same door and the same bounded passes; prints found, fixed and failed with each failure's reason, and is safe to run again (BLI-18006).",
|
|
@@ -161,7 +161,7 @@ const COMMAND_HELP = new Map([
|
|
|
161
161
|
"",
|
|
162
162
|
" cockpit settings personal [--chat-model <key>] [--brief-model <key>]",
|
|
163
163
|
" Your teams and your two model choices. No flags = show.",
|
|
164
|
-
" cockpit settings switches [set <key> <value>]",
|
|
164
|
+
" cockpit settings switches [set <key> <value> | set <key> --clear]",
|
|
165
165
|
" Access and feature switches. Everyone may look; only an admin may set.",
|
|
166
166
|
" cockpit settings models [set --chat <key>] [--memory <id>] [--note-reader <id>]",
|
|
167
167
|
" The ORG-WIDE model map. Admin only, in both directions.",
|
|
@@ -15,7 +15,7 @@ export const PROJECT_HELP = [
|
|
|
15
15
|
"archive / restore — soft: tickets keep their project, the Linear mapping is untouched, nothing is deleted. There is no delete verb.",
|
|
16
16
|
"Every write returns a receipt with delivery tower_only: a project write never reaches Linear and never queues a Linear change. The same --request-id replays the first receipt; with another body it is request_id_reused.",
|
|
17
17
|
"--expected-updated-at is the updated_at you last read (shown by show and --json); a stale one is revision_conflict with the current project and changes nothing.",
|
|
18
|
-
"Refusals keep Tower's label: project_name_required, project_name_taken, project_owned_by_linear (a project
|
|
18
|
+
"Refusals keep Tower's label: project_name_required, project_name_taken, project_owned_by_linear (only a project still marked as named in Linear; projects imported from Linear are Tower's own), project_already_archived, project_not_archived, project_not_found_or_unreadable, revision_conflict, request_id_reused, invalid_body.",
|
|
19
19
|
"BLI-22360 planning fields on create and update: --lead <email|me|none>, --member <email[,email]|none> (the whole set; repeatable), --add-member / --remove-member (update), --status backlog|planned|started|paused|completed|canceled|none, --priority none|urgent|high|medium|low (or 0-4), --start / --target <YYYY-MM-DD|none> with --start-resolution / --target-resolution month|quarter|halfYear|year|day (a date without one is an exact day), --content-file <path> or --content <text|none> (the long body; --description stays the short summary). People are matched by exact email, never by name. show prints them, the project's milestones and their progress (see `cockpit milestone`). Refusals: project_status_invalid, project_dates_invalid, project_person_not_found.",
|
|
20
20
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
21
21
|
],
|
|
@@ -125,7 +125,7 @@ const COMMAND_SUMMARY_LINES = [
|
|
|
125
125
|
" cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]",
|
|
126
126
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
127
127
|
" cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
128
|
-
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
128
|
+
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value> | set <key> --clear] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
129
129
|
" cockpit team github-login add|remove <person> <login> | list <person> [--json]",
|
|
130
130
|
" cockpit team timezone set <person> <iana-zone|unknown> | list [--json]",
|
|
131
131
|
" cockpit team [members | invite <email> --role <role> [--team-id <uuid>] | role <userId> --role <role> [--yes] | device [list] | device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]] [--json]",
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit manager decisions | add-decision | decide` (BLI-22187, Client Books data item 5): the choices someone has to
|
|
3
|
+
* make for a client, from the terminal. Decisions are their own kind on the manager server (`manager.decisions`), not
|
|
4
|
+
* timeline items: the timeline records what happened, a decision waits for someone.
|
|
5
|
+
*
|
|
6
|
+
* cockpit manager decisions <client> [--status open|approved|dismissed|all]
|
|
7
|
+
* cockpit manager add-decision <client> --title <t> [--why <t>] [--owners 'A, B'] [--due YYYY-MM-DD] [--request-id <uuid>]
|
|
8
|
+
* cockpit manager decide <decision-id> approve|dismiss|reopen --version <v> [--note <t>]
|
|
9
|
+
*
|
|
10
|
+
* This file holds what only decisions need: their flags' readers, their request paths, their words and their next steps.
|
|
11
|
+
* The parser (`local-args-tower-manager.ts`), the planner (`manager-requests.ts`) and the runner (`manager.ts`) call in.
|
|
12
|
+
*
|
|
13
|
+
* Everything through the `/api/manager` door is an agent's write (BLI-21947): an add is stored as your agent's
|
|
14
|
+
* (`origin` ai, you as creator) and a decide as decided by your agent (`decidedByAgent`), never as you clicking it.
|
|
15
|
+
*/
|
|
16
|
+
import { dim } from "./cli-io.js";
|
|
17
|
+
export const DECISION_VERBS = ["decisions", "add-decision", "decide"];
|
|
18
|
+
export const isDecisionVerb = (verb) => DECISION_VERBS.includes(verb);
|
|
19
|
+
/** The server's statuses and list filter (`TOWER_MANAGER_DECISION_STATUSES`, `_FILTERS`), copied: held to them by `manager-enum-words.test.ts`. */
|
|
20
|
+
export const DECISION_FILTERS = ["open", "approved", "dismissed", "all"];
|
|
21
|
+
export const DECISION_VERDICTS = ["approve", "dismiss", "reopen"];
|
|
22
|
+
/** Words for each status, held to the server's list by `manager-enum-words.test.ts`. */
|
|
23
|
+
export const DECISION_STATUS_WORDS = { open: "waiting", approved: "approved", dismissed: "dismissed" };
|
|
24
|
+
/** The table's limits (`TOWER_MANAGER_DECISION_LIMITS`), checked here so a long text is refused in words before anything is sent. */
|
|
25
|
+
export const DECISION_TEXT_LIMITS = { title: 300, why: 2000, note: 500 };
|
|
26
|
+
const VERSION = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
27
|
+
function refuseTooLong(flag, text, max) {
|
|
28
|
+
const length = [...text].length;
|
|
29
|
+
if (length > max)
|
|
30
|
+
throw new Error(`${flag} is ${length.toLocaleString("en-US")} characters; at most ${max.toLocaleString("en-US")}. Shorten it.`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads the decision verbs' own flags into the shape. `readers` are the parser's own (ids, dates, owner lists and the
|
|
34
|
+
* blank test), passed in so a decision's flags get exactly the checks an action's do.
|
|
35
|
+
*/
|
|
36
|
+
export function readDecisionFields(verb, values, shape, word, readers) {
|
|
37
|
+
if (verb === "decisions") {
|
|
38
|
+
const status = values.flags.get("--status");
|
|
39
|
+
if (status !== undefined) {
|
|
40
|
+
if (!DECISION_FILTERS.includes(status))
|
|
41
|
+
throw new Error(`--status on decisions wants one of ${DECISION_FILTERS.join(", ")} (open is the default).`);
|
|
42
|
+
shape.decisionStatus = status;
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (verb === "add-decision") {
|
|
47
|
+
const title = values.flags.get("--title");
|
|
48
|
+
if (title === undefined || readers.isBlank(title))
|
|
49
|
+
throw new Error("`cockpit manager add-decision` needs --title <the decision to make>, like --title 'Approve the phase 2 scope?'.");
|
|
50
|
+
refuseTooLong("--title", title.trim(), DECISION_TEXT_LIMITS.title);
|
|
51
|
+
shape.title = title.trim();
|
|
52
|
+
const why = values.flags.get("--why");
|
|
53
|
+
if (why !== undefined && !readers.isBlank(why)) {
|
|
54
|
+
refuseTooLong("--why", why.trim(), DECISION_TEXT_LIMITS.why);
|
|
55
|
+
shape.why = why.trim();
|
|
56
|
+
}
|
|
57
|
+
const owners = values.flags.get("--owners");
|
|
58
|
+
const list = owners === undefined ? [] : readers.readOwnersList(owners);
|
|
59
|
+
if (list.length > 0)
|
|
60
|
+
shape.owners = list;
|
|
61
|
+
const due = values.flags.get("--due");
|
|
62
|
+
if (due !== undefined && due.trim().toLowerCase() !== "none")
|
|
63
|
+
shape.dueAt = readers.readDate(due, "--due");
|
|
64
|
+
const requestId = values.flags.get("--request-id");
|
|
65
|
+
if (requestId !== undefined)
|
|
66
|
+
shape.requestId = readers.readId(requestId, "A request");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// decide
|
|
70
|
+
if (!DECISION_VERDICTS.includes(word ?? ""))
|
|
71
|
+
throw new Error("The last word of `cockpit manager decide` is approve, dismiss or reopen.");
|
|
72
|
+
shape.verdict = word;
|
|
73
|
+
const version = values.flags.get("--version");
|
|
74
|
+
if (version === undefined) {
|
|
75
|
+
throw new Error("`cockpit manager decide` needs --version <version>: the decision's version exactly as `cockpit manager decisions <client>` prints it. It keeps you from deciding over a change someone else made.");
|
|
76
|
+
}
|
|
77
|
+
if (!VERSION.test(version))
|
|
78
|
+
throw new Error("--version is the decision's version exactly as `cockpit manager decisions <client>` prints it, like 2026-10-09T17:03:11.123456Z.");
|
|
79
|
+
shape.version = version;
|
|
80
|
+
const note = values.flags.get("--note");
|
|
81
|
+
if (note !== undefined) {
|
|
82
|
+
if (shape.verdict === "reopen")
|
|
83
|
+
throw new Error("--note goes with approve or dismiss; reopen clears the note.");
|
|
84
|
+
if (readers.isBlank(note))
|
|
85
|
+
throw new Error("--note is blank. Leave it out, or say in a line what was decided.");
|
|
86
|
+
refuseTooLong("--note", note.trim(), DECISION_TEXT_LIMITS.note);
|
|
87
|
+
shape.note = note.trim();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const BASE = "/api/manager";
|
|
91
|
+
const enc = encodeURIComponent;
|
|
92
|
+
/** The one request each decision verb sends: method, path and body (the planner's `ManagerPlan` minus its log). */
|
|
93
|
+
export function decisionRequest(command, clientId, newRequestId) {
|
|
94
|
+
if (command.action === "decisions") {
|
|
95
|
+
const query = command.decisionStatus ? `?status=${enc(command.decisionStatus)}` : "";
|
|
96
|
+
return { method: "GET", path: `${BASE}/clients/${enc(clientId ?? "")}/decisions${query}` };
|
|
97
|
+
}
|
|
98
|
+
if (command.action === "add-decision") {
|
|
99
|
+
const requestId = command.requestId ?? newRequestId();
|
|
100
|
+
const body = {
|
|
101
|
+
title: command.title,
|
|
102
|
+
...(command.why === undefined ? {} : { why: command.why }),
|
|
103
|
+
...(command.owners ? { owners: command.owners } : {}),
|
|
104
|
+
...(command.dueAt ? { dueAt: command.dueAt } : {}),
|
|
105
|
+
requestId,
|
|
106
|
+
};
|
|
107
|
+
return { method: "POST", path: `${BASE}/clients/${enc(clientId ?? "")}/decisions`, body, requestId };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
method: "POST",
|
|
111
|
+
path: `${BASE}/decisions/${enc(command.id ?? "")}/decide`,
|
|
112
|
+
body: { verdict: command.verdict, expectedUpdatedAt: command.version, ...(command.note === undefined ? {} : { note: command.note }) },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** "Oct 1" for a `YYYY-MM-DD` day as written; "Oct 16" for an instant on Vancouver's calendar. */
|
|
116
|
+
const day = (value) => {
|
|
117
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
118
|
+
const at = match ? new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]), 12)) : new Date(value);
|
|
119
|
+
if (Number.isNaN(at.getTime()))
|
|
120
|
+
return value;
|
|
121
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: match ? "UTC" : "America/Vancouver", month: "short", day: "numeric" }).format(at);
|
|
122
|
+
};
|
|
123
|
+
export function decisionRowLines(decision, color) {
|
|
124
|
+
const status = DECISION_STATUS_WORDS[decision.status] ?? decision.status;
|
|
125
|
+
const when = decision.status !== "open" ? "" : decision.dueAt ? `, ${decision.overdue ? "was due" : "decide by"} ${day(decision.dueAt)}` : ", no day set";
|
|
126
|
+
const lines = [`${decision.title} [${status}${when}]`];
|
|
127
|
+
if (decision.why)
|
|
128
|
+
lines.push(` Why: ${decision.why}`);
|
|
129
|
+
// An AI decision with a creator is a person's agent's add through the door (BLI-21947); the model's own would have none.
|
|
130
|
+
const added = decision.origin !== "ai" ? "" : decision.createdBy ? ` · added by ${decision.createdByName ?? "a teammate"}'s agent` : " · suggested by AI";
|
|
131
|
+
lines.push(` ${decision.owners && decision.owners.length > 0 ? `Decides: ${decision.owners.join(", ")}` : "Nobody named to decide"}${added}`);
|
|
132
|
+
if (decision.status !== "open" && decision.decidedAt) {
|
|
133
|
+
const who = `${decision.decidedByName ?? "a teammate"}${decision.decidedByAgent ? "'s agent" : ""}`;
|
|
134
|
+
lines.push(` ${status[0].toUpperCase()}${status.slice(1)} by ${who}, ${day(decision.decidedAt)}${decision.note ? `: "${decision.note}"` : ""}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push(dim(` id ${decision.id} version ${decision.updatedAt}`, color));
|
|
137
|
+
return lines;
|
|
138
|
+
}
|
|
139
|
+
export function decisionListLines(list, color) {
|
|
140
|
+
const c = list.counts;
|
|
141
|
+
const shown = list.items.length;
|
|
142
|
+
const which = list.status && list.status !== "all" ? `${DECISION_STATUS_WORDS[list.status] ?? list.status} ` : "";
|
|
143
|
+
const head = shown < list.total ? `Showing ${shown} of ${list.total} ${which}decisions (a list holds at most 500).` : `${list.total} ${which}decision${list.total === 1 ? "" : "s"}.`;
|
|
144
|
+
const lines = [head];
|
|
145
|
+
if (c)
|
|
146
|
+
lines.push(`The client has ${c.open} waiting (${c.overdue} past their day), ${c.approved} approved and ${c.dismissed} dismissed.`);
|
|
147
|
+
if (shown === 0)
|
|
148
|
+
lines.push(list.status === "open" || list.status === undefined ? "Nothing is waiting for a decision. `--status all` lists the decided ones too." : "No decisions match.");
|
|
149
|
+
for (const decision of list.items)
|
|
150
|
+
lines.push("", ...decisionRowLines(decision, color));
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
/** What a decision write did, in one sentence, then the decision as written. */
|
|
154
|
+
export function decisionWriteLines(command, body, color) {
|
|
155
|
+
const sentence = command.action === "add-decision"
|
|
156
|
+
? body.created === false ? "Already added earlier under this request id; nothing new was written." : "Added. It waits for a decision."
|
|
157
|
+
: body.unchanged === true ? "No change; nothing was written."
|
|
158
|
+
: command.verdict === "approve" ? "Approved, recorded as your agent's decision."
|
|
159
|
+
: command.verdict === "dismiss" ? "Dismissed, recorded as your agent's decision."
|
|
160
|
+
: "Reopened; it waits for a decision again.";
|
|
161
|
+
return [sentence, ...decisionRowLines(body.item, color)];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The next step after a refused decision write or read, in the terminal's words (`undefined`: the generic step applies).
|
|
165
|
+
* `noAnswer`: Tower gave no answer or a server error, so a decide may or may not have landed.
|
|
166
|
+
*/
|
|
167
|
+
export function decisionNextStep(command, reason, refusal, noAnswer) {
|
|
168
|
+
if (!isDecisionVerb(command.action))
|
|
169
|
+
return undefined;
|
|
170
|
+
const list = "`cockpit manager decisions <client> --status all`";
|
|
171
|
+
if (command.action === "decide") {
|
|
172
|
+
if (noAnswer)
|
|
173
|
+
return ` It may or may not have been saved: list the client's decisions with ${list} to see, and use the version it prints if you try again.`;
|
|
174
|
+
if (refusal === "item_changed")
|
|
175
|
+
return ` List the client's decisions again with ${list} and use the version it prints.`;
|
|
176
|
+
if (refusal === "decision_decided")
|
|
177
|
+
return ` Run \`cockpit manager decide ${command.id ?? "<decision-id>"} reopen --version <v>\` with the version ${list} prints, then approve or dismiss it.`;
|
|
178
|
+
if (reason === "not_found")
|
|
179
|
+
return ` Check the id with ${list}.`;
|
|
180
|
+
}
|
|
181
|
+
if (refusal === "request_reused")
|
|
182
|
+
return " That request id already made something else. Leave --request-id out to add a new decision.";
|
|
183
|
+
if (refusal === "item_hr_tagged")
|
|
184
|
+
return " Run it again without the tagged text; that part stays out of Tower.";
|
|
185
|
+
// An add with no answer keeps the generic step: rerun it with the same --request-id.
|
|
186
|
+
if (reason === "manager_unavailable" && command.action === "decisions")
|
|
187
|
+
return " Try again in a minute; if it says decisions are not set up, Tower's update that adds them has not landed yet.";
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
@@ -46,6 +46,7 @@ export function summaryHints(home) {
|
|
|
46
46
|
export const ITEM_REFUSAL_NAMES = [
|
|
47
47
|
"item_changed", "item_busy", "item_archived", "item_not_archived", "item_dismissed", "person_item_not_dismissable",
|
|
48
48
|
"no_proposal", "proposal_replaced", "timeline_not_live", "request_reused", "proposal_decided", "proposal_not_addable", "item_hr_tagged",
|
|
49
|
+
"decision_decided",
|
|
49
50
|
];
|
|
50
51
|
/** Refusals that mean "the item is not as you last saw it": reading it again is the way forward. */
|
|
51
52
|
export const RESEEN_REFUSALS = new Set(["item_changed", "item_busy", "proposal_decided", "no_proposal"]);
|
|
@@ -67,6 +68,7 @@ export function itemHints(id, flag) {
|
|
|
67
68
|
timeline_not_live: "`cockpit manager list <client> --timeline` lists the client's live timeline items.",
|
|
68
69
|
item_not_archived: "Nothing to bring back: it is in the lists already.",
|
|
69
70
|
item_hr_tagged: "Run the add or edit again without the tagged text; that part stays out of Tower.",
|
|
71
|
+
decision_decided: `Run \`cockpit manager decide ${id} reopen --version <v>\` (\`decisions <client> --status all\` prints its version), then approve or dismiss it.`,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
@@ -44,6 +44,24 @@
|
|
|
44
44
|
"unit": "items",
|
|
45
45
|
"source": "TOWER_MANAGER_HOME_LIMITS.comingUp"
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
"id": "decisionNoteMax",
|
|
49
|
+
"value": 500,
|
|
50
|
+
"unit": "characters",
|
|
51
|
+
"source": "TOWER_MANAGER_DECISION_LIMITS.noteMax"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "decisionTitleMax",
|
|
55
|
+
"value": 300,
|
|
56
|
+
"unit": "characters",
|
|
57
|
+
"source": "TOWER_MANAGER_DECISION_LIMITS.titleMax"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "decisionWhyMax",
|
|
61
|
+
"value": 2000,
|
|
62
|
+
"unit": "characters",
|
|
63
|
+
"source": "TOWER_MANAGER_DECISION_LIMITS.whyMax"
|
|
64
|
+
},
|
|
47
65
|
{
|
|
48
66
|
"id": "headlineMax",
|
|
49
67
|
"value": 140,
|
|
@@ -110,6 +128,12 @@
|
|
|
110
128
|
"unit": "characters",
|
|
111
129
|
"source": "routes/validation.ts: sourceNote .max(1000, ...)"
|
|
112
130
|
},
|
|
131
|
+
{
|
|
132
|
+
"id": "tiePageMax",
|
|
133
|
+
"value": 200,
|
|
134
|
+
"unit": "rows",
|
|
135
|
+
"source": "TOWER_MANAGER_MEETING_TIE_LIMITS.pageMax"
|
|
136
|
+
},
|
|
113
137
|
{
|
|
114
138
|
"id": "whatChanged",
|
|
115
139
|
"value": 100,
|
|
@@ -150,6 +174,7 @@
|
|
|
150
174
|
"summary_hr_tagged"
|
|
151
175
|
],
|
|
152
176
|
"item": [
|
|
177
|
+
"decision_decided",
|
|
153
178
|
"item_archived",
|
|
154
179
|
"item_busy",
|
|
155
180
|
"item_changed",
|
|
@@ -210,10 +235,13 @@
|
|
|
210
235
|
"blocker",
|
|
211
236
|
"bullets",
|
|
212
237
|
"client_id",
|
|
238
|
+
"cursor",
|
|
213
239
|
"date",
|
|
214
240
|
"decision",
|
|
241
|
+
"decision_id",
|
|
215
242
|
"detail",
|
|
216
243
|
"due",
|
|
244
|
+
"event_id",
|
|
217
245
|
"expected_updated_at",
|
|
218
246
|
"headline",
|
|
219
247
|
"health",
|
|
@@ -221,10 +249,14 @@
|
|
|
221
249
|
"kind",
|
|
222
250
|
"limit",
|
|
223
251
|
"manager_add",
|
|
252
|
+
"manager_add_decision",
|
|
224
253
|
"manager_approve",
|
|
225
254
|
"manager_archive",
|
|
226
255
|
"manager_client",
|
|
227
256
|
"manager_clients",
|
|
257
|
+
"manager_decide",
|
|
258
|
+
"manager_decide_tie",
|
|
259
|
+
"manager_decisions",
|
|
228
260
|
"manager_dismiss",
|
|
229
261
|
"manager_done",
|
|
230
262
|
"manager_drop",
|
|
@@ -232,21 +264,27 @@
|
|
|
232
264
|
"manager_home",
|
|
233
265
|
"manager_link",
|
|
234
266
|
"manager_list",
|
|
267
|
+
"manager_meetings",
|
|
235
268
|
"manager_proposal",
|
|
236
269
|
"manager_queue",
|
|
237
270
|
"manager_reopen",
|
|
238
271
|
"manager_run",
|
|
239
272
|
"manager_set_summary",
|
|
240
273
|
"manager_show",
|
|
274
|
+
"manager_tie",
|
|
241
275
|
"manager_unarchive",
|
|
242
276
|
"meeting_id",
|
|
243
277
|
"needs_review",
|
|
244
278
|
"next",
|
|
245
279
|
"no_owner",
|
|
280
|
+
"note",
|
|
281
|
+
"note_id",
|
|
282
|
+
"occurrence_start",
|
|
246
283
|
"overdue",
|
|
247
284
|
"owner",
|
|
248
285
|
"owner_name",
|
|
249
286
|
"owners",
|
|
287
|
+
"page_size",
|
|
250
288
|
"proposal_id",
|
|
251
289
|
"request_id",
|
|
252
290
|
"restore",
|
|
@@ -256,9 +294,12 @@
|
|
|
256
294
|
"source_note",
|
|
257
295
|
"source_url",
|
|
258
296
|
"status",
|
|
297
|
+
"tie_id",
|
|
259
298
|
"timeline",
|
|
260
299
|
"title",
|
|
261
|
-
"
|
|
300
|
+
"verdict",
|
|
301
|
+
"version",
|
|
302
|
+
"why"
|
|
262
303
|
],
|
|
263
304
|
"doorRefusedOperations": [
|
|
264
305
|
"create_client",
|