Fixed the ai server & disabled the old ai-bridge

BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP BACKFLIP
This commit is contained in:
2026-03-24 02:56:27 +02:00
parent 93bd8db6bb
commit 5617886ebe
7 changed files with 143 additions and 61 deletions

View File

@@ -111,42 +111,88 @@ function hasRule(rules, want) {
} }
function inferNluFromText(text, rules) { function inferNluFromText(text, rules) {
// Minimal, rule-ish: only try to satisfy common skills.
const yn = classifyYesNo(text);
if (yn) {
// Many flows use the intent string as grammar tag.
return buildNluResult(yn, rules, {});
}
// If Jetstream provided tutorial/global rules, prefer returning the rule itself as intent.
// This matches flows that transition on a specific grammar tag.
if (Array.isArray(rules)) {
const t = normalizeText(text).toLowerCase();
// Many builds pass short rule names (e.g., 'launch', 'dance') instead of full paths.
if (hasRule(rules, 'dance') && /\bdance\b/.test(t)) return buildNluResult('dance', rules, {});
if (hasRule(rules, 'take_photo') && /\b(photo|picture|take a photo|take a picture)\b/.test(t)) return buildNluResult('take_photo', rules, {});
// Launch/global command grammar: some builds only provide 'launch' + globals/global_commands_launch
// for many commands. When the utterance clearly matches a known command, emit that intent so flows
// do not treat it as a generic launch request.
if (hasRule(rules, 'launch')) {
// Many global command handlers expect a launch intent with a target skill in entities.skill.
if (/\bdance\b/.test(t)) return buildNluResult('launch', rules, { skill: 'dance', query: normalizeText(text) }, ['skill']);
if (/\b(photo|picture|take a photo|take a picture|selfie)\b/.test(t)) return buildNluResult('launch', rules, { skill: 'photobooth', query: normalizeText(text) }, ['skill']);
// Otherwise: route to chitchat so the robot actually speaks an answer.
// We keep intent='launch' but provide a concrete skill target.
return buildNluResult('launch', rules, { skill: '@be/chitchat', domain: 'chitchat', query: normalizeText(text) }, ['skill']);
}
}
// Simple tutorial-ish intents.
const t = normalizeText(text).toLowerCase(); const t = normalizeText(text).toLowerCase();
if (/\b(dance|do a dance)\b/.test(t)) return buildNluResult('dance', rules, {});
if (/\b(photo|picture|take a photo|take a picture)\b/.test(t)) return buildNluResult('take_photo', rules, {});
// If rules indicate yes/no, but we couldn't classify, mark noMatch. // No text → empty intent (ListenResultState.noInput → Mim re-prompts or times out).
if (Array.isArray(rules) && rules.some((r) => /yes[_-]?no/i.test(r))) { if (!t) return buildNluResult('', rules, {});
// Yes/No detection — common across many MIM types.
const yn = classifyYesNo(text);
if (!Array.isArray(rules) || !rules.length) {
return yn ? buildNluResult(yn, rules, {}) : buildNluResult('', rules, {});
}
// ── Skill-specific rule matching (checked BEFORE global catch-all) ──
// introductions/recognition_type_menu: expects face, name, voice, all
if (hasRule(rules, 'recognition_type_menu')) {
if (/\bface\b/.test(t)) return buildNluResult('face', rules, {});
if (/\bname\b/.test(t)) return buildNluResult('name', rules, {});
if (/\bvoice\b/.test(t)) return buildNluResult('voice', rules, {});
if (/\b(all|everything|everyone)\b/.test(t)) return buildNluResult('all', rules, {});
if (yn) return buildNluResult(yn, rules, {});
return buildNluResult('', rules, {}); return buildNluResult('', rules, {});
} }
// introductions/voice_face_training_menu: similar recognition type choices
if (hasRule(rules, 'voice_face_training_menu')) {
if (/\bface\b/.test(t)) return buildNluResult('face', rules, {});
if (/\bname\b/.test(t)) return buildNluResult('name', rules, {});
if (/\bvoice\b/.test(t)) return buildNluResult('voice', rules, {});
if (/\b(all|everything)\b/.test(t)) return buildNluResult('all', rules, {});
if (yn) return buildNluResult(yn, rules, {});
return buildNluResult('', rules, {});
}
// introductions yes/no questions: face_capture_ready, did_i_hear_name,
// did_i_pronounce_name, any_more_intros, recognition_any_more
if (rules.some((r) => /face_capture|did_i_hear|did_i_pronounce|any_more|recognition_any_more/.test(r))) {
if (yn) return buildNluResult(yn, rules, {});
return buildNluResult('', rules, {});
}
// introductions/intro_looper: expects loopmember intent with loopMemberReferent entity.
// Without real NLU we cannot resolve the entity → return noMatch so the MIM re-prompts.
if (hasRule(rules, 'intro_looper')) {
if (yn) return buildNluResult(yn, rules, {});
return buildNluResult('', rules, {});
}
// main-menu/execute_main_menu: map spoken words to menu items.
if (hasRule(rules, 'execute_main_menu')) {
if (/\bintroduc/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'introductions' });
if (/\bsurprise/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'surprise-me' });
if (/\b(time|clock)\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'clock' });
if (/\bphoto\s*booth\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'photobooth' });
if (/\bgallery\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'gallery' });
if (/\b(exercise|workout)\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'exercise' });
if (/\b(radio|music)\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'radio' });
if (/\bsettings?\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'settings' });
if (/\btips?\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'tips-tricks' });
if (/\bfun\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'fun-stuff' });
if (/\bcreate\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'create' });
if (/\b(report|personal)\b/.test(t)) return buildNluResult('loadMenu', rules, { loadMenu: 'personal-report' });
// Fall through to generic patterns below.
}
// ── Generic patterns ──
// Yes/No (applies to any MIM type that accepts it)
if (yn) return buildNluResult(yn, rules, {});
// Dance / Photo (tutorial rules)
if (hasRule(rules, 'dance') && /\bdance\b/.test(t)) return buildNluResult('dance', rules, {});
if (hasRule(rules, 'take_photo') && /\b(photo|picture|take a photo|take a picture)\b/.test(t)) return buildNluResult('take_photo', rules, {});
// Global launch commands — only for specific well-known commands.
// Do NOT catch-all to chitchat; that breaks in-skill NLU.
if (hasRule(rules, 'launch')) {
if (/\bdance\b/.test(t)) return buildNluResult('launch', rules, { skill: 'dance', query: normalizeText(text) }, ['skill']);
if (/\b(photo|picture|take a photo|take a picture|selfie)\b/.test(t)) return buildNluResult('launch', rules, { skill: 'photobooth', query: normalizeText(text) }, ['skill']);
}
// Default: no match — lets the Mim framework re-prompt or handle noMatch.
return buildNluResult('', rules, {}); return buildNluResult('', rules, {});
} }
@@ -588,6 +634,7 @@ function createHubShim(configPath) {
let binaryBytes = 0; let binaryBytes = 0;
let lastContext = null; let lastContext = null;
let lastContextMsg = null;
let pendingListen = null; let pendingListen = null;
async function maybeHandleListen() { async function maybeHandleListen() {
@@ -600,25 +647,24 @@ function createHubShim(configPath) {
if (mode === 'CLIENT_NLU' && !listenMsg._clientNlu) return; if (mode === 'CLIENT_NLU' && !listenMsg._clientNlu) return;
pendingListen = null; pendingListen = null;
const t0 = nowMs(); // Derive transID: echo back the transID the jetstream-service sent.
const base = { // It may appear on either the LISTEN or CONTEXT message.
msgID: uuid(), const msgTransID = listenMsg.transID || (lastContextMsg && lastContextMsg.transID) || transID || '';
ts: nowMs(),
};
// Emit SOS immediately so the robot transitions into listening. const t0 = nowMs();
send(ws, { ...base, type: 'SOS', data: null, final: false });
let text = ''; let text = '';
try { try {
if (mode === 'CLIENT_ASR') { if (mode === 'CLIENT_ASR') {
text = normalizeText(listenMsg._clientAsrText); text = normalizeText(listenMsg._clientAsrText);
} else if (mode === 'CLIENT_NLU') {
// No ASR needed for client-supplied NLU.
} else { } else {
text = await runAsrQueued(() => asrServiceSttOnce(asrBaseUrl, wsPath, timeoutMs, audioSourceId, logger)); text = await runAsrQueued(() => asrServiceSttOnce(asrBaseUrl, wsPath, timeoutMs, audioSourceId, logger));
} }
} catch (e) { } catch (e) {
logger.warn('asr failed', { connId, err: String(e && (e.stack || e.message || e)) }); logger.warn('asr failed', { connId, err: String(e && (e.stack || e.message || e)) });
// Still emit EOS and an empty listen result. // Still send an empty listen result.
} }
const rules = Array.isArray(listenMsg?.data?.rules) ? listenMsg.data.rules : []; const rules = Array.isArray(listenMsg?.data?.rules) ? listenMsg.data.rules : [];
@@ -628,8 +674,16 @@ function createHubShim(configPath) {
: ((config?.nlu?.enabled === false) ? buildNluResult('', rules, {}) : inferNluFromText(text, rules)); : ((config?.nlu?.enabled === false) ? buildNluResult('', rules, {}) : inferNluFromText(text, rules));
const asrRes = buildAsrResult(text); const asrRes = buildAsrResult(text);
// Build the match object (mirrors what the cloud hub returns).
const skillID = (lastContext && lastContext.skill && lastContext.skill.skillID)
? lastContext.skill.skillID
: (lastContext && typeof lastContext.skill === 'string' ? lastContext.skill : '');
const matchObj = { onRobot: true };
if (skillID) matchObj.skillID = skillID;
logger.info('listen result', { logger.info('listen result', {
connId, connId,
transID: msgTransID || undefined,
text: String(text || '').slice(0, 120), text: String(text || '').slice(0, 120),
intent: nluRes && nluRes.intent, intent: nluRes && nluRes.intent,
slot0: nluRes && Array.isArray(nluRes.slotActions) ? nluRes.slotActions[0] : undefined, slot0: nluRes && Array.isArray(nluRes.slotActions) ? nluRes.slotActions[0] : undefined,
@@ -639,26 +693,25 @@ function createHubShim(configPath) {
rules: Array.isArray(rules) ? rules.slice(0, 6) : [], rules: Array.isArray(rules) ? rules.slice(0, 6) : [],
}); });
// Optionally provide incremental ASR/NLU; Jetstream consumers often listen for these. // Send a local TURN_RESULT (not just LISTEN) so the skill's local turn resolves.
send(ws, { ...base, type: 'ASR', data: asrRes, final: false }); const turnResult = {
send(ws, { ...base, type: 'NLU', data: nluRes, final: false }); type: 'TURN_RESULT',
msgID: listenMsg.msgID || uuid(),
// Final listen response. transID: msgTransID,
const listenResp = {
type: 'LISTEN',
msgID: uuid(),
ts: nowMs(), ts: nowMs(),
requestID: msgTransID, // local turn uses transID as requestID
data: { data: {
asr: asrRes, status: 'SUCCEEDED',
nlu: nluRes, global: false,
result: {
asr: asrRes,
nlu: nluRes,
match: matchObj,
},
}, },
final: true, final: true,
timings: { total: nowMs() - t0 },
}; };
send(ws, listenResp); send(ws, turnResult);
// Emit EOS to complete the listen lifecycle.
send(ws, { ...base, type: 'EOS', data: null, final: true });
} }
ws.on('message', async (data, isBinary) => { ws.on('message', async (data, isBinary) => {
@@ -690,8 +743,10 @@ function createHubShim(configPath) {
switch (msg.type) { switch (msg.type) {
case 'CONTEXT': case 'CONTEXT':
lastContext = msg.data; lastContext = msg.data;
lastContextMsg = msg;
logger.debug('context', { logger.debug('context', {
connId, connId,
transID: msg.transID || undefined,
hasSkill: !!(msg.data && msg.data.skill), hasSkill: !!(msg.data && msg.data.skill),
hasRuntime: !!(msg.data && msg.data.runtime), hasRuntime: !!(msg.data && msg.data.runtime),
}); });
@@ -700,6 +755,7 @@ function createHubShim(configPath) {
pendingListen = msg; pendingListen = msg;
logger.debug('listen req', { logger.debug('listen req', {
connId, connId,
transID: msg.transID || undefined,
hotphrase: !!(msg.data && msg.data.hotphrase), hotphrase: !!(msg.data && msg.data.hotphrase),
mode: msg.data && msg.data.mode, mode: msg.data && msg.data.mode,
rules: Array.isArray(msg.data && msg.data.rules) ? msg.data.rules : [], rules: Array.isArray(msg.data && msg.data.rules) ? msg.data.rules : [],
@@ -707,11 +763,15 @@ function createHubShim(configPath) {
break; break;
case 'CLIENT_ASR': case 'CLIENT_ASR':
// Accept client-provided text (requires a LISTEN message too). // Accept client-provided text (requires a LISTEN message too).
pendingListen = pendingListen || { type: 'LISTEN', msgID: uuid(), ts: nowMs(), data: { rules: [], mode: 'CLIENT_ASR' } }; if (!pendingListen) {
pendingListen = { type: 'LISTEN', msgID: uuid(), transID: msg.transID, ts: nowMs(), data: { rules: [], mode: 'CLIENT_ASR' } };
}
pendingListen._clientAsrText = msg.data?.text; pendingListen._clientAsrText = msg.data?.text;
break; break;
case 'CLIENT_NLU': case 'CLIENT_NLU':
pendingListen = pendingListen || { type: 'LISTEN', msgID: uuid(), ts: nowMs(), data: { rules: [], mode: 'CLIENT_NLU' } }; if (!pendingListen) {
pendingListen = { type: 'LISTEN', msgID: uuid(), transID: msg.transID, ts: nowMs(), data: { rules: [], mode: 'CLIENT_NLU' } };
}
pendingListen._clientNlu = msg.data; pendingListen._clientNlu = msg.data;
break; break;
default: default:

View File

@@ -6,7 +6,7 @@
"recordSeconds": 5, "recordSeconds": 5,
"useDumpStateAudio": true, "useDumpStateAudio": true,
"useAsrServiceStt": true, "useAsrServiceStt": false,
"asrServiceHost": "127.0.0.1", "asrServiceHost": "127.0.0.1",
"asrServicePort": 8088, "asrServicePort": 8088,
"asrAudioSourceId": "alsa1", "asrAudioSourceId": "alsa1",
@@ -28,8 +28,8 @@
"suppressWakeGreetings": true, "suppressWakeGreetings": true,
"jetstreamOfflineFallbackEnabled": true, "jetstreamOfflineFallbackEnabled": false,
"jetstreamInjectOnHjHeard": true, "jetstreamInjectOnHjHeard": false,
"aiForwardingOnlyAllowedSkills": true, "aiForwardingOnlyAllowedSkills": true,
"aiForwardingAllowedSkills": ["@be/main-menu", "@be/idle"], "aiForwardingAllowedSkills": ["@be/main-menu", "@be/idle"],

View File

@@ -3122,6 +3122,12 @@ AIBridge.prototype.setupTunables = function () {
}; };
AIBridge.prototype.start = function () { AIBridge.prototype.start = function () {
// ── DISABLED: skip all hooks/listeners to eliminate interference ──
if (rlog) {
rlog.info("ai-bridge", "start() SKIPPED ai-bridge disabled for hub-shim debugging", {});
}
return;
// ── end disabled block ──
try { try {
this._loadConfig(); this._loadConfig();
} catch (e0) { } catch (e0) {

View File

@@ -4,6 +4,7 @@ const jibo = require('jibo');
let rlog = null; let rlog = null;
try { try {
rlog = require('./robot-logger'); rlog = require('./robot-logger');
if (rlog) global.__rlog = rlog;
} catch (e) { } catch (e) {
// ignore // ignore
} }

View File

@@ -319,6 +319,7 @@ class Client {
} }
break; break;
case types.ServiceEventType.TURN_RESULT: case types.ServiceEventType.TURN_RESULT:
try { global.__rlog.info('js-client', 'TURN_RESULT raw', {status:event.data.status,resultType:typeof event.data.result,requestID:event.requestID,transID:event.transID,global:!!event.data.global}); } catch(_e){}
if (event.data.status === types.TurnResultType.SUCCEEDED && typeof event.data.result === 'string') { if (event.data.status === types.TurnResultType.SUCCEEDED && typeof event.data.result === 'string') {
event.data.result = JSON.parse(event.data.result); event.data.result = JSON.parse(event.data.result);
} }
@@ -326,6 +327,7 @@ class Client {
if (result && 'asr' in result) { if (result && 'asr' in result) {
event.data.result = new types.ListenResult(result.asr, result.nlu, result.match); event.data.result = new types.ListenResult(result.asr, result.nlu, result.match);
} }
try { global.__rlog.info('js-client', 'TURN_RESULT parsed', {intent:event.data.result&&event.data.result.intent,text:event.data.result&&event.data.result.text,state:event.data.result&&event.data.result.state,nluIntent:result&&result.nlu&&result.nlu.intent,nluRules:result&&result.nlu&&result.nlu.rules}); } catch(_e){}
event.data.transID = event.transID; event.data.transID = event.transID;
if (event.data.global || event.requestID === types.GLOBAL_REQUEST) { if (event.data.global || event.requestID === types.GLOBAL_REQUEST) {
const data = event.data; const data = event.data;
@@ -377,6 +379,7 @@ class Client {
} }
if (shouldPassToRequest) { if (shouldPassToRequest) {
const request = this._requests.get(event.requestID); const request = this._requests.get(event.requestID);
try { global.__rlog.info('js-client', 'passToRequest', {type:event.type,requestID:event.requestID,found:!!request,isGlobal:event.requestID===types.GLOBAL_REQUEST}); } catch(_e){}
if (request) { if (request) {
if (event.type === types.ServiceEventType.ERROR) { if (event.type === types.ServiceEventType.ERROR) {
request.error.emit(new Error(`Received error: ${event.data.message}`)); request.error.emit(new Error(`Received error: ${event.data.message}`));
@@ -387,6 +390,7 @@ class Client {
} }
else { else {
if (event.requestID !== types.GLOBAL_REQUEST) { if (event.requestID !== types.GLOBAL_REQUEST) {
try { global.__rlog.warn('js-client', 'request NOT FOUND for requestID', {requestID:event.requestID,type:event.type,mapSize:this._requests.size}); } catch(_e){}
} }
} }
} }

View File

@@ -2032,6 +2032,7 @@ class Mim extends Behavior_1.default {
listen.failedToGetListener = false; listen.failedToGetListener = false;
listen.listener.on(ListenEvent_1.default.FINISHED, () => { listen.listener.on(ListenEvent_1.default.FINISHED, () => {
if (this.status !== Status_1.default.IN_PROGRESS) { if (this.status !== Status_1.default.IN_PROGRESS) {
try { global.__rlog.warn('mim', 'FINISHED but status!=IN_PROGRESS', {status:this.status}); } catch(_e){}
return; return;
} }
listen.listener = null; listen.listener = null;
@@ -2039,25 +2040,31 @@ class Mim extends Behavior_1.default {
if (!this.asrResults) { if (!this.asrResults) {
this.asrResults = new jetstream_client_1.types.ListenResult(null); this.asrResults = new jetstream_client_1.types.ListenResult(null);
} }
try { global.__rlog.info('mim', 'FINISHED routing', {state:this.asrResults.state,intent:this.asrResults.intent,text:this.asrResults.text,domain:this.asrResults.entities&&this.asrResults.entities.domain,elapsed:Date.now()-listen.startTime,timeout:listen.timeout}); } catch(_e){}
if (this.checkResult) { if (this.checkResult) {
this.checkResult(this.asrResults); this.checkResult(this.asrResults);
} }
if (this.asrResults.state === jetstream_client_1.types.ListenResultState.noInput && Date.now() - listen.startTime < listen.timeout) { if (this.asrResults.state === jetstream_client_1.types.ListenResultState.noInput && Date.now() - listen.startTime < listen.timeout) {
try { global.__rlog.info('mim', 'FINISHED → restartListen (noInput, under timeout)'); } catch(_e){}
this.states.listen.transitionTo(this.states.restartListen); this.states.listen.transitionTo(this.states.restartListen);
} }
else if (this.asrResults.entities.domain === 'mim_global') { else if (this.asrResults.entities.domain === 'mim_global') {
try { global.__rlog.info('mim', 'FINISHED → analyzeMimGlobal'); } catch(_e){}
this.states.listen.transitionTo(this.states.analyzeMimGlobal); this.states.listen.transitionTo(this.states.analyzeMimGlobal);
} }
else if (this.asrResults && (this.asrResults.entities.domain === 'gui_command' || else if (this.asrResults && (this.asrResults.entities.domain === 'gui_command' ||
this.asrResults.entities.domain === 'menu_global')) { this.asrResults.entities.domain === 'menu_global')) {
try { global.__rlog.info('mim', 'FINISHED → analyzeMenuGlobal'); } catch(_e){}
this.states.listen.transitionTo(this.states.analyzeMenuGlobal); this.states.listen.transitionTo(this.states.analyzeMenuGlobal);
} }
else { else {
try { global.__rlog.info('mim', 'FINISHED → analyzeResults', {isNoInput:this.asrResults.state===jetstream_client_1.types.ListenResultState.noInput}); } catch(_e){}
this.states.listen.transitionTo(this.states.analyzeResults, this.asrResults.state === jetstream_client_1.types.ListenResultState.noInput); this.states.listen.transitionTo(this.states.analyzeResults, this.asrResults.state === jetstream_client_1.types.ListenResultState.noInput);
} }
} }
}); });
listen.listener.on(ListenEvent_1.default.CLOUD, (asrResultsData) => { listen.listener.on(ListenEvent_1.default.CLOUD, (asrResultsData) => {
try { global.__rlog.info('mim', 'CLOUD event', {status:asrResultsData.status,intent:asrResultsData.result&&asrResultsData.result.intent,text:asrResultsData.result&&asrResultsData.result.text,state:asrResultsData.result&&asrResultsData.result.state,nlu:asrResultsData.result&&asrResultsData.result.nlu}); } catch(_e){}
this.asrResults = asrResultsData.result; this.asrResults = asrResultsData.result;
this.exitInputType = analytics.INPUT.SPEECH; this.exitInputType = analytics.INPUT.SPEECH;
}); });
@@ -5118,6 +5125,7 @@ class GlobalListener extends events_1.EventEmitter {
Runtime_1.default.instance.jetstream.events.localTurnStarted.emit(); Runtime_1.default.instance.jetstream.events.localTurnStarted.emit();
try { try {
const data = yield this.turn.promise; const data = yield this.turn.promise;
try { global.__rlog.info('mim', 'turn resolved', {status:data.status,hasResult:!!data.result,intent:data.result&&data.result.intent,text:data.result&&data.result.text,state:data.result&&data.result.state}); } catch(_e){}
if (this.stopped) { if (this.stopped) {
return; return;
} }
@@ -6496,6 +6504,7 @@ class FlowMim extends ActivityImplementation {
firstGrammarTag = result.firstGrammarTag = result.asrResults.intent; firstGrammarTag = result.firstGrammarTag = result.asrResults.intent;
result.grammarResults = result.asrResults.entities; result.grammarResults = result.asrResults.entities;
} }
try { global.__rlog.info('mim', 'FlowMim onSuccess', {activityClass:this.activityClass,firstGrammarTag:firstGrammarTag,intent:result.asrResults&&result.asrResults.intent,text:result.asrResults&&result.asrResults.text,state:result.asrResults&&result.asrResults.state,entities:result.asrResults&&result.asrResults.entities}); } catch(_e){}
let transition = options.innerOnSuccess(result); let transition = options.innerOnSuccess(result);
if (transition === undefined) { if (transition === undefined) {
transition = firstGrammarTag; transition = firstGrammarTag;

View File

@@ -504,7 +504,9 @@
"environment": { "environment": {
"DISPLAY": ":0", "DISPLAY": ":0",
"XAUTHORITY": "/tmp/.Xauthority", "XAUTHORITY": "/tmp/.Xauthority",
"XDG_CONFIG_HOME": "/opt/home/jibo-skill/.config" "XDG_CONFIG_HOME": "/opt/home/jibo-skill/.config",
"JIBO_HUB_SHIM_HOST": "192.168.1.28",
"JIBO_GQA_ENDPOINT": "http://192.168.1.28:8080"
}, },
"path": { "path": {
"jibo": [ "jibo": [