@rse/ase 2.0.6 → 2.0.8

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.
Files changed (68) hide show
  1. package/dst/ase-agent-definition-api.d.ts +2 -0
  2. package/dst/ase-agent-definition-core.js +24 -3
  3. package/dst/ase-agent-fleet.js +2 -1
  4. package/dst/ase-agent-relay-client.js +3 -4
  5. package/dst/ase-agent-runtime-engine.js +22 -4
  6. package/dst/ase-agent-runtime-tool.js +10 -8
  7. package/dst/ase-agent-runtime.js +8 -4
  8. package/dst/ase-config-schema.js +4 -1
  9. package/dst/ase-lib-fetch.js +12 -0
  10. package/dst/ase-lib-server-cli.js +8 -5
  11. package/dst/ase-man-catalog.js +1 -1
  12. package/dst/ase-task-board-diff.js +120 -0
  13. package/dst/ase-task-board-tui-control.js +8 -5
  14. package/dst/ase-task-board-tui-model.js +54 -14
  15. package/dst/ase-task-board-tui-popup.js +41 -8
  16. package/dst/ase-task-board-tui.js +42 -27
  17. package/dst/ase-task-board-web-client.css +1 -1
  18. package/dst/ase-task-board-web-client.html +16 -0
  19. package/dst/ase-task-board-web-client.js +33 -33
  20. package/dst/ase-task-board-web-server.js +44 -9
  21. package/dst/ase-task-board.js +12 -4
  22. package/dst/ase-task-store-client.js +4 -5
  23. package/dst/ase-task-store-plugin-gitea.js +3 -3
  24. package/dst/man/ase-agent-adhoc.md +25 -6
  25. package/dst/man/ase-agent-condenser.md +7 -0
  26. package/dst/man/ase-agent-editor.md +7 -0
  27. package/dst/man/ase-agent-estimator.md +7 -0
  28. package/dst/man/ase-agent-fleet.md +7 -4
  29. package/dst/man/ase-agent-implementer.md +7 -0
  30. package/dst/man/ase-agent-integrator.md +7 -0
  31. package/dst/man/ase-agent-qualifier.md +7 -0
  32. package/dst/man/ase-agent-rebooter.md +7 -0
  33. package/dst/man/ase-agent-researcher.md +7 -0
  34. package/dst/man/ase-config-init.md +2 -1
  35. package/dst/man/ase-config.md +5 -0
  36. package/dst/man/ase-task-board.md +44 -2
  37. package/package.json +11 -10
  38. package/plugin/.claude-plugin/plugin.json +1 -1
  39. package/plugin/.codex-plugin/plugin.json +1 -1
  40. package/plugin/.github/plugin/plugin.json +1 -1
  41. package/plugin/meta/ase-common-task.md +33 -1
  42. package/plugin/meta/ase-control.md +14 -0
  43. package/plugin/meta/ase-format-specbook.yaml +9 -9
  44. package/plugin/meta/ase-skill.md +7 -0
  45. package/plugin/mods/ase-mods-common.ts +73 -42
  46. package/plugin/mods/ase-mods-help-hint.ts +1 -1
  47. package/plugin/mods/ase-mods-task-edit.ts +427 -0
  48. package/plugin/mods/ase-mods.ts +45 -14
  49. package/plugin/skills/ase-code-edit/SKILL.md +53 -16
  50. package/plugin/skills/ase-code-edit/help.md +26 -13
  51. package/plugin/skills/ase-code-lint/SKILL.md +7 -3
  52. package/plugin/skills/ase-help-intent/data.md +248 -36
  53. package/plugin/skills/ase-help-skill/catalog.md +1 -0
  54. package/plugin/skills/ase-meta-workflow/workflow.txt +2 -0
  55. package/plugin/skills/ase-repo-changelog/SKILL.md +29 -10
  56. package/plugin/skills/ase-repo-changelog/help.md +5 -1
  57. package/plugin/skills/ase-repo-commit/SKILL.md +4 -1
  58. package/plugin/skills/ase-repo-dissect/help.md +2 -1
  59. package/plugin/skills/ase-repo-stage/SKILL.md +459 -0
  60. package/plugin/skills/ase-repo-stage/help.md +120 -0
  61. package/plugin/skills/ase-spec-edit/SKILL.md +20 -12
  62. package/plugin/skills/ase-spec-edit/help.md +22 -14
  63. package/plugin/skills/ase-task-edit/SKILL.md +114 -9
  64. package/plugin/skills/ase-task-edit/help.md +60 -0
  65. package/plugin/skills/ase-task-grill/SKILL.md +35 -20
  66. package/plugin/skills/ase-task-grill/help.md +18 -8
  67. /package/plugin/mods/{ase-mods-evaluate.ts → ase-mods-meta-evaluate.ts} +0 -0
  68. /package/plugin/mods/{ase-mods-grill.ts → ase-mods-task-grill.ts} +0 -0
