oomi-ai 0.2.21 → 0.2.24
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 +129 -71
- package/openclaw.extension.js +3 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/bin/oomi-ai.js
CHANGED
|
@@ -1673,6 +1673,31 @@ function extractTextFromGatewayMessage(message) {
|
|
|
1673
1673
|
.join(' ');
|
|
1674
1674
|
}
|
|
1675
1675
|
|
|
1676
|
+
function summarizeVoiceFrameContract(frameText) {
|
|
1677
|
+
const frame = parseJsonPayload(frameText);
|
|
1678
|
+
if (!frame || typeof frame !== 'object') {
|
|
1679
|
+
return { parseable: false };
|
|
1680
|
+
}
|
|
1681
|
+
const payload = frame.payload && typeof frame.payload === 'object' ? frame.payload : {};
|
|
1682
|
+
const message = payload.message && typeof payload.message === 'object' ? payload.message : {};
|
|
1683
|
+
const metadata = message.metadata && typeof message.metadata === 'object' ? message.metadata : {};
|
|
1684
|
+
const spokenRaw = Object.prototype.hasOwnProperty.call(metadata, 'spoken') ? metadata.spoken : undefined;
|
|
1685
|
+
const spokenNormalized = normalizeSpokenMetadata(spokenRaw);
|
|
1686
|
+
const text = extractTextFromGatewayMessage(message);
|
|
1687
|
+
return {
|
|
1688
|
+
parseable: true,
|
|
1689
|
+
event: typeof frame.event === 'string' ? frame.event : '',
|
|
1690
|
+
state: typeof payload.state === 'string' ? payload.state : '',
|
|
1691
|
+
role: typeof message.role === 'string' ? message.role : '',
|
|
1692
|
+
contentLength: text.length,
|
|
1693
|
+
hasMetadata: Object.keys(metadata).length > 0,
|
|
1694
|
+
hasSpokenKey: Object.prototype.hasOwnProperty.call(metadata, 'spoken'),
|
|
1695
|
+
spokenRawType: spokenRaw === undefined ? 'missing' : Array.isArray(spokenRaw) ? 'array' : typeof spokenRaw,
|
|
1696
|
+
spokenNormalized: Boolean(spokenNormalized),
|
|
1697
|
+
spokenSegmentCount: Array.isArray(spokenNormalized?.segments) ? spokenNormalized.segments.length : 0,
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1676
1701
|
function ensureVoiceAssistantSpokenMetadata(frameText) {
|
|
1677
1702
|
const frame = parseJsonPayload(frameText);
|
|
1678
1703
|
if (!frame || typeof frame !== 'object') {
|
|
@@ -1702,10 +1727,10 @@ function ensureVoiceAssistantSpokenMetadata(frameText) {
|
|
|
1702
1727
|
? message.metadata
|
|
1703
1728
|
: {};
|
|
1704
1729
|
const metadata = { ...originalMetadata };
|
|
1705
|
-
const
|
|
1730
|
+
const normalizedExplicitSpoken = normalizeSpokenMetadata(originalMetadata.spoken);
|
|
1706
1731
|
const spoken =
|
|
1707
|
-
|
|
1708
|
-
|
|
1732
|
+
normalizedExplicitSpoken ||
|
|
1733
|
+
inferSpokenMetadataFromContent(extractTextFromGatewayMessage(message));
|
|
1709
1734
|
if (!spoken) {
|
|
1710
1735
|
return { frameText, changed: false, reason: '' };
|
|
1711
1736
|
}
|
|
@@ -1725,7 +1750,7 @@ function ensureVoiceAssistantSpokenMetadata(frameText) {
|
|
|
1725
1750
|
return {
|
|
1726
1751
|
frameText: nextFrame,
|
|
1727
1752
|
changed: nextFrame !== frameText,
|
|
1728
|
-
reason:
|
|
1753
|
+
reason: normalizedExplicitSpoken ? 'normalized' : (messageRole ? 'synthesized' : 'synthesized_missing_role'),
|
|
1729
1754
|
};
|
|
1730
1755
|
}
|
|
1731
1756
|
|
|
@@ -1906,11 +1931,11 @@ async function runBridgePreflight({ brokerWs, gatewayUrl, gatewayConfigPath }) {
|
|
|
1906
1931
|
await assertTcpReachable(parsedGatewayUrl.toString());
|
|
1907
1932
|
}
|
|
1908
1933
|
|
|
1909
|
-
function buildBridgeDetachArgs(rawFlags = {}) {
|
|
1910
|
-
const orderedKeys = [
|
|
1911
|
-
'broker-http',
|
|
1912
|
-
'broker-ws',
|
|
1913
|
-
'pair-code',
|
|
1934
|
+
function buildBridgeDetachArgs(rawFlags = {}) {
|
|
1935
|
+
const orderedKeys = [
|
|
1936
|
+
'broker-http',
|
|
1937
|
+
'broker-ws',
|
|
1938
|
+
'pair-code',
|
|
1914
1939
|
'app-url',
|
|
1915
1940
|
'device-id',
|
|
1916
1941
|
'device-token',
|
|
@@ -1928,9 +1953,13 @@ function buildBridgeDetachArgs(rawFlags = {}) {
|
|
|
1928
1953
|
if (!text) continue;
|
|
1929
1954
|
args.push(`--${key}`, text);
|
|
1930
1955
|
}
|
|
1931
|
-
|
|
1932
|
-
return args;
|
|
1933
|
-
}
|
|
1956
|
+
|
|
1957
|
+
return args;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function isServiceManagedBridgeStart(flags = {}) {
|
|
1961
|
+
return isTruthyFlag(flags['service-managed']);
|
|
1962
|
+
}
|
|
1934
1963
|
|
|
1935
1964
|
function startBridgeDetachedProcess(rawFlags = {}) {
|
|
1936
1965
|
const existing = findRunningBridgeProcess();
|
|
@@ -2101,17 +2130,17 @@ function runLaunchctl(args, { allowFailure = false } = {}) {
|
|
|
2101
2130
|
return { status, stdout, stderr };
|
|
2102
2131
|
}
|
|
2103
2132
|
|
|
2104
|
-
function buildBridgeLaunchAgentPlist() {
|
|
2105
|
-
const scriptPath = (() => {
|
|
2106
|
-
try {
|
|
2107
|
-
return fs.realpathSync(process.argv[1]);
|
|
2108
|
-
} catch {
|
|
2109
|
-
return process.argv[1];
|
|
2110
|
-
}
|
|
2111
|
-
})();
|
|
2112
|
-
const programArgs = [process.execPath, scriptPath, 'openclaw', 'bridge', 'start'];
|
|
2113
|
-
const bridgeLogPath = resolveBridgeLiveLogPath();
|
|
2114
|
-
const argsXml = programArgs.map((arg) => `<string>${xmlEscape(arg)}</string>`).join('\n ');
|
|
2133
|
+
function buildBridgeLaunchAgentPlist() {
|
|
2134
|
+
const scriptPath = (() => {
|
|
2135
|
+
try {
|
|
2136
|
+
return fs.realpathSync(process.argv[1]);
|
|
2137
|
+
} catch {
|
|
2138
|
+
return process.argv[1];
|
|
2139
|
+
}
|
|
2140
|
+
})();
|
|
2141
|
+
const programArgs = [process.execPath, scriptPath, 'openclaw', 'bridge', 'start', '--service-managed'];
|
|
2142
|
+
const bridgeLogPath = resolveBridgeLiveLogPath();
|
|
2143
|
+
const argsXml = programArgs.map((arg) => `<string>${xmlEscape(arg)}</string>`).join('\n ');
|
|
2115
2144
|
|
|
2116
2145
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2117
2146
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2167,17 +2196,18 @@ function readBridgeLaunchdStatus() {
|
|
|
2167
2196
|
};
|
|
2168
2197
|
}
|
|
2169
2198
|
|
|
2170
|
-
function startBridgeLaunchdService() {
|
|
2171
|
-
assertMacOSLaunchdAvailable();
|
|
2172
|
-
const plistPath = resolveBridgeLaunchAgentPlistPath();
|
|
2173
|
-
if (!fs.existsSync(plistPath)) {
|
|
2174
|
-
throw new Error('Bridge service is not installed. Run: oomi openclaw bridge service install');
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
runLaunchctl(['
|
|
2180
|
-
runLaunchctl(['
|
|
2199
|
+
function startBridgeLaunchdService() {
|
|
2200
|
+
assertMacOSLaunchdAvailable();
|
|
2201
|
+
const plistPath = resolveBridgeLaunchAgentPlistPath();
|
|
2202
|
+
if (!fs.existsSync(plistPath)) {
|
|
2203
|
+
throw new Error('Bridge service is not installed. Run: oomi openclaw bridge service install');
|
|
2204
|
+
}
|
|
2205
|
+
writeFile(plistPath, buildBridgeLaunchAgentPlist());
|
|
2206
|
+
const domain = launchctlDomain();
|
|
2207
|
+
const target = launchctlServiceTarget();
|
|
2208
|
+
runLaunchctl(['bootout', domain, plistPath], { allowFailure: true });
|
|
2209
|
+
runLaunchctl(['bootstrap', domain, plistPath]);
|
|
2210
|
+
runLaunchctl(['enable', target], { allowFailure: true });
|
|
2181
2211
|
runLaunchctl(['kickstart', '-k', target], { allowFailure: true });
|
|
2182
2212
|
}
|
|
2183
2213
|
|
|
@@ -2958,10 +2988,16 @@ async function startOpenclawBridge(flags) {
|
|
|
2958
2988
|
gatewaySocket.on('message', runBridgeCallbackSafely((gatewayRaw) => {
|
|
2959
2989
|
let frame = typeof gatewayRaw === 'string' ? gatewayRaw : gatewayRaw.toString();
|
|
2960
2990
|
if (classifyBridgeSessionScope(sessionId) === 'voice') {
|
|
2991
|
+
const beforeSummary = summarizeVoiceFrameContract(frame);
|
|
2961
2992
|
const spokenNormalized = ensureVoiceAssistantSpokenMetadata(frame);
|
|
2962
2993
|
if (spokenNormalized.changed) {
|
|
2963
2994
|
frame = spokenNormalized.frameText;
|
|
2964
|
-
console.log(`[bridge] voice.spoken_metadata.${spokenNormalized.reason} ${sessionId}
|
|
2995
|
+
console.log(`[bridge] voice.spoken_metadata.${spokenNormalized.reason} ${sessionId} ${JSON.stringify({
|
|
2996
|
+
before: beforeSummary,
|
|
2997
|
+
after: summarizeVoiceFrameContract(frame),
|
|
2998
|
+
})}`);
|
|
2999
|
+
} else if (beforeSummary.event === 'chat' && beforeSummary.state === 'final') {
|
|
3000
|
+
console.log(`[bridge] voice.chat.final ${sessionId} ${JSON.stringify(beforeSummary)}`);
|
|
2965
3001
|
}
|
|
2966
3002
|
}
|
|
2967
3003
|
const gatewayPayload = parseJsonPayload(frame);
|
|
@@ -3318,13 +3354,17 @@ async function startOpenclawBridge(flags) {
|
|
|
3318
3354
|
return;
|
|
3319
3355
|
}
|
|
3320
3356
|
|
|
3321
|
-
if (payload.type === 'client.frame') {
|
|
3322
|
-
const sessionId = String(payload.sessionId || '').trim();
|
|
3323
|
-
const frame = typeof payload.frame === 'string' ? payload.frame : '';
|
|
3324
|
-
if (!sessionId || !frame) return;
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3357
|
+
if (payload.type === 'client.frame') {
|
|
3358
|
+
const sessionId = String(payload.sessionId || '').trim();
|
|
3359
|
+
const frame = typeof payload.frame === 'string' ? payload.frame : '';
|
|
3360
|
+
if (!sessionId || !frame) return;
|
|
3361
|
+
if (classifyBridgeSessionScope(sessionId) === 'voice') {
|
|
3362
|
+
console.log(`[bridge] client.frame ${sessionId} ${JSON.stringify(summarizeVoiceFrameContract(frame))}`);
|
|
3363
|
+
} else {
|
|
3364
|
+
console.log(`[bridge] client.frame ${sessionId}`);
|
|
3365
|
+
}
|
|
3366
|
+
const sessionBridge = getOrCreateGatewaySession(sessionId);
|
|
3367
|
+
if (!sessionBridge) return;
|
|
3328
3368
|
const requestMeta = extractGatewayRequestMeta(frame);
|
|
3329
3369
|
if (requestMeta) {
|
|
3330
3370
|
if (!(sessionBridge.pendingRequests instanceof Map)) {
|
|
@@ -3941,12 +3981,17 @@ async function handleBridgeServiceCommand(actionRaw = '', flags = {}) {
|
|
|
3941
3981
|
);
|
|
3942
3982
|
}
|
|
3943
3983
|
|
|
3944
|
-
async function startBridgeLifecycle(flags = {}) {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3984
|
+
async function startBridgeLifecycle(flags = {}) {
|
|
3985
|
+
const serviceManaged = isServiceManagedBridgeStart(flags);
|
|
3986
|
+
if (serviceManaged && Boolean(flags.detach)) {
|
|
3987
|
+
throw new Error('Detached bridge mode cannot be combined with --service-managed.');
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
if (Boolean(flags.detach)) {
|
|
3991
|
+
const detachedFlags = { ...flags };
|
|
3992
|
+
delete detachedFlags.detach;
|
|
3993
|
+
const result = startBridgeDetachedProcess(detachedFlags);
|
|
3994
|
+
if (result.alreadyRunning) {
|
|
3950
3995
|
incrementBridgeMetric('duplicate_start_attempt_count');
|
|
3951
3996
|
console.log(`Bridge already running (pid: ${result.pid}).`);
|
|
3952
3997
|
return;
|
|
@@ -3955,19 +4000,30 @@ async function startBridgeLifecycle(flags = {}) {
|
|
|
3955
4000
|
console.log(`Bridge started in background (pid: ${result.pid}).`);
|
|
3956
4001
|
return;
|
|
3957
4002
|
}
|
|
3958
|
-
|
|
3959
|
-
const running = findRunningBridgeProcess();
|
|
3960
|
-
if (running) {
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4003
|
+
|
|
4004
|
+
const running = findRunningBridgeProcess();
|
|
4005
|
+
if (running) {
|
|
4006
|
+
if (!serviceManaged) {
|
|
4007
|
+
incrementBridgeMetric('duplicate_start_attempt_count');
|
|
4008
|
+
console.log(
|
|
4009
|
+
`Bridge already running (pid ${running.pid})${running.deviceId ? ` for device ${running.deviceId}` : ''}.`
|
|
4010
|
+
);
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
incrementBridgeMetric('bridge_restart_count');
|
|
4015
|
+
console.log(
|
|
4016
|
+
`Service-managed bridge start detected existing bridge (pid ${running.pid})${running.deviceId ? ` for device ${running.deviceId}` : ''}; reclaiming ownership.`
|
|
4017
|
+
);
|
|
4018
|
+
const result = await stopBridgeProcesses();
|
|
4019
|
+
if (Array.isArray(result.stillAlive) && result.stillAlive.length > 0) {
|
|
4020
|
+
throw new Error(`Failed to stop bridge processes: ${result.stillAlive.join(', ')}`);
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
incrementBridgeMetric('bridge_start_count');
|
|
4025
|
+
await startOpenclawBridge(flags);
|
|
4026
|
+
}
|
|
3971
4027
|
|
|
3972
4028
|
async function handleBridgeLifecycleCommand(flags = {}, actionRaw = '') {
|
|
3973
4029
|
const action = String(actionRaw || 'start').trim().toLowerCase();
|
|
@@ -4202,16 +4258,18 @@ if (__isDirectExecution) {
|
|
|
4202
4258
|
export {
|
|
4203
4259
|
prepareGatewayFrameForLocalGateway,
|
|
4204
4260
|
ensureVoiceAssistantSpokenMetadata,
|
|
4261
|
+
buildBridgeLaunchAgentPlist,
|
|
4205
4262
|
classifyBridgeFailure,
|
|
4206
4263
|
classifyBridgeSessionScope,
|
|
4207
4264
|
createBridgeProcessFaultHandler,
|
|
4208
|
-
computeReconnectDelayMs,
|
|
4209
|
-
resolveBridgeStatusForBrokerOpen,
|
|
4210
|
-
resolveBridgeStatusForRuntimeFault,
|
|
4211
|
-
runBridgeCallbackSafely,
|
|
4212
|
-
extractGatewayRequestMeta,
|
|
4213
|
-
extractGatewayResponseMeta,
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4265
|
+
computeReconnectDelayMs,
|
|
4266
|
+
resolveBridgeStatusForBrokerOpen,
|
|
4267
|
+
resolveBridgeStatusForRuntimeFault,
|
|
4268
|
+
runBridgeCallbackSafely,
|
|
4269
|
+
extractGatewayRequestMeta,
|
|
4270
|
+
extractGatewayResponseMeta,
|
|
4271
|
+
isServiceManagedBridgeStart,
|
|
4272
|
+
isGatewayRunStartedFrame,
|
|
4273
|
+
isBridgeWorkerCommand,
|
|
4274
|
+
parsePositiveInteger,
|
|
4275
|
+
};
|
package/openclaw.extension.js
CHANGED
|
@@ -186,10 +186,9 @@ function normalizeOutgoingMetadata(payloadMetadata, { accountId, correlationId,
|
|
|
186
186
|
? { ...payloadMetadata }
|
|
187
187
|
: {};
|
|
188
188
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(!explicitSpokenPresent ? inferSpokenMetadataFromContent(content) : null);
|
|
189
|
+
const spoken =
|
|
190
|
+
normalizeSpokenMetadata(metadata.spoken) ||
|
|
191
|
+
inferSpokenMetadataFromContent(content);
|
|
193
192
|
if (spoken) {
|
|
194
193
|
metadata.spoken = spoken;
|
|
195
194
|
} else {
|
package/openclaw.plugin.json
CHANGED