@pleri/olam-cli 0.1.195 → 0.1.198

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.
Files changed (144) hide show
  1. package/README.md +52 -0
  2. package/dist/ask/knowledge-pack.generated.d.ts.map +1 -1
  3. package/dist/ask/knowledge-pack.generated.js +12 -8
  4. package/dist/ask/knowledge-pack.generated.js.map +1 -1
  5. package/dist/commands/auth-list-json.d.ts +34 -0
  6. package/dist/commands/auth-list-json.d.ts.map +1 -1
  7. package/dist/commands/auth-list-json.js +24 -0
  8. package/dist/commands/auth-list-json.js.map +1 -1
  9. package/dist/commands/auth-migrate.d.ts +212 -0
  10. package/dist/commands/auth-migrate.d.ts.map +1 -0
  11. package/dist/commands/auth-migrate.js +465 -0
  12. package/dist/commands/auth-migrate.js.map +1 -0
  13. package/dist/commands/auth.d.ts.map +1 -1
  14. package/dist/commands/auth.js +239 -184
  15. package/dist/commands/auth.js.map +1 -1
  16. package/dist/commands/bootstrap.d.ts +4 -0
  17. package/dist/commands/bootstrap.d.ts.map +1 -1
  18. package/dist/commands/bootstrap.js +6 -0
  19. package/dist/commands/bootstrap.js.map +1 -1
  20. package/dist/commands/dispatch.d.ts.map +1 -1
  21. package/dist/commands/dispatch.js +11 -1
  22. package/dist/commands/dispatch.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +33 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +299 -12
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/kg-mirror.d.ts +18 -2
  28. package/dist/commands/kg-mirror.d.ts.map +1 -1
  29. package/dist/commands/kg-mirror.js +78 -3
  30. package/dist/commands/kg-mirror.js.map +1 -1
  31. package/dist/commands/mcp/complete.d.ts +36 -0
  32. package/dist/commands/mcp/complete.d.ts.map +1 -0
  33. package/dist/commands/mcp/complete.js +66 -0
  34. package/dist/commands/mcp/complete.js.map +1 -0
  35. package/dist/commands/mcp/index.d.ts +1 -1
  36. package/dist/commands/mcp/index.d.ts.map +1 -1
  37. package/dist/commands/mcp/index.js +3 -1
  38. package/dist/commands/mcp/index.js.map +1 -1
  39. package/dist/commands/memory/bridge.d.ts +1 -1
  40. package/dist/commands/memory/bridge.d.ts.map +1 -1
  41. package/dist/commands/memory/bridge.js +2 -6
  42. package/dist/commands/memory/bridge.js.map +1 -1
  43. package/dist/commands/memory/secret.d.ts.map +1 -1
  44. package/dist/commands/memory/secret.js +4 -3
  45. package/dist/commands/memory/secret.js.map +1 -1
  46. package/dist/commands/observe.d.ts +3 -3
  47. package/dist/commands/observe.d.ts.map +1 -1
  48. package/dist/commands/observe.js +11 -8
  49. package/dist/commands/observe.js.map +1 -1
  50. package/dist/commands/runbooks.d.ts.map +1 -1
  51. package/dist/commands/runbooks.js +77 -10
  52. package/dist/commands/runbooks.js.map +1 -1
  53. package/dist/commands/services-tls.d.ts.map +1 -1
  54. package/dist/commands/services-tls.js +65 -10
  55. package/dist/commands/services-tls.js.map +1 -1
  56. package/dist/commands/services.d.ts +35 -1
  57. package/dist/commands/services.d.ts.map +1 -1
  58. package/dist/commands/services.js +153 -32
  59. package/dist/commands/services.js.map +1 -1
  60. package/dist/commands/setup-phase-8-kg-hook.d.ts +48 -0
  61. package/dist/commands/setup-phase-8-kg-hook.d.ts.map +1 -0
  62. package/dist/commands/setup-phase-8-kg-hook.js +93 -0
  63. package/dist/commands/setup-phase-8-kg-hook.js.map +1 -0
  64. package/dist/commands/setup-phase-9-memory-bridge.d.ts +36 -0
  65. package/dist/commands/setup-phase-9-memory-bridge.d.ts.map +1 -0
  66. package/dist/commands/setup-phase-9-memory-bridge.js +59 -0
  67. package/dist/commands/setup-phase-9-memory-bridge.js.map +1 -0
  68. package/dist/commands/setup.d.ts +34 -1
  69. package/dist/commands/setup.d.ts.map +1 -1
  70. package/dist/commands/setup.js +372 -32
  71. package/dist/commands/setup.js.map +1 -1
  72. package/dist/commands/skills-source.d.ts.map +1 -1
  73. package/dist/commands/skills-source.js +70 -1
  74. package/dist/commands/skills-source.js.map +1 -1
  75. package/dist/commands/update.d.ts +24 -0
  76. package/dist/commands/update.d.ts.map +1 -1
  77. package/dist/commands/update.js +53 -0
  78. package/dist/commands/update.js.map +1 -1
  79. package/dist/commands/upgrade.d.ts +5 -0
  80. package/dist/commands/upgrade.d.ts.map +1 -1
  81. package/dist/commands/upgrade.js +31 -8
  82. package/dist/commands/upgrade.js.map +1 -1
  83. package/dist/image-digests.json +8 -8
  84. package/dist/index.js +4487 -2451
  85. package/dist/lib/auth-backend.d.ts +168 -0
  86. package/dist/lib/auth-backend.d.ts.map +1 -0
  87. package/dist/lib/auth-backend.js +172 -0
  88. package/dist/lib/auth-backend.js.map +1 -0
  89. package/dist/lib/auth-list-cache.d.ts +67 -0
  90. package/dist/lib/auth-list-cache.d.ts.map +1 -0
  91. package/dist/lib/auth-list-cache.js +84 -0
  92. package/dist/lib/auth-list-cache.js.map +1 -0
  93. package/dist/lib/auth-list.d.ts +107 -0
  94. package/dist/lib/auth-list.d.ts.map +1 -0
  95. package/dist/lib/auth-list.js +123 -0
  96. package/dist/lib/auth-list.js.map +1 -0
  97. package/dist/lib/auth-login.d.ts +92 -0
  98. package/dist/lib/auth-login.d.ts.map +1 -0
  99. package/dist/lib/auth-login.js +124 -0
  100. package/dist/lib/auth-login.js.map +1 -0
  101. package/dist/lib/auth-mutator-backend.d.ts +54 -0
  102. package/dist/lib/auth-mutator-backend.d.ts.map +1 -0
  103. package/dist/lib/auth-mutator-backend.js +62 -0
  104. package/dist/lib/auth-mutator-backend.js.map +1 -0
  105. package/dist/lib/auth-remote.d.ts +50 -0
  106. package/dist/lib/auth-remote.d.ts.map +1 -1
  107. package/dist/lib/auth-remote.js +84 -2
  108. package/dist/lib/auth-remote.js.map +1 -1
  109. package/dist/lib/bootstrap-kubernetes.d.ts +69 -10
  110. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  111. package/dist/lib/bootstrap-kubernetes.js +264 -46
  112. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  113. package/dist/lib/config.d.ts +35 -4
  114. package/dist/lib/config.d.ts.map +1 -1
  115. package/dist/lib/config.js +82 -11
  116. package/dist/lib/config.js.map +1 -1
  117. package/dist/lib/health-probes.d.ts +0 -22
  118. package/dist/lib/health-probes.d.ts.map +1 -1
  119. package/dist/lib/health-probes.js +57 -0
  120. package/dist/lib/health-probes.js.map +1 -1
  121. package/dist/lib/peripheral-registry.d.ts +11 -0
  122. package/dist/lib/peripheral-registry.d.ts.map +1 -1
  123. package/dist/lib/peripheral-registry.js +5 -0
  124. package/dist/lib/peripheral-registry.js.map +1 -1
  125. package/dist/lib/plans-client.d.ts.map +1 -1
  126. package/dist/lib/plans-client.js +6 -3
  127. package/dist/lib/plans-client.js.map +1 -1
  128. package/dist/mcp-server.js +138 -6
  129. package/hermes-bundle/version.json +1 -1
  130. package/host-cp/k8s/manifests/30-configmap.yaml +4 -0
  131. package/host-cp/k8s/manifests/50-deployment.yaml +13 -1
  132. package/host-cp/k8s/manifests/65-tls-secret-template.yaml.tmpl +35 -0
  133. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  134. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  135. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  136. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  137. package/host-cp/src/dispatch-persister.mjs +157 -0
  138. package/host-cp/src/pr-nanny.mjs +7 -0
  139. package/host-cp/src/server.mjs +175 -3
  140. package/host-cp/src/world-watchdog-pid-lookup.mjs +119 -0
  141. package/host-cp/src/world-watchdog-probes.mjs +271 -0
  142. package/host-cp/src/world-watchdog-recovery.mjs +192 -0
  143. package/host-cp/src/world-watchdog.mjs +313 -0
  144. package/package.json +1 -1
