@astrosheep/keiyaku 4.5.5 → 4.5.7
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/build/integrations/marketplace/plugins/keiyaku/opencode.js +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +114 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +15 -19
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +20 -33
- package/build/src/akuma/heart/index.d.ts +2 -2
- package/build/src/akuma/heart/index.js +2 -2
- package/build/src/akuma/heart/request-authority.d.ts +1 -0
- package/build/src/akuma/heart/request-authority.js +11 -1
- package/build/src/akuma/heart/storage.d.ts +0 -1
- package/build/src/akuma/heart/storage.js +0 -54
- package/build/src/akuma/publication.js +75 -13
- package/build/src/akuma/request-lifecycle.js +9 -22
- package/build/src/akuma/request-serve.js +17 -2
- package/build/src/cli/commands/task-invoke.d.ts +6 -2
- package/build/src/cli/commands/task-invoke.js +56 -24
- package/build/src/cli/commands/task.d.ts +1 -1
- package/build/src/cli/commands/task.js +28 -6
- package/build/src/cli/coordinates.d.ts +2 -0
- package/build/src/cli/coordinates.js +18 -2
- package/build/src/cli/invoke.js +1 -0
- package/build/src/cli/render/task.js +31 -6
- package/build/src/cli/square-edge.js +2 -2
- package/build/src/git/repository.d.ts +2 -0
- package/build/src/git/repository.js +34 -21
- package/build/src/git/target-placement.js +0 -6
- package/build/src/protocol/attempt.js +9 -4
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/task/compose-language.d.ts +47 -0
- package/build/src/task/compose-language.js +328 -0
- package/build/src/task/compose-parser.d.ts +32 -0
- package/build/src/task/compose-parser.js +180 -0
- package/build/src/task/compose.d.ts +29 -9
- package/build/src/task/compose.js +126 -287
- package/build/src/task/context.d.ts +7 -1
- package/build/src/task/context.js +25 -6
- package/build/src/task/index.d.ts +10 -4
- package/build/src/task/index.js +14 -3
- package/build/src/task/operations.d.ts +6 -1
- package/package.json +2 -2
|
@@ -1,305 +1,144 @@
|
|
|
1
1
|
import { documentDiff } from "../markdown/diff.js";
|
|
2
|
-
import {
|
|
2
|
+
import { planTaskComposition, } from "./compose-language.js";
|
|
3
3
|
import { serializeTaskDocument } from "./document.js";
|
|
4
|
-
import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
5
4
|
import { authorityPath, readBoard, replaceAuthority, withTaskLocks } from "./store.js";
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
return { kind: "invalid-composition",
|
|
5
|
+
export { taskCompositionNamespaceHeader } from "./compose-language.js";
|
|
6
|
+
function refusal(diagnostics) {
|
|
7
|
+
return { kind: "refused", refusal: { kind: "invalid-composition", diagnostics } };
|
|
9
8
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
return [];
|
|
13
|
-
const coordinate = parseTaskId(`task/${value}/placeholder`);
|
|
14
|
-
return coordinate.namespace;
|
|
15
|
-
}
|
|
16
|
-
function assignment(token) {
|
|
17
|
-
const matched = /^(parent|needs|supersedes|relates|pri|body)(\+=|=)(.*)$/u.exec(token);
|
|
18
|
-
if (matched === null)
|
|
19
|
-
return null;
|
|
20
|
-
const field = matched[1], append = matched[2] === "+=", value = matched[3];
|
|
21
|
-
if (append && (field === "parent" || field === "pri" || field === "body"))
|
|
22
|
-
throw new TypeError(`${field} does not accept +=`);
|
|
23
|
-
if (field === "body" && (append || value !== ""))
|
|
24
|
-
throw new TypeError("body accepts only bare body=");
|
|
25
|
-
return { field, append, value };
|
|
26
|
-
}
|
|
27
|
-
function parseNode(text, depth, index) {
|
|
28
|
-
const tokens = text.split(/ +/u);
|
|
29
|
-
if (text.startsWith("+ ")) {
|
|
30
|
-
const values = tokens.slice(1);
|
|
31
|
-
const at = values.findIndex((token) => assignment(token) !== null);
|
|
32
|
-
const title = values.slice(0, at < 0 ? values.length : at).join(" ");
|
|
33
|
-
if (title.trim().length === 0)
|
|
34
|
-
throw new TypeError("compose + node requires a title");
|
|
35
|
-
return {
|
|
36
|
-
index,
|
|
37
|
-
depth,
|
|
38
|
-
kind: "new",
|
|
39
|
-
title,
|
|
40
|
-
assignments: (at < 0 ? [] : values.slice(at)).map((token) => assignment(token) ??
|
|
41
|
-
(() => {
|
|
42
|
-
throw new TypeError(`invalid assignment token: ${token}`);
|
|
43
|
-
})()),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const [rawId, ...rest] = tokens;
|
|
47
|
-
if (rawId === undefined || !rawId.startsWith("@task/"))
|
|
48
|
-
throw new TypeError("compose node must begin with + or @task/");
|
|
49
|
-
const id = `task/${rawId.slice(1).slice(5)}`;
|
|
50
|
-
parseTaskId(id);
|
|
51
|
-
return {
|
|
52
|
-
index,
|
|
53
|
-
depth,
|
|
54
|
-
kind: "existing",
|
|
55
|
-
id,
|
|
56
|
-
assignments: rest.map((token) => assignment(token) ??
|
|
57
|
-
(() => {
|
|
58
|
-
throw new TypeError(`invalid assignment token: ${token}`);
|
|
59
|
-
})()),
|
|
60
|
-
};
|
|
9
|
+
function currentTimestamp() {
|
|
10
|
+
return new Date().toISOString();
|
|
61
11
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const text = raw.slice(/^(?:\t| )*/u.exec(raw)[0].length);
|
|
78
|
-
const nodeLine = text.startsWith("+ ") || text.startsWith("@task/");
|
|
79
|
-
if (nodeLine) {
|
|
80
|
-
if (leading.length % 2 !== 0)
|
|
81
|
-
throw new TypeError(`compose line ${line + 1} has invalid indentation`);
|
|
82
|
-
const depth = leading.length / 2;
|
|
83
|
-
if (depth > 0 && !mutable.some((entry) => entry.node.depth === depth - 1))
|
|
84
|
-
throw new TypeError(`compose line ${line + 1} skips a parent depth`);
|
|
85
|
-
mutable.push({ node: parseNode(text, depth, mutable.length), body: [] });
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const current = mutable.at(-1);
|
|
89
|
-
if (current === undefined) {
|
|
90
|
-
if (text.trim() === "")
|
|
91
|
-
continue;
|
|
92
|
-
throw new TypeError(`compose line ${line + 1} has body before a node`);
|
|
93
|
-
}
|
|
94
|
-
current.body.push(text.startsWith("\\") ? text.slice(1) : text);
|
|
95
|
-
}
|
|
96
|
-
const nodes = mutable.map(({ node, body }) => ({
|
|
97
|
-
...node,
|
|
98
|
-
...(body.length === 0 ? {} : { body: body.join("\n") }),
|
|
99
|
-
}));
|
|
100
|
-
return { ...(namespace === undefined ? {} : { namespace }), nodes };
|
|
12
|
+
function facts(plan) {
|
|
13
|
+
return { aliases: plan.aliases, admissionOrder: plan.admissionOrder };
|
|
14
|
+
}
|
|
15
|
+
function reference(id, remainingAliases) {
|
|
16
|
+
const alias = remainingAliases.get(id);
|
|
17
|
+
return alias === undefined ? `@${id}` : `^${alias}`;
|
|
18
|
+
}
|
|
19
|
+
function bodyToken(body) {
|
|
20
|
+
const lines = new Set(body.split(/\r\n|\n|\r/u));
|
|
21
|
+
for (let suffix = 0; Number.isSafeInteger(suffix); suffix += 1) {
|
|
22
|
+
const token = suffix === 0 ? "END_BODY" : `END_BODY_${suffix}`;
|
|
23
|
+
if (token.length > 32)
|
|
24
|
+
break;
|
|
25
|
+
if (!lines.has(token))
|
|
26
|
+
return token;
|
|
101
27
|
}
|
|
102
|
-
|
|
103
|
-
|
|
28
|
+
throw new Error("compose recovery body token space exhausted");
|
|
29
|
+
}
|
|
30
|
+
function taskDraft(task, remainingAliases) {
|
|
31
|
+
const document = task.after;
|
|
32
|
+
const lines = [task.kind === "new" ? `+ ${document.title}` : `@${document.id}`];
|
|
33
|
+
if (task.kind === "new" && task.alias !== undefined)
|
|
34
|
+
lines.push(`as = ${task.alias}`);
|
|
35
|
+
lines.push(`pri = ${document.priority}`, `needs = ${document.needs.map((id) => reference(id, remainingAliases)).join(", ")}`, `parent = ${document.parent === null ? "" : reference(document.parent, remainingAliases)}`, `supersedes = ${document.supersedes.map((id) => reference(id, remainingAliases)).join(", ")}`, `relates = ${document.relates.map((id) => reference(id, remainingAliases)).join(", ")}`);
|
|
36
|
+
if (document.body === "")
|
|
37
|
+
lines.push("body =");
|
|
38
|
+
else {
|
|
39
|
+
const token = bodyToken(document.body);
|
|
40
|
+
lines.push(`body <<${token}`, document.body, token);
|
|
104
41
|
}
|
|
42
|
+
return lines;
|
|
105
43
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
throw new TypeError("relation values must be @TaskId");
|
|
112
|
-
const id = raw.slice(1);
|
|
113
|
-
parseTaskId(id);
|
|
114
|
-
return id;
|
|
115
|
-
});
|
|
116
|
-
if (new Set(parsed).size !== parsed.length)
|
|
117
|
-
throw new TypeError("relation values must not contain duplicates");
|
|
118
|
-
return parsed;
|
|
119
|
-
}
|
|
120
|
-
function scalarId(value) {
|
|
121
|
-
const values = ids(value);
|
|
122
|
-
if (values.length > 1)
|
|
123
|
-
throw new TypeError("parent accepts at most one TaskId");
|
|
124
|
-
return values[0] ?? null;
|
|
125
|
-
}
|
|
126
|
-
function changed(current, value, append) {
|
|
127
|
-
return append ? [...current, ...value.filter((id) => !current.includes(id))] : value;
|
|
128
|
-
}
|
|
129
|
-
function applyAssignments(document, assignments, body) {
|
|
130
|
-
let next = document;
|
|
131
|
-
const seen = new Set();
|
|
132
|
-
for (const item of assignments) {
|
|
133
|
-
const mode = `${item.field}:${item.append}`;
|
|
134
|
-
if (seen.has(mode) || [...seen].some((value) => value.startsWith(`${item.field}:`) && value !== mode))
|
|
135
|
-
throw new TypeError(`duplicate compose assignment: ${item.field}`);
|
|
136
|
-
seen.add(mode);
|
|
137
|
-
if (item.field === "pri") {
|
|
138
|
-
const priority = Number(item.value);
|
|
139
|
-
if (!Number.isInteger(priority) || priority < 0 || priority > 3)
|
|
140
|
-
throw new TypeError("pri must be 0..3");
|
|
141
|
-
next = { ...next, priority: priority };
|
|
142
|
-
}
|
|
143
|
-
else if (item.field === "parent")
|
|
144
|
-
next = { ...next, parent: scalarId(item.value) };
|
|
145
|
-
else if (item.field === "body")
|
|
146
|
-
next = { ...next, body: "" };
|
|
147
|
-
else
|
|
148
|
-
next = { ...next, [item.field]: changed(next[item.field], ids(item.value), item.append) };
|
|
44
|
+
function recoveryDraft(namespace, remaining) {
|
|
45
|
+
const aliases = new Map();
|
|
46
|
+
for (const task of remaining) {
|
|
47
|
+
if (task.kind === "new" && task.alias !== undefined)
|
|
48
|
+
aliases.set(task.after.id, task.alias);
|
|
149
49
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
function createdTask(id, title, at, actor) {
|
|
156
|
-
return {
|
|
157
|
-
id,
|
|
158
|
-
title,
|
|
159
|
-
state: "open",
|
|
160
|
-
priority: 2,
|
|
161
|
-
needs: [],
|
|
162
|
-
parent: null,
|
|
163
|
-
supersedes: [],
|
|
164
|
-
relates: [],
|
|
165
|
-
note: "",
|
|
166
|
-
...(actor === undefined ? {} : { createdBy: actor }),
|
|
167
|
-
createdAt: at,
|
|
168
|
-
updatedAt: at,
|
|
169
|
-
body: "",
|
|
170
|
-
};
|
|
50
|
+
const lines = [`ns=${namespace.length === 0 ? "/" : namespace.join("/")}`];
|
|
51
|
+
for (const task of remaining)
|
|
52
|
+
lines.push("", ...taskDraft(task, aliases));
|
|
53
|
+
return `${lines.join("\n")}\n`;
|
|
171
54
|
}
|
|
172
|
-
function plan
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (node.depth > 0) {
|
|
201
|
-
const parent = byDepth[node.depth - 1];
|
|
202
|
-
if (parent === undefined)
|
|
203
|
-
throw new TypeError("compose parent is unavailable");
|
|
204
|
-
if (node.assignments.some((item) => item.field === "parent"))
|
|
205
|
-
throw new TypeError("indented node cannot also assign parent");
|
|
206
|
-
current = { ...current, parent };
|
|
207
|
-
}
|
|
208
|
-
current = applyAssignments(current, node.assignments, node.body);
|
|
209
|
-
if (before !== null &&
|
|
210
|
-
!Buffer.from(serializeTaskDocument(current)).equals(Buffer.from(serializeTaskDocument(before)))) {
|
|
211
|
-
current = {
|
|
212
|
-
...current,
|
|
213
|
-
updatedAt: advanceTaskTimestamp(before.updatedAt, at),
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
byDepth[node.depth] = current.id;
|
|
217
|
-
byDepth.length = node.depth + 1;
|
|
218
|
-
all.set(current.id, current);
|
|
219
|
-
planned.push({ node, before, after: current });
|
|
220
|
-
}
|
|
221
|
-
for (const item of planned) {
|
|
222
|
-
const problem = relationProblem({ tasks: all }, item.before, item.after);
|
|
223
|
-
if (problem !== null)
|
|
224
|
-
return failure(problem);
|
|
55
|
+
function plannedResult(plan) {
|
|
56
|
+
return { kind: "planned", ...facts(plan), bodies: plan.bodies };
|
|
57
|
+
}
|
|
58
|
+
function planAgainst(markdown, board, namespace, at, actor) {
|
|
59
|
+
return planTaskComposition({ markdown, board, namespace, at, ...(actor === undefined ? {} : { actor }) });
|
|
60
|
+
}
|
|
61
|
+
async function admitPlan(world, snapshot, plan, signal) {
|
|
62
|
+
const changes = [];
|
|
63
|
+
for (let index = 0; index < plan.tasks.length; index += 1) {
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
const item = plan.tasks[index];
|
|
66
|
+
const beforeBytes = item.before === null ? null : (snapshot.bytes.get(item.after.id) ?? null);
|
|
67
|
+
const afterBytes = serializeTaskDocument(item.after);
|
|
68
|
+
const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8");
|
|
69
|
+
const after = Buffer.from(afterBytes).toString("utf8");
|
|
70
|
+
const replaced = await replaceAuthority({
|
|
71
|
+
path: authorityPath(world, item.after.id),
|
|
72
|
+
expected: beforeBytes,
|
|
73
|
+
next: afterBytes,
|
|
74
|
+
});
|
|
75
|
+
if (replaced !== "replaced") {
|
|
76
|
+
return {
|
|
77
|
+
kind: "incomplete",
|
|
78
|
+
...facts(plan),
|
|
79
|
+
documentChanges: changes,
|
|
80
|
+
stopped: { kind: "retry", reason: "concurrent-modification" },
|
|
81
|
+
draft: recoveryDraft(plan.namespace, plan.tasks.slice(index)),
|
|
82
|
+
};
|
|
225
83
|
}
|
|
226
|
-
|
|
84
|
+
const label = `${item.after.id}.md`;
|
|
85
|
+
changes.push({
|
|
86
|
+
taskId: item.after.id,
|
|
87
|
+
kind: item.before === null ? "created" : "updated",
|
|
88
|
+
documentDiff: documentDiff(label, label, before, after),
|
|
89
|
+
});
|
|
227
90
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
91
|
+
return { kind: "accepted", ...facts(plan), documentChanges: changes };
|
|
92
|
+
}
|
|
93
|
+
async function composeUnderLocks(input) {
|
|
94
|
+
const snapshot = await readBoard(input.world);
|
|
95
|
+
const planned = planAgainst(input.markdown, snapshot.board, input.namespace, input.at, input.actor);
|
|
96
|
+
if (planned.kind === "refused")
|
|
97
|
+
return refusal(planned.diagnostics);
|
|
98
|
+
return await withTaskLocks({
|
|
99
|
+
world: input.world,
|
|
100
|
+
allocation: false,
|
|
101
|
+
ids: planned.plan.admissionOrder,
|
|
102
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
103
|
+
}, async () => {
|
|
104
|
+
const fresh = await readBoard(input.world);
|
|
105
|
+
const replanned = planAgainst(input.markdown, fresh.board, input.namespace, input.at, input.actor);
|
|
106
|
+
if (replanned.kind === "refused")
|
|
107
|
+
return refusal(replanned.diagnostics);
|
|
108
|
+
return await admitPlan(input.world, fresh, replanned.plan, input.signal);
|
|
109
|
+
});
|
|
237
110
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
for (const item of remaining) {
|
|
241
|
-
const task = item.after;
|
|
242
|
-
const assignments = [
|
|
243
|
-
`pri=${task.priority}`,
|
|
244
|
-
`needs=${task.needs.map(ref).join(",")}`,
|
|
245
|
-
`parent=${task.parent === null ? "" : ref(task.parent)}`,
|
|
246
|
-
`supersedes=${task.supersedes.map(ref).join(",")}`,
|
|
247
|
-
`relates=${task.relates.map(ref).join(",")}`,
|
|
248
|
-
];
|
|
249
|
-
lines.push(`${item.node.kind === "new" ? `+ ${task.title}` : ref(task.id)} ${assignments.join(" ")}${task.body === "" ? " body=" : ""}`);
|
|
250
|
-
if (task.body !== "")
|
|
251
|
-
lines.push(...bodyLines(task.body));
|
|
252
|
-
}
|
|
253
|
-
return `${lines.join("\n")}\n`;
|
|
111
|
+
function composeInput(input) {
|
|
112
|
+
return input;
|
|
254
113
|
}
|
|
255
|
-
export async function composeTasks(
|
|
256
|
-
const
|
|
257
|
-
if ("kind" in sketch)
|
|
258
|
-
return { kind: "refused", refusal: sketch };
|
|
114
|
+
export async function composeTasks(input) {
|
|
115
|
+
const { world, markdown, signal, actor, defaultNamespace = [], planOnly = false } = input;
|
|
259
116
|
const at = currentTimestamp();
|
|
260
|
-
const namespace = sketch.namespace ?? defaultNamespace ?? [];
|
|
261
117
|
const initial = await readBoard(world);
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
signal?.throwIfAborted();
|
|
275
|
-
const item = queue[index], path = authorityPath(world, item.after.id);
|
|
276
|
-
const beforeBytes = item.before === null ? null : (fresh.bytes.get(item.after.id) ?? null);
|
|
277
|
-
const afterBytes = serializeTaskDocument(item.after);
|
|
278
|
-
const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
|
|
279
|
-
if (before === after)
|
|
280
|
-
continue;
|
|
281
|
-
if ((await replaceAuthority({ path, expected: beforeBytes, next: afterBytes })) !== "replaced")
|
|
282
|
-
return {
|
|
283
|
-
kind: "incomplete",
|
|
284
|
-
documentChanges: changes,
|
|
285
|
-
stopped: { kind: "retry", reason: "concurrent-modification" },
|
|
286
|
-
draft: draft(namespace, queue.slice(index)),
|
|
287
|
-
};
|
|
288
|
-
const label = `${item.after.id}.md`;
|
|
289
|
-
changes.push({
|
|
290
|
-
taskId: item.after.id,
|
|
291
|
-
kind: item.before === null ? "created" : "updated",
|
|
292
|
-
documentDiff: documentDiff(label, label, before, after),
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
return { kind: "accepted", documentChanges: changes };
|
|
118
|
+
const initialPlan = planAgainst(markdown, initial.board, defaultNamespace, at, actor);
|
|
119
|
+
if (initialPlan.kind === "refused")
|
|
120
|
+
return refusal(initialPlan.diagnostics);
|
|
121
|
+
if (planOnly)
|
|
122
|
+
return plannedResult(initialPlan.plan);
|
|
123
|
+
const request = composeInput({
|
|
124
|
+
world,
|
|
125
|
+
markdown,
|
|
126
|
+
namespace: defaultNamespace,
|
|
127
|
+
at,
|
|
128
|
+
...(actor === undefined ? {} : { actor }),
|
|
129
|
+
...(signal === undefined ? {} : { signal }),
|
|
296
130
|
});
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
:
|
|
131
|
+
const allocation = initialPlan.plan.tasks.some((task) => task.kind === "new");
|
|
132
|
+
const admitted = allocation
|
|
133
|
+
? await withTaskLocks({ world, allocation: true, ids: [], ...(signal === undefined ? {} : { signal }) }, async () => await composeUnderLocks(request))
|
|
134
|
+
: await composeUnderLocks(request);
|
|
135
|
+
if (admitted !== "busy")
|
|
136
|
+
return admitted;
|
|
137
|
+
return {
|
|
138
|
+
kind: "incomplete",
|
|
139
|
+
...facts(initialPlan.plan),
|
|
140
|
+
documentChanges: [],
|
|
141
|
+
stopped: { kind: "retry", reason: "busy" },
|
|
142
|
+
draft: recoveryDraft(initialPlan.plan.namespace, initialPlan.plan.tasks),
|
|
143
|
+
};
|
|
305
144
|
}
|
|
@@ -5,11 +5,17 @@ export type NamespaceContextRead = readonly string[] | "absent" | Readonly<{
|
|
|
5
5
|
export type NamespaceContextInput = Readonly<{
|
|
6
6
|
directory: string;
|
|
7
7
|
boundary: string;
|
|
8
|
+
managed?: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export type NamespaceContextSource = "default-root" | "contract-installed" | "local-override";
|
|
11
|
+
export type ResolvedNamespaceContext = Readonly<{
|
|
12
|
+
namespace: readonly string[];
|
|
13
|
+
source: NamespaceContextSource;
|
|
8
14
|
}>;
|
|
9
15
|
export declare function readNamespaceContext(coordinates: NamespaceContextInput): Promise<NamespaceContextRead>;
|
|
10
16
|
export declare function installNamespaceContext(root: string, segments: readonly string[]): Promise<void>;
|
|
11
17
|
export declare function repairNamespaceContext(root: string, segments: readonly string[]): Promise<"kept" | "installed">;
|
|
12
|
-
export declare function resolveTaskNamespaceContext(input: NamespaceContextInput): Promise<
|
|
18
|
+
export declare function resolveTaskNamespaceContext(input: NamespaceContextInput): Promise<ResolvedNamespaceContext | Readonly<{
|
|
13
19
|
kind: "invalid-namespace-context";
|
|
14
20
|
path: string;
|
|
15
21
|
}>>;
|
|
@@ -40,6 +40,12 @@ async function readAt(root) {
|
|
|
40
40
|
const segments = content === "" ? [] : content.split("/");
|
|
41
41
|
return validNamespaceSegments(segments) ? segments : { kind: "malformed", path: currentPath(root) };
|
|
42
42
|
}
|
|
43
|
+
async function readAtWithPath(root) {
|
|
44
|
+
const selected = await readAt(root);
|
|
45
|
+
if (selected === "absent" || (typeof selected === "object" && "kind" in selected))
|
|
46
|
+
return selected;
|
|
47
|
+
return { value: selected, path: currentPath(root) };
|
|
48
|
+
}
|
|
43
49
|
export async function readNamespaceContext(coordinates) {
|
|
44
50
|
let current = coordinates.directory;
|
|
45
51
|
for (;;) {
|
|
@@ -75,12 +81,25 @@ export async function repairNamespaceContext(root, segments) {
|
|
|
75
81
|
return "installed";
|
|
76
82
|
}
|
|
77
83
|
export async function resolveTaskNamespaceContext(input) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
let current = input.directory;
|
|
85
|
+
for (;;) {
|
|
86
|
+
const selected = await readAtWithPath(current);
|
|
87
|
+
if (selected !== "absent") {
|
|
88
|
+
if (typeof selected === "object" && "kind" in selected)
|
|
89
|
+
return { kind: "invalid-namespace-context", path: selected.path };
|
|
90
|
+
return {
|
|
91
|
+
namespace: "value" in selected ? selected.value : selected,
|
|
92
|
+
source: current === input.boundary && input.managed === true ? "contract-installed" : "local-override",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (current === input.boundary)
|
|
96
|
+
return { namespace: [], source: "default-root" };
|
|
97
|
+
const resolvedParent = dirname(resolve(current));
|
|
98
|
+
if (resolvedParent === current || !resolvedParent.startsWith(`${input.boundary}/`)) {
|
|
99
|
+
return { namespace: [], source: "default-root" };
|
|
100
|
+
}
|
|
101
|
+
current = resolvedParent;
|
|
102
|
+
}
|
|
84
103
|
}
|
|
85
104
|
export async function writeTaskNamespaceContext(directory, value) {
|
|
86
105
|
await installNamespaceContext(directory, value);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { WorldRoot } from "../world.js";
|
|
2
2
|
export type { WorldRoot } from "../world.js";
|
|
3
3
|
import { type BlockedTaskRow, type TaskDetailFacts, type TaskDoctorIssue, type TaskRef, type TaskRow, type TaskTreeNode } from "./board.js";
|
|
4
|
-
import { type TaskCompositionResult } from "./compose.js";
|
|
4
|
+
import { type TaskCompositionAlias, type TaskCompositionBodyPreview, type TaskCompositionResult } from "./compose.js";
|
|
5
5
|
import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
|
|
6
6
|
import type { TaskId } from "./identity.js";
|
|
7
|
-
import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
|
|
7
|
+
import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskCompositionDiagnostic, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
|
|
8
8
|
import { TASK_RELATION_PREDICATE_FIELDS, type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort, type TaskRelationPredicateField } from "./query.js";
|
|
9
9
|
export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
|
|
10
10
|
task: TaskView;
|
|
@@ -12,12 +12,17 @@ export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
|
|
|
12
12
|
export type TaskList = TaskOutcome<TaskPage<TaskRow>>;
|
|
13
13
|
export type BlockedTaskList = TaskOutcome<TaskPage<BlockedTaskRow>>;
|
|
14
14
|
export type TaskQueryResult = TaskOutcome<TaskPage<TaskQueryRow>>;
|
|
15
|
-
export type
|
|
15
|
+
export type TaskContextSource = "default-root" | "contract-installed" | "local-override";
|
|
16
|
+
export type ResolvedNamespaceContext = Readonly<{
|
|
17
|
+
namespace: readonly string[];
|
|
18
|
+
source: TaskContextSource;
|
|
19
|
+
}>;
|
|
20
|
+
export type TaskContextResult = TaskOutcome<ResolvedNamespaceContext>;
|
|
16
21
|
export type TaskDoctorReport = Readonly<{
|
|
17
22
|
issues: readonly TaskDoctorIssue[];
|
|
18
23
|
}>;
|
|
19
24
|
export type TaskDecompositionTree = TaskOutcome<TaskTreeNode>;
|
|
20
|
-
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
|
|
25
|
+
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionDiagnostic, TaskCompositionAlias, TaskCompositionBodyPreview, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
|
|
21
26
|
export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
|
|
22
27
|
declare class TaskHandle {
|
|
23
28
|
readonly id: TaskId;
|
|
@@ -95,6 +100,7 @@ declare class TasksHandle {
|
|
|
95
100
|
namespace?: readonly string[];
|
|
96
101
|
actor?: string;
|
|
97
102
|
signal?: AbortSignal;
|
|
103
|
+
plan?: boolean;
|
|
98
104
|
}>): Promise<TaskCompositionResult>;
|
|
99
105
|
}
|
|
100
106
|
export type Tasks = TasksHandle;
|
package/build/src/task/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildTree, createTaskRelations, diagnoseBoard, projectDetailFacts, } from "./board.js";
|
|
2
|
-
import { composeTasks } from "./compose.js";
|
|
2
|
+
import { composeTasks, } from "./compose.js";
|
|
3
3
|
import { TaskAuthorityCorruptionError } from "./document.js";
|
|
4
4
|
import { addTask, addTaskDocument, batchTasks, blockedTasks, lifecycleTask, listTasks, queryTasks, readyTasks, taskView, updateTask, } from "./operations.js";
|
|
5
5
|
import { readBoard } from "./store.js";
|
|
@@ -149,12 +149,23 @@ class TasksHandle {
|
|
|
149
149
|
}
|
|
150
150
|
compose(input) {
|
|
151
151
|
const v = record(input, "compose input");
|
|
152
|
-
closed(v, ["markdown", "namespace", "actor", "signal"], "compose input");
|
|
152
|
+
closed(v, ["markdown", "namespace", "actor", "signal", "plan"], "compose input");
|
|
153
153
|
const markdown = text(v.markdown, "markdown");
|
|
154
154
|
if (markdown === undefined)
|
|
155
155
|
throw new TypeError("markdown is required");
|
|
156
|
+
if (v.plan !== undefined && typeof v.plan !== "boolean")
|
|
157
|
+
throw new TypeError("plan must be a boolean");
|
|
156
158
|
const selected = namespace(v.namespace);
|
|
157
|
-
|
|
159
|
+
const selectedSignal = signal(v.signal);
|
|
160
|
+
const selectedActor = actor(v.actor);
|
|
161
|
+
return composeTasks({
|
|
162
|
+
world: this.world,
|
|
163
|
+
markdown,
|
|
164
|
+
...(selectedSignal === undefined ? {} : { signal: selectedSignal }),
|
|
165
|
+
...(selectedActor === undefined ? {} : { actor: selectedActor }),
|
|
166
|
+
...(selected === undefined ? {} : { defaultNamespace: selected }),
|
|
167
|
+
planOnly: v.plan === true,
|
|
168
|
+
});
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
171
|
export const Tasks = Object.freeze({
|
|
@@ -9,6 +9,11 @@ export type TaskView = Readonly<TaskDocument & {
|
|
|
9
9
|
export declare function nukeTask(world: WorldRoot, options?: Readonly<{
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
}>): Promise<void>;
|
|
12
|
+
export type TaskCompositionDiagnostic = Readonly<{
|
|
13
|
+
line: number;
|
|
14
|
+
reason: string;
|
|
15
|
+
token: string;
|
|
16
|
+
}>;
|
|
12
17
|
export type TaskRefusal = Readonly<{
|
|
13
18
|
kind: "task-missing";
|
|
14
19
|
taskId: TaskId;
|
|
@@ -30,7 +35,7 @@ export type TaskRefusal = Readonly<{
|
|
|
30
35
|
declaringTask: TaskId;
|
|
31
36
|
}> | Readonly<{
|
|
32
37
|
kind: "invalid-composition";
|
|
33
|
-
|
|
38
|
+
diagnostics: readonly TaskCompositionDiagnostic[];
|
|
34
39
|
}>;
|
|
35
40
|
export type TaskRetry = "busy" | "concurrent-modification";
|
|
36
41
|
export type TaskOutcome<A> = Readonly<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/keiyaku",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.7",
|
|
4
4
|
"files": [
|
|
5
5
|
"build"
|
|
6
6
|
],
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@agentclientprotocol/sdk": "^1.3.0",
|
|
66
66
|
"@anthropic-ai/claude-agent-sdk": "^0.3.226",
|
|
67
|
-
"@astrosheep/square": "0.3.
|
|
67
|
+
"@astrosheep/square": "0.3.30",
|
|
68
68
|
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
69
69
|
"@opencode-ai/sdk": "1.18.3",
|
|
70
70
|
"cross-spawn": "^7.0.6",
|