@rse/ase 2.0.1 → 2.0.3
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/dst/ase-agent-definition-core.js +111 -87
- package/dst/ase-agent-fleet.js +1 -1
- package/dst/ase-agent-runtime-claim.js +5 -3
- package/dst/ase-agent-runtime-engine.js +1 -1
- package/dst/ase-agent-runtime-phase.js +1 -1
- package/dst/ase-agent-runtime-request.js +1 -1
- package/dst/ase-agent-runtime.js +3 -5
- package/dst/ase-artifact.js +3 -3
- package/dst/ase-config-core.js +2 -16
- package/dst/ase-config-schema.js +1 -3
- package/dst/ase-config.js +4 -1
- package/dst/ase-hook.js +14 -19
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +10 -10
- package/dst/ase-mcp.js +4 -4
- package/dst/ase-service-getopt.js +2 -2
- package/dst/ase-service-otel.js +4 -4
- package/dst/ase-service-skills.js +5 -15
- package/dst/ase-service.js +87 -210
- package/dst/ase-setup-common.js +1 -2
- package/dst/ase-setup-settings.js +6 -9
- package/dst/ase-setup.js +2 -3
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-core.js +1 -1
- package/dst/ase-task-board-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +51 -45
- package/dst/ase-task-board-tui-model.js +195 -181
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +88 -79
- package/dst/ase-task-board-web-client.css +1 -1
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +4 -27
- package/dst/ase-task-board.js +1 -1
- package/dst/ase-task-format.js +1 -1
- package/dst/ase-task-store-client.js +17 -5
- package/dst/ase-task-store-core.js +5 -7
- package/dst/ase-task-store-issues.js +91 -0
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +11 -97
- package/dst/ase-task-store-plugin-github.js +5 -77
- package/dst/ase-task-store-plugin-gitlab.js +4 -83
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +16 -42
- package/dst/ase.js +5 -5
- package/dst/man/ase-agent-adhoc.1 +1 -1
- package/dst/man/ase-agent-condenser.1 +1 -1
- package/dst/man/ase-agent-editor.1 +1 -1
- package/dst/man/ase-agent-estimator.1 +1 -1
- package/dst/man/ase-agent-fleet.1 +1 -1
- package/dst/man/ase-agent-implementer.1 +1 -1
- package/dst/man/ase-agent-integrator.1 +1 -1
- package/dst/man/ase-agent-qualifier.1 +1 -1
- package/dst/man/ase-agent-rebooter.1 +1 -1
- package/dst/man/ase-agent-relay.1 +1 -1
- package/dst/man/ase-agent-researcher.1 +1 -1
- package/dst/man/ase-agent.1 +1 -1
- package/dst/man/ase-artifact-list.1 +1 -1
- package/dst/man/ase-artifact-name.1 +1 -1
- package/dst/man/ase-artifact.1 +1 -1
- package/dst/man/ase-config-delete.1 +1 -1
- package/dst/man/ase-config-edit.1 +1 -1
- package/dst/man/ase-config-get.1 +1 -1
- package/dst/man/ase-config-init.1 +1 -1
- package/dst/man/ase-config-list.1 +1 -1
- package/dst/man/ase-config-set.1 +1 -1
- package/dst/man/ase-config.1 +1 -1
- package/dst/man/ase-hook-permission-request.1 +1 -1
- package/dst/man/ase-hook-pre-tool-use.1 +1 -1
- package/dst/man/ase-hook-session-end.1 +1 -1
- package/dst/man/ase-hook-session-persona.1 +1 -1
- package/dst/man/ase-hook-session-start.1 +1 -1
- package/dst/man/ase-hook-stop.1 +1 -1
- package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
- package/dst/man/ase-hook.1 +1 -1
- package/dst/man/ase-man.1 +1 -1
- package/dst/man/ase-mcp.1 +1 -1
- package/dst/man/ase-service-log.1 +1 -1
- package/dst/man/ase-service-send.1 +1 -1
- package/dst/man/ase-service-start.1 +1 -1
- package/dst/man/ase-service-status.1 +1 -1
- package/dst/man/ase-service-stop.1 +1 -1
- package/dst/man/ase-service.1 +1 -1
- package/dst/man/ase-setup-disable.1 +1 -1
- package/dst/man/ase-setup-enable.1 +1 -1
- package/dst/man/ase-setup-install.1 +1 -1
- package/dst/man/ase-setup-mcp.1 +1 -1
- package/dst/man/ase-setup-status.1 +1 -1
- package/dst/man/ase-setup-statusline.1 +1 -1
- package/dst/man/ase-setup-telemetry.1 +1 -1
- package/dst/man/ase-setup-uninstall.1 +1 -1
- package/dst/man/ase-setup-update.1 +1 -1
- package/dst/man/ase-setup.1 +1 -1
- package/dst/man/ase-spec-export.1 +1 -1
- package/dst/man/ase-spec-lint.1 +1 -1
- package/dst/man/ase-spec-preview.1 +1 -1
- package/dst/man/ase-spec.1 +1 -1
- package/dst/man/ase-statusline.1 +1 -1
- package/dst/man/ase-task-assign.1 +1 -1
- package/dst/man/ase-task-board.1 +3 -3
- package/dst/man/ase-task-delete.1 +1 -1
- package/dst/man/ase-task-edit.1 +1 -1
- package/dst/man/ase-task-idscheme.1 +1 -1
- package/dst/man/ase-task-lifecycle.1 +1 -1
- package/dst/man/ase-task-list.1 +1 -1
- package/dst/man/ase-task-load.1 +1 -1
- package/dst/man/ase-task-newid.1 +1 -1
- package/dst/man/ase-task-purge.1 +1 -1
- package/dst/man/ase-task-rename.1 +1 -1
- package/dst/man/ase-task-request.1 +1 -1
- package/dst/man/ase-task-save.1 +1 -1
- package/dst/man/ase-task-status.1 +1 -1
- package/dst/man/ase-task-store.1 +1 -1
- package/dst/man/ase-task-view.1 +1 -1
- package/dst/man/ase-task.1 +1 -1
- package/dst/man/ase-util-compat.1 +1 -1
- package/dst/man/ase-util-diagram.1 +1 -1
- package/dst/man/ase-util-meta.1 +1 -1
- package/dst/man/ase-util-metric.1 +1 -1
- package/dst/man/ase-util-mint.1 +1 -1
- package/dst/man/ase-util-worktree.1 +1 -1
- package/dst/man/ase-util.1 +1 -1
- package/dst/man/ase.1 +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-common-resolve.md +2 -2
- package/plugin/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +16 -12
- package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +26 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-edit/SKILL.md +5 -4
- package/plugin/skills/ase-code-edit/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +52 -27
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +60 -30
- package/plugin/skills/ase-help-skill/help.md +3 -4
- package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
- package/plugin/skills/ase-meta-quorum/help.md +5 -3
- package/plugin/skills/ase-meta-workflow/sample.md +5 -3
- package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
- package/plugin/skills/ase-repo-changelog/help.md +2 -2
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
- package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
- package/plugin/skills/ase-spec-edit/help.md +2 -1
- package/plugin/skills/ase-sync-reconcile/help.md +4 -4
- package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
- package/plugin/skills/ase-task-grill/help.md +1 -1
- package/plugin/skills/ase-task-implement/SKILL.md +58 -1
- package/plugin/skills/ase-task-implement/help.md +16 -0
- package/plugin/skills/ase-task-list/help.md +2 -2
|
@@ -161,6 +161,14 @@ const boxAt = (map, mx, my) => {
|
|
|
161
161
|
}
|
|
162
162
|
return undefined;
|
|
163
163
|
};
|
|
164
|
+
/* the box of every node of a laid out graph, for the spatial navigation and the scrolling */
|
|
165
|
+
const placesOf = (graph) => {
|
|
166
|
+
const map = new Map();
|
|
167
|
+
if (graph !== undefined)
|
|
168
|
+
for (const n of graph.nodes.values())
|
|
169
|
+
map.set(n.id, { r: n.y + Math.floor(n.h / 2), c: n.x + Math.floor(n.w / 2), top: n.y, bottom: n.y + n.h - 1, bl: n.x, br: n.x + n.w - 1 });
|
|
170
|
+
return map;
|
|
171
|
+
};
|
|
164
172
|
/* the state of the agents view: the agents of the master list, the selected one (followed by its id,
|
|
165
173
|
else, until explicitly selected again, the one at the index of the previously selected one),
|
|
166
174
|
the lines of its operation pane, and the lines of its recent processing log in the detail view (within its frame), scrolled
|
|
@@ -222,6 +230,183 @@ const useAgentsView = (agents, columns, boardH) => {
|
|
|
222
230
|
};
|
|
223
231
|
return { agentList, agentIdx, agent, agentOp, logLines, logH, logFirst, logMax, selectAgent, scrollLog, focusAgent };
|
|
224
232
|
};
|
|
233
|
+
/* the wall-clock and the heartbeats of the agents of the agents view and the agent graph */
|
|
234
|
+
const useBeats = (agents, view) => {
|
|
235
|
+
/* tick the wall-clock every second while the agents view is shown, for the live
|
|
236
|
+
durations of its footer (the time in the current state and the age of the heartbeat) */
|
|
237
|
+
const [now, setNow] = React.useState(Date.now());
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
if (view !== "agents")
|
|
240
|
+
return;
|
|
241
|
+
setNow(Date.now());
|
|
242
|
+
const timer = setInterval(() => {
|
|
243
|
+
setNow(Date.now());
|
|
244
|
+
}, 1000);
|
|
245
|
+
return () => {
|
|
246
|
+
clearInterval(timer);
|
|
247
|
+
};
|
|
248
|
+
}, [view]);
|
|
249
|
+
/* the local time at which the latest heartbeat of each agent was received (measured
|
|
250
|
+
locally, as the clock of the agent relay may be skewed), for the heart of its card
|
|
251
|
+
(filled for BEAT_SHOWN_MS after a heartbeat, else empty), where the heartbeats are
|
|
252
|
+
recognized by their changed time stamps and a filled heart is re-rendered once it is
|
|
253
|
+
due to be emptied (as the wall-clock tick alone would empty it too early or too late) */
|
|
254
|
+
const beats = React.useRef(new Map());
|
|
255
|
+
const beatsSeen = React.useMemo(() => {
|
|
256
|
+
const at = Date.now();
|
|
257
|
+
const list = agents?.agents ?? [];
|
|
258
|
+
const next = new Map();
|
|
259
|
+
for (const a of list) {
|
|
260
|
+
const prev = beats.current.get(a.id);
|
|
261
|
+
next.set(a.id, prev !== undefined && prev.beat === a.beat ? prev : { beat: a.beat, seen: at });
|
|
262
|
+
}
|
|
263
|
+
beats.current = next;
|
|
264
|
+
return next;
|
|
265
|
+
}, [agents]);
|
|
266
|
+
React.useEffect(() => {
|
|
267
|
+
if (view !== "agents" && view !== "flow")
|
|
268
|
+
return;
|
|
269
|
+
const latest = Math.max(0, ...[...beatsSeen.values()].map((b) => b.seen));
|
|
270
|
+
const due = latest + BEAT_SHOWN_MS - Date.now();
|
|
271
|
+
if (latest === 0 || due <= 0)
|
|
272
|
+
return;
|
|
273
|
+
const timer = setTimeout(() => {
|
|
274
|
+
setNow(Date.now());
|
|
275
|
+
}, due);
|
|
276
|
+
return () => {
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
};
|
|
279
|
+
}, [view, beatsSeen]);
|
|
280
|
+
const beatOf = (id) => now - (beatsSeen.get(id)?.seen ?? 0) < BEAT_SHOWN_MS;
|
|
281
|
+
return { now, beatOf };
|
|
282
|
+
};
|
|
283
|
+
/* the actions of the prompt popup of the terminal board: opening it for an edit request
|
|
284
|
+
or for a request by prompt of any name, cancelling it, and submitting it, with the
|
|
285
|
+
drafts of the cancelled (or failed to be sent) prompts and requests, per task */
|
|
286
|
+
const usePromptActions = (ctx) => {
|
|
287
|
+
const { log, all, cycle, agents, prompt, setPrompt, setNotice, track, promptLoad } = ctx;
|
|
288
|
+
/* the drafts of the prompts of cancelled (or failed to be sent) edit requests, per task */
|
|
289
|
+
const promptDrafts = React.useRef(new Map());
|
|
290
|
+
/* the drafts of the cancelled (or failed to be sent) requests by prompt, per task */
|
|
291
|
+
const requestDrafts = React.useRef(new Map());
|
|
292
|
+
/* open the prompt popup of an edit request of a task (refused for a finished task, and
|
|
293
|
+
while an edit of it is in progress, as a new request would change the task under the
|
|
294
|
+
running edit), prefilled with its draft of a cancelled (or failed to be sent) prompt,
|
|
295
|
+
else with the prompt of its most recent failed edit request (for a retry) */
|
|
296
|
+
const startPrompt = (id) => {
|
|
297
|
+
const card = all.cards.get(id);
|
|
298
|
+
if (card === undefined)
|
|
299
|
+
return;
|
|
300
|
+
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
301
|
+
setNotice(`editing task "${id}" by prompt refused: task is finished`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (card.edit === "active") {
|
|
305
|
+
setNotice(`editing task "${id}" by prompt refused: edit in progress`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const draft = promptDrafts.current.get(id);
|
|
309
|
+
if (draft !== undefined) {
|
|
310
|
+
setPrompt({ id, ...draft, focus: "prompt", request: null });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const token = ++promptLoad.current;
|
|
314
|
+
track({ text: "loading edit prompt of task", id }, editPrompt(log, id)).then((text) => {
|
|
315
|
+
if (token === promptLoad.current)
|
|
316
|
+
setPrompt({ id, text, pos: [...text].length, focus: "prompt", request: null });
|
|
317
|
+
}).catch((err) => {
|
|
318
|
+
if (token === promptLoad.current)
|
|
319
|
+
setNotice(`editing task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
/* open the prompt popup of a request by prompt of any name of a task (see "ase task
|
|
323
|
+
request", refused for a finished task), with the request name suggested from the
|
|
324
|
+
request agents announced to the agent relay, prefilled with its draft of a cancelled
|
|
325
|
+
(or failed to be sent) request, where a missing request name is to be entered first */
|
|
326
|
+
const startRequest = (id) => {
|
|
327
|
+
const card = all.cards.get(id);
|
|
328
|
+
if (card === undefined)
|
|
329
|
+
return;
|
|
330
|
+
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
331
|
+
setNotice(`requesting task "${id}" by prompt refused: task is finished`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const choices = requestNames(agents);
|
|
335
|
+
const ignored = choices.filter((choice) => requestPrompt(agents, choice) === "ignored");
|
|
336
|
+
const draft = requestDrafts.current.get(id);
|
|
337
|
+
const name = draft?.name ?? choices[0] ?? "";
|
|
338
|
+
const focus = name === "" ? "name" : ignored.includes(name) ? "request" : "prompt";
|
|
339
|
+
setPrompt({ id, text: draft?.text ?? "", pos: draft?.pos ?? 0, focus, request: { name, choices, ignored, typed: "" } });
|
|
340
|
+
};
|
|
341
|
+
/* cancel the prompt popup, keeping its prompt as the draft of the task (even if
|
|
342
|
+
empty, so that a cleared prompt is not prefilled again on reopening) */
|
|
343
|
+
const cancelPrompt = () => {
|
|
344
|
+
if (prompt === null)
|
|
345
|
+
return;
|
|
346
|
+
if (prompt.request !== null) {
|
|
347
|
+
requestDrafts.current.set(prompt.id, { name: prompt.request.name, text: prompt.text, pos: prompt.pos });
|
|
348
|
+
setNotice(`requesting task "${prompt.id}" by prompt cancelled`);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
promptDrafts.current.set(prompt.id, { text: prompt.text, pos: prompt.pos });
|
|
352
|
+
setNotice(`editing task "${prompt.id}" by prompt cancelled`);
|
|
353
|
+
}
|
|
354
|
+
setPrompt(null);
|
|
355
|
+
};
|
|
356
|
+
/* request a task by the prompt of its popup under its request name (processed by a
|
|
357
|
+
request agent of a definition, resp. by "ase agent editor" for "edit"), dropping
|
|
358
|
+
the draft of the task once sent, resp. keeping the request as its draft on failure,
|
|
359
|
+
where the prompt may be empty only for the requests of agents not requiring their prompts,
|
|
360
|
+
and is dropped for the requests of agents ignoring their prompts */
|
|
361
|
+
const submitRequest = (id, text, pos, name) => {
|
|
362
|
+
let requests;
|
|
363
|
+
try {
|
|
364
|
+
requests = requestsOf(name);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
setNotice(`requesting task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const use = requestPrompt(agents, name);
|
|
371
|
+
if (text.trim() === "" && use === "required") {
|
|
372
|
+
setNotice(`requesting "${name}" of task "${id}" failed: empty prompt`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
setPrompt(null);
|
|
376
|
+
track({ text: `requesting "${name}" of task`, id }, requests.request(log, id, use === "ignored" ? "" : text)).then(() => {
|
|
377
|
+
requestDrafts.current.delete(id);
|
|
378
|
+
setNotice(`"${name}" of task "${id}" requested`);
|
|
379
|
+
}).catch((err) => {
|
|
380
|
+
requestDrafts.current.set(id, { name, text, pos });
|
|
381
|
+
setNotice(`requesting "${name}" of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
/* request the edit of a task by the prompt of its popup (processed by "ase agent editor"),
|
|
385
|
+
dropping the draft of the task once sent, resp. keeping the prompt as its draft on failure,
|
|
386
|
+
or request a task by the prompt of its popup under its request name */
|
|
387
|
+
const submitPrompt = () => {
|
|
388
|
+
if (prompt === null)
|
|
389
|
+
return;
|
|
390
|
+
const { id, text, pos } = prompt;
|
|
391
|
+
if (prompt.request !== null) {
|
|
392
|
+
submitRequest(id, text, pos, prompt.request.name);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (text.trim() === "") {
|
|
396
|
+
setNotice(`requesting edit of task "${id}" failed: empty edit prompt`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
setPrompt(null);
|
|
400
|
+
track({ text: "requesting edit of task", id }, requestEdit(log, id, text)).then(() => {
|
|
401
|
+
promptDrafts.current.delete(id);
|
|
402
|
+
setNotice(`edit of task "${id}" requested`);
|
|
403
|
+
}).catch((err) => {
|
|
404
|
+
promptDrafts.current.set(id, { text, pos });
|
|
405
|
+
setNotice(`requesting edit of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
return { startPrompt, startRequest, cancelPrompt, submitPrompt };
|
|
409
|
+
};
|
|
225
410
|
/* the state of the terminal board: all state, the derived values, and the actions */
|
|
226
411
|
export const useBoardState = (log, initial, relay) => {
|
|
227
412
|
const { exit, suspendTerminal } = useApp();
|
|
@@ -262,10 +447,6 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
262
447
|
const [grow, setGrow] = React.useState(false);
|
|
263
448
|
const editing = React.useRef(false);
|
|
264
449
|
const drafts = React.useRef(new Map());
|
|
265
|
-
/* the drafts of the prompts of cancelled (or failed to be sent) edit requests, per task */
|
|
266
|
-
const promptDrafts = React.useRef(new Map());
|
|
267
|
-
/* the drafts of the cancelled (or failed to be sent) requests by prompt, per task */
|
|
268
|
-
const requestDrafts = React.useRef(new Map());
|
|
269
450
|
/* the busy popup of slow task store operations */
|
|
270
451
|
const { busy, busyTick, track } = useBusy();
|
|
271
452
|
/* the rendered boxes for the mouse hit-testing */
|
|
@@ -343,13 +524,7 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
343
524
|
}
|
|
344
525
|
}, [all, prompt]);
|
|
345
526
|
/* remember the box of every graph node for the spatial navigation and the scrolling */
|
|
346
|
-
const places = React.useMemo(() =>
|
|
347
|
-
const map = new Map();
|
|
348
|
-
if (layout !== null)
|
|
349
|
-
for (const n of layout.graph.nodes.values())
|
|
350
|
-
map.set(n.id, { r: n.y + Math.floor(n.h / 2), c: n.x + Math.floor(n.w / 2), top: n.y, bottom: n.y + n.h - 1, bl: n.x, br: n.x + n.w - 1 });
|
|
351
|
-
return map;
|
|
352
|
-
}, [layout]);
|
|
527
|
+
const places = React.useMemo(() => placesOf(layout?.graph), [layout]);
|
|
353
528
|
/* follow changes of the task storage and of the lifecycle mode,
|
|
354
529
|
and the kind and connection state of the task store
|
|
355
530
|
(in the background, i.e. without the busy popup) */
|
|
@@ -359,7 +534,9 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
359
534
|
};
|
|
360
535
|
const stop = watchTasks(log, refresh, setStore);
|
|
361
536
|
return () => {
|
|
362
|
-
stop().catch(() => {
|
|
537
|
+
stop().catch((err) => {
|
|
538
|
+
log.write("warning", `board: stopping task watcher failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
539
|
+
});
|
|
363
540
|
};
|
|
364
541
|
}, [log]);
|
|
365
542
|
/* follow the agents passed by the agent relay (if any, else null, and undefined until
|
|
@@ -416,13 +593,7 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
416
593
|
if (!flow.cards.has(flowSel) && !flow.states.some((s) => flowState(s) === flowSel) && flow.cards.size > 0)
|
|
417
594
|
setFlowSel(flow.cards.keys().next().value);
|
|
418
595
|
}, [flow, flowSel]);
|
|
419
|
-
const flowPlaces = React.useMemo(() =>
|
|
420
|
-
const map = new Map();
|
|
421
|
-
if (flowLayout !== null)
|
|
422
|
-
for (const n of flowLayout.graph.nodes.values())
|
|
423
|
-
map.set(n.id, { r: n.y + Math.floor(n.h / 2), c: n.x + Math.floor(n.w / 2), top: n.y, bottom: n.y + n.h - 1, bl: n.x, br: n.x + n.w - 1 });
|
|
424
|
-
return map;
|
|
425
|
-
}, [flowLayout]);
|
|
596
|
+
const flowPlaces = React.useMemo(() => placesOf(flowLayout?.graph), [flowLayout]);
|
|
426
597
|
/* fetch the AI usage of all tasks whenever the statistics view is shown or the board
|
|
427
598
|
changes, but only while the statistics view is shown (as each fetching loads all
|
|
428
599
|
plans), and once known in the background, i.e. without the busy popup */
|
|
@@ -705,122 +876,8 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
705
876
|
React.useEffect(() => {
|
|
706
877
|
promptLoad.current++;
|
|
707
878
|
}, [confirm, transfer, assign, prompt, dialog?.id, sel.id, view]);
|
|
708
|
-
/*
|
|
709
|
-
|
|
710
|
-
running edit), prefilled with its draft of a cancelled (or failed to be sent) prompt,
|
|
711
|
-
else with the prompt of its most recent failed edit request (for a retry) */
|
|
712
|
-
const startPrompt = (id) => {
|
|
713
|
-
const card = all.cards.get(id);
|
|
714
|
-
if (card === undefined)
|
|
715
|
-
return;
|
|
716
|
-
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
717
|
-
setNotice(`editing task "${id}" by prompt refused: task is finished`);
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
if (card.edit === "active") {
|
|
721
|
-
setNotice(`editing task "${id}" by prompt refused: edit in progress`);
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
const draft = promptDrafts.current.get(id);
|
|
725
|
-
if (draft !== undefined) {
|
|
726
|
-
setPrompt({ id, ...draft, focus: "prompt", request: null });
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
const token = ++promptLoad.current;
|
|
730
|
-
track({ text: "loading edit prompt of task", id }, editPrompt(log, id)).then((text) => {
|
|
731
|
-
if (token === promptLoad.current)
|
|
732
|
-
setPrompt({ id, text, pos: [...text].length, focus: "prompt", request: null });
|
|
733
|
-
}).catch((err) => {
|
|
734
|
-
if (token === promptLoad.current)
|
|
735
|
-
setNotice(`editing task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
736
|
-
});
|
|
737
|
-
};
|
|
738
|
-
/* open the prompt popup of a request by prompt of any name of a task (see "ase task
|
|
739
|
-
request", refused for a finished task), with the request name suggested from the
|
|
740
|
-
request agents announced to the agent relay, prefilled with its draft of a cancelled
|
|
741
|
-
(or failed to be sent) request, where a missing request name is to be entered first */
|
|
742
|
-
const startRequest = (id) => {
|
|
743
|
-
const card = all.cards.get(id);
|
|
744
|
-
if (card === undefined)
|
|
745
|
-
return;
|
|
746
|
-
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
747
|
-
setNotice(`requesting task "${id}" by prompt refused: task is finished`);
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
const choices = requestNames(agents);
|
|
751
|
-
const ignored = choices.filter((choice) => requestPrompt(agents, choice) === "ignored");
|
|
752
|
-
const draft = requestDrafts.current.get(id);
|
|
753
|
-
const name = draft?.name ?? choices[0] ?? "";
|
|
754
|
-
const focus = name === "" ? "name" : ignored.includes(name) ? "request" : "prompt";
|
|
755
|
-
setPrompt({ id, text: draft?.text ?? "", pos: draft?.pos ?? 0, focus, request: { name, choices, ignored, typed: "" } });
|
|
756
|
-
};
|
|
757
|
-
/* cancel the prompt popup, keeping its prompt as the draft of the task (even if
|
|
758
|
-
empty, so that a cleared prompt is not prefilled again on reopening) */
|
|
759
|
-
const cancelPrompt = () => {
|
|
760
|
-
if (prompt === null)
|
|
761
|
-
return;
|
|
762
|
-
if (prompt.request !== null) {
|
|
763
|
-
requestDrafts.current.set(prompt.id, { name: prompt.request.name, text: prompt.text, pos: prompt.pos });
|
|
764
|
-
setNotice(`requesting task "${prompt.id}" by prompt cancelled`);
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
promptDrafts.current.set(prompt.id, { text: prompt.text, pos: prompt.pos });
|
|
768
|
-
setNotice(`editing task "${prompt.id}" by prompt cancelled`);
|
|
769
|
-
}
|
|
770
|
-
setPrompt(null);
|
|
771
|
-
};
|
|
772
|
-
/* request a task by the prompt of its popup under its request name (processed by a
|
|
773
|
-
request agent of a definition, resp. by "ase agent editor" for "edit"), dropping
|
|
774
|
-
the draft of the task once sent, resp. keeping the request as its draft on failure,
|
|
775
|
-
where the prompt may be empty only for the requests of agents not requiring their prompts,
|
|
776
|
-
and is dropped for the requests of agents ignoring their prompts */
|
|
777
|
-
const submitRequest = (id, text, pos, name) => {
|
|
778
|
-
let requests;
|
|
779
|
-
try {
|
|
780
|
-
requests = requestsOf(name);
|
|
781
|
-
}
|
|
782
|
-
catch (err) {
|
|
783
|
-
setNotice(`requesting task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
const use = requestPrompt(agents, name);
|
|
787
|
-
if (text.trim() === "" && use === "required") {
|
|
788
|
-
setNotice(`requesting "${name}" of task "${id}" failed: empty prompt`);
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
setPrompt(null);
|
|
792
|
-
track({ text: `requesting "${name}" of task`, id }, requests.request(log, id, use === "ignored" ? "" : text)).then(() => {
|
|
793
|
-
requestDrafts.current.delete(id);
|
|
794
|
-
setNotice(`"${name}" of task "${id}" requested`);
|
|
795
|
-
}).catch((err) => {
|
|
796
|
-
requestDrafts.current.set(id, { name, text, pos });
|
|
797
|
-
setNotice(`requesting "${name}" of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
798
|
-
});
|
|
799
|
-
};
|
|
800
|
-
/* request the edit of a task by the prompt of its popup (processed by "ase agent editor"),
|
|
801
|
-
dropping the draft of the task once sent, resp. keeping the prompt as its draft on failure,
|
|
802
|
-
or request a task by the prompt of its popup under its request name */
|
|
803
|
-
const submitPrompt = () => {
|
|
804
|
-
if (prompt === null)
|
|
805
|
-
return;
|
|
806
|
-
const { id, text, pos } = prompt;
|
|
807
|
-
if (prompt.request !== null) {
|
|
808
|
-
submitRequest(id, text, pos, prompt.request.name);
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
if (text.trim() === "") {
|
|
812
|
-
setNotice(`requesting edit of task "${id}" failed: empty edit prompt`);
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
setPrompt(null);
|
|
816
|
-
track({ text: "requesting edit of task", id }, requestEdit(log, id, text)).then(() => {
|
|
817
|
-
promptDrafts.current.delete(id);
|
|
818
|
-
setNotice(`edit of task "${id}" requested`);
|
|
819
|
-
}).catch((err) => {
|
|
820
|
-
promptDrafts.current.set(id, { text, pos });
|
|
821
|
-
setNotice(`requesting edit of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
822
|
-
});
|
|
823
|
-
};
|
|
879
|
+
/* the actions of the prompt popup (with the drafts of its prompts and requests, per task) */
|
|
880
|
+
const { startPrompt, startRequest, cancelPrompt, submitPrompt } = usePromptActions({ log, all, cycle, agents, prompt, setPrompt, setNotice, track, promptLoad });
|
|
824
881
|
/* toggle a flag (task titles, key hints, or standalone tasks) of the TUI surface */
|
|
825
882
|
const toggleFlag = (flag) => {
|
|
826
883
|
BoardState.toggleFlag("tui", flag).then((state) => {
|
|
@@ -844,6 +901,7 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
844
901
|
if (grow && !growable)
|
|
845
902
|
setGrow(false);
|
|
846
903
|
}, [grow, growable]);
|
|
904
|
+
/* the heights of the header, footer, and board area, and the inner board width */
|
|
847
905
|
const headH = 3;
|
|
848
906
|
const footH = surface.keys ? (view === "agents" || view === "stats" || view === "flow" ? 5 : view === "lanes" ? 7 : 6) : 3;
|
|
849
907
|
const boardH = rows - headH - footH;
|
|
@@ -947,52 +1005,8 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
947
1005
|
};
|
|
948
1006
|
}, [pulsing]);
|
|
949
1007
|
const pulse = pulsing ? pulseTick : -1;
|
|
950
|
-
/*
|
|
951
|
-
|
|
952
|
-
const [now, setNow] = React.useState(Date.now());
|
|
953
|
-
React.useEffect(() => {
|
|
954
|
-
if (view !== "agents")
|
|
955
|
-
return;
|
|
956
|
-
setNow(Date.now());
|
|
957
|
-
const timer = setInterval(() => {
|
|
958
|
-
setNow(Date.now());
|
|
959
|
-
}, 1000);
|
|
960
|
-
return () => {
|
|
961
|
-
clearInterval(timer);
|
|
962
|
-
};
|
|
963
|
-
}, [view]);
|
|
964
|
-
/* the local time at which the latest heartbeat of each agent was received (measured
|
|
965
|
-
locally, as the clock of the agent relay may be skewed), for the heart of its card
|
|
966
|
-
(filled for BEAT_SHOWN_MS after a heartbeat, else empty), where the heartbeats are
|
|
967
|
-
recognized by their changed time stamps and a filled heart is re-rendered once it is
|
|
968
|
-
due to be emptied (as the wall-clock tick alone would empty it too early or too late) */
|
|
969
|
-
const beats = React.useRef(new Map());
|
|
970
|
-
const beatsSeen = React.useMemo(() => {
|
|
971
|
-
const at = Date.now();
|
|
972
|
-
const list = agents?.agents ?? [];
|
|
973
|
-
const next = new Map();
|
|
974
|
-
for (const a of list) {
|
|
975
|
-
const prev = beats.current.get(a.id);
|
|
976
|
-
next.set(a.id, prev !== undefined && prev.beat === a.beat ? prev : { beat: a.beat, seen: at });
|
|
977
|
-
}
|
|
978
|
-
beats.current = next;
|
|
979
|
-
return next;
|
|
980
|
-
}, [agents]);
|
|
981
|
-
React.useEffect(() => {
|
|
982
|
-
if (view !== "agents" && view !== "flow")
|
|
983
|
-
return;
|
|
984
|
-
const latest = Math.max(0, ...[...beatsSeen.values()].map((b) => b.seen));
|
|
985
|
-
const due = latest + BEAT_SHOWN_MS - Date.now();
|
|
986
|
-
if (latest === 0 || due <= 0)
|
|
987
|
-
return;
|
|
988
|
-
const timer = setTimeout(() => {
|
|
989
|
-
setNow(Date.now());
|
|
990
|
-
}, due);
|
|
991
|
-
return () => {
|
|
992
|
-
clearTimeout(timer);
|
|
993
|
-
};
|
|
994
|
-
}, [view, beatsSeen]);
|
|
995
|
-
const beatOf = (id) => now - (beatsSeen.get(id)?.seen ?? 0) < BEAT_SHOWN_MS;
|
|
1008
|
+
/* the wall-clock and the heartbeats of the agents (for the agents view and the agent graph) */
|
|
1009
|
+
const { now, beatOf } = useBeats(agents, view);
|
|
996
1010
|
return {
|
|
997
1011
|
log, exit, columns, rows, all, board, surface, view, setView, filter, setFilter, typing, setTyping,
|
|
998
1012
|
sel, setSel, dialog, setDialog, scroll, layout, notice, setNotice, carry, setCarry, cycle,
|
|
@@ -56,7 +56,7 @@ const wrap = (line, width) => {
|
|
|
56
56
|
return out;
|
|
57
57
|
};
|
|
58
58
|
/* make a text safe for the terminal: expand tabs and strip control characters */
|
|
59
|
-
export const sanitize = (text) => text.replace(/\t/g, " ").replace(/[\p{Cc}
|
|
59
|
+
export const sanitize = (text) => text.replace(/\t/g, " ").replace(/[\p{Cc}\u{061C}\u{200E}\u{200F}\u{202A}-\u{202E}\u{2066}-\u{2069}]/gu, "");
|
|
60
60
|
/* mark the checkbox of a plan line in the read dialog: checkboxes of items
|
|
61
61
|
which are done, in progress, or open are accented, while checkboxes of
|
|
62
62
|
items which are questioned, delegated, or dropped are signaled */
|
|
@@ -344,8 +344,7 @@ export const promptNext = (prompt) => promptIgnored(prompt) ? "request" : "promp
|
|
|
344
344
|
wrapped onto the five lines of the frame of an input box (like in the web board, in
|
|
345
345
|
accent color, resp. in signal color while focused), scrolled to show the cursor, with
|
|
346
346
|
the request and cancel buttons below it (in accent color, resp. in signal color while
|
|
347
|
-
focused), and with
|
|
348
|
-
every inner cell written (with spaces) to hide the content underneath */
|
|
347
|
+
focused), and with every inner cell written (with spaces) to hide the content underneath */
|
|
349
348
|
export const renderPrompt = (prompt, columns, rows, choices = []) => {
|
|
350
349
|
const { id, text, pos, request } = prompt;
|
|
351
350
|
const box = promptBox(columns, rows, request !== null);
|