oomi-ai 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -181,8 +181,10 @@ Rules for agents:
181
181
 
182
182
  - Answer from `healthContext.summary` and `healthContext.derived`.
183
183
  - Respect `healthContext.agentGuidance.canAnswerFromContext`.
184
- - If context is `missing`, `needs_sync`, `permission_denied`, `disabled_by_user`, or `unavailable`, tell the user what repair action is needed instead of guessing.
185
- - If context is `stale`, mention freshness when timing matters.
184
+ - If `healthContext.agentGuidance.recommendedCommand` is present, run it before answering current/latest health questions.
185
+ - If context is `stale` or `needs_sync`, run `oomi context health sync --wait --json`; answer only after fresh context is uploaded, or tell the user the phone sync is still pending if it times out.
186
+ - If context is `missing`, `permission_denied`, `disabled_by_user`, or `unavailable`, tell the user what repair action is needed instead of guessing.
187
+ - Never present stale HealthKit values as current values.
186
188
  - Do not ask the phone for HealthKit directly; the Oomi mobile app syncs HealthKit to the backend.
187
189
 
188
190
  ## Voice Contract
@@ -213,7 +213,7 @@ Rules:
213
213
  - `metadata.spoken.language` should use the backend-supported language value for the active provider; use `English` only when no more specific locale is supplied
214
214
  - `metadata.spoken.segments` can carry bounded per-segment prosody for pace, pitch, volume, and pause timing
215
215
  - `metadata.spoken.instructions` should be natural-language guidance, not raw bracket tags
216
- - visible chat text is never rewritten by the package
216
+ - visible chat text should be clean before sending; the package may strip private runtime wrappers and avatar command tags as a safety guardrail
217
217
 
218
218
  ## Avatar Commands
219
219
 
@@ -276,7 +276,9 @@ Rules:
276
276
 
277
277
  - Answer only from `healthContext.summary` and `healthContext.derived`.
278
278
  - Respect `healthContext.agentGuidance.canAnswerFromContext`.
279
- - If `canAnswerFromContext` is false, tell the user the relevant `healthContext.repair.label` or `healthContext.repair.reason`.
280
- - If `healthContext.status` is `stale`, mention that the latest HealthKit sync may be out of date when timing matters.
279
+ - If `healthContext.agentGuidance.recommendedCommand` is present, run it before answering current/latest health questions.
280
+ - If `healthContext.status` is `stale` or `needs_sync`, run `oomi context health sync --wait --json`; answer only after fresh context is uploaded, or tell the user the phone sync is still pending if it times out.
281
+ - If `canAnswerFromContext` is false and no sync command is recommended, tell the user the relevant `healthContext.repair.label` or `healthContext.repair.reason`.
282
+ - Never present stale HealthKit values as current values.
281
283
  - Do not infer unavailable health fields.
282
284
  - Do not request HealthKit directly from the phone; the Oomi mobile app owns permission prompts and syncs approved data to the backend.
package/bin/oomi-ai.js CHANGED
@@ -79,6 +79,12 @@ function parsePositiveInteger(value, fallback) {
79
79
  return Math.floor(num);
80
80
  }
81
81
 