@@ -11497,7 +11497,14 @@ var init_schema2 = __esm({
11497
11497
  // a doc-lookup task routes to `cloudflare-isolate` when its worker_url is
11498
11498
  // set). Never routes to an unconfigured tier; falls back to `default` with
11499
11499
  // an explicit reason. See packages/core/src/dispatch/tier-selection.ts.
11500
- verifiability_routing: external_exports.boolean().optional().default(false)
11500
+ verifiability_routing: external_exports.boolean().optional().default(false),
11501
+ // Phase B auto-recovery — off by default (backward-compatible with all
11502
+ // existing .olam/config.yaml files that omit this field).
11503
+ // false — detection only; never kills (default)
11504
+ // true — kill + replay when wedge confirmed + rate-limit allows
11505
+ // 'dry-run' — emits breadcrumbs but never kills (confidence mode)
11506
+ // See docs/architecture/world-watchdog.md Recovery section.
11507
+ autoRecover: external_exports.union([external_exports.boolean(), external_exports.literal("dry-run")]).default(false)
11501
11508
  });
11502
11509
  CostConfigSchema = external_exports.object({
11503
11510
  max_per_world_usd: external_exports.number().positive().default(50),
@@ -11695,7 +11702,10 @@ var init_defaults = __esm({
11695
11702
  providers: {},
11696
11703
  // Verifiability-routed tier selection is opt-in (ADR 022 §Consequences #2);
11697
11704
  // the default is OFF so dispatch behaviour is unchanged out of the box.
11698
- verifiability_routing: false
11705
+ verifiability_routing: false,
11706
+ // World-watchdog auto-recovery is opt-in; default OFF so existing operators
11707
+ // see no behaviour change until they explicitly enable it in config.
11708
+ autoRecover: false
11699
11709
  };
11700
11710
  DEFAULT_COST = {
11701
11711
  max_per_world_usd: 50,
@@ -11983,7 +11993,17 @@ var init_source_config_schema = __esm({
11983
11993
  * `SkillSourceSchema.prefixScope`. Operators inherit this when their
11984
11994
  * `~/.olam/config.json` has no explicit `prefixScope` override.
11985
11995
  */
11986
- prefixScope: external_exports.array(external_exports.enum(["skill", "agent"])).optional()
11996
+ prefixScope: external_exports.array(external_exports.enum(["skill", "agent"])).optional(),
11997
+ /**
11998
+ * Source's RECOMMENDED prefixTarget patterns. Same semantics as
11999
+ * operator-side `SkillSourceSchema.prefixTarget`: an array of glob
12000
+ * patterns (only `*` wildcard) that restrict which canonical names are
12001
+ * renamed. Operators inherit this when their `~/.olam/config.json` has
12002
+ * no explicit `prefixTarget` override.
12003
+ *
12004
+ * See: docs/decisions/022-prefix-target-name-patterns.md.
12005
+ */
12006
+ prefixTarget: external_exports.array(external_exports.string().min(1, "prefixTarget pattern must be a non-empty string")).optional()
11987
12007
  }).strict();
11988
12008
  SourceConfigSnapshotSchema = SourceConfigSchema;
11989
12009
  }
@@ -12029,6 +12049,29 @@ var init_schema3 = __esm({
12029
12049
  * NOT applicable when `prefix` is undefined.
12030
12050
  */
12031
12051
  prefixScope: external_exports.array(external_exports.enum(["skill", "agent"])).optional(),
12052
+ /**
12053
+ * Restrict prefix renaming to canonical names matching at least one glob
12054
+ * pattern. Patterns support `*` as a wildcard (zero or more chars); no
12055
+ * other metacharacters. Examples:
12056
+ *
12057
+ * `['*']` — rename ALL skills/agents (DEFAULT — today's behavior)
12058
+ * `['10x:*']` — rename only canonicals that start with `10x:`
12059
+ * `['10x:*', 'atl:*']`— match either namespace
12060
+ * `['plan-*']` — match a name-family prefix
12061
+ *
12062
+ * When `prefixTarget` is undefined OR `['*']`, all kind-allowed artifacts are
12063
+ * renamed (identical to the pre-ADR-022 behavior — full backward compat).
12064
+ *
12065
+ * When set, artifacts whose canonical name does NOT match ANY pattern are
12066
+ * deployed WITHOUT a prefix (canonical-only). No error is raised; `sync`
12067
+ * output emits a `[prefix-target: <n> skipped]` warning so operators can
12068
+ * detect misconfigured patterns.
12069
+ *
12070
+ * NOT applicable when `prefix` is undefined.
12071
+ *
12072
+ * See: docs/decisions/022-prefix-target-name-patterns.md.
12073
+ */
12074
+ prefixTarget: external_exports.array(external_exports.string().min(1, "prefixTarget pattern must be a non-empty string")).optional(),
12032
12075
  /**
12033
12076
  * Snapshot of the LAST source-config.yaml content this operator saw +
12034
12077
  * consented to (ADR-021). Trust-gate state: when the live source-config
@@ -12706,6 +12749,18 @@ function updateSkillSource(id, patch) {
12706
12749
  } else if (patch.prefixScope !== void 0) {
12707
12750
  working = { ...working, prefixScope: [...patch.prefixScope] };
12708
12751
  }
12752
+ if (patch.prefixTarget === null) {
12753
+ const { prefixTarget: _t, ...rest } = working;
12754
+ void _t;
12755
+ working = rest;
12756
+ } else if (patch.prefixTarget !== void 0) {
12757
+ for (const p of patch.prefixTarget) {
12758
+ if (typeof p !== "string" || p.length === 0) {
12759
+ throw new Error(`prefixTarget patterns must be non-empty strings \u2014 got ${JSON.stringify(p)}`);
12760
+ }
12761
+ }
12762
+ working = { ...working, prefixTarget: [...patch.prefixTarget] };
12763
+ }
12709
12764
  if (patch.lastSeenSourcePrefix === null) {
12710
12765
  const { lastSeenSourcePrefix: _l, ...rest } = working;
12711
12766
  void _l;
@@ -15083,10 +15138,61 @@ function rewriteFrontmatterName(content, rewriter) {
15083
15138
  const rebuilt = serializeFrontmatter(nextFm) + parsed.body;
15084
15139
  return Buffer.from(rebuilt, "utf-8");
15085
15140
  }
15141
+ function matchesSinglePattern(candidate, pattern) {
15142
+ const cacheKey2 = `${candidate}\0${pattern}`;
15143
+ const cached2 = _matchCache.get(cacheKey2);
15144
+ if (cached2 !== void 0)
15145
+ return cached2;
15146
+ const result = _matchesSinglePatternRaw(candidate, pattern);
15147
+ _matchCache.set(cacheKey2, result);
15148
+ return result;
15149
+ }
15150
+ function _matchesSinglePatternRaw(candidate, pattern) {
15151
+ if (pattern === "*")
15152
+ return true;
15153
+ if (!pattern.includes("*"))
15154
+ return candidate === pattern;
15155
+ const segments = pattern.split("*");
15156
+ let pos = 0;
15157
+ for (let i = 0; i < segments.length; i++) {
15158
+ const seg = segments[i];
15159
+ if (seg.length === 0)
15160
+ continue;
15161
+ if (i === 0) {
15162
+ if (!candidate.startsWith(seg))
15163
+ return false;
15164
+ pos = seg.length;
15165
+ } else if (i === segments.length - 1) {
15166
+ if (!candidate.endsWith(seg))
15167
+ return false;
15168
+ if (pos > candidate.length - seg.length)
15169
+ return false;
15170
+ } else {
15171
+ const found = candidate.indexOf(seg, pos);
15172
+ if (found === -1)
15173
+ return false;
15174
+ pos = found + seg.length;
15175
+ }
15176
+ }
15177
+ return true;
15178
+ }
15179
+ function matchesPrefixTarget(canonical, patterns) {
15180
+ if (patterns === void 0 || patterns.length === 0)
15181
+ return true;
15182
+ if (patterns.length === 1 && patterns[0] === "*")
15183
+ return true;
15184
+ for (const pattern of patterns) {
15185
+ if (matchesSinglePattern(canonical, pattern))
15186
+ return true;
15187
+ }
15188
+ return false;
15189
+ }
15190
+ var _matchCache;
15086
15191
  var init_prefix_rules = __esm({
15087
15192
  "../core/dist/skill-sync/prefix-rules.js"() {
15088
15193
  "use strict";
15089
15194
  init_markdown_merger();
15195
+ _matchCache = /* @__PURE__ */ new Map();
15090
15196
  }
15091
15197
  });
15092
15198
 
@@ -15096,17 +15202,20 @@ import * as path32 from "node:path";
15096
15202
  function buildSourcePrefixMap(sources) {
15097
15203
  const byId = /* @__PURE__ */ new Map();
15098
15204
  const scopeById = /* @__PURE__ */ new Map();
15205
+ const targetById = /* @__PURE__ */ new Map();
15099
15206
  const prefixes = /* @__PURE__ */ new Set();
15100
15207
  for (const s of sources) {
15101
15208
  if (s.prefix !== void 0 && s.prefix.length > 0) {
15102
15209
  byId.set(s.id, s.prefix);
15103
15210
  scopeById.set(s.id, s.prefixScope ?? DEFAULT_SCOPE);
15211
+ targetById.set(s.id, s.prefixTarget);
15104
15212
  prefixes.add(s.prefix);
15105
15213
  }
15106
15214
  }
15107
15215
  return {
15108
15216
  get: (sourceId) => byId.get(sourceId),
15109
15217
  getScope: (sourceId) => scopeById.get(sourceId) ?? DEFAULT_SCOPE,
15218
+ getPrefixTarget: (sourceId) => targetById.get(sourceId),
15110
15219
  registeredPrefixes: Array.from(prefixes)
15111
15220
  };
15112
15221
  }
@@ -15122,6 +15231,9 @@ function applyPrefixRewrites(baseArtifacts, sourceMap, claudeDir2, dryRun) {
15122
15231
  if (!scope.includes(artifact.kind))
15123
15232
  continue;
15124
15233
  const canonical = artifact.deployBasename;
15234
+ const prefixTarget = sourceMap.getPrefixTarget(artifact.sourceId);
15235
+ if (!matchesPrefixTarget(canonical, prefixTarget))
15236
+ continue;
15125
15237
  const otherPrefixes = sourceMap.registeredPrefixes.filter((p) => p !== prefix);
15126
15238
  const renamed = applyPrefix(canonical, prefix, otherPrefixes);
15127
15239
  if (renamed === canonical)
@@ -15241,18 +15353,23 @@ function errToMsg(err) {
15241
15353
  function resolveEffectivePrefix(operatorSource, sourceConfig) {
15242
15354
  const opPrefix = operatorSource.prefix;
15243
15355
  const opScope = operatorSource.prefixScope;
15356
+ const opTarget = operatorSource.prefixTarget;
15244
15357
  const srcPrefix = sourceConfig?.prefix;
15245
15358
  const srcScope = sourceConfig?.prefixScope;
15359
+ const srcTarget = sourceConfig?.prefixTarget;
15246
15360
  const effectivePrefix = opPrefix ?? srcPrefix;
15247
15361
  const effectiveScope = opScope ?? srcScope;
15248
- const operatorHasAny = opPrefix !== void 0 || opScope !== void 0 && opScope.length > 0;
15249
- const sourceHasAny = srcPrefix !== void 0 || srcScope !== void 0 && srcScope.length > 0;
15362
+ const effectiveTarget = opTarget ?? srcTarget;
15363
+ const operatorHasAny = opPrefix !== void 0 || opScope !== void 0 && opScope.length > 0 || opTarget !== void 0 && opTarget.length > 0;
15364
+ const sourceHasAny = srcPrefix !== void 0 || srcScope !== void 0 && srcScope.length > 0 || srcTarget !== void 0 && srcTarget.length > 0;
15250
15365
  const origin = operatorHasAny ? "operator" : sourceHasAny ? "source" : "none";
15251
15366
  const result = { origin };
15252
15367
  if (effectivePrefix !== void 0)
15253
15368
  result.prefix = effectivePrefix;
15254
15369
  if (effectiveScope !== void 0)
15255
15370
  result.prefixScope = effectiveScope;
15371
+ if (effectiveTarget !== void 0)
15372
+ result.prefixTarget = effectiveTarget;
15256
15373
  return result;
15257
15374
  }
15258
15375
  function sourceConfigsEqual(a, b) {
@@ -15265,6 +15382,9 @@ function sourceConfigsEqual(a, b) {
15265
15382
  if (c.prefixScope !== void 0 && c.prefixScope.length > 0) {
15266
15383
  out.prefixScope = [...c.prefixScope];
15267
15384
  }
15385
+ if (c.prefixTarget !== void 0 && c.prefixTarget.length > 0) {
15386
+ out.prefixTarget = [...c.prefixTarget];
15387
+ }
15268
15388
  return out;
15269
15389
  };
15270
15390
  const na = normalize(a);
@@ -15279,6 +15399,14 @@ function sourceConfigsEqual(a, b) {
15279
15399
  if (sa[i] !== sb[i])
15280
15400
  return false;
15281
15401
  }
15402
+ const ta = na.prefixTarget ?? [];
15403
+ const tb = nb.prefixTarget ?? [];
15404
+ if (ta.length !== tb.length)
15405
+ return false;
15406
+ for (let i = 0; i < ta.length; i++) {
15407
+ if (ta[i] !== tb[i])
15408
+ return false;
15409
+ }
15282
15410
  return true;
15283
15411
  }
15284
15412
  var init_resolve_source_config = __esm({
@@ -15361,6 +15489,8 @@ async function syncSkills(opts = {}) {
15361
15489
  entry.prefix = eff.prefix;
15362
15490
  if (eff.prefixScope !== void 0)
15363
15491
  entry.prefixScope = eff.prefixScope;
15492
+ if (eff.prefixTarget !== void 0)
15493
+ entry.prefixTarget = eff.prefixTarget;
15364
15494
  return entry;
15365
15495
  });
15366
15496
  const sourcePrefixGateOutcomes = [];
@@ -15419,6 +15549,7 @@ async function syncSkills(opts = {}) {
15419
15549
  } else {
15420
15550
  delete eff.prefix;
15421
15551
  delete eff.prefixScope;
15552
+ delete eff.prefixTarget;
15422
15553
  eff.origin = "none";
15423
15554
  const isNonTty = opts.sourcePrefixPrompt === void 0;
15424
15555
  sourcePrefixGateOutcomes.push({
@@ -37453,10 +37584,11 @@ except Exception: pass' 2>/dev/null`;
37453
37584
  const authHeader = isCloud ? `-H "Authorization: Bearer \${OLAM_KG_PROXY_BEARER:-}"` : "";
37454
37585
  const cloudGuard = isCloud ? `if [ -z "\${OLAM_KG_PROXY_URL:-}" ] || [ -z "\${OLAM_KG_PROXY_BEARER:-}" ]; then exit 0; fi; ` : "";
37455
37586
  const curlPost = `RESP=$(curl -s --max-time 1 -X POST -H 'Content-Type: application/json' ${authHeader} -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" ${resolvedUrl} 2>/dev/null)`;
37587
+ const hostRemoteFallback = opts.flavor === "host" ? `; if [ -z "$RESP" ] && [ -r "$HOME/.olam/kg-proxy-url" ] && [ -r "$HOME/.olam/kg-proxy-bearer" ]; then RESP=$(curl -s --max-time 2 -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $(cat "$HOME/.olam/kg-proxy-bearer")" -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" "$(cat "$HOME/.olam/kg-proxy-url")/v1/classify" 2>/dev/null); fi` : "";
37456
37588
  return [
37457
37589
  `KG_SENTINEL=${KG_HOOK_SENTINEL}`,
37458
37590
  `CMD=$(${extractCmd})`,
37459
- `case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) ${cloudGuard}${curlPost}`,
37591
+ `case "$CMD" in grep\\ *|grep|rg\\ *|ripgrep\\ *|find\\ *|fd\\ *|ack\\ *|ag\\ *) ${cloudGuard}${curlPost}${hostRemoteFallback}`,
37460
37592
  `KG_QUERY="$(echo \\"$CMD\\" | head -c 60 | tr '\\"' ' ')" echo "$RESP" | ${emitContext}`,
37461
37593
  `;; esac`
37462
37594
  ].join("; ");
@@ -1,4 +1,4 @@
1
1
  {
2
- "bundledAt": "2026-05-28T12:44:06.696Z",
2
+ "bundledAt": "2026-05-29T05:30:51.511Z",
3
3
  "kgFirstSha": "29a9ccce1b115d049e375c4a90eb5cf7c123e610e2d0590270a4db2cdbc64a28"
4
4
  }
@@ -51,3 +51,7 @@ data:
51
51
  OLAM_SECRET_CACHE_TTL_SEC: "300"
52
52
  OLAM_PR_POLL_INTERVAL_MS: "300000"
53
53
  OLAM_MERGE_GRACE_MS: "600000"
54
+ # World watchdog — periodic probe of each active world's claude PID for the
55
+ # three wedge signals (wchan + CLOSE_WAIT + CPU). Detection-only in Phase A.
56
+ # Set OLAM_WORLD_WATCHDOG_DISABLED=1 in the deployment env to kill-switch.
57
+ OLAM_WORLD_WATCHDOG_TICK_MS: "30000"
@@ -118,7 +118,7 @@ spec:
118
118
  # k3d), started by `olam upgrade` Step 0.7 — not inside this Pod.
119
119
  containers:
120
120
  - name: olam-host-cp
121
- image: ghcr.io/pleri/olam-host-cp@sha256:42fb12f23d51c229288e0c0fa93df8028784136ce75245e582e4fffbc5867798
121
+ image: ghcr.io/pleri/olam-host-cp@sha256:c072cebaa1387b6f16e441b55922973bd9c781e80dc867bcfdbed309b0653918
122
122
  imagePullPolicy: IfNotPresent
123
123
  securityContext:
124
124
  runAsNonRoot: true
@@ -131,6 +131,18 @@ spec:
131
131
  - name: http
132
132
  containerPort: 19000
133
133
  protocol: TCP
134
+ env:
135
+ # World watchdog — tick cadence (from ConfigMap default = 30s).
136
+ # Override per-operator to tune probe frequency.
137
+ - name: OLAM_WORLD_WATCHDOG_TICK_MS
138
+ valueFrom:
139
+ configMapKeyRef:
140
+ name: olam-host-cp-env
141
+ key: OLAM_WORLD_WATCHDOG_TICK_MS
142
+ # Set to "1" to disable the world-watchdog entirely (emergency kill switch).
143
+ # Unset by default — watchdog runs in detection-only mode.
144
+ # - name: OLAM_WORLD_WATCHDOG_DISABLED
145
+ # value: "1"
134
146
  envFrom:
135
147
  - configMapRef:
136
148
  name: olam-host-cp-env
@@ -0,0 +1,35 @@
1
+ # TLS secret template for olam-host-cp Traefik IngressRoute.
2
+ #
3
+ # DO NOT apply this template directly — the placeholders `__TLS_CRT_BASE64__`
4
+ # and `__TLS_KEY_BASE64__` are substituted at apply time by
5
+ # `olam services tls-install` (packages/cli/src/commands/services-tls.ts),
6
+ # which uses `mkcert` to mint a locally-trusted certificate for the SAN list
7
+ # olam.local 127.0.0.1 ::1
8
+ # and then `kubectl apply -f -` against the rendered manifest.
9
+ #
10
+ # Why a Secret of type kubernetes.io/tls (instead of a plain Opaque secret):
11
+ # Traefik's IngressRoute TLS resolver requires this exact type — it reads
12
+ # tls.crt + tls.key fields by convention. Using Opaque would silently fail
13
+ # the handshake at request time.
14
+ #
15
+ # Why the cert covers SANs (not just CN): modern browsers (Chrome 58+, Brave,
16
+ # Safari, Firefox) ignore the certificate CN entirely and only honour SANs.
17
+ # Without `127.0.0.1` + `::1` in the SAN list, hitting the IP directly fails
18
+ # even though the cert is "valid for olam.local".
19
+ #
20
+ # Renewal: certs minted by mkcert are valid ~2 years and 3 months. The
21
+ # tls-install command checks NotAfter and regenerates when within 30 days
22
+ # of expiry. To force regeneration: `kubectl -n olam delete secret olam-host-cp-tls`
23
+ # and re-run `olam services tls-install`.
24
+ apiVersion: v1
25
+ kind: Secret
26
+ metadata:
27
+ name: olam-host-cp-tls
28
+ namespace: olam
29
+ labels:
30
+ app: olam-host-cp
31
+ olam.io/component: host-stack
32
+ type: kubernetes.io/tls
33
+ data:
34
+ tls.crt: __TLS_CRT_BASE64__
35
+ tls.key: __TLS_KEY_BASE64__
@@ -70,7 +70,7 @@ spec:
70
70
  mountPath: /data
71
71
  containers:
72
72
  - name: olam-auth-service
73
- image: ghcr.io/pleri/olam-auth@sha256:e982aa9812c9c57768987d8fc0a22178c84811bf59a1470eb7a5aa58a73f11a5
73
+ image: ghcr.io/pleri/olam-auth@sha256:8ab49079d37fcc6aff70631526df1034f85d19e9854c7dc0d7789c0bd5a7f209
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -61,7 +61,7 @@ spec:
61
61
  mountPath: /data
62
62
  containers:
63
63
  - name: olam-kg-service
64
- image: ghcr.io/pleri/olam-kg-service@sha256:bd7c1c65b3537fd59a8a5f252a99a7fc5c2e195e973356bfe764b957fdebe58c
64
+ image: ghcr.io/pleri/olam-kg-service@sha256:318ad6566dc42d4202accd582086086783df2b4f0b7fe11fdbdeab3541489cf4
65
65
  imagePullPolicy: IfNotPresent
66
66
  securityContext:
67
67
  runAsNonRoot: true
@@ -68,7 +68,7 @@ spec:
68
68
  mountPath: /data
69
69
  containers:
70
70
  - name: olam-mcp-auth-service
71
- image: ghcr.io/pleri/olam-mcp-auth@sha256:1191734c32480a7ab22dbeede616c0f697ec02e3d0d43093cbbf56d6fe3b115c
71
+ image: ghcr.io/pleri/olam-mcp-auth@sha256:e59f7a04ae43d29233e29b812f3de22a74ad08bcb5060c4c580d8b6b4653d614
72
72
  imagePullPolicy: IfNotPresent
73
73
  securityContext:
74
74
  runAsNonRoot: true
@@ -70,7 +70,7 @@ spec:
70
70
  # bootstrap-placeholder comment + run `npm run refresh:manifest-digests`
71
71
  # once ghcr.io/pleri/olam-memory-service has a real published digest.
72
72
  # bootstrap-placeholder: pre-publish; refresh after first release
73
- image: ghcr.io/pleri/olam-memory-service@sha256:2037a12d390be09714bb80e2d707fb94d210f28b5227428d3047fe9155635acd
73
+ image: ghcr.io/pleri/olam-memory-service@sha256:c26d027243fe6f82c8cadb41d8174703e171a85a7872d1e9a55c6e2c80482a4d
74
74
  imagePullPolicy: IfNotPresent
75
75
  securityContext:
76
76
  runAsNonRoot: true
@@ -0,0 +1,157 @@
1
+ /**
2
+ * dispatch-persister.mjs — persist the last dispatch for each world.
3
+ *
4
+ * The world watchdog's recovery hook reads this to replay the last
5
+ * unanswered prompt when it auto-recovers a wedged claude process.
6
+ *
7
+ * Contract:
8
+ * persist({ worldId, messageId, prompt, source, statePath?, now? })
9
+ * Atomically writes ~/.olam/worlds/<worldId>/state/last-dispatch.json.
10
+ * Overwrites any previous file — only the LATEST dispatch matters for
11
+ * replay. Atomic write (tmp + fs.rename) prevents partial-write residue
12
+ * from corrupting recovery reads.
13
+ *
14
+ * read({ worldId, statePath? })
15
+ * Returns { messageId, prompt, dispatchedAt, source } or null.
16
+ * null on ENOENT (no dispatch persisted yet) — never throws.
17
+ * null on JSON parse error (logs + skips) — never throws on corrupt file.
18
+ *
19
+ * Multiple worlds are independent: world A and world B have separate files.
20
+ * Multiple concurrent persist() calls for the SAME world are safe — each
21
+ * write is a rename of a tmp file so the worst case is one write winning.
22
+ *
23
+ * @see docs/architecture/world-watchdog.md
24
+ */
25
+
26
+ import fs from 'node:fs/promises';
27
+ import path from 'node:path';
28
+ import os from 'node:os';
29
+
30
+ // Default base path under which per-world state directories live.
31
+ const DEFAULT_STATE_BASE = path.join(os.homedir(), '.olam', 'worlds');
32
+
33
+ /**
34
+ * Derive the path to last-dispatch.json for a world.
35
+ *
36
+ * @param {string} worldId
37
+ * @param {string} [stateBase] Override the base directory (for tests).
38
+ * @returns {string}
39
+ */
40
+ export function lastDispatchPath(worldId, stateBase = DEFAULT_STATE_BASE) {
41
+ return path.join(stateBase, worldId, 'state', 'last-dispatch.json');
42
+ }
43
+
44
+ /**
45
+ * Persist the last dispatch for a world.
46
+ *
47
+ * @param {{
48
+ * worldId: string,
49
+ * messageId: string,
50
+ * prompt: string,
51
+ * source: string,
52
+ * statePath?: string,
53
+ * now?: () => number,
54
+ * }} opts
55
+ * @returns {Promise<void>}
56
+ */
57
+ export async function persist({
58
+ worldId,
59
+ messageId,
60
+ prompt,
61
+ source,
62
+ statePath,
63
+ now = () => Date.now(),
64
+ }) {
65
+ const filePath = statePath ?? lastDispatchPath(worldId);
66
+ const dir = path.dirname(filePath);
67
+ const tmpPath = `${filePath}.tmp`;
68
+
69
+ const record = {
70
+ messageId,
71
+ prompt,
72
+ dispatchedAt: new Date(now()).toISOString(),
73
+ source,
74
+ };
75
+
76
+ // Ensure the directory exists.
77
+ await fs.mkdir(dir, { recursive: true });
78
+
79
+ // Atomic write: write to .tmp then rename over the target.
80
+ await fs.writeFile(tmpPath, JSON.stringify(record, null, 2) + '\n', 'utf8');
81
+ await fs.rename(tmpPath, filePath);
82
+ }
83
+
84
+ /**
85
+ * Fire-and-forget persist wrapper used at the dispatch call-sites.
86
+ *
87
+ * Centralises the void/.catch boilerplate so the two enrichment sites
88
+ * (pr-nanny + /api/cloud-dispatch) can't drift on future changes.
89
+ * Logs failures via the supplied logSource tag; never throws.
90
+ *
91
+ * @param {{
92
+ * worldId: string,
93
+ * messageId: string,
94
+ * prompt: string,
95
+ * source: string,
96
+ * logSource?: string,
97
+ * statePath?: string,
98
+ * now?: () => number,
99
+ * }} opts
100
+ * @returns {void}
101
+ */
102
+ export function safePersistLastDispatch(opts) {
103
+ const { logSource = opts.source, ...persistOpts } = opts;
104
+ void persist(persistOpts).catch((err) => {
105
+ console.warn(
106
+ `[${logSource}] persistLastDispatch failed (non-fatal): ${err?.message ?? err}`,
107
+ );
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Read the last persisted dispatch for a world.
113
+ *
114
+ * @param {{
115
+ * worldId: string,
116
+ * statePath?: string,
117
+ * }} opts
118
+ * @returns {Promise<{ messageId: string, prompt: string, dispatchedAt: string, source: string } | null>}
119
+ */
120
+ export async function read({ worldId, statePath }) {
121
+ const filePath = statePath ?? lastDispatchPath(worldId);
122
+
123
+ let raw;
124
+ try {
125
+ raw = await fs.readFile(filePath, 'utf8');
126
+ } catch (err) {
127
+ if (err?.code === 'ENOENT') return null;
128
+ // Other I/O errors (e.g. permissions) — log + return null (fail-soft).
129
+ console.error(`[dispatch-persister] readFile ${filePath}: ${err?.message ?? err}`);
130
+ return null;
131
+ }
132
+
133
+ try {
134
+ const parsed = JSON.parse(raw);
135
+ // Basic shape validation — don't throw on corrupt file.
136
+ if (
137
+ typeof parsed !== 'object' ||
138
+ parsed === null ||
139
+ typeof parsed.messageId !== 'string' ||
140
+ typeof parsed.prompt !== 'string' ||
141
+ typeof parsed.dispatchedAt !== 'string' ||
142
+ typeof parsed.source !== 'string'
143
+ ) {
144
+ console.error(`[dispatch-persister] ${filePath}: unexpected shape, skipping`);
145
+ return null;
146
+ }
147
+ return {
148
+ messageId: parsed.messageId,
149
+ prompt: parsed.prompt,
150
+ dispatchedAt: parsed.dispatchedAt,
151
+ source: parsed.source,
152
+ };
153
+ } catch (err) {
154
+ console.error(`[dispatch-persister] ${filePath}: JSON parse error: ${err?.message ?? err}`);
155
+ return null;
156
+ }
157
+ }
@@ -24,6 +24,7 @@
24
24
  import { execFile } from 'node:child_process';
25
25
  import { promisify } from 'node:util';
26
26
  import { pickNextTier } from './dispatch/tier-escalator.mjs';
27
+ import { safePersistLastDispatch } from './dispatch-persister.mjs';
27
28
 
28
29
  const execFileAsync = promisify(execFile);
29
30
 
@@ -251,6 +252,12 @@ export function createPrNanny({
251
252
 
252
253
  // Dispatch fix
253
254
  try {
255
+ safePersistLastDispatch({
256
+ worldId,
257
+ messageId: `nanny-${worldId}-${Date.now()}`,
258
+ prompt,
259
+ source: 'pr-nanny',
260
+ });
254
261
  await dispatchToWorld(worldId, prompt, { tier: tierForThisDispatch });
255
262
  const now = new Date().toISOString();
256
263
  prStateStore.set(worldId, {