oomi-ai 0.2.45 → 0.2.48

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.
package/bin/oomi-ai.js CHANGED
@@ -924,8 +924,98 @@ function collectManagedPersonaRefreshTargets({
924
924
 
925
925
  return targets.sort((a, b) => a.slug.localeCompare(b.slug));
926
926
  }
927
-
928
- async function findExistingManagedPersona(client, slug) {
927
+
928
+ function normalizeBackendPersonaRefreshRecord(rawPersona) {
929
+ if (!rawPersona || typeof rawPersona !== 'object') {
930
+ return null;
931
+ }
932
+
933
+ const slug = String(
934
+ rawPersona.slug ||
935
+ rawPersona.id ||
936
+ rawPersona.personaId ||
937
+ ''
938
+ ).trim();
939
+ if (!slug) {
940
+ return null;
941
+ }
942
+
943
+ return {
944
+ slug,
945
+ name: String(rawPersona.name || slug).trim() || slug,
946
+ description: String(rawPersona.description || rawPersona.summary || rawPersona.name || slug).trim() || slug,
947
+ templateVersion: String(rawPersona.promptTemplateVersion || 'v1').trim() || 'v1',
948
+ templateType: String(rawPersona.templateType || '').trim(),
949
+ };
950
+ }
951
+
952
+ function resolveExistingWorkspacePathForSlug(slug, workspaceRoot = defaultPersonaWorkspaceRoot()) {
953
+ for (const root of listPersonaWorkspaceRoots(workspaceRoot)) {
954
+ const workspacePath = resolvePersonaWorkspacePath({
955
+ workspaceRoot: root,
956
+ slug,
957
+ });
958
+ if (fs.existsSync(workspacePath)) {
959
+ return workspacePath;
960
+ }
961
+ }
962
+ return '';
963
+ }
964
+
965
+ async function discoverBackendLinkedPersonaRefreshTargets({
966
+ client,
967
+ workspaceRoot = defaultPersonaWorkspaceRoot(),
968
+ existingTargets = [],
969
+ logger = null,
970
+ } = {}) {
971
+ if (!client || typeof client.listPersonas !== 'function') {
972
+ return [];
973
+ }
974
+
975
+ const targetsBySlug = new Map(
976
+ Array.isArray(existingTargets)
977
+ ? existingTargets.map((target) => [String(target?.slug || '').trim(), target]).filter(([slug]) => slug)
978
+ : [],
979
+ );
980
+
981
+ const payload = await client.listPersonas();
982
+ const backendPersonas = Array.isArray(payload?.personas) ? payload.personas : [];
983
+ const discoveredTargets = [];
984
+
985
+ for (const rawPersona of backendPersonas) {
986
+ const backendPersona = normalizeBackendPersonaRefreshRecord(rawPersona);
987
+ if (!backendPersona || targetsBySlug.has(backendPersona.slug)) {
988
+ continue;
989
+ }
990
+ const workspacePath = resolveExistingWorkspacePathForSlug(backendPersona.slug, workspaceRoot);
991
+ if (!workspacePath) {
992
+ continue;
993
+ }
994
+ const state = readPersonaRuntimeState(workspacePath);
995
+ const target = {
996
+ slug: backendPersona.slug,
997
+ workspacePath,
998
+ state: {
999
+ ...state,
1000
+ slug: backendPersona.slug,
1001
+ name: backendPersona.name,
1002
+ description: backendPersona.description,
1003
+ templateVersion: backendPersona.templateVersion,
1004
+ },
1005
+ processRunning: false,
1006
+ };
1007
+ discoveredTargets.push(target);
1008
+ targetsBySlug.set(backendPersona.slug, target);
1009
+ }
1010
+
1011
+ if (discoveredTargets.length > 0) {
1012
+ logger?.(`Added ${discoveredTargets.length} backend-linked persona runtime target${discoveredTargets.length === 1 ? '' : 's'} from workspace discovery.`);
1013
+ }
1014
+
1015
+ return discoveredTargets;
1016
+ }
1017
+
1018
+ async function findExistingManagedPersona(client, slug) {
929
1019
  try {
930
1020
  return await client.getPersona({ slug });
931
1021
  } catch (error) {
@@ -1314,6 +1404,7 @@ function printStructuredResult(result, asJson = false) {
1314
1404
 
1315
1405
  async function handlePersonaRuntimeRegisterCommand(slug, flags = {}) {
1316
1406
  const client = createCliPersonaApiClient(flags);
1407
+ const jsonOutput = isTruthyFlag(flags.json);
1317
1408
  const runtime = resolvePersonaRuntimeInput(
1318
1409
  flags,
1319
1410
  {},
@@ -1322,19 +1413,27 @@ async function handlePersonaRuntimeRegisterCommand(slug, flags = {}) {
1322
1413
  workspaceRoot: resolvePersonaWorkspaceRoot(flags),
1323
1414
  },
1324
1415
  );
1416
+ if (!jsonOutput) {
1417
+ console.log(`[personas] Registering runtime for ${slug}.`);
1418
+ console.log(`[personas] Backend: ${resolvePersonaBackendUrl(flags)}`);
1419
+ console.log(`[personas] Device: ${resolvePersonaDeviceId(flags)}`);
1420
+ console.log(`[personas] Endpoint: ${runtime.endpoint}`);
1421
+ console.log(`[personas] Healthcheck: ${runtime.healthcheckUrl}`);
1422
+ }
1325
1423
  const payload = await client.registerRuntime({
1326
1424
  slug,
1327
1425
  endpoint: runtime.endpoint,
1328
- healthcheckUrl: runtime.healthcheckUrl,
1329
- localPort: runtime.localPort,
1330
- transport: runtime.transport,
1331
- startedAt: parseIsoTimestamp(flags['started-at'], 'started-at'),
1332
- });
1333
- printStructuredResult(payload, isTruthyFlag(flags.json));
1334
- }
1335
-
1426
+ healthcheckUrl: runtime.healthcheckUrl,
1427
+ localPort: runtime.localPort,
1428
+ transport: runtime.transport,
1429
+ startedAt: parseIsoTimestamp(flags['started-at'], 'started-at'),
1430
+ });
1431
+ printStructuredResult(payload, jsonOutput);
1432
+ }
1433
+
1336
1434
  async function handlePersonaHeartbeatCommand(slug, flags = {}) {
1337
1435
  const client = createCliPersonaApiClient(flags);
1436
+ const jsonOutput = isTruthyFlag(flags.json);
1338
1437
  const runtime = resolvePersonaRuntimeInput(
1339
1438
  flags,
1340
1439
  {},
@@ -1343,16 +1442,23 @@ async function handlePersonaHeartbeatCommand(slug, flags = {}) {
1343
1442
  workspaceRoot: resolvePersonaWorkspaceRoot(flags),
1344
1443
  },
1345
1444
  );
1445
+ if (!jsonOutput) {
1446
+ console.log(`[personas] Sending heartbeat for ${slug}.`);
1447
+ console.log(`[personas] Backend: ${resolvePersonaBackendUrl(flags)}`);
1448
+ console.log(`[personas] Device: ${resolvePersonaDeviceId(flags)}`);
1449
+ console.log(`[personas] Endpoint: ${runtime.endpoint}`);
1450
+ console.log(`[personas] Healthcheck: ${runtime.healthcheckUrl}`);
1451
+ }
1346
1452
  const payload = await client.heartbeatRuntime({
1347
1453
  slug,
1348
1454
  endpoint: runtime.endpoint,
1349
- healthcheckUrl: runtime.healthcheckUrl,
1350
- localPort: runtime.localPort,
1351
- transport: runtime.transport,
1352
- observedAt: parseIsoTimestamp(flags['observed-at'], 'observed-at'),
1353
- });
1354
- printStructuredResult(payload, isTruthyFlag(flags.json));
1355
- }
1455
+ healthcheckUrl: runtime.healthcheckUrl,
1456
+ localPort: runtime.localPort,
1457
+ transport: runtime.transport,
1458
+ observedAt: parseIsoTimestamp(flags['observed-at'], 'observed-at'),
1459
+ });
1460
+ printStructuredResult(payload, jsonOutput);
1461
+ }
1356
1462
 