@@ -13,6 +13,7 @@ import { userId } from "./ase-service-otel.js";
13
13
  import { Config } from "./ase-config-core.js";
14
14
  import { configSchema, webColorDefaults, webColorNames } from "./ase-config-schema.js";
15
15
  import { buildBoard, watchTasks, toneOf, laneMoves, cardMoves, BoardState, attachmentTabs, isDiff, diffTones, dataLang, dataLoad, dataSpans, newTaskText, createTask, saveTask, TaskConflict, watchAgents } from "./ase-task-board-core.js";
16
+ import { diffHunks, diffMarkdown, diffMarksToHTML } from "./ase-task-board-diff.js";
16
17
  import { layoutGraph, drawGraphSVG } from "./ase-task-board-graph.js";
17
18
  import { flowOf, layoutFlow, drawFlowSVG } from "./ase-task-board-flow.js";
18
19
  import { filterBoard, dropStandalone } from "./ase-task-board-filter.js";
@@ -94,9 +95,9 @@ const markedLoad = async (remote) => {
94
95
  (title ? ` title="${escapeHTML(title)}"` : "") + ">" : escapeHTML(text);
95
96
  },
96
97
  code({ text, lang }) {
97
- if (lang === "mermaid") {
98
+ if (lang === "mermaid" && !text.includes("⟦chg:")) {
98
99
  try {
99
- return `<div class="diagram">${renderMermaidSVG(text)}</div>`;
100
+ return `<div class="diagram">${renderMermaidSVG(text, { bg: "var(--c-paper)", fg: "var(--c-text)" })}</div>`;
100
101
  }
101
102
  catch {
102
103
  /* fall through to the plain rendering of the source */
@@ -119,12 +120,12 @@ const renderPlan = async (body, remote = false) => {
119
120
  const outside = fence === "";
120
121
  fence = TaskFormat.fenceTrack(fence, line);
121
122
  return !outside || fence !== "" ? line : line
122
- .replace(/^(\s*(?:[-*]|\d+[.)])\s+)\[([ x/?\->])\]/, (_m, lead, box) => `${lead}⟦box:${boxes[box]}⟧`);
123
+ .replace(/^(\s*(?:[-*]|\d+[.)])\s+(?:⟦chg:[a-z-]+⟧)?)\[([ x/?\->])\]/, (_m, lead, box) => `${lead}⟦box:${boxes[box]}⟧`);
123
124
  }).join("\n");
124
125
  if (!marked.has(remote))
125
126
  marked.set(remote, markedLoad(remote));
126
127
  const html = (await marked.get(remote)).parse(prepared, { async: false });
127
- return html.replace(/⟦box:([a-z]+)⟧/g, (_m, state) => `<span class="box ${state}" title="[${escapeHTML(boxChars[state] ?? " ")}]"></span>`);
128
+ return diffMarksToHTML(html.replace(/⟦box:([a-z]+)⟧/g, (_m, state) => `<span class="box ${state}" title="[${escapeHTML(boxChars[state] ?? " ")}]"></span>`));
128
129
  };
129
130
  const colorRoles = Object.keys(webColorDefaults);
