@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.
Files changed (279) hide show
  1. package/dst/ase-agent-definition-api.d.ts +184 -0
  2. package/dst/ase-agent-definition-api.js +6 -0
  3. package/dst/ase-agent-definition-builtin/condenser.js +42 -0
  4. package/dst/ase-agent-definition-builtin/editor.js +45 -0
  5. package/dst/ase-agent-definition-builtin/estimator.js +98 -0
  6. package/dst/ase-agent-definition-builtin/implementer.js +121 -0
  7. package/dst/ase-agent-definition-builtin/integrator.js +56 -0
  8. package/dst/ase-agent-definition-builtin/qualifier.js +61 -0
  9. package/dst/ase-agent-definition-builtin/rebooter.js +41 -0
  10. package/dst/ase-agent-definition-builtin/researcher.js +77 -0
  11. package/dst/ase-agent-definition-core.js +827 -0
  12. package/dst/ase-agent-fleet.js +772 -0
  13. package/dst/ase-agent-process.js +232 -0
  14. package/dst/ase-agent-relay-client.js +509 -0
  15. package/dst/ase-agent-relay-core.js +213 -0
  16. package/dst/ase-agent-relay-server-bind.js +242 -0
  17. package/dst/ase-agent-relay-server-cli.js +50 -0
  18. package/dst/ase-agent-runtime-claim.js +189 -0
  19. package/dst/ase-agent-runtime-engine.js +1100 -0
  20. package/dst/ase-agent-runtime-exec.js +275 -0
  21. package/dst/ase-agent-runtime-phase.js +581 -0
  22. package/dst/ase-agent-runtime-request.js +272 -0
  23. package/dst/ase-agent-runtime-tool.js +207 -0
  24. package/dst/ase-agent-runtime.js +1316 -0
  25. package/dst/ase-agent.js +65 -0
  26. package/dst/ase-artifact.js +6 -5
  27. package/dst/ase-config-core.js +465 -0
  28. package/dst/ase-config-schema.js +351 -0
  29. package/dst/ase-config-scope.js +172 -0
  30. package/dst/ase-config.js +25 -585
  31. package/dst/ase-hook.js +228 -111
  32. package/dst/ase-lib-daemon-cli.js +130 -0
  33. package/dst/ase-lib-hash.js +20 -0
  34. package/dst/{ase-log.js → ase-lib-log.js} +40 -7
  35. package/dst/ase-lib-server-bind.js +115 -0
  36. package/dst/ase-lib-server-cli.js +474 -0
  37. package/dst/{ase-table.js → ase-lib-table.js} +1 -1
  38. package/dst/{ase-version.js → ase-lib-version.js} +3 -0
  39. package/dst/ase-man.js +96 -0
  40. package/dst/ase-mcp.js +166 -13
  41. package/dst/{ase-getopt.js → ase-service-getopt.js} +82 -80
  42. package/dst/{ase-markdown.js → ase-service-markdown.js} +18 -11
  43. package/dst/ase-service-otel.js +1246 -0
  44. package/dst/{ase-skills.js → ase-service-skills.js} +29 -64
  45. package/dst/ase-service-state.js +101 -0
  46. package/dst/ase-service.js +251 -76
  47. package/dst/ase-setup-common.js +141 -0
  48. package/dst/ase-setup-mcp.js +359 -0
  49. package/dst/ase-setup-settings.js +837 -0
  50. package/dst/ase-setup.js +113 -869
  51. package/dst/ase-spec.js +31 -19
  52. package/dst/ase-statusline.js +108 -43
  53. package/dst/ase-task-board-core.js +739 -0
  54. package/dst/ase-task-board-filter.js +127 -0
  55. package/dst/ase-task-board-flow.js +237 -0
  56. package/dst/ase-task-board-graph.js +497 -0
  57. package/dst/ase-task-board-stats.js +263 -0
  58. package/dst/ase-task-board-tui-control.js +775 -0
  59. package/dst/ase-task-board-tui-model.js +1008 -0
  60. package/dst/ase-task-board-tui-popup.js +626 -0
  61. package/dst/ase-task-board-tui-style.js +106 -0
  62. package/dst/ase-task-board-tui-view.js +847 -0
  63. package/dst/ase-task-board-tui.js +189 -0
  64. package/dst/ase-task-board-web-client.css +1 -0
  65. package/dst/ase-task-board-web-client.html +20 -0
  66. package/dst/ase-task-board-web-client.js +81 -0
  67. package/dst/ase-task-board-web-server.js +823 -0
  68. package/dst/ase-task-board.js +134 -0
  69. package/dst/ase-task-format.js +757 -0
  70. package/dst/ase-task-request.js +208 -0
  71. package/dst/ase-task-store-client.js +495 -0
  72. package/dst/ase-task-store-core.js +804 -0
  73. package/dst/ase-task-store-issues.js +237 -0
  74. package/dst/ase-task-store-plugin-api.js +6 -0
  75. package/dst/ase-task-store-plugin-ase.js +484 -0
  76. package/dst/ase-task-store-plugin-delegate.js +137 -0
  77. package/dst/ase-task-store-plugin-gitea.js +666 -0
  78. package/dst/ase-task-store-plugin-github.js +696 -0
  79. package/dst/ase-task-store-plugin-gitlab.js +668 -0
  80. package/dst/ase-task-store-server-bind.js +538 -0
  81. package/dst/ase-task-store-server-cli.js +154 -0
  82. package/dst/ase-task.js +1335 -357
  83. package/dst/{ase-compat.js → ase-util-compat.js} +2 -2
  84. package/dst/{ase-diagram.js → ase-util-diagram.js} +107 -32
  85. package/dst/{ase-meta.js → ase-util-meta.js} +2 -2
  86. package/dst/{ase-metric.js → ase-util-metric.js} +2 -2
  87. package/dst/{ase-mint.js → ase-util-mint.js} +19 -12
  88. package/dst/{ase-worktree.js → ase-util-worktree.js} +30 -19
  89. package/dst/ase-util.js +20 -0
  90. package/dst/ase.js +108 -32
  91. package/dst/man/ase-agent-adhoc.1 +515 -0
  92. package/dst/man/ase-agent-condenser.1 +193 -0
  93. package/dst/man/ase-agent-editor.1 +212 -0
  94. package/dst/man/ase-agent-estimator.1 +214 -0
  95. package/dst/man/ase-agent-fleet.1 +185 -0
  96. package/dst/man/ase-agent-implementer.1 +233 -0
  97. package/dst/man/ase-agent-integrator.1 +213 -0
  98. package/dst/man/ase-agent-qualifier.1 +214 -0
  99. package/dst/man/ase-agent-rebooter.1 +193 -0
  100. package/dst/man/ase-agent-relay.1 +128 -0
  101. package/dst/man/ase-agent-researcher.1 +217 -0
  102. package/dst/man/ase-agent.1 +264 -0
  103. package/dst/man/ase-artifact-list.1 +38 -0
  104. package/dst/man/ase-artifact-name.1 +39 -0
  105. package/dst/man/ase-artifact.1 +56 -0
  106. package/dst/man/ase-config-delete.1 +45 -0
  107. package/dst/man/ase-config-edit.1 +48 -0
  108. package/dst/man/ase-config-get.1 +46 -0
  109. package/dst/man/ase-config-init.1 +60 -0
  110. package/dst/man/ase-config-list.1 +42 -0
  111. package/dst/man/ase-config-set.1 +52 -0
  112. package/dst/man/ase-config.1 +225 -0
  113. package/dst/man/ase-hook-permission-request.1 +36 -0
  114. package/dst/man/ase-hook-pre-tool-use.1 +59 -0
  115. package/dst/man/ase-hook-session-end.1 +47 -0
  116. package/dst/man/ase-hook-session-persona.1 +37 -0
  117. package/dst/man/ase-hook-session-start.1 +81 -0
  118. package/dst/man/ase-hook-stop.1 +34 -0
  119. package/dst/man/ase-hook-user-prompt-submit.1 +34 -0
  120. package/dst/man/ase-hook.1 +51 -0
  121. package/dst/man/ase-man.1 +67 -0
  122. package/dst/man/ase-mcp.1 +61 -0
  123. package/dst/man/ase-service-log.1 +43 -0
  124. package/dst/man/ase-service-send.1 +47 -0
  125. package/dst/man/ase-service-start.1 +54 -0
  126. package/dst/man/ase-service-status.1 +46 -0
  127. package/dst/man/ase-service-stop.1 +37 -0
  128. package/dst/man/ase-service.1 +85 -0
  129. package/dst/man/ase-setup-disable.1 +44 -0
  130. package/dst/man/ase-setup-enable.1 +45 -0
  131. package/dst/man/ase-setup-install.1 +73 -0
  132. package/dst/man/ase-setup-mcp.1 +124 -0
  133. package/dst/man/ase-setup-status.1 +60 -0
  134. package/dst/man/ase-setup-statusline.1 +92 -0
  135. package/dst/man/ase-setup-telemetry.1 +92 -0
  136. package/dst/man/ase-setup-uninstall.1 +59 -0
  137. package/dst/man/ase-setup-update.1 +64 -0
  138. package/dst/man/ase-setup.1 +93 -0
  139. package/dst/man/ase-spec-export.1 +62 -0
  140. package/dst/man/ase-spec-lint.1 +40 -0
  141. package/dst/man/ase-spec-preview.1 +40 -0
  142. package/dst/man/ase-spec.1 +46 -0
  143. package/dst/man/ase-statusline.1 +189 -0
  144. package/dst/man/ase-task-assign.1 +71 -0
  145. package/dst/man/ase-task-board.1 +384 -0
  146. package/dst/man/ase-task-delete.1 +35 -0
  147. package/dst/man/ase-task-edit.1 +46 -0
  148. package/dst/man/ase-task-idscheme.1 +65 -0
  149. package/dst/man/ase-task-lifecycle.1 +57 -0
  150. package/dst/man/ase-task-list.1 +47 -0
  151. package/dst/man/ase-task-load.1 +36 -0
  152. package/dst/man/ase-task-newid.1 +61 -0
  153. package/dst/man/ase-task-purge.1 +37 -0
  154. package/dst/man/ase-task-rename.1 +44 -0
  155. package/dst/man/ase-task-request.1 +61 -0
  156. package/dst/man/ase-task-save.1 +48 -0
  157. package/dst/man/ase-task-status.1 +55 -0
  158. package/dst/man/ase-task-store.1 +194 -0
  159. package/dst/man/ase-task-view.1 +40 -0
  160. package/dst/man/ase-task.1 +160 -0
  161. package/dst/man/ase-util-compat.1 +25 -0
  162. package/dst/man/ase-util-diagram.1 +76 -0
  163. package/dst/man/ase-util-meta.1 +45 -0
  164. package/dst/man/ase-util-metric.1 +54 -0
  165. package/dst/man/ase-util-mint.1 +59 -0
  166. package/dst/man/ase-util-worktree.1 +72 -0
  167. package/dst/man/ase-util.1 +50 -0
  168. package/dst/man/ase.1 +124 -0
  169. package/package.json +79 -21
  170. package/plugin/.claude-plugin/plugin.json +1 -1
  171. package/plugin/.codex-plugin/plugin.json +1 -1
  172. package/plugin/.github/plugin/plugin.json +1 -1
  173. package/plugin/agents/ase-meta-diagram.md +12 -8
  174. package/plugin/agents/ase-repo-review.md +322 -0
  175. package/plugin/hooks/hooks-codex.json +5 -0
  176. package/plugin/hooks/hooks-copilot.json +5 -0
  177. package/plugin/hooks/hooks.json +8 -0
  178. package/plugin/meta/ase-common-approach.md +117 -0
  179. package/plugin/meta/ase-common-changeset.md +256 -0
  180. package/plugin/meta/ase-common-code.md +10 -173
  181. package/plugin/meta/ase-common-grill.md +317 -0
  182. package/plugin/meta/ase-common-resolve.md +305 -0
  183. package/plugin/meta/ase-common-task.md +83 -4
  184. package/plugin/meta/ase-constitution.md +0 -2
  185. package/plugin/meta/ase-format-specbook.md +208 -25
  186. package/plugin/meta/ase-format-specbook.yaml +242 -851
  187. package/plugin/meta/ase-format-task.md +669 -125
  188. package/plugin/meta/ase-persona.md +3 -4
  189. package/plugin/meta/ase-skill.md +9 -0
  190. package/plugin/mods/ase-mods.ts +409 -0
  191. package/plugin/mods/claude-code.d.ts +13417 -0
  192. package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
  193. package/plugin/skills/ase-arch-analyze/help.md +2 -2
  194. package/plugin/skills/ase-code-analyze/SKILL.md +59 -8
  195. package/plugin/skills/ase-code-analyze/help.md +29 -4
  196. package/plugin/skills/ase-code-edit/SKILL.md +107 -181
  197. package/plugin/skills/ase-code-edit/help.md +118 -31
  198. package/plugin/skills/ase-code-lint/SKILL.md +64 -13
  199. package/plugin/skills/ase-code-lint/help.md +32 -2
  200. package/plugin/skills/ase-docs-proofread/SKILL.md +4 -4
  201. package/plugin/skills/ase-docs-proofread/help.md +1 -1
  202. package/plugin/skills/ase-docs-refine/SKILL.md +4 -4
  203. package/plugin/skills/ase-docs-shorten/SKILL.md +4 -4
  204. package/plugin/skills/ase-help-intent/data.md +2164 -1087
  205. package/plugin/skills/ase-help-intent/help.md +1 -1
  206. package/plugin/skills/ase-help-skill/catalog.md +12 -9
  207. package/plugin/skills/ase-meta-brainstorm/SKILL.md +1 -1
  208. package/plugin/skills/ase-meta-config/SKILL.md +3 -4
  209. package/plugin/skills/ase-meta-config/help.md +31 -10
  210. package/plugin/skills/ase-meta-mint/SKILL.md +5 -5
  211. package/plugin/skills/ase-meta-mint/help.md +10 -5
  212. package/plugin/skills/ase-meta-why/help.md +1 -1
  213. package/plugin/skills/ase-meta-workflow/SKILL.md +7 -7
  214. package/plugin/skills/ase-meta-workflow/help.md +4 -4
  215. package/plugin/skills/ase-meta-workflow/sample.md +3 -3
  216. package/plugin/skills/ase-meta-workflow/workflow.txt +22 -18
  217. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/SKILL.md +4 -3
  218. package/plugin/skills/{ase-meta-changelog → ase-repo-changelog}/help.md +5 -5
  219. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/SKILL.md +6 -6
  220. package/plugin/skills/{ase-meta-commit → ase-repo-commit}/help.md +5 -5
  221. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/SKILL.md +6 -6
  222. package/plugin/skills/{ase-meta-diff → ase-repo-diff}/help.md +8 -8
  223. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/SKILL.md +19 -19
  224. package/plugin/skills/{ase-code-dissect → ase-repo-dissect}/help.md +10 -12
  225. package/plugin/skills/ase-repo-merge/SKILL.md +346 -0
  226. package/plugin/skills/ase-repo-merge/help.md +107 -0
  227. package/plugin/skills/ase-repo-resolve/SKILL.md +158 -0
  228. package/plugin/skills/ase-repo-resolve/help.md +102 -0
  229. package/plugin/skills/ase-repo-review/SKILL.md +406 -0
  230. package/plugin/skills/ase-repo-review/help.md +181 -0
  231. package/plugin/skills/ase-spec-edit/SKILL.md +117 -183
  232. package/plugin/skills/ase-spec-edit/help.md +107 -26
  233. package/plugin/skills/ase-sync-reconcile/help.md +1 -1
  234. package/plugin/skills/ase-task-assign/SKILL.md +111 -0
  235. package/plugin/skills/ase-task-assign/help.md +119 -0
  236. package/plugin/skills/ase-task-condense/SKILL.md +21 -21
  237. package/plugin/skills/ase-task-condense/help.md +12 -10
  238. package/plugin/skills/ase-task-delete/SKILL.md +1 -1
  239. package/plugin/skills/ase-task-dissect/SKILL.md +71 -48
  240. package/plugin/skills/ase-task-dissect/help.md +10 -9
  241. package/plugin/skills/ase-task-edit/SKILL.md +379 -94
  242. package/plugin/skills/ase-task-edit/help.md +95 -22
  243. package/plugin/skills/ase-task-estimate/SKILL.md +492 -0
  244. package/plugin/skills/ase-task-estimate/help.md +158 -0
  245. package/plugin/skills/ase-task-grill/SKILL.md +246 -138
  246. package/plugin/skills/ase-task-grill/help.md +129 -26
  247. package/plugin/skills/ase-task-id/SKILL.md +8 -1
  248. package/plugin/skills/ase-task-id/help.md +6 -3
  249. package/plugin/skills/ase-task-implement/SKILL.md +535 -145
  250. package/plugin/skills/ase-task-implement/help.md +138 -47
  251. package/plugin/skills/ase-task-integrate/SKILL.md +549 -0
  252. package/plugin/skills/ase-task-integrate/help.md +187 -0
  253. package/plugin/skills/ase-task-list/SKILL.md +55 -23
  254. package/plugin/skills/ase-task-list/help.md +45 -21
  255. package/plugin/skills/ase-task-reboot/SKILL.md +84 -43
  256. package/plugin/skills/ase-task-reboot/help.md +28 -8
  257. package/plugin/skills/ase-task-rename/SKILL.md +10 -2
  258. package/plugin/skills/ase-task-research/SKILL.md +386 -0
  259. package/plugin/skills/ase-task-research/help.md +102 -0
  260. package/plugin/skills/ase-task-status/SKILL.md +137 -0
  261. package/plugin/skills/ase-task-status/help.md +80 -0
  262. package/plugin/skills/ase-task-view/SKILL.md +49 -24
  263. package/plugin/skills/ase-task-view/help.md +19 -9
  264. package/plugin/agents/ase-meta-review.md +0 -187
  265. package/plugin/skills/ase-code-craft/SKILL.md +0 -264
  266. package/plugin/skills/ase-code-craft/help.md +0 -122
  267. package/plugin/skills/ase-code-refactor/SKILL.md +0 -232
  268. package/plugin/skills/ase-code-refactor/help.md +0 -109
  269. package/plugin/skills/ase-code-resolve/SKILL.md +0 -289
  270. package/plugin/skills/ase-code-resolve/help.md +0 -116
  271. package/plugin/skills/ase-meta-review/SKILL.md +0 -210
  272. package/plugin/skills/ase-meta-review/help.md +0 -96
  273. package/plugin/skills/ase-task-preflight/SKILL.md +0 -166
  274. package/plugin/skills/ase-task-preflight/help.md +0 -82
  275. /package/dst/{ase-ignore.js → ase-lib-ignore.js} +0 -0
  276. /package/dst/{ase-stdio.js → ase-lib-stdio.js} +0 -0
  277. /package/dst/{ase-kv.js → ase-service-kv.js} +0 -0
  278. /package/dst/{ase-sleep.js → ase-service-sleep.js} +0 -0
  279. /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.green }
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
- if (this._logFile === "-" && process.stderr.isTTY)
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
+ }