Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 24 additions & 6 deletions LifeOS/Tools/DeployCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import { existsSync, mkdirSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { basename, dirname, join } from "node:path";
import { copyMissing, detectDevTree } from "./InstallEngine";

// Runtime top-level entries this tool does NOT deploy:
Expand Down Expand Up @@ -242,10 +242,10 @@ function deployDependencies(payloadInstall: string, configRoot: string, apply: b
* pulse.ts otherwise reports as a copy-paste fix command, so a fresh Pulse
* doesn't 503 until a human intervenes.
*/
function findNestedDependencyDirs(runtimeDst: string): string[] {
function findNestedDependencyDirs(runtimeDst: string, skip: Set<string> = new Set()): string[] {
const found: string[] = [];
const walk = (dir: string): void => {
if (!existsSync(dir)) return;
if (!existsSync(dir) || skip.has(dir)) return;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.name === "node_modules" || entry.name === ".git") continue;
const p = join(dir, entry.name);
Expand All @@ -260,7 +260,7 @@ function findNestedDependencyDirs(runtimeDst: string): string[] {
return found.sort();
}

function deployNestedDependencies(configRoot: string, apply: boolean): DeployResult {
function deployNestedDependencies(payloadInstall: string, configRoot: string, apply: boolean): DeployResult {
const runtimeDst = join(configRoot, "LIFEOS");
const skillsDst = join(configRoot, "skills");
const r: DeployResult = {
Expand All @@ -272,9 +272,27 @@ function deployNestedDependencies(configRoot: string, apply: boolean): DeployRes
// Walk skills/ alongside LIFEOS/ — skills ship nested package.json manifests
// too (Apify, Evals, Prompting templates, Art/Remotion tools), and installing
// only the runtime tree left them import-broken. Public issue #1605, @cristbc.
//
// Scoped to the skills THIS payload ships, per skill dir: configRoot/skills
// also holds the principal's own pre-existing skills — running `bun install`
// in those mutates dirs we never deployed (and whose install we don't own),
// and any failure there fails OUR deploy. Skills we skipped on a
// case-insensitive collision are excluded for the same reason (not in the
// payload-name → deployed-dir set we created).
const payloadSkills = join(payloadInstall, "skills");
// The deployed LifeOS skill carries a second full copy of this very payload
// under <skill>/install — walking it re-installs every payload skill a second
// time, into a tree nothing imports from.
const nestedPayload = join(skillsDst, basename(dirname(payloadInstall)), "install");
const ownSkillDirs = existsSync(payloadSkills)
? readdirSync(payloadSkills, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => join(skillsDst, e.name))
.filter(existsSync)
: [];
const dirs = [
...findNestedDependencyDirs(runtimeDst),
...(existsSync(skillsDst) ? findNestedDependencyDirs(skillsDst) : []),
...ownSkillDirs.flatMap((d) => findNestedDependencyDirs(d, new Set([nestedPayload]))),
];
for (const dir of dirs) {
const isObservability = dir === join(runtimeDst, "PULSE", "Observability");
Expand Down Expand Up @@ -330,7 +348,7 @@ function main(): void {
deployRuntime(payloadInstall, configRoot, apply),
scaffoldMemory(configRoot, apply),
deployDependencies(payloadInstall, configRoot, apply),
deployNestedDependencies(configRoot, apply),
deployNestedDependencies(payloadInstall, configRoot, apply),
];

// A missing required payload source (blocker) or a copy failure is a hard
Expand Down
15 changes: 12 additions & 3 deletions LifeOS/install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ step "5/6 Migrating launch aliases (pre-7.x upgrades)"
CONFIG_ROOT="$(dirname "$LIFEOS_SKILLS_DIR")"
LAUNCHER="$CONFIG_ROOT/LIFEOS/TOOLS/lifeos.ts"
SYS_PROMPT="$CONFIG_ROOT/LIFEOS/LIFEOS_SYSTEM_PROMPT.md"
# Single-quote a value for safe embedding in shell source. A literal `'` is
# closed, backslash-escaped and reopened (it's → 'it'\''s'). Needed because the
# alias body is re-parsed by the shell: an unquoted $HOME with a space breaks the
# launcher, and one with a quote corrupts the rc file — and by this point the old
# working alias is already commented out, so the user is left with no alias.
shq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
migrate_rc() {
local rc="$1" stale names n ts
[ -f "$rc" ] || return 0
Expand All @@ -304,16 +310,19 @@ migrate_rc() {
{ print }
' "$rc" > "$rc.lifeos-tmp" && mv "$rc.lifeos-tmp" "$rc"
if [ -f "$LAUNCHER" ]; then
local add_lifeos=1
local add_lifeos=1 alias_body
# Two levels of quoting for two levels of parsing: the inner shq protects the
# paths when the alias body runs, the outer one when the rc file is sourced.
alias_body="bun $(shq "$LAUNCHER") -s $(shq "$SYS_PROMPT")"
printf '%s\n' $names | grep -qx lifeos && add_lifeos=0
grep -E '^[[:space:]]*alias[[:space:]]+lifeos=' "$rc" 2>/dev/null | grep -q 'LIFEOS_SYSTEM_PROMPT' && add_lifeos=0
{
echo ""
echo "# LifeOS ${LIFEOS_TAG} launch aliases (repointed from pre-7.x by install.sh)"
for n in $names; do
echo "alias $n='bun $LAUNCHER -s $SYS_PROMPT'"
echo "alias $n=$(shq "$alias_body")"
done
if [ "$add_lifeos" = "1" ]; then echo "alias lifeos='bun $LAUNCHER -s $SYS_PROMPT'"; fi
if [ "$add_lifeos" = "1" ]; then echo "alias lifeos=$(shq "$alias_body")"; fi
} >> "$rc"
success "Repointed $(echo $names | tr '\n' ' ')to the constituted 7.x launcher (backup: $(basename "$rc").lifeos-backup-$ts)"
else
Expand Down
3 changes: 3 additions & 0 deletions LifeOS/install/skills/AudioEditor/Tools/GateRepair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ for (let iter = 1; iter <= 5; iter++) {
const enc = spawnSync("ffmpeg", ["-v", "error", "-i", inFile, "-i", tmpWav, "-map", "0:v:0", "-map", "1:a:0", "-c:v", "copy", "-c:a", "aac", "-b:a", ABR, "-ar", String(SR), "-movflags", "+faststart", outFile, "-y"]);
if (enc.status !== 0) { console.error("encode failed:", enc.stderr?.toString().slice(0, 300)); process.exit(2); }
const dec = spawnSync("ffmpeg", ["-v", "error", "-i", outFile, "-map", "0:a:0", "-ac", "1", "-ar", String(SR), "-f", "f32le", "-"], { maxBuffer: 8 * (1 << 30) });
// An unchecked decode certifies CLEAN on an empty scan — never claim a file
// is clean off a read that failed.
if (dec.status !== 0 || !dec.stdout) { console.error("verification decode failed:", dec.stderr?.toString().slice(0, 300)); process.exit(2); }
const xb: Buffer = dec.stdout;
const xe = new Float32Array(xb.buffer, xb.byteOffset, Math.floor(xb.length / 4));
const sites = stepScan(xe, SR);
Expand Down
20 changes: 19 additions & 1 deletion LifeOS/install/skills/Evals/Tools/EvalRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ export async function runSuite(name: string, override: Partial<EvalSuiteV2> = {}
} else {
return { suite: name, type: 'unknown', passed: false, score: 0, pass_to_k: 0, pass_at_k: 0, summary: `Suite not found: ${name}`, run_id: 'error', cases: [] };
}
// Diagnose a v1 suite instead of dying on `for (const c of undefined)`. The
// only shipped suite (Suites/Regression/core-behaviors.yaml) still uses the
// legacy `tasks:` list, so every run threw a bare TypeError — and callers that
// catch broadly (ConfigEvalOnChange) turned that into a regression eval which
// silently never ran. The two formats are not mechanically convertible:
// UseCases/*.yaml define weighted graders and carry no `prompt`, so there is
// nothing for this single-shot runner to send. Say so rather than guess.
if (!Array.isArray(suite.cases) || suite.cases.length === 0) {
const legacy = (suite as unknown as { tasks?: unknown[] }).tasks;
throw new Error(
Array.isArray(legacy)
? `Suite '${name}' is in the legacy v1 format: it lists ${legacy.length} \`tasks:\` referencing Evals/UseCases/*.yaml, which define graders and no \`prompt\`. This runner needs v2 \`cases:\` with prompt/assert. Migrate the suite, or run it through a UseCase-aware runner.`
: `Suite '${name}' has no \`cases:\` to run.`,
);
}
const threshold = suite.pass_threshold ?? 0.75;
const trials = suite.trials ?? 3;
const agentLevel: InferenceLevel = suite.agent_level ?? 'medium';
Expand Down Expand Up @@ -146,7 +161,10 @@ export async function runSuite(name: string, override: Partial<EvalSuiteV2> = {}
id: c.id,
mean_score: trialResults.reduce((s, t) => s + t.score, 0) / trials,
pass_at_k: passed > 0 ? 1 : 0,
pass_to_k: passed / trials,
// pass^k is "every one of the k trials passed" — a reliability measure.
// passed/trials is the MEAN, which reported 2-of-3 as 67% where the true
// pass^k is 0, making a flaky case look like a mostly-passing one.
pass_to_k: passed === trials ? 1 : 0,
trials: trialResults,
});
}
Expand Down
30 changes: 24 additions & 6 deletions LifeOS/install/skills/LifeOS/Tools/DeployCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import { existsSync, mkdirSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { basename, dirname, join } from "node:path";
import { copyMissing, detectDevTree } from "./InstallEngine";

// Runtime top-level entries this tool does NOT deploy:
Expand Down Expand Up @@ -242,10 +242,10 @@ function deployDependencies(payloadInstall: string, configRoot: string, apply: b
* pulse.ts otherwise reports as a copy-paste fix command, so a fresh Pulse
* doesn't 503 until a human intervenes.
*/
function findNestedDependencyDirs(runtimeDst: string): string[] {
function findNestedDependencyDirs(runtimeDst: string, skip: Set<string> = new Set()): string[] {
const found: string[] = [];
const walk = (dir: string): void => {
if (!existsSync(dir)) return;
if (!existsSync(dir) || skip.has(dir)) return;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.name === "node_modules" || entry.name === ".git") continue;
const p = join(dir, entry.name);
Expand All @@ -260,7 +260,7 @@ function findNestedDependencyDirs(runtimeDst: string): string[] {
return found.sort();
}

function deployNestedDependencies(configRoot: string, apply: boolean): DeployResult {
function deployNestedDependencies(payloadInstall: string, configRoot: string, apply: boolean): DeployResult {
const runtimeDst = join(configRoot, "LIFEOS");
const skillsDst = join(configRoot, "skills");
const r: DeployResult = {
Expand All @@ -272,9 +272,27 @@ function deployNestedDependencies(configRoot: string, apply: boolean): DeployRes
// Walk skills/ alongside LIFEOS/ — skills ship nested package.json manifests
// too (Apify, Evals, Prompting templates, Art/Remotion tools), and installing
// only the runtime tree left them import-broken. Public issue #1605, @cristbc.
//
// Scoped to the skills THIS payload ships, per skill dir: configRoot/skills
// also holds the principal's own pre-existing skills — running `bun install`
// in those mutates dirs we never deployed (and whose install we don't own),
// and any failure there fails OUR deploy. Skills we skipped on a
// case-insensitive collision are excluded for the same reason (not in the
// payload-name → deployed-dir set we created).
const payloadSkills = join(payloadInstall, "skills");
// The deployed LifeOS skill carries a second full copy of this very payload
// under <skill>/install — walking it re-installs every payload skill a second
// time, into a tree nothing imports from.
const nestedPayload = join(skillsDst, basename(dirname(payloadInstall)), "install");
const ownSkillDirs = existsSync(payloadSkills)
? readdirSync(payloadSkills, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => join(skillsDst, e.name))
.filter(existsSync)
: [];
const dirs = [
...findNestedDependencyDirs(runtimeDst),
...(existsSync(skillsDst) ? findNestedDependencyDirs(skillsDst) : []),
...ownSkillDirs.flatMap((d) => findNestedDependencyDirs(d, new Set([nestedPayload]))),
];
for (const dir of dirs) {
const isObservability = dir === join(runtimeDst, "PULSE", "Observability");
Expand Down Expand Up @@ -330,7 +348,7 @@ function main(): void {
deployRuntime(payloadInstall, configRoot, apply),
scaffoldMemory(configRoot, apply),
deployDependencies(payloadInstall, configRoot, apply),
deployNestedDependencies(configRoot, apply),
deployNestedDependencies(payloadInstall, configRoot, apply),
];

// A missing required payload source (blocker) or a copy failure is a hard
Expand Down
15 changes: 12 additions & 3 deletions LifeOS/install/skills/LifeOS/install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ step "5/6 Migrating launch aliases (pre-7.x upgrades)"
CONFIG_ROOT="$(dirname "$LIFEOS_SKILLS_DIR")"
LAUNCHER="$CONFIG_ROOT/LIFEOS/TOOLS/lifeos.ts"
SYS_PROMPT="$CONFIG_ROOT/LIFEOS/LIFEOS_SYSTEM_PROMPT.md"
# Single-quote a value for safe embedding in shell source. A literal `'` is
# closed, backslash-escaped and reopened (it's → 'it'\''s'). Needed because the
# alias body is re-parsed by the shell: an unquoted $HOME with a space breaks the
# launcher, and one with a quote corrupts the rc file — and by this point the old
# working alias is already commented out, so the user is left with no alias.
shq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
migrate_rc() {
local rc="$1" stale names n ts
[ -f "$rc" ] || return 0
Expand All @@ -304,16 +310,19 @@ migrate_rc() {
{ print }
' "$rc" > "$rc.lifeos-tmp" && mv "$rc.lifeos-tmp" "$rc"
if [ -f "$LAUNCHER" ]; then
local add_lifeos=1
local add_lifeos=1 alias_body
# Two levels of quoting for two levels of parsing: the inner shq protects the
# paths when the alias body runs, the outer one when the rc file is sourced.
alias_body="bun $(shq "$LAUNCHER") -s $(shq "$SYS_PROMPT")"
printf '%s\n' $names | grep -qx lifeos && add_lifeos=0
grep -E '^[[:space:]]*alias[[:space:]]+lifeos=' "$rc" 2>/dev/null | grep -q 'LIFEOS_SYSTEM_PROMPT' && add_lifeos=0
{
echo ""
echo "# LifeOS ${LIFEOS_TAG} launch aliases (repointed from pre-7.x by install.sh)"
for n in $names; do
echo "alias $n='bun $LAUNCHER -s $SYS_PROMPT'"
echo "alias $n=$(shq "$alias_body")"
done
if [ "$add_lifeos" = "1" ]; then echo "alias lifeos='bun $LAUNCHER -s $SYS_PROMPT'"; fi
if [ "$add_lifeos" = "1" ]; then echo "alias lifeos=$(shq "$alias_body")"; fi
} >> "$rc"
success "Repointed $(echo $names | tr '\n' ' ')to the constituted 7.x launcher (backup: $(basename "$rc").lifeos-backup-$ts)"
else
Expand Down
10 changes: 9 additions & 1 deletion LifeOS/install/skills/LocalIntelligence/Tools/UserSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,15 @@ export async function loadUserSources(): Promise<UserSource[]> {
} catch {
return [] // no config = no user sources, not an error
}
const parsed = JSON.parse(raw) as { sources?: UserSource[] }
let parsed: { sources?: UserSource[] }
try {
parsed = JSON.parse(raw) as { sources?: UserSource[] }
} catch (err) {
// Malformed user config must not sink a whole refresh — the built-in
// fetchers have already run by this point and the digest is unsaved.
console.error(`[user-sources] ignoring malformed ${CONFIG_PATH}: ${(err as Error).message}`)
return []
}
return (parsed.sources ?? []).filter(
(s) => s.enabled !== false && SECTION_KEYS.includes(s.section) && s.url && s.name
)
Expand Down
13 changes: 10 additions & 3 deletions LifeOS/install/skills/Tldraw/Tools/Tldr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,16 @@ async function cmdRemove(file: string) {
if (!full) die(`no record ${id}`)
drop.add(full)
}
// cascade: bindings touching dropped shapes, and arrows left dangling
for (const b of doc.records.filter((r) => r.typeName === "binding")) {
if (drop.has(b.toId) || drop.has(b.fromId)) { drop.add(b.id); drop.add(b.fromId) }
// cascade: bindings touching dropped shapes, and arrows left dangling.
// Repeat to a fixpoint — dropping an arrow orphans its OTHER binding, which a
// single forward pass has already walked past (remove the `to` shape and the
// `start` binding survives, dangling).
const bindings = doc.records.filter((r) => r.typeName === "binding")
for (let size = -1; size !== drop.size; ) {
size = drop.size
for (const b of bindings) {
if (drop.has(b.toId) || drop.has(b.fromId)) { drop.add(b.id); drop.add(b.fromId) }
}
}
const before = doc.records.length
doc.records = doc.records.filter((r) => !drop.has(r.id))
Expand Down