130
131
  const colorBases = (log) => {
@@ -154,10 +155,18 @@ const editorKeymap = (log) => {
154
155
  const value = cfg.get("board.web.editor.keymap");
155
156
  return value === "vim" || value === "emacs" ? value : "default";
156
157
  };
157
- /* render a standalone document (styled by the client stylesheet) out of
158
- key/value fields, separated by a line from the content */
158
+ /* the color theme of the web board, resolved from the
159
+ "board.web.theme" configuration (on each page load) */
160
+ const boardTheme = (log) => {
161
+ const cfg = new Config("config", configSchema, log);
162
+ cfg.read();
163
+ const value = cfg.get("board.web.theme");
164
+ return value === "light" || value === "dark" ? value : "auto";
165
+ };
166
+ /* render a standalone document (styled by the client stylesheet) out of key/value
167
+ fields (optionally marked as added or removed), separated by a line from the content */
159
168
  const fieldsDocument = (fields, content) => {
160
- const rows = fields.map(([k, v]) => `<tr><td class="k">${escapeHTML(k)}</td><td>${escapeHTML(v)}</td></tr>`).join("");
169
+ const rows = fields.map(([k, v, mark]) => `<tr${mark !== undefined ? ` class="${mark}"` : ""}><td class="k">${escapeHTML(k)}</td><td>${escapeHTML(v)}</td></tr>`).join("");
161
170
  return "<!DOCTYPE html><html><head><meta charset=\"utf-8\">" +
162
171
  "<link rel=\"stylesheet\" href=\"/task-board/colors.css\">" +
163
172
  "<link rel=\"stylesheet\" href=\"/task-board/ase-task-board-web-client.css\"></head><body class=\"plan\"><article>" +
@@ -166,6 +175,16 @@ const fieldsDocument = (fields, content) => {
166
175
  };
167
176
  /* render a task plan as a standalone document: frontmatter fields and the numbered plan sections */
168
177
  const taskDocument = async (keys, body) => fieldsDocument([...keys].filter(([k]) => k !== "Type"), await renderPlan(body));
178
+ const isPlanText = (value) => {
179
+ const plan = value;
180
+ return typeof plan?.body === "string" && Array.isArray(plan.keys) && plan.keys.every((field) => Array.isArray(field) && field.length === 2 && field.every((text) => typeof text === "string"));
181
+ };
182
+ /* render the diff of two task plans as a standalone document, in the look of a task plan */
183
+ const diffDocument = async (before, after) => fieldsDocument(diffHunks(before.keys, after.keys, (field) => JSON.stringify(field)).flatMap(({ kept, removed, added }) => [
184
+ ...kept,
185
+ ...removed.map(([k, v]) => [k, v, "del"]),
186
+ ...added.map(([k, v]) => [k, v, "add"])
187
+ ]), await renderPlan(diffMarkdown(before.body, after.body)));
169
188
  /* render a task plan attachment as a standalone document: its fields and
170
189
  its content, with images embedded, Markdown rendered, YAML or JSON data syntax-highlighted, a diff
171
190
  colored per line, binary content as a placeholder, and else verbatim text */
@@ -335,11 +354,13 @@ const guarded = (handler) => async (request, h) => {
335
354
  };
336
355
  /* register the page routes of the web board: its client assets and its colors */
337
356
  const registerPageRoutes = (server, log) => {
338
- /* the single page of the web board, with its stylesheet and browser-side code */
357
+ /* the single page of the web board, with its stylesheet and browser-side code,
358
+ and with its color theme (resolved on each page load) on its root element */
339
359
  server.route({
340
360
  method: "GET",
341
361
  path: "/task-board",
342
- handler: (_request, h) => h.response(clientAsset("html")).type("text/html; charset=utf-8")
362
+ handler: (_request, h) => h.response(clientAsset("html")
363
+ .replace(/<html\b/, `<html data-theme="${boardTheme(log)}"`)).type("text/html; charset=utf-8")
343
364
  });
344
365
  server.route({
345
366
  method: "GET",
@@ -439,12 +460,26 @@ const registerViewRoutes = (server, log) => {
439
460
  group: board.groups.find((g) => g.lanes.some((l) => l.status === card.status))?.title ?? "",
440
461
  epic: card.group,
441
462
  doc: await taskDocument(parts.keys, parts.body),
463
+ plan: { keys: [...parts.keys].filter(([k]) => k !== "Type"), body: parts.body },
442
464
  tabs: attachmentTabs(await Task.attachments(log, id), parts.keys.get("Modified") ?? ""),
443
465
  pred: ref(board.pred.get(id) ?? []),
444
466
  succ: ref(board.succ.get(id) ?? [])
445
467
  });
446
468
  })
447
469
  });
470
+ /* the diff of two states of a task plan, rendered statelessly out of the states
471
+ kept by the client, as the changes are followed per task dialog */
472
+ server.route({
473
+ method: "POST",
474
+ path: "/task-board/api/diff",
475
+ options: { payload: { parse: true, allow: "application/json" } },
476
+ handler: guarded(async (request, h) => {
477
+ const p = request.payload;
478
+ if (!isPlanText(p?.before) || !isPlanText(p.after))
479
+ return h.response({ error: "invalid diff request" }).code(400);
480
+ return h.response({ doc: await diffDocument(p.before, p.after) });
481
+ })
482
+ });
448
483
  /* one task plan as text for editing, with the entity tag it is based on and the editor key bindings */
