Add Colibri runtime version inventory
Add a structured runtime inventory schema, drift summary tests, and skills for Pi provider smoke tests plus Node/Pi/npm version synchronization across hosts and ISO build inputs. --- Build: pass | Tests: pass — 2485 passed (186 files)
This commit is contained in:
parent
4cf190deb8
commit
b26e4da118
6 changed files with 584 additions and 0 deletions
112
.agent/skills/pi-provider-smoke/SKILL.md
Normal file
112
.agent/skills/pi-provider-smoke/SKILL.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
name: pi-provider-smoke
|
||||
description: Smoke-test a Pi provider lane using structured JSON output. Use for DeepSeek, ZAI, or other Pi built-in providers before wiring them into Colibri.
|
||||
---
|
||||
|
||||
# Pi Provider Smoke
|
||||
|
||||
Validate that a provider works through `pi` and emits structured events that
|
||||
Colibri can consume.
|
||||
|
||||
## Rules
|
||||
|
||||
- Never paste API keys into chat, logs, commits, or command output.
|
||||
- Prefer Pi's credential store (`~/.pi/agent/auth.json`) or environment variables.
|
||||
- Do not hardcode model IDs before `pi --provider <name> --list-models` succeeds.
|
||||
- Use `--mode json` for the final smoke test.
|
||||
- One command at a time; inspect output before continuing.
|
||||
|
||||
## 1. Identify Pi
|
||||
|
||||
```bash
|
||||
which pi
|
||||
```
|
||||
|
||||
```bash
|
||||
pi --version
|
||||
```
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
Record host OS and install path.
|
||||
|
||||
## 2. Confirm provider models
|
||||
|
||||
For DeepSeek:
|
||||
|
||||
```bash
|
||||
pi --provider deepseek --list-models
|
||||
```
|
||||
|
||||
For ZAI:
|
||||
|
||||
```bash
|
||||
pi --provider zai --list-models
|
||||
```
|
||||
|
||||
If the command fails because credentials are missing, stop and ask the operator
|
||||
to add the key locally.
|
||||
|
||||
## 3. Credential layout
|
||||
|
||||
Pi persistent credentials live at:
|
||||
|
||||
```text
|
||||
~/.pi/agent/auth.json
|
||||
```
|
||||
|
||||
DeepSeek shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"deepseek": { "type": "api_key", "key": "<YOUR_DEEPSEEK_KEY>" }
|
||||
}
|
||||
```
|
||||
|
||||
ZAI uses the same shape with provider key `zai`.
|
||||
|
||||
Important:
|
||||
|
||||
- field is `key`, not `apiKey`
|
||||
- type is exactly `api_key`
|
||||
- `auth.json` wins over environment variables when both are set
|
||||
- never commit `auth.json`
|
||||
|
||||
## 4. JSON-mode smoke
|
||||
|
||||
Use a tiny deterministic prompt:
|
||||
|
||||
```bash
|
||||
pi --provider deepseek -p --mode json "reply with the single word: ok"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- exit code 0
|
||||
- output is JSONL
|
||||
- stream contains Pi lifecycle events such as `session`, `agent_start`,
|
||||
`message_update`, `turn_end`, `agent_end`
|
||||
- assistant text contains `ok`
|
||||
|
||||
## 5. Colibri parser check
|
||||
|
||||
If working inside `clawdie-ai`, save a short JSONL sample under repo `tmp/` and
|
||||
parse it with the Colibri tests/helpers. Do not store secrets in the sample.
|
||||
|
||||
```bash
|
||||
npx vitest run src/colibri-pi-events.test.ts
|
||||
```
|
||||
|
||||
## 6. Report
|
||||
|
||||
Report:
|
||||
|
||||
- OS and host
|
||||
- Pi version
|
||||
- Node version
|
||||
- provider name
|
||||
- exact model IDs listed, if relevant
|
||||
- JSON-mode smoke pass/fail
|
||||
- whether Colibri parser accepted a representative stream
|
||||
206
.agent/skills/runtime-version-sync/SKILL.md
Normal file
206
.agent/skills/runtime-version-sync/SKILL.md
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
---
|
||||
name: runtime-version-sync
|
||||
description: Check and align Node, FreeBSD pkg, npm global, and Pi versions across Linux and FreeBSD hosts. Use for preventing runtime drift between OSA, debby, domedog, and ISO builds.
|
||||
---
|
||||
|
||||
# Runtime Version Sync
|
||||
|
||||
Keep Linux, FreeBSD, and ISO runtime inputs aligned without relying on moving
|
||||
`latest` tags during builds.
|
||||
|
||||
## Rules
|
||||
|
||||
- Do not upgrade anything until the inventory is recorded.
|
||||
- Fetch remotes before claiming remote repository state.
|
||||
- For security-sensitive auth paths, inspect before mutating.
|
||||
- Pin build inputs; do not let ISO builds depend on moving npm dist-tags.
|
||||
- Prefer Node 24 across Linux and FreeBSD unless a target explicitly requires a
|
||||
legacy lane.
|
||||
- Use repo-local `tmp/` for generated inventories and manifests.
|
||||
|
||||
## 1. Inventory local host
|
||||
|
||||
```bash
|
||||
uname -a
|
||||
```
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
```bash
|
||||
npm --version
|
||||
```
|
||||
|
||||
```bash
|
||||
which pi
|
||||
```
|
||||
|
||||
```bash
|
||||
pi --version
|
||||
```
|
||||
|
||||
```bash
|
||||
npm config get prefix
|
||||
```
|
||||
|
||||
```bash
|
||||
npm outdated -g --depth=0
|
||||
```
|
||||
|
||||
On FreeBSD, also check packages:
|
||||
|
||||
```bash
|
||||
pkg info -x 'node|npm|pi|codex|gemini'
|
||||
```
|
||||
|
||||
On Linux with nvm:
|
||||
|
||||
```bash
|
||||
command -v nvm
|
||||
```
|
||||
|
||||
```bash
|
||||
nvm current
|
||||
```
|
||||
|
||||
## 2. Check upstream versions
|
||||
|
||||
Pi:
|
||||
|
||||
```bash
|
||||
npm view @earendil-works/pi-coding-agent version
|
||||
```
|
||||
|
||||
```bash
|
||||
npm view @earendil-works/pi-coding-agent dist-tags --json
|
||||
```
|
||||
|
||||
Gemini CLI, if still intentionally shipped:
|
||||
|
||||
```bash
|
||||
npm view @google/gemini-cli version
|
||||
```
|
||||
|
||||
FreeBSD package availability:
|
||||
|
||||
```bash
|
||||
pkg search '^node[0-9]+'
|
||||
```
|
||||
|
||||
```bash
|
||||
pkg search '^npm'
|
||||
```
|
||||
|
||||
## 3. Cross-host manifest
|
||||
|
||||
Each host should emit a small JSON manifest under repo-local or user-local
|
||||
state. Example schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "clawdie.runtime-version-inventory.v1",
|
||||
"host": "osa",
|
||||
"os": "FreeBSD",
|
||||
"node": "v24.14.1",
|
||||
"npm": "11.x",
|
||||
"pi": "0.75.5",
|
||||
"npm_prefix": "/home/clawdie/.npm-global",
|
||||
"package_manager": "pkg",
|
||||
"notes": []
|
||||
}
|
||||
```
|
||||
|
||||
Colibri can aggregate these manifests later, the same way it aggregates
|
||||
interagent run manifests.
|
||||
|
||||
## 4. Upgrade policy
|
||||
|
||||
### FreeBSD
|
||||
|
||||
Preferred Node package:
|
||||
|
||||
```text
|
||||
node24
|
||||
```
|
||||
|
||||
Use FreeBSD package operations only after checking current state and taking any
|
||||
needed ZFS/package rollback precautions.
|
||||
|
||||
Install/upgrade package names:
|
||||
|
||||
```bash
|
||||
sudo pkg install -y node24 npm-node24
|
||||
```
|
||||
|
||||
Then verify:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
```bash
|
||||
npm --version
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
If Node is managed by nvm, use nvm for Node 24:
|
||||
|
||||
```bash
|
||||
nvm install 24
|
||||
```
|
||||
|
||||
```bash
|
||||
nvm alias default 24
|
||||
```
|
||||
|
||||
If Node is system-managed, do not assume the package manager. Inventory first
|
||||
and choose the host's standard channel.
|
||||
|
||||
## 5. npm global policy
|
||||
|
||||
Pi should be updated through Pi when possible:
|
||||
|
||||
```bash
|
||||
pi update --self
|
||||
```
|
||||
|
||||
Fallback:
|
||||
|
||||
```bash
|
||||
npm install -g @earendil-works/pi-coding-agent@<pinned-version>
|
||||
```
|
||||
|
||||
ISO builds must use pinned npm globals from the ISO repo, not `latest`:
|
||||
|
||||
```text
|
||||
/home/clawdie/clawdie-iso/packages/npm-globals.txt
|
||||
```
|
||||
|
||||
## 6. ISO follow-up
|
||||
|
||||
When Pi or npm global versions change, update the ISO pin file in a separate
|
||||
ISO commit and smoke the pack step:
|
||||
|
||||
```bash
|
||||
sh -n scripts/fetch-npm-globals.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
OUT_DIR="$(pwd)/tmp/npm-globals-pin-smoke" ./scripts/fetch-npm-globals.sh
|
||||
```
|
||||
|
||||
Do not run a full ISO build unless explicitly assigned.
|
||||
|
||||
## 7. Report
|
||||
|
||||
Report:
|
||||
|
||||
- hosts checked
|
||||
- Node versions and desired target
|
||||
- Pi versions and desired target
|
||||
- npm global pins changed
|
||||
- package manager actions proposed or performed
|
||||
- whether ISO pin file is aligned
|
||||
- any blockers for Node 24 unification
|
||||
|
|
@ -167,6 +167,16 @@ skills:
|
|||
description: Update the pi coding-agent harness, including package rename migration and pi packages/extensions
|
||||
tags: [agent, ai-provider, update]
|
||||
|
||||
- id: pi-provider-smoke
|
||||
source: local:.agent/skills/pi-provider-smoke/SKILL.md
|
||||
description: Smoke-test a Pi provider lane using structured JSON output for Colibri validation
|
||||
tags: [agent, ai-provider, colibri, smoke]
|
||||
|
||||
- id: runtime-version-sync
|
||||
source: local:.agent/skills/runtime-version-sync/SKILL.md
|
||||
description: Check and align Node, FreeBSD pkg, npm global, and Pi versions across Linux, FreeBSD, and ISO builds
|
||||
tags: [agent, infra, update, colibri, node, npm]
|
||||
|
||||
- id: agent-setup
|
||||
source: local:.agent/skills/agent-setup/SKILL.md
|
||||
description: Set up the Clawdie runtime on FreeBSD — host orchestrator plus all service jails
|
||||
|
|
|
|||
|
|
@ -107,6 +107,37 @@ No legacy runner/status code should be removed until these are true:
|
|||
7. The network throughput coordination test has produced structured manifests
|
||||
from at least two hosts.
|
||||
|
||||
## Runtime Drift And Version Sync
|
||||
|
||||
Colibri should also become the coordination layer for boring but important
|
||||
runtime hygiene: Node, npm globals, Pi provider lanes, and ISO build inputs.
|
||||
|
||||
Current target:
|
||||
|
||||
```text
|
||||
Node: 24.x on Linux and FreeBSD
|
||||
Pi: pinned through each host inventory, not assumed from PATH
|
||||
ISO npm globals: pinned in the ISO repo, not fetched from moving latest tags
|
||||
```
|
||||
|
||||
Principles:
|
||||
|
||||
- each host emits a small version inventory manifest
|
||||
- the coordinator compares manifests before upgrades
|
||||
- FreeBSD package actions stay locally authorized and rollback-aware
|
||||
- Linux Node management respects the host manager (`nvm`, system package, etc.)
|
||||
- ISO build inputs are pinned separately so live USB rebuilds are reproducible
|
||||
|
||||
The first skills for this are:
|
||||
|
||||
- `pi-provider-smoke` — validates DeepSeek/ZAI/etc through Pi `--mode json`
|
||||
- `runtime-version-sync` — inventories and aligns Node, Pi, npm globals, and ISO
|
||||
pins across hosts
|
||||
|
||||
This is a natural cross-agent communication use case: OSA, debby, and domedog
|
||||
can each produce a manifest; Colibri aggregates the manifests and proposes the
|
||||
minimal safe changes.
|
||||
|
||||
## Branch Plan
|
||||
|
||||
Implementation branch:
|
||||
|
|
|
|||
104
src/colibri-runtime-inventory.test.ts
Normal file
104
src/colibri-runtime-inventory.test.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
COLIBRI_RUNTIME_INVENTORY_SCHEMA,
|
||||
buildRuntimeDriftReport,
|
||||
parseColibriRuntimeInventory,
|
||||
parseColibriRuntimeInventoryJson,
|
||||
summarizeRuntimeDriftReport,
|
||||
} from './colibri-runtime-inventory.js';
|
||||
|
||||
const PI_PACKAGE = '@earendil-works/pi-coding-agent';
|
||||
|
||||
const OSA_INVENTORY = {
|
||||
schema: COLIBRI_RUNTIME_INVENTORY_SCHEMA,
|
||||
host: 'osa',
|
||||
os: 'FreeBSD',
|
||||
node: 'v24.14.1',
|
||||
npm: '11.6.2',
|
||||
pi: '0.75.5',
|
||||
npm_prefix: '/home/clawdie/.npm-global',
|
||||
package_manager: 'pkg',
|
||||
iso_npm_globals_pin: {
|
||||
[PI_PACKAGE]: '0.75.5',
|
||||
},
|
||||
notes: [],
|
||||
};
|
||||
|
||||
describe('colibri runtime inventory', () => {
|
||||
it('parses a runtime version inventory manifest', () => {
|
||||
const result = parseColibriRuntimeInventory(OSA_INVENTORY);
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.inventory.host).toBe('osa');
|
||||
expect(result.inventory.node).toBe('v24.14.1');
|
||||
expect(result.inventory.iso_npm_globals_pin[PI_PACKAGE]).toBe('0.75.5');
|
||||
});
|
||||
|
||||
it('applies defaults for optional map and notes fields', () => {
|
||||
const result = parseColibriRuntimeInventory({
|
||||
schema: COLIBRI_RUNTIME_INVENTORY_SCHEMA,
|
||||
host: 'debby',
|
||||
os: 'Linux',
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.inventory.iso_npm_globals_pin).toEqual({});
|
||||
expect(result.inventory.notes).toEqual([]);
|
||||
});
|
||||
|
||||
it('reports invalid JSON text', () => {
|
||||
const result = parseColibriRuntimeInventoryJson('{bad');
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.errors[0]).toContain('invalid JSON');
|
||||
});
|
||||
|
||||
it('detects Node, Pi, and ISO pin drift across hosts', () => {
|
||||
const report = buildRuntimeDriftReport(
|
||||
[
|
||||
OSA_INVENTORY,
|
||||
{
|
||||
schema: COLIBRI_RUNTIME_INVENTORY_SCHEMA,
|
||||
host: 'debby',
|
||||
os: 'Linux',
|
||||
node: 'v20.19.0',
|
||||
pi: '0.75.5',
|
||||
iso_npm_globals_pin: {},
|
||||
notes: [],
|
||||
},
|
||||
{
|
||||
schema: COLIBRI_RUNTIME_INVENTORY_SCHEMA,
|
||||
host: 'oldfreebsd',
|
||||
os: 'FreeBSD',
|
||||
node: 'v24.10.0',
|
||||
pi: '0.74.0',
|
||||
iso_npm_globals_pin: {
|
||||
[PI_PACKAGE]: '0.74.0',
|
||||
},
|
||||
notes: [],
|
||||
},
|
||||
],
|
||||
{ targetNodeMajor: 24, targetPiVersion: '0.75.5' },
|
||||
);
|
||||
|
||||
expect(report.nodeDriftHosts).toEqual(['debby']);
|
||||
expect(report.piDriftHosts).toEqual(['oldfreebsd']);
|
||||
expect(report.isoPinDrift).toEqual(['oldfreebsd']);
|
||||
});
|
||||
|
||||
it('renders a compact drift report summary', () => {
|
||||
const report = buildRuntimeDriftReport([OSA_INVENTORY], {
|
||||
targetPiVersion: '0.75.5',
|
||||
});
|
||||
|
||||
const summary = summarizeRuntimeDriftReport(report);
|
||||
expect(summary).toContain('<colibri-runtime-drift>');
|
||||
expect(summary).toContain('target_node_major=24');
|
||||
expect(summary).toContain('target_pi_version=0.75.5');
|
||||
expect(summary).toContain('node_drift_hosts=none');
|
||||
});
|
||||
});
|
||||
121
src/colibri-runtime-inventory.ts
Normal file
121
src/colibri-runtime-inventory.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const COLIBRI_RUNTIME_INVENTORY_SCHEMA =
|
||||
'clawdie.runtime-version-inventory.v1' as const;
|
||||
|
||||
export const ColibriRuntimeInventorySchema = z.object({
|
||||
schema: z.literal(COLIBRI_RUNTIME_INVENTORY_SCHEMA),
|
||||
host: z.string().min(1),
|
||||
os: z.string().min(1),
|
||||
node: z.string().min(1).nullable().optional(),
|
||||
npm: z.string().min(1).nullable().optional(),
|
||||
pi: z.string().min(1).nullable().optional(),
|
||||
npm_prefix: z.string().min(1).nullable().optional(),
|
||||
package_manager: z.string().min(1).nullable().optional(),
|
||||
iso_npm_globals_pin: z.record(z.string(), z.string()).default({}),
|
||||
notes: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
export type ColibriRuntimeInventory = z.infer<
|
||||
typeof ColibriRuntimeInventorySchema
|
||||
>;
|
||||
|
||||
export type ColibriRuntimeInventoryParseResult =
|
||||
| { ok: true; inventory: ColibriRuntimeInventory }
|
||||
| { ok: false; errors: string[] };
|
||||
|
||||
export interface ColibriRuntimeDriftReport {
|
||||
targetNodeMajor: number;
|
||||
targetPiVersion?: string;
|
||||
hosts: string[];
|
||||
nodeDriftHosts: string[];
|
||||
piDriftHosts: string[];
|
||||
missingPiHosts: string[];
|
||||
isoPinDrift: string[];
|
||||
}
|
||||
|
||||
export function parseColibriRuntimeInventory(
|
||||
input: unknown,
|
||||
): ColibriRuntimeInventoryParseResult {
|
||||
const parsed = ColibriRuntimeInventorySchema.safeParse(input);
|
||||
if (parsed.success) return { ok: true, inventory: parsed.data };
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
errors: parsed.error.issues.map((issue) => {
|
||||
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
||||
return `${path}: ${issue.message}`;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseColibriRuntimeInventoryJson(
|
||||
text: string,
|
||||
): ColibriRuntimeInventoryParseResult {
|
||||
let raw: unknown;
|
||||
try {
|
||||
raw = JSON.parse(text);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return { ok: false, errors: [`invalid JSON: ${message}`] };
|
||||
}
|
||||
return parseColibriRuntimeInventory(raw);
|
||||
}
|
||||
|
||||
function nodeMajor(version: string | null | undefined): number | null {
|
||||
if (!version) return null;
|
||||
const match = version.trim().match(/^v?(\d+)\./u);
|
||||
if (!match?.[1]) return null;
|
||||
return Number.parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
export function buildRuntimeDriftReport(
|
||||
inventories: ColibriRuntimeInventory[],
|
||||
opts: { targetNodeMajor?: number; targetPiVersion?: string } = {},
|
||||
): ColibriRuntimeDriftReport {
|
||||
const targetNodeMajor = opts.targetNodeMajor ?? 24;
|
||||
const targetPiVersion = opts.targetPiVersion;
|
||||
const piPackage = '@earendil-works/pi-coding-agent';
|
||||
|
||||
return {
|
||||
targetNodeMajor,
|
||||
targetPiVersion,
|
||||
hosts: inventories.map((entry) => entry.host),
|
||||
nodeDriftHosts: inventories
|
||||
.filter((entry) => nodeMajor(entry.node) !== targetNodeMajor)
|
||||
.map((entry) => entry.host),
|
||||
piDriftHosts: targetPiVersion
|
||||
? inventories
|
||||
.filter((entry) => entry.pi !== targetPiVersion)
|
||||
.map((entry) => entry.host)
|
||||
: [],
|
||||
missingPiHosts: inventories
|
||||
.filter((entry) => !entry.pi)
|
||||
.map((entry) => entry.host),
|
||||
isoPinDrift: targetPiVersion
|
||||
? inventories
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.iso_npm_globals_pin[piPackage] !== undefined &&
|
||||
entry.iso_npm_globals_pin[piPackage] !== targetPiVersion,
|
||||
)
|
||||
.map((entry) => entry.host)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeRuntimeDriftReport(
|
||||
report: ColibriRuntimeDriftReport,
|
||||
): string {
|
||||
return [
|
||||
'<colibri-runtime-drift>',
|
||||
`target_node_major=${report.targetNodeMajor}`,
|
||||
`target_pi_version=${report.targetPiVersion ?? 'unspecified'}`,
|
||||
`hosts=${report.hosts.join(',') || 'none'}`,
|
||||
`node_drift_hosts=${report.nodeDriftHosts.join(',') || 'none'}`,
|
||||
`pi_drift_hosts=${report.piDriftHosts.join(',') || 'none'}`,
|
||||
`missing_pi_hosts=${report.missingPiHosts.join(',') || 'none'}`,
|
||||
`iso_pin_drift=${report.isoPinDrift.join(',') || 'none'}`,
|
||||
'</colibri-runtime-drift>',
|
||||
].join('\n');
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue