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,7 +1,12 @@
1
1
  import { join } from "node:path";
2
+ import { ticketStatus } from "../ticket-status.js";
3
+ import { analysisWorker } from "./analysis-worker.js";
4
+ import { analysisMethod } from "./analysis-launch.js";
5
+ import { estimatedCostUsd, priceForModel, } from "../token-prices.js";
2
6
  import { profileCommand, } from "../turn-profile.js";
3
- import { startTurn, } from "./claude-turn.js";
4
- import { interrupted, located as locatedIn, operationKey, readRecord as read, replied as repliedWith, reportOf, writeRecord as written, } from "./operation-journal.js";
7
+ import {} from "./claude-turn.js";
8
+ import { startRetriedTurn } from "./retrying-turn.js";
9
+ import { interrupted, located, operationKey, readRecord as read, replied, reportOf, writeRecord as written, } from "./operation-journal.js";
5
10
  import { changes, treeFailure, workTree } from "./work-tree.js";
6
11
  export const claudeLimits = {
7
12
  summaryCharacters: 16_000,
@@ -9,9 +14,6 @@ export const claudeLimits = {
9
14
  stopMilliseconds: 2_000,
10
15
  };
11
16
  const worker = "claude";
12
- function located(settings, operationId) {
13
- return locatedIn(settings.journal, operationId);
14
- }
15
17
  function alive(processId) {
16
18
  if (typeof processId !== "number")
17
19
  return false;
@@ -23,14 +25,11 @@ function alive(processId) {
23
25
  return Reflect.get(Object(error), "code") === "EPERM";
24
26
  }
25
27
  }
26
- function replied(key, recorded) {
27
- return repliedWith({ receipt: key, worker }, recorded);
28
- }
29
28
  async function reconciled(parts, intent) {
30
- const { key, folder } = located(parts.settings, intent.operationId);
29
+ const { key, folder } = located(parts.settings.journal, intent.operationId);
31
30
  const result = await read(folder, "result.json");
32
31
  if (result !== undefined)
33
- return replied(key, result);
32
+ return replied({ receipt: key, worker }, result);
34
33
  if (parts.active.has(key))
35
34
  return { kind: "accepted", receipt: key, worker };
36
35
  const started = await read(folder, "started.json");
@@ -40,64 +39,78 @@ async function reconciled(parts, intent) {
40
39
  return { kind: "unknown", evidence: `claude-turn:${key}` };
41
40
  const ending = interrupted("claude-turn-interrupted");
42
41
  await written(folder, { name: "result.json", value: ending });
43
- return replied(key, ending);
42
+ return replied({ receipt: key, worker }, ending);
44
43
  }
45
- async function resultText(tree, result) {
44
+ async function resultText(tree, turn) {
45
+ const { result, settings } = turn;
46
46
  const fields = {
47
+ ticketStatus: ticketStatus(result.text),
47
48
  summary: result.text.slice(0, claudeLimits.summaryCharacters),
48
49
  ...(await changes(tree)),
49
50
  worktree: tree.path,
50
51
  costUsd: result.costUsd,
52
+ usage: result.usage,
53
+ model: result.model,
54
+ estimatedCostUsd: estimatedCostUsd(result.usage, {
55
+ worker: settings.worker ?? "claude",
56
+ price: priceForModel(settings.tokenPrices ?? {}, result.model),
57
+ }),
51
58
  sessionId: result.sessionId,
52
59
  turns: result.turns,
53
60
  };
54
61
  const text = JSON.stringify(fields);
55
62
  const excess = Buffer.byteLength(text) - claudeLimits.resultBytes;
56
63
  if (excess <= 0)
57
- return text;
64
+ return { text };
58
65
  const kept = Math.max(0, fields.summary.length - excess);
59
- return JSON.stringify({ ...fields, summary: fields.summary.slice(0, kept) });
66
+ const shortened = JSON.stringify({
67
+ ...fields,
68
+ summary: fields.summary.slice(0, kept),
69
+ });
70
+ if (Buffer.byteLength(shortened) <= claudeLimits.resultBytes)
71
+ return { text: shortened };
72
+ const metadata = JSON.stringify({
73
+ ...fields,
74
+ ticketStatus: undefined,
75
+ summary: "",
76
+ });
77
+ return {
78
+ reason: "claude-result-too-large",
79
+ ...(Buffer.byteLength(metadata) <= claudeLimits.resultBytes
80
+ ? { text: metadata }
81
+ : {}),
82
+ };
60
83
  }
61
84
  async function outcome(running, end) {
62
85
  const endedAt = new Date().toISOString();
63
86
  const { result } = end;
64
- const text = result === undefined ? undefined : await resultText(running.tree, result);
65
- if (text !== undefined && end.exitCode === 0 && result?.failed === false)
87
+ const report = result === undefined
88
+ ? {}
89
+ : await resultText(running.tree, { result, settings: running.settings });
90
+ const { text, reason } = report;
91
+ if (reason === undefined &&
92
+ text !== undefined &&
93
+ end.exitCode === 0 &&
94
+ result?.failed === false)
66
95
  return { outcome: "completed", result: text, endedAt };
67
96
  if (running.stopping.requested)
68
97
  return { outcome: "stopped", endedAt };
69
98
  return {
70
99
  outcome: "failed",
71
- reason: "claude-turn-failed",
100
+ reason: reason ?? "claude-turn-failed",
72
101
  ...(text === undefined ? {} : { result: text }),
73
102
  endedAt,
74
103
  };
75
104
  }
76
- function reported(started, recorded) {
77
- const { intent, key } = started;
78
- return reportOf({ intent, commandId: `claude-turn:${key}`, worker }, recorded);
79
- }
80
105
  async function concluded(parts, running) {
81
- const recorded = await outcome(running, await running.turn.ended);
106
+ const { intent, key } = running;
107
+ const recorded = await outcome({ ...running, settings: parts.settings }, await running.turn.ended);
82
108
  await written(running.folder, { name: "result.json", value: recorded });
83
- await parts.settings.report(reported(running, recorded));
109
+ await parts.settings.report(reportOf({ intent, commandId: `claude-turn:${key}`, worker }, recorded));
84
110
  }
85
111
  async function begun(parts, start) {
86
112
  const { intent, key, folder, tree, prompt } = start;
87
- const record = (processId) => written(folder, {
88
- name: "started.json",
89
- value: {
90
- operationId: intent.operationId,
91
- target: intent.target,
92
- branch: tree.branch,
93
- worktree: tree.path,
94
- startCommit: tree.startCommit,
95
- processId,
96
- startedAt: new Date().toISOString(),
97
- },
98
- });
99
- await record(null);
100
- const turn = startTurn({
113
+ const turn = startRetriedTurn({
101
114
  worker: parts.settings.worker ?? "claude",
102
115
  command: profileCommand(parts.settings.command, start.profile),
103
116
  environment: start.profile.env ?? {},
@@ -105,16 +118,26 @@ async function begun(parts, start) {
105
118
  directory: tree.path,
106
119
  folder,
107
120
  prompt,
121
+ }, {
122
+ ...parts.settings,
123
+ metadata: {
124
+ operationId: intent.operationId,
125
+ target: intent.target,
126
+ branch: tree.branch,
127
+ worktree: tree.path,
128
+ startCommit: tree.startCommit,
129
+ },
108
130
  });
109
131
  const stopping = { requested: false };
110
132
  parts.active.set(key, {
111
133
  turn,
112
134
  stopping,
113
- concluded: concluded(parts, { ...start, turn, stopping })
135
+ concluded: turn
136
+ .then((running) => concluded(parts, { ...start, turn: running, stopping }))
114
137
  .catch(() => undefined)
115
138
  .finally(() => parts.active.delete(key)),
116
139
  });
117
- await record(turn.processId);
140
+ await turn;
118
141
  }
119
142
  async function firstTurn(settings, opening) {
120
143
  const { tree, intent } = opening;
@@ -171,8 +194,11 @@ async function selectedProfile(settings, intent) {
171
194
  async function delivered(parts, call) {
172
195
  const { intent, signal } = call;
173
196
  const { settings } = parts;
197
+ const aborted = () => signal.aborted;
174
198
  if (!turnIntent(intent))
175
199
  return failure("unsupported-effect");
200
+ if (aborted())
201
+ return failure("claude-start-aborted");
176
202
  const known = await reconciled(parts, intent);
177
203
  if (known.kind !== "absent")
178
204
  return known;
@@ -188,22 +214,25 @@ async function delivered(parts, call) {
188
214
  target: intent.target,
189
215
  signal,
190
216
  }).catch(treeFailure);
191
- if (signal.aborted)
217
+ if (aborted())
192
218
  return failure("claude-start-aborted");
193
219
  if (typeof tree === "string")
194
220
  return failure(tree);
195
- const place = located(settings, intent.operationId);
221
+ const place = located(settings.journal, intent.operationId);
196
222
  const first = await firstTurn(settings, { tree, intent });
223
+ if (aborted())
224
+ return failure("claude-start-aborted");
197
225
  await begun(parts, { ...place, intent, tree: first, prompt, profile });
198
226
  return { receipt: place.key, worker };
199
227
  }
200
228
  async function halted(parts, active) {
201
229
  active.stopping.requested = true;
202
- active.turn.stop(parts.settings.stopMilliseconds ?? claudeLimits.stopMilliseconds);
230
+ const turn = await active.turn.catch(() => undefined);
231
+ turn?.stop(parts.settings.stopMilliseconds ?? claudeLimits.stopMilliseconds);
203
232
  await active.concluded;
204
233
  }
205
234
  async function stopped(parts, intent) {
206
- const { key } = located(parts.settings, intent.operationId);
235
+ const { key } = located(parts.settings.journal, intent.operationId);
207
236
  const active = parts.active.get(key);
208
237
  if (active !== undefined)
209
238
  await halted(parts, active);
@@ -212,12 +241,35 @@ async function stopped(parts, intent) {
212
241
  export function claudeWorker(settings) {
213
242
  if (settings.command.length === 0)
214
243
  throw new Error("invalid-claude-command");
215
- const parts = { settings, active: new Map() };
244
+ const parts = {
245
+ settings,
246
+ active: new Map(),
247
+ stopping: new AbortController(),
248
+ };
249
+ const analysis = analysisWorker(settings);
250
+ const guarded = async (intent, run) => {
251
+ if (!turnIntent(intent) ||
252
+ Object.hasOwn(settings.profiles ?? {}, "analyze-chats") ||
253
+ (await runWorkflow(settings, intent.runId)) !== "analyze-chats")
254
+ return failure("analysis-worker-unavailable");
255
+ return run();
256
+ };
216
257
  return {
217
- deliver: (intent, signal) => delivered(parts, { intent, signal }),
218
- reconcile: (intent) => reconciled(parts, intent),
219
- stop: (intent) => stopped(parts, intent),
258
+ deliver: (intent, signal) => intent.method === analysisMethod
259
+ ? guarded(intent, () => analysis.deliver(intent, signal))
260
+ : delivered(parts, {
261
+ intent,
262
+ signal: AbortSignal.any([signal, parts.stopping.signal]),
263
+ }),
264
+ reconcile: (intent, signal) => intent.method === analysisMethod
265
+ ? guarded(intent, () => analysis.reconcile(intent, signal))
266
+ : reconciled(parts, intent),
267
+ stop: (intent, signal) => intent.method === analysisMethod
268
+ ? guarded(intent, () => analysis.stop(intent, signal))
269
+ : stopped(parts, intent),
220
270
  close: async () => {
271
+ parts.stopping.abort();
272
+ await analysis.close();
221
273
  await Promise.all([...parts.active.values()].map((active) => halted(parts, active)));
222
274
  },
223
275
  };
@@ -1,3 +1,4 @@
1
+ import { sumUsage, usageOf } from "../worker-usage.js";
1
2
  function record(value) {
2
3
  return typeof value === "object" && value !== null ? { ...value } : {};
3
4
  }
@@ -43,9 +44,20 @@ function completed(item) {
43
44
  }
44
45
  function updated(event, state) {
45
46
  const type = event["type"];
47
+ if (typeof type === "string" &&
48
+ ["thread.started", "turn.started", "turn.completed"].includes(type))
49
+ state.model = text(event["model"]) || state.model;
46
50
  if (type === "thread.started")
47
51
  state.sessionId = text(event["thread_id"]) || null;
48
52
  if (type === "turn.completed") {
53
+ const usage = record(event["usage"]);
54
+ const current = usageOf({
55
+ input_tokens: usage["input_tokens"],
56
+ cache_read_input_tokens: usage["cached_input_tokens"],
57
+ output_tokens: usage["output_tokens"],
58
+ });
59
+ state.usage =
60
+ state.turns === 0 ? current : sumUsage([state.usage, current]);
49
61
  state.turns += 1;
50
62
  state.error = null;
51
63
  }
@@ -82,8 +94,10 @@ function translated(event, state) {
82
94
  }
83
95
  export function codexStream() {
84
96
  const state = {
97
+ model: null,
85
98
  sessionId: null,
86
99
  turns: 0,
100
+ usage: usageOf(null),
87
101
  failure: null,
88
102
  error: null,
89
103
  message: "",
@@ -105,6 +119,8 @@ export function codexStream() {
105
119
  state.error ||
106
120
  `codex ended with the code ${String(exitCode)}`,
107
121
  total_cost_usd: null,
122
+ ...(state.model === null ? {} : { model: state.model }),
123
+ usage: state.usage,
108
124
  session_id: state.sessionId,
109
125
  num_turns: state.turns,
110
126
  });
@@ -1,9 +1,11 @@
1
1
  import { type WorkflowFunction } from "../host/start-delivery.ts";
2
2
  import { type WorkflowHandler } from "../host/world-host.ts";
3
3
  import type { TurnProfiles } from "../turn-profile.ts";
4
+ import type { TokenPrices } from "../token-prices.ts";
4
5
  import type { DeclaredWorkflow } from "../workflow-declaration.ts";
5
6
  import type { Worker } from "./claude-turn.ts";
6
7
  export interface DaemonSettings {
8
+ readonly tokenPrices?: TokenPrices;
7
9
  readonly worker?: Worker;
8
10
  readonly profiles?: TurnProfiles;
9
11
  readonly directory: string;
@@ -19,6 +21,7 @@ export interface DaemonSettings {
19
21
  readonly notifyCommand?: readonly string[];
20
22
  readonly notifyMilliseconds?: number;
21
23
  readonly queueMilliseconds?: number;
24
+ readonly resetQueue?: boolean;
22
25
  readonly githubCommand?: readonly string[];
23
26
  readonly checkCommand?: readonly string[];
24
27
  readonly privateTexts?: readonly string[];
@@ -9,6 +9,7 @@ import { startService, } from "../service/service.js";
9
9
  import { claudeWorker } from "./claude-worker.js";
10
10
  import { deliveryRouter } from "./delivery-router.js";
11
11
  import { githubWorker } from "./github-worker.js";
12
+ import { chatsWorker } from "./chats-worker.js";
12
13
  import { worldDirectory } from "./host-environment.js";
13
14
  import { issueQueue } from "./issue-queue.js";
14
15
  import { notifyRuns } from "./notify.js";
@@ -76,6 +77,7 @@ function adapters(settings, options) {
76
77
  const document = async (digest) => (await given).document(digest);
77
78
  const report = async (effect) => (await given).reportEffect(effect);
78
79
  const worker = claudeWorker({
80
+ tokenPrices: settings.tokenPrices ?? {},
79
81
  worker: settings.worker ?? "claude",
80
82
  profiles: settings.profiles ?? {},
81
83
  workflowId: async (runId) => (await given).workflowId(runId),
@@ -87,6 +89,11 @@ function adapters(settings, options) {
87
89
  journal: join(directory, "journal"),
88
90
  });
89
91
  const github = githubOf(settings, given);
92
+ const chats = chatsWorker({
93
+ runtimeDirectory: settings.runtimeDirectory,
94
+ document,
95
+ workflowId: async (runId) => (await given).workflowId(runId),
96
+ });
90
97
  const runs = startDelivery({
91
98
  document,
92
99
  runs: taggedRuns,
@@ -94,7 +101,7 @@ function adapters(settings, options) {
94
101
  input: async (runId) => (await given).input(runId),
95
102
  workflow: async (runId) => workflows[String(await (await given).workflowId(runId))],
96
103
  });
97
- return { worker, github, runs };
104
+ return { worker, github, runs, chats };
98
105
  }
99
106
  export async function startDaemon(settings) {
100
107
  const { directory } = settings;
@@ -111,7 +118,7 @@ export async function startDaemon(settings) {
111
118
  detail: admitted.detail,
112
119
  };
113
120
  const given = Promise.withResolvers();
114
- const { worker, github, runs } = adapters(settings, {
121
+ const { worker, github, runs, chats } = adapters(settings, {
115
122
  given: given.promise,
116
123
  table,
117
124
  });
@@ -129,7 +136,7 @@ export async function startDaemon(settings) {
129
136
  })),
130
137
  delivery: deliveryRouter({
131
138
  runs,
132
- effects: { claude: worker, github },
139
+ effects: { claude: worker, github, chats },
133
140
  }),
134
141
  effects: { leaseMilliseconds: turnLeaseMilliseconds },
135
142
  host: (ports) => {
@@ -138,7 +145,7 @@ export async function startDaemon(settings) {
138
145
  },
139
146
  });
140
147
  if (opening.kind === "refused") {
141
- await Promise.all([worker.close(), github.close()]);
148
+ await Promise.all([worker.close(), github.close(), chats.close()]);
142
149
  return opening;
143
150
  }
144
151
  return {
@@ -147,11 +154,12 @@ export async function startDaemon(settings) {
147
154
  service: opening.service,
148
155
  worker,
149
156
  github,
157
+ chats,
150
158
  }),
151
159
  };
152
160
  }
153
161
  async function runningDaemon(settings, resources) {
154
- const { service, worker, github } = resources;
162
+ const { service, worker, github, chats } = resources;
155
163
  await github.scan();
156
164
  const notifier = settings.notifyCommand === undefined
157
165
  ? undefined
@@ -163,7 +171,7 @@ async function runningDaemon(settings, resources) {
163
171
  ? {}
164
172
  : { intervalMilliseconds: settings.notifyMilliseconds }),
165
173
  }).catch(async (error) => {
166
- await Promise.all([worker.close(), github.close()]);
174
+ await Promise.all([worker.close(), github.close(), chats.close()]);
167
175
  await service.close();
168
176
  throw error;
169
177
  });
@@ -173,13 +181,14 @@ async function runningDaemon(settings, resources) {
173
181
  directory: settings.directory,
174
182
  runtimeDirectory: settings.runtimeDirectory,
175
183
  intervalMilliseconds: settings.queueMilliseconds,
184
+ resetQueue: settings.resetQueue ?? false,
176
185
  });
177
186
  return {
178
187
  socket: service.socket,
179
188
  close: async () => {
180
189
  await notifier?.stop();
181
190
  await queue?.stop();
182
- await Promise.all([worker.close(), github.close()]);
191
+ await Promise.all([worker.close(), github.close(), chats.close()]);
183
192
  await service.close();
184
193
  },
185
194
  };
@@ -1,5 +1,6 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
2
  import { findingsOf, scoreOf } from "./github-calls.js";
3
+ import { closeIssue, partialIssue } from "./github-ticket-status.js";
3
4
  import { merge } from "./github-merge.js";
4
5
  import { createIssue, discard } from "./github-issue-methods.js";
5
6
  import { existingTree, defaultBranch, changeChecked, field, githubTiming, repositoryPath, text, } from "./github-operation.js";
@@ -11,6 +12,7 @@ export const methodReasons = new Set([
11
12
  "post-uncertain",
12
13
  "private-texts-not-set",
13
14
  "github-call-failed",
15
+ "github-issue-conflict",
14
16
  "git-call-failed",
15
17
  "check-failed",
16
18
  "merge-conflict",
@@ -183,6 +185,8 @@ async function waitReview(operation) {
183
185
  }
184
186
  export const methods = new Map([
185
187
  ["prepare", prepare],
188
+ ["close-issue", closeIssue],
189
+ ["partial-issue", partialIssue],
186
190
  ["label", label],
187
191
  ["create-issue", createIssue],
188
192
  ["discard", discard],
@@ -0,0 +1,3 @@
1
+ import { type Operation } from "./github-operation.ts";
2
+ export declare function closeIssue(operation: Operation): Promise<unknown>;
3
+ export declare function partialIssue(operation: Operation): Promise<unknown>;