@suwujs/king-ai 0.2.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 +96 -0
- package/dist/src/agent-config-validation.d.ts +9 -0
- package/dist/src/agent-config-validation.js +30 -0
- package/dist/src/api.d.ts +4 -0
- package/dist/src/api.js +48 -0
- package/dist/src/attachments.d.ts +45 -0
- package/dist/src/attachments.js +322 -0
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +1697 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +20 -0
- package/dist/src/cron.d.ts +11 -0
- package/dist/src/cron.js +65 -0
- package/dist/src/daemon.d.ts +36 -0
- package/dist/src/daemon.js +373 -0
- package/dist/src/engine.d.ts +32 -0
- package/dist/src/engine.js +1014 -0
- package/dist/src/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat.js +28 -0
- package/dist/src/host-api.d.ts +40 -0
- package/dist/src/host-api.js +59 -0
- package/dist/src/host-control.d.ts +48 -0
- package/dist/src/host-control.js +1279 -0
- package/dist/src/host-export.d.ts +50 -0
- package/dist/src/host-export.js +187 -0
- package/dist/src/host-feedback.d.ts +78 -0
- package/dist/src/host-feedback.js +178 -0
- package/dist/src/host-home.d.ts +13 -0
- package/dist/src/host-home.js +54 -0
- package/dist/src/host-ledger.d.ts +261 -0
- package/dist/src/host-ledger.js +554 -0
- package/dist/src/host-loop-events.d.ts +69 -0
- package/dist/src/host-loop-events.js +288 -0
- package/dist/src/host-permission.d.ts +36 -0
- package/dist/src/host-permission.js +180 -0
- package/dist/src/host-policy.d.ts +15 -0
- package/dist/src/host-policy.js +36 -0
- package/dist/src/host-run-executor.d.ts +13 -0
- package/dist/src/host-run-executor.js +221 -0
- package/dist/src/host-run-heartbeat.d.ts +40 -0
- package/dist/src/host-run-heartbeat.js +103 -0
- package/dist/src/host-run-layout.d.ts +17 -0
- package/dist/src/host-run-layout.js +387 -0
- package/dist/src/host-run-meta.d.ts +41 -0
- package/dist/src/host-run-meta.js +115 -0
- package/dist/src/host-run-spec.d.ts +149 -0
- package/dist/src/host-run-spec.js +465 -0
- package/dist/src/host-runs.d.ts +77 -0
- package/dist/src/host-runs.js +195 -0
- package/dist/src/host-sdk.d.ts +412 -0
- package/dist/src/host-sdk.js +628 -0
- package/dist/src/host-server.d.ts +26 -0
- package/dist/src/host-server.js +921 -0
- package/dist/src/host-timeline.d.ts +24 -0
- package/dist/src/host-timeline.js +161 -0
- package/dist/src/jsonl.d.ts +13 -0
- package/dist/src/jsonl.js +47 -0
- package/dist/src/lifecycle.d.ts +5 -0
- package/dist/src/lifecycle.js +18 -0
- package/dist/src/message-routing.d.ts +32 -0
- package/dist/src/message-routing.js +119 -0
- package/dist/src/paths.d.ts +19 -0
- package/dist/src/paths.js +26 -0
- package/dist/src/project-profile.d.ts +49 -0
- package/dist/src/project-profile.js +356 -0
- package/dist/src/remediation.d.ts +14 -0
- package/dist/src/remediation.js +114 -0
- package/dist/src/remote-devices.d.ts +41 -0
- package/dist/src/remote-devices.js +156 -0
- package/dist/src/remote-diagnostics.d.ts +39 -0
- package/dist/src/remote-diagnostics.js +199 -0
- package/dist/src/remote-ssh.d.ts +39 -0
- package/dist/src/remote-ssh.js +129 -0
- package/dist/src/run-stream.d.ts +57 -0
- package/dist/src/run-stream.js +119 -0
- package/dist/src/runner.d.ts +131 -0
- package/dist/src/runner.js +1161 -0
- package/dist/src/runtime-data.d.ts +68 -0
- package/dist/src/runtime-data.js +172 -0
- package/dist/src/service.d.ts +114 -0
- package/dist/src/service.js +631 -0
- package/dist/src/shared-skills.d.ts +26 -0
- package/dist/src/shared-skills.js +85 -0
- package/dist/src/shim.d.ts +1 -0
- package/dist/src/shim.js +64 -0
- package/dist/src/skill-check.d.ts +17 -0
- package/dist/src/skill-check.js +158 -0
- package/dist/src/sse.d.ts +9 -0
- package/dist/src/sse.js +36 -0
- package/dist/src/team-routing.d.ts +55 -0
- package/dist/src/team-routing.js +131 -0
- package/dist/src/team-workflow.d.ts +78 -0
- package/dist/src/team-workflow.js +253 -0
- package/dist/src/text.d.ts +7 -0
- package/dist/src/text.js +27 -0
- package/dist/src/types.d.ts +98 -0
- package/dist/src/types.js +1 -0
- package/dist/src/usage.d.ts +116 -0
- package/dist/src/usage.js +350 -0
- package/dist/src/workspace.d.ts +9 -0
- package/dist/src/workspace.js +56 -0
- package/dist/src/worktree.d.ts +47 -0
- package/dist/src/worktree.js +201 -0
- package/package.json +63 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { appendJsonl, compactJsonl, readJsonl } from "./jsonl.js";
|
|
3
|
+
export function hostTasksPathForOutputDir(outputDir = "deliverables") {
|
|
4
|
+
return join(resolve(outputDir), "tasks.jsonl");
|
|
5
|
+
}
|
|
6
|
+
export function hostCapsulesPathForOutputDir(outputDir = "deliverables") {
|
|
7
|
+
return join(resolve(outputDir), "capsules.jsonl");
|
|
8
|
+
}
|
|
9
|
+
export function hostWorkflowPathForOutputDir(outputDir = "deliverables") {
|
|
10
|
+
return join(resolve(outputDir), "workflow.jsonl");
|
|
11
|
+
}
|
|
12
|
+
export async function createHostTask(input, now = () => new Date()) {
|
|
13
|
+
const createdAt = now().toISOString();
|
|
14
|
+
const task = {
|
|
15
|
+
id: safeId(input.id) ?? buildId("task", createdAt),
|
|
16
|
+
title: requiredString(input.title, "task title"),
|
|
17
|
+
status: normalizeTaskStatus(input.status ?? "pending"),
|
|
18
|
+
createdAt,
|
|
19
|
+
assignee: cleanString(input.assignee),
|
|
20
|
+
ownerRole: cleanString(input.ownerRole),
|
|
21
|
+
reviewerRole: cleanString(input.reviewerRole),
|
|
22
|
+
priority: cleanString(input.priority),
|
|
23
|
+
dependsOn: normalizeStringArray(input.dependsOn, "dependsOn"),
|
|
24
|
+
capsuleId: cleanString(input.capsuleId),
|
|
25
|
+
acceptance: normalizeStringArray(input.acceptance, "acceptance"),
|
|
26
|
+
detail: cleanString(input.detail)
|
|
27
|
+
};
|
|
28
|
+
await appendJsonl(taskPath(input), task);
|
|
29
|
+
return task;
|
|
30
|
+
}
|
|
31
|
+
export async function updateHostTask(input, now = () => new Date()) {
|
|
32
|
+
const id = requiredId(input.id, "task id");
|
|
33
|
+
const existing = (await listHostTasks({ outputDir: input.outputDir, tasksFile: input.tasksFile })).find((task) => task.id === id);
|
|
34
|
+
if (!existing)
|
|
35
|
+
throw new Error(`host task not found: ${id}`);
|
|
36
|
+
const update = dropUndefined({
|
|
37
|
+
id,
|
|
38
|
+
updatedAt: now().toISOString(),
|
|
39
|
+
status: input.status === undefined ? undefined : normalizeTaskStatus(input.status),
|
|
40
|
+
assignee: cleanString(input.assignee),
|
|
41
|
+
ownerRole: cleanString(input.ownerRole),
|
|
42
|
+
reviewerRole: cleanString(input.reviewerRole),
|
|
43
|
+
priority: cleanString(input.priority),
|
|
44
|
+
dependsOn: input.dependsOn === undefined ? undefined : normalizeStringArray(input.dependsOn, "dependsOn"),
|
|
45
|
+
capsuleId: cleanString(input.capsuleId),
|
|
46
|
+
acceptance: input.acceptance === undefined ? undefined : normalizeStringArray(input.acceptance, "acceptance"),
|
|
47
|
+
detail: cleanString(input.detail),
|
|
48
|
+
result: cleanString(input.result)
|
|
49
|
+
});
|
|
50
|
+
await appendJsonl(taskPath(input), update);
|
|
51
|
+
return applyTaskUpdate(existing, update);
|
|
52
|
+
}
|
|
53
|
+
export async function listHostTasks(input = {}) {
|
|
54
|
+
const status = input.status === undefined ? undefined : normalizeTaskStatus(input.status);
|
|
55
|
+
const assignee = cleanString(input.assignee);
|
|
56
|
+
const limit = normalizeLimit(input.limit);
|
|
57
|
+
const tasks = await readMergedTasks(taskPath(input));
|
|
58
|
+
const filtered = tasks.filter((task) => {
|
|
59
|
+
if (status && task.status !== status)
|
|
60
|
+
return false;
|
|
61
|
+
if (assignee && task.assignee !== assignee)
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
return limit ? filtered.slice(-limit).reverse() : filtered;
|
|
66
|
+
}
|
|
67
|
+
export async function buildHostAgenda(input = {}) {
|
|
68
|
+
const tasks = await listHostTasks(input);
|
|
69
|
+
const done = new Set(tasks.filter((task) => task.status === "done" || task.status === "cancelled").map((task) => task.id));
|
|
70
|
+
const agendaTasks = tasks
|
|
71
|
+
.filter((task) => task.status !== "done" && task.status !== "cancelled")
|
|
72
|
+
.map((task) => {
|
|
73
|
+
const blockedBy = task.dependsOn.filter((id) => !done.has(id));
|
|
74
|
+
return {
|
|
75
|
+
...task,
|
|
76
|
+
blockedBy,
|
|
77
|
+
ready: task.status !== "blocked" && blockedBy.length === 0
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
const ready = agendaTasks.filter((task) => task.ready);
|
|
81
|
+
const blocked = agendaTasks.filter((task) => !task.ready);
|
|
82
|
+
const limit = normalizeLimit(input.limit);
|
|
83
|
+
const selected = (limit ? agendaTasks.slice(0, limit) : agendaTasks);
|
|
84
|
+
return {
|
|
85
|
+
tasks: selected,
|
|
86
|
+
readyCount: ready.length,
|
|
87
|
+
blockedCount: blocked.length,
|
|
88
|
+
summary: formatHostAgenda({ tasks: selected, readyCount: ready.length, blockedCount: blocked.length })
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export async function createHostCapsule(input, now = () => new Date()) {
|
|
92
|
+
const createdAt = now().toISOString();
|
|
93
|
+
const capsule = {
|
|
94
|
+
id: safeId(input.id) ?? buildId("capsule", createdAt),
|
|
95
|
+
status: normalizeCapsuleStatus(input.status ?? "open"),
|
|
96
|
+
createdAt,
|
|
97
|
+
taskId: cleanString(input.taskId),
|
|
98
|
+
goal: requiredString(input.goal, "capsule goal"),
|
|
99
|
+
owner: requiredString(input.owner, "capsule owner"),
|
|
100
|
+
branchOrWorktree: requiredString(input.branchOrWorktree, "capsule branchOrWorktree"),
|
|
101
|
+
allowedPaths: requiredStringArray(input.allowedPaths, "allowedPaths"),
|
|
102
|
+
acceptance: requiredStringArray(input.acceptance, "acceptance"),
|
|
103
|
+
reviewer: requiredString(input.reviewer, "capsule reviewer"),
|
|
104
|
+
verificationCommands: requiredStringArray(input.verificationCommands, "verificationCommands")
|
|
105
|
+
};
|
|
106
|
+
await appendJsonl(capsulePath(input), capsule);
|
|
107
|
+
return capsule;
|
|
108
|
+
}
|
|
109
|
+
export async function updateHostCapsule(input, now = () => new Date()) {
|
|
110
|
+
const id = requiredId(input.id, "capsule id");
|
|
111
|
+
const existing = (await listHostCapsules({ outputDir: input.outputDir, capsulesFile: input.capsulesFile })).find((capsule) => capsule.id === id);
|
|
112
|
+
if (!existing)
|
|
113
|
+
throw new Error(`host capsule not found: ${id}`);
|
|
114
|
+
const update = dropUndefined({
|
|
115
|
+
id,
|
|
116
|
+
updatedAt: now().toISOString(),
|
|
117
|
+
status: input.status === undefined ? undefined : normalizeCapsuleStatus(input.status),
|
|
118
|
+
taskId: cleanString(input.taskId),
|
|
119
|
+
goal: cleanString(input.goal),
|
|
120
|
+
owner: cleanString(input.owner),
|
|
121
|
+
branchOrWorktree: cleanString(input.branchOrWorktree),
|
|
122
|
+
allowedPaths: input.allowedPaths === undefined ? undefined : requiredStringArray(input.allowedPaths, "allowedPaths"),
|
|
123
|
+
acceptance: input.acceptance === undefined ? undefined : requiredStringArray(input.acceptance, "acceptance"),
|
|
124
|
+
reviewer: cleanString(input.reviewer),
|
|
125
|
+
verificationCommands: input.verificationCommands === undefined ? undefined : requiredStringArray(input.verificationCommands, "verificationCommands")
|
|
126
|
+
});
|
|
127
|
+
await appendJsonl(capsulePath(input), update);
|
|
128
|
+
return applyCapsuleUpdate(existing, update);
|
|
129
|
+
}
|
|
130
|
+
export async function listHostCapsules(input = {}) {
|
|
131
|
+
const status = input.status === undefined ? undefined : normalizeCapsuleStatus(input.status);
|
|
132
|
+
const owner = cleanString(input.owner);
|
|
133
|
+
const limit = normalizeLimit(input.limit);
|
|
134
|
+
const capsules = await readMergedCapsules(capsulePath(input));
|
|
135
|
+
const filtered = capsules.filter((capsule) => {
|
|
136
|
+
if (status && capsule.status !== status)
|
|
137
|
+
return false;
|
|
138
|
+
if (owner && capsule.owner !== owner)
|
|
139
|
+
return false;
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
return limit ? filtered.slice(-limit).reverse() : filtered;
|
|
143
|
+
}
|
|
144
|
+
export async function getHostCapsule(input) {
|
|
145
|
+
const id = requiredId(input.id, "capsule id");
|
|
146
|
+
return (await listHostCapsules(input)).find((capsule) => capsule.id === id) ?? null;
|
|
147
|
+
}
|
|
148
|
+
export async function createHostWorkflowCard(input, now = () => new Date()) {
|
|
149
|
+
const createdAt = now().toISOString();
|
|
150
|
+
const card = {
|
|
151
|
+
kind: normalizeWorkflowKind(input.kind),
|
|
152
|
+
id: safeId(input.id) ?? buildId(input.kind, createdAt),
|
|
153
|
+
title: requiredString(input.title, "workflow title"),
|
|
154
|
+
status: normalizeWorkflowStatus(input.status ?? defaultWorkflowStatus(input.kind)),
|
|
155
|
+
createdAt,
|
|
156
|
+
ownerRole: cleanString(input.ownerRole),
|
|
157
|
+
reviewerRole: cleanString(input.reviewerRole),
|
|
158
|
+
assignee: cleanString(input.assignee),
|
|
159
|
+
priority: cleanString(input.priority),
|
|
160
|
+
dependsOn: normalizeStringArray(input.dependsOn, "dependsOn"),
|
|
161
|
+
acceptance: normalizeStringArray(input.acceptance, "acceptance"),
|
|
162
|
+
handoffPolicy: input.handoffPolicy,
|
|
163
|
+
sourceId: cleanString(input.sourceId),
|
|
164
|
+
targetRole: cleanString(input.targetRole),
|
|
165
|
+
decisionBy: cleanString(input.decisionBy),
|
|
166
|
+
artifactPath: cleanString(input.artifactPath),
|
|
167
|
+
detail: cleanString(input.detail),
|
|
168
|
+
result: cleanString(input.result),
|
|
169
|
+
metadata: normalizeMetadata(input.metadata)
|
|
170
|
+
};
|
|
171
|
+
await appendJsonl(workflowPath(input), card);
|
|
172
|
+
return card;
|
|
173
|
+
}
|
|
174
|
+
export async function updateHostWorkflowCard(input, now = () => new Date()) {
|
|
175
|
+
const id = requiredId(input.id, "workflow id");
|
|
176
|
+
const cards = await listHostWorkflowCards({ outputDir: input.outputDir, workflowFile: input.workflowFile });
|
|
177
|
+
const existing = cards.find((card) => card.id === id);
|
|
178
|
+
if (!existing)
|
|
179
|
+
throw new Error(`host workflow card not found: ${id}`);
|
|
180
|
+
const update = dropUndefined({
|
|
181
|
+
id,
|
|
182
|
+
updatedAt: now().toISOString(),
|
|
183
|
+
status: input.status === undefined ? undefined : normalizeWorkflowStatus(input.status),
|
|
184
|
+
ownerRole: cleanString(input.ownerRole),
|
|
185
|
+
reviewerRole: cleanString(input.reviewerRole),
|
|
186
|
+
assignee: cleanString(input.assignee),
|
|
187
|
+
priority: cleanString(input.priority),
|
|
188
|
+
dependsOn: input.dependsOn === undefined ? undefined : normalizeStringArray(input.dependsOn, "dependsOn"),
|
|
189
|
+
acceptance: input.acceptance === undefined ? undefined : normalizeStringArray(input.acceptance, "acceptance"),
|
|
190
|
+
handoffPolicy: input.handoffPolicy,
|
|
191
|
+
sourceId: cleanString(input.sourceId),
|
|
192
|
+
targetRole: cleanString(input.targetRole),
|
|
193
|
+
decisionBy: cleanString(input.decisionBy),
|
|
194
|
+
artifactPath: cleanString(input.artifactPath),
|
|
195
|
+
detail: cleanString(input.detail),
|
|
196
|
+
result: cleanString(input.result),
|
|
197
|
+
metadata: normalizeMetadata(input.metadata)
|
|
198
|
+
});
|
|
199
|
+
const merged = applyWorkflowUpdate(existing, update);
|
|
200
|
+
validateWorkflowUpdate(existing, merged, cards);
|
|
201
|
+
await appendJsonl(workflowPath(input), update);
|
|
202
|
+
return merged;
|
|
203
|
+
}
|
|
204
|
+
export async function listHostWorkflowCards(input = {}) {
|
|
205
|
+
const kind = input.kind === undefined ? undefined : normalizeWorkflowKind(input.kind);
|
|
206
|
+
const status = input.status === undefined ? undefined : normalizeWorkflowStatus(input.status);
|
|
207
|
+
const ownerRole = cleanString(input.ownerRole);
|
|
208
|
+
const reviewerRole = cleanString(input.reviewerRole);
|
|
209
|
+
const assignee = cleanString(input.assignee);
|
|
210
|
+
const limit = normalizeLimit(input.limit);
|
|
211
|
+
const cards = await readMergedWorkflowCards(workflowPath(input));
|
|
212
|
+
const filtered = cards.filter((card) => {
|
|
213
|
+
if (kind && card.kind !== kind)
|
|
214
|
+
return false;
|
|
215
|
+
if (status && card.status !== status)
|
|
216
|
+
return false;
|
|
217
|
+
if (ownerRole && card.ownerRole !== ownerRole)
|
|
218
|
+
return false;
|
|
219
|
+
if (reviewerRole && card.reviewerRole !== reviewerRole)
|
|
220
|
+
return false;
|
|
221
|
+
if (assignee && card.assignee !== assignee)
|
|
222
|
+
return false;
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
225
|
+
return limit ? filtered.slice(-limit).reverse() : filtered;
|
|
226
|
+
}
|
|
227
|
+
export function formatHostWorkflowCards(cards) {
|
|
228
|
+
if (cards.length === 0)
|
|
229
|
+
return "no host workflow cards";
|
|
230
|
+
return cards.map((card) => {
|
|
231
|
+
const owner = card.ownerRole ? ` ownerRole=${card.ownerRole}` : "";
|
|
232
|
+
const reviewer = card.reviewerRole ? ` reviewerRole=${card.reviewerRole}` : "";
|
|
233
|
+
const assignee = card.assignee ? ` @${card.assignee}` : "";
|
|
234
|
+
const deps = card.dependsOn.length ? ` dependsOn=${card.dependsOn.join(",")}` : "";
|
|
235
|
+
const artifact = card.artifactPath ? ` artifact=${card.artifactPath}` : "";
|
|
236
|
+
return `${card.id} ${card.kind} ${card.status}${assignee}${owner}${reviewer}${deps}${artifact}: ${card.title}`;
|
|
237
|
+
}).join("\n");
|
|
238
|
+
}
|
|
239
|
+
export function formatHostTasks(tasks) {
|
|
240
|
+
if (tasks.length === 0)
|
|
241
|
+
return "no host tasks";
|
|
242
|
+
return tasks.map((task) => {
|
|
243
|
+
const deps = task.dependsOn.length ? ` dependsOn=${task.dependsOn.join(",")}` : "";
|
|
244
|
+
const capsule = task.capsuleId ? ` capsule=${task.capsuleId}` : "";
|
|
245
|
+
const owner = task.ownerRole ? ` ownerRole=${task.ownerRole}` : "";
|
|
246
|
+
const reviewer = task.reviewerRole ? ` reviewerRole=${task.reviewerRole}` : "";
|
|
247
|
+
return `${task.id} ${task.status}${task.assignee ? ` @${task.assignee}` : ""}${owner}${reviewer}${deps}${capsule}: ${task.title}`;
|
|
248
|
+
}).join("\n");
|
|
249
|
+
}
|
|
250
|
+
export function formatHostCapsules(capsules) {
|
|
251
|
+
if (capsules.length === 0)
|
|
252
|
+
return "no host capsules";
|
|
253
|
+
return capsules.map((capsule) => {
|
|
254
|
+
const task = capsule.taskId ? ` task=${capsule.taskId}` : "";
|
|
255
|
+
return `${capsule.id} ${capsule.status} owner=${capsule.owner} reviewer=${capsule.reviewer}${task}: ${capsule.goal}`;
|
|
256
|
+
}).join("\n");
|
|
257
|
+
}
|
|
258
|
+
export function formatHostAgenda(agenda) {
|
|
259
|
+
const lines = [`agenda: ready=${agenda.readyCount} blocked=${agenda.blockedCount}`];
|
|
260
|
+
if (agenda.tasks.length === 0) {
|
|
261
|
+
lines.push("no open host tasks");
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
for (const task of agenda.tasks) {
|
|
265
|
+
const blockedBy = task.blockedBy.length ? ` blockedBy=${task.blockedBy.join(",")}` : "";
|
|
266
|
+
lines.push(`${task.ready ? "ready" : "blocked"} ${task.id}${task.assignee ? ` @${task.assignee}` : ""}${blockedBy}: ${task.title}`);
|
|
267
|
+
}
|
|
268
|
+
return lines.join("\n");
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Rewrite the append-only task, capsule, and workflow logs into their merged latest-state
|
|
272
|
+
* snapshots. The result is observationally identical to the pre-compaction ledger but drops
|
|
273
|
+
* superseded update records, bounding unbounded log growth from long multi-role runs.
|
|
274
|
+
*/
|
|
275
|
+
export async function compactHostLedger(input = {}) {
|
|
276
|
+
const tasks = await compactJsonl(taskPath(input), (records) => mergeTasks(records));
|
|
277
|
+
const capsules = await compactJsonl(capsulePath(input), (records) => mergeCapsules(records));
|
|
278
|
+
const workflow = await compactJsonl(workflowPath(input), (records) => mergeWorkflowCards(records));
|
|
279
|
+
return { tasks, capsules, workflow };
|
|
280
|
+
}
|
|
281
|
+
export function formatHostLedgerCompact(result) {
|
|
282
|
+
const line = (label, entry) => `${label}: ${entry.records} -> ${entry.written} records (${entry.path})`;
|
|
283
|
+
return [
|
|
284
|
+
"ledger compacted",
|
|
285
|
+
line("tasks", result.tasks),
|
|
286
|
+
line("capsules", result.capsules),
|
|
287
|
+
line("workflow", result.workflow)
|
|
288
|
+
].join("\n");
|
|
289
|
+
}
|
|
290
|
+
function taskPath(input) {
|
|
291
|
+
return input.tasksFile ? resolve(input.tasksFile) : hostTasksPathForOutputDir(input.outputDir);
|
|
292
|
+
}
|
|
293
|
+
function capsulePath(input) {
|
|
294
|
+
return input.capsulesFile ? resolve(input.capsulesFile) : hostCapsulesPathForOutputDir(input.outputDir);
|
|
295
|
+
}
|
|
296
|
+
function workflowPath(input) {
|
|
297
|
+
return input.workflowFile ? resolve(input.workflowFile) : hostWorkflowPathForOutputDir(input.outputDir);
|
|
298
|
+
}
|
|
299
|
+
function mergeTasks(records) {
|
|
300
|
+
const byId = new Map();
|
|
301
|
+
for (const record of records) {
|
|
302
|
+
if (isTask(record))
|
|
303
|
+
byId.set(record.id, record);
|
|
304
|
+
else if (isTaskUpdate(record)) {
|
|
305
|
+
const existing = byId.get(record.id);
|
|
306
|
+
if (existing)
|
|
307
|
+
byId.set(record.id, applyTaskUpdate(existing, record));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return [...byId.values()];
|
|
311
|
+
}
|
|
312
|
+
function mergeCapsules(records) {
|
|
313
|
+
const byId = new Map();
|
|
314
|
+
for (const record of records) {
|
|
315
|
+
if (isCapsule(record))
|
|
316
|
+
byId.set(record.id, record);
|
|
317
|
+
else if (isCapsuleUpdate(record)) {
|
|
318
|
+
const existing = byId.get(record.id);
|
|
319
|
+
if (existing)
|
|
320
|
+
byId.set(record.id, applyCapsuleUpdate(existing, record));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return [...byId.values()];
|
|
324
|
+
}
|
|
325
|
+
function mergeWorkflowCards(records) {
|
|
326
|
+
const byId = new Map();
|
|
327
|
+
for (const record of records) {
|
|
328
|
+
if (isWorkflowCard(record))
|
|
329
|
+
byId.set(record.id, record);
|
|
330
|
+
else if (isWorkflowUpdate(record)) {
|
|
331
|
+
const existing = byId.get(record.id);
|
|
332
|
+
if (existing)
|
|
333
|
+
byId.set(record.id, applyWorkflowUpdate(existing, record));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return [...byId.values()];
|
|
337
|
+
}
|
|
338
|
+
async function readMergedTasks(path) {
|
|
339
|
+
return mergeTasks(await readJsonl(path));
|
|
340
|
+
}
|
|
341
|
+
async function readMergedCapsules(path) {
|
|
342
|
+
return mergeCapsules(await readJsonl(path));
|
|
343
|
+
}
|
|
344
|
+
async function readMergedWorkflowCards(path) {
|
|
345
|
+
return mergeWorkflowCards(await readJsonl(path));
|
|
346
|
+
}
|
|
347
|
+
function applyTaskUpdate(task, update) {
|
|
348
|
+
return {
|
|
349
|
+
...task,
|
|
350
|
+
status: update.status ?? task.status,
|
|
351
|
+
assignee: update.assignee ?? task.assignee,
|
|
352
|
+
ownerRole: update.ownerRole ?? task.ownerRole,
|
|
353
|
+
reviewerRole: update.reviewerRole ?? task.reviewerRole,
|
|
354
|
+
priority: update.priority ?? task.priority,
|
|
355
|
+
dependsOn: update.dependsOn ?? task.dependsOn,
|
|
356
|
+
capsuleId: update.capsuleId ?? task.capsuleId,
|
|
357
|
+
acceptance: update.acceptance ?? task.acceptance,
|
|
358
|
+
detail: update.detail ?? task.detail,
|
|
359
|
+
result: update.result ?? task.result,
|
|
360
|
+
updatedAt: update.updatedAt
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function applyWorkflowUpdate(card, update) {
|
|
364
|
+
return {
|
|
365
|
+
...card,
|
|
366
|
+
status: update.status ?? card.status,
|
|
367
|
+
ownerRole: update.ownerRole ?? card.ownerRole,
|
|
368
|
+
reviewerRole: update.reviewerRole ?? card.reviewerRole,
|
|
369
|
+
assignee: update.assignee ?? card.assignee,
|
|
370
|
+
priority: update.priority ?? card.priority,
|
|
371
|
+
dependsOn: update.dependsOn ?? card.dependsOn,
|
|
372
|
+
acceptance: update.acceptance ?? card.acceptance,
|
|
373
|
+
handoffPolicy: update.handoffPolicy ?? card.handoffPolicy,
|
|
374
|
+
sourceId: update.sourceId ?? card.sourceId,
|
|
375
|
+
targetRole: update.targetRole ?? card.targetRole,
|
|
376
|
+
decisionBy: update.decisionBy ?? card.decisionBy,
|
|
377
|
+
artifactPath: update.artifactPath ?? card.artifactPath,
|
|
378
|
+
detail: update.detail ?? card.detail,
|
|
379
|
+
result: update.result ?? card.result,
|
|
380
|
+
metadata: update.metadata ?? card.metadata,
|
|
381
|
+
updatedAt: update.updatedAt
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function validateWorkflowUpdate(existing, next, cards) {
|
|
385
|
+
if (next.status !== "done")
|
|
386
|
+
return;
|
|
387
|
+
const done = new Set(cards
|
|
388
|
+
.filter((card) => card.id !== next.id && (card.status === "done" || card.status === "cancelled"))
|
|
389
|
+
.map((card) => card.id));
|
|
390
|
+
const blockedBy = next.dependsOn.filter((id) => !done.has(id));
|
|
391
|
+
if (blockedBy.length > 0) {
|
|
392
|
+
throw new Error(`workflow card ${next.id} depends on unfinished workflow cards: ${blockedBy.join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
if (next.acceptance.length > 0 && !next.result && !next.artifactPath) {
|
|
395
|
+
throw new Error(`workflow card ${next.id} requires result or artifactPath before marking done`);
|
|
396
|
+
}
|
|
397
|
+
if (next.kind === "review" && !reviewVerdict(next.result)) {
|
|
398
|
+
throw new Error(`review card ${next.id} requires result=approved or result=changes_requested before marking done`);
|
|
399
|
+
}
|
|
400
|
+
void existing;
|
|
401
|
+
}
|
|
402
|
+
function reviewVerdict(result) {
|
|
403
|
+
const value = result?.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
404
|
+
if (!value)
|
|
405
|
+
return undefined;
|
|
406
|
+
if (value === "approved" || value === "approve" || value === "accepted" || value === "pass" || value === "passed")
|
|
407
|
+
return "approved";
|
|
408
|
+
if (value === "changes_requested" || value === "change_requested" || value === "rejected" || value === "needs_work" || value === "fail" || value === "failed")
|
|
409
|
+
return "changes_requested";
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
function applyCapsuleUpdate(capsule, update) {
|
|
413
|
+
return {
|
|
414
|
+
...capsule,
|
|
415
|
+
status: update.status ?? capsule.status,
|
|
416
|
+
taskId: update.taskId ?? capsule.taskId,
|
|
417
|
+
goal: update.goal ?? capsule.goal,
|
|
418
|
+
owner: update.owner ?? capsule.owner,
|
|
419
|
+
branchOrWorktree: update.branchOrWorktree ?? capsule.branchOrWorktree,
|
|
420
|
+
allowedPaths: update.allowedPaths ?? capsule.allowedPaths,
|
|
421
|
+
acceptance: update.acceptance ?? capsule.acceptance,
|
|
422
|
+
reviewer: update.reviewer ?? capsule.reviewer,
|
|
423
|
+
verificationCommands: update.verificationCommands ?? capsule.verificationCommands,
|
|
424
|
+
updatedAt: update.updatedAt
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function isTask(value) {
|
|
428
|
+
if (!value || typeof value !== "object")
|
|
429
|
+
return false;
|
|
430
|
+
const record = value;
|
|
431
|
+
return typeof record.id === "string" && typeof record.title === "string" && Array.isArray(record.dependsOn) && Array.isArray(record.acceptance) && isTaskStatus(record.status);
|
|
432
|
+
}
|
|
433
|
+
function isTaskUpdate(value) {
|
|
434
|
+
if (!value || typeof value !== "object")
|
|
435
|
+
return false;
|
|
436
|
+
const record = value;
|
|
437
|
+
return typeof record.id === "string" && typeof record.updatedAt === "string" && !("title" in record);
|
|
438
|
+
}
|
|
439
|
+
function isCapsule(value) {
|
|
440
|
+
if (!value || typeof value !== "object")
|
|
441
|
+
return false;
|
|
442
|
+
const record = value;
|
|
443
|
+
return typeof record.id === "string" && typeof record.goal === "string" && Array.isArray(record.allowedPaths) && isCapsuleStatus(record.status);
|
|
444
|
+
}
|
|
445
|
+
function isCapsuleUpdate(value) {
|
|
446
|
+
if (!value || typeof value !== "object")
|
|
447
|
+
return false;
|
|
448
|
+
const record = value;
|
|
449
|
+
return typeof record.id === "string" && typeof record.updatedAt === "string" && !("createdAt" in record);
|
|
450
|
+
}
|
|
451
|
+
function isWorkflowCard(value) {
|
|
452
|
+
if (!value || typeof value !== "object")
|
|
453
|
+
return false;
|
|
454
|
+
const record = value;
|
|
455
|
+
return typeof record.id === "string" && typeof record.title === "string" && isWorkflowKind(record.kind) && isWorkflowStatus(record.status) && Array.isArray(record.dependsOn) && Array.isArray(record.acceptance);
|
|
456
|
+
}
|
|
457
|
+
function isWorkflowUpdate(value) {
|
|
458
|
+
if (!value || typeof value !== "object")
|
|
459
|
+
return false;
|
|
460
|
+
const record = value;
|
|
461
|
+
return typeof record.id === "string" && typeof record.updatedAt === "string" && !("kind" in record);
|
|
462
|
+
}
|
|
463
|
+
function normalizeTaskStatus(value) {
|
|
464
|
+
if (isTaskStatus(value))
|
|
465
|
+
return value;
|
|
466
|
+
throw new Error(`invalid task status: ${String(value)}`);
|
|
467
|
+
}
|
|
468
|
+
function isTaskStatus(value) {
|
|
469
|
+
return value === "pending" || value === "in_progress" || value === "blocked" || value === "done" || value === "cancelled";
|
|
470
|
+
}
|
|
471
|
+
function normalizeCapsuleStatus(value) {
|
|
472
|
+
if (isCapsuleStatus(value))
|
|
473
|
+
return value;
|
|
474
|
+
throw new Error(`invalid capsule status: ${String(value)}`);
|
|
475
|
+
}
|
|
476
|
+
function isCapsuleStatus(value) {
|
|
477
|
+
return value === "open" || value === "in_review" || value === "accepted" || value === "abandoned";
|
|
478
|
+
}
|
|
479
|
+
function normalizeWorkflowKind(value) {
|
|
480
|
+
if (isWorkflowKind(value))
|
|
481
|
+
return value;
|
|
482
|
+
throw new Error(`invalid workflow kind: ${String(value)}`);
|
|
483
|
+
}
|
|
484
|
+
function isWorkflowKind(value) {
|
|
485
|
+
return value === "initiative" || value === "task" || value === "handoff" || value === "review" || value === "decision" || value === "artifact";
|
|
486
|
+
}
|
|
487
|
+
function normalizeWorkflowStatus(value) {
|
|
488
|
+
if (isWorkflowStatus(value))
|
|
489
|
+
return value;
|
|
490
|
+
throw new Error(`invalid workflow status: ${String(value)}`);
|
|
491
|
+
}
|
|
492
|
+
function isWorkflowStatus(value) {
|
|
493
|
+
return value === "open" || value === "assigned" || value === "in_progress" || value === "blocked" || value === "review" || value === "waiting_human" || value === "done" || value === "cancelled";
|
|
494
|
+
}
|
|
495
|
+
function defaultWorkflowStatus(kind) {
|
|
496
|
+
return kind === "decision" ? "waiting_human" : "open";
|
|
497
|
+
}
|
|
498
|
+
function normalizeLimit(value) {
|
|
499
|
+
if (value === undefined)
|
|
500
|
+
return undefined;
|
|
501
|
+
const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
502
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
503
|
+
throw new Error("limit must be a positive integer");
|
|
504
|
+
return Math.floor(parsed);
|
|
505
|
+
}
|
|
506
|
+
function normalizeStringArray(value, label) {
|
|
507
|
+
if (value === undefined)
|
|
508
|
+
return [];
|
|
509
|
+
if (!Array.isArray(value))
|
|
510
|
+
throw new Error(`${label} must be an array`);
|
|
511
|
+
return value.map((entry) => requiredString(entry, label));
|
|
512
|
+
}
|
|
513
|
+
function requiredStringArray(value, label) {
|
|
514
|
+
const entries = normalizeStringArray(value, label);
|
|
515
|
+
if (entries.length === 0)
|
|
516
|
+
throw new Error(`${label} must include at least one entry`);
|
|
517
|
+
return entries;
|
|
518
|
+
}
|
|
519
|
+
function requiredString(value, label) {
|
|
520
|
+
const cleaned = cleanString(value);
|
|
521
|
+
if (!cleaned)
|
|
522
|
+
throw new Error(`${label} is required`);
|
|
523
|
+
return cleaned;
|
|
524
|
+
}
|
|
525
|
+
function requiredId(value, label) {
|
|
526
|
+
const cleaned = safeId(value);
|
|
527
|
+
if (!cleaned)
|
|
528
|
+
throw new Error(`${label} is required`);
|
|
529
|
+
return cleaned;
|
|
530
|
+
}
|
|
531
|
+
function safeId(value) {
|
|
532
|
+
if (typeof value !== "string" || !value.trim())
|
|
533
|
+
return undefined;
|
|
534
|
+
const cleaned = value.trim();
|
|
535
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(cleaned))
|
|
536
|
+
throw new Error(`invalid id: ${cleaned}`);
|
|
537
|
+
return cleaned;
|
|
538
|
+
}
|
|
539
|
+
function buildId(prefix, iso) {
|
|
540
|
+
return `${prefix}-${iso.replace(/[:.]/g, "-").slice(0, 19)}`;
|
|
541
|
+
}
|
|
542
|
+
function cleanString(value) {
|
|
543
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
544
|
+
}
|
|
545
|
+
function normalizeMetadata(value) {
|
|
546
|
+
if (value === undefined)
|
|
547
|
+
return undefined;
|
|
548
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
549
|
+
throw new Error("metadata must be an object");
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
function dropUndefined(value) {
|
|
553
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
554
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface HostLoopEvent {
|
|
2
|
+
type?: string;
|
|
3
|
+
runId?: string;
|
|
4
|
+
loop?: number;
|
|
5
|
+
timestamp?: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
classification?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface HostLoopEventsInput {
|
|
11
|
+
file?: string;
|
|
12
|
+
outputDir?: string;
|
|
13
|
+
runId?: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
classification?: string;
|
|
17
|
+
tail?: number;
|
|
18
|
+
resultsFile?: string;
|
|
19
|
+
writeResults?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface HostLoopEventAppendInput {
|
|
22
|
+
file?: string;
|
|
23
|
+
outputDir?: string;
|
|
24
|
+
event: HostLoopEvent;
|
|
25
|
+
}
|
|
26
|
+
export interface HostLoopEventsResult {
|
|
27
|
+
file: string;
|
|
28
|
+
events: HostLoopEvent[];
|
|
29
|
+
totalEvents: number;
|
|
30
|
+
filteredEvents: number;
|
|
31
|
+
summary: HostLoopEventsSummary;
|
|
32
|
+
results: HostLoopResultsTable;
|
|
33
|
+
}
|
|
34
|
+
export interface HostLoopEventsSummary {
|
|
35
|
+
loops: number;
|
|
36
|
+
classifications: Record<string, number>;
|
|
37
|
+
productiveRate: number;
|
|
38
|
+
}
|
|
39
|
+
export interface HostLoopResultsTable {
|
|
40
|
+
file: string;
|
|
41
|
+
rows: HostLoopResultsRow[];
|
|
42
|
+
written: boolean;
|
|
43
|
+
text?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface HostLoopResultsRow {
|
|
46
|
+
runId: string;
|
|
47
|
+
loop: number;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
classification: string;
|
|
50
|
+
tasksCreated: number;
|
|
51
|
+
tasksDone: number;
|
|
52
|
+
artifactsCreated: number;
|
|
53
|
+
pendingMessages: number;
|
|
54
|
+
completionRate: string;
|
|
55
|
+
notes: string;
|
|
56
|
+
}
|
|
57
|
+
export interface HostLoopResultsInput {
|
|
58
|
+
file?: string;
|
|
59
|
+
outputDir?: string;
|
|
60
|
+
resultsFile?: string;
|
|
61
|
+
}
|
|
62
|
+
export declare const HOST_LOOP_RESULTS_HEADER: string;
|
|
63
|
+
export declare function appendHostLoopEvent(input: HostLoopEventAppendInput): Promise<string>;
|
|
64
|
+
export declare function readHostLoopEvents(input?: HostLoopEventsInput): Promise<HostLoopEventsResult>;
|
|
65
|
+
export declare function readHostLoopResults(input?: HostLoopResultsInput): Promise<HostLoopResultsTable>;
|
|
66
|
+
export declare function formatHostLoopEvents(result: HostLoopEventsResult): string;
|
|
67
|
+
export declare function buildLoopResultsRows(events: HostLoopEvent[]): HostLoopResultsRow[];
|
|
68
|
+
export declare function formatLoopResultsTable(rows: HostLoopResultsRow[]): string;
|
|
69
|
+
export declare function parseLoopResultsTable(text: string): HostLoopResultsRow[];
|