cloving 1.0.1 → 1.1.0

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.
Files changed (177) hide show
  1. package/README.md +145 -14
  2. package/dist/src/authority/commands.js +10 -2
  3. package/dist/src/authority/reconciliation.d.ts +2 -0
  4. package/dist/src/authority/reconciliation.js +10 -1
  5. package/dist/src/authority/transitions.d.ts +1 -1
  6. package/dist/src/authority/transitions.js +2 -0
  7. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  8. package/dist/src/broker/actor/authority-session.js +3 -4
  9. package/dist/src/chats/analysis-batches.d.ts +24 -0
  10. package/dist/src/chats/analysis-batches.js +83 -0
  11. package/dist/src/chats/analysis-contract.d.ts +80 -0
  12. package/dist/src/chats/analysis-contract.js +16 -0
  13. package/dist/src/chats/analysis-data.d.ts +3 -0
  14. package/dist/src/chats/analysis-data.js +85 -0
  15. package/dist/src/chats/analysis-input.d.ts +12 -0
  16. package/dist/src/chats/analysis-input.js +83 -0
  17. package/dist/src/chats/analysis-links.d.ts +2 -0
  18. package/dist/src/chats/analysis-links.js +25 -0
  19. package/dist/src/chats/analysis-prompt.d.ts +10 -0
  20. package/dist/src/chats/analysis-prompt.js +16 -0
  21. package/dist/src/chats/analysis-proposal.d.ts +2 -0
  22. package/dist/src/chats/analysis-proposal.js +32 -0
  23. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  24. package/dist/src/chats/analysis-provenance.js +99 -0
  25. package/dist/src/chats/analysis-shape.d.ts +3 -0
  26. package/dist/src/chats/analysis-shape.js +228 -0
  27. package/dist/src/chats/analysis.d.ts +3 -0
  28. package/dist/src/chats/analysis.js +42 -0
  29. package/dist/src/chats/corpus-input.d.ts +5 -0
  30. package/dist/src/chats/corpus-input.js +288 -0
  31. package/dist/src/chats/destination.js +3 -1
  32. package/dist/src/chats/import.d.ts +8 -6
  33. package/dist/src/chats/import.js +195 -23
  34. package/dist/src/chats/input-file.d.ts +3 -0
  35. package/dist/src/chats/input-file.js +32 -0
  36. package/dist/src/chats/normalize.d.ts +11 -2
  37. package/dist/src/chats/normalize.js +18 -2
  38. package/dist/src/chats/previous-import.d.ts +5 -0
  39. package/dist/src/chats/previous-import.js +34 -0
  40. package/dist/src/chats/reader.d.ts +7 -1
  41. package/dist/src/chats/reader.js +46 -15
  42. package/dist/src/chats/redact-import.d.ts +14 -0
  43. package/dist/src/chats/redact-import.js +141 -0
  44. package/dist/src/chats/redaction.d.ts +41 -0
  45. package/dist/src/chats/redaction.js +161 -0
  46. package/dist/src/chats/revision-input.d.ts +3 -0
  47. package/dist/src/chats/revision-input.js +180 -0
  48. package/dist/src/chats/revision.d.ts +48 -0
  49. package/dist/src/chats/revision.js +94 -0
  50. package/dist/src/chats/selection.d.ts +25 -0
  51. package/dist/src/chats/selection.js +109 -0
  52. package/dist/src/chats/source.d.ts +23 -0
  53. package/dist/src/chats/source.js +135 -0
  54. package/dist/src/chats/structure.d.ts +57 -0
  55. package/dist/src/chats/structure.js +216 -0
  56. package/dist/src/cli/arguments.d.ts +8 -0
  57. package/dist/src/cli/arguments.js +94 -13
  58. package/dist/src/cli/client.d.ts +1 -0
  59. package/dist/src/cli/client.js +38 -1
  60. package/dist/src/cli/commands.d.ts +3 -0
  61. package/dist/src/cli/commands.js +29 -11
  62. package/dist/src/cli/main.d.ts +2 -1
  63. package/dist/src/cli/main.js +61 -14
  64. package/dist/src/cli/render.d.ts +2 -0
  65. package/dist/src/cli/render.js +70 -2
  66. package/dist/src/cli/usage-render.d.ts +3 -0
  67. package/dist/src/cli/usage-render.js +28 -0
  68. package/dist/src/cli/usage.d.ts +3 -2
  69. package/dist/src/cli/usage.js +38 -6
  70. package/dist/src/cli/watch-actions.d.ts +10 -0
  71. package/dist/src/cli/watch-actions.js +58 -0
  72. package/dist/src/cli/watch-board-view.d.ts +32 -0
  73. package/dist/src/cli/watch-board-view.js +88 -15
  74. package/dist/src/cli/watch-board.js +6 -1
  75. package/dist/src/cli/watch-card-client.d.ts +1 -0
  76. package/dist/src/cli/watch-card-client.js +44 -0
  77. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  78. package/dist/src/cli/watch-card-forms.js +33 -0
  79. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  80. package/dist/src/cli/watch-card-reply.js +34 -0
  81. package/dist/src/cli/watch-card-view.d.ts +40 -0
  82. package/dist/src/cli/watch-card-view.js +86 -0
  83. package/dist/src/cli/watch-client.d.ts +1 -0
  84. package/dist/src/cli/watch-client.js +118 -0
  85. package/dist/src/cli/watch-model.js +31 -14
  86. package/dist/src/cli/watch-page.d.ts +1 -1
  87. package/dist/src/cli/watch-page.js +71 -85
  88. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  89. package/dist/src/cli/watch-pr-board.js +26 -0
  90. package/dist/src/cli/watch-pr-command.d.ts +15 -0
  91. package/dist/src/cli/watch-pr-command.js +37 -0
  92. package/dist/src/cli/watch-pr-identity.d.ts +20 -0
  93. package/dist/src/cli/watch-pr-identity.js +62 -0
  94. package/dist/src/cli/watch-pr-observer.d.ts +19 -0
  95. package/dist/src/cli/watch-pr-observer.js +108 -0
  96. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  97. package/dist/src/cli/watch-pr-settings.js +47 -0
  98. package/dist/src/cli/watch-reads.js +4 -0
  99. package/dist/src/cli/watch-render.d.ts +16 -2
  100. package/dist/src/cli/watch-render.js +10 -3
  101. package/dist/src/cli/watch-run-input.d.ts +14 -0
  102. package/dist/src/cli/watch-run-input.js +49 -0
  103. package/dist/src/cli/watch-server.d.ts +3 -0
  104. package/dist/src/cli/watch-server.js +44 -6
  105. package/dist/src/cli/watch-start-view.d.ts +18 -0
  106. package/dist/src/cli/watch-start-view.js +77 -0
  107. package/dist/src/cli/watch-turn.d.ts +1 -0
  108. package/dist/src/cli/watch-turn.js +7 -2
  109. package/dist/src/cli/watch-usage.d.ts +6 -0
  110. package/dist/src/cli/watch-usage.js +65 -0
  111. package/dist/src/cli/watch-view.d.ts +15 -0
  112. package/dist/src/cli/watch-view.js +61 -17
  113. package/dist/src/cli.js +48 -6
  114. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  115. package/dist/src/daemon/analysis-launch.js +81 -0
  116. package/dist/src/daemon/analysis-token.d.ts +6 -0
  117. package/dist/src/daemon/analysis-token.js +88 -0
  118. package/dist/src/daemon/analysis-worker.d.ts +22 -0
  119. package/dist/src/daemon/analysis-worker.js +219 -0
  120. package/dist/src/daemon/chats-files.d.ts +15 -0
  121. package/dist/src/daemon/chats-files.js +113 -0
  122. package/dist/src/daemon/chats-worker.d.ts +12 -0
  123. package/dist/src/daemon/chats-worker.js +200 -0
  124. package/dist/src/daemon/claude-turn.d.ts +10 -0
  125. package/dist/src/daemon/claude-turn.js +96 -15
  126. package/dist/src/daemon/claude-worker.d.ts +4 -1
  127. package/dist/src/daemon/claude-worker.js +99 -47
  128. package/dist/src/daemon/codex-stream.js +16 -0
  129. package/dist/src/daemon/daemon.d.ts +3 -0
  130. package/dist/src/daemon/daemon.js +16 -7
  131. package/dist/src/daemon/github-methods.js +4 -0
  132. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  133. package/dist/src/daemon/github-ticket-status.js +268 -0
  134. package/dist/src/daemon/issue-queue.d.ts +1 -0
  135. package/dist/src/daemon/issue-queue.js +115 -24
  136. package/dist/src/daemon/operation-journal.d.ts +1 -0
  137. package/dist/src/daemon/operation-journal.js +4 -2
  138. package/dist/src/daemon/retrying-turn.d.ts +9 -0
  139. package/dist/src/daemon/retrying-turn.js +185 -0
  140. package/dist/src/host/world-reconciliation.d.ts +2 -0
  141. package/dist/src/host/world-reconciliation.js +6 -0
  142. package/dist/src/host/world-runs.js +9 -1
  143. package/dist/src/service/run-handlers.js +15 -23
  144. package/dist/src/service/worker-totals.d.ts +3 -0
  145. package/dist/src/service/worker-totals.js +28 -0
  146. package/dist/src/ticket-status.d.ts +8 -0
  147. package/dist/src/ticket-status.js +45 -0
  148. package/dist/src/token-prices.d.ts +19 -0
  149. package/dist/src/token-prices.js +76 -0
  150. package/dist/src/worker-usage.d.ts +22 -0
  151. package/dist/src/worker-usage.js +44 -0
  152. package/dist/src/workflow-candidate.d.ts +29 -0
  153. package/dist/src/workflow-candidate.js +59 -0
  154. package/dist/src/workflow-contracts.d.ts +2 -0
  155. package/dist/src/workflow-contracts.js +27 -5
  156. package/dist/src/workflow-declaration.d.ts +1 -1
  157. package/dist/src/workflow-declaration.js +3 -1
  158. package/dist/src/workflows.d.ts +1 -0
  159. package/dist/src/workflows.js +1 -0
  160. package/dist/tools/self-host-processes.js +7 -2
  161. package/dist/tools/self-host-workflows.d.ts +1 -1
  162. package/dist/tools/self-host-workflows.js +2 -2
  163. package/dist/tools/self-host.js +6 -6
  164. package/dist/tools/self-update-host.d.ts +7 -0
  165. package/dist/tools/self-update-host.js +48 -1
  166. package/dist/tools/self-update.d.ts +1 -0
  167. package/dist/tools/self-update.js +85 -13
  168. package/dist/workflows/analyze-chats.d.ts +10 -0
  169. package/dist/workflows/analyze-chats.declaration.json +9 -0
  170. package/dist/workflows/analyze-chats.js +73 -0
  171. package/dist/workflows/develop-ticket.d.ts +1 -7
  172. package/dist/workflows/develop-ticket.js +42 -141
  173. package/dist/workflows/issue-labels.d.ts +3 -1
  174. package/dist/workflows/issue-labels.js +34 -0
  175. package/dist/workflows/ticket-prompts.d.ts +35 -0
  176. package/dist/workflows/ticket-prompts.js +133 -0
  177. package/package.json +5 -3
