@rse/ase 2.0.2 → 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 +2 -0
- package/dst/ase-hook.js +12 -12
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +4 -5
- 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-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +41 -35
- package/dst/ase-task-board-tui-model.js +185 -167
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +74 -70
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +2 -2
- 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 +16 -1
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +9 -32
- package/dst/ase-task-store-plugin-github.js +3 -12
- package/dst/ase-task-store-plugin-gitlab.js +2 -18
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +13 -37
- 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 +1 -1
- 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-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +10 -7
- package/plugin/skills/ase-code-analyze/SKILL.md +18 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-lint/SKILL.md +37 -23
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +24 -12
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
|
@@ -230,6 +230,183 @@ const useAgentsView = (agents, columns, boardH) => {
|
|
|
230
230
|
};
|
|
231
231
|
return { agentList, agentIdx, agent, agentOp, logLines, logH, logFirst, logMax, selectAgent, scrollLog, focusAgent };
|
|
232
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
|
+
};
|
|
233
410
|
/* the state of the terminal board: all state, the derived values, and the actions */
|
|
234
411
|
export const useBoardState = (log, initial, relay) => {
|
|
235
412
|
const { exit, suspendTerminal } = useApp();
|
|
@@ -270,10 +447,6 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
270
447
|
const [grow, setGrow] = React.useState(false);
|
|
271
448
|
const editing = React.useRef(false);
|
|
272
449
|
const drafts = React.useRef(new Map());
|
|
273
|
-
/* the drafts of the prompts of cancelled (or failed to be sent) edit requests, per task */
|
|
274
|
-
const promptDrafts = React.useRef(new Map());
|
|
275
|
-
/* the drafts of the cancelled (or failed to be sent) requests by prompt, per task */
|
|
276
|
-
const requestDrafts = React.useRef(new Map());
|
|
277
450
|
/* the busy popup of slow task store operations */
|
|
278
451
|
const { busy, busyTick, track } = useBusy();
|
|
279
452
|
/* the rendered boxes for the mouse hit-testing */
|
|
@@ -361,7 +534,9 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
361
534
|
};
|
|
362
535
|
const stop = watchTasks(log, refresh, setStore);
|
|
363
536
|
return () => {
|
|
364
|
-
stop().catch(() => {
|
|
537
|
+
stop().catch((err) => {
|
|
538
|
+
log.write("warning", `board: stopping task watcher failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
539
|
+
});
|
|
365
540
|
};
|
|
366
541
|
}, [log]);
|
|
367
542
|
/* follow the agents passed by the agent relay (if any, else null, and undefined until
|
|
@@ -701,122 +876,8 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
701
876
|
React.useEffect(() => {
|
|
702
877
|
promptLoad.current++;
|
|
703
878
|
}, [confirm, transfer, assign, prompt, dialog?.id, sel.id, view]);
|
|
704
|
-
/*
|
|
705
|
-
|
|
706
|
-
running edit), prefilled with its draft of a cancelled (or failed to be sent) prompt,
|
|
707
|
-
else with the prompt of its most recent failed edit request (for a retry) */
|
|
708
|
-
const startPrompt = (id) => {
|
|
709
|
-
const card = all.cards.get(id);
|
|
710
|
-
if (card === undefined)
|
|
711
|
-
return;
|
|
712
|
-
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
713
|
-
setNotice(`editing task "${id}" by prompt refused: task is finished`);
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
if (card.edit === "active") {
|
|
717
|
-
setNotice(`editing task "${id}" by prompt refused: edit in progress`);
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
const draft = promptDrafts.current.get(id);
|
|
721
|
-
if (draft !== undefined) {
|
|
722
|
-
setPrompt({ id, ...draft, focus: "prompt", request: null });
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
const token = ++promptLoad.current;
|
|
726
|
-
track({ text: "loading edit prompt of task", id }, editPrompt(log, id)).then((text) => {
|
|
727
|
-
if (token === promptLoad.current)
|
|
728
|
-
setPrompt({ id, text, pos: [...text].length, focus: "prompt", request: null });
|
|
729
|
-
}).catch((err) => {
|
|
730
|
-
if (token === promptLoad.current)
|
|
731
|
-
setNotice(`editing task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
732
|
-
});
|
|
733
|
-
};
|
|
734
|
-
/* open the prompt popup of a request by prompt of any name of a task (see "ase task
|
|
735
|
-
request", refused for a finished task), with the request name suggested from the
|
|
736
|
-
request agents announced to the agent relay, prefilled with its draft of a cancelled
|
|
737
|
-
(or failed to be sent) request, where a missing request name is to be entered first */
|
|
738
|
-
const startRequest = (id) => {
|
|
739
|
-
const card = all.cards.get(id);
|
|
740
|
-
if (card === undefined)
|
|
741
|
-
return;
|
|
742
|
-
if (cycle !== null && editRefused(cycle, card.actual)) {
|
|
743
|
-
setNotice(`requesting task "${id}" by prompt refused: task is finished`);
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
const choices = requestNames(agents);
|
|
747
|
-
const ignored = choices.filter((choice) => requestPrompt(agents, choice) === "ignored");
|
|
748
|
-
const draft = requestDrafts.current.get(id);
|
|
749
|
-
const name = draft?.name ?? choices[0] ?? "";
|
|
750
|
-
const focus = name === "" ? "name" : ignored.includes(name) ? "request" : "prompt";
|
|
751
|
-
setPrompt({ id, text: draft?.text ?? "", pos: draft?.pos ?? 0, focus, request: { name, choices, ignored, typed: "" } });
|
|
752
|
-
};
|
|
753
|
-
/* cancel the prompt popup, keeping its prompt as the draft of the task (even if
|
|
754
|
-
empty, so that a cleared prompt is not prefilled again on reopening) */
|
|
755
|
-
const cancelPrompt = () => {
|
|
756
|
-
if (prompt === null)
|
|
757
|
-
return;
|
|
758
|
-
if (prompt.request !== null) {
|
|
759
|
-
requestDrafts.current.set(prompt.id, { name: prompt.request.name, text: prompt.text, pos: prompt.pos });
|
|
760
|
-
setNotice(`requesting task "${prompt.id}" by prompt cancelled`);
|
|
761
|
-
}
|
|
762
|
-
else {
|
|
763
|
-
promptDrafts.current.set(prompt.id, { text: prompt.text, pos: prompt.pos });
|
|
764
|
-
setNotice(`editing task "${prompt.id}" by prompt cancelled`);
|
|
765
|
-
}
|
|
766
|
-
setPrompt(null);
|
|
767
|
-
};
|
|
768
|
-
/* request a task by the prompt of its popup under its request name (processed by a
|
|
769
|
-
request agent of a definition, resp. by "ase agent editor" for "edit"), dropping
|
|
770
|
-
the draft of the task once sent, resp. keeping the request as its draft on failure,
|
|
771
|
-
where the prompt may be empty only for the requests of agents not requiring their prompts,
|
|
772
|
-
and is dropped for the requests of agents ignoring their prompts */
|
|
773
|
-
const submitRequest = (id, text, pos, name) => {
|
|
774
|
-
let requests;
|
|
775
|
-
try {
|
|
776
|
-
requests = requestsOf(name);
|
|
777
|
-
}
|
|
778
|
-
catch (err) {
|
|
779
|
-
setNotice(`requesting task "${id}" by prompt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
const use = requestPrompt(agents, name);
|
|
783
|
-
if (text.trim() === "" && use === "required") {
|
|
784
|
-
setNotice(`requesting "${name}" of task "${id}" failed: empty prompt`);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
setPrompt(null);
|
|
788
|
-
track({ text: `requesting "${name}" of task`, id }, requests.request(log, id, use === "ignored" ? "" : text)).then(() => {
|
|
789
|
-
requestDrafts.current.delete(id);
|
|
790
|
-
setNotice(`"${name}" of task "${id}" requested`);
|
|
791
|
-
}).catch((err) => {
|
|
792
|
-
requestDrafts.current.set(id, { name, text, pos });
|
|
793
|
-
setNotice(`requesting "${name}" of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
794
|
-
});
|
|
795
|
-
};
|
|
796
|
-
/* request the edit of a task by the prompt of its popup (processed by "ase agent editor"),
|
|
797
|
-
dropping the draft of the task once sent, resp. keeping the prompt as its draft on failure,
|
|
798
|
-
or request a task by the prompt of its popup under its request name */
|
|
799
|
-
const submitPrompt = () => {
|
|
800
|
-
if (prompt === null)
|
|
801
|
-
return;
|
|
802
|
-
const { id, text, pos } = prompt;
|
|
803
|
-
if (prompt.request !== null) {
|
|
804
|
-
submitRequest(id, text, pos, prompt.request.name);
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
if (text.trim() === "") {
|
|
808
|
-
setNotice(`requesting edit of task "${id}" failed: empty edit prompt`);
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
setPrompt(null);
|
|
812
|
-
track({ text: "requesting edit of task", id }, requestEdit(log, id, text)).then(() => {
|
|
813
|
-
promptDrafts.current.delete(id);
|
|
814
|
-
setNotice(`edit of task "${id}" requested`);
|
|
815
|
-
}).catch((err) => {
|
|
816
|
-
promptDrafts.current.set(id, { text, pos });
|
|
817
|
-
setNotice(`requesting edit of task "${id}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
818
|
-
});
|
|
819
|
-
};
|
|
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 });
|
|
820
881
|
/* toggle a flag (task titles, key hints, or standalone tasks) of the TUI surface */
|
|
821
882
|
const toggleFlag = (flag) => {
|
|
822
883
|
BoardState.toggleFlag("tui", flag).then((state) => {
|
|
@@ -840,6 +901,7 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
840
901
|
if (grow && !growable)
|
|
841
902
|
setGrow(false);
|
|
842
903
|
}, [grow, growable]);
|
|
904
|
+
/* the heights of the header, footer, and board area, and the inner board width */
|
|
843
905
|
const headH = 3;
|
|
844
906
|
const footH = surface.keys ? (view === "agents" || view === "stats" || view === "flow" ? 5 : view === "lanes" ? 7 : 6) : 3;
|
|
845
907
|
const boardH = rows - headH - footH;
|
|
@@ -943,52 +1005,8 @@ export const useBoardState = (log, initial, relay) => {
|
|
|
943
1005
|
};
|
|
944
1006
|
}, [pulsing]);
|
|
945
1007
|
const pulse = pulsing ? pulseTick : -1;
|
|
946
|
-
/*
|
|
947
|
-
|
|
948
|
-
const [now, setNow] = React.useState(Date.now());
|
|
949
|
-
React.useEffect(() => {
|
|
950
|
-
if (view !== "agents")
|
|
951
|
-
return;
|
|
952
|
-
setNow(Date.now());
|
|
953
|
-
const timer = setInterval(() => {
|
|
954
|
-
setNow(Date.now());
|
|
955
|
-
}, 1000);
|
|
956
|
-
return () => {
|
|
957
|
-
clearInterval(timer);
|
|
958
|
-
};
|
|
959
|
-
}, [view]);
|
|
960
|
-
/* the local time at which the latest heartbeat of each agent was received (measured
|
|
961
|
-
locally, as the clock of the agent relay may be skewed), for the heart of its card
|
|
962
|
-
(filled for BEAT_SHOWN_MS after a heartbeat, else empty), where the heartbeats are
|
|
963
|
-
recognized by their changed time stamps and a filled heart is re-rendered once it is
|
|
964
|
-
due to be emptied (as the wall-clock tick alone would empty it too early or too late) */
|
|
965
|
-
const beats = React.useRef(new Map());
|
|
966
|
-
const beatsSeen = React.useMemo(() => {
|
|
967
|
-
const at = Date.now();
|
|
968
|
-
const list = agents?.agents ?? [];
|
|
969
|
-
const next = new Map();
|
|
970
|
-
for (const a of list) {
|
|
971
|
-
const prev = beats.current.get(a.id);
|
|
972
|
-
next.set(a.id, prev !== undefined && prev.beat === a.beat ? prev : { beat: a.beat, seen: at });
|
|
973
|
-
}
|
|
974
|
-
beats.current = next;
|
|
975
|
-
return next;
|
|
976
|
-
}, [agents]);
|
|
977
|
-
React.useEffect(() => {
|
|
978
|
-
if (view !== "agents" && view !== "flow")
|
|
979
|
-
return;
|
|
980
|
-
const latest = Math.max(0, ...[...beatsSeen.values()].map((b) => b.seen));
|
|
981
|
-
const due = latest + BEAT_SHOWN_MS - Date.now();
|
|
982
|
-
if (latest === 0 || due <= 0)
|
|
983
|
-
return;
|
|
984
|
-
const timer = setTimeout(() => {
|
|
985
|
-
setNow(Date.now());
|
|
986
|
-
}, due);
|
|
987
|
-
return () => {
|
|
988
|
-
clearTimeout(timer);
|
|
989
|
-
};
|
|
990
|
-
}, [view, beatsSeen]);
|
|
991
|
-
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);
|
|
992
1010
|
return {
|
|
993
1011
|
log, exit, columns, rows, all, board, surface, view, setView, filter, setFilter, typing, setTyping,
|
|
994
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);
|