Compare commits
2 Commits
docs/fleet
...
feat/p6-do
| Author | SHA1 | Date | |
|---|---|---|---|
| 996651c6f3 | |||
| 244290d64d |
@@ -54,7 +54,3 @@ Active workstream is **W1 — Federation v1**. Workers should:
|
|||||||
## P6 — Docs, compliance matrix, alpha tag (#606) — feat/p6-docs-compliance-alpha
|
## P6 — Docs, compliance matrix, alpha tag (#606) — feat/p6-docs-compliance-alpha
|
||||||
|
|
||||||
- Status: in-repo deliverables done (CONTRIBUTING.md + harness×gate compliance matrix + check-resident-budget.sh + CI wiring + ALPHA-DOD.md). Remaining: alpha tag v0.0.39-alpha (Lead, post-merge). aiguide reconcile merged (#8). Detail: scratchpads/p6-docs-compliance-alpha.md.
|
- Status: in-repo deliverables done (CONTRIBUTING.md + harness×gate compliance matrix + check-resident-budget.sh + CI wiring + ALPHA-DOD.md). Remaining: alpha tag v0.0.39-alpha (Lead, post-merge). aiguide reconcile merged (#8). Detail: scratchpads/p6-docs-compliance-alpha.md.
|
||||||
|
|
||||||
## F3-m3 — mosaic update re-seeds framework + relaunches agents (#609) — feat/f3-m3-update-reseed
|
|
||||||
|
|
||||||
- Status: implemented + tested. Closes R13: `mosaic update` now re-seeds the framework (data-safe MOSAIC_SYNC_ONLY) after the CLI install so shipped launcher/runtime changes activate; `--relaunch` restarts rostered agents; `--no-reseed` opts out. Detail: scratchpads/f3-m3-update-reseed.md.
|
|
||||||
|
|||||||
@@ -73,37 +73,6 @@ diff-sanity → squash-merge → verify), **decide-and-inform** cadence, and a d
|
|||||||
this model. See `mosaicstack-aiguide` whitepapers 01 (inter-agent comms) and 03
|
this model. See `mosaicstack-aiguide` whitepapers 01 (inter-agent comms) and 03
|
||||||
(orchestration model) for the rationale.
|
(orchestration model) for the rationale.
|
||||||
|
|
||||||
## Fleet roster — the two-agent floor and the role library
|
|
||||||
|
|
||||||
A fleet is **never a single agent**. The minimum viable fleet is **two**:
|
|
||||||
|
|
||||||
| Role | Mandate | Boundaries |
|
|
||||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
||||||
| **Orchestrator** | The user's **single point of contact**. Owns the general flow, keeps agentic actions on-target, and **adds/removes agents from the fleet at will** to meet goals and user needs. Exactly **one** per fleet (the existing R5 invariant). | Delegates source work; never the sole worker. |
|
|
||||||
| **Enhancer** | The fleet's **continuous-improvement loop**. Monitors fleet activity, analyzes for enhancements/optimizations, builds a **plan of remediation**, and — **with the orchestrator** — upgrades fleet capability: tool creation/repair, skills, harness improvements, and **bug reports filed to Mosaic Stack** for proper remediation. Recommends which agents are needed. | **Does not code, review code, or perform delivery tasks.** Improvement and diagnosis only. |
|
|
||||||
|
|
||||||
> **Why two, not one:** the orchestrator drives delivery; the enhancer makes the fleet
|
|
||||||
> _get better at delivering_ over time. The enhancer is how the fleet self-heals its tools,
|
|
||||||
> skills, and harnesses, and how real defects flow back to Mosaic Stack as bug reports.
|
|
||||||
> Together they are the irreducible core — every other role is added on demand.
|
|
||||||
|
|
||||||
A **general** fleet starts at this floor: the orchestrator (advised by the enhancer)
|
|
||||||
materializes whatever roles prove necessary over the mission's life. Specialized presets
|
|
||||||
(coding, research, etc.) seed additional roles up front, but all reduce to the same two-agent
|
|
||||||
spine plus an on-demand **role library**:
|
|
||||||
|
|
||||||
| Role profile | Purpose |
|
|
||||||
| ------------------- | --------------------------------------------------------------------------------- |
|
|
||||||
| **orchestrator** | point of contact, flow control, fleet composition (1 per fleet) |
|
|
||||||
| **enhancer** | fleet monitoring, optimization, tool/skill/harness upgrades, upstream bug reports |
|
|
||||||
| **coder** | implementation (worker; stops at PR-open) |
|
|
||||||
| **code review** | independent code review gate |
|
|
||||||
| **security review** | security/auth/secret review gate |
|
|
||||||
| **research** | investigation, synthesis, options analysis |
|
|
||||||
| **board** | deliberation panel — moonshot, contrarian, technical, business, financial lenses |
|
|
||||||
| **operations** | infra, deploy, health, incident response |
|
|
||||||
| _…extensible_ | new profiles added as missions demand (orchestrator + enhancer decide) |
|
|
||||||
|
|
||||||
## Invariants — "maximal vision, incremental delivery, zero foreclosure"
|
## Invariants — "maximal vision, incremental delivery, zero foreclosure"
|
||||||
|
|
||||||
Every artifact, starting Phase 2, MUST:
|
Every artifact, starting Phase 2, MUST:
|
||||||
@@ -133,7 +102,7 @@ Every artifact, starting Phase 2, MUST:
|
|||||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||||
| 0–1 | tmux PoC, hardening, published CLI v0.0.34 (#565–#568) | ✅ done |
|
| 0–1 | tmux PoC, hardening, published CLI v0.0.34 (#565–#568) | ✅ done |
|
||||||
| **2 — Observability** | `fleet ps` (host+tenant aware join), heartbeat protocol + dogfood stub answers it, `agent watch` (read-only), `agent send --verify` receipts | ▶ now |
|
| **2 — Observability** | `fleet ps` (host+tenant aware join), heartbeat protocol + dogfood stub answers it, `agent watch` (read-only), `agent send --verify` receipts | ▶ now |
|
||||||
| 3 — Real runtimes | claude/codex/pi/opencode answer heartbeat; **hybrid lifecycle** (core always-on: **orchestrator + enhancer**; ephemeral workers per lane) | planned |
|
| 3 — Real runtimes | claude/codex/pi/opencode answer heartbeat; **hybrid lifecycle** (core always-on: orchestrator+reviewer; ephemeral workers per lane) | planned |
|
||||||
| 4 — Unified definition | one agent schema in gateway; `mosaic agent --new` → materialized per-tenant session; uid-tenant provisioning | planned |
|
| 4 — Unified definition | one agent schema in gateway; `mosaic agent --new` → materialized per-tenant session; uid-tenant provisioning | planned |
|
||||||
| 5 — Control plane | federation-backed cross-host × cross-tenant fleet view; **webUI** (surface chosen then) for MVP-X1 parity | planned |
|
| 5 — Control plane | federation-backed cross-host × cross-tenant fleet view; **webUI** (surface chosen then) for MVP-X1 parity | planned |
|
||||||
|
|
||||||
@@ -152,28 +121,6 @@ Every artifact, starting Phase 2, MUST:
|
|||||||
runtime-bin on PATH (baked into the pane command) + boot-survival (`enable` + linger),
|
runtime-bin on PATH (baked into the pane command) + boot-survival (`enable` + linger),
|
||||||
which `fleet init` should automate.
|
which `fleet init` should automate.
|
||||||
|
|
||||||
## Decisions of record (2026-06-22, with Jason)
|
|
||||||
|
|
||||||
- **Two-agent floor:** every fleet has, at minimum, an **orchestrator** and an **enhancer**.
|
|
||||||
The orchestrator is the user's point of contact and composes the fleet; the enhancer runs the
|
|
||||||
continuous-improvement loop (monitor → analyze → remediate → upgrade tools/skills/harness →
|
|
||||||
file Mosaic Stack bug reports) and **does not code or review**.
|
|
||||||
- **Role library:** orchestrator, enhancer, coder, code review, security review, research,
|
|
||||||
board (moonshot/contrarian/technical/business/financial), operations — extensible; the
|
|
||||||
orchestrator (advised by the enhancer) adds roles as missions demand.
|
|
||||||
- **Orchestrator chat connector:** the orchestrator is reachable over a user-chosen connector
|
|
||||||
(tmux now; Telegram/Discord/Matrix/Slack configurable). Validated live: **"Mos" orchestrator
|
|
||||||
on Discord** via the Claude Code discord channel plugin (w-jarvis).
|
|
||||||
|
|
||||||
## Future enhancements (north-star, post-MVP — not on the MVP track)
|
|
||||||
|
|
||||||
- **Mosaic Claude Discord Plugin** — a first-party Mosaic Discord connector that properly
|
|
||||||
implements the basic Discord functions **and native Discord threads**. Threads let a user
|
|
||||||
separate conversation topics with the orchestrator (the pattern proven by the Hermes agent).
|
|
||||||
A major enhancement over the current third-party channel plugin; **not required for the MVP**,
|
|
||||||
but a committed north-star target. `ASSUMPTION:` ships as a Mosaic-owned plugin so the fleet
|
|
||||||
controls Discord UX (threads, reactions, attachments, per-thread context) end-to-end.
|
|
||||||
|
|
||||||
## Assumptions (veto-able)
|
## Assumptions (veto-able)
|
||||||
|
|
||||||
- `ASSUMPTION:` first-class runtimes = claude, codex, pi, opencode; a "role" (analyst,
|
- `ASSUMPTION:` first-class runtimes = claude, codex, pi, opencode; a "role" (analyst,
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
# F3-m3 — `mosaic update` re-seeds framework + relaunches agents (R13)
|
|
||||||
|
|
||||||
- **Issue:** #609 · **Branch:** `feat/f3-m3-update-reseed`
|
|
||||||
|
|
||||||
## Gap (found in 0.0.39 production validation)
|
|
||||||
|
|
||||||
`mosaic update` installs the new npm CLI but never re-seeds `~/.config/mosaic/` from the package's
|
|
||||||
bundled `framework/`. So the shipped custom Pi harness (agent-name export + native HB, 0.0.39) stays
|
|
||||||
DORMANT until a re-seed — operators get the new CLI on a stale framework.
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
- `update-checker.ts`: `resolveBundledFrameworkRoot()`, `buildReseedCommand()` (install.sh in
|
|
||||||
`MOSAIC_SYNC_ONLY=1 MOSAIC_INSTALL_MODE=keep` — the P4 data-safe reconcile), `runFrameworkReseed()`,
|
|
||||||
`readRosterAgentNames()`, `buildRelaunchCommands()` (systemctl --user restart per agent).
|
|
||||||
- `cli.ts` `update`: after a successful CLI install that includes `@mosaicstack/mosaic`, re-seed the
|
|
||||||
framework (default-on; `--no-reseed` to skip). Then either `--relaunch` (restart rostered agents) or
|
|
||||||
print clear guidance to run `mosaic update --relaunch` / `mosaic fleet restart`.
|
|
||||||
|
|
||||||
## Flow
|
|
||||||
|
|
||||||
`update CLI → re-seed framework (data-safe) → relaunch agents (opt-in)` — closes R13, activates the
|
|
||||||
native harness for every operator.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- 6 new unit tests (reseed command/env, relaunch commands, roster parse, missing-installer guard).
|
|
||||||
- 19 runtime + 26 launch tests still green; tsc/eslint/prettier clean.
|
|
||||||
- Data-safety of the sync is already proven (P4 5-fixture matrix + live dragon-lin validation).
|
|
||||||
@@ -26,10 +26,6 @@ import {
|
|||||||
checkForAllUpdates,
|
checkForAllUpdates,
|
||||||
formatAllPackagesTable,
|
formatAllPackagesTable,
|
||||||
getInstallAllCommand,
|
getInstallAllCommand,
|
||||||
runFrameworkReseed,
|
|
||||||
readRosterAgentNames,
|
|
||||||
buildRelaunchCommands,
|
|
||||||
FRAMEWORK_RESEED_PACKAGE,
|
|
||||||
} from './runtime/update-checker.js';
|
} from './runtime/update-checker.js';
|
||||||
import { runWizard } from './wizard.js';
|
import { runWizard } from './wizard.js';
|
||||||
import { ClackPrompter } from './prompter/clack-prompter.js';
|
import { ClackPrompter } from './prompter/clack-prompter.js';
|
||||||
@@ -408,12 +404,7 @@ program
|
|||||||
.command('update')
|
.command('update')
|
||||||
.description('Check for and install Mosaic CLI updates')
|
.description('Check for and install Mosaic CLI updates')
|
||||||
.option('--check', 'Check only, do not install')
|
.option('--check', 'Check only, do not install')
|
||||||
.option(
|
.action(async (opts: { check?: boolean }) => {
|
||||||
'--no-reseed',
|
|
||||||
'Skip re-seeding framework files into ~/.config/mosaic after the CLI update',
|
|
||||||
)
|
|
||||||
.option('--relaunch', 'Restart durable fleet agents so the new launcher/runtime takes effect')
|
|
||||||
.action(async (opts: { check?: boolean; reseed?: boolean; relaunch?: boolean }) => {
|
|
||||||
// checkForAllUpdates imported statically above
|
// checkForAllUpdates imported statically above
|
||||||
const { execSync } = await import('node:child_process');
|
const { execSync } = await import('node:child_process');
|
||||||
|
|
||||||
@@ -451,51 +442,6 @@ program
|
|||||||
console.error('\nUpdate failed. Try manually: bash tools/install.sh');
|
console.error('\nUpdate failed. Try manually: bash tools/install.sh');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// F3-m3 / R13: the CLI is updated, but the framework files in
|
|
||||||
// ~/.config/mosaic/ are still the previous version. Re-seed them from the
|
|
||||||
// freshly-installed package so shipped launcher/runtime changes ACTIVATE.
|
|
||||||
// Only when the framework-bearing package itself updated.
|
|
||||||
const mosaicUpdated = outdated.some(
|
|
||||||
(r: { package: string }) => r.package === FRAMEWORK_RESEED_PACKAGE,
|
|
||||||
);
|
|
||||||
if (mosaicUpdated && opts.reseed !== false) {
|
|
||||||
console.log(
|
|
||||||
'\nRe-seeding framework files into ~/.config/mosaic (data-safe; keeps your edits)…',
|
|
||||||
);
|
|
||||||
const reseed = runFrameworkReseed();
|
|
||||||
if (reseed.ok) {
|
|
||||||
console.log('✔ Framework re-seeded.');
|
|
||||||
const agents = readRosterAgentNames();
|
|
||||||
if (agents.length > 0) {
|
|
||||||
if (opts.relaunch) {
|
|
||||||
console.log(
|
|
||||||
`\nRelaunching ${agents.length} fleet agent(s) to pick up the new runtime…`,
|
|
||||||
);
|
|
||||||
for (const restart of buildRelaunchCommands(agents)) {
|
|
||||||
try {
|
|
||||||
execSync(restart.join(' '), { stdio: 'inherit', timeout: 30_000 });
|
|
||||||
} catch {
|
|
||||||
console.error(` ⚠ failed to restart agent — run: ${restart.join(' ')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('✔ Agents relaunched.');
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`\nℹ ${agents.length} fleet agent(s) are still running the previous runtime. ` +
|
|
||||||
'Restart them to activate the update:\n mosaic update --relaunch ' +
|
|
||||||
'(or: mosaic fleet restart <agent>)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
`\n⚠ Framework re-seed skipped: ${reseed.reason ?? 'unknown'}.\n` +
|
|
||||||
' Activate manually: bash "$(npm root -g)/@mosaicstack/mosaic/framework/install.sh" ' +
|
|
||||||
'(MOSAIC_SYNC_ONLY=1 MOSAIC_INSTALL_MODE=keep)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── wizard ─────────────────────────────────────────────────────────────
|
// ─── wizard ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
||||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
||||||
import { tmpdir } from 'node:os';
|
|
||||||
import { join } from 'node:path';
|
|
||||||
import {
|
|
||||||
buildReseedCommand,
|
|
||||||
buildRelaunchCommands,
|
|
||||||
readRosterAgentNames,
|
|
||||||
runFrameworkReseed,
|
|
||||||
} from './update-checker.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* F3-m3 / R13: `mosaic update` re-seeds the framework + (opt-in) relaunches
|
|
||||||
* durable agents so shipped launcher/runtime changes activate. These cover the
|
|
||||||
* pure builders + the missing-installer guard (the exec path is integration).
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('buildReseedCommand', () => {
|
|
||||||
it('invokes the package install.sh in data-safe sync-only keep mode', () => {
|
|
||||||
const out = buildReseedCommand('/pkg/framework', '/home/u/.config/mosaic');
|
|
||||||
expect(out.installer).toBe('/pkg/framework/install.sh');
|
|
||||||
expect(out.command).toBe('bash /pkg/framework/install.sh');
|
|
||||||
expect(out.env).toEqual({
|
|
||||||
MOSAIC_SYNC_ONLY: '1',
|
|
||||||
MOSAIC_INSTALL_MODE: 'keep',
|
|
||||||
MOSAIC_HOME: '/home/u/.config/mosaic',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('buildRelaunchCommands', () => {
|
|
||||||
it('builds a systemctl --user restart per agent unit', () => {
|
|
||||||
expect(buildRelaunchCommands(['orchestrator', 'coder0'])).toEqual([
|
|
||||||
['systemctl', '--user', 'restart', 'mosaic-agent@orchestrator.service'],
|
|
||||||
['systemctl', '--user', 'restart', 'mosaic-agent@coder0.service'],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is empty for an empty roster', () => {
|
|
||||||
expect(buildRelaunchCommands([])).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('readRosterAgentNames', () => {
|
|
||||||
let home: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
home = mkdtempSync(join(tmpdir(), 'mosaic-roster-'));
|
|
||||||
});
|
|
||||||
afterEach(() => {
|
|
||||||
rmSync(home, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns [] when no roster exists', () => {
|
|
||||||
expect(readRosterAgentNames(home)).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('extracts agent names from roster.yaml', () => {
|
|
||||||
mkdirSync(join(home, 'fleet'), { recursive: true });
|
|
||||||
writeFileSync(
|
|
||||||
join(home, 'fleet', 'roster.yaml'),
|
|
||||||
[
|
|
||||||
'version: 1',
|
|
||||||
'agents:',
|
|
||||||
' - name: orchestrator',
|
|
||||||
' runtime: pi',
|
|
||||||
' - name: coder0',
|
|
||||||
' runtime: claude',
|
|
||||||
' - name: "reviewer-1"',
|
|
||||||
' runtime: codex',
|
|
||||||
].join('\n') + '\n',
|
|
||||||
);
|
|
||||||
expect(readRosterAgentNames(home)).toEqual(['orchestrator', 'coder0', 'reviewer-1']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('runFrameworkReseed', () => {
|
|
||||||
it('reports not-ok (not throw) when the installer is absent', () => {
|
|
||||||
const missing = mkdtempSync(join(tmpdir(), 'mosaic-noinstaller-'));
|
|
||||||
const res = runFrameworkReseed(missing, join(missing, 'home'));
|
|
||||||
expect(res.ok).toBe(false);
|
|
||||||
expect(res.reason).toContain('installer not found');
|
|
||||||
rmSync(missing, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -16,8 +16,7 @@
|
|||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
|
|
||||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -454,98 +453,6 @@ export function getInstallAllCommand(outdated: PackageUpdateResult[]): string {
|
|||||||
return `npm i -g ${pkgs.join(' ')}`;
|
return `npm i -g ${pkgs.join(' ')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Post-update framework re-seed + agent relaunch (F3-m3 / R13) ─────────────
|
|
||||||
//
|
|
||||||
// `mosaic update` installs the new npm CLI but, on its own, leaves the framework
|
|
||||||
// files in ~/.config/mosaic/ stale — so shipped launcher/runtime changes (e.g.
|
|
||||||
// the agent-name export + native heartbeat) never ACTIVATE until a re-seed.
|
|
||||||
// These helpers run the package's own install.sh in sync-only mode (the P4
|
|
||||||
// data-safe reconcile: framework-owned overwrite + backup-once; SOUL/USER/
|
|
||||||
// *.local/credentials preserved) and, opt-in, relaunch durable agents.
|
|
||||||
|
|
||||||
/** Resolve the framework/ directory bundled in the installed package. */
|
|
||||||
export function resolveBundledFrameworkRoot(): string {
|
|
||||||
// dist/runtime/update-checker.js → ../../framework (package files: dist + framework)
|
|
||||||
return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'framework');
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FRAMEWORK_RESEED_PACKAGE = PKG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the framework re-seed invocation: the package's install.sh in
|
|
||||||
* sync-only mode (file phase only — no environment-touching post-install),
|
|
||||||
* keep mode (never overwrite user files). Returned as data so it is unit
|
|
||||||
* testable; `runFrameworkReseed` executes it.
|
|
||||||
*/
|
|
||||||
export function buildReseedCommand(
|
|
||||||
frameworkRoot: string,
|
|
||||||
mosaicHome: string,
|
|
||||||
): { installer: string; command: string; env: Record<string, string> } {
|
|
||||||
const installer = join(frameworkRoot, 'install.sh');
|
|
||||||
return {
|
|
||||||
installer,
|
|
||||||
command: `bash ${installer}`,
|
|
||||||
env: {
|
|
||||||
MOSAIC_SYNC_ONLY: '1',
|
|
||||||
MOSAIC_INSTALL_MODE: 'keep',
|
|
||||||
MOSAIC_HOME: mosaicHome,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-seed the framework from the freshly-installed package. Returns a result
|
|
||||||
* describing what happened (so callers can message + decide on relaunch).
|
|
||||||
* Best-effort: a missing installer or a non-zero exit is reported, not thrown.
|
|
||||||
*/
|
|
||||||
export function runFrameworkReseed(
|
|
||||||
frameworkRoot = resolveBundledFrameworkRoot(),
|
|
||||||
mosaicHome = join(homedir(), '.config', 'mosaic'),
|
|
||||||
): { ok: boolean; reason?: string } {
|
|
||||||
const { installer, command, env } = buildReseedCommand(frameworkRoot, mosaicHome);
|
|
||||||
if (!existsSync(installer)) {
|
|
||||||
return { ok: false, reason: `installer not found: ${installer}` };
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
execSync(command, { stdio: 'inherit', env: { ...process.env, ...env }, timeout: 120_000 });
|
|
||||||
return { ok: true };
|
|
||||||
} catch (err) {
|
|
||||||
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Best-effort parse of the fleet roster for agent names (used to relaunch
|
|
||||||
* durable agents after a re-seed). Returns [] when no roster exists.
|
|
||||||
*/
|
|
||||||
export function readRosterAgentNames(mosaicHome = join(homedir(), '.config', 'mosaic')): string[] {
|
|
||||||
const rosterPath = join(mosaicHome, 'fleet', 'roster.yaml');
|
|
||||||
if (!existsSync(rosterPath)) return [];
|
|
||||||
let text: string;
|
|
||||||
try {
|
|
||||||
text = readFileSync(rosterPath, 'utf-8');
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// Roster agents are listed as `- name: <id>` entries under `agents:`.
|
|
||||||
const names: string[] = [];
|
|
||||||
for (const line of text.split('\n')) {
|
|
||||||
const m = line.match(/^\s*-?\s*name:\s*["']?([A-Za-z0-9._-]+)["']?\s*$/);
|
|
||||||
if (m && m[1]) names.push(m[1]);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build the per-agent systemd relaunch commands (drain+relaunch via restart). */
|
|
||||||
export function buildRelaunchCommands(agentNames: string[]): string[][] {
|
|
||||||
return agentNames.map((name) => [
|
|
||||||
'systemctl',
|
|
||||||
'--user',
|
|
||||||
'restart',
|
|
||||||
`mosaic-agent@${name}.service`,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a table showing all packages with their current/latest versions.
|
* Format a table showing all packages with their current/latest versions.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user