oomi-ai 0.3.0 → 0.3.2
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 +22 -1
- package/agent_instructions.md +20 -0
- package/bin/oomi-ai.js +145 -0
- package/lib/personaApiClient.js +9 -0
- package/openclaw.extension.js +50 -1
- package/package.json +1 -1
- package/persona-app/README.md +6 -0
- package/persona-app/registry/v1.json +25 -3
- package/skills/oomi/SKILL.md +17 -0
- package/skills/oomi/agent_instructions.md +18 -0
package/README.md
CHANGED
|
@@ -28,7 +28,12 @@ The current model is:
|
|
|
28
28
|
- Files: `persona-app/*`, `lib/personaApiClient.js`, `bin/oomi-ai.js`
|
|
29
29
|
- Purpose: inspect account-linked component-composed persona apps and apply approved backend actions.
|
|
30
30
|
|
|
31
|
-
4.
|
|
31
|
+
4. Permissioned context tools
|
|
32
|
+
|
|
33
|
+
- Files: `lib/personaApiClient.js`, `bin/oomi-ai.js`
|
|
34
|
+
- Purpose: read user-approved Oomi backend context, such as HealthKit summaries synced by the mobile app.
|
|
35
|
+
|
|
36
|
+
5. Agent/operator instructions
|
|
32
37
|
|
|
33
38
|
- Files: `agent_instructions.md`, `skills/oomi/*`
|
|
34
39
|
- Purpose: tell OpenClaw agents how to connect, repair, and use Oomi without local UI code generation.
|
|
@@ -164,6 +169,22 @@ oomi persona-apps apply-action fitness-today --action fitness.complete_goal --pa
|
|
|
164
169
|
oomi persona-apps apply-action fitness-today --action fitness.complete_workout --payload-json '{"goalId":"easy-run","minutes":20}' --json
|
|
165
170
|
```
|
|
166
171
|
|
|
172
|
+
## Context Tools
|
|
173
|
+
|
|
174
|
+
Use context tools when the user asks a data-backed question that should come from Oomi-approved mobile permissions, not model memory. For health and fitness questions, read the latest backend HealthKit context first:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
oomi context health --json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Rules for agents:
|
|
181
|
+
|
|
182
|
+
- Answer from `healthContext.summary` and `healthContext.derived`.
|
|
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.
|
|
186
|
+
- Do not ask the phone for HealthKit directly; the Oomi mobile app syncs HealthKit to the backend.
|
|
187
|
+
|
|
167
188
|
## Voice Contract
|
|
168
189
|
|
|
169
190
|
Managed voice uses the same Oomi plugin and bridge layer as managed chat.
|
package/agent_instructions.md
CHANGED
|
@@ -12,6 +12,7 @@ Get OpenClaw into a state where:
|
|
|
12
12
|
- Oomi device-backed voice can start cleanly
|
|
13
13
|
- the bridge is supervised or restartable
|
|
14
14
|
- component-composed persona apps can be inspected and updated through Oomi API tools
|
|
15
|
+
- approved user context, such as HealthKit data synced by the Oomi mobile app, can be read through Oomi backend context tools
|
|
15
16
|
- the user never has to paste raw pair identifiers or gateway secrets manually when managed connect is available
|
|
16
17
|
|
|
17
18
|
## Standard Connect Flow
|
|
@@ -260,3 +261,22 @@ Strict rules:
|
|
|
260
261
|
- Do not install app framework dependencies for persona UI work.
|
|
261
262
|
- Do not present local URLs as persona app URLs.
|
|
262
263
|
- If a persona app needs to be created, tell the user to add it from the Oomi client first so permissions and hydration run correctly.
|
|
264
|
+
|
|
265
|
+
## Permissioned Context Tools
|
|
266
|
+
|
|
267
|
+
Use Oomi context tools when the user asks about personal data that should come from mobile permissions instead of model memory.
|
|
268
|
+
|
|
269
|
+
For health, fitness, sleep, workout, cardio, or movement questions:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
oomi context health --json
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Rules:
|
|
276
|
+
|
|
277
|
+
- Answer only from `healthContext.summary` and `healthContext.derived`.
|
|
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.
|
|
281
|
+
- Do not infer unavailable health fields.
|
|
282
|
+
- 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
|
@@ -227,6 +227,9 @@ Commands:
|
|
|
227
227
|
persona-apps apply-action <slug>
|
|
228
228
|
Apply an approved persona app action through the Oomi backend.
|
|
229
229
|
|
|
230
|
+
context health
|
|
231
|
+
Show the latest account-linked health context available to this paired OpenClaw device.
|
|
232
|
+
|
|
230
233
|
Common flags:
|
|
231
234
|
--agents-file PATH Override AGENTS.md path
|
|
232
235
|
--workspace PATH Override OpenClaw workspace root
|
|
@@ -566,6 +569,12 @@ async function handlePersonaAppApplyActionCommand(slug, flags = {}) {
|
|
|
566
569
|
printStructuredResult(result, isTruthyFlag(flags.json));
|
|
567
570
|
}
|
|
568
571
|
|
|
572
|
+
async function handleContextHealthCommand(flags = {}) {
|
|
573
|
+
const client = createCliPersonaApiClient(flags);
|
|
574
|
+
const payload = await client.getHealthContext();
|
|
575
|
+
printStructuredResult(payload, isTruthyFlag(flags.json));
|
|
576
|
+
}
|
|
577
|
+
|
|
569
578
|
async function refreshBridgeForUpdate(flags = {}, options = {}) {
|
|
570
579
|
const logger = options.logger || null;
|
|
571
580
|
if (process.platform === 'darwin') {
|
|
@@ -1346,6 +1355,127 @@ function extractTextFromGatewayMessage(message) {
|
|
|
1346
1355
|
.join(' ');
|
|
1347
1356
|
}
|
|
1348
1357
|
|
|
1358
|
+
function normalizeWhitespace(value) {
|
|
1359
|
+
return String(value || '')
|
|
1360
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
1361
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1362
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
1363
|
+
.trim();
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function sanitizeUserVisibleText(value) {
|
|
1367
|
+
let text = String(value || '');
|
|
1368
|
+
if (!text.trim()) return '';
|
|
1369
|
+
|
|
1370
|
+
text = text.replace(/<think>[\s\S]*?<\/think>/gi, ' ');
|
|
1371
|
+
if (/<think>/i.test(text)) {
|
|
1372
|
+
text = text.replace(/<think>[\s\S]*$/i, ' ');
|
|
1373
|
+
}
|
|
1374
|
+
text = text.replace(/<\/think>/gi, ' ');
|
|
1375
|
+
|
|
1376
|
+
text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
|
|
1377
|
+
text = text.replace(
|
|
1378
|
+
/(?:^|\n)An async command you ran earlier has completed\.[\s\S]*?(?:\nCurrent time:[^\n]*(?:\n|$)|$)/gi,
|
|
1379
|
+
'\n'
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
return normalizeWhitespace(text);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function shouldSuppressOomiVisibleText(value) {
|
|
1386
|
+
const text = String(value || '').trim();
|
|
1387
|
+
if (!text) return true;
|
|
1388
|
+
|
|
1389
|
+
if (/System \(untrusted\):/i.test(text)) return true;
|
|
1390
|
+
if (/An async command you ran earlier has completed\./i.test(text)) return true;
|
|
1391
|
+
if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
|
|
1392
|
+
if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
|
|
1393
|
+
if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
|
|
1394
|
+
|
|
1395
|
+
return !sanitizeUserVisibleText(text);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function sanitizeGatewayMessageForOomi(message) {
|
|
1399
|
+
if (!message || typeof message !== 'object') return null;
|
|
1400
|
+
|
|
1401
|
+
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
1402
|
+
if (role && !['assistant', 'user'].includes(role)) {
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
const rawText = extractTextFromGatewayMessage(message);
|
|
1407
|
+
if (shouldSuppressOomiVisibleText(rawText)) {
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const content = sanitizeUserVisibleText(rawText);
|
|
1412
|
+
if (!content) return null;
|
|
1413
|
+
|
|
1414
|
+
return {
|
|
1415
|
+
...message,
|
|
1416
|
+
role: role || 'assistant',
|
|
1417
|
+
content,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function sanitizeGatewayFrameForOomiClient(frameText) {
|
|
1422
|
+
const frame = parseJsonPayload(frameText);
|
|
1423
|
+
if (!frame || typeof frame !== 'object') {
|
|
1424
|
+
return { frameText, changed: false, suppressed: false };
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (frame.type === 'res' && frame.payload && typeof frame.payload === 'object' && Array.isArray(frame.payload.messages)) {
|
|
1428
|
+
const nextMessages = frame.payload.messages
|
|
1429
|
+
.map((message) => sanitizeGatewayMessageForOomi(message))
|
|
1430
|
+
.filter(Boolean);
|
|
1431
|
+
const nextFrame = {
|
|
1432
|
+
...frame,
|
|
1433
|
+
payload: {
|
|
1434
|
+
...frame.payload,
|
|
1435
|
+
messages: nextMessages,
|
|
1436
|
+
},
|
|
1437
|
+
};
|
|
1438
|
+
const nextFrameText = JSON.stringify(nextFrame);
|
|
1439
|
+
return {
|
|
1440
|
+
frameText: nextFrameText,
|
|
1441
|
+
changed: nextFrameText !== frameText,
|
|
1442
|
+
suppressed: nextMessages.length < frame.payload.messages.length,
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (frame.type === 'event' && frame.event === 'chat') {
|
|
1447
|
+
const payload = frame.payload && typeof frame.payload === 'object' ? frame.payload : null;
|
|
1448
|
+
const message = payload?.message && typeof payload.message === 'object' ? payload.message : null;
|
|
1449
|
+
if (!payload || !message) {
|
|
1450
|
+
return { frameText, changed: false, suppressed: false };
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const sanitizedMessage = sanitizeGatewayMessageForOomi(message);
|
|
1454
|
+
const nextFrame = {
|
|
1455
|
+
...frame,
|
|
1456
|
+
payload: {
|
|
1457
|
+
...payload,
|
|
1458
|
+
message: sanitizedMessage || {
|
|
1459
|
+
...message,
|
|
1460
|
+
content: '',
|
|
1461
|
+
metadata: {
|
|
1462
|
+
...(message.metadata && typeof message.metadata === 'object' && !Array.isArray(message.metadata) ? message.metadata : {}),
|
|
1463
|
+
oomiSuppressed: true,
|
|
1464
|
+
},
|
|
1465
|
+
},
|
|
1466
|
+
},
|
|
1467
|
+
};
|
|
1468
|
+
const nextFrameText = JSON.stringify(nextFrame);
|
|
1469
|
+
return {
|
|
1470
|
+
frameText: nextFrameText,
|
|
1471
|
+
changed: nextFrameText !== frameText,
|
|
1472
|
+
suppressed: !sanitizedMessage,
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return { frameText, changed: false, suppressed: false };
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1349
1479
|
function summarizeVoiceFrameContract(frameText) {
|
|
1350
1480
|
const frame = parseJsonPayload(frameText);
|
|
1351
1481
|
if (!frame || typeof frame !== 'object') {
|
|
@@ -2988,6 +3118,13 @@ async function startOpenclawBridge(flags) {
|
|
|
2988
3118
|
|
|
2989
3119
|
gatewaySocket.on('message', runBridgeCallbackSafely((gatewayRaw) => {
|
|
2990
3120
|
let frame = typeof gatewayRaw === 'string' ? gatewayRaw : gatewayRaw.toString();
|
|
3121
|
+
const visibleSanitized = sanitizeGatewayFrameForOomiClient(frame);
|
|
3122
|
+
if (visibleSanitized.changed) {
|
|
3123
|
+
frame = visibleSanitized.frameText;
|
|
3124
|
+
bridgeDebugLog(
|
|
3125
|
+
`[bridge] oomi.visible_sanitized ${sessionId} suppressed=${visibleSanitized.suppressed ? 'yes' : 'no'}`
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
2991
3128
|
const spokenNormalized = normalizeAssistantGatewayFrame(sessionId, frame);
|
|
2992
3129
|
if (spokenNormalized.changed) {
|
|
2993
3130
|
frame = spokenNormalized.frameText;
|
|
@@ -4181,6 +4318,11 @@ async function main() {
|
|
|
4181
4318
|
return;
|
|
4182
4319
|
}
|
|
4183
4320
|
|
|
4321
|
+
if (command === 'context' && subcommand === 'health') {
|
|
4322
|
+
await handleContextHealthCommand(args.flags);
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4184
4326
|
console.error(`Unknown command: ${command} ${subcommand || ''}`.trim());
|
|
4185
4327
|
usage();
|
|
4186
4328
|
process.exit(1);
|
|
@@ -4199,6 +4341,9 @@ if (__isDirectExecution) {
|
|
|
4199
4341
|
|
|
4200
4342
|
export {
|
|
4201
4343
|
prepareGatewayFrameForLocalGateway,
|
|
4344
|
+
sanitizeGatewayFrameForOomiClient,
|
|
4345
|
+
sanitizeUserVisibleText,
|
|
4346
|
+
shouldSuppressOomiVisibleText,
|
|
4202
4347
|
ensureAssistantSpokenMetadata,
|
|
4203
4348
|
normalizeAssistantGatewayFrame,
|
|
4204
4349
|
runAssistantFinalDebugCheck,
|
package/lib/personaApiClient.js
CHANGED
|
@@ -139,5 +139,14 @@ export function createPersonaApiClient({
|
|
|
139
139
|
},
|
|
140
140
|
});
|
|
141
141
|
},
|
|
142
|
+
|
|
143
|
+
getHealthContext() {
|
|
144
|
+
return getJson({
|
|
145
|
+
fetchImpl,
|
|
146
|
+
backendUrl: resolvedBackendUrl,
|
|
147
|
+
deviceToken: resolvedDeviceToken,
|
|
148
|
+
path: '/v1/context/health',
|
|
149
|
+
});
|
|
150
|
+
},
|
|
142
151
|
};
|
|
143
152
|
}
|
package/openclaw.extension.js
CHANGED
|
@@ -107,6 +107,46 @@ function extractText(payload) {
|
|
|
107
107
|
return '';
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
function normalizeWhitespace(value) {
|
|
111
|
+
return String(value || '')
|
|
112
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
113
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
114
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
115
|
+
.trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sanitizeUserVisibleText(value) {
|
|
119
|
+
let text = String(value || '');
|
|
120
|
+
if (!text.trim()) return '';
|
|
121
|
+
|
|
122
|
+
text = text.replace(/<think>[\s\S]*?<\/think>/gi, ' ');
|
|
123
|
+
if (/<think>/i.test(text)) {
|
|
124
|
+
text = text.replace(/<think>[\s\S]*$/i, ' ');
|
|
125
|
+
}
|
|
126
|
+
text = text.replace(/<\/think>/gi, ' ');
|
|
127
|
+
|
|
128
|
+
text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
|
|
129
|
+
text = text.replace(
|
|
130
|
+
/(?:^|\n)An async command you ran earlier has completed\.[\s\S]*?(?:\nCurrent time:[^\n]*(?:\n|$)|$)/gi,
|
|
131
|
+
'\n'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return normalizeWhitespace(text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function shouldSuppressOomiVisibleText(value) {
|
|
138
|
+
const text = String(value || '').trim();
|
|
139
|
+
if (!text) return true;
|
|
140
|
+
|
|
141
|
+
if (/System \(untrusted\):/i.test(text)) return true;
|
|
142
|
+
if (/An async command you ran earlier has completed\./i.test(text)) return true;
|
|
143
|
+
if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
|
|
144
|
+
if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
|
|
145
|
+
if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
|
|
146
|
+
|
|
147
|
+
return !sanitizeUserVisibleText(text);
|
|
148
|
+
}
|
|
149
|
+
|
|
110
150
|
function extractConversationKey(payload) {
|
|
111
151
|
const candidates = [
|
|
112
152
|
payload?.conversationKey,
|
|
@@ -290,7 +330,16 @@ const oomiChannelPlugin = {
|
|
|
290
330
|
};
|
|
291
331
|
}
|
|
292
332
|
|
|
293
|
-
const
|
|
333
|
+
const rawContent = extractText(payload);
|
|
334
|
+
if (shouldSuppressOomiVisibleText(rawContent)) {
|
|
335
|
+
return {
|
|
336
|
+
ok: true,
|
|
337
|
+
suppressed: true,
|
|
338
|
+
reason: 'internal_content',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const content = sanitizeUserVisibleText(rawContent);
|
|
294
343
|
if (!content) {
|
|
295
344
|
return {
|
|
296
345
|
ok: false,
|
package/package.json
CHANGED
package/persona-app/README.md
CHANGED
|
@@ -24,4 +24,10 @@ oomi persona-apps show fitness-today --json
|
|
|
24
24
|
oomi persona-apps apply-action fitness-today --action fitness.complete_workout --payload-json '{"goalId":"easy-run","minutes":20}' --json
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
When answering data-backed health or fitness questions, agents should read approved backend context first:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
oomi context health --json
|
|
31
|
+
```
|
|
32
|
+
|
|
27
33
|
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.
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "persona-app.v1",
|
|
3
|
-
"registryVersion": "2026-04-
|
|
3
|
+
"registryVersion": "2026-04-24.1",
|
|
4
|
+
"contextTools": [
|
|
5
|
+
{
|
|
6
|
+
"id": "health.context.read",
|
|
7
|
+
"command": "oomi context health --json",
|
|
8
|
+
"endpoint": "/v1/context/health",
|
|
9
|
+
"description": "Reads the latest account-linked health context synced by Oomi mobile clients.",
|
|
10
|
+
"permissions": ["healthkit.read", "health_connect.read"],
|
|
11
|
+
"readOnly": true,
|
|
12
|
+
"targets": ["ios", "android", "web"]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
4
15
|
"sharedComponents": [
|
|
5
16
|
{
|
|
6
17
|
"type": "shared.personaHero",
|
|
@@ -27,6 +38,16 @@
|
|
|
27
38
|
"type": "fitness.goalChecklist",
|
|
28
39
|
"description": "Checklist of suggested goals for today.",
|
|
29
40
|
"targets": ["ios", "android", "web"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "fitness.metricGrid",
|
|
44
|
+
"description": "Grid of approved HealthKit or Health Connect activity, sleep, cardio, and body metrics.",
|
|
45
|
+
"targets": ["ios", "android", "web"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"type": "fitness.workoutList",
|
|
49
|
+
"description": "Recent workouts synced from the user's approved mobile health source.",
|
|
50
|
+
"targets": ["ios", "android", "web"]
|
|
30
51
|
}
|
|
31
52
|
],
|
|
32
53
|
"actions": [
|
|
@@ -55,9 +76,10 @@
|
|
|
55
76
|
],
|
|
56
77
|
"permissions": [
|
|
57
78
|
"fitness.state.read",
|
|
58
|
-
"fitness.state.write"
|
|
79
|
+
"fitness.state.write",
|
|
80
|
+
"healthkit.read",
|
|
81
|
+
"health_connect.read"
|
|
59
82
|
]
|
|
60
83
|
}
|
|
61
84
|
]
|
|
62
85
|
}
|
|
63
|
-
|
package/skills/oomi/SKILL.md
CHANGED
|
@@ -10,6 +10,7 @@ Use this skill when you need to:
|
|
|
10
10
|
- repair the Oomi plugin or bridge on a machine
|
|
11
11
|
- inspect managed chat or voice health
|
|
12
12
|
- inspect or update component-composed Oomi persona app state
|
|
13
|
+
- read approved Oomi backend context such as HealthKit summaries synced by the mobile app
|
|
13
14
|
- control the Oomi avatar with inline tags
|
|
14
15
|
|
|
15
16
|
## Primary Operator Workflow
|
|
@@ -112,6 +113,22 @@ Rules:
|
|
|
112
113
|
- do not create local persona UI projects from the OpenClaw machine
|
|
113
114
|
- do not execute persona UI jobs or start local persona app runtimes
|
|
114
115
|
|
|
116
|
+
## Permissioned Context Tools
|
|
117
|
+
|
|
118
|
+
Use context commands before answering personal data questions that should come from Oomi-approved permissions.
|
|
119
|
+
|
|
120
|
+
For health, fitness, sleep, workout, cardio, or movement questions:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
oomi context health --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Rules:
|
|
127
|
+
- answer only from `healthContext.summary` and `healthContext.derived`
|
|
128
|
+
- respect `healthContext.agentGuidance.canAnswerFromContext`
|
|
129
|
+
- if context is missing, stale, denied, disabled, or unavailable, follow `healthContext.repair`
|
|
130
|
+
- do not ask HealthKit directly; the Oomi mobile app syncs approved HealthKit data to the backend
|
|
131
|
+
|
|
115
132
|
## Hidden Speech Payload
|
|
116
133
|
|
|
117
134
|
Managed voice can carry a hidden TTS-only speech sidecar alongside the normal assistant message.
|
|
@@ -97,3 +97,21 @@ Rules:
|
|
|
97
97
|
- apply updates only through approved `persona-apps apply-action` actions
|
|
98
98
|
- if the persona app is missing, ask the user to add it from the Oomi client first so permissions and hydration can run
|
|
99
99
|
- do not execute persona UI jobs or start local persona app runtimes
|
|
100
|
+
|
|
101
|
+
## Permissioned Context Tools
|
|
102
|
+
|
|
103
|
+
Use Oomi context tools when the user asks about personal data that should come from mobile permissions instead of memory.
|
|
104
|
+
|
|
105
|
+
For health, fitness, sleep, workout, cardio, or movement questions:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
oomi context health --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Rules:
|
|
112
|
+
- answer only from `healthContext.summary` and `healthContext.derived`
|
|
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
|
|
116
|
+
- do not infer unavailable health fields
|
|
117
|
+
- do not request HealthKit directly from the phone; the Oomi mobile app owns permission prompts and syncs approved data to the backend
|