@@ -1,8 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { record, } from "../protocol.js";
4
- import { follow, mutate, send } from "./client.js";
5
- import { errorText, eventText, pendingSummary, rendered } from "./render.js";
4
+ import { follow, listRuns, mutate, send } from "./client.js";
5
+ import { errorText, eventText, pendingSummary, rendered, runOverview, statusJson, } from "./render.js";
6
6
  import { commandUsage } from "./usage.js";
7
7
  import { defaultWatchPort, watchServer } from "./watch-server.js";
8
8
  function errorObject(error, format) {
@@ -39,7 +39,9 @@ function presented(environment, outcome) {
39
39
  const { reply, format } = outcome;
40
40
  if (reply.kind === "error")
41
41
  return failed(environment, { error: reply.error, format });
42
- environment.terminal.out(format.json ? `${JSON.stringify(reply.result)}\n` : rendered(reply.result));
42
+ environment.terminal.out(format.json
43
+ ? `${JSON.stringify(statusJson(reply.result))}\n`
44
+ : rendered(reply.result));
43
45
  return 0;
44
46
  }
45
47
  async function requested(environment, exchange) {
@@ -135,6 +137,15 @@ async function status(environment, invocation) {
135
137
  const { runId, limit, after, json } = invocation;
136
138
  if (!json && runId === null)
137
139
  await summarized(environment);
140
+ if (!json && runId === null && limit === null && after === null) {
141
+ const reply = await listRuns(environment.runtimeDirectory);
142
+ return presented(environment, {
143
+ format: { json },
144
+ reply: reply.kind === "result"
145
+ ? { kind: "result", result: runOverview(reply.result) }
146
+ : reply,
147
+ });
148
+ }
138
149
  return requested(environment, {
139
150
  format: { json },
140
151
  request: {
@@ -235,14 +246,7 @@ function located(invocation, environment) {
235
246
  if (invocation.kind === "events")
236
247
  return events(environment, invocation);
237
248
  if (invocation.kind === "watch")
238
- return watchServer({
239
- runtimeDirectory: environment.runtimeDirectory,
240
- port: invocation.port ?? defaultWatchPort,
241
- client: environment.client ?? "cloving",
242
- announce: environment.terminal.out,
243
- complain: environment.terminal.error,
244
- stopped: environment.interrupted(),
245
- });
249
+ return watch(environment, invocation);
246
250
  return requested(environment, {
247
251
  format: { json: invocation.json },
248
252
  request: {
@@ -251,3 +255,17 @@ function located(invocation, environment) {
251
255
  },
252
256
  });
253
257
  }
258
+ function watch(environment, invocation) {
259
+ return watchServer({
260
+ actions: environment.watchActions ?? "on",
261
+ ...(environment.watchPr === undefined
262
+ ? {}
263
+ : { pullRequests: environment.watchPr }),
264
+ runtimeDirectory: environment.runtimeDirectory,
265
+ port: invocation.port ?? defaultWatchPort,
266
+ client: environment.client ?? "cloving",
267
+ announce: environment.terminal.out,
268
+ complain: environment.terminal.error,
269
+ stopped: environment.interrupted(),
270
+ });
271
+ }
@@ -1 +1,2 @@
1
- export declare function main(argumentsList: readonly string[]): Promise<void>;
1
+ import type { WatchPrSettings } from "./watch-pr-settings.ts";
2
+ export declare function main(argumentsList: readonly string[], watchPr?: WatchPrSettings): Promise<void>;
@@ -1,34 +1,43 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import packageMetadata from "#package" with { type: "json" };
3
+ import { redactImport } from "../chats/redact-import.js";
4
+ import { emptyCounts } from "../chats/redaction.js";
3
5
  import { importChats } from "../chats/import.js";
4
6
  import { parseInvocation } from "./arguments.js";
5
7
  import { runtimeCommand } from "./commands.js";
6
8
  import { clientCommand, runtimeDirectory } from "./runtime-directory.js";
7
- import { chatsImportUsage, usage, validateUsage } from "./usage.js";
9
+ import { chatsRedactUsage, chatsImportUsage, usage, validateUsage, } from "./usage.js";
8
10
  import { validateWorkflow } from "./validate.js";
9
- export async function main(argumentsList) {
11
+ const helpTexts = {
12
+ help: usage,
13
+ version: `${packageMetadata.version}\n`,
14
+ "validate-help": validateUsage,
15
+ "chats-import-help": chatsImportUsage,
16
+ "chats-redact-help": chatsRedactUsage,
17
+ };
18
+ function helpInvocation(invocation) {
19
+ return Object.hasOwn(helpTexts, invocation.kind);
20
+ }
21
+ export async function main(argumentsList, watchPr) {
10
22
  const invocation = parseInvocation(argumentsList);
23
+ if (refuseChatPlatform(invocation))
24
+ return;
25
+ if (helpInvocation(invocation)) {
26
+ process.stdout.write(helpTexts[invocation.kind]);
27
+ return;
28
+ }
11
29
  if (invocation.kind === "usage-error") {
12
30
  presentUsageError(invocation);
13
31
  }
14
32
  else if (invocation.kind === "validate") {
15
33
  await validationCommand(invocation);
16
34
  }
17
- else if (invocation.kind === "version") {
18
- process.stdout.write(`${packageMetadata.version}\n`);
19
- }
20
- else if (invocation.kind === "help") {
21
- process.stdout.write(usage);
22
- }
23
- else if (invocation.kind === "validate-help") {
24
- process.stdout.write(validateUsage);
25
- }
26
- else if (invocation.kind === "chats-import-help") {
27
- process.stdout.write(chatsImportUsage);
35
+ else if (invocation.kind === "chats-redact") {
36
+ await redactionCommand(invocation.importId);
28
37
  }
29
38
  else if (invocation.kind === "chats-import") {
30
39
  const result = await importChats({
31
- paths: invocation.paths,
40
+ ...invocation,
32
41
  workingDirectory: process.cwd(),
33
42
  runtimeDirectory: runtimeDirectory(process.env),
34
43
  });
@@ -38,6 +47,10 @@ export async function main(argumentsList) {
38
47
  else {
39
48
  process.exitCode = await runtimeCommand(invocation, {
40
49
  runtimeDirectory: runtimeDirectory(process.env),
50
+ ...(process.env["CLOVING_WATCH_ACTIONS"] === undefined
51
+ ? {}
52
+ : { watchActions: process.env["CLOVING_WATCH_ACTIONS"] }),
53
+ ...(watchPr === undefined ? {} : { watchPr }),
41
54
  client: clientCommand(process.env),
42
55
  workingDirectory: process.cwd(),
43
56
  terminal: {
@@ -54,6 +67,16 @@ export async function main(argumentsList) {
54
67
  });
55
68
  }
56
69
  }
70
+ function refuseChatPlatform(invocation) {
71
+ if (process.platform === "darwin")
72
+ return false;
73
+ if (invocation.kind !== "chats-import" && invocation.kind !== "chats-redact")
74
+ return false;
75
+ process.stdout.write(`${JSON.stringify({ code: "platform-unsupported" })}\n`);
76
+ process.stderr.write("platform-unsupported: Chat commands require macOS.\n");
77
+ process.exitCode = 1;
78
+ return true;
79
+ }
57
80
  function presentUsageError(invocation) {
58
81
  if (invocation.json)
59
82
  process.stdout.write(`${JSON.stringify({ schemaVersion: 1, kind: "usage-error", code: invocation.code })}\n`);
@@ -78,3 +101,27 @@ function present(report, format) {
78
101
  for (const error of report.errors)
79
102
  process.stderr.write(`${error.code}: ${error.file}: ${error.detail}\n`);
80
103
  }
104
+ async function redactionCommand(importId) {
105
+ let privateTexts = [];
106
+ try {
107
+ const setting = process.env["CLOVING_PRIVATE_TEXTS"];
108
+ if (setting !== undefined)
109
+ privateTexts = JSON.parse(setting);
110
+ if (!Array.isArray(privateTexts) ||
111
+ !privateTexts.every((item) => typeof item === "string" && item.trim() !== ""))
112
+ throw new Error("invalid-settings");
113
+ }
114
+ catch {
115
+ process.stdout.write(`${JSON.stringify({ code: "invalid-settings", importId, recordCount: 0, counts: emptyCounts(), totalCount: 0 })}\n`);
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+ const result = await redactImport({
120
+ importId,
121
+ workingDirectory: process.cwd(),
122
+ runtimeDirectory: runtimeDirectory(process.env),
123
+ settings: { home: process.env["HOME"] || null, privateTexts },
124
+ });
125
+ process.stdout.write(`${JSON.stringify(result)}\n`);
126
+ process.exitCode = result.code === "redaction-complete" ? 0 : 1;
127
+ }
@@ -1,5 +1,7 @@
1
1
  import { type Fields } from "../protocol.ts";
2
2
  export declare function errorText(error: Fields): string;
3
+ export declare function runOverview(result: Fields): Fields;
4
+ export declare function statusJson(result: Fields): Fields;
3
5
  export declare function runStatus(result: Fields): readonly string[];
4
6
  export declare function decisionLines(decision: unknown): readonly string[];
5
7
  export declare function decisionList(result: Fields): readonly string[];
@@ -1,3 +1,4 @@
1
+ import { priceText, usageText, totalsSuffix } from "./usage-render.js";
1
2
  import { record } from "../protocol.js";
2
3
  function show(value) {
3
4
  return typeof value === "string" ? value : JSON.stringify(value);
@@ -58,13 +59,38 @@ function admitted(result) {
58
59
  function runLine(run) {
59
60
  return ` ${show(part(run, "runId"))} ${show(part(run, "workflowId"))} ${show(part(run, "condition"))} revision ${show(part(run, "revision"))} latest at ${show(part(run, "latestAt") ?? "none")}`;
60
61
  }
62
+ function ended(run) {
63
+ return ["completed", "failed", "cancelled"].includes(show(run["state"]));
64
+ }
65
+ function newest(first, second) {
66
+ return show(second["latestAt"] ?? "").localeCompare(show(first["latestAt"] ?? ""));
67
+ }
68
+ export function runOverview(result) {
69
+ const listed = result["runs"];
70
+ const runs = Array.isArray(listed)
71
+ ? listed.map(record).filter((run) => run !== undefined)
72
+ : [];
73
+ const active = runs.filter((run) => !ended(run)).toSorted(newest);
74
+ const finished = runs.filter(ended).toSorted(newest);
75
+ return {
76
+ ...result,
77
+ runs: [...active, ...finished.slice(0, 20)],
78
+ hiddenRuns: Math.max(0, finished.length - 20),
79
+ };
80
+ }
61
81
  function runList(result) {
62
82
  const runs = result["runs"];
63
83
  const lines = Array.isArray(runs) ? runs.map(runLine) : [];
84
+ const hiddenRuns = result["hiddenRuns"];
64
85
  return [
65
86
  `Workflows: ${visible(listing(result["workflows"]))}`,
66
87
  "Runs:",
67
88
  ...(lines.length === 0 ? [" none"] : lines),
89
+ ...(typeof hiddenRuns === "number" && hiddenRuns > 0
90
+ ? [
91
+ `${String(hiddenRuns)} older runs: use --limit and --after to see them`,
92
+ ]
93
+ : []),
68
94
  ];
69
95
  }
70
96
  function observation(value) {
@@ -82,17 +108,57 @@ function outcome(value) {
82
108
  const kind = visible(part(value, "kind"));
83
109
  return typeof reason === "string" ? `${kind}: ${visible(reason)}` : kind;
84
110
  }
111
+ function workerLines(value) {
112
+ return (Array.isArray(value) ? value : []).flatMap((turn) => [
113
+ `Worker turn ${visible(part(turn, "operationId"))}: ${priceText(part(turn, "costUsd"), part(turn, "estimatedCostUsd"))}`,
114
+ ` ${usageText(part(turn, "usage"))}`,
115
+ ]);
116
+ }
117
+ export function statusJson(result) {
118
+ const run = record(result["run"]);
119
+ if (result["kind"] !== "run-status" || run === undefined)
120
+ return result;
121
+ const turns = run["workerTurns"];
122
+ return {
123
+ ...result,
124
+ run: {
125
+ ...run,
126
+ costDisplay: priceText(run["claudeCostUsd"], run["estimatedCostUsd"]),
127
+ ...(Array.isArray(turns)
128
+ ? {
129
+ workerTurns: turns.map((turn) => {
130
+ const fields = record(turn);
131
+ if (fields === undefined)
132
+ return turn;
133
+ return {
134
+ ...fields,
135
+ costDisplay: priceText(fields["costUsd"], fields["estimatedCostUsd"]),
136
+ };
137
+ }),
138
+ }
139
+ : {}),
140
+ },
141
+ };
142
+ }
85
143
  export function runStatus(result) {
86
144
  const run = result["run"];
87
145
  const reason = part(run, "reason");
88
146
  const recovery = part(run, "recovery");
89
147
  const at = part(run, "nextActionAt") ?? null;
90
- const cost = part(run, "claudeCostUsd");
148
+ const suffix = totalsSuffix(part(run, "state"));
91
149
  return [
92
150
  `Run ${show(part(run, "runId"))} (${show(part(run, "workflowId"))})`,
93
151
  `Title: ${visible(part(run, "title") ?? "none")}`,
94
152
  `State: ${show(part(run, "state"))}`,
95
153
  `Outcome: ${outcome(part(run, "outcome"))}`,
154
+ ...(typeof part(part(run, "outcome"), "ticketStatus") === "string"
155
+ ? [
156
+ `Ticket status: ${visible(part(part(run, "outcome"), "ticketStatus"))}`,
157
+ ]
158
+ : []),
159
+ ...(part(part(run, "outcome"), "issueUpdate") === "failed"
160
+ ? ["Issue status update failed; check the GitHub operation."]
161
+ : []),
96
162
  `Condition: ${show(part(run, "condition"))}`,
97
163
  `Reason: ${show(part(reason, "code"))}: ${show(part(reason, "detail"))}`,
98
164
  `Revision: ${show(part(run, "revision"))}, latest sequence ${show(part(run, "latestSequence"))}`,
@@ -101,7 +167,9 @@ export function runStatus(result) {
101
167
  `Evidence: ${listing(part(run, "evidence"))}`,
102
168
  `Last confirmed observation: ${observation(part(run, "lastConfirmedObservation"))}`,
103
169
  `Recovery: ${show(part(recovery, "attempts"))} of ${show(part(recovery, "limit") ?? "no limit")}`,
104
- `Claude cost: ${(typeof cost === "number" ? cost : 0).toFixed(2)} USD`,
170
+ `Cost${suffix}: ${priceText(part(run, "claudeCostUsd"), part(run, "estimatedCostUsd"))}`,
171
+ `Token usage${suffix}: ${usageText(part(run, "usage"))}`,
172
+ ...workerLines(part(run, "workerTurns")),
105
173
  `Next action: ${show(part(run, "nextAction") ?? "none")}${at === null ? "" : ` at ${show(at)}`}`,
106
174
  `Human input can help: ${part(run, "humanInputCanHelp") === true ? "yes" : "no"}`,
107
175
  ...feedbackLines(part(run, "feedback")),
@@ -0,0 +1,3 @@
1
+ export declare function priceText(value: unknown, estimate?: unknown): string;
2
+ export declare function usageText(value: unknown): string;
3
+ export declare function totalsSuffix(state: unknown): string;
@@ -0,0 +1,28 @@
1
+ import { priceOf, usageOf } from "../worker-usage.js";
2
+ export function priceText(value, estimate) {
3
+ const price = priceOf(value);
4
+ if (price === null && priceOf(estimate) !== null)
5
+ return `≈ ${priceText(estimate)} (estimate)`;
6
+ if (price === null)
7
+ return "Price not known";
8
+ return `${price.toLocaleString("en-US", {
9
+ useGrouping: false,
10
+ minimumFractionDigits: 2,
11
+ maximumFractionDigits: 2,
12
+ })} USD`;
13
+ }
14
+ export function usageText(value) {
15
+ const usage = usageOf(value);
16
+ return [
17
+ ["Input tokens", usage.input_tokens],
18
+ ["Cache read tokens", usage.cache_read_input_tokens],
19
+ ["Output tokens", usage.output_tokens],
20
+ ]
21
+ .map(([label, count]) => `${String(label)}: ${count === null ? "Token usage not known" : String(count)}`)
22
+ .join(" · ");
23
+ }
24
+ export function totalsSuffix(state) {
25
+ return ["completed", "failed", "cancelled"].includes(String(state))
26
+ ? ""
27
+ : " so far";
28
+ }
@@ -1,4 +1,5 @@
1
1
  export declare const commandUsage: Readonly<Record<string, string>>;
2
- export declare const usage = "Usage: cloving [--help] [--version]\n cloving workflows validate <manifest-path> [--input <json-file>] [--json]\n cloving <command> [--help]\n\nCommands:\n workflows validate Check a compiled workflow manifest, its artifacts and optional runtime input.\n chats import Import explicitly selected Claude Code transcripts into private runtime files.\n run Submit one run of an admitted workflow.\n status List the runs or show one run.\n questions List the pending decisions.\n answer Answer one design question.\n approve Approve one pending approval.\n deny Deny one pending approval.\n pause Stop new dispatch for a run.\n resume Resume a paused run.\n drop Cancel a run and keep its files.\n events Print the committed events.\n watch Serve a page that shows the runs live.\n start Start the service for a repository.\n\nThe runtime commands need a Cloving service. CLOVING_RUNTIME_DIR names its runtime directory.\nUse npm exec -- cloving ... for a project-local installation.\n";
3
- export declare const chatsImportUsage = "Usage: cloving chats import [--] <path> [<path> ...]\n\nImports only the selected Claude Code JSONL files. Relative paths use the current directory.\nThis local command needs no service. It does not expand globs or scan directories.\nThe file limit is 10 MiB (10,485,760 bytes). Links and nonregular files are refused.\nPrivate corpus.jsonl and coverage.json files go into a new import directory under the runtime directory's chats directory.\nThe runtime selection is CLOVING_RUNTIME_DIR, XDG_RUNTIME_DIR, XDG_STATE_HOME, then HOME.\nOutput contains only counts, fixed codes and an opaque import ID. Source paths stay in the private manifest.\nExit codes: 0 for complete coverage, 1 for refused sources or output failure, 2 for invalid usage.\n";
2
+ export declare const usage = "Usage: cloving [--help] [--version]\n cloving workflows validate <manifest-path> [--input <json-file>] [--json]\n cloving <command> [--help]\n\nCommands:\n workflows validate Check a compiled workflow manifest, its artifacts and optional runtime input.\n chats redact Redact one imported chat corpus into private runtime files.\n chats import Import explicitly selected Claude Code transcripts into private runtime files.\n run Submit one run of an admitted workflow.\n status List the runs or show one run.\n questions List the pending decisions.\n answer Answer one design question.\n approve Approve one pending approval.\n deny Deny one pending approval.\n pause Stop new dispatch for a run.\n resume Resume a paused run.\n drop Cancel a run and keep its files.\n events Print the committed events.\n watch Serve a page that shows the runs live.\n start Start the service for a repository.\n\nThe runtime commands need a Cloving service. CLOVING_RUNTIME_DIR names its runtime directory.\nUse npm exec -- cloving ... for a project-local installation.\n";
3
+ export declare const chatsImportUsage = "Usage: cloving chats import [--] <path> [<path> ...]\n cloving chats import --root <projects-folder> [--root <projects-folder> ...] [--exclude-project <name> ...] [-- <path> ...]\n\nImports only the selected Claude Code JSONL files. Relative paths use the current directory.\nThis local command needs no service. It scans only explicitly named roots and does not expand globs or discover configuration.\nPositional files come first, retaining order and repetitions. Roots sort by canonical path, then entries by relative path in JavaScript string order.\nExclusions match direct project folder names exactly and prune their children. Exclusions require a root.\nScans accept lowercase .jsonl filenames, attempt at most 100 eligible files across roots, and stop before entering folders at depth four.\nLater file candidates and depth-limited folders are pending. Select pending files explicitly in a later import.\nLinked components, containment changes and roots in Git repositories are refused. Scan protections require the supported macOS kernel.\nThe file limit is 10 MiB (10,485,760 bytes). Links and nonregular files are refused.\nThe total normalized corpus limit is 50 MiB (52,428,800 UTF-8 bytes), including newlines and repeated selections.\nExceeding that total returns output-failed with exit code 1 and no completed coverage manifest; partial files may remain.\n--previous-import <IMPORT_ID> compares against one explicitly named UUID v4 import before --.\nThe earlier coverage and optional revision must be valid, completed private metadata; otherwise previous-import-refused returns exit 1 with no new import.\nA comparison writes private revision.json with ordered selection evidence and six status counts. Matching uses source path and selected root path; repetitions pair in order.\nByte-verified extensions are extended; shorter sources are truncated. Legacy imports without sizes can establish unchanged digests but cannot prove extension or truncation.\nMissing or truncated evidence makes the import partial. History completeness remains unknown.\nPrivate corpus.jsonl, structure.json and coverage.json files go into a new import directory under the runtime directory's chats directory.\nThe runtime selection is CLOVING_RUNTIME_DIR, XDG_RUNTIME_DIR, XDG_STATE_HOME, then HOME.\nOutput contains only counts, fixed codes and an opaque import ID. Source paths stay in the private manifest.\nStructure preserves branches and marks known copies without removing records. History completeness remains unknown.\nRoot imports write coverage version 2 and add excluded and pending entry counts; positional-only imports retain coverage version 1.\nExit codes: 0 for no refused, pending, missing or truncated entries (exclusions alone permit success without comparison), 1 for partial coverage or processing failure, 2 for invalid usage.\n";
4
4
  export declare const validateUsage = "Usage: cloving workflows validate <manifest-path> [--input <json-file>] [--json]\n\nChecks a Cloving workflow manifest, its compiled SDK artifacts, declared resources and toolchain, and an optional runtime input file. Relative paths resolve against the current directory.\n\nOptions:\n --input <json-file> Validate this runtime input file.\n --json Print one versioned JSON result on stdout.\n\nValidation does not admit a run, dispatch effects, import workflow code or call a model.\n\nExample:\n cloving workflows validate cloving.workflow.json --input input.json\n";
5
+ export declare const chatsRedactUsage = "Usage: cloving chats redact <IMPORT_ID>\n\nRedact one existing import selected by its UUID v4 import ID. No service is required.\nWrite redacted.jsonl and redaction.json beside the original corpus with mode 0600.\nHOME supplies the home folder. CLOVING_PRIVATE_TEXTS is an optional JSON list of nonblank strings.\nOriginals and metadata remain private. Redaction does not authorize model use or guarantee removal of every private value.\nExit codes: 0 complete, 1 processing failure, 2 invalid usage.\n";
@@ -50,28 +50,36 @@ Serves a page on http://127.0.0.1:<port>/ that shows the runs live: the state,
50
50
  the events, the work of the Claude worker and each pending decision.
51
51
  The default port is 3663. The page is read only. Stop it with Control-C.
52
52
  `,
53
- start: `Usage: cloving start [--repository <directory>]
53
+ start: `Usage: cloving start [--repository <directory>] [--reset-queue]
54
54
 
55
55
  Starts the Cloving service for one git repository, with the workflow host,
56
56
  the Claude worker and the watch page. The default repository is the current
57
57
  directory. This command works in a development checkout of Cloving.
58
58
  Stop it with Control-C.
59
59
 
60
+ The queue continues after unsuccessful runs and stops after three runs in a row
61
+ without a successful handoff. A successful handoff resets the count; ordinary restarts preserve the count.
62
+ Stop the service, inspect the failures and reconcile affected issues, then start with --reset-queue and the same queue settings to clear the failure streak once. Tracked commands are preserved.
63
+
60
64
  The command reads these settings from the environment:
61
65
  CLOVING_RUNTIME_DIR The path of the runtime directory. The default is ~/.local/state/cloving/run, if XDG_RUNTIME_DIR and XDG_STATE_HOME are not set.
62
66
  CLOVING_TICKET_COMMAND A JSON list of a command and its arguments. The command gets a ticket ID as its last argument and prints the ticket. The setting has no default, and a run with --ticket needs it.
63
67
  CLOVING_NOTIFY_COMMAND A JSON list of a command and its arguments. Every 15 seconds, new decisions, blocked runs and failed runs send JSON on standard input with no added argument. The setting has no default.
64
68
  CLOVING_NEXT_COMMAND A JSON list of a command and its arguments. The command runs with no added argument and prints one ready ticket ID for --next. The setting has no default.
65
- CLOVING_QUEUE_SECONDS A whole number from 10 to 3600 seconds between develop-issue queue checks; requires CLOVING_NEXT_COMMAND and CLOVING_TICKET_COMMAND. Unset means no queue. The queue stops after a failed, cancelled or unsuccessful run until restart; manual develop-issue submissions are refused with workflow-active while an issue run is active.
69
+ CLOVING_QUEUE_SECONDS A whole number from 10 to 3600 seconds between develop-issue queue checks; requires CLOVING_NEXT_COMMAND and CLOVING_TICKET_COMMAND. Unset means no queue. The queue stops after three runs in a row without a successful handoff; manual develop-issue submissions are refused with workflow-active while an issue run is active.
66
70
  CLOVING_SELF_UPDATE_SECONDS A whole number from 30 to 3600 seconds between checks of the own checkout; unset means no update. An update stops the daemon and restarts with new code. A change of package-lock.json needs the operator to update and install by hand.
71
+ CLOVING_ANALYSIS_TOKEN_COMMAND A JSON list of a program and its nonempty string arguments, run without a shell just before analysis. It has limits of 10 seconds and 4096 combined stdout/stderr bytes; stdout must be one printable ASCII token without spaces, at most 512 bytes after one final line break. Only analysis receives CLAUDE_CODE_OAUTH_TOKEN; API keys are excluded. There is no default.
67
72
  CLOVING_CLAUDE_COMMAND A JSON list of a command and its arguments for the Claude worker. The default is the claude command with a limit of 5 USD for each turn.
68
73
  CLOVING_PROFILES The path of a JSON file of operator-defined Claude turn profiles, read once at start. The setting has no default.
74
+ CLOVING_TOKEN_PRICES A JSON object from model names to input, cachedInput and output USD prices per million tokens for estimates; entries override matching built-in prices.
69
75
  CLOVING_WORKER The worker for each turn: claude or codex. The default is claude. Other values stop the start with invalid-worker.
70
76
  CLOVING_CODEX_COMMAND A JSON list of a command and its arguments, used only when CLOVING_WORKER is codex; CLOVING_CLAUDE_COMMAND is then ignored. The default is codex exec --json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check -, with no sandbox and no approval prompts.
77
+ CLOVING_WATCH_REPOSITORY The service repository context for the watch board, set by start from --repository. Its local origin supplies the GitHub identity. There is no current-directory default.
78
+ CLOVING_WATCH_ACTIONS The value off removes the board run form and its write endpoint. The default is on.
71
79
  CLOVING_WATCH_PORT The port number of the watch page. The value off starts no watch page. The default is 3663.
72
80
  CLOVING_OWNERSHIP_LEASE_SECONDS The time in seconds of the ownership lease of a started workflow step. The default is 15.
73
81
  CLOVING_HOST_BOUND The largest number of queue messages that the workflow host handles at one time, from 1 to 64. The default is 8.
74
- CLOVING_GH_COMMAND A JSON list of a command and its arguments for the GitHub CLI, which the workflow develop-issue uses for pull requests and develop-prd uses for issues. The default is gh.
82
+ CLOVING_GH_COMMAND A JSON list of a command and its arguments for the GitHub CLI, which the workflow develop-issue uses for pull requests and develop-prd uses for issues. The watch board also uses it to read pull request status. The default is gh.
75
83
  CLOVING_CHECK_COMMAND A JSON list of a command and its arguments that the workflow develop-issue runs in the work tree before a merge. A merge does not occur if the command fails. The setting has no default, and with no setting the merge occurs with no check.
76
84
  CLOVING_PRIVATE_TEXTS A JSON list of texts that the workflow develop-issue does not push in a file or in a commit message. The workflow develop-prd requires the list before posting an issue. The list adds to the path of the home folder, and the case of the letters does not matter. The setting has no default.
77
85
  `,
@@ -82,6 +90,7 @@ export const usage = `Usage: cloving [--help] [--version]
82
90
 
83
91
  Commands:
84
92
  workflows validate Check a compiled workflow manifest, its artifacts and optional runtime input.
93
+ chats redact Redact one imported chat corpus into private runtime files.
85
94
  chats import Import explicitly selected Claude Code transcripts into private runtime files.
86
95
  run Submit one run of an admitted workflow.
87
96
  status List the runs or show one run.
@@ -100,14 +109,29 @@ The runtime commands need a Cloving service. CLOVING_RUNTIME_DIR names its runti
100
109
  Use npm exec -- cloving ... for a project-local installation.
101
110
  `;
102
111
  export const chatsImportUsage = `Usage: cloving chats import [--] <path> [<path> ...]
112
+ cloving chats import --root <projects-folder> [--root <projects-folder> ...] [--exclude-project <name> ...] [-- <path> ...]
103
113
 
104
114
  Imports only the selected Claude Code JSONL files. Relative paths use the current directory.
105
- This local command needs no service. It does not expand globs or scan directories.
115
+ This local command needs no service. It scans only explicitly named roots and does not expand globs or discover configuration.
116
+ Positional files come first, retaining order and repetitions. Roots sort by canonical path, then entries by relative path in JavaScript string order.
117
+ Exclusions match direct project folder names exactly and prune their children. Exclusions require a root.
118
+ Scans accept lowercase .jsonl filenames, attempt at most 100 eligible files across roots, and stop before entering folders at depth four.
119
+ Later file candidates and depth-limited folders are pending. Select pending files explicitly in a later import.
120
+ Linked components, containment changes and roots in Git repositories are refused. Scan protections require the supported macOS kernel.
106
121
  The file limit is 10 MiB (10,485,760 bytes). Links and nonregular files are refused.
107
- Private corpus.jsonl and coverage.json files go into a new import directory under the runtime directory's chats directory.
122
+ The total normalized corpus limit is 50 MiB (52,428,800 UTF-8 bytes), including newlines and repeated selections.
123
+ Exceeding that total returns output-failed with exit code 1 and no completed coverage manifest; partial files may remain.
124
+ --previous-import <IMPORT_ID> compares against one explicitly named UUID v4 import before --.
125
+ The earlier coverage and optional revision must be valid, completed private metadata; otherwise previous-import-refused returns exit 1 with no new import.
126
+ A comparison writes private revision.json with ordered selection evidence and six status counts. Matching uses source path and selected root path; repetitions pair in order.
127
+ Byte-verified extensions are extended; shorter sources are truncated. Legacy imports without sizes can establish unchanged digests but cannot prove extension or truncation.
128
+ Missing or truncated evidence makes the import partial. History completeness remains unknown.
129
+ Private corpus.jsonl, structure.json and coverage.json files go into a new import directory under the runtime directory's chats directory.
108
130
  The runtime selection is CLOVING_RUNTIME_DIR, XDG_RUNTIME_DIR, XDG_STATE_HOME, then HOME.
109
131
  Output contains only counts, fixed codes and an opaque import ID. Source paths stay in the private manifest.
110
- Exit codes: 0 for complete coverage, 1 for refused sources or output failure, 2 for invalid usage.
132
+ Structure preserves branches and marks known copies without removing records. History completeness remains unknown.
133
+ Root imports write coverage version 2 and add excluded and pending entry counts; positional-only imports retain coverage version 1.
134
+ Exit codes: 0 for no refused, pending, missing or truncated entries (exclusions alone permit success without comparison), 1 for partial coverage or processing failure, 2 for invalid usage.
111
135
  `;
112
136
  export const validateUsage = `Usage: cloving workflows validate <manifest-path> [--input <json-file>] [--json]
113
137
 
@@ -122,3 +146,11 @@ Validation does not admit a run, dispatch effects, import workflow code or call
122
146
  Example:
123
147
  cloving workflows validate cloving.workflow.json --input input.json
124
148
  `;
149
+ export const chatsRedactUsage = `Usage: cloving chats redact <IMPORT_ID>
150
+
151
+ Redact one existing import selected by its UUID v4 import ID. No service is required.
152
+ Write redacted.jsonl and redaction.json beside the original corpus with mode 0600.
153
+ HOME supplies the home folder. CLOVING_PRIVATE_TEXTS is an optional JSON list of nonblank strings.
154
+ Originals and metadata remain private. Redaction does not authorize model use or guarantee removal of every private value.
155
+ Exit codes: 0 complete, 1 processing failure, 2 invalid usage.
156
+ `;
@@ -0,0 +1,10 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { type CardForm } from "./watch-run-input.ts";
3
+ export interface WatchActions {
4
+ readonly forms: readonly CardForm[];
5
+ readonly token: string;
6
+ readonly runtimeDirectory: string;
7
+ }
8
+ export declare function watchActions(runtimeDirectory: string): Promise<WatchActions>;
9
+ export declare function watchHost(request: IncomingMessage): boolean;
10
+ export declare function watchAction(actions: WatchActions, request: IncomingMessage): Promise<string | null>;
@@ -0,0 +1,58 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { send } from "./client.js";
3
+ import { watchCardForms } from "./watch-card-forms.js";
4
+ import { join } from "node:path";
5
+ import { cardRunRequest } from "./watch-run-input.js";
6
+ export async function watchActions(runtimeDirectory) {
7
+ return {
8
+ runtimeDirectory,
9
+ forms: await watchCardForms(join(import.meta.dirname, "../../workflows")),
10
+ token: randomBytes(32).toString("hex"),
11
+ };
12
+ }
13
+ export function watchHost(request) {
14
+ const port = request.socket.localPort;
15
+ return [`127.0.0.1:${String(port)}`, `localhost:${String(port)}`].includes(request.headers.host ?? "");
16
+ }
17
+ function authorized(actions, request) {
18
+ return (watchHost(request) &&
19
+ request.headers.origin === `http://${request.headers.host ?? ""}` &&
20
+ request.headers["x-cloving-token"] === actions.token &&
21
+ /^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(request.headers["content-type"] ?? ""));
22
+ }
23
+ async function bodyOf(request) {
24
+ const chunks = [];
25
+ let size = 0;
26
+ for await (const chunk of request) {
27
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
28
+ size += bytes.length;
29
+ if (size > 16384)
30
+ return null;
31
+ chunks.push(bytes);
32
+ }
33
+ try {
34
+ const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
35
+ return value;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ export async function watchAction(actions, request) {
42
+ if (!authorized(actions, request))
43
+ return null;
44
+ const given = cardRunRequest(actions.forms, await bodyOf(request));
45
+ if (given === null)
46
+ return null;
47
+ const status = await send(actions.runtimeDirectory, {
48
+ operation: "status",
49
+ parameters: {},
50
+ });
51
+ if (status.kind === "error")
52
+ return JSON.stringify(status);
53
+ const workflows = status.result["workflows"];
54
+ if (!Array.isArray(workflows) ||
55
+ !workflows.includes(given.parameters["workflowId"]))
56
+ return null;
57
+ return JSON.stringify(await send(actions.runtimeDirectory, given));
58
+ }
@@ -1,5 +1,20 @@
1
+ import { type CardControls } from "./watch-card-view.ts";
2
+ import type { CardForm } from "./watch-run-input.ts";
1
3
  import { type Shown } from "./watch-element.ts";
2
4
  import type { WatchCard, WatchWorkflow } from "./watch-render.ts";
5
+ export declare const boardSizing: {
6
+ minimumColumnWidth: number;
7
+ collapsedColumnWidth: number;
8
+ gap: number;
9
+ };
10
+ export declare function boardLayout(input: {
11
+ readonly contentWidth: number;
12
+ readonly openColumns: number;
13
+ readonly collapsedColumns: number;
14
+ }): {
15
+ readonly columnWidth: number;
16
+ readonly totalWidth: number;
17
+ };
3
18
  export declare function cardStep(card: WatchCard, now: string): string;
4
19
  export declare function cardReview(card: WatchCard): string;
5
20
  export declare function CardReview(props: {
@@ -16,7 +31,24 @@ export declare function WorkflowList(props: {
16
31
  readonly workflows: readonly WatchWorkflow[];
17
32
  readonly selected: string | null;
18
33
  }): Shown;
34
+ export interface ColumnControls {
35
+ readonly choices: Readonly<Record<string, boolean>> | null;
36
+ readonly onToggle: (name: string) => void;
37
+ readonly onHover?: (name: string | null) => void;
38
+ readonly onFocus?: (name: string | null) => void;
39
+ }
40
+ export declare function columnCollapsed(choices: Readonly<Record<string, boolean>> | null, name: string): boolean;
41
+ export declare function Column(props: {
42
+ readonly column: WatchWorkflow["columns"][number];
43
+ readonly addCard?: Shown;
44
+ readonly workflowId: string;
45
+ readonly now: string;
46
+ readonly controls?: ColumnControls | undefined;
47
+ }): Shown;
19
48
  export declare function Board(props: {
20
49
  readonly workflow: WatchWorkflow;
50
+ readonly cardForm?: CardForm | undefined;
51
+ readonly cards?: CardControls | undefined;
21
52
  readonly now: string;
53
+ readonly controls?: ColumnControls | undefined;
22
54
  }): Shown;