82
+ function delay(ms) {
83
+ return new Promise((resolve) => {
84
+ setTimeout(resolve, ms);
85
+ });
86
+ }
87
+
82
88
  function readJsonSafe(filePath) {
83
89
  if (!fs.existsSync(filePath)) return null;
84
90
  try {
@@ -229,6 +235,9 @@ Commands:
229
235
 
230
236
  context health
231
237
  Show the latest account-linked health context available to this paired OpenClaw device.
238
+ context health sync [--wait]
239
+ Request the linked Oomi mobile app to sync fresh HealthKit context.
240
+ Use --wait to poll until fresh context is uploaded or the timeout expires.
232
241
 
233
242
  Common flags:
234
243
  --agents-file PATH Override AGENTS.md path
@@ -575,6 +584,48 @@ async function handleContextHealthCommand(flags = {}) {
575
584
  printStructuredResult(payload, isTruthyFlag(flags.json));
576
585
  }
577
586
 
587
+ async function handleContextHealthSyncCommand(flags = {}) {
588
+ const client = createCliPersonaApiClient(flags);
589
+ const payload = await client.requestHealthContextSync({
590
+ reason: flags.reason || 'agent_requested',
591
+ });
592
+
593
+ if (isTruthyFlag(flags.wait)) {
594
+ const previousCapturedAt = payload?.healthContext?.source?.capturedAt || null;
595
+ const timeoutMs = parsePositiveInteger(flags['timeout-ms'] || flags.timeoutMs, 30_000);
596
+ const pollMs = parsePositiveInteger(flags['poll-ms'] || flags.pollMs, 3_000);
597
+ const startedAt = Date.now();
598
+ let latestPayload = payload;
599
+
600
+ while (Date.now() - startedAt < timeoutMs) {
601
+ await delay(pollMs);
602
+ latestPayload = await client.getHealthContext();
603
+ const healthContext = latestPayload?.healthContext;
604
+ const capturedAt = healthContext?.source?.capturedAt || null;
605
+ if (healthContext?.status === 'ready' && (!previousCapturedAt || capturedAt !== previousCapturedAt)) {
606
+ printStructuredResult({
607
+ ...payload,
608
+ refreshed: true,
609
+ waitedMs: Date.now() - startedAt,
610
+ healthContext,
611
+ }, isTruthyFlag(flags.json));
612
+ return;
613
+ }
614
+ }
615
+
616
+ printStructuredResult({
617
+ ...payload,
618
+ refreshed: false,
619
+ waitedMs: Date.now() - startedAt,
620
+ waitTimedOut: true,
621
+ healthContext: latestPayload?.healthContext || payload.healthContext,
622
+ }, isTruthyFlag(flags.json));
623
+ return;
624
+ }
625
+
626
+ printStructuredResult(payload, isTruthyFlag(flags.json));
627
+ }
628
+
578
629
  async function refreshBridgeForUpdate(flags = {}, options = {}) {
579
630
  const logger = options.logger || null;
580
631
  if (process.platform === 'darwin') {
@@ -1372,6 +1423,8 @@ function sanitizeUserVisibleText(value) {
1372
1423
  text = text.replace(/<think>[\s\S]*$/i, ' ');
1373
1424
  }
1374
1425
  text = text.replace(/<\/think>/gi, ' ');
1426
+ text = text.replace(/<\/?final>/gi, ' ');
1427
+ text = text.replace(/\[(?:anim|face|gesture|look):[^\]\n]{1,80}\]/gi, ' ');
1375
1428
 
1376
1429
  text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
1377
1430
  text = text.replace(
@@ -1391,6 +1444,7 @@ function shouldSuppressOomiVisibleText(value) {
1391
1444
  if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
1392
1445
  if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
1393
1446
  if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
1447
+ if (/^NO_REPLY$/i.test(text)) return true;
1394
1448
 
1395
1449
  return !sanitizeUserVisibleText(text);
1396
1450
  }
@@ -4319,6 +4373,10 @@ async function main() {
4319
4373
  }
4320
4374
 
4321
4375
  if (command === 'context' && subcommand === 'health') {
4376
+ if (args.positionals[0] === 'sync') {
4377
+ await handleContextHealthSyncCommand(args.flags);
4378
+ return;
4379
+ }
4322
4380
  await handleContextHealthCommand(args.flags);
4323
4381
  return;
4324
4382
  }
@@ -148,5 +148,19 @@ export function createPersonaApiClient({
148
148
  path: '/v1/context/health',
149
149
  });
150
150
  },
151
+
152
+ requestHealthContextSync({
153
+ reason = 'agent_requested',
154
+ } = {}) {
155
+ return postJson({
156
+ fetchImpl,
157
+ backendUrl: resolvedBackendUrl,
158
+ deviceToken: resolvedDeviceToken,
159
+ path: '/v1/context/health/sync',
160
+ body: {
161
+ reason: trimString(reason) || 'agent_requested',
162
+ },
163
+ });
164
+ },
151
165
  };
152
166
  }
@@ -124,6 +124,8 @@ function sanitizeUserVisibleText(value) {
124
124
  text = text.replace(/<think>[\s\S]*$/i, ' ');
125
125
  }
126
126
  text = text.replace(/<\/think>/gi, ' ');
127
+ text = text.replace(/<\/?final>/gi, ' ');
128
+ text = text.replace(/\[(?:anim|face|gesture|look):[^\]\n]{1,80}\]/gi, ' ');
127
129
 