1357
1463
  async function handlePersonaRuntimeFailCommand(slug, flags = {}) {
1358
1464
  const code = String(flags.code || '').trim();
@@ -1566,13 +1672,16 @@ async function handlePersonaDeleteCommand(slug, flags = {}) {
1566
1672
  printStructuredResult(result, isTruthyFlag(flags.json));
1567
1673
  }
1568
1674
 
1569
- async function restartManagedPersonaRefreshTargets(flags = {}) {
1675
+ async function restartManagedPersonaRefreshTargets(flags = {}, options = {}) {
1570
1676
  const workspaceRoot = resolvePersonaWorkspaceRoot(flags);
1571
- const targets = collectManagedPersonaRefreshTargets({
1677
+ const localTargets = collectManagedPersonaRefreshTargets({
1572
1678
  workspaceRoot,
1573
1679
  includeStopped: isTruthyFlag(flags['include-stopped']),
1574
1680
  });
1681
+ const targetsBySlug = new Map(localTargets.map((target) => [target.slug, target]));
1682
+ const targets = [...localTargets];
1575
1683
  const results = [];
1684
+ const logger = options.logger || null;
1576
1685
 
1577
1686
  let client = null;
1578
1687
  let registrationError = '';
@@ -1580,10 +1689,35 @@ async function restartManagedPersonaRefreshTargets(flags = {}) {
1580
1689
  client = createCliPersonaApiClient(flags);
1581
1690
  } catch (error) {
1582
1691
  registrationError = error instanceof Error ? error.message : String(error);
1692
+ logger?.(`Persona backend registration unavailable: ${registrationError}`);
1693
+ }
1694
+
1695
+ if (client) {
1696
+ try {
1697
+ const backendTargets = await discoverBackendLinkedPersonaRefreshTargets({
1698
+ client,
1699
+ workspaceRoot,
1700
+ existingTargets: targets,
1701
+ logger,
1702
+ });
1703
+ for (const target of backendTargets) {
1704
+ targetsBySlug.set(target.slug, target);
1705
+ targets.push(target);
1706
+ }
1707
+ } catch (error) {
1708
+ logger?.(`Backend persona discovery failed: ${error instanceof Error ? error.message : String(error)}`);
1709
+ }
1583
1710
  }
1584
1711
 
1712
+ logger?.(
1713
+ `Discovered ${targets.length} managed persona runtime${targets.length == 1 ? '' : 's'} to refresh.`,
1714
+ );
1715
+
1585
1716
  for (const target of targets) {
1586
1717
  const state = target.state && typeof target.state === 'object' ? target.state : {};
1718
+ logger?.(
1719
+ `Refreshing persona ${target.slug} (previous port: ${String(state.localPort || 'unknown')}).`,
1720
+ );
1587
1721
  const launchResult = await launchManagedPersonaRuntime({
1588
1722
  slug: target.slug,
1589
1723
  name: String(state.name || target.slug).trim() || target.slug,
@@ -1600,6 +1734,7 @@ async function restartManagedPersonaRefreshTargets(flags = {}) {
1600
1734
  let registration = null;
1601
1735
  if (client) {
1602
1736
  try {
1737
+ logger?.(`Re-registering runtime for ${launchResult.slug} at ${launchResult.runtime.endpoint}.`);
1603
1738
  registration = await client.registerRuntime({
1604
1739
  slug: launchResult.slug,
1605
1740
  endpoint: launchResult.runtime.endpoint,
@@ -1613,9 +1748,14 @@ async function restartManagedPersonaRefreshTargets(flags = {}) {
1613
1748
  ok: false,
1614
1749
  error: error instanceof Error ? error.message : String(error),
1615
1750
  };
1751
+ logger?.(`Runtime registration failed for ${launchResult.slug}: ${registration.error}`);
1616
1752
  }
1617
1753
  }
1618
1754
 
1755
+ logger?.(
1756
+ `Persona ${launchResult.slug} refreshed on port ${launchResult.runtime.localPort} (${launchResult.runtime.endpoint}).`,
1757
+ );
1758
+
1619
1759
  results.push({
1620
1760
  slug: launchResult.slug,
1621
1761
  workspacePath: launchResult.workspacePath,
@@ -1634,10 +1774,12 @@ async function restartManagedPersonaRefreshTargets(flags = {}) {
1634
1774
  };
1635
1775
  }
1636
1776
 
1637
- async function refreshBridgeForUpdate(flags = {}) {
1777
+ async function refreshBridgeForUpdate(flags = {}, options = {}) {
1778
+ const logger = options.logger || null;
1638
1779
  if (process.platform === 'darwin') {
1639
1780
  const launchdStatus = readBridgeLaunchdStatus();
1640
1781
  if (launchdStatus.installed) {
1782
+ logger?.(`Restarting launchd-managed bridge ${launchdStatus.target}.`);
1641
1783
  await stopBridgeLaunchdService();
1642
1784
  startBridgeLaunchdService();
1643
1785
  incrementBridgeMetric('bridge_restart_count');
@@ -1651,12 +1793,14 @@ async function refreshBridgeForUpdate(flags = {}) {
1651
1793
 
1652
1794
  const running = findRunningBridgeProcess();
1653
1795
  if (!running) {
1796
+ logger?.('No bridge process is currently running; skipping bridge restart.');
1654
1797
  return {
1655
1798
  restarted: false,
1656
1799
  mode: 'process',
1657
1800
  };
1658
1801
  }
1659
1802
 
1803
+ logger?.(`Restarting bridge process ${running.pid}.`);
1660
1804
  const stopResult = await stopBridgeProcesses();
1661
1805
  if (Array.isArray(stopResult.stillAlive) && stopResult.stillAlive.length > 0) {
1662
1806
  throw new Error(`Failed to stop bridge processes: ${stopResult.stillAlive.join(', ')}`);
@@ -1673,19 +1817,31 @@ async function refreshBridgeForUpdate(flags = {}) {
1673
1817
  }
1674
1818
 
1675
1819
  async function handleOpenclawRefreshCommand(flags = {}) {
1820
+ const jsonOutput = isTruthyFlag(flags.json);
1821
+ const logProgress = jsonOutput ? null : (message) => console.log(`[refresh] ${message}`);
1676
1822
  const currentVersion = currentPackageVersion();
1677
1823
  let latestVersion = '';
1678
1824
  if (!isTruthyFlag(flags['skip-version-check'])) {
1825
+ logProgress?.(`Checking npm for the latest oomi-ai version (installed: ${currentVersion || 'unknown'}).`);
1679
1826
  latestVersion = await fetchLatestPublishedVersion('oomi-ai');
1680
1827
  if (latestVersion && compareVersions(currentVersion, latestVersion) < 0) {
1681
1828
  throw new Error(
1682
1829
  `Installed oomi-ai ${currentVersion} is behind npm ${latestVersion}. Update first, then rerun: oomi openclaw refresh`
1683
1830
  );
1684
1831
  }
1832
+ if (latestVersion) {
1833
+ logProgress?.(`Latest published version is ${latestVersion}.`);
1834
+ }
1685
1835
  }
1686
1836
 
1687
- const personaRefresh = await restartManagedPersonaRefreshTargets(flags);
1688
- const bridgeRefresh = await refreshBridgeForUpdate(flags);
1837
+ logProgress?.('Refreshing managed persona runtimes.');
1838
+ const personaRefresh = await restartManagedPersonaRefreshTargets(flags, {
1839
+ logger: logProgress,
1840
+ });
1841
+ logProgress?.('Refreshing bridge process.');
1842
+ const bridgeRefresh = await refreshBridgeForUpdate(flags, {
1843
+ logger: logProgress,
1844
+ });
1689
1845
  const payload = {
1690
1846
  ok: true,
1691
1847
  currentVersion,
@@ -1699,7 +1855,7 @@ async function handleOpenclawRefreshCommand(flags = {}) {
1699
1855
  bridge: bridgeRefresh,
1700
1856
  };
1701
1857
 
1702
- if (isTruthyFlag(flags.json)) {
1858
+ if (jsonOutput) {
1703
1859
  console.log(JSON.stringify(payload, null, 2));
1704
1860
  return;
1705
1861
  }
@@ -5827,5 +5983,8 @@ export {
5827
5983
  isBridgeWorkerCommand,
5828
5984
  parsePositiveInteger,
5829
5985
  collectManagedPersonaRefreshTargets,
5986
+ discoverBackendLinkedPersonaRefreshTargets,
5987
+ normalizeBackendPersonaRefreshRecord,
5830
5988
  resolvePersonaRuntimeInput,
5989
+ resolveExistingWorkspacePathForSlug,
5831
5990
  };
@@ -100,10 +100,19 @@ export function createPersonaApiClient({
100
100
  };
101
101
  }
102
102
 
103
- return {
104
- getPersona({
105
- slug,
106
- }) {
103
+ return {
104
+ listPersonas() {
105
+ return getJson({
106
+ fetchImpl,
107
+ backendUrl: resolvedBackendUrl,
108
+ deviceToken: resolvedDeviceToken,
109
+ path: '/v1/personas',
110
+ });
111
+ },
112
+
113
+ getPersona({
114
+ slug,
115
+ }) {
107
116
  const safeSlug = trimString(slug);
108
117
  if (!safeSlug) {
109
118
  throw new Error('Persona slug is required.');
@@ -2,7 +2,7 @@
2
2
  "id": "oomi-ai",
3
3
  "name": "Oomi Channel Plugin",
4
4
  "description": "Managed Oomi channel integration for OpenClaw.",
5
- "version": "0.2.45",
5
+ "version": "0.2.48",
6
6
  "author": "Oomi",
7
7
  "license": "MIT",
8
8
  "openclawVersion": ">=0.5.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oomi-ai",
3
- "version": "0.2.45",
3
+ "version": "0.2.48",
4
4
  "description": "Oomi OpenClaw channel plugin and bridge tooling",
5
5
  "bin": {
6
6
  "oomi": "bin/oomi-ai.js"