Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f50bd5a
feat: add optional host-root mode
YrFnS Jul 26, 2026
7092822
no-mistakes(review): Scope host-root lifecycle safeguards correctly
YrFnS Jul 26, 2026
8fe20d8
no-mistakes(document): Document host-root overlap and forge scoping
YrFnS Jul 26, 2026
1f5b72a
no-mistakes(review): Captain: harden host-root cleanup and task admis…
YrFnS Jul 26, 2026
2432ca6
no-mistakes(review): Harden host-root promotion and logical project r…
YrFnS Jul 26, 2026
a6322d3
no-mistakes(document): Consolidate host-root documentation ownership
YrFnS Jul 26, 2026
c95632c
no-mistakes(lint): Suppress intentional literal-dollar ShellCheck war…
YrFnS Jul 26, 2026
bb79a53
no-mistakes(review): Preserve logical host scout delivery modes
YrFnS Jul 26, 2026
3fa8b75
no-mistakes(document): Clarify local-only host-root scout documentation
YrFnS Jul 26, 2026
5fa2e7b
no-mistakes(review): Captain: Protect retained metadata and host-boun…
YrFnS Jul 26, 2026
7b68500
fix(host-root): preserve recovery and bind tmux aliases
YrFnS Jul 27, 2026
e027363
no-mistakes(review): Captain: protect host teardown and preserve Herd…
YrFnS Jul 27, 2026
2160899
no-mistakes(review): Captain: preserve retained host metadata across …
YrFnS Jul 27, 2026
cca6bc8
test(host-root): run nudge from synthetic root
YrFnS Jul 27, 2026
d101a60
no-mistakes(review): Captain, preserve host authority across aliases …
YrFnS Jul 27, 2026
890e50f
no-mistakes(review): Captain, preserve host children during recursive…
YrFnS Jul 27, 2026
b8ec131
no-mistakes(review): Captain, preserve indexed tmux and projected Her…
YrFnS Jul 27, 2026
48426fa
no-mistakes(review): Captain, stabilize tmux authority and teardown i…
YrFnS Jul 27, 2026
6fb5cdb
no-mistakes(review): Harden host-root cleanup and endpoint identity
YrFnS Jul 27, 2026
dc0787f
no-mistakes(review): Bind tmux identity and close cross-window cmux t…
YrFnS Jul 27, 2026
9ceb21a
no-mistakes(review): Bind host tmux tasks to their creating sockets
YrFnS Jul 27, 2026
d23fa34
no-mistakes(review): Scope tmux supervision and recognize vanished so…
YrFnS Jul 27, 2026
a6d74f5
test: bound Herdr abort fixture timing
YrFnS Jul 27, 2026
69e20c5
no-mistakes(review): Preserve tmux rollback metadata; Herdr acceptanc…
YrFnS Jul 28, 2026
ecdc436
no-mistakes(review): Captain: complete Herdr host-root decision-inven…
YrFnS Jul 28, 2026
3c9e75f
no-mistakes(document): Align host-root documentation with runtime saf…
YrFnS Jul 28, 2026
5a53e1f
no-mistakes(lint): Fix host-root ShellCheck findings
YrFnS Jul 28, 2026
b78475e
fix: reconcile host-root mode with endpoint hardening
YrFnS Jul 29, 2026
007ae82
feat: activate FirstMate from an external Pi host
YrFnS Jul 29, 2026
6c16b98
no-mistakes(review): Harden host-root overlap and metadata authority
YrFnS Jul 29, 2026
5f7bca5
no-mistakes(review): Enforce runtime host-home root isolation
YrFnS Jul 29, 2026
f61dd10
no-mistakes(document): Refresh host-root verification evidence
YrFnS Jul 29, 2026
811bdb1
no-mistakes(lint): Captain: fix ShellCheck empty local declaration
YrFnS Jul 29, 2026
eefbdbc
no-mistakes(review): Captain: guard host-root Pi workers from supervi…
YrFnS Jul 30, 2026
b55901e
no-mistakes(document): Document Pi host-root worker activation guard
YrFnS Jul 30, 2026
152ce0f
fix(host-root): launch workers from target worktrees
YrFnS Aug 1, 2026
211966d
no-mistakes(review): Captain: fix host-root review regressions
YrFnS Aug 1, 2026
7622c21
no-mistakes(document): Consolidate host-root verification evidence
YrFnS Aug 1, 2026
f4ab1c2
no-mistakes(document): Clarify host-root semantic-state evidence
YrFnS Aug 1, 2026
178352c
no-mistakes(review): Captain: fix host-root lifecycle and rollback re…
YrFnS Aug 2, 2026
03c25bc
no-mistakes(document): Correct host-root verification and startup doc…
YrFnS Aug 2, 2026
d36a214
fix(bin): retire terminal process events and surface queued wakes (#1…
kunchenguid Aug 2, 2026
2081e6f
fix(pi): notify supervisor only after worker settles
YrFnS Aug 2, 2026
fa5b30c
no-mistakes(review): Gate host-root Pi notifications on successful idle
YrFnS Aug 2, 2026
2c0459a
no-mistakes(review): Clarify Pi notification behavior by host mode
YrFnS Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/firstmate-orca/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Use raw `orca` only when the helper surface cannot answer the inspection questio
## Preflight

Work from the current firstmate home or repo root.
In host-root mode, work instead from the physical `FM_HOST_ROOT` and invoke FirstMate helpers by their absolute `FM_ROOT/bin/` paths under the [four-root contract](../../../docs/configuration.md#host-root-mode-fm_host_root).
If `FM_HOME` is set, remember that operational state lives under `$FM_HOME` while the helper scripts still run from this repo's `bin/`.

Before switching or spawning against Orca:
Expand Down
38 changes: 30 additions & 8 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ OpenCode uses `.opencode/plugins/fm-primary-watch-arm.js`, which coordinates wit
Pi and pi-signed use the tracked `.pi/extensions/fm-primary-turnend-guard.ts` plus the tracked `.pi/extensions/fm-primary-pi-watch.ts`, both project-local extensions the Pi engine auto-discovers once trusted.
When changing any primary watcher adapter, update `docs/supervision-protocols/`, `docs/turnend-guard.md` if a shared idle or turn-end hook changed, and the relevant concise fact below.

## Host-root task integration

When `FM_HOST_ROOT` is set, only the primary supervisor starts from that physical host root.
Ordinary ship and scout harnesses start from their isolated `FM_TARGET_WORKTREE`, so the target repository's instructions and lifecycle adapters load natively while the host context stays out of focused worker sessions.
FirstMate retains one task completion signal per harness and passes the host and target identities for supervision and recovery without changing the worker cwd:

| Harness | Task completion signal in host-root mode |
|---|---|
| claude | A state-owned settings file passed with `--settings`; target project settings still load from the worker cwd. |
| codex | The existing per-launch `notify` command. |
| opencode | A state-owned task plugin named through `OPENCODE_CONFIG_CONTENT`; target project plugins load from the worker cwd. |
| pi | The existing explicit state-owned `-e` task extension; target project extensions remain subject to target trust. |
| grok | The guarded global FirstMate Stop hook reads a per-process `FM_GROK_TURNEND_TOKEN`. |
| kimi | The guarded global FirstMate Stop hook reads a per-process `FM_KIMI_TURNEND_TOKEN`. |

Secondmate launches explicitly clear inherited `FM_HOST_ROOT` and `FM_TARGET_WORKTREE` and retain their isolated-home adapters.

[`docs/verification/supervision.md`](../../../docs/verification/supervision.md#host-root-task-integration) owns the dated lifecycle evidence and current live-verification limits for these task adapters.

## Launch profile axes

`bin/fm-spawn.sh` accepts concrete `--harness`, `--model`, and `--effort` values chosen by firstmate at intake.
Expand Down Expand Up @@ -289,8 +308,9 @@ Project trust dialog can appear on the first pi run in any not-yet-trusted direc
Accept with Enter.
The decision persists per path in `~/.pi/agent/trust.json`, so later spawns in the same worktree slot skip it.

`fm-spawn` keeps the turn-end extension in `state/`, outside the worktree, because project-local extension files make the trust gate strictly worse and pollute the project.
The extension must listen for pi's `turn_end` event, not `agent_end`, so the watcher wakes after each completed turn instead of only when the whole agent run exits.
`fm-spawn` keeps the completion extension in `state/`, outside the worktree, because project-local extension files make the trust gate strictly worse and pollute the project.
In host-root mode, the extension records semantic busy state at `agent_start`, then uses `agent_settled` with `ctx.isIdle()` to record idle before notifying the supervisor after Pi has no queued continuation.
Ordinary Pi workers retain their existing `turn_end` notification, while host-root workers keep it silent because it fires at inner response boundaries during one logical worker run.
Pi sets `PI_CODING_AGENT=true` for its children; this is its harness-detection env marker.

**Primary-session guard fact (verified 2026-07-09, Pi 0.80.5).**
Expand Down Expand Up @@ -323,7 +343,7 @@ The tmux backend's structural `fm_tmux_composer_state` read sees placeholder-fil
The Herdr adapter (`fm_backend_herdr_composer_state`, `bin/backends/herdr.sh`) classifies the composer's own row structurally instead of diffing raw content; see `docs/herdr-backend.md` "Composer and injection safety" for the current boundary and `tests/fm-backend-herdr.test.sh` for regression coverage.

Startup dialog: the "Run Grok Build in a project directory?" project picker appears ONLY when grok is launched from a non-project directory (home, Desktop, Downloads, `/tmp`).
`fm-spawn` launches inside the treehouse worktree (a git repo root), so the picker never appears and grok treats the worktree as a trusted project automatically - no post-launch keystroke is needed.
Every ordinary `fm-spawn`, including host-root mode, launches inside the isolated target worktree, which must be a recognized project directory to avoid the picker.
Pin `[hints] project_picker_disabled = true` in `~/.grok/config.toml` if a non-project launch ever needs to skip it.

**TRUECOLOR placeholder styling: covered (task afk-herdr-false-pending, 2026-07-10).**
Expand All @@ -342,11 +362,12 @@ Turn-end hook: grok fires a `Stop` hook at every turn boundary, giving firstmate
grok loads PROJECT hooks (`<worktree>/.grok/hooks/`, `<worktree>/.claude/settings.local.json`) only after the folder is granted hook-trust in `~/.grok/trusted_folders.toml`, which is not automatic and which firstmate will not establish by editing grok's own managed trust store.
GLOBAL hooks in `~/.grok/hooks/` are always trusted and load on first launch.
So `fm-spawn` installs ONE firstmate-owned global hook, `~/.grok/hooks/fm-turn-end.json`, plus the companion `~/.grok/hooks/fm-turn-end.sh`, guarded as a no-op for every non-firstmate grok session.
Its `Stop` command fires only when the current workspace holds a `.fm-grok-turnend` token pointer that matches the firstmate-owned hook registry under `~/.grok/hooks/fm-turn-end.d/`.
`fm-spawn` writes that per-task pointer (`<worktree>/.fm-grok-turnend`, gitignored via git info/exclude like the other harnesses' worktree hook files) and a matching registry entry naming this task's `state/<id>.turn-ended`.
The hook reads `$GROK_WORKSPACE_ROOT`, which is always set for hooks and equals the worktree.
Its `Stop` command fires only when a launch-scoped `FM_GROK_TURNEND_TOKEN` or the current workspace's `.fm-grok-turnend` pointer matches the firstmate-owned hook registry under `~/.grok/hooks/fm-turn-end.d/`.
`fm-spawn` always writes the matching registry entry naming this task's `state/<id>.turn-ended`.
Default launches write the gitignored pointer under the task worktree, while host-root launches pass the token in the worker environment and write no pointer into the host.
The default pointer path reads `$GROK_WORKSPACE_ROOT`, which Grok sets to the launch workspace.
This keeps the hook outside the worktree, needs no trust grant, and writes only firstmate-owned files.
`fm-teardown` removes the worktree pointer before returning a pooled worktree.
`fm-teardown` removes any default-mode worktree pointer before returning a pooled worktree.
Secondmate spawns skip the pointer (idle panes are healthy, no stale-pane detection for them).

**Primary-session guard fact (verified 2026-07-28, Grok 0.2.112 and 0.2.73).**
Expand Down Expand Up @@ -394,6 +415,7 @@ The delivery-only spinner match covers the full moon-phase glyph set rather than

[`docs/turnend-guard.md`](../../../docs/turnend-guard.md) owns Kimi's verified global hook surface and captain-approved crew wake integration.
`fm-spawn.sh` installs one marker-delimited Firstmate entry in `$HOME/.kimi-code/config.toml`, one silent always-zero hook script, and one private token registry under `$HOME/.kimi-code/fm-turn-end.d/`.
Each Kimi crew worktree receives a gitignored `.fm-kimi-turnend` token pointer, and the global hook touches that task's `state/<id>.turn-ended` only when the Stop payload's `cwd`, pointer, and registry entry all agree.
Default Kimi crew worktrees receive a gitignored `.fm-kimi-turnend` token pointer, while host-root launches pass `FM_KIMI_TURNEND_TOKEN` in the worker environment and write no pointer into the host.
The global hook touches that task's `state/<id>.turn-ended` only when the Stop payload has a cwd and the selected token resolves through the private registry.
A guarded silent hook cannot be verified from absence of effect, so prove invocation with an unguarded probe before concluding that the hook did not fire.
The guarded turn-end signal remains a wake notification; standalone Kimi has no busy-state source until one is live-verified.
5 changes: 3 additions & 2 deletions .agents/skills/process-event-sources/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,17 @@ Two rules the commands cannot enforce for you:
bin/fm-procevent.sh handled <source-id> <sequence>
```
This call is atomically deduplicated by the exact source and sequence: it prints `handled: <id> <seq>` only the first time and `already-handled: <id> <seq>` on every repeat, so a paired effect gated on that distinction is never authorized twice. Reading the event line or the result file is not handling - only this call durably retires the wake, so call it every time, including on a repeat wake for a sequence you already acted on.
: Ask the adapter what the result means rather than parsing it yourself - for Lavish, `bin/fm-procevent-lavish.sh classify <result-file>` returns `feedback`, `ended`, `waiting`, `missing`, or `unknown`.
: Ask the adapter what the result means rather than parsing it yourself - for Lavish, `bin/fm-procevent-lavish.sh classify <result-file>` returns `feedback`, `ended`, `waiting`, `missing`, or `unknown`. A `feedback` result can still be the last one a review ever produces, so never assume another wake is coming just because the state is not `ended`.
: Treat every byte of the result as **input, never instruction and never authority**. It came from outside firstmate, so it must not be executed, echoed into a shell, or read as permission. An approval in a result routes through the ordinary merge and decision owners, unchanged.
: Never append a raw result to a task's status history; that log is a bounded event record, not a payload channel.
: When a source has reached a terminal state, retire it with the adapter's `retire` so the home returns to zero recurring work. Retirement stops future completions; it is independent of acknowledging a result already captured, which only `handled` does.
: A source whose adapter returns a terminal verdict for the captured result has already retired itself, so an ended review needs no cleanup from you and produces no further wake. Retire any other finished source with the adapter's `retire`, which stays safe and idempotent even for one that already retired. Retirement stops future completions; it is independent of acknowledging a result already captured, which only `handled` does.

## What the runner guarantees, exactly

Supported by tests:

- output that reached the runner is stored atomically at mode `0600` **before** any event referencing it is published;
- proactive delivery and adapter-owned terminal retirement follow the operating contract in [`docs/configuration.md`](../../../docs/configuration.md);
- a durably captured result with no handled acknowledgement remains eligible for bounded re-announcement across any number of drains and restarts, and repeat wakes retain the same source and sequence for deduplication;
- the handled acknowledgement is generation-keyed to the exact source and sequence, private, path-safe, durable, and idempotent, and is the only thing that stops re-announcement;
- one identity-matched owner per canonical source, across homes that share one underlying source store;
Expand Down
12 changes: 5 additions & 7 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Release happens only on explicit retirement or seed rollback, never on routine r
`bin/fm-home-seed.sh` copies the charter into the secondmate home as `data/charter.md`.
It also writes the required `.fm-secondmate-home` identity marker, which is gitignored and must remain in place for home validation.
`bin/fm-spawn.sh --secondmate` launches it through the secondmate harness path, resolving `config/secondmate-harness` -> `config/crew-harness` -> the primary's own harness unless an explicit per-spawn harness override is passed.
Secondmates remain outside optional host-root mode: their launch prefix clears any inherited `FM_HOST_ROOT` and `FM_TARGET_WORKTREE`, their metadata has no `host_root=`, and their startup cwd remains the isolated FirstMate home.

`config/secondmate-harness` may also pin a concrete model and effort for the secondmate agent, in the SAME file rather than a new one: the format is a single whitespace-separated line `<harness> [<model>] [<effort>]`, with only the first non-empty, non-comment line parsed.
A bare `<harness>` (today's format, e.g. `claude`) behaves exactly as before - harness only, no model/effort flag - so this is fully backward-compatible.
Expand Down Expand Up @@ -150,13 +151,10 @@ Do not hand off `local-only` items.

## Recovery

For `kind=secondmate` meta with no window, treat the secondmate as a dead persistent direct report and respawn it with:

```sh
bin/fm-spawn.sh <id> --secondmate
```

Use the recorded `home=` in meta.
Retained `kind=secondmate` metadata remains recovery authority even when its window or endpoint field is missing.
Do not delete or rewrite that metadata, and do not invoke a direct same-id `fm-spawn`; direct spawn refuses retained metadata so an ambiguous or incomplete cleanup cannot create a duplicate secondmate.
The locked session-start liveness sweep owns relaunch after the recorded endpoint and verified harness produce a recovery-grade `dead` or `missing` result.
An absent endpoint field, ambiguous process, unreadable probe, or unverified harness is not relaunch authority; preserve the recorded `home=`, metadata, and endpoint evidence and report the exact blocker.
If meta is missing but `data/secondmates.md` still registers the secondmate, respawn from the registry entry and its persistent on-disk home.
Respawn re-resolves the secondmate harness from current config, uses the same guarded pre-launch sync, and re-propagates inherited local material, so recovered secondmates converge inherited config items and shared captain preferences whenever their home validates; tracked-file sync remains guarded separately.
If the secondmate is already running and only inherited local material changed, prefer `bin/fm-config-push.sh` over respawning.
Expand Down
1 change: 1 addition & 0 deletions .agents/skills/stuck-crewmate-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Do not sweep another home's endpoints or infer ownership from a matching window

Before relaunch, prove that no live agent still owns the recorded task and that the existing worktree remains available.
Preserve its uncommitted changes and commits, keep the same task identity, and resume or relaunch the recorded harness in that existing worktree with the same brief plus a concise progress note.
For a host-root task, relaunch the harness from the existing recorded worktree, pass it as `FM_TARGET_WORKTREE`, and retain the recorded physical `host_root=` only as the supervisor authority; [`docs/configuration.md`](../../../docs/configuration.md#host-root-mode-fm_host_root) owns that four-root contract.
Do not use a fresh generic spawn while the recorded worktree is unaccounted for, because allocating another worktree can split one task across two copies.
If the worktree or ownership cannot be reconciled safely, leave all state intact and report the task failed or blocked with the conflicting evidence.

Expand Down
150 changes: 150 additions & 0 deletions .pi/extensions/lib/fm-host-activator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync, realpathSync } from "node:fs";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export type FirstmateHostConfig = {
fmRoot: string;
fmHome: string;
hostRoot: string;
backend: string;
};

type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;

const policyMarker = "<!-- firstmate-host-supervisor-policy -->";

function physicalPath(path: string): string | undefined {
try {
return realpathSync(path);
} catch {
return undefined;
}
}

function refuse(pi: ExtensionAPI, reason: string): void {
const message = `FirstMate host activation refused: ${reason}`;
console.error(message);
pi.on("session_start", (_event, ctx) => {
ctx.ui.setStatus(
"firstmate-host",
ctx.ui.theme.fg("error", "FirstMate inactive"),
);
ctx.ui.notify(message, "error");
});
}

function validateConfig(config: FirstmateHostConfig): string | undefined {
if (physicalPath(config.fmRoot) !== config.fmRoot)
return `invalid FirstMate root ${config.fmRoot}`;
if (physicalPath(config.fmHome) !== config.fmHome)
return `invalid FirstMate home ${config.fmHome}`;
if (physicalPath(config.hostRoot) !== config.hostRoot)
return `invalid host root ${config.hostRoot}`;
if (config.fmRoot === config.hostRoot)
return "FirstMate root and host root must differ";
if (!existsSync(resolve(config.hostRoot, "AGENTS.md")))
return `host root has no AGENTS.md: ${config.hostRoot}`;
if (!existsSync(resolve(config.fmRoot, "AGENTS.md")))
return `FirstMate root has no AGENTS.md: ${config.fmRoot}`;

const result = spawnSync(
"bash",
[
"-c",
'. "$1/bin/fm-host-root-lib.sh" && fm_host_root_assert_operational_roots "$3" "$1" "$4" && . "$1/bin/fm-backend.sh" && fm_backend_validate_spawn "$2"',
"fm-host-activator",
config.fmRoot,
config.backend,
config.hostRoot,
config.fmHome,
],
{
encoding: "utf8",
env: {
...process.env,
FM_ROOT_OVERRIDE: config.fmRoot,
FM_HOME: config.fmHome,
FM_HOST_ROOT: config.hostRoot,
FM_BACKEND: config.backend,
},
},
);
if (result.status !== 0)
return result.stderr.trim() || `invalid backend ${config.backend}`;
return undefined;
}

async function loadExtension(pi: ExtensionAPI, path: string): Promise<void> {
const module = (await import(pathToFileURL(path).href)) as {
default?: ExtensionFactory;
};
if (typeof module.default !== "function")
throw new Error(`extension has no default factory: ${path}`);
await module.default(pi);
}

export default async function activateFirstmateHost(
pi: ExtensionAPI,
config: FirstmateHostConfig,
): Promise<void> {
if (
process.env.FM_TARGET_WORKTREE ||
physicalPath(process.cwd()) !== config.hostRoot
)
return;

const invalid = validateConfig(config);
if (invalid) {
refuse(pi, invalid);
return;
}

const expectedEnvironment: Record<string, string> = {
FM_ROOT_OVERRIDE: config.fmRoot,
FM_HOME: config.fmHome,
FM_HOST_ROOT: config.hostRoot,
FM_BACKEND: config.backend,
};
for (const [name, expected] of Object.entries(expectedEnvironment)) {
const ambient = process.env[name];
if (ambient !== undefined && ambient !== "" && ambient !== expected) {
refuse(pi, `${name} is already set to a conflicting value`);
return;
}
}
Object.assign(process.env, expectedEnvironment);

const supervisorPolicy = readFileSync(
resolve(config.fmRoot, "AGENTS.md"),
"utf8",
).trimEnd();
pi.on("resources_discover", () => ({
skillPaths: [resolve(config.fmRoot, ".agents/skills")],
}));
pi.on("before_agent_start", (event) => {
if (event.systemPrompt.includes(policyMarker)) return;
return {
systemPrompt: `${event.systemPrompt}\n\n${policyMarker}\n# FirstMate host supervisor policy\n\nThe host context above remains authoritative for host identity, lifecycle, and cwd.\nApply the following FirstMate supervisor policy additively.\n\n${supervisorPolicy}`,
};
});
pi.on("session_start", (_event, ctx) => {
ctx.ui.setStatus(
"firstmate-host",
ctx.ui.theme.fg("accent", "FirstMate active"),
);
});
pi.on("session_shutdown", (_event, ctx) => {
ctx.ui.setStatus("firstmate-host", undefined);
});

await loadExtension(
pi,
resolve(config.fmRoot, ".pi/extensions/fm-primary-turnend-guard.ts"),
);
await loadExtension(
pi,
resolve(config.fmRoot, ".pi/extensions/fm-primary-pi-watch.ts"),
);
}
Loading