@rse/ase 2.0.2 → 2.0.4
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 +13 -13
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +4 -5
- package/dst/ase-lib-version.js +14 -5
- package/dst/ase-man-catalog.js +229 -0
- package/dst/ase-man-render.js +278 -0
- package/dst/ase-man-tui.js +636 -0
- package/dst/ase-man.js +33 -27
- 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 +4 -5
- package/dst/ase-spec.js +103 -20
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-filter.js +5 -4
- 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 +27 -27
- 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.md +1165 -0
- package/dst/man/ase-agent-condenser.md +383 -0
- package/dst/man/ase-agent-editor.md +441 -0
- package/dst/man/ase-agent-estimator.md +477 -0
- package/dst/man/ase-agent-fleet.md +358 -0
- package/dst/man/ase-agent-implementer.md +521 -0
- package/dst/man/ase-agent-integrator.md +449 -0
- package/dst/man/ase-agent-qualifier.md +426 -0
- package/dst/man/ase-agent-rebooter.md +384 -0
- package/dst/man/ase-agent-relay.md +262 -0
- package/dst/man/ase-agent-researcher.md +478 -0
- package/dst/man/ase-agent.md +628 -0
- package/dst/man/ase-artifact-list.md +57 -0
- package/dst/man/ase-artifact-name.md +56 -0
- package/dst/man/ase-artifact.md +92 -0
- package/dst/man/ase-config-delete.md +74 -0
- package/dst/man/ase-config-edit.md +83 -0
- package/dst/man/ase-config-get.md +70 -0
- package/dst/man/ase-config-init.md +110 -0
- package/dst/man/ase-config-list.md +68 -0
- package/dst/man/ase-config-set.md +92 -0
- package/dst/man/ase-config.md +426 -0
- package/dst/man/ase-hook-permission-request.md +61 -0
- package/dst/man/ase-hook-pre-tool-use.md +96 -0
- package/dst/man/ase-hook-session-end.md +71 -0
- package/dst/man/ase-hook-session-persona.md +65 -0
- package/dst/man/ase-hook-session-start.md +184 -0
- package/dst/man/ase-hook-stop.md +52 -0
- package/dst/man/ase-hook-user-prompt-submit.md +54 -0
- package/dst/man/ase-hook.md +93 -0
- package/dst/man/ase-man.md +195 -0
- package/dst/man/ase-mcp.md +102 -0
- package/dst/man/ase-service-log.md +59 -0
- package/dst/man/ase-service-send.md +60 -0
- package/dst/man/ase-service-start.md +93 -0
- package/dst/man/ase-service-status.md +58 -0
- package/dst/man/ase-service-stop.md +52 -0
- package/dst/man/ase-service.md +124 -0
- package/dst/man/ase-setup-disable.md +67 -0
- package/dst/man/ase-setup-enable.md +66 -0
- package/dst/man/ase-setup-install.md +116 -0
- package/dst/man/ase-setup-mcp.md +213 -0
- package/dst/man/ase-setup-status.md +101 -0
- package/dst/man/ase-setup-statusline.md +162 -0
- package/dst/man/ase-setup-telemetry.md +166 -0
- package/dst/man/ase-setup-uninstall.md +85 -0
- package/dst/man/ase-setup-update.md +117 -0
- package/dst/man/ase-setup.md +156 -0
- package/dst/man/ase-spec-export.md +99 -0
- package/dst/man/ase-spec-init.md +57 -0
- package/dst/man/ase-spec-lint.md +74 -0
- package/dst/man/ase-spec-preview.md +57 -0
- package/dst/man/ase-spec.md +85 -0
- package/dst/man/ase-statusline.md +330 -0
- package/dst/man/ase-task-assign.md +138 -0
- package/dst/man/ase-task-board.md +651 -0
- package/dst/man/ase-task-delete.md +46 -0
- package/dst/man/ase-task-edit.md +73 -0
- package/dst/man/ase-task-idscheme.md +92 -0
- package/dst/man/ase-task-lifecycle.md +92 -0
- package/dst/man/ase-task-list.md +86 -0
- package/dst/man/ase-task-load.md +48 -0
- package/dst/man/ase-task-newid.md +100 -0
- package/dst/man/ase-task-purge.md +49 -0
- package/dst/man/ase-task-rename.md +69 -0
- package/dst/man/ase-task-request.md +108 -0
- package/dst/man/ase-task-save.md +78 -0
- package/dst/man/ase-task-status.md +79 -0
- package/dst/man/ase-task-store.md +377 -0
- package/dst/man/ase-task-view.md +50 -0
- package/dst/man/ase-task.md +335 -0
- package/dst/man/ase-util-compat.md +38 -0
- package/dst/man/ase-util-diagram.md +140 -0
- package/dst/man/ase-util-meta.md +68 -0
- package/dst/man/ase-util-metric.md +74 -0
- package/dst/man/ase-util-mint.md +85 -0
- package/dst/man/ase-util-worktree.md +109 -0
- package/dst/man/ase-util.md +81 -0
- package/dst/man/ase.md +228 -0
- package/package.json +12 -17
- 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/agents/ase-code-lint.md +3 -3
- package/plugin/agents/ase-docs-proofread.md +3 -3
- package/plugin/agents/ase-docs-refine.md +3 -3
- package/plugin/agents/ase-spec-lint.md +338 -0
- package/plugin/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +10 -7
- package/plugin/mods/claude-code.d.ts +26 -3
- 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 +204 -13
- package/plugin/skills/ase-help-skill/catalog.md +1 -0
- package/plugin/skills/ase-meta-workflow/workflow.txt +2 -0
- package/plugin/skills/ase-repo-commit/SKILL.md +1 -0
- package/plugin/skills/ase-repo-merge/SKILL.md +1 -0
- 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 +3 -3
- package/plugin/skills/ase-spec-edit/help.md +1 -1
- package/plugin/skills/ase-spec-lint/SKILL.md +695 -0
- package/plugin/skills/ase-spec-lint/help.md +171 -0
- package/plugin/skills/ase-task-implement/SKILL.md +24 -6
- package/plugin/skills/ase-task-implement/help.md +11 -1
- package/dst/man/ase-agent-adhoc.1 +0 -515
- package/dst/man/ase-agent-condenser.1 +0 -193
- package/dst/man/ase-agent-editor.1 +0 -212
- package/dst/man/ase-agent-estimator.1 +0 -214
- package/dst/man/ase-agent-fleet.1 +0 -185
- package/dst/man/ase-agent-implementer.1 +0 -233
- package/dst/man/ase-agent-integrator.1 +0 -213
- package/dst/man/ase-agent-qualifier.1 +0 -214
- package/dst/man/ase-agent-rebooter.1 +0 -193
- package/dst/man/ase-agent-relay.1 +0 -128
- package/dst/man/ase-agent-researcher.1 +0 -217
- package/dst/man/ase-agent.1 +0 -264
- package/dst/man/ase-artifact-list.1 +0 -38
- package/dst/man/ase-artifact-name.1 +0 -39
- package/dst/man/ase-artifact.1 +0 -56
- package/dst/man/ase-config-delete.1 +0 -45
- package/dst/man/ase-config-edit.1 +0 -48
- package/dst/man/ase-config-get.1 +0 -46
- package/dst/man/ase-config-init.1 +0 -60
- package/dst/man/ase-config-list.1 +0 -42
- package/dst/man/ase-config-set.1 +0 -52
- package/dst/man/ase-config.1 +0 -231
- package/dst/man/ase-hook-permission-request.1 +0 -36
- package/dst/man/ase-hook-pre-tool-use.1 +0 -59
- package/dst/man/ase-hook-session-end.1 +0 -47
- package/dst/man/ase-hook-session-persona.1 +0 -37
- package/dst/man/ase-hook-session-start.1 +0 -81
- package/dst/man/ase-hook-stop.1 +0 -34
- package/dst/man/ase-hook-user-prompt-submit.1 +0 -34
- package/dst/man/ase-hook.1 +0 -51
- package/dst/man/ase-man.1 +0 -67
- package/dst/man/ase-mcp.1 +0 -61
- package/dst/man/ase-service-log.1 +0 -43
- package/dst/man/ase-service-send.1 +0 -47
- package/dst/man/ase-service-start.1 +0 -54
- package/dst/man/ase-service-status.1 +0 -46
- package/dst/man/ase-service-stop.1 +0 -37
- package/dst/man/ase-service.1 +0 -85
- package/dst/man/ase-setup-disable.1 +0 -44
- package/dst/man/ase-setup-enable.1 +0 -45
- package/dst/man/ase-setup-install.1 +0 -73
- package/dst/man/ase-setup-mcp.1 +0 -124
- package/dst/man/ase-setup-status.1 +0 -60
- package/dst/man/ase-setup-statusline.1 +0 -92
- package/dst/man/ase-setup-telemetry.1 +0 -92
- package/dst/man/ase-setup-uninstall.1 +0 -59
- package/dst/man/ase-setup-update.1 +0 -64
- package/dst/man/ase-setup.1 +0 -93
- package/dst/man/ase-spec-export.1 +0 -62
- package/dst/man/ase-spec-lint.1 +0 -40
- package/dst/man/ase-spec-preview.1 +0 -40
- package/dst/man/ase-spec.1 +0 -46
- package/dst/man/ase-statusline.1 +0 -189
- package/dst/man/ase-task-assign.1 +0 -71
- package/dst/man/ase-task-board.1 +0 -384
- package/dst/man/ase-task-delete.1 +0 -35
- package/dst/man/ase-task-edit.1 +0 -46
- package/dst/man/ase-task-idscheme.1 +0 -65
- package/dst/man/ase-task-lifecycle.1 +0 -57
- package/dst/man/ase-task-list.1 +0 -47
- package/dst/man/ase-task-load.1 +0 -36
- package/dst/man/ase-task-newid.1 +0 -61
- package/dst/man/ase-task-purge.1 +0 -37
- package/dst/man/ase-task-rename.1 +0 -44
- package/dst/man/ase-task-request.1 +0 -61
- package/dst/man/ase-task-save.1 +0 -48
- package/dst/man/ase-task-status.1 +0 -55
- package/dst/man/ase-task-store.1 +0 -194
- package/dst/man/ase-task-view.1 +0 -40
- package/dst/man/ase-task.1 +0 -160
- package/dst/man/ase-util-compat.1 +0 -25
- package/dst/man/ase-util-diagram.1 +0 -76
- package/dst/man/ase-util-meta.1 +0 -45
- package/dst/man/ase-util-metric.1 +0 -54
- package/dst/man/ase-util-mint.1 +0 -59
- package/dst/man/ase-util-worktree.1 +0 -72
- package/dst/man/ase-util.1 +0 -50
- package/dst/man/ase.1 +0 -124
|
@@ -192,7 +192,7 @@ const attachmentDocument = async (log, id, n) => {
|
|
|
192
192
|
try {
|
|
193
193
|
text = new TextDecoder("utf-8", { fatal: true }).decode(a.content);
|
|
194
194
|
}
|
|
195
|
-
catch
|
|
195
|
+
catch {
|
|
196
196
|
text = "\0";
|
|
197
197
|
}
|
|
198
198
|
if (text.includes("\0"))
|
|
@@ -364,7 +364,7 @@ const registerPageRoutes = (server, log) => {
|
|
|
364
364
|
};
|
|
365
365
|
/* the filter query of a request (its optional "filter" query parameter) */
|
|
366
366
|
const filterQuery = (request) => typeof request.query.filter === "string" ? request.query.filter : "";
|
|
367
|
-
/* register the view routes of the web board: the board, the
|
|
367
|
+
/* register the view routes of the web board: the board, the task and agent graphs, the statistics, the new task text, and the tasks with their attachments */
|
|
368
368
|
const registerViewRoutes = (server, log) => {
|
|
369
369
|
/* the board model, reduced onto the tasks matching the filter query */
|
|
370
370
|
server.route({
|
package/dst/ase-task-board.js
CHANGED
|
@@ -78,7 +78,7 @@ export default class TaskBoardCommand {
|
|
|
78
78
|
"(restart it with \"ase service stop\" once no agent session uses it)");
|
|
79
79
|
return port;
|
|
80
80
|
}
|
|
81
|
-
/* open
|
|
81
|
+
/* open a URL in the default browser of the platform, waiting only for
|
|
82
82
|
the opener to spawn, as e.g. "xdg-open" can stay attached to the browser */
|
|
83
83
|
async openBrowser(url) {
|
|
84
84
|
const [cmd, ...args] = process.platform === "darwin" ? ["open", url] :
|
package/dst/ase-task-format.js
CHANGED
|
@@ -745,7 +745,7 @@ export const bindEffort = (plan) => {
|
|
|
745
745
|
if (effort !== null && effort.hash === "")
|
|
746
746
|
plan.header.Effort = `${effort.size} ${hash}`;
|
|
747
747
|
else if (effort === null || effort.hash !== hash)
|
|
748
|
-
|
|
748
|
+
delete plan.header.Effort;
|
|
749
749
|
return plan;
|
|
750
750
|
};
|
|
751
751
|
/* derive the title of a task plan from its body as defined by the
|
|
@@ -58,6 +58,7 @@ export class LocalTaskStoreClient {
|
|
|
58
58
|
this.configure("project.task.idscheme", spec);
|
|
59
59
|
this.idscheme = (await this.core.projectSet(this.prjId, { idscheme: spec })).project.idscheme;
|
|
60
60
|
}
|
|
61
|
+
/* run an operation, yielding a fallback for a missing (404) task plan */
|
|
61
62
|
async missing(op, fallback) {
|
|
62
63
|
try {
|
|
63
64
|
return await op();
|
|
@@ -68,6 +69,7 @@ export class LocalTaskStoreClient {
|
|
|
68
69
|
throw err;
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
/* open the storage delegate shared by all clients of the store and register the project */
|
|
71
73
|
async open() {
|
|
72
74
|
let entry = LocalTaskStoreClient.shared.get(this.storage.key);
|
|
73
75
|
if (entry === undefined) {
|
|
@@ -99,6 +101,7 @@ export class LocalTaskStoreClient {
|
|
|
99
101
|
throw err;
|
|
100
102
|
}
|
|
101
103
|
}
|
|
104
|
+
/* release the shared storage delegate, closing it with its last client */
|
|
102
105
|
async close() {
|
|
103
106
|
const entry = this.entry;
|
|
104
107
|
if (entry === undefined)
|
|
@@ -107,7 +110,9 @@ export class LocalTaskStoreClient {
|
|
|
107
110
|
if (--entry.refs === 0) {
|
|
108
111
|
if (LocalTaskStoreClient.shared.get(this.storage.key) === entry)
|
|
109
112
|
LocalTaskStoreClient.shared.delete(this.storage.key);
|
|
110
|
-
await entry.opened.then((opened) => opened.store.close(), () => {
|
|
113
|
+
await entry.opened.then((opened) => opened.store.close(), () => {
|
|
114
|
+
/* a failed opening was already raised to its opener */
|
|
115
|
+
});
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
list(fields) {
|
|
@@ -153,11 +158,13 @@ export class LocalTaskStoreClient {
|
|
|
153
158
|
onChange();
|
|
154
159
|
};
|
|
155
160
|
let stopped = false;
|
|
161
|
+
let started = false;
|
|
156
162
|
const start = async () => {
|
|
157
163
|
await this.open();
|
|
158
164
|
if (stopped)
|
|
159
165
|
await this.close();
|
|
160
166
|
else {
|
|
167
|
+
started = true;
|
|
161
168
|
this.entry?.listeners.add(listener);
|
|
162
169
|
onState?.(true);
|
|
163
170
|
}
|
|
@@ -167,6 +174,8 @@ export class LocalTaskStoreClient {
|
|
|
167
174
|
});
|
|
168
175
|
return () => {
|
|
169
176
|
stopped = true;
|
|
177
|
+
if (!started)
|
|
178
|
+
return;
|
|
170
179
|
this.entry?.listeners.delete(listener);
|
|
171
180
|
this.close().catch(() => { });
|
|
172
181
|
};
|
|
@@ -174,9 +183,10 @@ export class LocalTaskStoreClient {
|
|
|
174
183
|
}
|
|
175
184
|
/* the interval of the keepalive pings of the event subscription (where a connection without
|
|
176
185
|
any traffic for this interval is pinged and without any traffic for twice this interval
|
|
177
|
-
is terminated)
|
|
186
|
+
is terminated), the timeout of its WebSocket handshake, and the timeout of the REST API requests */
|
|
178
187
|
const KEEPALIVE_MS = 15 * 1000;
|
|
179
188
|
const HANDSHAKE_MS = 10 * 1000;
|
|
189
|
+
const REQUEST_MS = 10 * 1000;
|
|
180
190
|
/* the remote client: the REST API of a task store server, with the
|
|
181
191
|
project registered under its configured lifecycle model and task id
|
|
182
192
|
scheme on first use only (afterwards adopting the lifecycle model and
|
|
@@ -231,7 +241,7 @@ export class RemoteTaskStoreClient {
|
|
|
231
241
|
},
|
|
232
242
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
233
243
|
dispatcher: this.dispatcher,
|
|
234
|
-
signal: AbortSignal.timeout(
|
|
244
|
+
signal: AbortSignal.timeout(REQUEST_MS),
|
|
235
245
|
ignoreResponseError: true
|
|
236
246
|
}).catch((err) => this.unreachable(err));
|
|
237
247
|
if (r.status === 404 || tolerated.includes(r.status))
|
|
@@ -402,7 +412,7 @@ export class RemoteTaskStoreClient {
|
|
|
402
412
|
headers: { Authorization: `Bearer ${this.token}` },
|
|
403
413
|
responseType: "arrayBuffer",
|
|
404
414
|
dispatcher: this.dispatcher,
|
|
405
|
-
signal: AbortSignal.timeout(
|
|
415
|
+
signal: AbortSignal.timeout(REQUEST_MS),
|
|
406
416
|
ignoreResponseError: true
|
|
407
417
|
}).catch((err) => this.unreachable(err));
|
|
408
418
|
if (r.status === 404)
|
|
@@ -489,7 +499,9 @@ export class RemoteTaskStoreClient {
|
|
|
489
499
|
if (timer !== null)
|
|
490
500
|
clearTimeout(timer);
|
|
491
501
|
ws?.close();
|
|
492
|
-
this.close().catch(() => {
|
|
502
|
+
this.close().catch(() => {
|
|
503
|
+
/* best-effort release of the dispatcher on unsubscribe */
|
|
504
|
+
});
|
|
493
505
|
};
|
|
494
506
|
}
|
|
495
507
|
}
|
|
@@ -143,7 +143,7 @@ export class TaskStoreCore {
|
|
|
143
143
|
if (effort !== undefined)
|
|
144
144
|
header.Effort = effort;
|
|
145
145
|
else
|
|
146
|
-
|
|
146
|
+
delete header.Effort;
|
|
147
147
|
return header;
|
|
148
148
|
}
|
|
149
149
|
/* load an existing task plan of a registered project */
|
|
@@ -408,7 +408,7 @@ export class TaskStoreCore {
|
|
|
408
408
|
if (lifecycle.states.includes(status) && !states.includes(status))
|
|
409
409
|
continue;
|
|
410
410
|
const att = fields === "usage" ? (await this.store.taskLoad(prjId, entry.id))?.attachment
|
|
411
|
-
.find((
|
|
411
|
+
.find((a) => TaskFormat.isUsage(a) && a.Data !== undefined) : undefined;
|
|
412
412
|
const header = fields === "header" ? await this.boundHeader(prjId, entry) : entry.header;
|
|
413
413
|
result.push({
|
|
414
414
|
id: entry.id,
|
|
@@ -605,12 +605,10 @@ export class TaskStoreCore {
|
|
|
605
605
|
references: referrers.length
|
|
606
606
|
};
|
|
607
607
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
this.emitUpdated(prjId, taskId, plan, lifecycle, ["header"]);
|
|
611
|
-
}
|
|
608
|
+
await this.store.taskSave(prjId, taskId, plan);
|
|
609
|
+
this.emitUpdated(prjId, taskId, plan, lifecycle, ["header"]);
|
|
612
610
|
return {
|
|
613
|
-
id:
|
|
611
|
+
id: taskId,
|
|
614
612
|
status: TaskFormat.taskStatus(plan.header, lifecycle),
|
|
615
613
|
...(status !== null ? { from } : {})
|
|
616
614
|
};
|
|
@@ -235,6 +235,19 @@ export const attachmentSync = (current, wanted) => {
|
|
|
235
235
|
}
|
|
236
236
|
return ops;
|
|
237
237
|
};
|
|
238
|
+
/* reuse the cached promise of a load, else start the load and cache its promise
|
|
239
|
+
(dropped again on failure), so repeated and concurrent loads share one request */
|
|
240
|
+
export const memoize = (cache, key, load) => {
|
|
241
|
+
let promise = cache.get(key);
|
|
242
|
+
if (promise === undefined) {
|
|
243
|
+
promise = load();
|
|
244
|
+
cache.set(key, promise);
|
|
245
|
+
promise.catch(() => {
|
|
246
|
+
cache.delete(key);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return promise;
|
|
250
|
+
};
|
|
238
251
|
/* the common base of the issue tracker storage plugins: the projects mapped onto
|
|
239
252
|
their locations, the storage lifecycle polling for changes while opened and
|
|
240
253
|
observed (without keeping the process alive), and the project listing */
|
|
@@ -261,7 +274,9 @@ export class IssueTaskStoragePlugin {
|
|
|
261
274
|
this.opened = this.now();
|
|
262
275
|
if (this.poll > 0 && this.listener !== null) {
|
|
263
276
|
this.timer = setInterval(() => {
|
|
264
|
-
this.pollAll().catch(() => {
|
|
277
|
+
this.pollAll().catch(() => {
|
|
278
|
+
/* failures are already logged per project */
|
|
279
|
+
});
|
|
265
280
|
}, this.poll * 1000);
|
|
266
281
|
this.timer.unref();
|
|
267
282
|
}
|
|
@@ -143,7 +143,7 @@ class FileTaskStoragePlugin {
|
|
|
143
143
|
doc = parseYAML(text);
|
|
144
144
|
}
|
|
145
145
|
catch (err) {
|
|
146
|
-
this.ctx.log("warning", `malformed project file "${file}" ignored: ${err.message}`);
|
|
146
|
+
this.ctx.log("warning", `malformed project file "${file}" ignored: ${err instanceof Error ? err.message : String(err)}`);
|
|
147
147
|
return { lifecycle: this.lifecycle, idscheme: this.idscheme, seqmark: 0, broken: true };
|
|
148
148
|
}
|
|
149
149
|
return {
|
|
@@ -339,7 +339,9 @@ class FileTaskStoragePlugin {
|
|
|
339
339
|
if (this.watcher !== w)
|
|
340
340
|
return;
|
|
341
341
|
this.watcher = null;
|
|
342
|
-
w.close().catch(() => {
|
|
342
|
+
w.close().catch(() => {
|
|
343
|
+
/* re-arm even if the outdated watcher fails to close */
|
|
344
|
+
}).finally(() => {
|
|
343
345
|
this.arm();
|
|
344
346
|
this.schedule();
|
|
345
347
|
});
|
|
@@ -418,7 +420,9 @@ class FileTaskStoragePlugin {
|
|
|
418
420
|
clearTimeout(this.timer);
|
|
419
421
|
this.timer = setTimeout(() => {
|
|
420
422
|
this.timer = null;
|
|
421
|
-
this.rescan().catch(() => {
|
|
423
|
+
this.rescan().catch(() => {
|
|
424
|
+
/* failures are already logged per directory */
|
|
425
|
+
});
|
|
422
426
|
}, 100);
|
|
423
427
|
this.timer.unref();
|
|
424
428
|
}
|
|
@@ -248,7 +248,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
248
248
|
await found(this.call(loc, this.gt.repos.issueDeleteLabel(loc.owner, loc.repo, label.id)).then(() => true)) : null;
|
|
249
249
|
this.labels.delete(`${loc.owner}/${loc.repo}`);
|
|
250
250
|
this.registry.delete(prjId);
|
|
251
|
-
return
|
|
251
|
+
return result !== null;
|
|
252
252
|
}
|
|
253
253
|
/* ==== issue mapping ==== */
|
|
254
254
|
/* fetch the live issue of an issue number (null if none) */
|
|
@@ -264,15 +264,8 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
264
264
|
const key = `${loc.owner}/${loc.repo}#${issue.number}@${issue.updated_at}`;
|
|
265
265
|
if (fresh)
|
|
266
266
|
this.depends.delete(key);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
depends = found(this.all(loc, (query) => this.gt.repos.issueListIssueDependencies(loc.owner, loc.repo, String(issue.number), query)))
|
|
270
|
-
.then((issues) => (issues ?? []));
|
|
271
|
-
this.depends.set(key, depends);
|
|
272
|
-
depends.catch(() => {
|
|
273
|
-
this.depends.delete(key);
|
|
274
|
-
});
|
|
275
|
-
}
|
|
267
|
+
const depends = TaskIssues.memoize(this.depends, key, () => found(this.all(loc, (query) => this.gt.repos.issueListIssueDependencies(loc.owner, loc.repo, String(issue.number), query)))
|
|
268
|
+
.then((issues) => (issues ?? [])));
|
|
276
269
|
return (await depends).filter((blocker) => inRepo(loc, blocker));
|
|
277
270
|
}
|
|
278
271
|
/* the comments of an issue */
|
|
@@ -323,10 +316,10 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
323
316
|
if (label === undefined) {
|
|
324
317
|
label = await this.call(loc, this.gt.repos.issueCreateLabel(loc.owner, loc.repo, { name, color: name.startsWith("ase:") ? "#c5def5" : "#ededed" })).catch(async (err) => {
|
|
325
318
|
/* tolerate a label created concurrently */
|
|
326
|
-
const
|
|
327
|
-
if (
|
|
319
|
+
const existing = statusOf(err) === 422 ? (await this.labelMap(loc, true)).get(name.toLowerCase()) : undefined;
|
|
320
|
+
if (existing === undefined)
|
|
328
321
|
throw err;
|
|
329
|
-
return
|
|
322
|
+
return existing;
|
|
330
323
|
});
|
|
331
324
|
const map = await this.labelMap(loc);
|
|
332
325
|
map.set(name.toLowerCase(), label);
|
|
@@ -383,15 +376,8 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
383
376
|
if (typeof assignee === "string" && assignee !== "") {
|
|
384
377
|
const logins = issue?.assignees?.map((user) => user.login) ?? [];
|
|
385
378
|
const key = `${loc.owner}/${loc.repo}`;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
users = this.call(loc, this.gt.repos.repoGetAssignees(loc.owner, loc.repo))
|
|
389
|
-
.then((list) => new Set(list.map((user) => (user.login ?? "").toLowerCase())));
|
|
390
|
-
this.assignable.set(key, users);
|
|
391
|
-
users.catch(() => {
|
|
392
|
-
this.assignable.delete(key);
|
|
393
|
-
});
|
|
394
|
-
}
|
|
379
|
+
const users = TaskIssues.memoize(this.assignable, key, () => this.call(loc, this.gt.repos.repoGetAssignees(loc.owner, loc.repo))
|
|
380
|
+
.then((list) => new Set(list.map((user) => (user.login ?? "").toLowerCase()))));
|
|
395
381
|
if (logins.includes(assignee))
|
|
396
382
|
assignees = logins;
|
|
397
383
|
else if ((await users).has(assignee.toLowerCase()))
|
|
@@ -447,11 +433,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
447
433
|
own write or detected change of the issue), as e.g. the boards load the plan
|
|
448
434
|
and its attachments in separate operations */
|
|
449
435
|
load(prjId, loc, n) {
|
|
450
|
-
|
|
451
|
-
const cached = this.loads.get(key);
|
|
452
|
-
if (cached !== undefined)
|
|
453
|
-
return cached;
|
|
454
|
-
const load = (async () => {
|
|
436
|
+
return TaskIssues.memoize(this.loads, `${prjId}#${n}`, async () => {
|
|
455
437
|
const [issue, comments] = await Promise.all([
|
|
456
438
|
this.fetch(loc, n),
|
|
457
439
|
n === 0 ? [] : found(this.comments(loc, n))
|
|
@@ -459,12 +441,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
459
441
|
if (issue === null)
|
|
460
442
|
return null;
|
|
461
443
|
return { issue, after: await this.blockers(loc, issue), comments: comments ?? [] };
|
|
462
|
-
})();
|
|
463
|
-
this.loads.set(key, load);
|
|
464
|
-
load.catch(() => {
|
|
465
|
-
this.loads.delete(key);
|
|
466
444
|
});
|
|
467
|
-
return load;
|
|
468
445
|
}
|
|
469
446
|
/* ==== task plans ==== */
|
|
470
447
|
async taskList(prjId) {
|
|
@@ -73,7 +73,7 @@ class GitHubTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
73
73
|
constructor(ctx) {
|
|
74
74
|
super(ctx, "repositories", now);
|
|
75
75
|
const options = ctx.options;
|
|
76
|
-
const token = options.token ?? process.env.GITHUB_TOKEN
|
|
76
|
+
const token = options.token ?? (process.env.GITHUB_TOKEN || process.env.GH_TOKEN) ?? "";
|
|
77
77
|
if (typeof token !== "string" || token === "")
|
|
78
78
|
throw new Error("task store: plugin \"github\" requires the \"token\" option (or $GITHUB_TOKEN resp. $GH_TOKEN)");
|
|
79
79
|
const url = options.url ?? "https://github.com";
|
|
@@ -122,7 +122,7 @@ class GitHubTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
122
122
|
/* explain a denied access (but not an exceeded rate limit), as the
|
|
123
123
|
GitHub message names neither the token source nor the missing permission */
|
|
124
124
|
const source = options.token !== undefined ? "the \"token\" option (resp. \"project.task.store.token\")" :
|
|
125
|
-
process.env.GITHUB_TOKEN !== undefined ? "$GITHUB_TOKEN" : "$GH_TOKEN";
|
|
125
|
+
process.env.GITHUB_TOKEN !== undefined && process.env.GITHUB_TOKEN !== "" ? "$GITHUB_TOKEN" : "$GH_TOKEN";
|
|
126
126
|
this.gh.hook.error("request", (err, request) => {
|
|
127
127
|
const status = statusOf(err);
|
|
128
128
|
if ((status === 401 || status === 403) && !/rate limit/i.test(err.message)) {
|
|
@@ -462,11 +462,7 @@ class GitHubTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
462
462
|
own write or detected change of the issue), as e.g. the boards load the plan
|
|
463
463
|
and its attachments in separate operations */
|
|
464
464
|
load(prjId, loc, n) {
|
|
465
|
-
|
|
466
|
-
const cached = this.loads.get(key);
|
|
467
|
-
if (cached !== undefined)
|
|
468
|
-
return cached;
|
|
469
|
-
const load = (async () => {
|
|
465
|
+
return TaskIssues.memoize(this.loads, `${prjId}#${n}`, async () => {
|
|
470
466
|
const [issue, comments] = await Promise.all([
|
|
471
467
|
this.fetch(loc, n),
|
|
472
468
|
n === 0 ? [] : found(this.gh.paginate(this.gh.rest.issues.listComments, { ...loc, issue_number: n, per_page: 100 }))
|
|
@@ -474,12 +470,7 @@ class GitHubTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
474
470
|
if (issue === null)
|
|
475
471
|
return null;
|
|
476
472
|
return { issue, after: await this.blockers(loc, issue), comments: comments ?? [] };
|
|
477
|
-
})();
|
|
478
|
-
this.loads.set(key, load);
|
|
479
|
-
load.catch(() => {
|
|
480
|
-
this.loads.delete(key);
|
|
481
473
|
});
|
|
482
|
-
return load;
|
|
483
474
|
}
|
|
484
475
|
/* ==== task plans ==== */
|
|
485
476
|
async taskList(prjId) {
|
|
@@ -240,14 +240,7 @@ class GitLabTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
240
240
|
const key = `${path}#${issue.iid}@${issue.updated_at}`;
|
|
241
241
|
if (fresh)
|
|
242
242
|
this.linked.delete(key);
|
|
243
|
-
|
|
244
|
-
if (links === undefined) {
|
|
245
|
-
links = this.links.all(path, issue.iid).then((data) => data);
|
|
246
|
-
this.linked.set(key, links);
|
|
247
|
-
links.catch(() => {
|
|
248
|
-
this.linked.delete(key);
|
|
249
|
-
});
|
|
250
|
-
}
|
|
243
|
+
const links = TaskIssues.memoize(this.linked, key, () => this.links.all(path, issue.iid).then((data) => data));
|
|
251
244
|
return (await links).filter((link) => link.link_type === "is_blocked_by" && link.project_id === issue.project_id);
|
|
252
245
|
}
|
|
253
246
|
/* the (non-system) notes of an issue */
|
|
@@ -451,11 +444,7 @@ class GitLabTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
451
444
|
own write or detected change of the issue), as e.g. the boards load the plan
|
|
452
445
|
and its attachments in separate operations */
|
|
453
446
|
load(prjId, path, n) {
|
|
454
|
-
|
|
455
|
-
const cached = this.loads.get(key);
|
|
456
|
-
if (cached !== undefined)
|
|
457
|
-
return cached;
|
|
458
|
-
const load = (async () => {
|
|
447
|
+
return TaskIssues.memoize(this.loads, `${prjId}#${n}`, async () => {
|
|
459
448
|
const [issue, notes] = await Promise.all([
|
|
460
449
|
this.fetch(path, n),
|
|
461
450
|
n === 0 ? [] : found(this.comments(path, n))
|
|
@@ -463,12 +452,7 @@ class GitLabTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
|
|
|
463
452
|
if (issue === null)
|
|
464
453
|
return null;
|
|
465
454
|
return { issue, after: await this.blockers(path, issue), notes: notes ?? [] };
|
|
466
|
-
})();
|
|
467
|
-
this.loads.set(key, load);
|
|
468
|
-
load.catch(() => {
|
|
469
|
-
this.loads.delete(key);
|
|
470
455
|
});
|
|
471
|
-
return load;
|
|
472
456
|
}
|
|
473
457
|
/* ==== task plans ==== */
|
|
474
458
|
async taskList(prjId) {
|
package/dst/ase-task.js
CHANGED
|
@@ -341,7 +341,7 @@ export class Task {
|
|
|
341
341
|
try {
|
|
342
342
|
url = new URL(store.value);
|
|
343
343
|
}
|
|
344
|
-
catch
|
|
344
|
+
catch {
|
|
345
345
|
throw unsupported();
|
|
346
346
|
}
|
|
347
347
|
if (url.hostname === "" || url.port === "" || url.username !== "" || url.password !== ""
|
|
@@ -444,21 +444,12 @@ export class Task {
|
|
|
444
444
|
tag: Core.taskTag(plan)
|
|
445
445
|
};
|
|
446
446
|
}
|
|
447
|
-
/* save a task as text under the given id
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
the entity tag of the just-saved plan, and the text of the just-saved plan
|
|
454
|
-
(as sent, re-serialized from its parsed form, i.e., before any
|
|
455
|
-
normalization by the task store, and with its elided attachment
|
|
456
|
-
payloads still elided, while they are restored from the stored
|
|
457
|
-
plan for the save itself); the entity tag
|
|
458
|
-
is the one the task store reports for the plan as stored, and only on a
|
|
459
|
-
task store server predating this report the one of the plan as sent (which
|
|
460
|
-
never is the one of a plan saved by somebody else in the meantime, so a
|
|
461
|
-
conditional save based on it at worst is refused spuriously) */
|
|
447
|
+
/* save a task as text under the given id, throwing on a "Status:" unknown to or not
|
|
448
|
+
reachable in the task lifecycle model, on a conditional save (with an entity tag) a
|
|
449
|
+
problem 412 on a concurrent change, and on a create-only save an error if the task
|
|
450
|
+
exists; elided attachment payloads are restored from the stored plan; returns the
|
|
451
|
+
id scheme warning (else empty), the entity tag of the saved plan (as reported by the
|
|
452
|
+
task store, else of the plan as sent), and the text of the plan as sent */
|
|
462
453
|
static async save(log, id, text, tag, create = false) {
|
|
463
454
|
if (typeof text !== "string")
|
|
464
455
|
throw new Error("task: text must be a string");
|
|
@@ -993,6 +984,17 @@ export default class TaskCommand {
|
|
|
993
984
|
process.exit(0);
|
|
994
985
|
}
|
|
995
986
|
});
|
|
987
|
+
/* register the further CLI sub-commands */
|
|
988
|
+
this.registerRequestCommands(task);
|
|
989
|
+
this.registerPlanCommands(task);
|
|
990
|
+
this.registerProjectCommands(task);
|
|
991
|
+
/* register CLI sub-command group "ase task store" */
|
|
992
|
+
new TaskStoreCommand(this.log).register(task);
|
|
993
|
+
/* provide "ase task" command for further sub-commands */
|
|
994
|
+
return task;
|
|
995
|
+
}
|
|
996
|
+
/* register the CLI sub-commands requesting and assigning a task */
|
|
997
|
+
registerRequestCommands(task) {
|
|
996
998
|
/* register CLI sub-command "ase task request" */
|
|
997
999
|
task
|
|
998
1000
|
.command("request")
|
|
@@ -1049,6 +1051,9 @@ export default class TaskCommand {
|
|
|
1049
1051
|
}
|
|
1050
1052
|
process.exit(0);
|
|
1051
1053
|
});
|
|
1054
|
+
}
|
|
1055
|
+
/* register the CLI sub-commands loading, viewing, editing, saving, deleting, and renaming a task */
|
|
1056
|
+
registerPlanCommands(task) {
|
|
1052
1057
|
/* register CLI sub-command "ase task load" */
|
|
1053
1058
|
task
|
|
1054
1059
|
.command("load")
|
|
@@ -1130,6 +1135,9 @@ export default class TaskCommand {
|
|
|
1130
1135
|
this.log.write("info", `task: no task "${oldId}" to rename`);
|
|
1131
1136
|
process.exit(result !== null ? 0 : 1);
|
|
1132
1137
|
});
|
|
1138
|
+
}
|
|
1139
|
+
/* register the CLI sub-commands of the task ids, the purging, and the project settings */
|
|
1140
|
+
registerProjectCommands(task) {
|
|
1133
1141
|
/* register CLI sub-command "ase task newid" */
|
|
1134
1142
|
task
|
|
1135
1143
|
.command("newid")
|
|
@@ -1197,10 +1205,6 @@ export default class TaskCommand {
|
|
|
1197
1205
|
process.exit(0);
|
|
1198
1206
|
}
|
|
1199
1207
|
});
|
|
1200
|
-
/* register CLI sub-command group "ase task store" */
|
|
1201
|
-
new TaskStoreCommand(this.log).register(task);
|
|
1202
|
-
/* provide "ase task" command for further sub-commands */
|
|
1203
|
-
return task;
|
|
1204
1208
|
}
|
|
1205
1209
|
}
|
|
1206
1210
|
/* render a caught error as an MCP tool error result */
|
|
@@ -1264,9 +1268,8 @@ export class TaskMCP {
|
|
|
1264
1268
|
try {
|
|
1265
1269
|
const verbose = args.verbose ?? false;
|
|
1266
1270
|
const items = await Task.list(this.log);
|
|
1267
|
-
const tasks = verbose ?
|
|
1268
|
-
items.map((
|
|
1269
|
-
items.map((item) => ({ id: item.id, status: item.status, title: item.title }));
|
|
1271
|
+
const tasks = verbose ? items :
|
|
1272
|
+
items.map(({ id, status, title }) => ({ id, status, title }));
|
|
1270
1273
|
const result = { tasks };
|
|
1271
1274
|
return {
|
|
1272
1275
|
structuredContent: result,
|
|
@@ -1277,6 +1280,16 @@ export class TaskMCP {
|
|
|
1277
1280
|
return mcpError(err);
|
|
1278
1281
|
}
|
|
1279
1282
|
});
|
|
1283
|
+
/* register the further MCP tools */
|
|
1284
|
+
this.registerLoadTool(mcp);
|
|
1285
|
+
this.registerSaveTool(mcp);
|
|
1286
|
+
this.registerPlanTools(mcp);
|
|
1287
|
+
this.registerStatusTools(mcp);
|
|
1288
|
+
this.registerAssignTool(mcp);
|
|
1289
|
+
this.registerIdTools(mcp);
|
|
1290
|
+
}
|
|
1291
|
+
/* register the MCP tool loading a task plan */
|
|
1292
|
+
registerLoadTool(mcp) {
|
|
1280
1293
|
/* task load */
|
|
1281
1294
|
mcp.registerTool("ase_task_load", {
|
|
1282
1295
|
title: "ASE task load",
|
|
@@ -1332,6 +1345,9 @@ export class TaskMCP {
|
|
|
1332
1345
|
return mcpError(err);
|
|
1333
1346
|
}
|
|
1334
1347
|
});
|
|
1348
|
+
}
|
|
1349
|
+
/* register the MCP tool saving a task plan */
|
|
1350
|
+
registerSaveTool(mcp) {
|
|
1335
1351
|
/* task save */
|
|
1336
1352
|
mcp.registerTool("ase_task_save", {
|
|
1337
1353
|
title: "ASE task save",
|
|
@@ -1408,6 +1424,9 @@ export class TaskMCP {
|
|
|
1408
1424
|
return mcpError(err);
|
|
1409
1425
|
}
|
|
1410
1426
|
});
|
|
1427
|
+
}
|
|
1428
|
+
/* register the MCP tools reading an attachment of, deleting, and renaming a task plan */
|
|
1429
|
+
registerPlanTools(mcp) {
|
|
1411
1430
|
/* task attachment */
|
|
1412
1431
|
mcp.registerTool("ase_task_attachment", {
|
|
1413
1432
|
title: "ASE task attachment",
|
|
@@ -1482,6 +1501,9 @@ export class TaskMCP {
|
|
|
1482
1501
|
return mcpError(err);
|
|
1483
1502
|
}
|
|
1484
1503
|
});
|
|
1504
|
+
}
|
|
1505
|
+
/* register the MCP tools getting/setting the status of and requesting the processing of a task plan */
|
|
1506
|
+
registerStatusTools(mcp) {
|
|
1485
1507
|
/* task status get/set */
|
|
1486
1508
|
mcp.registerTool("ase_task_status", {
|
|
1487
1509
|
title: "ASE task status get/set",
|
|
@@ -1539,6 +1561,9 @@ export class TaskMCP {
|
|
|
1539
1561
|
return mcpError(err);
|
|
1540
1562
|
}
|
|
1541
1563
|
});
|
|
1564
|
+
}
|
|
1565
|
+
/* register the MCP tool assigning a task plan (with AI usage accounting) */
|
|
1566
|
+
registerAssignTool(mcp) {
|
|
1542
1567
|
/* task assignment (with AI usage accounting) */
|
|
1543
1568
|
mcp.registerTool("ase_task_assign", {
|
|
1544
1569
|
title: "ASE task assignment",
|
|
@@ -1586,6 +1611,9 @@ export class TaskMCP {
|
|
|
1586
1611
|
return mcpError(err);
|
|
1587
1612
|
}
|
|
1588
1613
|
});
|
|
1614
|
+
}
|
|
1615
|
+
/* register the MCP tools generating task ids and getting/setting the active task id */
|
|
1616
|
+
registerIdTools(mcp) {
|
|
1589
1617
|
/* task id generation */
|
|
1590
1618
|
mcp.registerTool("ase_task_newid", {
|
|
1591
1619
|
title: "ASE task id generation",
|
package/dst/ase-util-compat.js
CHANGED
|
@@ -50,7 +50,7 @@ export default class CompatCommand {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
/* MCP tool "ase_compat"
|
|
53
|
+
/* MCP tool "ase_compat" -- lets the skill call this without a Bash tool */
|
|
54
54
|
export class CompatMCP {
|
|
55
55
|
register(mcp) {
|
|
56
56
|
mcp.registerTool("ase_compat", {
|