@rse/ase 1.0.7 → 2.0.0
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-api.d.ts +184 -0
- package/dst/ase-agent-definition-api.js +6 -0
- package/dst/ase-agent-definition-builtin/condenser.js +42 -0
- package/dst/ase-agent-definition-builtin/editor.js +45 -0
- package/dst/ase-agent-definition-builtin/estimator.js +98 -0
- package/dst/ase-agent-definition-builtin/implementer.js +121 -0
- package/dst/ase-agent-definition-builtin/integrator.js +56 -0
- package/dst/ase-agent-definition-builtin/qualifier.js +61 -0
- package/dst/ase-agent-definition-builtin/rebooter.js +41 -0
- package/dst/ase-agent-definition-builtin/researcher.js +77 -0
- package/dst/ase-agent-definition-core.js +827 -0
- package/dst/ase-agent-fleet.js +772 -0
- package/dst/ase-agent-process.js +232 -0
- package/dst/ase-agent-relay-client.js +509 -0
- package/dst/ase-agent-relay-core.js +213 -0
- package/dst/ase-agent-relay-server-bind.js +242 -0
- package/dst/ase-agent-relay-server-cli.js +50 -0
- package/dst/ase-agent-runtime-claim.js +189 -0
- package/dst/ase-agent-runtime-engine.js +1100 -0
- package/dst/ase-agent-runtime-exec.js +275 -0
- package/dst/ase-agent-runtime-phase.js +581 -0
- package/dst/ase-agent-runtime-request.js +272 -0
- package/dst/ase-agent-runtime-tool.js +207 -0
- package/dst/ase-agent-runtime.js +1316 -0
- package/dst/ase-agent.js +65 -0
- package/dst/ase-artifact.js +6 -5
- package/dst/ase-config-core.js +465 -0
- package/dst/ase-config-schema.js +351 -0
- package/dst/ase-config-scope.js +172 -0
- package/dst/ase-config.js +25 -585
- package/dst/ase-hook.js +228 -111
- package/dst/ase-lib-daemon-cli.js +130 -0
- package/dst/ase-lib-hash.js +20 -0
- package/dst/{ase-log.js → ase-lib-log.js} +40 -7
- package/dst/ase-lib-server-bind.js +115 -0
- package/dst/ase-lib-server-cli.js +474 -0
- package/dst/{ase-table.js → ase-lib-table.js} +1 -1
- package/dst/{ase-version.js → ase-lib-version.js} +3 -0
- package/dst/ase-man.js +96 -0
- package/dst/ase-mcp.js +166 -13
- package/dst/{ase-getopt.js → ase-service-getopt.js} +82 -80
- package/dst/{ase-markdown.js → ase-service-markdown.js} +18 -11
- package/dst/ase-service-otel.js +1246 -0
- package/dst/{ase-skills.js → ase-service-skills.js} +29 -64
- package/dst/ase-service-state.js +101 -0
- package/dst/ase-service.js +251 -76
- package/dst/ase-setup-common.js +141 -0
- package/dst/ase-setup-mcp.js +359 -0
- package/dst/ase-setup-settings.js +837 -0
- package/dst/ase-setup.js +113 -869
- package/dst/ase-spec.js +31 -19
- package/dst/ase-statusline.js +108 -43
- package/dst/ase-task-board-core.js +739 -0
- package/dst/ase-task-board-filter.js +127 -0
- package/dst/ase-task-board-flow.js +237 -0
- package/dst/ase-task-board-graph.js +497 -0
- package/dst/ase-task-board-stats.js +263 -0
- package/dst/ase-task-board-tui-control.js +775 -0
- package/dst/ase-task-board-tui-model.js +1008 -0
- package/dst/ase-task-board-tui-popup.js +626 -0
- package/dst/ase-task-board-tui-style.js +106 -0
- package/dst/ase-task-board-tui-view.js +847 -0
- package/dst/ase-task-board-tui.js +189 -0
- package/dst/ase-task-board-web-client.css +1 -0
- package/dst/ase-task-board-web-client.html +20 -0
- package/dst/ase-task-board-web-client.js +81 -0
- package/dst/ase-task-board-web-server.js +823 -0
- package/dst/ase-task-board.js +134 -0
- package/dst/ase-task-format.js +757 -0
- package/dst/ase-task-request.js +208 -0
- package/dst/ase-task-store-client.js +495 -0
- package/dst/ase-task-store-core.js +804 -0
- package/dst/ase-task-store-issues.js +237 -0
- package/dst/ase-task-store-plugin-api.js +6 -0
- package/dst/ase-task-store-plugin-ase.js +484 -0
- package/dst/ase-task-store-plugin-delegate.js +137 -0
- package/dst/ase-task-store-plugin-gitea.js +666 -0
- package/dst/ase-task-store-plugin-github.js +696 -0
- package/dst/ase-task-store-plugin-gitlab.js +668 -0
- package/dst/ase-task-store-server-bind.js +538 -0
- package/dst/ase-task-store-server-cli.js +154 -0
- package/dst/ase-task.js +1335 -357
- package/dst/{ase-compat.js → ase-util-compat.js} +2 -2
- package/dst/{ase-diagram.js → ase-util-diagram.js} +107 -32
- package/dst/{ase-meta.js → ase-util-meta.js} +2 -2
- package/dst/{ase-metric.js → ase-util-metric.js} +2 -2
- package/dst/{ase-mint.js → ase-util-mint.js} +19 -12
- package/dst/{ase-worktree.js → ase-util-worktree.js} +30 -19
- package/dst/ase-util.js +20 -0
- package/dst/ase.js +108 -32
- package/dst/man/ase-agent-adhoc.1 +515 -0
- package/dst/man/ase-agent-condenser.1 +193 -0
- package/dst/man/ase-agent-editor.1 +212 -0
- package/dst/man/ase-agent-estimator.1 +214 -0
- package/dst/man/ase-agent-fleet.1 +185 -0
- package/dst/man/ase-agent-implementer.1 +233 -0
- package/dst/man/ase-agent-integrator.1 +213 -0
- package/dst/man/ase-agent-qualifier.1 +214 -0
- package/dst/man/ase-agent-rebooter.1 +193 -0
- package/dst/man/ase-agent-relay.1 +128 -0
- package/dst/man/ase-agent-researcher.1 +217 -0
- package/dst/man/ase-agent.1 +264 -0
- package/dst/man/ase-artifact-list.1 +38 -0
- package/dst/man/ase-artifact-name.1 +39 -0
- package/dst/man/ase-artifact.1 +56 -0
- package/dst/man/ase-config-delete.1 +45 -0
- package/dst/man/ase-config-edit.1 +48 -0
- package/dst/man/ase-config-get.1 +46 -0
- package/dst/man/ase-config-init.1 +60 -0
- package/dst/man/ase-config-list.1 +42 -0
- package/dst/man/ase-config-set.1 +52 -0
- package/dst/man/ase-config.1 +225 -0
- package/dst/man/ase-hook-permission-request.1 +36 -0
- package/dst/man/ase-hook-pre-tool-use.1 +59 -0
- package/dst/man/ase-hook-session-end.1 +47 -0
- package/dst/man/ase-hook-session-persona.1 +37 -0
- package/dst/man/ase-hook-session-start.1 +81 -0
- package/dst/man/ase-hook-stop.1 +34 -0
- package/dst/man/ase-hook-user-prompt-submit.1 +34 -0
- package/dst/man/ase-hook.1 +51 -0
- package/dst/man/ase-man.1 +67 -0
- package/dst/man/ase-mcp.1 +61 -0
- package/dst/man/ase-service-log.1 +43 -0
- package/dst/man/ase-service-send.1 +47 -0
- package/dst/man/ase-service-start.1 +54 -0
- package/dst/man/ase-service-status.1 +46 -0
- package/dst/man/ase-service-stop.1 +37 -0
- package/dst/man/ase-service.1 +85 -0
- package/dst/man/ase-setup-disable.1 +44 -0
- package/dst/man/ase-setup-enable.1 +45 -0
- package/dst/man/ase-setup-install.1 +73 -0
- package/dst/man/ase-setup-mcp.1 +124 -0
- package/dst/man/ase-setup-status.1 +60 -0
- package/dst/man/ase-setup-statusline.1 +92 -0
- package/dst/man/ase-setup-telemetry.1 +92 -0
- package/dst/man/ase-setup-uninstall.1 +59 -0
- package/dst/man/ase-setup-update.1 +64 -0
- package/dst/man/ase-setup.1 +93 -0
- package/dst/man/ase-spec-export.1 +62 -0
- package/dst/man/ase-spec-lint.1 +40 -0
- package/dst/man/ase-spec-preview.1 +40 -0
- package/dst/man/ase-spec.1 +46 -0
- package/dst/man/ase-statusline.1 +189 -0
- package/dst/man/ase-task-assign.1 +71 -0
- package/dst/man/ase-task-board.1 +384 -0
- package/dst/man/ase-task-delete.1 +35 -0
- package/dst/man/ase-task-edit.1 +46 -0
- package/dst/man/ase-task-idscheme.1 +65 -0
- package/dst/man/ase-task-lifecycle.1 +57 -0
- package/dst/man/ase-task-list.1 +47 -0
- package/dst/man/ase-task-load.1 +36 -0
- package/dst/man/ase-task-newid.1 +61 -0
- package/dst/man/ase-task-purge.1 +37 -0
- package/dst/man/ase-task-rename.1 +44 -0
- package/dst/man/ase-task-request.1 +61 -0
- package/dst/man/ase-task-save.1 +48 -0
- package/dst/man/ase-task-status.1 +55 -0
- package/dst/man/ase-task-store.1 +194 -0
- package/dst/man/ase-task-view.1 +40 -0
- package/dst/man/ase-task.1 +160 -0
- package/dst/man/ase-util-compat.1 +25 -0
- package/dst/man/ase-util-diagram.1 +76 -0
- package/dst/man/ase-util-meta.1 +45 -0
- package/dst/man/ase-util-metric.1 +54 -0
- package/dst/man/ase-util-mint.1 +59 -0
- package/dst/man/ase-util-worktree.1 +72 -0
- package/dst/man/ase-util.1 +50 -0
- package/dst/man/ase.1 +124 -0
- package/package.json +79 -21
- 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-meta-diagram.md +12 -8
- package/plugin/agents/ase-repo-review.md +322 -0
- package/plugin/hooks/hooks-codex.json +5 -0
- package/plugin/hooks/hooks-copilot.json +5 -0
- package/plugin/hooks/hooks.json +8 -0
- package/plugin/meta/ase-common-approach.md +117 -0
- package/plugin/meta/ase-common-changeset.md +256 -0
- package/plugin/meta/ase-common-code.md +10 -173
- package/plugin/meta/ase-common-grill.md +317 -0
- package/plugin/meta/ase-common-resolve.md +305 -0
- package/plugin/meta/ase-common-task.md +83 -4
- package/plugin/meta/ase-constitution.md +0 -2
- package/plugin/meta/ase-format-specbook.md +208 -25
- package/plugin/meta/ase-format-specbook.yaml +242 -851
- package/plugin/meta/ase-format-task.md +669 -125
- package/plugin/meta/ase-persona.md +3 -4
- package/plugin/meta/ase-skill.md +9 -0
- package/plugin/mods/ase-mods.ts +409 -0
- package/plugin/mods/claude-code.d.ts +13417 -0
- package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
- package/plugin/skills/ase-arch-analyze/help.md +2 -2
- package/plugin/skills/ase-code-analyze/SKILL.md +59 -8
- package/plugin/skills/ase-code-analyze/help.md +29 -4
- package/plugin/skills/ase-code-edit/SKILL.md +107 -181
- package/plugin/skills/ase-code-edit/help.md +118 -31
- package/plugin/skills/ase-code-lint/SKILL.md +64 -13
- package/plugin/skills/ase-code-lint/help.md +32 -2
- package/plugin/skills/ase-docs-proofread/SKILL.md +4 -4
- package/plugin/skills/ase-docs-proofread/help.md +1 -1
- package/plugin/skills/ase-docs-refine/SKILL.md +4 -4
- package/plugin/skills/ase-docs-shorten/SKILL.md +4 -4
- package/plugin/skills/ase-help-intent/data.md +2164 -1087
- package/plugin/skills/ase-help-intent/help.md +1 -1
- package/plugin/skills/ase-help-skill/catalog.md +12 -9
- package/plugin/skills/ase-meta-brainstorm/SKILL.md +1 -1
- package/plugin/skills/ase-meta-config/SKILL.md +3 -4
- package/plugin/skills/ase-meta-config/help.md +31 -10
- package/plugin/skills/ase-meta-mint/SKILL.md +5 -5
- package/plugin/skills/ase-meta-mint/help.md +10 -5
- package/plugin/skills/ase-meta-why/help.md +1 -1
- package/plugin/skills/ase-meta-workflow/SKILL.md +7 -7
- package/plugin/skills/ase-meta-workflow/help.md +4 -4
- package/plugin/skills/ase-meta-workflow/sample.md +3 -3
- package/plugin/skills/ase-meta-workflow/workflow.txt +22 -18
- package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/SKILL.md +4 -3
- package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/help.md +5 -5
- package/plugin/skills/{ase-meta-commit → ase-repo-commit}/SKILL.md +6 -6
- package/plugin/skills/{ase-meta-commit → ase-repo-commit}/help.md +5 -5
- package/plugin/skills/{ase-meta-diff → ase-repo-diff}/SKILL.md +6 -6
- package/plugin/skills/{ase-meta-diff → ase-repo-diff}/help.md +8 -8
- package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/SKILL.md +19 -19
- package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/help.md +10 -12
- package/plugin/skills/ase-repo-merge/SKILL.md +346 -0
- package/plugin/skills/ase-repo-merge/help.md +107 -0
- package/plugin/skills/ase-repo-resolve/SKILL.md +158 -0
- package/plugin/skills/ase-repo-resolve/help.md +102 -0
- package/plugin/skills/ase-repo-review/SKILL.md +406 -0
- package/plugin/skills/ase-repo-review/help.md +181 -0
- package/plugin/skills/ase-spec-edit/SKILL.md +117 -183
- package/plugin/skills/ase-spec-edit/help.md +107 -26
- package/plugin/skills/ase-sync-reconcile/help.md +1 -1
- package/plugin/skills/ase-task-assign/SKILL.md +111 -0
- package/plugin/skills/ase-task-assign/help.md +119 -0
- package/plugin/skills/ase-task-condense/SKILL.md +21 -21
- package/plugin/skills/ase-task-condense/help.md +12 -10
- package/plugin/skills/ase-task-delete/SKILL.md +1 -1
- package/plugin/skills/ase-task-dissect/SKILL.md +71 -48
- package/plugin/skills/ase-task-dissect/help.md +10 -9
- package/plugin/skills/ase-task-edit/SKILL.md +379 -94
- package/plugin/skills/ase-task-edit/help.md +95 -22
- package/plugin/skills/ase-task-estimate/SKILL.md +492 -0
- package/plugin/skills/ase-task-estimate/help.md +158 -0
- package/plugin/skills/ase-task-grill/SKILL.md +246 -138
- package/plugin/skills/ase-task-grill/help.md +129 -26
- package/plugin/skills/ase-task-id/SKILL.md +8 -1
- package/plugin/skills/ase-task-id/help.md +6 -3
- package/plugin/skills/ase-task-implement/SKILL.md +535 -145
- package/plugin/skills/ase-task-implement/help.md +138 -47
- package/plugin/skills/ase-task-integrate/SKILL.md +549 -0
- package/plugin/skills/ase-task-integrate/help.md +187 -0
- package/plugin/skills/ase-task-list/SKILL.md +55 -23
- package/plugin/skills/ase-task-list/help.md +45 -21
- package/plugin/skills/ase-task-reboot/SKILL.md +84 -43
- package/plugin/skills/ase-task-reboot/help.md +28 -8
- package/plugin/skills/ase-task-rename/SKILL.md +10 -2
- package/plugin/skills/ase-task-research/SKILL.md +386 -0
- package/plugin/skills/ase-task-research/help.md +102 -0
- package/plugin/skills/ase-task-status/SKILL.md +137 -0
- package/plugin/skills/ase-task-status/help.md +80 -0
- package/plugin/skills/ase-task-view/SKILL.md +49 -24
- package/plugin/skills/ase-task-view/help.md +19 -9
- package/plugin/agents/ase-meta-review.md +0 -187
- package/plugin/skills/ase-code-craft/SKILL.md +0 -264
- package/plugin/skills/ase-code-craft/help.md +0 -122
- package/plugin/skills/ase-code-refactor/SKILL.md +0 -232
- package/plugin/skills/ase-code-refactor/help.md +0 -109
- package/plugin/skills/ase-code-resolve/SKILL.md +0 -289
- package/plugin/skills/ase-code-resolve/help.md +0 -116
- package/plugin/skills/ase-meta-review/SKILL.md +0 -210
- package/plugin/skills/ase-meta-review/help.md +0 -96
- package/plugin/skills/ase-task-preflight/SKILL.md +0 -166
- package/plugin/skills/ase-task-preflight/help.md +0 -82
- /package/dst/{ase-ignore.js → ase-lib-ignore.js} +0 -0
- /package/dst/{ase-stdio.js → ase-lib-stdio.js} +0 -0
- /package/dst/{ase-kv.js → ase-service-kv.js} +0 -0
- /package/dst/{ase-sleep.js → ase-service-sleep.js} +0 -0
- /package/dst/{ase-timestamp.js → ase-service-timestamp.js} +0 -0
|
@@ -6,30 +6,37 @@
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { DateTime } from "luxon";
|
|
9
|
+
/* the supported log levels (in ascending verbosity order) and their terminal styles */
|
|
9
10
|
const levels = [
|
|
10
11
|
{ name: "error", style: chalk.red.bold },
|
|
11
12
|
{ name: "warning", style: chalk.yellow.bold },
|
|
12
13
|
{ name: "info", style: chalk.blue },
|
|
13
|
-
{ name: "debug", style: chalk.
|
|
14
|
+
{ name: "debug", style: chalk.grey }
|
|
14
15
|
];
|
|
15
16
|
/* check whether an arbitrary string is a valid log level */
|
|
16
17
|
export const isLogLevel = (level) => levels.some((l) => l.name === level);
|
|
18
|
+
/* logger with level-based filtering, writing to stderr ("-") or a log file */
|
|
17
19
|
export default class Log {
|
|
18
20
|
_program;
|
|
19
21
|
_logLevel;
|
|
20
22
|
_logFile;
|
|
21
23
|
stream = null;
|
|
22
24
|
logLevelIdx = 0;
|
|
25
|
+
deferred = null;
|
|
26
|
+
_prefix = "";
|
|
27
|
+
/* creation */
|
|
23
28
|
constructor(_program, _logLevel, _logFile) {
|
|
24
29
|
this._program = _program;
|
|
25
30
|
this._logLevel = _logLevel;
|
|
26
31
|
this._logFile = _logFile;
|
|
27
32
|
}
|
|
33
|
+
/* initialize the logger with the initially configured level and file */
|
|
28
34
|
async init() {
|
|
29
35
|
this.logLevel(this._logLevel);
|
|
30
36
|
if (this._logFile !== "-")
|
|
31
37
|
this.stream = this.openStream(this._logFile);
|
|
32
38
|
}
|
|
39
|
+
/* open a log file stream for appending, reporting write errors on stderr */
|
|
33
40
|
openStream(file) {
|
|
34
41
|
const stream = fs.createWriteStream(file, { flags: "a", encoding: "utf8" });
|
|
35
42
|
stream.on("error", (err) => {
|
|
@@ -37,6 +44,7 @@ export default class Log {
|
|
|
37
44
|
});
|
|
38
45
|
return stream;
|
|
39
46
|
}
|
|
47
|
+
/* get the current log level or set a new one */
|
|
40
48
|
logLevel(level) {
|
|
41
49
|
if (level === undefined)
|
|
42
50
|
return this._logLevel;
|
|
@@ -47,6 +55,7 @@ export default class Log {
|
|
|
47
55
|
this.logLevelIdx = idx;
|
|
48
56
|
return this._logLevel;
|
|
49
57
|
}
|
|
58
|
+
/* switch the log file ("-" for stderr), closing any previous log file stream */
|
|
50
59
|
logFile(file) {
|
|
51
60
|
if (file === this._logFile)
|
|
52
61
|
return;
|
|
@@ -58,22 +67,46 @@ export default class Log {
|
|
|
58
67
|
if (file !== "-")
|
|
59
68
|
this.stream = this.openStream(file);
|
|
60
69
|
}
|
|
70
|
+
/* get the current line prefix or set a new one (e.g. the name of
|
|
71
|
+
a member of an agent fleet, sharing the log with the others) */
|
|
72
|
+
prefix(prefix) {
|
|
73
|
+
if (prefix !== undefined)
|
|
74
|
+
this._prefix = prefix;
|
|
75
|
+
return this._prefix;
|
|
76
|
+
}
|
|
77
|
+
/* render the tag of a log level, styled for a terminal if <styled> */
|
|
78
|
+
tag(level, styled) {
|
|
79
|
+
const tag = `[${level.toUpperCase()}]`;
|
|
80
|
+
const def = levels.find((l) => l.name === level);
|
|
81
|
+
return styled && def !== undefined ? def.style(tag) : tag;
|
|
82
|
+
}
|
|
83
|
+
/* write a message on a log level, if enabled by the current log level */
|
|
61
84
|
write(level, msg) {
|
|
62
85
|
const idx = levels.findIndex((l) => l.name === level);
|
|
63
86
|
if (idx !== -1 && idx <= this.logLevelIdx) {
|
|
64
87
|
const timestamp = DateTime.now().toFormat("yyyy-LL-dd HH:mm:ss.SSS");
|
|
65
|
-
let line = `${this._program}: [${timestamp}]: `;
|
|
66
|
-
|
|
67
|
-
line += `${levels[idx].style("[" + levels[idx].name.toUpperCase() + "]")}`;
|
|
68
|
-
else
|
|
69
|
-
line += `[${levels[idx].name.toUpperCase()}]`;
|
|
88
|
+
let line = `${this._prefix}${this._program}: [${timestamp}]: `;
|
|
89
|
+
line += this.tag(level, this._logFile === "-" && process.stderr.isTTY);
|
|
70
90
|
line += `: ${msg}\n`;
|
|
71
|
-
if (this._logFile === "-")
|
|
91
|
+
if (this._logFile === "-" && this.deferred !== null)
|
|
92
|
+
this.deferred.push(line);
|
|
93
|
+
else if (this._logFile === "-")
|
|
72
94
|
process.stderr.write(line);
|
|
73
95
|
else if (this.stream !== null)
|
|
74
96
|
this.stream.write(line);
|
|
75
97
|
}
|
|
76
98
|
}
|
|
99
|
+
/* defer stderr output (e.g. while a TUI owns the terminal) and flush it once re-enabled */
|
|
100
|
+
defer(enable) {
|
|
101
|
+
if (enable && this.deferred === null)
|
|
102
|
+
this.deferred = [];
|
|
103
|
+
else if (!enable && this.deferred !== null) {
|
|
104
|
+
const lines = this.deferred;
|
|
105
|
+
this.deferred = null;
|
|
106
|
+
for (const line of lines)
|
|
107
|
+
process.stderr.write(line);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
77
110
|
/* close the log file stream, flushing all pending writes */
|
|
78
111
|
async close() {
|
|
79
112
|
if (this.stream === null)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
|
+
*/
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import net from "node:net";
|
|
8
|
+
import * as Core from "./ase-task-store-core.js";
|
|
9
|
+
/* the host part of an address for use in a URL (brackets for IPv6) */
|
|
10
|
+
export const urlHost = (address) => net.isIPv6(address) ? `[${address}]` : address;
|
|
11
|
+
/* whether an address (or a WHATWG URL hostname, i.e. with brackets
|
|
12
|
+
around an IPv6 address) denotes the loopback interface */
|
|
13
|
+
export const isLoopback = (address) => /^(?:127(?:\.\d{1,3}){3}|\[?::1\]?|localhost)$/i.test(address);
|
|
14
|
+
/* the response header identifying the server by its label on a rejected authentication
|
|
15
|
+
(so a probe tells the different servers, sharing the same range of ports, apart) */
|
|
16
|
+
export const serverHeader = "ASE-Server";
|
|
17
|
+
/* the common extensions of the bearer token authenticated REST API servers (the task store
|
|
18
|
+
and the agent relay server): the bearer token authentication, the problem details rendering,
|
|
19
|
+
the request logging, and the hooking of the WebSocket handshakes */
|
|
20
|
+
export class ServerGuard {
|
|
21
|
+
log;
|
|
22
|
+
options;
|
|
23
|
+
token;
|
|
24
|
+
constructor(log, options) {
|
|
25
|
+
this.log = log;
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.token = Buffer.from(options.token, "utf8");
|
|
28
|
+
}
|
|
29
|
+
/* check a bearer token against the configured one in constant time */
|
|
30
|
+
authorized(header, query) {
|
|
31
|
+
let token;
|
|
32
|
+
if (typeof header === "string") {
|
|
33
|
+
const m = /^Bearer[ \t]+(\S+)[ \t]*$/i.exec(header);
|
|
34
|
+
if (m === null)
|
|
35
|
+
return false;
|
|
36
|
+
token = m[1];
|
|
37
|
+
}
|
|
38
|
+
else if (typeof query === "string")
|
|
39
|
+
token = query;
|
|
40
|
+
if (token === undefined)
|
|
41
|
+
return false;
|
|
42
|
+
const buf = Buffer.from(token, "utf8");
|
|
43
|
+
return buf.length === this.token.length && crypto.timingSafeEqual(buf, this.token);
|
|
44
|
+
}
|
|
45
|
+
/* reject a WebSocket handshake with a plain HTTP problem response */
|
|
46
|
+
rejectUpgrade(socket, status, detail, instance) {
|
|
47
|
+
const body = JSON.stringify(Core.problemBody(status, detail, instance));
|
|
48
|
+
socket.write(`HTTP/1.1 ${status} ${Core.problemTitles[status] ?? "Error"}\r\n` +
|
|
49
|
+
"Content-Type: application/problem+json\r\n" +
|
|
50
|
+
`Content-Length: ${Buffer.byteLength(body)}\r\n` +
|
|
51
|
+
"Connection: close\r\n\r\n" + body);
|
|
52
|
+
socket.end(() => { socket.destroy(); });
|
|
53
|
+
}
|
|
54
|
+
/* install the bearer token authentication, the request logging,
|
|
55
|
+
the problem details rendering, and the WebSocket upgrade hook */
|
|
56
|
+
install(server) {
|
|
57
|
+
const { label, failure, level, upgrade } = this.options;
|
|
58
|
+
/* authenticate every request except a CORS preflight, which
|
|
59
|
+
carries no credentials by definition */
|
|
60
|
+
server.ext("onRequest", (request, h) => {
|
|
61
|
+
if (request.method === "options")
|
|
62
|
+
return h.continue;
|
|
63
|
+
if (!this.authorized(request.headers.authorization, undefined))
|
|
64
|
+
return h.response(Core.problemBody(401, "missing or invalid bearer token", request.path))
|
|
65
|
+
.code(401).type("application/problem+json").header(serverHeader, label).takeover();
|
|
66
|
+
return h.continue;
|
|
67
|
+
});
|
|
68
|
+
/* render thrown problems and framework errors as problem details */
|
|
69
|
+
server.ext("onPreResponse", (request, h) => {
|
|
70
|
+
const response = request.response;
|
|
71
|
+
let status = null;
|
|
72
|
+
let detail = "";
|
|
73
|
+
if (response instanceof Core.Problem) {
|
|
74
|
+
status = response.status;
|
|
75
|
+
detail = response.message;
|
|
76
|
+
}
|
|
77
|
+
else if ("isBoom" in response && response.isBoom) {
|
|
78
|
+
status = response.output.statusCode;
|
|
79
|
+
detail = response.message;
|
|
80
|
+
}
|
|
81
|
+
if (status === null)
|
|
82
|
+
return h.continue;
|
|
83
|
+
/* log server-side failures in full, but expose no internals to the client */
|
|
84
|
+
if (status >= 500) {
|
|
85
|
+
this.log.write("error", `${label}: ${request.method.toUpperCase()} ${request.path}: ${detail}`);
|
|
86
|
+
detail = failure;
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
this.log.write("debug", `${label}: ${request.method.toUpperCase()} ${request.path}: ${status}: ${detail}`);
|
|
90
|
+
return h.response(Core.problemBody(status, detail, request.path))
|
|
91
|
+
.code(status).type("application/problem+json");
|
|
92
|
+
});
|
|
93
|
+
/* log every regular request */
|
|
94
|
+
server.events.on("response", (request) => {
|
|
95
|
+
const response = request.response;
|
|
96
|
+
let code = 0;
|
|
97
|
+
if (typeof response === "object" && response !== null) {
|
|
98
|
+
if ("isBoom" in response && response.isBoom && "output" in response)
|
|
99
|
+
code = response.output.statusCode;
|
|
100
|
+
else if ("statusCode" in response && typeof response.statusCode === "number")
|
|
101
|
+
code = response.statusCode;
|
|
102
|
+
}
|
|
103
|
+
this.log.write(level(request.path), `${label}: ${request.method.toUpperCase()} ${request.path} ${code}`);
|
|
104
|
+
});
|
|
105
|
+
/* hook the WebSocket handshakes */
|
|
106
|
+
server.listener.on("upgrade", (req, socket, head) => {
|
|
107
|
+
socket.on("error", () => { socket.destroy(); });
|
|
108
|
+
Promise.resolve().then(() => upgrade(req, socket, head)).catch((err) => {
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
this.log.write("error", `${label}: WebSocket upgrade failed: ${message}`);
|
|
111
|
+
socket.destroy();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|