mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
p2 rewritten by the Chord v2 authoring rules (decision "App authoring rules confirmed by the p2 audit"), in four parts merged together: - core: session lifecycle (relocate, clone, fork, archive) and search rebuild are persisted jobs on a new server store; Sessions v6 with client-chosen ids and visible operations; Retry answers with the worker's real outcome; workers push branch, subagent and status state to the server; roots take the method's ctx. - services: models, envs, ports, login, mcp, logs, sharing and voice follow external state (self.follow, followNative) instead of copy loops; methods answer after their state commits; presence from host.references. - chat: chat mode, artifacts and prompt resources follow Durable and native state; shell commands are Chord jobs (shellId is the requestId); status pushed before send and stop answer; race-free draft claim. - ui: optimistic updates on web (useOptimistic) and terminal (TuiScope.optimistic, extended in Chord with a reducer and a pending flag), pending states for long work, visible rollback, load waterfalls removed; merged with the one composer and immediate first send (ccff9876e) and the folder + button (5a31a6d5f). docs/optimistic-ui.md describes the pattern.
38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
import { NodeExecutionEnv } from "@earendil-works/pi-durable/env/node";
|
|
import { Observer } from "@p2/app/v1/node";
|
|
import { type EnvsConfig, envsPath, loadEnvs } from "./config.ts";
|
|
|
|
export type EnvSnapshot = { configs: EnvsConfig; error: string | null };
|
|
/** Watch before loading. Invalid saves keep the last valid configuration. One observer per process. */
|
|
export class EnvStore {
|
|
readonly observer: Observer;
|
|
#snapshot: EnvSnapshot = { configs: {}, error: null };
|
|
readonly #listeners = new Set<(snapshot: EnvSnapshot) => void>();
|
|
constructor(directory: string) {
|
|
this.observer = new Observer(
|
|
[{ env: new NodeExecutionEnv({ cwd: directory }), targets: () => [{ path: envsPath(directory) }] }],
|
|
async (retrying) => {
|
|
try {
|
|
this.#snapshot = { configs: await loadEnvs(directory), error: null };
|
|
} catch {
|
|
if (!retrying) return true;
|
|
this.#snapshot = {
|
|
configs: this.#snapshot.configs,
|
|
error: "Could not load envs.json; keeping last valid state",
|
|
};
|
|
}
|
|
for (const listener of this.#listeners) listener(this.#snapshot);
|
|
return false;
|
|
},
|
|
2000,
|
|
);
|
|
}
|
|
get snapshot(): EnvSnapshot {
|
|
return this.#snapshot;
|
|
}
|
|
/** `listener` hears of every load's snapshot from now on; the result stops it. */
|
|
subscribe(listener: (snapshot: EnvSnapshot) => void): () => void {
|
|
this.#listeners.add(listener);
|
|
return () => this.#listeners.delete(listener);
|
|
}
|
|
}
|