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 +4 -2
- package/agent_instructions.md +5 -3
- package/bin/oomi-ai.js +58 -0
- package/lib/personaApiClient.js +14 -0
- package/openclaw.extension.js +3 -0
- package/package.json +1 -1
- package/persona-app/README.md +8 -0
- package/skills/oomi/SKILL.md +4 -1
- package/skills/oomi/agent_instructions.md +5 -3
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
|
|
185
|
-
- If context is `stale`,
|
|
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
|
package/agent_instructions.md
CHANGED
|
@@ -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
|
|
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 `
|
|
280
|
-
- If `healthContext.status` is `stale`,
|
|
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
|
}
|
package/lib/personaApiClient.js
CHANGED
|
@@ -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
|
}
|
package/openclaw.extension.js
CHANGED
|
@@ -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
package/persona-app/README.md
CHANGED
|
@@ -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.
|
package/skills/oomi/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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 `
|
|
115
|
-
- if `healthContext.status` is `stale`,
|
|
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
|