Archived
feat: expose daemon-owned active agent profile
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import type { ActiveAgentProfileDescriptor } from "./apiTypes.js";
|
||||
|
||||
export const ACTIVE_AGENT_PROFILE_SCHEMA_VERSION = 1 as const;
|
||||
|
||||
const ACTIVE_AGENT_PROFILE_FIELDS = new Set([
|
||||
"schemaVersion",
|
||||
"revision",
|
||||
"command",
|
||||
"dir",
|
||||
"sessionDirEnvKeys",
|
||||
]);
|
||||
const SHA256_REVISION_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
||||
|
||||
export function parseActiveAgentProfileDescriptor(value: unknown): ActiveAgentProfileDescriptor | undefined {
|
||||
if (!isRecord(value) || Object.keys(value).some((key) => !ACTIVE_AGENT_PROFILE_FIELDS.has(key))) return undefined;
|
||||
|
||||
const schemaVersion = value["schemaVersion"];
|
||||
const revision = value["revision"];
|
||||
const command = value["command"];
|
||||
const dir = value["dir"];
|
||||
const sessionDirEnvKeys = value["sessionDirEnvKeys"];
|
||||
if (schemaVersion !== ACTIVE_AGENT_PROFILE_SCHEMA_VERSION) return undefined;
|
||||
if (typeof revision !== "string" || !SHA256_REVISION_PATTERN.test(revision)) return undefined;
|
||||
if (typeof command !== "string" || command === "" || typeof dir !== "string" || dir === "") return undefined;
|
||||
if (!isNonEmptyStringArray(sessionDirEnvKeys)) return undefined;
|
||||
if (new Set(sessionDirEnvKeys).size !== sessionDirEnvKeys.length) return undefined;
|
||||
|
||||
return Object.freeze({
|
||||
schemaVersion,
|
||||
revision,
|
||||
command,
|
||||
dir,
|
||||
sessionDirEnvKeys: Object.freeze([...sessionDirEnvKeys]),
|
||||
});
|
||||
}
|
||||
|
||||
function isNonEmptyStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && value.every((entry: unknown) => typeof entry === "string" && entry !== "");
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -593,12 +593,23 @@ export interface PiWebComponentStatus {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/** Secret-free identity of the Pi-compatible CLI/state profile fixed for one sessiond lifetime. */
|
||||
export interface ActiveAgentProfileDescriptor {
|
||||
readonly schemaVersion: 1;
|
||||
readonly revision: string;
|
||||
readonly command: string;
|
||||
readonly dir: string;
|
||||
readonly sessionDirEnvKeys: readonly string[];
|
||||
}
|
||||
|
||||
export interface PiWebRuntimeComponent {
|
||||
component: PiWebServiceComponent;
|
||||
label: string;
|
||||
runtimeVersion?: string;
|
||||
available: boolean;
|
||||
capabilities: PiWebCapability[];
|
||||
/** Present only for a session daemon that supports active-profile reporting. */
|
||||
activeAgentProfile?: ActiveAgentProfileDescriptor;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,56 @@ describe("PI WEB status parsing", () => {
|
||||
})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parses and freezes a session daemon active agent profile", () => {
|
||||
const parsed = parsePiWebRuntimeResponse({
|
||||
packageName: "@jmfederico/pi-web",
|
||||
generatedAt: "now",
|
||||
components: {
|
||||
web: { component: "web", label: "Web/UI", available: true, capabilities: [] },
|
||||
sessiond: {
|
||||
component: "sessiond",
|
||||
label: "Session daemon",
|
||||
available: true,
|
||||
capabilities: [],
|
||||
activeAgentProfile: {
|
||||
schemaVersion: 1,
|
||||
revision: `sha256:${"a".repeat(64)}`,
|
||||
command: "acme-agent",
|
||||
dir: "/opt/acme-agent/state",
|
||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR"],
|
||||
},
|
||||
},
|
||||
},
|
||||
capabilities: [],
|
||||
});
|
||||
|
||||
expect(parsed?.components.sessiond.activeAgentProfile).toMatchObject({ command: "acme-agent", dir: "/opt/acme-agent/state" });
|
||||
expect(Object.isFrozen(parsed?.components.sessiond.activeAgentProfile)).toBe(true);
|
||||
expect(Object.isFrozen(parsed?.components.sessiond.activeAgentProfile?.sessionDirEnvKeys)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects malformed, secret-bearing, or web-owned active profile descriptors", () => {
|
||||
const profile = {
|
||||
schemaVersion: 1,
|
||||
revision: `sha256:${"a".repeat(64)}`,
|
||||
command: "acme-agent",
|
||||
dir: "/opt/acme-agent/state",
|
||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR"],
|
||||
};
|
||||
const responseFor = (webProfile: unknown, sessiondProfile: unknown) => ({
|
||||
packageName: "@jmfederico/pi-web",
|
||||
generatedAt: "now",
|
||||
components: {
|
||||
web: { component: "web", label: "Web/UI", available: true, capabilities: [], ...(webProfile === undefined ? {} : { activeAgentProfile: webProfile }) },
|
||||
sessiond: { component: "sessiond", label: "Session daemon", available: true, capabilities: [], ...(sessiondProfile === undefined ? {} : { activeAgentProfile: sessiondProfile }) },
|
||||
},
|
||||
capabilities: [],
|
||||
});
|
||||
|
||||
expect(parsePiWebRuntimeResponse(responseFor(undefined, { ...profile, token: "secret" }))).toBeUndefined();
|
||||
expect(parsePiWebRuntimeResponse(responseFor(profile, undefined))).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parses Docker installation metadata", () => {
|
||||
expect(parsePiWebInstallationInfo({ kind: "docker", path: "/srv/pi-web-docker", dockerMode: "runtime" })).toEqual({
|
||||
kind: "docker",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PiWebComponentStatus, PiWebInstallationInfo, PiWebRuntimeComponent, PiWebRuntimeResponse, PiWebVersionResponse } from "./apiTypes.js";
|
||||
import { parseActiveAgentProfileDescriptor } from "./activeAgentProfile.js";
|
||||
import { parseKnownPiWebCapabilities } from "./capabilities.js";
|
||||
|
||||
export function parsePiWebVersionResponse(value: unknown): PiWebVersionResponse | undefined {
|
||||
@@ -33,15 +34,19 @@ export function parsePiWebRuntimeComponent(value: unknown): PiWebRuntimeComponen
|
||||
const runtimeVersion = value["runtimeVersion"];
|
||||
const available = value["available"];
|
||||
const capabilities = parseKnownPiWebCapabilities(value["capabilities"]);
|
||||
const activeAgentProfileValue = value["activeAgentProfile"];
|
||||
const activeAgentProfile = activeAgentProfileValue === undefined ? undefined : parseActiveAgentProfileDescriptor(activeAgentProfileValue);
|
||||
const error = value["error"];
|
||||
if (component !== "web" && component !== "sessiond") return undefined;
|
||||
if (typeof label !== "string" || label === "" || typeof available !== "boolean" || capabilities === undefined) return undefined;
|
||||
if (activeAgentProfileValue !== undefined && (component !== "sessiond" || activeAgentProfile === undefined)) return undefined;
|
||||
return {
|
||||
component,
|
||||
label,
|
||||
...(typeof runtimeVersion === "string" ? { runtimeVersion } : {}),
|
||||
available,
|
||||
capabilities,
|
||||
...(activeAgentProfile === undefined ? {} : { activeAgentProfile }),
|
||||
...(typeof error === "string" ? { error } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user