shadok-ai 0.17.41 → 0.17.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -99,15 +99,59 @@ export declare function commonsOsUser(input: {
99
99
  * path is unreadable and the guard dies `check-failed` in silence. The migration
100
100
  * relocates the `.claude` tree into the new HOME, so the reference must follow.
101
101
  *
102
- * Only the `<oldHome>/.claude` base is rewritten — the one tree that moved — not
103
- * every `<oldHome>` mention, so an unrelated `/root/...` reference is left intact.
104
- * Identity when the home did not move (mono-tenant, or old === new) or when either
105
- * base is blank (never blank out a path). A `$HOME`-relative check is already
106
- * portable and contains no absolute base to match, so it passes through untouched.
107
- * Pure, so the boot self-heal that walks the cron file stays a thin caller — and
108
- * the exact substitution is locked by test, the twin of `rewriteSkillsBase`.
102
+ * The `<oldHome>/.claude` base is ALWAYS rewritten — the tree the migration moved —
103
+ * plus any extra RELATIVE prefix the caller passes (the `~/.shadok-ai/<dir>` script
104
+ * dirs a guard also names; see `shadokScriptDirs`). Every OTHER `/root/...` mention
105
+ * is left intact, so a reference to a dir that was NOT relocated keeps pointing at
106
+ * the original. Identity when the home did not move (mono-tenant, or old === new) or
107
+ * when either base is blank (never blank out a path). A `$HOME`-relative check is
108
+ * already portable and contains no absolute base to match, so it passes through
109
+ * untouched. Pure, so the boot self-heal that walks the cron file stays a thin
110
+ * caller — and the exact substitution is locked by test, the twin of
111
+ * `rewriteSkillsBase`.
109
112
  */
110
- export declare function rewriteCronCheckHome(check: string, oldHome: string, newHome: string): string;
113
+ export declare function rewriteCronCheckHome(check: string, oldHome: string, newHome: string, extraRelPrefixes?: readonly string[]): string;
114
+ /**
115
+ * Shadok's OWN directories under `~/.shadok-ai` — NEVER carried into the commons
116
+ * HOME by the stranded-script heal, even if a cron check names one. The credential
117
+ * stores (`ssh`, `users`, `secrets`) are the load-bearing entries: copying them
118
+ * into `/home/main` would hand every commons agent the SSH private key, the
119
+ * per-session signing keys and the account password hashes. The rest are internal
120
+ * per-instance state a guard has no business reading. A denylist (not an allowlist)
121
+ * on purpose: an operator/agent names its script dir whatever it likes (`scripts`,
122
+ * `checks`, `marketing`, …), and an allowlist would silently fail to heal a
123
+ * custom-named one — reintroducing the exact silent breakage this closes.
124
+ */
125
+ export declare const SHADOK_INTERNAL_DIRS: readonly string[];
126
+ /**
127
+ * The `~/.claude/<dir>` subtrees NEVER carried by the heal. `.credentials` is a
128
+ * FILE, not a dir, so it can't match anyway — the real entries are the big
129
+ * already-migrated trees (`migChownTreeToCommons` copies `projects`/`todos`/
130
+ * `shell-snapshots`/`file-history`) and the caches (`statsig`/`plugins`/`ide`):
131
+ * re-copying them would be wasteful, and they are not scripts a guard runs.
132
+ * `skills` and `scripts` (and any custom dir) are deliberately NOT here — those
133
+ * are where an agent keeps the monitoring scripts it authored.
134
+ */
135
+ export declare const CLAUDE_INTERNAL_DIRS: readonly string[];
136
+ /**
137
+ * The home-rooted `<top>/<sub>` script directories a cron `check` names, that the
138
+ * HOME move off `/root` stranded. A dropped `main` guard cannot traverse `/root`
139
+ * (0700), so ANY `/root/<top>/<sub>/…` path it names — a script or its sibling
140
+ * state file — is unreachable and the guard dies `check-failed` in silence. #419
141
+ * relocated only `.claude/skills`; this generalises the discovery so `.claude/scripts`
142
+ * (seen on the biosense cockpit), every `.shadok-ai/<dir>` (the dogfood instance),
143
+ * and any future script dir are all carried across — the trap is not one directory,
144
+ * it is "any `/root` path a check names".
145
+ *
146
+ * Matches an absolute `oldHome` reference AND a `$HOME`/`${HOME}`/`~`-relative one
147
+ * (a portable check still needs its dir to EXIST in the new HOME). Only the 2-level
148
+ * `<top>/<sub>` unit is relocated, with `<top>` restricted to the two script homes
149
+ * and `<sub>` refused when it is shadok's own state/credentials or a big
150
+ * already-migrated `.claude` tree (`RELOCATABLE_TOPS`). Returns distinct RELATIVE
151
+ * prefixes (`".claude/scripts"`, `".shadok-ai/checks"`, …), ready to feed
152
+ * `rewriteCronCheckHome` and `path.join`. Pure.
153
+ */
154
+ export declare function strandedCheckDirs(check: string, oldHome: string): string[];
111
155
  /**
112
156
  * The running agents to respawn, as session ids — deduped, order preserved so
113
157
  * the caller cycles them ONE AT A TIME (the OAuth refresh-token race,
@@ -100,18 +100,124 @@ export function commonsOsUser(input) {
100
100
  * path is unreadable and the guard dies `check-failed` in silence. The migration
101
101
  * relocates the `.claude` tree into the new HOME, so the reference must follow.
102
102
  *
103
- * Only the `<oldHome>/.claude` base is rewritten — the one tree that moved — not
104
- * every `<oldHome>` mention, so an unrelated `/root/...` reference is left intact.
105
- * Identity when the home did not move (mono-tenant, or old === new) or when either
106
- * base is blank (never blank out a path). A `$HOME`-relative check is already
107
- * portable and contains no absolute base to match, so it passes through untouched.
108
- * Pure, so the boot self-heal that walks the cron file stays a thin caller — and
109
- * the exact substitution is locked by test, the twin of `rewriteSkillsBase`.
103
+ * The `<oldHome>/.claude` base is ALWAYS rewritten — the tree the migration moved —
104
+ * plus any extra RELATIVE prefix the caller passes (the `~/.shadok-ai/<dir>` script
105
+ * dirs a guard also names; see `shadokScriptDirs`). Every OTHER `/root/...` mention
106
+ * is left intact, so a reference to a dir that was NOT relocated keeps pointing at
107
+ * the original. Identity when the home did not move (mono-tenant, or old === new) or
108
+ * when either base is blank (never blank out a path). A `$HOME`-relative check is
109
+ * already portable and contains no absolute base to match, so it passes through
110
+ * untouched. Pure, so the boot self-heal that walks the cron file stays a thin
111
+ * caller — and the exact substitution is locked by test, the twin of
112
+ * `rewriteSkillsBase`.
110
113
  */
111
- export function rewriteCronCheckHome(check, oldHome, newHome) {
114
+ export function rewriteCronCheckHome(check, oldHome, newHome, extraRelPrefixes = []) {
112
115
  if (!check || !oldHome || !newHome || oldHome === newHome)
113
116
  return check;
114
- return check.split(oldHome + "/.claude").join(newHome + "/.claude");
117
+ let out = check;
118
+ for (const rel of [".claude", ...extraRelPrefixes]) {
119
+ out = out.split(oldHome + "/" + rel).join(newHome + "/" + rel);
120
+ }
121
+ return out;
122
+ }
123
+ /** Escape a string for literal use inside a `RegExp`. */
124
+ function escapeRegExp(s) {
125
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
126
+ }
127
+ /**
128
+ * Shadok's OWN directories under `~/.shadok-ai` — NEVER carried into the commons
129
+ * HOME by the stranded-script heal, even if a cron check names one. The credential
130
+ * stores (`ssh`, `users`, `secrets`) are the load-bearing entries: copying them
131
+ * into `/home/main` would hand every commons agent the SSH private key, the
132
+ * per-session signing keys and the account password hashes. The rest are internal
133
+ * per-instance state a guard has no business reading. A denylist (not an allowlist)
134
+ * on purpose: an operator/agent names its script dir whatever it likes (`scripts`,
135
+ * `checks`, `marketing`, …), and an allowlist would silently fail to heal a
136
+ * custom-named one — reintroducing the exact silent breakage this closes.
137
+ */
138
+ export const SHADOK_INTERNAL_DIRS = [
139
+ "app",
140
+ "browsers",
141
+ "channels",
142
+ "config",
143
+ "crons",
144
+ "files",
145
+ "keys",
146
+ "ledger",
147
+ "locks",
148
+ "media",
149
+ "peers",
150
+ "pilotctl",
151
+ "profiles",
152
+ "run",
153
+ "secrets",
154
+ "self",
155
+ "ssh",
156
+ "tail",
157
+ "tokens",
158
+ "users",
159
+ "whisper",
160
+ "worktrees",
161
+ "wt",
162
+ ];
163
+ /**
164
+ * The `~/.claude/<dir>` subtrees NEVER carried by the heal. `.credentials` is a
165
+ * FILE, not a dir, so it can't match anyway — the real entries are the big
166
+ * already-migrated trees (`migChownTreeToCommons` copies `projects`/`todos`/
167
+ * `shell-snapshots`/`file-history`) and the caches (`statsig`/`plugins`/`ide`):
168
+ * re-copying them would be wasteful, and they are not scripts a guard runs.
169
+ * `skills` and `scripts` (and any custom dir) are deliberately NOT here — those
170
+ * are where an agent keeps the monitoring scripts it authored.
171
+ */
172
+ export const CLAUDE_INTERNAL_DIRS = [
173
+ "projects",
174
+ "todos",
175
+ "shell-snapshots",
176
+ "file-history",
177
+ "statsig",
178
+ "plugins",
179
+ "ide",
180
+ ];
181
+ /** The two home subtrees under which an agent/operator legitimately drops a
182
+ * script a cron guard runs. An ALLOWLIST of TOPS (safe — scripts never live
183
+ * anywhere else under the HOME) combined with a per-top DENYLIST of SUBS (so a
184
+ * custom-named script dir still heals). */
185
+ const RELOCATABLE_TOPS = {
186
+ ".claude": CLAUDE_INTERNAL_DIRS,
187
+ ".shadok-ai": SHADOK_INTERNAL_DIRS,
188
+ };
189
+ /**
190
+ * The home-rooted `<top>/<sub>` script directories a cron `check` names, that the
191
+ * HOME move off `/root` stranded. A dropped `main` guard cannot traverse `/root`
192
+ * (0700), so ANY `/root/<top>/<sub>/…` path it names — a script or its sibling
193
+ * state file — is unreachable and the guard dies `check-failed` in silence. #419
194
+ * relocated only `.claude/skills`; this generalises the discovery so `.claude/scripts`
195
+ * (seen on the biosense cockpit), every `.shadok-ai/<dir>` (the dogfood instance),
196
+ * and any future script dir are all carried across — the trap is not one directory,
197
+ * it is "any `/root` path a check names".
198
+ *
199
+ * Matches an absolute `oldHome` reference AND a `$HOME`/`${HOME}`/`~`-relative one
200
+ * (a portable check still needs its dir to EXIST in the new HOME). Only the 2-level
201
+ * `<top>/<sub>` unit is relocated, with `<top>` restricted to the two script homes
202
+ * and `<sub>` refused when it is shadok's own state/credentials or a big
203
+ * already-migrated `.claude` tree (`RELOCATABLE_TOPS`). Returns distinct RELATIVE
204
+ * prefixes (`".claude/scripts"`, `".shadok-ai/checks"`, …), ready to feed
205
+ * `rewriteCronCheckHome` and `path.join`. Pure.
206
+ */
207
+ export function strandedCheckDirs(check, oldHome) {
208
+ if (!check || !oldHome)
209
+ return [];
210
+ const homeAlt = [escapeRegExp(oldHome), "\\$HOME", "\\$\\{HOME\\}", "~"].join("|");
211
+ const tops = Object.keys(RELOCATABLE_TOPS).map((t) => escapeRegExp(t)).join("|");
212
+ const re = new RegExp("(?:" + homeAlt + ")/(" + tops + ")/([^/\\s'\"]+)/", "g");
213
+ const out = new Set();
214
+ for (let m = re.exec(check); m; m = re.exec(check)) {
215
+ const [, top, sub] = m;
216
+ if (RELOCATABLE_TOPS[top]?.includes(sub))
217
+ continue;
218
+ out.add(top + "/" + sub);
219
+ }
220
+ return [...out];
115
221
  }
116
222
  /**
117
223
  * The running agents to respawn, as session ids — deduped, order preserved so
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-groups.js","sourceRoot":"","sources":["../src/migrate-groups.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrate-groups.js","sourceRoot":"","sources":["../src/migrate-groups.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -34,7 +34,7 @@ import { readGroundAt } from "./ground.js";
34
34
  import { homeGreetingBrief } from "./greeting.js";
35
35
  import { ensureSshIdentity } from "./ssh.js";
36
36
  import { accountsEnabled, deploymentMode, probeCanProvision } from "./capability.js";
37
- import { migrationPlan, runMigration, respawnPlan, commonsOsUser, COMMONS_USER, MIGRATION_MARKER_FILE, rewriteCronCheckHome, } from "./migrate-groups.js";
37
+ import { migrationPlan, runMigration, respawnPlan, commonsOsUser, COMMONS_USER, MIGRATION_MARKER_FILE, rewriteCronCheckHome, strandedCheckDirs, } from "./migrate-groups.js";
38
38
  import { relayOAuthCallback } from "./oauth-relay.js";
39
39
  import { openBrowser } from "./open-browser.js";
40
40
  import { parseSkillMeta, prepromptParts, prepromptFingerprint, stalenessReasons, LEGACY_FINGERPRINT, } from "./preprompt.js";
@@ -3013,20 +3013,27 @@ async function ensureGroupsProvisioned() {
3013
3013
  preseedGroupHomes();
3014
3014
  // And heal the commons agent's own helper scripts + cron guards, which the HOME
3015
3015
  // move off /root left stranded (see below).
3016
- await healCommonsClaudeScripts();
3016
+ await healCommonsStrandedScripts();
3017
3017
  }
3018
3018
  /**
3019
- * Boot self-heal for the commons agent's OWN `.claude` content and cron guards
3020
- * after its HOME moved off `/root` to `/home/main`.
3019
+ * Boot self-heal for the commons agent's OWN helper scripts and cron guards after
3020
+ * its HOME moved off `/root` to `/home/main`. A cron guard runs DROPPED as the
3021
+ * commons `main` user, which cannot traverse `/root` (0700) — so ANY absolute
3022
+ * `/root/...` path it names (script or sibling state file) is unreadable and the
3023
+ * guard dies `check-failed` in silence (measured on a live instance: monitoring
3024
+ * crons stuck, "son script est sous /root"). New agents are fine (their HOME is
3025
+ * already `/home/main`); this is the one-way carry for what predates the move.
3021
3026
  *
3022
- * The one-shot migration (`migChownTreeToCommons`) relocates the transcript tree
3023
- * but NOT `~/.claude/skills`, where an agent keeps the custom monitoring scripts
3024
- * it authored. Those scripts (and any cron `check` that hard-codes their old
3025
- * `/root/.claude/...` path) are then unreachable: a cron guard runs DROPPED as the
3026
- * commons `main` user, which cannot traverse `/root` (0700) — so the guard dies
3027
- * `check-failed` in silence (measured on a live instance: three monitoring crons
3028
- * stuck, "son script est sous /root"). New agents are fine (their HOME is already
3029
- * `/home/main`); this is the one-way carry for what predates the move.
3027
+ * What is relocated here and then repointed:
3028
+ * 1. `~/.claude/skills` — ALWAYS, the subtree `migChownTreeToCommons` skipped,
3029
+ * where an agent keeps the custom skills it authored even when no guard names
3030
+ * them (#419).
3031
+ * 2. Every home-rooted `<top>/<sub>` script dir a cron check NAMES — `.claude/scripts`
3032
+ * (seen on the biosense cockpit), any `.shadok-ai/<dir>` (the dogfood instance),
3033
+ * and any future one — discovered by `strandedCheckDirs`. The trap is not one
3034
+ * directory, it is "any `/root` path a check names". Its denylist refuses shadok's
3035
+ * own state/credential stores and the big already-migrated `.claude` trees, so a
3036
+ * key/secret dir is NEVER copied into `/home/main`.
3030
3037
  *
3031
3038
  * Runs on EVERY capable boot, independent of the migration marker (the twin of
3032
3039
  * the reprovision reconciler above), so an ALREADY-migrated instance self-heals on
@@ -3035,38 +3042,62 @@ async function ensureGroupsProvisioned() {
3035
3042
  * (`cp -rn`, non-clobber) and the checks already rewritten (identity). No-op unless
3036
3043
  * the HOME actually moved (mono-tenant / pre-migration: old === new).
3037
3044
  */
3038
- async function healCommonsClaudeScripts() {
3045
+ async function healCommonsStrandedScripts() {
3039
3046
  if (DEPLOYMENT_MODE !== "multi-tenant")
3040
3047
  return;
3041
3048
  const oldHome = os.homedir();
3042
3049
  const newHome = homeForUser(COMMONS_USER, GROUP_HOME_BASE);
3043
3050
  if (!newHome || newHome === oldHome)
3044
3051
  return; // not migrated off /root → nothing moved
3045
- // 1) Carry the agent-authored scripts across: <oldHome>/.claude/skills →
3046
- // <newHome>/.claude/skills, non-clobber, owned by `main`. This is the one
3047
- // `.claude` subtree the migration skipped (skills are seeded machine-wide in
3048
- // MT, but an agent's CUSTOM files live only in this per-HOME copy).
3052
+ const owner = `${COMMONS_USER}:${COMMONS_USER}`;
3053
+ // Carry one relative tree <oldHome>/<rel> → <newHome>/<rel>, non-clobber, owned
3054
+ // by `main`. The parent is `install -d`'d too so `~/.shadok-ai` exists in the new
3055
+ // HOME. No-op when the source is absent.
3056
+ const copyTree = async (rel) => {
3057
+ const src = path.join(oldHome, rel);
3058
+ if (!fs.existsSync(src))
3059
+ return;
3060
+ const dst = path.join(newHome, rel);
3061
+ await runPrivileged("install", ["-d", "-o", COMMONS_USER, "-g", COMMONS_USER, path.dirname(dst)]);
3062
+ await runPrivileged("install", ["-d", "-o", COMMONS_USER, "-g", COMMONS_USER, dst]);
3063
+ await runPrivileged("cp", ["-rn", `${src}/.`, `${dst}/`], { timeoutMs: TREE_OP_TIMEOUT_MS });
3064
+ await runPrivileged("chown", ["-R", owner, dst], { timeoutMs: TREE_OP_TIMEOUT_MS });
3065
+ };
3066
+ // Load once; the same list feeds the dir discovery AND the rewrite below.
3067
+ let crons = [];
3049
3068
  try {
3050
- const srcSkills = path.join(oldHome, ".claude", "skills");
3051
- if (fs.existsSync(srcSkills)) {
3052
- const owner = `${COMMONS_USER}:${COMMONS_USER}`;
3053
- const dstSkills = path.join(newHome, ".claude", "skills");
3054
- await runPrivileged("install", ["-d", "-o", COMMONS_USER, "-g", COMMONS_USER, dstSkills]);
3055
- await runPrivileged("cp", ["-rn", `${srcSkills}/.`, `${dstSkills}/`], { timeoutMs: TREE_OP_TIMEOUT_MS });
3056
- await runPrivileged("chown", ["-R", owner, dstSkills], { timeoutMs: TREE_OP_TIMEOUT_MS });
3057
- }
3069
+ crons = loadCrons();
3058
3070
  }
3059
3071
  catch (e) {
3060
- console.error(`heal: relocating commons ~/.claude/skills failed (best-effort): ${e?.message ?? e}`);
3072
+ console.error(`heal: reading crons failed (best-effort): ${e?.message ?? e}`);
3073
+ }
3074
+ // The home-rooted <top>/<sub> script dirs the guards name (.claude/scripts,
3075
+ // .shadok-ai/<dir>, …), deduped — never a credential/state dir, strandedCheckDirs
3076
+ // denylists those.
3077
+ const scriptDirs = [
3078
+ ...new Set(crons.flatMap((c) => (c.check ? strandedCheckDirs(c.check, oldHome) : []))),
3079
+ ];
3080
+ // 1) Relocate ~/.claude/skills unconditionally (the subtree the migration skipped,
3081
+ // where an agent's custom skills live even when no guard names them), plus every
3082
+ // discovered script dir. Deduped; each copy is independent and best-effort.
3083
+ for (const rel of [...new Set([path.join(".claude", "skills"), ...scriptDirs])]) {
3084
+ try {
3085
+ await copyTree(rel);
3086
+ }
3087
+ catch (e) {
3088
+ console.error(`heal: relocating commons ~/${rel} failed (best-effort): ${e?.message ?? e}`);
3089
+ }
3061
3090
  }
3062
- // 2) Repoint this instance's cron guards that still name the old /root HOME.
3091
+ // 2) Repoint this instance's cron guards: the .claude base (covers any .claude/*
3092
+ // path) AND every relocated .shadok-ai/<dir>. A /root path NOT relocated (a
3093
+ // denylisted or unknown dir) stays intact — surfaced as a check-failed, not
3094
+ // silently repointed to a place nothing was copied.
3063
3095
  try {
3064
- const crons = loadCrons();
3065
3096
  let changed = false;
3066
3097
  for (const c of crons) {
3067
3098
  if (!c.check)
3068
3099
  continue;
3069
- const next = rewriteCronCheckHome(c.check, oldHome, newHome);
3100
+ const next = rewriteCronCheckHome(c.check, oldHome, newHome, scriptDirs);
3070
3101
  if (next !== c.check) {
3071
3102
  c.check = next;
3072
3103
  changed = true;
@@ -3074,7 +3105,8 @@ async function healCommonsClaudeScripts() {
3074
3105
  }
3075
3106
  if (changed) {
3076
3107
  saveCrons(crons);
3077
- console.log(`heal: repointed cron guard path(s) from ${oldHome}/.claude → ${newHome}/.claude`);
3108
+ console.log(`heal: repointed cron guard path(s) from ${oldHome} → ${newHome} ` +
3109
+ `(.claude${scriptDirs.length ? ", " + scriptDirs.join(", ") : ""})`);
3078
3110
  }
3079
3111
  }
3080
3112
  catch (e) {