128
130
  text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
129
131
  text = text.replace(
@@ -143,6 +145,7 @@ function shouldSuppressOomiVisibleText(value) {
143
145
  if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
144
146
  if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
145
147
  if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
148
+ if (/^NO_REPLY$/i.test(text)) return true;
146
149
 
147
150
  return !sanitizeUserVisibleText(text);
148
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oomi-ai",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Managed Oomi channel, bridge, voice, and persona app API tooling for OpenClaw",
5
5
  "bin": {
6
6
  "oomi": "bin/oomi-ai.js"
@@ -30,4 +30,12 @@ When answering data-backed health or fitness questions, agents should read appro
30
30
  oomi context health --json
31
31
  ```
32
32
 
33
+ If the user asks for current/latest health context and the context is stale or needs sync, run:
34
+
35
+ ```bash
36
+ oomi context health sync --wait --json
37
+ ```
38
+
39
+ Answer only after fresh context is uploaded. If the command times out, tell the user the phone sync is still pending.
40
+
33
41
  If a persona app is missing, ask the user to add it from the Oomi client first so platform-specific permissions and hydration can run.
@@ -126,7 +126,10 @@ oomi context health --json
126
126
  Rules:
127
127
  - answer only from `healthContext.summary` and `healthContext.derived`
128
128
  - respect `healthContext.agentGuidance.canAnswerFromContext`
129
- - if context is missing, stale, denied, disabled, or unavailable, follow `healthContext.repair`
129
+ - if `healthContext.agentGuidance.recommendedCommand` is present, run it before answering current/latest health questions
130
+ - if context is stale or needs sync, run `oomi context health sync --wait --json`; answer only after fresh context is uploaded, or tell the user the phone sync is still pending if it times out
131
+ - if context is missing, denied, disabled, or unavailable, follow `healthContext.repair`
132
+ - never present stale HealthKit values as current values
130
133
  - do not ask HealthKit directly; the Oomi mobile app syncs approved HealthKit data to the backend
131
134
 
132
135
  ## Hidden Speech Payload
@@ -78,7 +78,7 @@ Rules:
78
78
  - `metadata.spoken.style` is optional metadata for debugging or future mapping
79
79
  - if no hidden speech sidecar exists, Oomi falls back to speaking the visible assistant text
80
80
  - if you omit `metadata.spoken`, the plugin now synthesizes a bounded hidden fallback from visible assistant text
81
- - visible chat text is never rewritten by the plugin
81
+ - visible chat text should be clean before sending; the plugin may strip private runtime wrappers and avatar command tags as a safety guardrail
82
82
 
83
83
  ## Persona App API Tools
84
84
 
@@ -111,7 +111,9 @@ oomi context health --json
111
111
  Rules:
112
112
  - answer only from `healthContext.summary` and `healthContext.derived`
113
113
  - respect `healthContext.agentGuidance.canAnswerFromContext`
114
- - if `canAnswerFromContext` is false, tell the user the relevant `healthContext.repair.label` or `healthContext.repair.reason`
115
- - if `healthContext.status` is `stale`, mention that the latest HealthKit sync may be out of date when timing matters
114
+ - if `healthContext.agentGuidance.recommendedCommand` is present, run it before answering current/latest health questions
115
+ - if `healthContext.status` is `stale` or `needs_sync`, run `oomi context health sync --wait --json`; answer only after fresh context is uploaded, or tell the user the phone sync is still pending if it times out
116
+ - if `canAnswerFromContext` is false and no sync command is recommended, tell the user the relevant `healthContext.repair.label` or `healthContext.repair.reason`
117
+ - never present stale HealthKit values as current values
116
118
  - do not infer unavailable health fields
117
119
  - do not request HealthKit directly from the phone; the Oomi mobile app owns permission prompts and syncs approved data to the backend