@astrofoundry/pi-astro 0.20.1 → 0.20.3
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
@@ -107,3 +107,16 @@ describe("discovery", () => {
|
|
|
107
107
|
expect(missing).toEqual(["nope"]);
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
|
+
|
|
111
|
+
describe("bundled agent files", () => {
|
|
112
|
+
it("every agents/*.md parses and declares a specialist-safe front matter", () => {
|
|
113
|
+
const dir = join(import.meta.dirname, "..", "..", "agents");
|
|
114
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
115
|
+
expect(files.length).toBeGreaterThan(0);
|
|
116
|
+
for (const file of files) {
|
|
117
|
+
const agent = parseAgentFile(readFileSync(join(dir, file), "utf-8"), join(dir, file), "bundled");
|
|
118
|
+
expect(agent, file).not.toBeNull();
|
|
119
|
+
expect(agent?.name, file).toBe(`astro.${file.replace(/\.md$/, "")}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
package/package.json
CHANGED
|
@@ -31,13 +31,14 @@ A model load after restart takes up to a minute; `health=503` during that time i
|
|
|
31
31
|
| `hermes-api-health` | `/health` (expect 200) and `/v1/models` (expect 401: the endpoint is up and requires the backend key) |
|
|
32
32
|
| `hermes-version` | installed version |
|
|
33
33
|
| `hermes-guest-status`, `hermes-snapshots` | LXC 101 state and snapshots (Pulsar) |
|
|
34
|
-
| `hermes-releases [n]` | upstream releases of `NousResearch/hermes-agent
|
|
34
|
+
| `hermes-releases [n]` | upstream releases of `NousResearch/hermes-agent`, notes cut at 6000 characters |
|
|
35
|
+
| `hermes-release <tag>` | one release with its complete notes |
|
|
35
36
|
| `hermes-upgrade --confirm` | Pulsar wrapper: snapshot `preupgrade<timestamp>`, `hermes update`, `hermes config migrate`, restart both gateways, wait for a Discord connection and API 401, keep the two newest snapshots |
|
|
36
37
|
|
|
37
38
|
## Hermes upgrade procedure
|
|
38
39
|
|
|
39
40
|
1. `hermes-version` and `hermes-releases 10`.
|
|
40
|
-
2. Read every release from the installed version up to the target. Stop and report when a note mentions a manual step, a configuration change beyond `hermes config migrate`, a breaking change, a new Python or system dependency, or a required intermediate version. Quote the sentence.
|
|
41
|
+
2. Read every release from the installed version up to the target; use `hermes-release <tag>` for any note the list cut off. Stop and report when a note mentions a manual step, a configuration change beyond `hermes config migrate`, a breaking change, a new Python or system dependency, or a required intermediate version. Quote the sentence.
|
|
41
42
|
3. `hermes-status` and `hermes-api-health` must be healthy before the upgrade; `hermes-snapshots` shows the existing snapshots.
|
|
42
43
|
4. `hermes-upgrade --confirm` (up to 20 minutes). The wrapper prints the rollback command when a step fails; repeat it verbatim in your report and do not roll back yourself.
|
|
43
44
|
5. `hermes-version`, `hermes-status`, `hermes-api-health`. Report old and new version, the snapshot name, and the health results.
|
|
@@ -14,7 +14,7 @@ port() { case "$1" in embed) echo 8081 ;; rerank) echo 8082 ;; esac; }
|
|
|
14
14
|
agent_state() {
|
|
15
15
|
l="$(label "$1")"
|
|
16
16
|
printf '%-8s ' "$1"
|
|
17
|
-
launchctl print "gui/$uid/$l" 2>/dev/null | grep -E '
|
|
17
|
+
launchctl print "gui/$uid/$l" 2>/dev/null | grep -E '^[[:space:]](state|pid) = ' | tr -s ' ' | tr '\n' ' ' || printf 'not loaded '
|
|
18
18
|
printf 'health=%s\n' "$(curl -s -o /dev/null -w '%{http_code}' -m 5 "http://127.0.0.1:$(port "$1")/health" || echo 000)"
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -75,7 +75,8 @@ ${Object.entries(INFERENCE_COMMANDS)
|
|
|
75
75
|
.map(([name, c]) => ` ${name.padEnd(22)} ${c.help}`)
|
|
76
76
|
.join("\n")}
|
|
77
77
|
europa-health GET the Europa embedding /health over HTTP (Europa is usually off by design)
|
|
78
|
-
hermes-releases [n] latest n upstream Hermes releases
|
|
78
|
+
hermes-releases [n] latest n upstream Hermes releases, notes cut at 6000 chars (default 5)
|
|
79
|
+
hermes-release <tag> one release with its full notes; read every release between the installed version and the target before hermes-upgrade
|
|
79
80
|
|
|
80
81
|
Europa control and Hermes configuration are operator tasks.`;
|
|
81
82
|
|
|
@@ -121,16 +122,29 @@ export function compactReleases(releases: GithubRelease[]): { tag: string; name:
|
|
|
121
122
|
}));
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
const RELEASE_TAG = /^[A-Za-z0-9._-]{1,60}$/;
|
|
126
|
+
|
|
127
|
+
async function github(config: InferenceConfig, path: string): Promise<unknown> {
|
|
127
128
|
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(config.hermesRepo)) throw new ServiceError("hermesRepo must be owner/name");
|
|
128
|
-
const response = await fetch(`https://api.github.com/repos/${config.hermesRepo}
|
|
129
|
+
const response = await fetch(`https://api.github.com/repos/${config.hermesRepo}${path}`, {
|
|
129
130
|
headers: { Accept: "application/vnd.github+json", "User-Agent": "pi-astro-inference-specialist" },
|
|
130
131
|
signal: AbortSignal.timeout(30_000),
|
|
131
132
|
});
|
|
132
|
-
if (!response.ok) throw new ServiceError(`GitHub answered ${response.status} for ${config.hermesRepo}
|
|
133
|
-
|
|
133
|
+
if (!response.ok) throw new ServiceError(`GitHub answered ${response.status} for ${config.hermesRepo}${path}`);
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function hermesReleases(config: InferenceConfig, countArg: string | undefined): Promise<number> {
|
|
138
|
+
const n = countArg === undefined ? 5 : Number(countArg);
|
|
139
|
+
if (!Number.isInteger(n) || n < 1 || n > 30) throw new UsageError("hermes-releases [n]: n must be 1..30");
|
|
140
|
+
printJson({ repository: config.hermesRepo, releases: compactReleases((await github(config, `/releases?per_page=${n}`)) as GithubRelease[]) });
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function hermesRelease(config: InferenceConfig, tag: string | undefined): Promise<number> {
|
|
145
|
+
if (tag === undefined || !RELEASE_TAG.test(tag)) throw new UsageError("hermes-release <tag>, e.g. v2026.9.14");
|
|
146
|
+
const release = (await github(config, `/releases/tags/${encodeURIComponent(tag)}`)) as GithubRelease;
|
|
147
|
+
printJson({ repository: config.hermesRepo, ...compactReleases([release])[0], notes: release.body ?? "" });
|
|
134
148
|
return 0;
|
|
135
149
|
}
|
|
136
150
|
|
|
@@ -150,6 +164,7 @@ export async function command(args: string[]): Promise<number> {
|
|
|
150
164
|
const config = readConfig<InferenceConfig>(SERVICE, SHAPE);
|
|
151
165
|
if (args[0] === "europa-health") return europaHealth(config);
|
|
152
166
|
if (args[0] === "hermes-releases") return hermesReleases(config, args[1]);
|
|
167
|
+
if (args[0] === "hermes-release") return hermesRelease(config, args[1]);
|
|
153
168
|
const { remote, spec } = buildInferenceRemote(args);
|
|
154
169
|
const targets: Record<Target, { host: string; user: string; keyFile: string }> = {
|
|
155
170
|
nexus: { host: config.nexusHost, user: config.nexusUser, keyFile: secretPath(SERVICE, "SSH_KEY_NEXUS") },
|