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.
- package/README.md +145 -14
- package/dist/src/authority/commands.js +10 -2
- package/dist/src/authority/reconciliation.d.ts +2 -0
- package/dist/src/authority/reconciliation.js +10 -1
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +2 -0
- package/dist/src/broker/actor/authority-session.d.ts +2 -0
- package/dist/src/broker/actor/authority-session.js +3 -4
- package/dist/src/chats/analysis-batches.d.ts +24 -0
- package/dist/src/chats/analysis-batches.js +83 -0
- package/dist/src/chats/analysis-contract.d.ts +80 -0
- package/dist/src/chats/analysis-contract.js +16 -0
- package/dist/src/chats/analysis-data.d.ts +3 -0
- package/dist/src/chats/analysis-data.js +85 -0
- package/dist/src/chats/analysis-input.d.ts +12 -0
- package/dist/src/chats/analysis-input.js +83 -0
- package/dist/src/chats/analysis-links.d.ts +2 -0
- package/dist/src/chats/analysis-links.js +25 -0
- package/dist/src/chats/analysis-prompt.d.ts +10 -0
- package/dist/src/chats/analysis-prompt.js +16 -0
- package/dist/src/chats/analysis-proposal.d.ts +2 -0
- package/dist/src/chats/analysis-proposal.js +32 -0
- package/dist/src/chats/analysis-provenance.d.ts +4 -0
- package/dist/src/chats/analysis-provenance.js +99 -0
- package/dist/src/chats/analysis-shape.d.ts +3 -0
- package/dist/src/chats/analysis-shape.js +228 -0
- package/dist/src/chats/analysis.d.ts +3 -0
- package/dist/src/chats/analysis.js +42 -0
- package/dist/src/chats/corpus-input.d.ts +5 -0
- package/dist/src/chats/corpus-input.js +288 -0
- package/dist/src/chats/destination.js +3 -1
- package/dist/src/chats/import.d.ts +8 -6
- package/dist/src/chats/import.js +195 -23
- package/dist/src/chats/input-file.d.ts +3 -0
- package/dist/src/chats/input-file.js +32 -0
- package/dist/src/chats/normalize.d.ts +11 -2
- package/dist/src/chats/normalize.js +18 -2
- package/dist/src/chats/previous-import.d.ts +5 -0
- package/dist/src/chats/previous-import.js +34 -0
- package/dist/src/chats/reader.d.ts +7 -1
- package/dist/src/chats/reader.js +46 -15
- package/dist/src/chats/redact-import.d.ts +14 -0
- package/dist/src/chats/redact-import.js +141 -0
- package/dist/src/chats/redaction.d.ts +41 -0
- package/dist/src/chats/redaction.js +161 -0
- package/dist/src/chats/revision-input.d.ts +3 -0
- package/dist/src/chats/revision-input.js +180 -0
- package/dist/src/chats/revision.d.ts +48 -0
- package/dist/src/chats/revision.js +94 -0
- package/dist/src/chats/selection.d.ts +25 -0
- package/dist/src/chats/selection.js +109 -0
- package/dist/src/chats/source.d.ts +23 -0
- package/dist/src/chats/source.js +135 -0
- package/dist/src/chats/structure.d.ts +57 -0
- package/dist/src/chats/structure.js +216 -0
- package/dist/src/cli/arguments.d.ts +8 -0
- package/dist/src/cli/arguments.js +94 -13
- package/dist/src/cli/client.d.ts +1 -0
- package/dist/src/cli/client.js +38 -1
- package/dist/src/cli/commands.d.ts +3 -0
- package/dist/src/cli/commands.js +29 -11
- package/dist/src/cli/main.d.ts +2 -1
- package/dist/src/cli/main.js +61 -14
- package/dist/src/cli/render.d.ts +2 -0
- package/dist/src/cli/render.js +70 -2
- package/dist/src/cli/usage-render.d.ts +3 -0
- package/dist/src/cli/usage-render.js +28 -0
- package/dist/src/cli/usage.d.ts +3 -2
- package/dist/src/cli/usage.js +38 -6
- package/dist/src/cli/watch-actions.d.ts +10 -0
- package/dist/src/cli/watch-actions.js +58 -0
- package/dist/src/cli/watch-board-view.d.ts +32 -0
- package/dist/src/cli/watch-board-view.js +88 -15
- package/dist/src/cli/watch-board.js +6 -1
- package/dist/src/cli/watch-card-client.d.ts +1 -0
- package/dist/src/cli/watch-card-client.js +44 -0
- package/dist/src/cli/watch-card-forms.d.ts +2 -0
- package/dist/src/cli/watch-card-forms.js +33 -0
- package/dist/src/cli/watch-card-reply.d.ts +5 -0
- package/dist/src/cli/watch-card-reply.js +34 -0
- package/dist/src/cli/watch-card-view.d.ts +40 -0
- package/dist/src/cli/watch-card-view.js +86 -0
- package/dist/src/cli/watch-client.d.ts +1 -0
- package/dist/src/cli/watch-client.js +118 -0
- package/dist/src/cli/watch-model.js +31 -14
- package/dist/src/cli/watch-page.d.ts +1 -1
- package/dist/src/cli/watch-page.js +71 -85
- package/dist/src/cli/watch-pr-board.d.ts +3 -0
- package/dist/src/cli/watch-pr-board.js +26 -0
- package/dist/src/cli/watch-pr-command.d.ts +15 -0
- package/dist/src/cli/watch-pr-command.js +37 -0
- package/dist/src/cli/watch-pr-identity.d.ts +20 -0
- package/dist/src/cli/watch-pr-identity.js +62 -0
- package/dist/src/cli/watch-pr-observer.d.ts +19 -0
- package/dist/src/cli/watch-pr-observer.js +108 -0
- package/dist/src/cli/watch-pr-settings.d.ts +8 -0
- package/dist/src/cli/watch-pr-settings.js +47 -0
- package/dist/src/cli/watch-reads.js +4 -0
- package/dist/src/cli/watch-render.d.ts +16 -2
- package/dist/src/cli/watch-render.js +10 -3
- package/dist/src/cli/watch-run-input.d.ts +14 -0
- package/dist/src/cli/watch-run-input.js +49 -0
- package/dist/src/cli/watch-server.d.ts +3 -0
- package/dist/src/cli/watch-server.js +44 -6
- package/dist/src/cli/watch-start-view.d.ts +18 -0
- package/dist/src/cli/watch-start-view.js +77 -0
- package/dist/src/cli/watch-turn.d.ts +1 -0
- package/dist/src/cli/watch-turn.js +7 -2
- package/dist/src/cli/watch-usage.d.ts +6 -0
- package/dist/src/cli/watch-usage.js +65 -0
- package/dist/src/cli/watch-view.d.ts +15 -0
- package/dist/src/cli/watch-view.js +61 -17
- package/dist/src/cli.js +48 -6
- package/dist/src/daemon/analysis-launch.d.ts +5 -0
- package/dist/src/daemon/analysis-launch.js +81 -0
- package/dist/src/daemon/analysis-token.d.ts +6 -0
- package/dist/src/daemon/analysis-token.js +88 -0
- package/dist/src/daemon/analysis-worker.d.ts +22 -0
- package/dist/src/daemon/analysis-worker.js +219 -0
- package/dist/src/daemon/chats-files.d.ts +15 -0
- package/dist/src/daemon/chats-files.js +113 -0
- package/dist/src/daemon/chats-worker.d.ts +12 -0
- package/dist/src/daemon/chats-worker.js +200 -0
- package/dist/src/daemon/claude-turn.d.ts +10 -0
- package/dist/src/daemon/claude-turn.js +96 -15
- package/dist/src/daemon/claude-worker.d.ts +4 -1
- package/dist/src/daemon/claude-worker.js +99 -47
- package/dist/src/daemon/codex-stream.js +16 -0
- package/dist/src/daemon/daemon.d.ts +3 -0
- package/dist/src/daemon/daemon.js +16 -7
- package/dist/src/daemon/github-methods.js +4 -0
- package/dist/src/daemon/github-ticket-status.d.ts +3 -0
- package/dist/src/daemon/github-ticket-status.js +268 -0
- package/dist/src/daemon/issue-queue.d.ts +1 -0
- package/dist/src/daemon/issue-queue.js +115 -24
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/operation-journal.js +4 -2
- package/dist/src/daemon/retrying-turn.d.ts +9 -0
- package/dist/src/daemon/retrying-turn.js +185 -0
- package/dist/src/host/world-reconciliation.d.ts +2 -0
- package/dist/src/host/world-reconciliation.js +6 -0
- package/dist/src/host/world-runs.js +9 -1
- package/dist/src/service/run-handlers.js +15 -23
- package/dist/src/service/worker-totals.d.ts +3 -0
- package/dist/src/service/worker-totals.js +28 -0
- package/dist/src/ticket-status.d.ts +8 -0
- package/dist/src/ticket-status.js +45 -0
- package/dist/src/token-prices.d.ts +19 -0
- package/dist/src/token-prices.js +76 -0
- package/dist/src/worker-usage.d.ts +22 -0
- package/dist/src/worker-usage.js +44 -0
- package/dist/src/workflow-candidate.d.ts +29 -0
- package/dist/src/workflow-candidate.js +59 -0
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +27 -5
- package/dist/src/workflow-declaration.d.ts +1 -1
- package/dist/src/workflow-declaration.js +3 -1
- package/dist/src/workflows.d.ts +1 -0
- package/dist/src/workflows.js +1 -0
- package/dist/tools/self-host-processes.js +7 -2
- package/dist/tools/self-host-workflows.d.ts +1 -1
- package/dist/tools/self-host-workflows.js +2 -2
- package/dist/tools/self-host.js +6 -6
- package/dist/tools/self-update-host.d.ts +7 -0
- package/dist/tools/self-update-host.js +48 -1
- package/dist/tools/self-update.d.ts +1 -0
- package/dist/tools/self-update.js +85 -13
- package/dist/workflows/analyze-chats.d.ts +10 -0
- package/dist/workflows/analyze-chats.declaration.json +9 -0
- package/dist/workflows/analyze-chats.js +73 -0
- package/dist/workflows/develop-ticket.d.ts +1 -7
- package/dist/workflows/develop-ticket.js +42 -141
- package/dist/workflows/issue-labels.d.ts +3 -1
- package/dist/workflows/issue-labels.js +34 -0
- package/dist/workflows/ticket-prompts.d.ts +35 -0
- package/dist/workflows/ticket-prompts.js +133 -0
- package/package.json +5 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { prRepository } from "./watch-pr-identity.js";
|
|
2
|
+
import { prObserver } from "./watch-pr-observer.js";
|
|
3
|
+
function githubCommand(given) {
|
|
4
|
+
if (given === undefined)
|
|
5
|
+
return ["gh"];
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(given);
|
|
8
|
+
if (!Array.isArray(parsed) ||
|
|
9
|
+
!parsed.every((part) => typeof part === "string"))
|
|
10
|
+
return null;
|
|
11
|
+
return parsed.map(String);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function watchPrObserver(settings) {
|
|
18
|
+
const directory = settings.repository;
|
|
19
|
+
const command = githubCommand(settings.githubCommand);
|
|
20
|
+
const output = directory === undefined
|
|
21
|
+
? null
|
|
22
|
+
: await settings
|
|
23
|
+
.execute({
|
|
24
|
+
command: [
|
|
25
|
+
"git",
|
|
26
|
+
"config",
|
|
27
|
+
"--local",
|
|
28
|
+
"--get-all",
|
|
29
|
+
"remote.origin.url",
|
|
30
|
+
],
|
|
31
|
+
directory,
|
|
32
|
+
signal: AbortSignal.timeout(5000),
|
|
33
|
+
})
|
|
34
|
+
.catch(() => null);
|
|
35
|
+
if (output?.errorCode === "ENOENT" || output?.errorCode === "EACCES")
|
|
36
|
+
return {
|
|
37
|
+
observe: () => ({ status: "unavailable", failure: "command" }),
|
|
38
|
+
close: () => undefined,
|
|
39
|
+
};
|
|
40
|
+
const repository = output?.status === 0 ? prRepository(output.stdout) : null;
|
|
41
|
+
return prObserver({
|
|
42
|
+
repository,
|
|
43
|
+
directory: directory ?? "",
|
|
44
|
+
command,
|
|
45
|
+
execute: settings.execute,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -84,6 +84,8 @@ function matchingCompletion(status, ticketIds) {
|
|
|
84
84
|
return (status.state === "completed" &&
|
|
85
85
|
status["workflowId"] === "develop-issue" &&
|
|
86
86
|
record(status["outcome"])?.["kind"] === "handoff_requested" &&
|
|
87
|
+
record(status["outcome"])?.["ticketStatus"] !== "partial" &&
|
|
88
|
+
record(status["outcome"])?.["issueUpdate"] !== "failed" &&
|
|
87
89
|
typeof ticketId === "string" &&
|
|
88
90
|
ticketIds.has(ticketId));
|
|
89
91
|
}
|
|
@@ -171,6 +173,8 @@ function stoppedTicketIds(runs, now) {
|
|
|
171
173
|
return new Set(stopped.flatMap((status) => {
|
|
172
174
|
const ticketId = status["ticketId"];
|
|
173
175
|
return status.state === "completed" &&
|
|
176
|
+
record(status["outcome"])?.["ticketStatus"] !== "partial" &&
|
|
177
|
+
record(status["outcome"])?.["issueUpdate"] !== "failed" &&
|
|
174
178
|
typeof ticketId === "string" &&
|
|
175
179
|
ticketId !== ""
|
|
176
180
|
? [ticketId]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { CardForm } from "./watch-run-input.ts";
|
|
2
|
+
import type { WorkerUsage } from "../worker-usage.ts";
|
|
3
|
+
import type { PrObservation } from "./watch-pr-identity.ts";
|
|
1
4
|
export interface WatchItem {
|
|
2
5
|
readonly kind: string;
|
|
3
6
|
readonly text: string;
|
|
4
7
|
readonly costUsd?: number;
|
|
5
8
|
}
|
|
6
9
|
export interface WatchTurn {
|
|
10
|
+
readonly estimatedCostUsd: number | null;
|
|
11
|
+
readonly operationId: string;
|
|
12
|
+
readonly usage: WorkerUsage;
|
|
7
13
|
readonly key: string;
|
|
8
14
|
readonly runId: string;
|
|
9
15
|
readonly target: string;
|
|
@@ -56,9 +62,11 @@ export interface WatchStep {
|
|
|
56
62
|
readonly startedAt: string | null;
|
|
57
63
|
}
|
|
58
64
|
export interface WatchCard {
|
|
65
|
+
readonly estimatedCostUsd: number | null;
|
|
59
66
|
readonly runId: string;
|
|
60
67
|
readonly title: string | null;
|
|
61
|
-
readonly claudeCostUsd: number;
|
|
68
|
+
readonly claudeCostUsd: number | null;
|
|
69
|
+
readonly usage: WorkerUsage;
|
|
62
70
|
readonly currentStep: WatchStep | null;
|
|
63
71
|
readonly endedAt: string | null;
|
|
64
72
|
readonly decisionWaiting: boolean;
|
|
@@ -66,6 +74,7 @@ export interface WatchCard {
|
|
|
66
74
|
readonly greptileScore: number | null;
|
|
67
75
|
readonly pullRequestNumber: number | null;
|
|
68
76
|
readonly completionMark: WatchCompletionMark | null;
|
|
77
|
+
readonly pullRequestObservation: PrObservation | null;
|
|
69
78
|
}
|
|
70
79
|
export interface WatchCompletionMark {
|
|
71
80
|
readonly text: string;
|
|
@@ -83,11 +92,15 @@ export interface WatchWorkflow {
|
|
|
83
92
|
readonly columns: readonly WatchColumn[];
|
|
84
93
|
}
|
|
85
94
|
export interface WatchRun {
|
|
95
|
+
readonly issueUpdate?: "failed";
|
|
96
|
+
readonly ticketStatus?: "complete" | "partial";
|
|
97
|
+
readonly estimatedCostUsd: number | null;
|
|
86
98
|
readonly ticketId: string | null;
|
|
87
99
|
readonly title: string | null;
|
|
88
100
|
readonly outcome: string | null;
|
|
89
101
|
readonly latestAt: string | null;
|
|
90
|
-
readonly claudeCostUsd: number;
|
|
102
|
+
readonly claudeCostUsd: number | null;
|
|
103
|
+
readonly usage: WorkerUsage;
|
|
91
104
|
readonly currentStep: WatchStep;
|
|
92
105
|
readonly round: number;
|
|
93
106
|
readonly runId: string;
|
|
@@ -108,6 +121,7 @@ export interface WatchRun {
|
|
|
108
121
|
readonly mergeCommit: string | null;
|
|
109
122
|
}
|
|
110
123
|
export interface WatchModel {
|
|
124
|
+
readonly cardForms?: readonly CardForm[];
|
|
111
125
|
readonly workflows: readonly WatchWorkflow[];
|
|
112
126
|
readonly startWorkflowId: string | null;
|
|
113
127
|
readonly updatedAt: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { priceText } from "./usage-render.js";
|
|
1
2
|
export function counted(count, noun) {
|
|
2
3
|
return `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
|
|
3
4
|
}
|
|
@@ -49,7 +50,7 @@ export function runTone(run) {
|
|
|
49
50
|
return toneOf(run.state);
|
|
50
51
|
}
|
|
51
52
|
export function runName(run) {
|
|
52
|
-
return run.turns[0]?.target
|
|
53
|
+
return run.turns[0]?.target || run.runId.slice(0, 12);
|
|
53
54
|
}
|
|
54
55
|
export function waitingDetail(run, now) {
|
|
55
56
|
const opened = run.phases.find((phase) => phase.state === "active");
|
|
@@ -60,7 +61,7 @@ export function waitingDetail(run, now) {
|
|
|
60
61
|
export function completedDetail(run) {
|
|
61
62
|
const [first] = run.events;
|
|
62
63
|
const last = run.events.at(-1);
|
|
63
|
-
const cost = run.
|
|
64
|
+
const cost = priceText(run.claudeCostUsd, run.estimatedCostUsd);
|
|
64
65
|
const time = first === undefined || last === undefined
|
|
65
66
|
? ""
|
|
66
67
|
: ` in ${elapsed(first.recordedAt, last.recordedAt)}`;
|
|
@@ -68,7 +69,13 @@ export function completedDetail(run) {
|
|
|
68
69
|
approved: "You approved the work. ",
|
|
69
70
|
denied: "You denied the work. ",
|
|
70
71
|
};
|
|
71
|
-
|
|
72
|
+
const ticket = run.ticketStatus === undefined
|
|
73
|
+
? ""
|
|
74
|
+
: ` Ticket status: ${run.ticketStatus}.`;
|
|
75
|
+
const update = run.issueUpdate === "failed"
|
|
76
|
+
? " Issue status update failed; check the GitHub operation."
|
|
77
|
+
: "";
|
|
78
|
+
return `${verdicts[run.decision] ?? ""}The run completed${time} · cost ${cost}.${ticket}${update}`;
|
|
72
79
|
}
|
|
73
80
|
export function phaseTime(phase, now) {
|
|
74
81
|
if (phase.startedAt === null)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ServiceRequest } from "../protocol.ts";
|
|
2
|
+
export interface CardField {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly multiline: boolean;
|
|
6
|
+
readonly optional: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface CardForm {
|
|
9
|
+
readonly workflowId: string;
|
|
10
|
+
readonly source: "ticket" | "prompt" | "input";
|
|
11
|
+
readonly fields: readonly CardField[];
|
|
12
|
+
}
|
|
13
|
+
export declare function builtinCardForms(): readonly CardForm[];
|
|
14
|
+
export declare function cardRunRequest(forms: readonly CardForm[], value: unknown): ServiceRequest | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { record } from "../protocol.js";
|
|
2
|
+
export function builtinCardForms() {
|
|
3
|
+
const entries = [
|
|
4
|
+
["develop-issue", "ticket", "Issue number"],
|
|
5
|
+
["develop-ticket", "ticket", "Ticket ID"],
|
|
6
|
+
["develop-prd", "prompt", "Idea"],
|
|
7
|
+
["develop-prompt", "prompt", "Task"],
|
|
8
|
+
];
|
|
9
|
+
return entries.map(([workflowId, name, label]) => ({
|
|
10
|
+
workflowId,
|
|
11
|
+
source: name === "ticket" ? "ticket" : "prompt",
|
|
12
|
+
fields: [{ name, label, multiline: name === "prompt", optional: false }],
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
function validFields(form, fields) {
|
|
16
|
+
if (Object.keys(fields).some((name) => !form.fields.some((field) => field.name === name)))
|
|
17
|
+
return false;
|
|
18
|
+
return form.fields.every((field) => {
|
|
19
|
+
if (field.optional && !Object.hasOwn(fields, field.name))
|
|
20
|
+
return true;
|
|
21
|
+
const text = fields[field.name];
|
|
22
|
+
return typeof text === "string" && text.trim() !== "";
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function cardRunRequest(forms, value) {
|
|
26
|
+
const body = record(value);
|
|
27
|
+
if (body === undefined ||
|
|
28
|
+
Object.keys(body).some((key) => !["workflowId", "fields", "commandId"].includes(key)))
|
|
29
|
+
return null;
|
|
30
|
+
const { workflowId, commandId } = body;
|
|
31
|
+
const form = forms.find((item) => item.workflowId === workflowId);
|
|
32
|
+
const fields = record(body["fields"]);
|
|
33
|
+
if (form === undefined ||
|
|
34
|
+
fields === undefined ||
|
|
35
|
+
typeof commandId !== "string" ||
|
|
36
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(commandId))
|
|
37
|
+
return null;
|
|
38
|
+
if (!validFields(form, fields))
|
|
39
|
+
return null;
|
|
40
|
+
return {
|
|
41
|
+
operation: "run",
|
|
42
|
+
parameters: {
|
|
43
|
+
workflowId,
|
|
44
|
+
source: form.source,
|
|
45
|
+
submission: fields,
|
|
46
|
+
commandId,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type WatchPrSettings } from "./watch-pr-settings.ts";
|
|
1
2
|
export declare const defaultWatchPort = 3663;
|
|
2
3
|
export interface WatchSettings {
|
|
4
|
+
readonly actions?: string;
|
|
5
|
+
readonly pullRequests?: WatchPrSettings;
|
|
3
6
|
readonly runtimeDirectory: string;
|
|
4
7
|
readonly port: number;
|
|
5
8
|
readonly client: string;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { watchAction, watchActions, watchHost, } from "./watch-actions.js";
|
|
2
|
+
import { observedBoard } from "./watch-pr-board.js";
|
|
3
|
+
import { watchPrObserver } from "./watch-pr-settings.js";
|
|
1
4
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
5
|
import { createServer, } from "node:http";
|
|
3
6
|
import { homedir } from "node:os";
|
|
@@ -8,6 +11,7 @@ import { turnOf, watchModel } from "./watch-model.js";
|
|
|
8
11
|
import { pageDocument } from "./watch-page.js";
|
|
9
12
|
import { watchStream } from "./watch-stream.js";
|
|
10
13
|
export const defaultWatchPort = 3663;
|
|
14
|
+
const forbidden = { status: 403, type: "text/plain", body: "refused" };
|
|
11
15
|
const notFound = { status: 404, type: "text/plain", body: "not found" };
|
|
12
16
|
const libraryFile = fileURLToPath(import.meta.resolve("preact"));
|
|
13
17
|
function parsed(text) {
|
|
@@ -75,7 +79,7 @@ async function snapshot(watch, runId) {
|
|
|
75
79
|
turns(watch),
|
|
76
80
|
githubRecords(watch),
|
|
77
81
|
]);
|
|
78
|
-
|
|
82
|
+
const model = watchModel({
|
|
79
83
|
...read,
|
|
80
84
|
home: homedir(),
|
|
81
85
|
client: watch.settings.client,
|
|
@@ -83,17 +87,34 @@ async function snapshot(watch, runId) {
|
|
|
83
87
|
turns: journal,
|
|
84
88
|
githubRecords: github,
|
|
85
89
|
});
|
|
90
|
+
const shown = runId === null ? observedBoard(model, watch.observer) : model;
|
|
91
|
+
return {
|
|
92
|
+
...shown,
|
|
93
|
+
cardForms: watch.actions?.forms.filter((form) => shown.workflows.some((workflow) => workflow.workflowId === form.workflowId)) ?? [],
|
|
94
|
+
};
|
|
86
95
|
}
|
|
87
96
|
async function answered(watch, request) {
|
|
97
|
+
if (!watchHost(request))
|
|
98
|
+
return forbidden;
|
|
88
99
|
const url = new URL(String(request.url), "http://localhost");
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
if (request.method === "GET")
|
|
101
|
+
return readReply(watch, url);
|
|
102
|
+
if (request.method !== "POST" ||
|
|
103
|
+
url.pathname !== "/actions/run" ||
|
|
104
|
+
watch.actions === null)
|
|
91
105
|
return notFound;
|
|
106
|
+
const body = await watchAction(watch.actions, request).catch(() => null);
|
|
107
|
+
return body === null
|
|
108
|
+
? forbidden
|
|
109
|
+
: { status: 200, type: "application/json", body };
|
|
110
|
+
}
|
|
111
|
+
async function readReply(watch, url) {
|
|
112
|
+
const path = url.pathname;
|
|
92
113
|
if (path === "/")
|
|
93
114
|
return {
|
|
94
115
|
status: 200,
|
|
95
116
|
type: "text/html; charset=utf-8",
|
|
96
|
-
body: pageDocument(),
|
|
117
|
+
body: pageDocument(watch.actions?.token ?? null),
|
|
97
118
|
};
|
|
98
119
|
if (path === "/preact.mjs")
|
|
99
120
|
return {
|
|
@@ -132,9 +153,14 @@ function subscribed(stream, exchange) {
|
|
|
132
153
|
changed();
|
|
133
154
|
request.once("close", stream.subscribe(changed));
|
|
134
155
|
}
|
|
135
|
-
export function watchServer(settings) {
|
|
156
|
+
export async function watchServer(settings) {
|
|
157
|
+
const observer = await observerFor(settings);
|
|
136
158
|
const watch = {
|
|
159
|
+
actions: settings.actions === "off"
|
|
160
|
+
? null
|
|
161
|
+
: await watchActions(settings.runtimeDirectory),
|
|
137
162
|
settings,
|
|
163
|
+
observer,
|
|
138
164
|
reader: watchReader(settings.runtimeDirectory),
|
|
139
165
|
streams: new Map(),
|
|
140
166
|
github: new Map(),
|
|
@@ -145,7 +171,9 @@ export function watchServer(settings) {
|
|
|
145
171
|
retryMilliseconds: 1000,
|
|
146
172
|
});
|
|
147
173
|
const server = createServer((request, response) => {
|
|
148
|
-
if (request
|
|
174
|
+
if (watchHost(request) &&
|
|
175
|
+
request.method === "GET" &&
|
|
176
|
+
request.url === "/events") {
|
|
149
177
|
subscribed(stream, { request, response });
|
|
150
178
|
return;
|
|
151
179
|
}
|
|
@@ -157,6 +185,7 @@ export function watchServer(settings) {
|
|
|
157
185
|
});
|
|
158
186
|
return new Promise((settle) => {
|
|
159
187
|
server.once("error", (error) => {
|
|
188
|
+
observer.close();
|
|
160
189
|
stream.close();
|
|
161
190
|
settings.complain(`watch-not-started: ${error.message}\n`);
|
|
162
191
|
settle(1);
|
|
@@ -166,6 +195,7 @@ export function watchServer(settings) {
|
|
|
166
195
|
settings.announce(`Watch page: http://127.0.0.1:${String(bound)}/\n`);
|
|
167
196
|
});
|
|
168
197
|
const closed = () => {
|
|
198
|
+
observer.close();
|
|
169
199
|
stream.close();
|
|
170
200
|
server.close();
|
|
171
201
|
server.closeAllConnections();
|
|
@@ -174,3 +204,11 @@ export function watchServer(settings) {
|
|
|
174
204
|
settings.stopped.then(closed, closed);
|
|
175
205
|
});
|
|
176
206
|
}
|
|
207
|
+
function observerFor(settings) {
|
|
208
|
+
if (settings.pullRequests !== undefined)
|
|
209
|
+
return watchPrObserver(settings.pullRequests);
|
|
210
|
+
return Promise.resolve({
|
|
211
|
+
observe: () => ({ status: "unavailable", failure: "configuration" }),
|
|
212
|
+
close: () => undefined,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Shown } from "./watch-element.ts";
|
|
2
|
+
import type { Copying, WatchModel } from "./watch-render.ts";
|
|
3
|
+
interface StartPrompt {
|
|
4
|
+
readonly workflowId: string;
|
|
5
|
+
readonly sentence: string;
|
|
6
|
+
readonly prompt: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function startPrompts(): readonly StartPrompt[];
|
|
9
|
+
export declare function StartCard(props: {
|
|
10
|
+
readonly item: StartPrompt;
|
|
11
|
+
readonly copying: Copying;
|
|
12
|
+
}): Shown;
|
|
13
|
+
export declare function StartRun(props: {
|
|
14
|
+
readonly workflows: WatchModel["workflows"];
|
|
15
|
+
readonly copying: Copying;
|
|
16
|
+
readonly shown?: boolean;
|
|
17
|
+
}): Shown;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { component, element } from "./watch-element.js";
|
|
2
|
+
import { Command } from "./watch-turn.js";
|
|
3
|
+
export function startPrompts() {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
workflowId: "develop-issue",
|
|
7
|
+
sentence: "Work on a GitHub issue through a pull request.",
|
|
8
|
+
prompt: `Start a Cloving run for a GitHub issue in this repository.
|
|
9
|
+
Run: cloving run develop-issue --ticket ISSUE_NUMBER
|
|
10
|
+
Use the issue number that I give on the last line.
|
|
11
|
+
Report the run ID. Then run cloving status RUN_ID until the run completes or stops, and tell me the result and the pull request.
|
|
12
|
+
Do not push, merge or change labels yourself.
|
|
13
|
+
Issue number:`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
workflowId: "develop-ticket",
|
|
17
|
+
sentence: "Work on a ticket with your review of the diff.",
|
|
18
|
+
prompt: `Start a Cloving run for a ticket in this repository.
|
|
19
|
+
Run: cloving run develop-ticket --ticket TICKET_ID
|
|
20
|
+
Use the ticket ID that I give on the last line.
|
|
21
|
+
When the run waits for a decision, run cloving questions, show me the diff and the decision, and give me the approve and deny commands.
|
|
22
|
+
Run only the command that I choose. Do not decide for me.
|
|
23
|
+
Ticket ID:`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
workflowId: "develop-prd",
|
|
27
|
+
sentence: "Turn an idea into a GitHub issue.",
|
|
28
|
+
prompt: `Start a Cloving run that turns an idea into a GitHub issue.
|
|
29
|
+
Run: cloving run develop-prd --prompt "IDEA"
|
|
30
|
+
Use the idea that I give on the last line as IDEA.
|
|
31
|
+
When the run completes, show me the new issue.
|
|
32
|
+
Add the label cloving: ready only when I tell you to.
|
|
33
|
+
Idea:`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
workflowId: "develop-prompt",
|
|
37
|
+
sentence: "Do a small task with your review of the diff.",
|
|
38
|
+
prompt: `Start a Cloving run for a small task in this repository.
|
|
39
|
+
Run: cloving run develop-prompt --prompt "TASK"
|
|
40
|
+
Use the task that I give on the last line as TASK.
|
|
41
|
+
When the run waits for a decision, run cloving questions, show me the diff, and give me the approve and deny commands.
|
|
42
|
+
Run only the command that I choose. Do not decide for me.
|
|
43
|
+
Task:`,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
export function StartCard(props) {
|
|
48
|
+
const { item, copying } = props;
|
|
49
|
+
return element("article", {
|
|
50
|
+
class: "start-card",
|
|
51
|
+
children: [
|
|
52
|
+
element("h3", { children: item.workflowId }),
|
|
53
|
+
element("p", { children: item.sentence }),
|
|
54
|
+
component(Command, {
|
|
55
|
+
label: "Agent prompt",
|
|
56
|
+
command: item.prompt,
|
|
57
|
+
copyLabel: "Copy the prompt",
|
|
58
|
+
copying,
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function StartRun(props) {
|
|
64
|
+
const registered = startPrompts().filter((item) => props.workflows.some((workflow) => workflow.workflowId === item.workflowId));
|
|
65
|
+
const cards = registered.map((item) => component(StartCard, {
|
|
66
|
+
key: item.workflowId,
|
|
67
|
+
item,
|
|
68
|
+
copying: props.copying,
|
|
69
|
+
}));
|
|
70
|
+
return element("section", {
|
|
71
|
+
class: "start-run",
|
|
72
|
+
id: "start-run",
|
|
73
|
+
"aria-label": "Start a run",
|
|
74
|
+
hidden: props.shown !== true,
|
|
75
|
+
children: element("div", { class: "start-cards", children: cards }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { priceText, usageText } from "./usage-render.js";
|
|
1
2
|
import { component, element } from "./watch-element.js";
|
|
2
3
|
import { clockTime, counted, groupActivity, turnTime, } from "./watch-render.js";
|
|
3
4
|
export function Item(props) {
|
|
@@ -30,7 +31,7 @@ export function Turn(props) {
|
|
|
30
31
|
const { turn, now } = props;
|
|
31
32
|
const groups = groupActivity(turn.activity);
|
|
32
33
|
const working = turn.outcome === "in work";
|
|
33
|
-
const cost = turn.costUsd
|
|
34
|
+
const cost = priceText(turn.costUsd, turn.estimatedCostUsd);
|
|
34
35
|
const heading = ["Claude turn", turn.outcome, turnTime(turn, now), cost];
|
|
35
36
|
const place = `Branch ${turn.branch} · work tree ${turn.worktree}`;
|
|
36
37
|
const activity = groups.map((items, index) => component(Group, { items, open: working && index === groups.length - 1 }));
|
|
@@ -42,6 +43,10 @@ export function Turn(props) {
|
|
|
42
43
|
children: heading.filter((part) => part !== "").join(" · "),
|
|
43
44
|
}),
|
|
44
45
|
element("p", { class: "meta", children: place }),
|
|
46
|
+
element("p", {
|
|
47
|
+
class: "turn-usage",
|
|
48
|
+
children: usageText(turn.usage),
|
|
49
|
+
}),
|
|
45
50
|
element("div", { class: "activity", children: activity }),
|
|
46
51
|
],
|
|
47
52
|
});
|
|
@@ -77,7 +82,7 @@ export function Command(props) {
|
|
|
77
82
|
const onClick = () => {
|
|
78
83
|
copying.onCopy(command);
|
|
79
84
|
};
|
|
80
|
-
const shown = copying.command === command ? copying.label : "Copy";
|
|
85
|
+
const shown = copying.command === command ? copying.label : (props.copyLabel ?? "Copy");
|
|
81
86
|
return element("div", {
|
|
82
87
|
class: "command code-window",
|
|
83
88
|
children: [
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Fields } from "../protocol.ts";
|
|
2
|
+
import type { WatchTurn } from "./watch-render.ts";
|
|
3
|
+
export declare function priceFields(value: unknown): {
|
|
4
|
+
readonly costUsd?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function statusTurns(turns: readonly WatchTurn[], status: Fields): readonly WatchTurn[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { record } from "../protocol.js";
|
|
2
|
+
import { priceOf, usageOf } from "../worker-usage.js";
|
|
3
|
+
function text(value) {
|
|
4
|
+
return typeof value === "string" ? value : "";
|
|
5
|
+
}
|
|
6
|
+
function emptyTurn(operationId, runId) {
|
|
7
|
+
return {
|
|
8
|
+
operationId,
|
|
9
|
+
runId,
|
|
10
|
+
key: operationId,
|
|
11
|
+
usage: usageOf(null),
|
|
12
|
+
costUsd: null,
|
|
13
|
+
estimatedCostUsd: null,
|
|
14
|
+
target: "",
|
|
15
|
+
branch: "",
|
|
16
|
+
worktree: "",
|
|
17
|
+
startedAt: "",
|
|
18
|
+
endedAt: null,
|
|
19
|
+
activeAt: null,
|
|
20
|
+
outcome: "ended",
|
|
21
|
+
activity: [],
|
|
22
|
+
commits: [],
|
|
23
|
+
files: [],
|
|
24
|
+
uncommitted: [],
|
|
25
|
+
summary: "",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function priceFields(value) {
|
|
29
|
+
const cost = priceOf(value);
|
|
30
|
+
return cost === null ? {} : { costUsd: cost };
|
|
31
|
+
}
|
|
32
|
+
export function statusTurns(turns, status) {
|
|
33
|
+
const runId = text(status["runId"]);
|
|
34
|
+
const journal = turns.filter((turn) => turn.runId === runId);
|
|
35
|
+
if (!Array.isArray(status["workerTurns"]))
|
|
36
|
+
return journal;
|
|
37
|
+
const ended = status["workerTurns"].map((value) => {
|
|
38
|
+
const fields = record(value) ?? {};
|
|
39
|
+
const operationId = text(fields["operationId"]);
|
|
40
|
+
const known = journal.find((turn) => turn.operationId === operationId) ??
|
|
41
|
+
emptyTurn(operationId, runId);
|
|
42
|
+
return {
|
|
43
|
+
...known,
|
|
44
|
+
runId,
|
|
45
|
+
operationId,
|
|
46
|
+
outcome: known.outcome === "in work" ? "ended" : known.outcome,
|
|
47
|
+
activeAt: null,
|
|
48
|
+
usage: usageOf(fields["usage"]),
|
|
49
|
+
costUsd: priceOf(fields["costUsd"]),
|
|
50
|
+
estimatedCostUsd: priceOf(fields["estimatedCostUsd"]),
|
|
51
|
+
activity: known.activity.map((item) => item.kind === "result"
|
|
52
|
+
? {
|
|
53
|
+
kind: item.kind,
|
|
54
|
+
text: item.text,
|
|
55
|
+
...priceFields(fields["costUsd"]),
|
|
56
|
+
}
|
|
57
|
+
: item),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return [
|
|
61
|
+
...ended,
|
|
62
|
+
...journal.filter((turn) => turn.outcome === "in work" &&
|
|
63
|
+
!ended.some((item) => item.operationId === turn.operationId)),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { CardControls } from "./watch-card-view.ts";
|
|
1
2
|
import { type Shown } from "./watch-element.ts";
|
|
2
3
|
import { type Copying, type WatchModel, type WatchPhase, type WatchRun } from "./watch-render.ts";
|
|
4
|
+
import { type ColumnControls } from "./watch-board-view.ts";
|
|
3
5
|
interface Timed {
|
|
4
6
|
readonly run: WatchRun;
|
|
5
7
|
readonly now: string;
|
|
@@ -24,16 +26,29 @@ export declare function PullRequest(props: {
|
|
|
24
26
|
export declare function Run(props: Timed & {
|
|
25
27
|
readonly copying: Copying;
|
|
26
28
|
}): Shown;
|
|
29
|
+
export interface StartControls {
|
|
30
|
+
readonly shown: boolean;
|
|
31
|
+
readonly onToggle: () => void;
|
|
32
|
+
}
|
|
27
33
|
export declare function Header(props: {
|
|
28
34
|
readonly model: WatchModel | null;
|
|
29
35
|
readonly stale: boolean;
|
|
36
|
+
readonly start?: StartControls;
|
|
30
37
|
}): Shown;
|
|
31
38
|
export interface PageProps {
|
|
39
|
+
readonly cards?: CardControls;
|
|
32
40
|
readonly model: WatchModel | null;
|
|
33
41
|
readonly selected: string;
|
|
34
42
|
readonly now: string;
|
|
35
43
|
readonly stale: boolean;
|
|
36
44
|
readonly copying: Copying;
|
|
45
|
+
readonly columns?: ColumnControls;
|
|
46
|
+
readonly start?: StartControls;
|
|
37
47
|
}
|
|
38
48
|
export declare function Page(props: PageProps): Shown;
|
|
49
|
+
export declare function BoardPage(props: PageProps & {
|
|
50
|
+
readonly model: WatchModel;
|
|
51
|
+
readonly workflowId: string | null;
|
|
52
|
+
readonly down: Shown;
|
|
53
|
+
}): Shown;
|
|
39
54
|
export {};
|