449
484
  server.route({
450
485
  method: "GET",
@@ -108,27 +108,35 @@ export default class TaskBoardCommand {
108
108
  .option("--agent-relay <url>", "agent relay to receive the live presence of the agents from, " +
109
109
  "as \"ase[s]://<addr>:<port>[/<token>]\" (default: the configured \"project.relay.url\", else none; " +
110
110
  "the web board always follows the configured one)")
111
+ .option("-T, --task <id>", "show only the task dialog of the given task as a task viewer, " +
112
+ "following all its changes live (with --text or without a terminal: print its plan and exit)")
111
113
  .action(async (opts) => {
114
+ /* refuse the task viewer on an unknown task */
115
+ const plan = opts.task !== undefined ? await Task.load(this.log, opts.task) : "";
116
+ if (opts.task !== undefined && plan === "")
117
+ throw new Error(`board: no task "${opts.task}"`);
112
118
  /* serve and open the web board */
113
119
  if (opts.web === true) {
114
120
  if (opts.agentRelay !== undefined)
115
121
  this.log.write("warning", "board: \"--agent-relay\" is ignored by the web board, " +
116
122
  "which follows the configured \"project.relay.url\"");
117
- const url = `http://${SERVICE_HOST}:${await this.servicePort()}/task-board`;
123
+ const url = `http://${SERVICE_HOST}:${await this.servicePort()}/task-board` +
124
+ (opts.task !== undefined ? `?task=${encodeURIComponent(opts.task)}` : "");
118
125
  await writeStdout(`${url}\n`);
119
126
  await this.openBrowser(url);
120
127
  return;
121
128
  }
122
- /* print the plain text overview if requested or without a terminal */
129
+ /* print the plain text overview (resp. the plan of the task
130
+ of the task viewer) if requested or without a terminal */
123
131
  if (opts.text === true || !process.stdout.isTTY || !process.stdin.isTTY) {
124
- await writeStdout(await textOverview(this.log, opts.agentRelay));
132
+ await writeStdout(opts.task !== undefined ? plan : await textOverview(this.log, opts.agentRelay));
125
133
  return;
126
134
  }
127
135
  /* run the interactive terminal board, with the production build of React
128
136
  (its development build records a never cleared performance measure per render) */
129
137
  process.env.NODE_ENV ??= "production";
130
138
  const { runTUI } = await import("./ase-task-board-tui.js");
131
- await runTUI(this.log, opts.agentRelay);
139
+ await runTUI(this.log, opts.agentRelay, opts.task);
132
140
  });
133
141
  }
134
142
  }
@@ -5,10 +5,9 @@
5
5
  */
6
6
  import path from "node:path";
7
7
  import fs from "node:fs";
8
- import { ofetch } from "ofetch";
9
- import { Agent } from "undici";
10
8
  import { WebSocket } from "ws";
11
9
  import { LRUCache } from "lru-cache";
10
+ import { dispatchFetch, insecureAgent } from "./ase-lib-fetch.js";
12
11
  import { Config } from "./ase-config-core.js";
13
12
  import { configSchema } from "./ase-config-schema.js";
14
13
  import { parseScope, userStateDir } from "./ase-config-scope.js";
@@ -217,7 +216,7 @@ export class RemoteTaskStoreClient {
217
216
  this.insecure = insecure;
218
217
  this.lifecycle = configured;
219
218
  this.idscheme = configuredIdScheme;
220
- this.dispatcher = insecure ? new Agent({ connect: { rejectUnauthorized: false } }) : undefined;
219
+ this.dispatcher = insecure ? insecureAgent() : undefined;
221
220
  }
222
221
  /* raise a failed connection as an unreachable store error,
223
222
  reporting the innermost cause (like "connect ECONNREFUSED") */
@@ -232,7 +231,7 @@ export class RemoteTaskStoreClient {
232
231
  a null result, any other error response is raised as a problem carrying
233
232
  its status, and a failed connection is raised as an unreachable store error */
234
233
  async request(method, url, body, headers = {}, tolerated = []) {
235
- const r = await ofetch.raw(`${this.base}${url}`, {
234
+ const r = await dispatchFetch.raw(`${this.base}${url}`, {
236
235
  method,
237
236
  headers: {
238
237
  Authorization: `Bearer ${this.token}`,
@@ -408,7 +407,7 @@ export class RemoteTaskStoreClient {
408
407
  return await this.task(await this.request("POST", `${this.tasks}/${encodeURIComponent(id)}/attachment${append ? "?append=true" : ""}`, attachment)) !== null;
409
408
  }
410
409
  async content(id, index) {
411
- const r = await ofetch.raw(`${this.base}${this.tasks}/${encodeURIComponent(id)}/attachment/${index}/content`, {
410
+ const r = await dispatchFetch.raw(`${this.base}${this.tasks}/${encodeURIComponent(id)}/attachment/${index}/content`, {
412
411
  headers: { Authorization: `Bearer ${this.token}` },
413
412
  responseType: "arrayBuffer",
414
413
  dispatcher: this.dispatcher,
@@ -3,7 +3,7 @@
3
3
  ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
5
  */
6
- import { giteaApi } from "gitea-js";
6
+ import { giteaApi, ContentType } from "gitea-js";
7
7
  import { LRUCache } from "lru-cache";
8
8
  import * as TaskFormat from "./ase-task-format.js";
9
9
  import * as TaskIssues from "./ase-task-store-issues.js";
@@ -396,7 +396,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
396
396
  for (const blocker of after) {
397
397
  if (have.has(blocker.number))
398
398
  continue;
399
- await this.call(loc, this.gt.repos.issueCreateIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker)))
399
+ await this.call(loc, this.gt.repos.issueCreateIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker), { type: ContentType.Json }))
400
400
  .catch((err) => {
401
401
  if (statusOf(err) === 404)
402
402
  throw new Error(`Gitea rejected the dependency of issue #${issue.number} on issue #${blocker.number} ` +
@@ -406,7 +406,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
406
406
  }
407
407
  for (const blocker of current)
408
408
  if (!want.has(blocker.number))
409
- await found(this.call(loc, this.gt.repos.issueRemoveIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker))));
409
+ await found(this.call(loc, this.gt.repos.issueRemoveIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker), { type: ContentType.Json })));
410
410
  }
411
411
  /* synchronize the comments with the attachments, touching changed ones only
412
412
  (see the common issue tracker parts) */
@@ -19,6 +19,7 @@
19
19
  \[`--lifecycle` *model*\]
20
20
  \[`--serial`\]
21
21
  \[`--unclaimed`\]
22
+ \[`-M`|`--model` *model*\]
22
23
  \[`--params` *name*`=`*value*\]
23
24
  \[`--state-`*item* *state*\]
24
25
  \[`--tag-`*item* *tag*\]
@@ -169,12 +170,12 @@ tags (except the request state tag) and, by default, gets its previous
169
170
  assignee back. An agent consumes the request prompts if its prompt
170
171
  template references `{prompt}` (except within the conditional
171
172
  `{?prompt:`*text*`}` only, which makes the prompt *optional*) or it
172
- has a hook `prepare`, `prompt`, or `finish` (which receive the request
173
- prompt in their *job*), where it then refuses a request with an empty
174
- prompt as failed, else it accepts requests with an empty prompt (which
175
- an agent with an optional prompt still passes on if non-empty), and
176
- announces this to the agent relay, so the task boards accept an empty
177
- prompt for its requests, too.
173
+ has a hook `prepare`, `prompt`, `model`, or `finish` (which receive
174
+ the request prompt in their *job*), where it then refuses a request
175
+ with an empty prompt as failed, else it accepts requests with an empty
176
+ prompt (which an agent with an optional prompt still passes on if
177
+ non-empty), and announces this to the agent relay, so the task boards
178
+ accept an empty prompt for its requests, too.
178
179
 
179
180
  Only the prompt (and `--state-incoming` for `--type phase`) is
180
181
  mandatory, while all other options default to the safe *annotate*
@@ -431,6 +432,16 @@ and `--prompt-skill` with `--prompt-args`, and `--tag-retry` and
431
432
  `passes`, and `--workdir changeset` resp. `target` (as the task can
432
433
  be claimed by another agent meanwhile).
433
434
 
435
+ - \[`-M`|`--model` *model*\]:
436
+ The model of the agent tool, passed to it as `--model` *model*
437
+ verbatim (e.g., an alias like `sonnet` or a full model name), either a
438
+ *model* for all tasks, or comma-separated *size*`=`*model* pairs per
439
+ `Effort:` size of the task (`XS`, `S`, `M`, `L`, `XL`, or `*` for all
440
+ other tasks, e.g., `XS=haiku,S=haiku,M=sonnet,*=opus`), where a task
441
+ without a matching pair gets the default model of the agent tool (the
442
+ default for all tasks). The hook `model` can override it per task (see
443
+ **HOOKS**).
444
+
434
445
  - \[`--params` *name*`=`*value*\]:
435
446
  (repeatable) The value of the parameter *name* declared by the
436
447
  definition (see **PARAMETERS**), where a later value of the same
@@ -986,6 +997,14 @@ invocation:
986
997
  `{ text }`, a plain prompt text, or `undefined` to keep the
987
998
  configured one, where a thrown error fails the processing.
988
999
 
1000
+ - `model(task, model, job, ctx)`:
1001
+ The model of the agent tool for the job, given the configured *model*
1002
+ of `--model` for the `Effort:` size of the task (empty for the default
1003
+ model of the agent tool): a model (empty for the default one), or
1004
+ `undefined` to keep the configured one, where a thrown error fails the
1005
+ processing. This allows choosing the model per task by any means,
1006
+ e.g., by asking a cheap model via `ctx.exec`.
1007
+
989
1008
  - `verdict(task, result, ctx)`:
990
1009
  (after a successful agent tool only) The verdict of the result (an
991
1010
  upper-case word, accepted only if listed by `--verdict-allow`), an
@@ -9,6 +9,7 @@
9
9
  \[`-T`|`--timeout` *duration*\]
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
+ \[`-M`|`--model` *model*\]
12
13
  \[`--`*group*`-`*item* *value* \[...\]\]
13
14
  \[`-f`|`--foreground`\]
14
15
 
@@ -145,6 +146,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
145
146
  (`phase` only). Built-in: not set. Not allowed, as the condenser
146
147
  claims the tasks into its activity state.
147
148
 
149
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
150
+ for all tasks, or comma-separated *size*`=`*model* pairs per
151
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
152
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
153
+ model of the agent tool. Built-in: none.
154
+
148
155
  - \[`--params` *name*`=`*value*\]: The value of a parameter
149
156
  declared by the definition (repeatable). Built-in: none declared.
150
157
 
@@ -11,6 +11,7 @@
11
11
  \[`--agent-relay` *url*\]
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
+ \[`-M`|`--model` *model*\]
14
15
  \[`--`*group*`-`*item* *value* \[...\]\]
15
16
  \[`-f`|`--foreground`\]
16
17
 
@@ -192,6 +193,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
192
193
  (`phase` only). Built-in: not set. Not allowed, as the editor is
193
194
  of type `request`.
194
195
 
196
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
197
+ for all tasks, or comma-separated *size*`=`*model* pairs per
198
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
199
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
200
+ model of the agent tool. Built-in: none.
201
+
195
202
  - \[`--params` *name*`=`*value*\]: The value of a parameter
196
203
  declared by the definition (repeatable). Built-in: none declared.
197
204
 
@@ -10,6 +10,7 @@
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
12
  \[`-m`|`--mode` `phase`|`request`\]
13
+ \[`-M`|`--model` *model*\]
13
14
  \[`--`*group*`-`*item* *value* \[...\]\]
14
15
  \[`-f`|`--foreground`\]
15
16
 
@@ -204,6 +205,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
204
205
  (`phase` only). Built-in: enabled (mode `phase`) resp. not set
205
206
  (mode `request`).
206
207
 
208
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
209
+ for all tasks, or comma-separated *size*`=`*model* pairs per
210
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
211
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
212
+ model of the agent tool. Built-in: none.
213
+
207
214
  - \[`--params` *name*`=`*value*\]: The value of a parameter
208
215
  declared by the definition (repeatable). Built-in: none declared.
209
216
 
@@ -79,7 +79,7 @@ members) or `ase agent fleet stop`.
79
79
  (with all its options commented out, so they fall back to
80
80
  `project.relay.url`, `project.relay.crt`, and `project.relay.key`),
81
81
  the section `default` (with `tool`, `parallel: 1`, and `timeout: 60m`,
82
- plus `repo` and `tempdir` commented out), the edit request agent
82
+ plus `model`, `repo`, and `tempdir` commented out), the edit request agent
83
83
  `editor`, the effort estimation agent `estimator` (mode `phase`,
84
84
  estimating the planned tasks, i.e., the `OPEN` tasks for `solo`, the
85
85
  `PLANNING` tasks for `team`, and the `PLANNED` tasks for `enterprise`,
@@ -201,8 +201,9 @@ value omits an option, and a list repeats an option:
201
201
  `project.relay.url` selecting HTTPS requires both.
202
202
 
203
203
  - `default`:
204
- (optional) The default options of all agents: `tool`, `parallel`,
205
- `timeout`, `repo`, `tempdir`, and `agent-relay` (see `ase-agent`(1)),
204
+ (optional) The default options of all agents: `tool`, `model`,
205
+ `parallel`, `timeout`, `repo`, `tempdir`, and `agent-relay` (see
206
+ `ase-agent`(1) and `ase-agent-adhoc`(1)),
206
207
  each one applied only to the agents supporting it (e.g., `repo` not to
207
208
  `rebooter`), and overridden by the section of an agent. The option
208
209
  `agent-relay` is not allowed together with the section `relay`.
@@ -315,7 +316,8 @@ $ ase agent fleet stop
315
316
  ```
316
317
 
317
318
  A configuration `.ase/fleet.yaml` with the agent relay, the built-in
318
- agents, two editors of different agent tools, an agent of the
319
+ agents (with an implementer using cheaper models for smaller tasks),
320
+ two editors of different agent tools, an agent of the
319
321
  definition file `.ase/agents/reviewer.mts`, and an ad-hoc request
320
322
  agent:
321
323
 
@@ -329,6 +331,7 @@ default:
329
331
  rebooter:
330
332
  implementer:
331
333
  parallel: 2
334
+ model: XS=haiku,S=haiku,*=sonnet
332
335
  editor-claude:
333
336
  kind: editor
334
337
  editor-codex:
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `implement`|`draft`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -260,6 +261,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
260
261
  (`phase` only). Built-in: not set. Not allowed, as the implementer
261
262
  claims the tasks into its activity state.
262
263
 
264
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
265
+ for all tasks, or comma-separated *size*`=`*model* pairs per
266
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
267
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
268
+ model of the agent tool. Built-in: none.
269
+
263
270
  - \[`--params` *name*`=`*value*\]: The value of a parameter
264
271
  declared by the definition (repeatable). Built-in: `effort-wait`
265
272
  (`true` or `false`, default: `true`), whether to wait for the
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `merge`|`rebase`|`squash`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -206,6 +207,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
206
207
  (`phase` only). Built-in: not set. Not allowed, as the integrator
207
208
  claims the tasks into its activity state.
208
209
 
210
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
211
+ for all tasks, or comma-separated *size*`=`*model* pairs per
212
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
213
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
214
+ model of the agent tool. Built-in: none.
215
+
209
216
  - \[`--params` *name*`=`*value*\]: The value of a parameter
210
217
  declared by the definition (repeatable). Built-in: none declared.
211
218
 
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `judge`|`qualify`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -187,6 +188,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
187
188
  (`phase` only). Built-in: not set. Not allowed, as the qualifier
188
189
  claims the tasks into its activity state.
189
190
 
191
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
192
+ for all tasks, or comma-separated *size*`=`*model* pairs per
193
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
194
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
195
+ model of the agent tool. Built-in: none.
196
+
190
197
  - \[`--params` *name*`=`*value*\]: The value of a parameter
191
198
  declared by the definition (repeatable). Built-in: none declared.
192
199
 
@@ -9,6 +9,7 @@
9
9
  \[`-T`|`--timeout` *duration*\]
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
+ \[`-M`|`--model` *model*\]
12
13
  \[`--`*group*`-`*item* *value* \[...\]\]
13
14
  \[`-f`|`--foreground`\]
14
15
 
@@ -145,6 +146,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
145
146
  (`phase` only). Built-in: not set. Not allowed, as the rebooter
146
147
  claims the tasks into its activity state.
147
148
 
149
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
150
+ for all tasks, or comma-separated *size*`=`*model* pairs per
151
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
152
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
153
+ model of the agent tool. Built-in: none.
154
+
148
155
  - \[`--params` *name*`=`*value*\]: The value of a parameter
149
156
  declared by the definition (repeatable). Built-in: none declared.
150
157
 
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `phase`|`request`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -246,6 +247,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
246
247
  - \[`--unclaimed`\]: Process the tasks of the incoming states
247
248
  without claiming them (`phase` only). Built-in: not set.
248
249
 
250
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
251
+ for all tasks, or comma-separated *size*`=`*model* pairs per
252
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
253
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
254
+ model of the agent tool. Built-in: none.
255
+
249
256
  - \[`--params` *name*`=`*value*\]: The value of a parameter
250
257
  declared by the definition (repeatable). Built-in: none declared.
251
258
 
@@ -63,7 +63,8 @@ file lock and atomically.
63
63
  `project.task.effort.line` `0.025`,
64
64
  `project.task.effort.iteration` `2.5`, the full set of
65
65
  `project.artifact.*` globs, the `board.tui.color.*`,
66
- `board.web.color.*`, and `board.web.editor.keymap` defaults, and
66
+ `board.web.color.*`, `board.web.editor.keymap`, and
67
+ `board.web.theme` defaults, and
67
68
  `telemetry.enabled` `false`, `telemetry.prices.ai`
68
69
  `*=4.00/20.00/0.20/5.00`, and `telemetry.prices.hr` `*=110`.
69
70
  - `vibe`:
@@ -361,6 +361,11 @@ Task board (all writable on the `user` and `project` scopes only, see
361
361
  The key bindings of the task plan editor of the web board (default:
362
362
  `default`).
363
363
 
364
+ - `board.web.theme` (`auto`, `light`, or `dark`):
365
+ The color theme of the web board, where `auto` follows the light or
366
+ dark preference of the operating system resp. browser (default:
367
+ `auto`).
368
+
364
369
  ## ENVIRONMENT
365
370
 
366
371
  - `EDITOR`, `VISUAL`:
@@ -7,6 +7,7 @@
7
7
  \[`-w`|`--web`\]
8
8
  \[`-t`|`--text`\]
9
9
  \[`--agent-relay` *url*\]
10
+ \[`-T`|`--task` *id*\]
10
11
 
11
12
  ## DESCRIPTION
12
13
 
@@ -89,6 +90,11 @@ set snapshot `kind="changeset"` of the `implementer` and `integrator`
89
90
  agents) is colored per line, YAML and JSON data are syntax-highlighted,
90
91
  and a change set snapshot older than the plan is marked `(stale)`.
91
92
 
93
+ While the task dialog is open, the last change of the shown plan is displayed
94
+ as a diff in the plan tab, with the removed and added lines colored (in the
95
+ terminal board also signed) and their changed words shown inverse, until `#` clears it or a next change
96
+ replaces it. Closing the task dialog or switching to another task drops it.
97
+
92
98
  A task is moved into another state (changing its `Status:`) either by
93
99
  picking it up with `SPACE` and dropping it with a second `SPACE` onto
94
100
  the selected lane (only lanes whose state is reachable from the current
@@ -214,6 +220,26 @@ waiting) with their status and their last processing log line, and
214
220
  finally all warnings of the board (behind `⚠`, e.g. on dependency
215
221
  cycles).
216
222
 
223
+ ### Task Viewer
224
+
225
+ With `--task`, the terminal and the web board act as a *task viewer*:
226
+ instead of the board, they show only the task dialog of the given task,
227
+ spanning the entire window and following all changes of the task live.
228
+ All actions of the task dialog stay available, and a click onto a
229
+ predecessor or successor shows that task instead. The shown view of the
230
+ board stays untouched. An unknown task is refused with an error.
231
+
232
+ The terminal board quits on `q` or `Ctrl`+`C` only, and with a warning
233
+ once the shown task no longer exists (e.g. as it was deleted or renamed
234
+ elsewhere).
235
+
236
+ The web board has no closing keys, as a page cannot close its browser
237
+ tab. Once the shown task no longer exists, it shows only a notice, and
238
+ shows the task again as soon as it exists again.
239
+
240
+ With `--text`, and also without an interactive terminal, the plan of the
241
+ task is printed as plain text and the command exits.
242
+
217
243
  ### Statistics View
218
244
 
219
245
  The third view *statistics* shows the statistics of the tasks matching the
@@ -410,6 +436,10 @@ relay, the agent graph view is skipped.
410
436
  board always follows the configured `project.relay.url` (and hence
411
437
  warns about the option).
412
438
 
439
+ - \[`-T`|`--task` *id*\]:
440
+ Show only the task dialog of the task *id* instead of the board, as a
441
+ viewer and watcher of the task (see **Task Viewer**).
442
+
413
443
  ## KEYS
414
444
 
415
445
  The following keys are supported by the terminal board (and, unless
@@ -426,6 +456,9 @@ letter under the kitty keyboard protocol.
426
456
  views).
427
457
  - `?`: Hide or show the key hint lines (persisted); while hidden, the
428
458
  information line ends with `?: show key hints`.
459
+ - `L`: Cycle the color theme through `auto`, `light`, and `dark`,
460
+ overriding `board.web.theme` in the current browser until cycled back
461
+ to its value (web board only).
429
462
  - `D`: Delete the selected task, after a confirmation (not in the
430
463
  agent logs and statistics views).
431
464
  - `T`: Transfer the selected task to another state via the transfer
@@ -515,7 +548,8 @@ letter under the kitty keyboard protocol.
515
548
  - `↑`/`↓`: Scroll the content.
516
549
  - `PgUp`/`PgDn`: Scroll the content by one page.
517
550
  - `e`: Edit the task plan.
518
- - `D`, `T`, `A`, `E`, `R`, `M`: As in every view.
551
+ - `#`: Clear the diff of the last change of the task plan.
552
+ - `D`, `T`, `A`, `E`, `R`, `M`, `L`: As in every view.
519
553
  - `⏎`, `ESC`: Close the task dialog.
520
554
 
521
555
  ### Filter Field
@@ -594,6 +628,11 @@ The following configuration keys (on scope `user` or `project`, see
594
628
  The key bindings of the task plan editor of the web board: `default`,
595
629
  `vim`, or `emacs` (default: `default`).
596
630
 
631
+ - `board.web.theme`:
632
+ The color theme of the web board: `light`, `dark`, or `auto`, which
633
+ follows the light or dark preference of the operating system resp.
634
+ browser (default: `auto`).
635
+
597
636
  - `project.relay.url`:
598
637
  The default agent relay of the boards (see `--agent-relay`).
599
638
 
@@ -625,7 +664,7 @@ The following configuration keys (on scope `user` or `project`, see
625
664
  The command exits with status `0` once the terminal board is quit, the
626
665
  text overview is printed, resp. the web board is opened, and with
627
666
  status `1` on any error (e.g. an *ASE* service without web board on
628
- `--web`).
667
+ `--web`, or an unknown task on `--task`).
629
668
 
630
669
  ## EXAMPLES
631
670
 
@@ -636,6 +675,9 @@ $ ase task board
636
675
  # show the web board in the browser
637
676
  $ ase task board --web
638
677
 
678
+ # view and watch a single task in the terminal
679
+ $ ase task board --task T1
680
+
639
681
  # print the lane overview, including the agents of a remote agent relay
640
682
  $ ase task board --text --agent-relay ase://relay.example.com:42043
641
683
  ```