oomi-ai 0.12.0 → 0.12.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/lib/localAgent.js +1 -1
- package/lib/processTable.js +64 -0
- package/package.json +4 -2
- package/skills/oomi/SKILL.md +3 -1
package/lib/localAgent.js
CHANGED
|
@@ -440,7 +440,7 @@ export function startChannelWorker(state, runtime) {
|
|
|
440
440
|
export function practiceContext(brief) {
|
|
441
441
|
if (!brief || typeof brief !== 'object' || Array.isArray(brief)) return '';
|
|
442
442
|
if (!/^lp_[a-f0-9]{24}$/.test(brief.practiceSessionId || '') || !/^spc_[a-f0-9]{24}$/.test(brief.spaceId || '') ||
|
|
443
|
-
!['es', 'fr', 'de', 'it', 'pt'].includes(brief.targetLanguage) || brief.instructionLanguage !== 'en' ||
|
|
443
|
+
!['es', 'fr', 'de', 'it', 'pt', 'zh-Hans', 'ja', 'ko'].includes(brief.targetLanguage) || brief.instructionLanguage !== 'en' ||
|
|
444
444
|
!['beginner', 'intermediate', 'advanced'].includes(brief.level) || typeof brief.goal !== 'string' || brief.goal.length > 300 ||
|
|
445
445
|
!Number.isInteger(brief.minutesPerDay) || brief.minutesPerDay < 3 || brief.minutesPerDay > 60 ||
|
|
446
446
|
!Array.isArray(brief.phrasesToTry) || brief.phrasesToTry.length > 10 || brief.phrasesToTry.some(p => !p || typeof p.text !== 'string' || p.text.length > 120 || typeof p.translation !== 'string' || p.translation.length > 120)) return '';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
// The host's processes as `ps` lists them on macOS and Linux. Windows has no ps: its table comes
|
|
5
|
+
// from CIM (Win32_Process) through Windows PowerShell, which every supported Windows ships. Its
|
|
6
|
+
// full path, because a bridge's PATH may hold only System32 (the Windows oomi app's).
|
|
7
|
+
function windowsPowerShell() {
|
|
8
|
+
return path.win32.join(process.env.SystemRoot || process.env.windir || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function powershell(command, { run = spawnSync, timeout = 15000 } = {}) {
|
|
12
|
+
return run(windowsPowerShell(), ['-NoProfile', '-NonInteractive', '-Command', command],
|
|
13
|
+
{ encoding: 'utf8', timeout, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** "pid ppid command" lines into rows; a row's command is '' when the host withholds it. */
|
|
17
|
+
export function parseProcessTable(text) {
|
|
18
|
+
const rows = [];
|
|
19
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
20
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)(?:\s+(.*))?$/);
|
|
21
|
+
if (match) rows.push({ pid: Number(match[1]), ppid: Number(match[2]), command: match[3] || '' });
|
|
22
|
+
}
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Every process as { pid, ppid, command }, or null when the table can't be read. */
|
|
27
|
+
export function readProcessTable({ run = spawnSync, platform = process.platform } = {}) {
|
|
28
|
+
try {
|
|
29
|
+
const result = platform === 'win32'
|
|
30
|
+
? powershell("Get-CimInstance Win32_Process | ForEach-Object { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, $_.CommandLine }", { run })
|
|
31
|
+
: run('ps', ['-axo', 'pid=,ppid=,command='], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
32
|
+
if (!result || result.status !== 0) return null;
|
|
33
|
+
return parseProcessTable(result.stdout);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One process's command line: '' when it's gone or withheld, null when the host can't say. */
|
|
40
|
+
export function processCommand(pid, { run = spawnSync, platform = process.platform } = {}) {
|
|
41
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return '';
|
|
42
|
+
try {
|
|
43
|
+
const result = platform === 'win32'
|
|
44
|
+
? powershell(`(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`, { run })
|
|
45
|
+
: run('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
46
|
+
if (!result || (result.status !== 0 && platform === 'win32')) return null;
|
|
47
|
+
return String(result.stdout || '').trim();
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** When the process started, as the host states it (a reused pid starts at another time), or null. */
|
|
54
|
+
export function processStartTime(pid, { run = spawnSync, platform = process.platform } = {}) {
|
|
55
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return null;
|
|
56
|
+
try {
|
|
57
|
+
const result = platform === 'win32'
|
|
58
|
+
? powershell(`$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; if ($p) { $p.CreationDate.ToUniversalTime().ToString('o') }`, { run })
|
|
59
|
+
: run('ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8', timeout: 2000 });
|
|
60
|
+
return result && result.status === 0 ? String(result.stdout || '').trim() || null : null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oomi-ai",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Connect OpenClaw, Hermes, Grok Bot, and other agents to Oomi with verified runtime adapters",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oomi": "bin/oomi-ai.js"
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"check": "node --check bin/oomi-ai.js && node --check lib/bridgeService.js && node --check lib/bridgeMaintenance.js && node --check lib/processTable.js && node --check lib/localAgent.js && node --check lib/hermesGateway.js && node --check lib/hermesAcp.js && node --check lib/grokbotRuntime.js && node --check lib/connectionSetup.js && node --check lib/processSupervisor.js && node --check lib/agentChannels.js && node --check lib/runtimeFailure.js",
|
|
53
|
-
"test": "node --test test/*.test.mjs"
|
|
53
|
+
"test": "node --test test/*.test.mjs",
|
|
54
|
+
"prepublishOnly": "npm run check && node --test test/packageFiles.test.mjs"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"ws": "^8.19.0"
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
"lib/grokbotRuntime.js",
|
|
85
86
|
"lib/connectionSetup.js",
|
|
86
87
|
"lib/processSupervisor.js",
|
|
88
|
+
"lib/processTable.js",
|
|
87
89
|
"lib/agentChannels.js",
|
|
88
90
|
"lib/runtimeFailure.js",
|
|
89
91
|
"skills/oomi-delivery/SKILL.md"
|
package/skills/oomi/SKILL.md
CHANGED
|
@@ -519,6 +519,8 @@ Oomi renders simple Markdown in chat: short paragraphs, bullet or numbered lists
|
|
|
519
519
|
|
|
520
520
|
### Language Spaces v1
|
|
521
521
|
|
|
522
|
-
Choose Language in Talk after the owner's instruction; ask only for missing target language (`es`, `fr`, `de`, `it`, `pt`), level, goal and minutes/day (3–60), and confirm English instruction and the IANA timezone. Use `oomi spaces catalog` and build the approved Path / Lesson / Lesson complete / Words / Practice call views from `learning_path`, `exercise`, `step_sequence` variant `lesson`, `recap` variant `language`, `review_deck`, and `practice_feedback`. Write lessons as JSON data, each with `provenance: {kind:"persona", validation:"structural"}`; translations are persona-written and not teacher-checked. A daily objective is a separate opt-in; Space creation schedules nothing.
|
|
522
|
+
Choose Language in Talk after the owner's instruction; ask only for missing target language (`es`, `fr`, `de`, `it`, `pt`, `zh-Hans`, `ja`, `ko`), level, goal and minutes/day (3–60), and confirm English instruction and the IANA timezone. Use `oomi spaces catalog` and build the approved Path / Lesson / Lesson complete / Words / Practice call views from `learning_path`, `exercise`, `step_sequence` variant `lesson`, `recap` variant `language`, `review_deck`, and `practice_feedback`. Write lessons as JSON data, each with `provenance: {kind:"persona", validation:"structural"}`; translations are persona-written and not teacher-checked. A daily objective is a separate opt-in; Space creation schedules nothing.
|
|
523
|
+
|
|
524
|
+
Offer Mandarin (simplified Chinese, `zh-Hans`), Japanese and Korean in Talk like the other languages. At beginner level, use choose, listen-and-choose and arrange; translate answers are in English. Typing in the target language starts at intermediate. List accepted alternatives explicitly, including Japanese kanji and kana spellings where both are natural; never guess alternatives. Put reading aids (pinyin with tone marks, romaji or Revised Romanization) in `explanation`, optionally after a word in an option, like "你好 (nǐ hǎo)". Never put romanization in an accepted text unless the exercise asks for romanization.
|
|
523
525
|
|
|
524
526
|
Read before writing; send `space.v4`, revision, stable idempotency key and an owner `sourceMessageId`. The CLI advertises `cooking.v1, language.v1`. `oomi spaces language-action SPACE --component COMPONENT --file action.json` performs permitted actions. Never answer or skip exercises for the owner. After an ordinary practice call ends, use that handoff's `practiceSessionId` with `action:"save_practice_feedback"`, at most five `phrasesToTry` (`text,translation`) and five `corrections` (`original,suggested,reason`), each field at most 300 characters. Feedback is your suggestions, not mastery scoring. A call is eligible only after its practice brief was accepted. The feedback event stays pending until the runtime confirms startup; confirmed non-starts retry, and an uncertain start gets one recovery attempt with the same dedupe key before expiring after 24 hours. Read it with `oomi spaces practice-feedback SPACE --component COMPONENT`; paginate cards with `oomi spaces reviews SPACE --component COMPONENT [--cursor CURSOR]`. Never fabricate a call, request audio from client-written text, or start a microphone. Calls are saved in Talk.
|