@rse/ase 1.0.6 → 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 +230 -116
- 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
|
@@ -0,0 +1,232 @@
|
|
|
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 path from "node:path";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
import lockfile from "proper-lockfile";
|
|
10
|
+
import { writeStdout } from "./ase-lib-stdio.js";
|
|
11
|
+
import { DaemonCommand } from "./ase-lib-daemon-cli.js";
|
|
12
|
+
import { projectRoot, ensureAseGitignore } from "./ase-config-scope.js";
|
|
13
|
+
import { pidAlive, pidStart } from "./ase-agent-runtime-exec.js";
|
|
14
|
+
import { relaySpec, AgentRelayClient } from "./ase-agent-relay-client.js";
|
|
15
|
+
/* the duration after which the start lock of an agent counts as stale */
|
|
16
|
+
const startStale = 10 * 1000;
|
|
17
|
+
/* the directory of the process files of the agents (and of their fleet) of the project */
|
|
18
|
+
export const processDir = () => {
|
|
19
|
+
const root = projectRoot();
|
|
20
|
+
if (root === null)
|
|
21
|
+
throw new Error("agent: no project context " +
|
|
22
|
+
"(a Git repository or a \".ase\" directory at or above the current directory)");
|
|
23
|
+
ensureAseGitignore(path.join(root, ".ase"));
|
|
24
|
+
return path.join(root, ".ase", "agent");
|
|
25
|
+
};
|
|
26
|
+
/* read a process state file (or null if missing or invalid) */
|
|
27
|
+
export const readState = (file) => {
|
|
28
|
+
try {
|
|
29
|
+
const data = YAML.parse(fs.readFileSync(file, "utf8"));
|
|
30
|
+
return data !== null && typeof data === "object" && typeof data.pid === "number" ? data : null;
|
|
31
|
+
}
|
|
32
|
+
catch (_err) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/* write a process state file (atomically) */
|
|
37
|
+
export const writeState = (file, state) => {
|
|
38
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
39
|
+
fs.writeFileSync(`${file}.tmp`, YAML.stringify(state));
|
|
40
|
+
fs.renameSync(`${file}.tmp`, file);
|
|
41
|
+
};
|
|
42
|
+
/* whether the process recorded in a process state is still alive */
|
|
43
|
+
export const stateAlive = async (state) => state !== null && pidAlive(state.pid) && (state.start === "" || await pidStart(state.pid) === state.start);
|
|
44
|
+
/* validate the name of an agent of a kind, which must neither be the name of another
|
|
45
|
+
agent kind nor of another "ase agent" command, as it names its process files */
|
|
46
|
+
export const validateName = (name, kind, kinds) => {
|
|
47
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name) || name.length > 64)
|
|
48
|
+
throw new Error(`agent: invalid agent name "${name}" ` +
|
|
49
|
+
"(expected up to 64 letters, digits, underscores, and dashes, starting with a letter or digit)");
|
|
50
|
+
if (name !== kind && [...kinds, "relay", "fleet"].includes(name))
|
|
51
|
+
throw new Error(`agent: agent name "${name}" clashes with the command "ase agent ${name}"`);
|
|
52
|
+
};
|
|
53
|
+
/* the defaults of the options shared by all agents (where the one of "--tool"
|
|
54
|
+
is derived from the ASE_TOOL environment variable, validated lazily in "setup") */
|
|
55
|
+
export const agentDefaults = () => {
|
|
56
|
+
const envTool = process.env.ASE_TOOL ?? "";
|
|
57
|
+
return { tool: envTool !== "" ? envTool : "claude", parallel: "1", timeout: "60m", oneShot: false };
|
|
58
|
+
};
|
|
59
|
+
/* the command-line arguments of the explicitly given options of a command (except "--foreground"),
|
|
60
|
+
where a repeatable option is repeated per value */
|
|
61
|
+
const givenArgs = (cmd) => cmd.options.flatMap((option) => {
|
|
62
|
+
const key = option.attributeName();
|
|
63
|
+
if (option.long === undefined || option.long === "--foreground" || cmd.getOptionValueSource(key) !== "cli")
|
|
64
|
+
return [];
|
|
65
|
+
const value = cmd.getOptionValue(key);
|
|
66
|
+
return value === true ? [option.long] :
|
|
67
|
+
(Array.isArray(value) ? value : [value]).flatMap((item) => [option.long, String(item)]);
|
|
68
|
+
});
|
|
69
|
+
/* CLI command group "ase agent <kind>", managing a single agent of a kind
|
|
70
|
+
(named by "--name") in the background or in the foreground */
|
|
71
|
+
export default class AgentProcessCommand extends DaemonCommand {
|
|
72
|
+
agent;
|
|
73
|
+
kinds;
|
|
74
|
+
label;
|
|
75
|
+
words;
|
|
76
|
+
description;
|
|
77
|
+
constructor(log, agent, kinds) {
|
|
78
|
+
super(log);
|
|
79
|
+
this.agent = agent;
|
|
80
|
+
this.kinds = kinds;
|
|
81
|
+
this.label = `agent ${agent.kind}`;
|
|
82
|
+
this.words = `agent ${agent.kind}`;
|
|
83
|
+
this.description = agent.command().description();
|
|
84
|
+
}
|
|
85
|
+
/* the process state file and the log file of an agent of the project */
|
|
86
|
+
file(name, ext) {
|
|
87
|
+
return path.join(processDir(), `${name}.${ext}`);
|
|
88
|
+
}
|
|
89
|
+
/* start flow: detach the agent (or run it in the foreground or as the detached process) */
|
|
90
|
+
async doStart(start, opts) {
|
|
91
|
+
const kind = this.agent.kind;
|
|
92
|
+
const name = opts.name ?? kind;
|
|
93
|
+
validateName(name, kind, [...this.kinds.keys()]);
|
|
94
|
+
const file = this.file(name, "yaml");
|
|
95
|
+
const serve = process.env.ASE_AGENT_SERVE === "1";
|
|
96
|
+
delete process.env.ASE_AGENT_SERVE;
|
|
97
|
+
/* idempotent start: an already running agent is left alone
|
|
98
|
+
(unless the name is taken by a running agent of another kind) */
|
|
99
|
+
const state = readState(file);
|
|
100
|
+
if (state !== null && await stateAlive(state)) {
|
|
101
|
+
if (state.kind !== kind)
|
|
102
|
+
throw new Error(`agent ${name}: name already taken by a running ${state.kind} agent (pid ${state.pid})`);
|
|
103
|
+
if (opts.foreground || serve)
|
|
104
|
+
throw new Error(`agent ${name}: already running (pid ${state.pid}) ` +
|
|
105
|
+
`(stop it first with "ase agent ${kind} stop -n ${name}")`);
|
|
106
|
+
this.log.write("info", `agent ${name}: already running (pid ${state.pid})`);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
/* spawn the agent detached (after validating its options), logging into
|
|
110
|
+
its log file, and await its successful setup */
|
|
111
|
+
if (!opts.foreground && !serve) {
|
|
112
|
+
await this.agent.validate(opts);
|
|
113
|
+
const logFile = this.file(name, "log");
|
|
114
|
+
const child = this.spawnDetached(["-l", this.log.logLevel(), "agent", kind, "start", ...givenArgs(start)], { ASE_AGENT_SERVE: "1" }, logFile);
|
|
115
|
+
await this.awaitDetached(child, logFile, () => readState(file)?.pid === child.pid, 30 * 1000);
|
|
116
|
+
this.log.write("info", `agent ${name}: started (pid ${child.pid}), logging to "${logFile}"`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
/* run the agent in this process (with the client of the agent relay, if any),
|
|
120
|
+
recording its process state from its successful setup until its termination,
|
|
121
|
+
where the start lock (held from re-checking the process state until recording
|
|
122
|
+
it) ensures that of concurrently started agents of the same name only one runs */
|
|
123
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
124
|
+
const unlock = await lockfile.lock(file, {
|
|
125
|
+
realpath: false, stale: startStale, update: startStale / 5,
|
|
126
|
+
onCompromised: (err) => {
|
|
127
|
+
this.log.write("warning", `agent ${name}: start lock compromised: ${err.message}`);
|
|
128
|
+
}
|
|
129
|
+
}).catch((err) => {
|
|
130
|
+
throw err.code === "ELOCKED" ?
|
|
131
|
+
new Error(`agent ${name}: already being started concurrently`) : err;
|
|
132
|
+
});
|
|
133
|
+
try {
|
|
134
|
+
const other = readState(file);
|
|
135
|
+
if (other !== null && await stateAlive(other))
|
|
136
|
+
throw new Error(`agent ${name}: already running (pid ${other.pid})`);
|
|
137
|
+
const spec = relaySpec(this.log, opts.agentRelay);
|
|
138
|
+
const relay = spec !== null ? new AgentRelayClient(this.log, spec) : null;
|
|
139
|
+
await this.agent.setup(agentDefaults(), opts, relay);
|
|
140
|
+
writeState(file, { kind, pid: process.pid, start: await pidStart(process.pid) });
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await unlock();
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await this.agent.run();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
if (readState(file)?.pid === process.pid)
|
|
150
|
+
fs.rmSync(file, { force: true });
|
|
151
|
+
}
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
/* status flow: report whether the agent is running */
|
|
155
|
+
async doStatus(name) {
|
|
156
|
+
validateName(name, this.agent.kind, [...this.kinds.keys()]);
|
|
157
|
+
const state = readState(this.file(name, "yaml"));
|
|
158
|
+
if (state === null || !(await stateAlive(state))) {
|
|
159
|
+
await writeStdout(`agent ${name}: not running\n`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
if (state.kind !== this.agent.kind) {
|
|
163
|
+
await writeStdout(`agent ${name}: not running (name taken by a running ${state.kind} agent, pid ${state.pid})\n`);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
await writeStdout(`agent ${name}: running (pid ${state.pid})\n`);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
/* stop flow: terminate the agent process and wait until it is gone
|
|
170
|
+
(where the agent awaits its aborted processings and their cleanups) */
|
|
171
|
+
async doStop(name) {
|
|
172
|
+
validateName(name, this.agent.kind, [...this.kinds.keys()]);
|
|
173
|
+
const file = this.file(name, "yaml");
|
|
174
|
+
const state = readState(file);
|
|
175
|
+
if (state === null || !(await stateAlive(state))) {
|
|
176
|
+
this.log.write("info", `agent ${name}: not running`);
|
|
177
|
+
fs.rmSync(file, { force: true });
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
if (state.kind !== this.agent.kind) {
|
|
181
|
+
this.log.write("info", `agent ${name}: not running (name taken by a running ${state.kind} agent, pid ${state.pid})`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
if (await this.terminate(state.pid, 5 * 60 * 1000))
|
|
185
|
+
this.log.write("info", `agent ${name}: stopped (process ${state.pid})`);
|
|
186
|
+
else
|
|
187
|
+
this.log.write("info", `agent ${name}: not running (process ${state.pid} already gone)`);
|
|
188
|
+
fs.rmSync(file, { force: true });
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
/* register the CLI sub-command group and its commands
|
|
192
|
+
"start", "status", "log", and "stop" below the CLI command <parent> */
|
|
193
|
+
register(parent) {
|
|
194
|
+
const kind = this.agent.kind;
|
|
195
|
+
const group = this.group(parent);
|
|
196
|
+
/* register CLI sub-command "start" (with the options of the agent) */
|
|
197
|
+
const start = this.agent.command();
|
|
198
|
+
start
|
|
199
|
+
.description(`Start the ${kind} agent in the background (idempotent if already running) or in the foreground`)
|
|
200
|
+
.option("-f, --foreground", "run the agent in the foreground, logging to the terminal, " +
|
|
201
|
+
"until it is interrupted or stopped")
|
|
202
|
+
.action(async (opts) => {
|
|
203
|
+
process.exitCode = await this.doStart(start, opts);
|
|
204
|
+
});
|
|
205
|
+
group.addCommand(start);
|
|
206
|
+
/* register CLI sub-command "status" */
|
|
207
|
+
group
|
|
208
|
+
.command("status")
|
|
209
|
+
.description(`Report whether the ${kind} agent is running`)
|
|
210
|
+
.option("-n, --name <name>", `name of the agent (default: "${kind}")`)
|
|
211
|
+
.action(async (opts) => {
|
|
212
|
+
process.exitCode = await this.doStatus(opts.name ?? kind);
|
|
213
|
+
});
|
|
214
|
+
/* register CLI sub-command "log" (with the name option in its long form only,
|
|
215
|
+
as the short form "-n" is the one of the number of lines, like in "tail") */
|
|
216
|
+
this.registerLog(group, (opts) => {
|
|
217
|
+
const name = opts.name ?? kind;
|
|
218
|
+
validateName(name, kind, [...this.kinds.keys()]);
|
|
219
|
+
return this.file(name, "log");
|
|
220
|
+
}, (cmd) => {
|
|
221
|
+
cmd.option("--name <name>", `name of the agent (default: "${kind}")`);
|
|
222
|
+
});
|
|
223
|
+
/* register CLI sub-command "stop" */
|
|
224
|
+
group
|
|
225
|
+
.command("stop")
|
|
226
|
+
.description(`Stop the ${kind} agent`)
|
|
227
|
+
.option("-n, --name <name>", `name of the agent (default: "${kind}")`)
|
|
228
|
+
.action(async (opts) => {
|
|
229
|
+
process.exitCode = await this.doStop(opts.name ?? kind);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|