Stub in framework for new .net Open Jibo cloud

This commit is contained in:
Jacob Dubin
2026-04-11 07:12:57 -05:00
parent 0c040d1348
commit 8f838787a0
54 changed files with 1933 additions and 897 deletions

4
.gitignore vendored
View File

@@ -412,3 +412,7 @@ FodyWeavers.xsd
# Built Visual Studio Code Extensions
*.vsix
# Local .NET CLI home and first-run artifacts
.dotnet/
**/.dotnet/

6
OpenJibo/NuGet.Config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
</packageSources>
</configuration>

View File

@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jibo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jibo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=openjibo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,9 +1,35 @@
<Solution>
<Folder Name="/docs/">
<File Path="docs/device-bootstrap.md" />
<File Path="docs/protocol-inventory.md" />
<File Path="docs/public-site-plan.md" />
<File Path="docs/support-tiers.md" />
</Folder>
<Folder Name="/scripts/" />
<Folder Name="/scripts/boostrap/">
<File Path="scripts/bootstrap/Discover-JiboHosts.ps1" />
<File Path="scripts/bootstrap/Generate-JiboDnsOverrides.ps1" />
<File Path="scripts/bootstrap/README.md" />
<File Path="scripts/bootstrap/Test-OpenJiboRouting.ps1" />
</Folder>
<Folder Name="/scripts/cloud/">
<File Path="scripts/cloud/Invoke-CloudSmoke.ps1" />
<File Path="scripts/cloud/Invoke-ProtocolFixture.ps1" />
<File Path="scripts/cloud/README.md" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Jibo.Runtime.Abstractions/Jibo.Runtime.Abstractions.csproj" Id="41abf805-373e-4e71-b1bf-2cab6dbf892e" />
<Project Path="src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Domain/Jibo.Cloud.Domain.csproj" />
<Project Path="src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Jibo.Cloud.Application.csproj" />
<Project Path="src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Jibo.Cloud.Infrastructure.csproj" />
<Project Path="src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Jibo.Cloud.Api.csproj" />
<Project Path="src/Playground/Playground.csproj" />
</Folder>
<Folder Name="/src/OpenJibo.Site/">
<File Path="src/OpenJibo.Site/index.html" />
<File Path="src/OpenJibo.Site/site.css" />
</Folder>
</Solution>

View File

@@ -1,439 +1,89 @@
# Hybrid Jibo Runtime Plan
# OpenJibo
## Goal
## Summary
Build a **modern local-first Jibo runtime** while preserving the parts of native Jibo that are still useful:
OpenJibo is the working revival track for Jibo.
* native wake/turn plumbing where helpful
* native skills where helpful
* native embodiment and rendering
* fast experimentation in **.NET 10** off-robot
The near-term plan is intentionally concrete:
Jibos native runtime already exposes a layered service model centered around **Jetstream** for turn/event flow, **GlobalManagerService** for routing, **SkillsService** for skill lifecycle, and **ExpressionService** for embodiment/rendering. The SSM startup is config-driven and mode-driven, which suggests a hybrid mode is a viable path.
1. Build a stable replacement cloud on Azure.
2. Use the existing Node prototype as the protocol oracle and capture harness.
3. Port the hosted implementation to .NET as a modular monolith.
4. Bring real robots online first through RCM plus controlled DNS/TLS patching.
5. Use OTA later to reduce setup friction once the hosted cloud is proven.
---
This keeps the project grounded in what is already working while moving toward a maintainable hosted platform.
## Architecture Direction
## Current Truth
We will keep the **main experimental runtime in .NET 10** and treat Jibo as an embodied endpoint with a thin bridge layer.
The repo now has three distinct lanes:
That means:
- `src/Jibo.Cloud/node`
The discovery server. This is the best source of observed protocol behavior today.
- `src/Jibo.Cloud/dotnet`
The long-term hosted implementation. This is where the stable cloud is being built.
- `src/Jibo.Runtime.Abstractions`
The normalized runtime seam between robot/cloud traffic and modern conversation logic.
* **off-robot**: conversation logic, planning, AI routing, capabilities
* **on-robot**: thin adapter/bridge to native Jibo services
* **native Jibo**: reuse rendering, skill hosting, and useful event seams
---
## High-Level ASCII Flowchart
The key architectural idea is:
```text
+--------------------------------------------------------------+
| NATIVE JIBO LAYER |
|--------------------------------------------------------------|
| Wake / Turn Events |
| - Jetstream |
| - hjHeard / turn started / turn result |
| |
| Native Services |
| - GlobalManagerService |
| - SkillsService |
| - ExpressionService |
| - TTS / Body / Visual / Motion services |
+------------------------------+-------------------------------+
|
| events / hooks / commands
v
+--------------------------------------------------------------+
| JIBO BRIDGE LAYER |
|--------------------------------------------------------------|
| Thin adapter between Jibo and modern runtime |
| |
| Responsibilities: |
| - receive turn/wake events |
| - receive skill context / native state |
| - forward normalized events to .NET runtime |
| - accept ResponsePlans / commands from .NET runtime |
| - invoke native skills / expression / TTS / visuals |
+------------------------------+-------------------------------+
|
| normalized turn context
v
+--------------------------------------------------------------+
| MODERN .NET 10 RUNTIME |
|--------------------------------------------------------------|
| Conversation Broker |
| - session state |
| - follow-up windows |
| - topic/context tracking |
| |
| STT Strategy Selector |
| - native transcript |
| - local STT |
| - cloud STT |
| |
| Brain Strategy Selector |
| - skill/rules path |
| - local AI |
| - cloud AI |
| - hybrid routing |
| |
| Action / Orchestration Planner |
| - gestures / visuals / ESML / delegation |
| - capability/tool calls |
| - build final ResponsePlan |
| |
| Capability Registry |
| - weather / time / reminders / tools |
| - native skill delegation |
| - robot expression helpers |
+------------------------------+-------------------------------+
|
| ResponsePlan / commands
v
+--------------------------------------------------------------+
| EXECUTION TARGETS |
|--------------------------------------------------------------|
| - Native SkillsService |
| - Native ExpressionService |
| - Native TTS / visuals / motion |
| - Local AI backends |
| - Cloud AI backends |
| - External APIs / tools |
+--------------------------------------------------------------+
Jibo device -> OpenJibo cloud -> normalized runtime contracts -> capabilities and planning
```
---
## First Supported Device Path
## Runtime Flow
The first supported recovery path is enthusiast-friendly, not zero-touch:
```text
[Wake Word / Turn / Follow-up]
|
v
[Jibo Native Events]
|
v
[Jibo Bridge Layer]
|
v
[Conversation Broker (.NET)]
|
v
[STT Strategy Selection]
|
v
[Brain Strategy Selection]
/ | \
/ | \
[Skill/Rules] [Local AI] [Cloud AI]
\ | /
\ | /
[Planner]
|
v
[ResponsePlan Built]
|
v
[Jibo Bridge Layer]
|
v
[Skills / Expression / TTS / Motion / Visuals]
|
v
[Follow-up Window or Timeout]
QR Wi-Fi -> controlled router/DNS -> redirect legacy Jibo hosts ->
RCM/device patch for TLS and host acceptance -> OpenJibo cloud on Azure
```
---
That path is documented in [docs/device-bootstrap.md](C:/Projects/JiboExperiments/OpenJibo/docs/device-bootstrap.md).
## Planned Hybrid Mode
Jibos startup and service composition are mode-driven and config-driven, so the long-term plan is to add a **new custom mode** rather than replacing stock behavior outright.
### Candidate mode names
* `hybrid`
* `openjibo`
* `revival`
* `local-first`
### Intent of the mode
The custom mode should:
* preserve normal mode for stock behavior
* preserve developer mode for native debugging
* enable the bridge/runtime path for hybrid experiments
* allow selective routing between old and new Jibo behavior
---
## Design Principles
### 1. Keep Jibo-specific code at the edges
The .NET runtime should know about:
* turns
* sessions
* plans
* capabilities
* render actions
It should **not** depend directly on:
* Electron internals
* SSM implementation quirks
* old Linux deployment constraints
### 2. Reuse native embodiment
Native Jibo rendering is valuable. ExpressionService appears to own animation, attention, DOF arbitration, and embodied output, so it should be reused as long as possible.
### 3. Replace cognition before replacing embodiment
The first thing to modernize is:
* routing
* planning
* AI selection
* follow-up conversation behavior
Not necessarily:
* body motion
* TTS
* expression plumbing
### 4. Favor thin robot-side code
The bridge on Jibo should stay small and stable. Fast-moving logic belongs in .NET 10.
### 5. Everything should converge to a ResponsePlan
Regardless of source:
* skill
* rules engine
* local AI
* cloud AI
the result should become a single normalized response/output plan.
---
## Native Jibo Mapping
Based on current reverse engineering, the native service boundaries map roughly like this: Jetstream is the turn/event seam, GlobalManagerService performs routing and skill-launch logic, SkillsService manages skill lifecycle, and ExpressionService handles embodiment/rendering.
## Repo Map
```text
Our Concept Native Jibo Equivalent
---------------------------- --------------------------------
Wake / Turn Source Jetstream
Conversation Broker split across Jetstream + routing
Brain Selection GlobalManagerService + skills
Skill Execution SkillsService
Renderer / Embodiment ExpressionService
OpenJibo/
docs/
device-bootstrap.md
protocol-inventory.md
public-site-plan.md
support-tiers.md
scripts/bootstrap/
Discover-JiboHosts.ps1
Generate-JiboDnsOverrides.ps1
Test-OpenJiboRouting.ps1
src/
Jibo.Cloud/
node/
dotnet/
Jibo.Runtime.Abstractions/
Playground/
OpenJibo.Site/
```
---
## Decisions Locked In
## Proposed Project Layout
- The first milestone is `core revive`, not full protocol parity.
- Azure SQL is the relational system of record for the hosted cloud.
- Billing and donations are future-compatible concerns, not phase-one delivery requirements.
- OTA is a phase-two simplification strategy, not the initial dependency.
```text
/src
/Jibo.Runtime
Core runtime orchestration
- ConversationBroker
- Session state
- Turn pipeline
- ResponsePlan builder
## Near-Term Work
/Jibo.Runtime.Abstractions
Interfaces and models
- ITurnSource
- ISttStrategy
- IBrainStrategy
- IResponsePlanner
- IRobotAdapter
- TurnContext
- ResponsePlan
- port required endpoint and WebSocket behavior from Node to .NET
- keep protocol captures and replay fixtures current
- harden device bootstrap documentation and scripts
- stand up the initial `openjibo.com` information site
/Jibo.Bridge
Jibo adapter / compatibility layer
- robot event ingestion
- command dispatch back to Jibo
- native hook integration
## Important Docs
/Jibo.Brain.Rules
deterministic routing / skills / decision tree
/Jibo.Brain.Local
local AI experiments
/Jibo.Brain.Cloud
cloud AI experiments
/Jibo.Capabilities
tools and callable capabilities
- weather
- time
- reminders
- skill delegation
- expression helpers
/Jibo.Simulator
fake robot target for testing ResponsePlans
/docs
architecture
notes
traces
```
---
## Initial Build Plan
### Phase 1 — Contracts and runtime skeleton
Build the core models and interfaces first:
* `TurnContext`
* `ConversationSession`
* `SttResult`
* `BrainDecision`
* `ResponsePlan`
* `RenderAction`
* `FollowupPolicy`
### Phase 2 — Minimal broker
Implement:
* session open/close
* follow-up timeout
* topic/context tracking
### Phase 3 — Bridge skeleton
Create the adapter boundary for:
* inbound Jibo events
* outbound robot commands
Even if the first version is mocked, keep the interface stable.
### Phase 4 — First working path
Implement a narrow vertical slice:
* input turn
* decision/rules path
* weather example
* TTS response
* follow-up window
### Phase 5 — Native integration expansion
Add native delegation for:
* skills
* expression
* visuals
* gestures
* local turn/open follow-up behavior
### Phase 6 — Hybrid AI routing
Add:
* local AI path
* cloud AI path
* confidence/routing policy
---
## First Vertical Slice
Recommended first demonstration:
### Example
User says:
> Hey Jibo, whats the weather?
System flow:
1. Jibo event arrives through bridge
2. .NET broker opens a session
3. transcript enters routing
4. weather capability is called
5. planner builds a `ResponsePlan`
6. bridge sends speech + visual action back to Jibo
7. follow-up window stays open
Then:
> What about the low tonight?
The same session stays active without wake word if the follow-up window is still open.
---
## Near-Term Questions to Answer
* What is the cleanest robot-side bridge seam:
* Jetstream hook
* skill hook
* local service calls
* mixed approach
* What is the smallest command set needed to drive Jibo usefully:
* speak
* gesture
* visual
* launch skill
* keep listening
* Which pieces should remain native the longest:
* expression
* skill hosting
* turn engine
* wake-word flow
* How should custom mode selection activate the hybrid path
---
## Practical Strategy
For now:
* **develop fast in .NET 10**
* **use Jibo as an embodied endpoint**
* **keep the robot-side integration thin**
* **delay deep on-robot porting until architecture proves itself**
This keeps experimentation fast while preserving a path toward deeper integration later.
---
## Current Working Hypothesis
The best long-term shape is:
```text
stock Jibo embodiment + modern external cognition + thin hybrid bridge
```
That gives us:
* rapid iteration
* local-first experiments
* preserved native robot personality/expression
* reduced dependence on brittle legacy cloud paths
- [Cloud overview](/src/Jibo.Cloud/README.md)
- [Protocol inventory](/docs/protocol-inventory.md)
- [Support tiers](/docs/support-tiers.md)
- [Device bootstrap path](/docs/device-bootstrap.md)
- [Public site plan](/docs/public-site-plan.md)

View File

@@ -0,0 +1,66 @@
# Device Bootstrap Path
## Supported First Path
The first supported OpenJibo recovery path is:
```text
QR Wi-Fi -> controlled router/DNS -> redirect Jibo hosts ->
RCM/device patch -> Azure-hosted OpenJibo cloud
```
This is the path we can document, repeat, and improve.
## Why This Path Comes First
- it matches what the current Node prototype already requires
- it keeps the hosted cloud work grounded in real device traffic
- it avoids blocking the entire revival on OTA before cloud compatibility exists
## Bootstrap Checklist
1. Connect the robot to a controlled Wi-Fi network.
2. Redirect legacy cloud hostnames to the OpenJibo environment.
3. Prevent fallback DNS from bypassing the controlled resolver.
4. Gain RCM/device access for targeted TLS or host validation changes.
5. Verify robot startup, token flow, socket flow, and first-turn behavior.
## Required Host Routing
At minimum, watch and validate:
- `api.jibo.com`
- `api-socket.jibo.com`
- `neo-hub.jibo.com`
- `neohub.jibo.com`
## Scripted Helpers
Bootstrap helper scripts live in [scripts/bootstrap](C:/Projects/JiboExperiments/OpenJibo/scripts/bootstrap):
- `Discover-JiboHosts.ps1`
- `Generate-JiboDnsOverrides.ps1`
- `Test-OpenJiboRouting.ps1`
These are intentionally conservative helpers for discovery and verification, not destructive patch tools.
## TLS And Runtime Patching
Patching requirements will vary by device version and by where certificate validation is enforced.
Near-term guidance:
- record each patch location by software version
- prefer small, repeatable changes over ad hoc edits
- keep a versioned host inventory and patch checklist
- do not describe OTA as the primary bootstrap method until the hosted cloud is stable
## Smoke Test Goals
The first real-device smoke test should confirm:
- robot startup reaches the hosted cloud
- token issuance succeeds
- required sockets connect
- the robot can complete one simple turn
- update metadata calls do not break startup

View File

@@ -0,0 +1,70 @@
# Protocol Inventory
## Purpose
This document tracks the currently observed cloud surface area for Jibo and helps keep the .NET port aligned with real behavior captured by the Node prototype.
Confidence levels:
- `high`: observed in code and currently represented in the .NET scaffold
- `medium`: observed in the Node oracle and documented, but not fully ported yet
- `low`: expected or inferred, needs more robot validation
## Known Hosts
| Host | Purpose | Confidence | Notes |
| --- | --- | --- | --- |
| `api.jibo.com` | HTTPS API target for `X-Amz-Target` operations | high | Main request dispatch path in the Node prototype |
| `api-socket.jibo.com` | token-authenticated WebSocket path | medium | Node accepts tokenized connections and intentionally sends no greeting |
| `neo-hub.jibo.com` | listen and proactive WebSocket traffic | medium | Path-driven split between listen and `/v1/proactive` |
| `neohub.jibo.com` | likely alias/spelling variant to watch | low | Mentioned in docs; validate against real traffic |
## HTTP Dispatch Families
Observed from `open-jibo-link.js`:
| Service family | Example operations | Confidence | Current .NET status |
| --- | --- | --- | --- |
| `Account_*` | `CreateHubToken`, `CreateAccessToken`, `Login`, `Get` | high | initial dispatch implemented |
| `Notification_*` | `NewRobotToken` | high | initial dispatch implemented |
| `Loop_*` | `List`, `ListLoops` | medium | initial dispatch implemented |
| `Robot_*` | `GetRobot`, `UpdateRobot` | medium | initial dispatch implemented |
| `Update_*` | `ListUpdates`, `ListUpdatesFrom`, `GetUpdateFrom`, `CreateUpdate`, `RemoveUpdate` | medium | list/get scaffolding implemented |
| `Media_20160725` | `List`, `Get`, `Create`, `Remove` | medium | not yet ported |
| `Log_*` | `PutEvents`, `PutEventsAsync`, `PutBinaryAsync`, `PutAsrBinary` | medium | upload endpoints reserved; detailed handling pending |
| `Key_*` | `ShouldCreate`, `CreateSymmetricKey`, `GetRequest` | medium | pending |
| `Person_*` | `ListHolidays` | low | pending |
| `Backup_*` | `List` | low | pending |
## WebSocket Flows
| Host/path | Flow | Confidence | Current .NET status |
| --- | --- | --- | --- |
| `api-socket.jibo.com/{token}` | token-authenticated socket for API-side signaling | medium | stub endpoint implemented |
| `neo-hub.jibo.com/{listen-path}` | listen turn flow with JSON and binary audio traffic | medium | initial JSON flow implemented |
| `neo-hub.jibo.com/v1/proactive` | proactive connection flow | medium | stub endpoint implemented |
## Upload Paths
| Path | Purpose | Confidence | Current .NET status |
| --- | --- | --- | --- |
| `/upload/asr-binary` | async audio/log upload target | medium | placeholder endpoint accepted |
| `/upload/log-events` | async log upload target | medium | placeholder endpoint accepted |
| `/upload/log-binary` | async binary upload target | medium | placeholder endpoint accepted |
## First Core Revive Slice
The first .NET hosted milestone should fully support:
- `Account.CreateHubToken`
- `Notification.NewRobotToken`
- `Loop.List` and `Loop.ListLoops`
- `Robot.GetRobot`
- `Update.ListUpdates`, `Update.ListUpdatesFrom`, `Update.GetUpdateFrom`
- root probe and health checks
- basic listen/proactive WebSocket acceptance
- normalized turn and reply mapping for simple chat
## Fixture Source
Sanitized fixtures live under [src/Jibo.Cloud/node/fixtures](C:/Projects/JiboExperiments/OpenJibo/src/Jibo.Cloud/node/fixtures) and should be expanded as real traffic is captured.

View File

@@ -0,0 +1,24 @@
# Public Site Plan
## Goal
Stand up a small public site on `openjibo.com` that makes the project understandable in a few minutes.
## First Version Content
- project overview
- current status
- links to source repositories
- links to device bootstrap docs
- explanation of the hosted-cloud direction
- contribution/contact or waitlist path
## What The Site Should Not Pretend Yet
- zero-touch recovery
- complete parity with the original cloud
- public production readiness before device validation is repeatable
## Initial Repo Asset
A simple static site scaffold lives in [src/OpenJibo.Site](C:/Projects/JiboExperiments/OpenJibo/src/OpenJibo.Site).

View File

@@ -0,0 +1,33 @@
# Support Tiers
## Purpose
This document keeps the revival effort honest about what must work first, what can wait for parity, and what belongs to later modernization.
## Required For Core Revive
- hosted replacement cloud reachable through legacy Jibo host routing
- token, account, robot, and session bootstrap flows needed for startup
- basic WebSocket connectivity for listen and proactive channels
- minimal turn handling and a normalized `ResponsePlan` path
- Azure deployment foundation
- Azure SQL-backed persistence design
- bootstrap documentation for router, DNS, RCM, TLS patching, and smoke tests
## Optional For Parity
- broader `X-Amz-Target` family coverage
- richer media management
- more complete key and sharing flows
- higher-fidelity update metadata behavior
- more native-skill bridging and expression parity
- more complete per-version device behavior mapping
## Future Modernization
- OTA-first recovery for non-technical owners
- paid hosted plans, subscriptions, and donation flows
- deeper on-device modernization
- richer runtime orchestration and AI providers
- community plugin or skill ecosystem
- OS, bridge, and firmware modernization beyond hosted-cloud recovery

View File

@@ -0,0 +1,34 @@
param(
[string]$LogDirectory = ".",
[string[]]$KnownHosts = @(
"api.jibo.com",
"api-socket.jibo.com",
"neo-hub.jibo.com",
"neohub.jibo.com"
)
)
$resolved = foreach ($host in $KnownHosts) {
try {
$dns = Resolve-DnsName -Name $host -ErrorAction Stop
[pscustomobject]@{
Host = $host
Addresses = ($dns | Where-Object { $_.IPAddress } | Select-Object -ExpandProperty IPAddress) -join ", "
ObservedUtc = [DateTime]::UtcNow.ToString("o")
}
}
catch {
[pscustomobject]@{
Host = $host
Addresses = "<unresolved>"
ObservedUtc = [DateTime]::UtcNow.ToString("o")
}
}
}
$resolved | Tee-Object -Variable rows | Format-Table -AutoSize
$outputPath = Join-Path $LogDirectory "jibo-host-discovery.json"
$rows | ConvertTo-Json -Depth 4 | Set-Content -Path $outputPath
Write-Host "Saved discovery report to $outputPath"

View File

@@ -0,0 +1,23 @@
param(
[string]$TargetIp,
[string[]]$HostNames = @(
"api.jibo.com",
"api-socket.jibo.com",
"neo-hub.jibo.com",
"neohub.jibo.com"
)
)
if ([string]::IsNullOrWhiteSpace($TargetIp)) {
throw "TargetIp is required."
}
$entries = foreach ($host in $HostNames) {
[pscustomobject]@{
Host = $host
TargetIp = $TargetIp
HostsFileLine = "$TargetIp`t$host"
}
}
$entries | Format-Table -AutoSize

View File

@@ -0,0 +1,9 @@
# Bootstrap Scripts
These scripts support the first OpenJibo recovery path:
- discover which hosts the robot is trying to reach
- generate DNS override records for a controlled environment
- verify that the robot-facing domains resolve and answer as expected
They are intentionally non-destructive.

View File

@@ -0,0 +1,30 @@
param(
[string[]]$Hosts = @(
"https://api.jibo.com/health",
"https://api-socket.jibo.com/",
"https://neo-hub.jibo.com/v1/proactive"
)
)
foreach ($url in $Hosts) {
try {
$response = Invoke-WebRequest -Uri $url -Method Get -SkipCertificateCheck -ErrorAction Stop
[pscustomobject]@{
Url = $url
StatusCode = $response.StatusCode
Success = $true
}
}
catch {
$statusCode = $null
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
[pscustomobject]@{
Url = $url
StatusCode = $statusCode
Success = $false
}
}
}

View File

@@ -0,0 +1,32 @@
param(
[string]$BaseUrl = "http://localhost:5000"
)
$checks = @(
@{ Name = "Health"; Method = "GET"; Url = "$BaseUrl/health"; Headers = @{}; Body = $null },
@{ Name = "CreateHubToken"; Method = "POST"; Url = "$BaseUrl/"; Headers = @{ "X-Amz-Target" = "Account_20160715.CreateHubToken"; Host = "api.jibo.com" }; Body = "{}" },
@{ Name = "NewRobotToken"; Method = "POST"; Url = "$BaseUrl/"; Headers = @{ "X-Amz-Target" = "Notification_20160715.NewRobotToken"; Host = "api.jibo.com" }; Body = '{"deviceId":"my-robot-serial-number"}' }
)
foreach ($check in $checks) {
try {
$response = Invoke-WebRequest -Uri $check.Url -Method $check.Method -Headers $check.Headers -Body $check.Body -ContentType "application/json"
[pscustomobject]@{
Name = $check.Name
StatusCode = $response.StatusCode
Success = $true
}
}
catch {
$statusCode = $null
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
[pscustomobject]@{
Name = $check.Name
StatusCode = $statusCode
Success = $false
}
}
}

View File

@@ -0,0 +1,37 @@
param(
[string]$BaseUrl = "http://localhost:5000",
[string]$FixturePath
)
if ([string]::IsNullOrWhiteSpace($FixturePath)) {
throw "FixturePath is required."
}
$fixture = Get-Content $FixturePath | ConvertFrom-Json
$headers = @{}
foreach ($property in $fixture.headers.PSObject.Properties) {
$headers[$property.Name] = [string]$property.Value
}
if (-not $headers.ContainsKey("Host") -and $fixture.host) {
$headers["Host"] = [string]$fixture.host
}
$body = ""
if ($null -ne $fixture.body) {
$body = $fixture.body | ConvertTo-Json -Depth 10
}
$response = Invoke-WebRequest `
-Uri ($BaseUrl + [string]$fixture.path) `
-Method ([string]$fixture.method) `
-Headers $headers `
-Body $body `
-ContentType "application/json"
[pscustomobject]@{
Fixture = $FixturePath
StatusCode = $response.StatusCode
Body = $response.Content
}

View File

@@ -0,0 +1,8 @@
# Cloud Scripts
These scripts help exercise the new .NET hosted cloud locally.
- `Invoke-CloudSmoke.ps1`
Runs a few quick HTTP checks against a local OpenJibo cloud instance.
- `Invoke-ProtocolFixture.ps1`
Replays a sanitized HTTP fixture against a running local instance.

View File

@@ -1,97 +1,62 @@
# Jibo.Cloud
## Overview
## Summary
**Jibo.Cloud** is the replacement cloud layer for the OpenJibo project.
`Jibo.Cloud` is the replacement cloud layer for OpenJibo.
The original Jibo relied heavily on cloud services for core functionality including speech, skills, configuration, and identity. With the original cloud infrastructure no longer available, this project aims to recreate and eventually improve that layer so Jibo can function again for everyday users.
Its job is to restore the hosted services that physical Jibo devices still expect, while also becoming the bridge into a modern .NET runtime and future capabilities.
This is not just a mock or emulator. The goal is to build a functional, extensible cloud platform that can support both the original Jibo behaviors and new capabilities over time.
## Current Strategy
---
The project is deliberately split into two roles:
## Current Approach
- `node/`
Reverse-engineering oracle, discovery server, fixture source, and rapid protocol lab.
- `dotnet/`
Stable hosted implementation intended for Azure deployment and long-term maintenance.
The cloud layer is being developed in stages.
The Node server remains valuable, but it is no longer the target production architecture.
To move quickly and understand Jibos behavior, development started with a lightweight Node.js implementation that acts as a “fake cloud.” This allows rapid experimentation, endpoint discovery, and validation of how Jibo communicates.
## First Production Goal
As the system stabilizes, the implementation is being ported to **C# / .NET** for long-term maintainability, performance, and integration with hosted infrastructure.
The first milestone is a stable hosted cloud that can support:
---
- token and session issuance
- account and robot identity flows needed for startup
- required HTTPS `X-Amz-Target` operations
- required WebSocket listen and proactive flows
- basic media and update metadata handling
- normalized handoff into OpenJibo runtime contracts
## Architecture Direction
## Hosting Direction
The long-term vision for Jibo.Cloud is:
The hosted deployment target is Azure:
* Provide a stable replacement for Jibos original cloud endpoints
* Support secure communication (TLS) using a real hosted domain
* Act as a bridge between the physical robot and the OpenJibo runtime
* Enable new capabilities beyond the original Jibo feature set
- Azure App Service with WebSockets enabled
- Azure SQL as the system of record
- Azure Blob Storage for upload and update artifacts
- Azure Key Vault for secrets and certificates
- Application Insights for telemetry and diagnostics
### OTA Update Strategy
Human-facing entry points will live on domains such as:
One of the key strategies for restoring and extending Jibo functionality is leveraging its existing **OTA (over-the-air) update mechanism**.
- `openjibo.com`
- `openjibo.ai`
Rather than requiring users to manually modify their devices, Jibo.Cloud aims to:
Robot traffic may still arrive using legacy hostnames routed to the OpenJibo service.
* Deliver updates through Jibos native update flow
* Push new or modified skills directly to the robot
* Eventually enable delivery of larger system updates (including OpenJibo components)
## Recovery Strategy
This approach significantly lowers the barrier for non-technical users and creates a path toward a true “plug-and-play” recovery experience.
The first supported device path is:
---
## Hosting Strategy
The cloud service is intended to be hosted publicly using domains such as:
* `openjibo.com`
* `openjibo.ai`
Final domain structure is still being evaluated and may include subdomains similar to the original Jibo architecture.
---
## Project Structure
```plaintext id="6h2v1k"
Jibo.Cloud/
node/ # Initial prototype implementation (Node.js)
dotnet/ # Long-term implementation (C# / .NET)
```text
RCM + controlled DNS/TLS patching + hosted OpenJibo cloud
```
---
OTA remains important, but it is a later simplification layer after the hosted cloud is stable on real hardware.
## Goals
* Restore functionality to existing Jibo devices
* Provide an “easy button” for non-technical users
* Leverage OTA updates to simplify delivery and adoption
* Keep the system open and extensible for the community
* Build a foundation for future OpenJibo capabilities
---
## Status
This project is actively in development.
* Node.js prototype: in progress and functional for basic interactions
* C# implementation: planned and in progress
---
## Contributing
If you're interested in helping, exploring, or building on this work, contributions are welcome.
The goal is to make Jibo accessible again, not just for developers, but for anyone who owns one.
---
## Notes
This project is not affiliated with the original Jibo company. It is a community-driven effort to restore and extend the platform.
## Supporting Docs
- [Protocol inventory](C:/Projects/JiboExperiments/OpenJibo/docs/protocol-inventory.md)
- [Support tiers](C:/Projects/JiboExperiments/OpenJibo/docs/support-tiers.md)
- [Device bootstrap path](C:/Projects/JiboExperiments/OpenJibo/docs/device-bootstrap.md)

View File

@@ -1,204 +1,65 @@
# Jibo.Cloud.DotNet
## Overview
## Summary
**Jibo.Cloud.DotNet** is the long-term, production-focused implementation of the OpenJibo cloud layer.
`Jibo.Cloud.DotNet` is the stable hosted implementation of the OpenJibo cloud.
While the Node.js implementation was used to rapidly explore and validate Jibos cloud behavior, this project represents the future direction: a clean, maintainable, and scalable cloud platform built on **C# and .NET**.
This is the production-oriented path for restoring device connectivity and creating a foundation for future runtime, AI, and OTA work.
This is where the OpenJibo cloud becomes real.
## Architecture
---
The first implementation is a modular monolith:
## Vision
The goal of this project is not just to replicate the original Jibo cloud, but to build something better:
* A stable and secure cloud platform for Jibo devices
* A bridge between the physical robot and modern AI-driven systems
* A foundation for new capabilities beyond what Jibo originally supported
This is the backbone of the OpenJibo ecosystem.
---
## Design Principles
### 1. Clean Architecture
This implementation is designed around clear separation of concerns:
* Transport (HTTP, WebSocket)
* Application logic (routing, orchestration)
* Domain models (robot, session, capabilities)
* Integration layers (AI, storage, external services)
---
### 2. Compatibility First
The system will:
* Emulate required Jibo cloud endpoints
* Support existing device expectations
* Preserve OTA update compatibility
This ensures existing devices can reconnect without invasive changes.
---
### 3. Extensibility
The platform is being designed to support:
* New skills and capabilities
* AI-driven conversation and planning
* External integrations (APIs, services, tools)
* Multi-agent orchestration (future CoffeeBreak integration)
---
### 4. Cloud-Native Deployment
The target deployment model includes:
* Azure-hosted services
* Real domains (`openjibo.com`, `openjibo.ai`)
* Proper TLS / certificate chains
* Scalable service architecture
---
## Role in the OpenJibo Architecture
Jibo.Cloud sits between the robot and the runtime:
```plaintext id="l3tq9n"
Jibo Device
Jibo.Cloud (this project)
OpenJibo Runtime (.NET)
Capabilities / AI / Services
```text
Api -> Application -> Domain -> Infrastructure
```
Responsibilities include:
This keeps deployment simple while preserving clean boundaries.
* Handling device communication (HTTPS + WebSockets)
* Managing identity, sessions, and tokens
* Routing requests to runtime services
* Delivering OTA updates
* Acting as the central coordination layer
## Azure Direction
---
The target Azure footprint is:
## OTA Update Strategy
- Azure App Service for HTTP and WebSocket traffic
- Azure SQL for relational persistence
- Azure Blob Storage for uploads and update artifacts
- Azure Key Vault for secrets and certificates
- Application Insights for observability
A key part of this implementation is full support for Jibos OTA update mechanism.
Azure SQL is the primary system of record for:
This enables:
- accounts
- devices
- sessions
- update metadata
- host mappings
- bootstrap and provisioning records
* Delivery of updated skills
* Deployment of new capabilities
* Gradual rollout of OpenJibo runtime components
* A path toward restoring devices without manual intervention
## Compatibility Goal
The goal is to make recovery and updates feel native to the device.
The first compatibility milestone is `core revive`.
---
That means the .NET cloud should handle:
## Planned Features
- token and session issuance
- account and robot identity flows needed for startup
- core `X-Amz-Target` dispatch
- listen and proactive WebSocket paths
- basic media and update metadata responses
- handoff into normalized `TurnContext` and `ResponsePlan` contracts
This project will evolve to support:
## Relationship To The Node Prototype
### Core Platform
The Node server remains the discovery harness and fixture source.
* HTTPS API endpoints compatible with Jibo
* WebSocket communication layer
* Authentication and token services
* Device and user management
The .NET implementation should:
### Runtime Integration
- copy observed behavior where needed
- use fixtures captured from Node and real robots
- avoid speculative protocol design
* Conversation orchestration
* Capability routing
* Response planning
* Integration with OpenJibo runtime abstractions
## Current State
### AI Integration
* Speech-to-text and text-to-speech
* Intent recognition and planning
* External AI providers (pluggable)
### Update System
* OTA update orchestration
* Skill delivery pipeline
* Versioning and rollout control
---
## Project Structure
This folder will contain one or more .NET projects, likely including:
```plaintext id="2qk0a7"
dotnet/
Jibo.Cloud.Api/ # HTTP + WebSocket endpoints
Jibo.Cloud.Application/ # Application logic and orchestration
Jibo.Cloud.Domain/ # Core models and contracts
Jibo.Cloud.Infrastructure/ # External integrations (storage, AI, etc.)
```
Structure may evolve as the system matures.
---
## Relationship to Node Implementation
The Node.js implementation remains valuable as:
* A reference for endpoint behavior
* A rapid testing environment
* A discovery tool for protocol details
However, this .NET implementation is the intended long-term solution.
---
## Status
This project is in early development.
* Core abstractions are being defined
* Endpoint behavior is being mapped from the Node implementation
* Initial service scaffolding is planned
---
## Contributing
If you're interested in building the future of Jibo, this is the place to do it.
Areas where help is especially valuable:
* API design and endpoint mapping
* WebSocket protocol handling
* OTA update workflows
* AI and conversation systems
* Cloud infrastructure and deployment
---
## Notes
This project is part of the OpenJibo initiative and is not affiliated with the original Jibo company.
The mission is simple:
Bring Jibo back for everyone, technical or not.
Make him what he was meant to be.
Then we make him better.
This folder now contains the first hosted scaffold, not just a README.
The intent is to grow from a runnable dev monolith into the real Azure deployment target without abandoning the existing abstractions work.

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\Jibo.Cloud.Application\Jibo.Cloud.Application.csproj" />
<ProjectReference Include="..\Jibo.Cloud.Infrastructure\Jibo.Cloud.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\..\Jibo.Runtime.Abstractions\Jibo.Runtime.Abstractions.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,170 @@
using System.Net.WebSockets;
using System.Text;
using Jibo.Cloud.Application.Services;
using Jibo.Cloud.Domain.Models;
using Jibo.Cloud.Infrastructure.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenJiboCloud();
var app = builder.Build();
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
await next();
return;
}
var webSocketService = context.RequestServices.GetRequiredService<JiboWebSocketService>();
using var socket = await context.WebSockets.AcceptWebSocketAsync();
var kind = ResolveSocketKind(context.Request.Host.Host, context.Request.Path);
var token = ResolveToken(context.Request);
while (socket.State == WebSocketState.Open)
{
var received = await ReceiveAsync(socket, context.RequestAborted);
if (received.MessageType == WebSocketMessageType.Close)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", context.RequestAborted);
break;
}
var envelope = new WebSocketMessageEnvelope
{
ConnectionId = Guid.NewGuid().ToString("N"),
HostName = context.Request.Host.Host,
Path = context.Request.Path.Value ?? "/",
Kind = kind,
Token = token,
Text = received.MessageType == WebSocketMessageType.Text ? Encoding.UTF8.GetString(received.Buffer) : null,
Binary = received.MessageType == WebSocketMessageType.Binary ? received.Buffer : null
};
var replies = await webSocketService.HandleMessageAsync(envelope, context.RequestAborted);
foreach (var reply in replies)
{
if (string.IsNullOrWhiteSpace(reply.Text))
{
continue;
}
var payload = Encoding.UTF8.GetBytes(reply.Text);
await socket.SendAsync(payload, WebSocketMessageType.Text, true, context.RequestAborted);
}
}
});
app.MapGet("/health", () => Results.Json(new { ok = true, service = "OpenJibo Cloud Api" }));
app.MapMethods("/{**path}", ["GET", "POST", "PUT"], async (HttpContext context, JiboCloudProtocolService service, CancellationToken cancellationToken) =>
{
var envelope = await BuildEnvelopeAsync(context, cancellationToken);
var result = await service.DispatchAsync(envelope, cancellationToken);
context.Response.StatusCode = result.StatusCode;
context.Response.ContentType = result.ContentType;
foreach (var header in result.Headers)
{
context.Response.Headers[header.Key] = header.Value;
}
if (!string.IsNullOrEmpty(result.BodyText))
{
await context.Response.WriteAsync(result.BodyText, cancellationToken);
}
});
app.Run();
return;
static async Task<ReceivedSocketMessage> ReceiveAsync(WebSocket socket, CancellationToken cancellationToken)
{
var buffer = new byte[8192];
using var ms = new MemoryStream();
WebSocketReceiveResult result;
do
{
result = await socket.ReceiveAsync(buffer, cancellationToken);
ms.Write(buffer, 0, result.Count);
}
while (!result.EndOfMessage);
return new ReceivedSocketMessage(result.MessageType, ms.ToArray());
}
static async Task<ProtocolEnvelope> BuildEnvelopeAsync(HttpContext context, CancellationToken cancellationToken)
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
var bodyText = await reader.ReadToEndAsync(cancellationToken);
context.Request.Body.Position = 0;
var target = context.Request.Headers["X-Amz-Target"].ToString();
var targetParts = target.Split('.', 2, StringSplitOptions.RemoveEmptyEntries);
return new ProtocolEnvelope
{
RequestId = Guid.NewGuid().ToString("N"),
Transport = "http",
Method = context.Request.Method,
HostName = context.Request.Host.Host,
Path = context.Request.Path.Value ?? "/",
ServicePrefix = targetParts.Length > 0 ? targetParts[0] : null,
Operation = targetParts.Length > 1 ? targetParts[1] : null,
DeviceId = context.Request.Headers["X-Jibo-RobotId"].ToString(),
CorrelationId = context.TraceIdentifier,
FirmwareVersion = context.Request.Headers["X-OpenJibo-Firmware"].ToString(),
ApplicationVersion = context.Request.Headers["X-OpenJibo-AppVersion"].ToString(),
BodyText = bodyText,
Headers = context.Request.Headers.ToDictionary(pair => pair.Key, pair => pair.Value.ToString(), StringComparer.OrdinalIgnoreCase)
};
}
static string ResolveSocketKind(string host, PathString path)
{
if (host.Equals("api-socket.jibo.com", StringComparison.OrdinalIgnoreCase))
{
return "api-socket";
}
if (host.Equals("neo-hub.jibo.com", StringComparison.OrdinalIgnoreCase) &&
path.StartsWithSegments("/v1/proactive"))
{
return "neo-hub-proactive";
}
if (host.Equals("neo-hub.jibo.com", StringComparison.OrdinalIgnoreCase))
{
return "neo-hub-listen";
}
return "openjibo";
}
static string? ResolveToken(HttpRequest request)
{
var auth = request.Headers.Authorization.ToString();
if (auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
return auth["Bearer ".Length..].Trim();
}
var path = request.Path.Value;
if (!string.IsNullOrWhiteSpace(path) && path.Length > 1)
{
return path.Trim('/');
}
return null;
}
internal sealed record ReceivedSocketMessage(WebSocketMessageType MessageType, byte[] Buffer);

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"Jibo.Cloud.Api": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:24604;http://localhost:24605"
}
}
}

View File

@@ -0,0 +1,17 @@
using Jibo.Cloud.Domain.Models;
namespace Jibo.Cloud.Application.Abstractions;
public interface ICloudStateStore
{
AccountProfile GetAccount();
DeviceRegistration GetRobot();
DeviceRegistration GetOrCreateDevice(string deviceId, string? firmwareVersion, string? applicationVersion);
string IssueHubToken();
string IssueRobotToken(string deviceId);
CloudSession OpenSession(string kind, string? deviceId, string? token, string? hostName, string? path);
CloudSession? FindSessionByToken(string token);
IReadOnlyList<UpdateManifest> ListUpdates(string? subsystem = null, string? filter = null);
UpdateManifest GetUpdateFrom(string? subsystem, string? fromVersion, string? filter);
void UpdateRobot(DeviceRegistration registration);
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Jibo.Cloud.Domain\Jibo.Cloud.Domain.csproj" />
<ProjectReference Include="..\..\..\..\Jibo.Runtime.Abstractions\Jibo.Runtime.Abstractions.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,62 @@
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed class DemoConversationBroker : IConversationBroker
{
public Task<ResponsePlan> HandleTurnAsync(TurnContext turn, CancellationToken cancellationToken = default)
{
var transcript = (turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty).Trim();
var lowered = transcript.ToLowerInvariant();
var reply = transcript.Length == 0
? "I am listening."
: lowered.Contains("time")
? $"It is {DateTime.Now:hh:mm tt}."
: lowered.Contains("hello") || lowered.Contains("hi")
? "Hello from the OpenJibo cloud."
: lowered.Contains("joke")
? "Why did the robot bring a ladder? Because it wanted to reach the cloud."
: $"I heard: {transcript}";
var plan = new ResponsePlan
{
SessionId = turn.SessionId,
Status = ResponseStatus.Succeeded,
IntentName = lowered.Contains("joke") ? "joke" : lowered.Contains("time") ? "time" : "chat",
Topic = "conversation",
DeviceId = turn.DeviceId,
TargetHost = turn.HostName,
DebugRoute = "demo-broker",
Actions =
{
new SpeakAction
{
Sequence = 0,
Text = reply,
Voice = "griffin"
},
new ListenAction
{
Sequence = 1,
Timeout = TimeSpan.FromSeconds(12),
Mode = "follow-up"
}
},
FollowUp = new FollowUpPolicy
{
KeepMicOpen = true,
Timeout = TimeSpan.FromSeconds(12),
ExpectedTopic = "conversation"
},
ProtocolMetadata = new Dictionary<string, object?>
{
["host"] = turn.HostName,
["service"] = turn.ProtocolService,
["operation"] = turn.ProtocolOperation
}
};
return Task.FromResult(plan);
}
}

View File

@@ -0,0 +1,251 @@
using System.Text.Json;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
namespace Jibo.Cloud.Application.Services;
public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
{
private static readonly string[] AcceptedHosts =
[
"api.jibo.com",
"openjibo.com",
"openjibo.ai",
"localhost"
];
public Task<ProtocolDispatchResult> DispatchAsync(ProtocolEnvelope envelope, CancellationToken cancellationToken = default)
{
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
envelope.Path == "/" &&
string.IsNullOrWhiteSpace(envelope.ServicePrefix))
{
return Task.FromResult(ProtocolDispatchResult.NoContent());
}
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
envelope.Path.Equals("/health", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(ProtocolDispatchResult.Ok(new { ok = true, host = envelope.HostName }));
}
if (envelope.Method.Equals("PUT", StringComparison.OrdinalIgnoreCase) &&
(envelope.Path.Equals("/upload/asr-binary", StringComparison.OrdinalIgnoreCase) ||
envelope.Path.Equals("/upload/log-events", StringComparison.OrdinalIgnoreCase) ||
envelope.Path.Equals("/upload/log-binary", StringComparison.OrdinalIgnoreCase)))
{
return Task.FromResult(ProtocolDispatchResult.Raw(200, string.Empty));
}
if (!AcceptedHosts.Contains(envelope.HostName, StringComparer.OrdinalIgnoreCase))
{
return Task.FromResult(ProtocolDispatchResult.Ok(new
{
ok = true,
accepted = false,
host = envelope.HostName
}));
}
var servicePrefix = envelope.ServicePrefix ?? string.Empty;
var operation = envelope.Operation ?? string.Empty;
if (servicePrefix.StartsWith("Account_", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(HandleAccount(operation));
}
if (servicePrefix.StartsWith("Notification_", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(HandleNotification(operation, envelope));
}
if (servicePrefix.StartsWith("Loop_", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(HandleLoop(operation));
}
if (servicePrefix.StartsWith("Robot_", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(HandleRobot(operation, envelope));
}
if (servicePrefix.StartsWith("Update_", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(HandleUpdate(operation, envelope));
}
return Task.FromResult(ProtocolDispatchResult.Ok(new
{
ok = true,
service = servicePrefix,
operation
}));
}
private ProtocolDispatchResult HandleAccount(string operation)
{
var account = stateStore.GetAccount();
return operation switch
{
"CreateHubToken" => ProtocolDispatchResult.Ok(new
{
token = stateStore.IssueHubToken(),
expires = DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeMilliseconds()
}),
"CreateAccessToken" => ProtocolDispatchResult.Ok(new
{
token = $"access-{account.AccountId}-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}",
expires = DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeMilliseconds()
}),
"Get" => ProtocolDispatchResult.Ok(new[]
{
new
{
id = account.AccountId,
email = account.Email,
firstName = account.FirstName,
lastName = account.LastName,
accessKeyId = account.AccessKeyId,
secretAccessKey = account.SecretAccessKey
}
}),
_ => ProtocolDispatchResult.Ok(new
{
id = account.AccountId,
email = account.Email,
firstName = account.FirstName,
lastName = account.LastName
})
};
}
private ProtocolDispatchResult HandleNotification(string operation, ProtocolEnvelope envelope)
{
if (!operation.Equals("NewRobotToken", StringComparison.OrdinalIgnoreCase))
{
return ProtocolDispatchResult.Ok(new { ok = true, operation });
}
var body = envelope.TryParseBody();
var deviceId = envelope.DeviceId
?? ReadString(body, "deviceId")
?? ReadString(body, "serialNumber")
?? "unknown-device";
stateStore.GetOrCreateDevice(deviceId, envelope.FirmwareVersion, envelope.ApplicationVersion);
return ProtocolDispatchResult.Ok(new
{
token = stateStore.IssueRobotToken(deviceId)
});
}
private ProtocolDispatchResult HandleLoop(string operation)
{
if (operation is not ("List" or "ListLoops"))
{
return ProtocolDispatchResult.Ok(Array.Empty<object>());
}
var robot = stateStore.GetRobot();
var account = stateStore.GetAccount();
return ProtocolDispatchResult.Ok(new[]
{
new
{
id = "openjibo-default-loop",
name = "OpenJibo Default Loop",
owner = account.AccountId,
robot = robot.RobotId,
robotFriendlyId = robot.DeviceId,
members = Array.Empty<object>(),
isSuspended = false,
created = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
updated = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}
});
}
private ProtocolDispatchResult HandleRobot(string operation, ProtocolEnvelope envelope)
{
var robot = stateStore.GetRobot();
if (operation.Equals("UpdateRobot", StringComparison.OrdinalIgnoreCase))
{
var updated = new DeviceRegistration
{
DeviceId = robot.DeviceId,
RobotId = robot.RobotId,
FriendlyName = robot.FriendlyName,
FirmwareVersion = envelope.FirmwareVersion ?? robot.FirmwareVersion,
ApplicationVersion = envelope.ApplicationVersion ?? robot.ApplicationVersion,
HostMappings = robot.HostMappings
};
stateStore.UpdateRobot(updated);
robot = updated;
}
return ProtocolDispatchResult.Ok(new
{
id = robot.RobotId,
friendlyId = robot.DeviceId,
name = robot.FriendlyName,
firmwareVersion = robot.FirmwareVersion,
applicationVersion = robot.ApplicationVersion
});
}
private ProtocolDispatchResult HandleUpdate(string operation, ProtocolEnvelope envelope)
{
var body = envelope.TryParseBody();
var subsystem = ReadString(body, "subsystem");
var filter = ReadString(body, "filter");
var fromVersion = ReadString(body, "fromVersion");
return operation switch
{
"ListUpdates" => ProtocolDispatchResult.Ok(stateStore.ListUpdates(subsystem, filter).Select(MapUpdate).ToArray()),
"ListUpdatesFrom" => ProtocolDispatchResult.Ok(stateStore.ListUpdates(subsystem, filter).Select(MapUpdate).ToArray()),
"GetUpdateFrom" => ProtocolDispatchResult.Ok(MapUpdate(stateStore.GetUpdateFrom(subsystem, fromVersion, filter))),
_ => ProtocolDispatchResult.Ok(Array.Empty<object>())
};
}
private static object MapUpdate(UpdateManifest update)
{
return new
{
_id = update.UpdateId,
created = update.CreatedUtc.ToUnixTimeMilliseconds(),
fromVersion = update.FromVersion,
toVersion = update.ToVersion,
changes = update.Changes,
url = update.Url,
shaHash = update.ShaHash,
length = update.Length,
subsystem = update.Subsystem,
filter = update.Filter
};
}
private static string? ReadString(JsonElement? element, string propertyName)
{
if (element is null)
{
return null;
}
if (!element.Value.TryGetProperty(propertyName, out var property))
{
return null;
}
return property.ValueKind == JsonValueKind.String
? property.GetString()
: property.ToString();
}
}

View File

@@ -0,0 +1,91 @@
using System.Text.Json;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed class JiboWebSocketService(
ICloudStateStore stateStore,
ProtocolToTurnContextMapper turnContextMapper,
IConversationBroker conversationBroker,
ResponsePlanToSocketMessagesMapper replyMapper)
{
public async Task<IReadOnlyList<WebSocketReply>> HandleMessageAsync(WebSocketMessageEnvelope envelope, CancellationToken cancellationToken = default)
{
var session = stateStore.FindSessionByToken(envelope.Token ?? string.Empty) ??
stateStore.OpenSession(envelope.Kind, null, envelope.Token, envelope.HostName, envelope.Path);
if (envelope.IsBinary)
{
return
[
new WebSocketReply
{
Text = JsonSerializer.Serialize(new
{
type = "OPENJIBO_AUDIO_RECEIVED",
data = new
{
bytes = envelope.Binary?.Length ?? 0,
sessionId = session.SessionId
}
})
}
];
}
var parsedType = ReadMessageType(envelope.Text);
session.LastListenType = parsedType;
if (parsedType is "LISTEN" or "CLIENT_NLU" or "CLIENT_ASR")
{
var turn = turnContextMapper.MapListenMessage(envelope, session);
var plan = await conversationBroker.HandleTurnAsync(turn, cancellationToken);
return replyMapper.Map(plan).Select(text => new WebSocketReply
{
Text = text
}).ToArray();
}
return
[
new WebSocketReply
{
Text = JsonSerializer.Serialize(new
{
type = "OPENJIBO_ACK",
data = new
{
messageType = parsedType,
sessionId = session.SessionId
}
})
}
];
}
private static string ReadMessageType(string? text)
{
if (string.IsNullOrWhiteSpace(text))
{
return "UNKNOWN";
}
try
{
using var document = JsonDocument.Parse(text);
if (document.RootElement.TryGetProperty("type", out var type) && type.ValueKind == JsonValueKind.String)
{
return type.GetString() ?? "UNKNOWN";
}
}
catch
{
return "TEXT";
}
return "UNKNOWN";
}
}

View File

@@ -0,0 +1,63 @@
using System.Text.Json;
using Jibo.Cloud.Domain.Models;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed class ProtocolToTurnContextMapper
{
public TurnContext MapListenMessage(WebSocketMessageEnvelope envelope, CloudSession session)
{
var text = ExtractTranscript(envelope.Text);
return new TurnContext
{
SessionId = session.SessionId,
InputMode = session.LastListenType == "follow-up" ? TurnInputMode.FollowUp : TurnInputMode.DirectText,
SourceKind = TurnSourceKind.Api,
RawTranscript = text,
NormalizedTranscript = text?.Trim(),
DeviceId = session.DeviceId,
HostName = envelope.HostName,
RequestId = envelope.ConnectionId,
ProtocolService = "neo-hub",
ProtocolOperation = "listen",
FirmwareVersion = session.Metadata.TryGetValue("firmwareVersion", out var firmwareVersion) ? firmwareVersion as string : null,
ApplicationVersion = session.Metadata.TryGetValue("applicationVersion", out var applicationVersion) ? applicationVersion as string : null,
IsFollowUpEligible = true
};
}
private static string? ExtractTranscript(string? text)
{
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
try
{
using var document = JsonDocument.Parse(text);
var root = document.RootElement;
if (root.TryGetProperty("data", out var data))
{
if (data.TryGetProperty("text", out var transcript) && transcript.ValueKind == JsonValueKind.String)
{
return transcript.GetString();
}
if (data.TryGetProperty("intent", out var intent) && intent.ValueKind == JsonValueKind.String)
{
return intent.GetString();
}
}
}
catch
{
return text;
}
return text;
}
}

View File

@@ -0,0 +1,39 @@
using System.Text.Json;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed class ResponsePlanToSocketMessagesMapper
{
public IReadOnlyList<string> Map(ResponsePlan plan)
{
var speak = plan.Actions.OfType<SpeakAction>().FirstOrDefault();
var messages = new List<string>();
if (speak is not null)
{
messages.Add(JsonSerializer.Serialize(new
{
type = "OPENJIBO_RESPONSE",
data = new
{
intent = plan.IntentName,
text = speak.Text,
followUpOpen = plan.FollowUp.KeepMicOpen,
timeoutMs = (int)plan.FollowUp.Timeout.TotalMilliseconds
}
}));
}
messages.Add(JsonSerializer.Serialize(new
{
type = "EOS",
data = new
{
sessionId = plan.SessionId
}
}));
return messages;
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,11 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class AccountProfile
{
public string AccountId { get; init; } = "usr_openjibo_owner";
public string Email { get; init; } = "owner@openjibo.local";
public string FirstName { get; init; } = "Jibo";
public string LastName { get; init; } = "Owner";
public string AccessKeyId { get; init; } = "openjibo-access-key";
public string SecretAccessKey { get; init; } = "openjibo-secret-access-key";
}

View File

@@ -0,0 +1,11 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class CapturedExchange
{
public string CaptureId { get; init; } = Guid.NewGuid().ToString("N");
public DateTimeOffset CapturedUtc { get; init; } = DateTimeOffset.UtcNow;
public ProtocolEnvelope Request { get; init; } = new();
public ProtocolDispatchResult Response { get; init; } = ProtocolDispatchResult.Ok();
public string Confidence { get; init; } = "observed";
public IDictionary<string, string> Tags { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

View File

@@ -0,0 +1,17 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class CloudSession
{
public string SessionId { get; init; } = Guid.NewGuid().ToString("N");
public string Kind { get; init; } = "http";
public string? AccountId { get; init; }
public string? DeviceId { get; init; }
public string? Token { get; init; }
public string? HostName { get; init; }
public string? Path { get; init; }
public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow;
public DateTimeOffset LastSeenUtc { get; set; } = DateTimeOffset.UtcNow;
public string? LastListenType { get; set; }
public string? LastTranscript { get; set; }
public IDictionary<string, object?> Metadata { get; init; } = new Dictionary<string, object?>();
}

View File

@@ -0,0 +1,12 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class DeviceRegistration
{
public string DeviceId { get; init; } = "my-robot-serial-number";
public string RobotId { get; init; } = "my-robot-name";
public string FriendlyName { get; init; } = "OpenJibo Dev Robot";
public string? FirmwareVersion { get; init; }
public string? ApplicationVersion { get; init; }
public bool IsActive { get; init; } = true;
public IDictionary<string, string> HostMappings { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

View File

@@ -0,0 +1,40 @@
using System.Text.Json;
namespace Jibo.Cloud.Domain.Models;
public sealed class ProtocolDispatchResult
{
public int StatusCode { get; init; } = 200;
public string ContentType { get; init; } = "application/x-amz-json-1.1";
public string BodyText { get; init; } = "{}";
public IDictionary<string, string> Headers { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public static ProtocolDispatchResult Ok(object? body = null)
{
return new ProtocolDispatchResult
{
StatusCode = 200,
BodyText = JsonSerializer.Serialize(body ?? new { ok = true })
};
}
public static ProtocolDispatchResult NoContent()
{
return new ProtocolDispatchResult
{
StatusCode = 204,
BodyText = string.Empty,
ContentType = "text/plain"
};
}
public static ProtocolDispatchResult Raw(int statusCode, string bodyText, string contentType = "text/plain")
{
return new ProtocolDispatchResult
{
StatusCode = statusCode,
BodyText = bodyText,
ContentType = contentType
};
}
}

View File

@@ -0,0 +1,39 @@
using System.Text.Json;
namespace Jibo.Cloud.Domain.Models;
public sealed class ProtocolEnvelope
{
public string RequestId { get; init; } = Guid.NewGuid().ToString("N");
public DateTimeOffset ReceivedUtc { get; init; } = DateTimeOffset.UtcNow;
public string Transport { get; init; } = "http";
public string Method { get; init; } = "POST";
public string HostName { get; init; } = "api.jibo.com";
public string Path { get; init; } = "/";
public string? ServicePrefix { get; init; }
public string? Operation { get; init; }
public string? DeviceId { get; init; }
public string? CorrelationId { get; init; }
public string? FirmwareVersion { get; init; }
public string? ApplicationVersion { get; init; }
public string BodyText { get; init; } = string.Empty;
public IDictionary<string, string> Headers { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public JsonElement? TryParseBody()
{
if (string.IsNullOrWhiteSpace(BodyText))
{
return null;
}
try
{
using var document = JsonDocument.Parse(BodyText);
return document.RootElement.Clone();
}
catch
{
return null;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class UpdateManifest
{
public string UpdateId { get; init; } = "noop-update";
public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow;
public string FromVersion { get; init; } = "unknown";
public string ToVersion { get; init; } = "unknown";
public string Changes { get; init; } = "No update available";
public string Url { get; init; } = "https://api.jibo.com/update/noop";
public string ShaHash { get; init; } = "noop";
public long Length { get; init; }
public string Subsystem { get; init; } = "robot";
public string? Filter { get; init; }
}

View File

@@ -0,0 +1,10 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class UploadReference
{
public string UploadId { get; init; } = Guid.NewGuid().ToString("N");
public string Path { get; init; } = string.Empty;
public string ContentType { get; init; } = "application/octet-stream";
public long Length { get; init; }
public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow;
}

View File

@@ -0,0 +1,13 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class WebSocketMessageEnvelope
{
public string ConnectionId { get; init; } = Guid.NewGuid().ToString("N");
public string HostName { get; init; } = string.Empty;
public string Path { get; init; } = "/";
public string Kind { get; init; } = "unknown";
public string? Token { get; init; }
public string? Text { get; init; }
public byte[]? Binary { get; init; }
public bool IsBinary => Binary is { Length: > 0 };
}

View File

@@ -0,0 +1,7 @@
namespace Jibo.Cloud.Domain.Models;
public sealed class WebSocketReply
{
public string? Text { get; init; }
public bool Close { get; init; }
}

View File

@@ -0,0 +1,22 @@
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Application.Services;
using Jibo.Cloud.Infrastructure.Persistence;
using Jibo.Runtime.Abstractions;
using Microsoft.Extensions.DependencyInjection;
namespace Jibo.Cloud.Infrastructure.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddOpenJiboCloud(this IServiceCollection services)
{
services.AddSingleton<ICloudStateStore, InMemoryCloudStateStore>();
services.AddSingleton<IConversationBroker, DemoConversationBroker>();
services.AddSingleton<ProtocolToTurnContextMapper>();
services.AddSingleton<ResponsePlanToSocketMessagesMapper>();
services.AddSingleton<JiboCloudProtocolService>();
services.AddSingleton<JiboWebSocketService>();
return services;
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Jibo.Cloud.Application\Jibo.Cloud.Application.csproj" />
<ProjectReference Include="..\Jibo.Cloud.Domain\Jibo.Cloud.Domain.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,150 @@
using System.Collections.Concurrent;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
namespace Jibo.Cloud.Infrastructure.Persistence;
public sealed class InMemoryCloudStateStore : ICloudStateStore
{
private readonly AccountProfile _account = new();
private readonly ConcurrentDictionary<string, DeviceRegistration> _devices = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, CloudSession> _sessionsByToken = new(StringComparer.OrdinalIgnoreCase);
private readonly List<UpdateManifest> _updates;
private DeviceRegistration _robot;
public InMemoryCloudStateStore()
{
_robot = new DeviceRegistration
{
HostMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["api.jibo.com"] = "openjibo.com",
["api-socket.jibo.com"] = "openjibo.com",
["neo-hub.jibo.com"] = "openjibo.com",
["neohub.jibo.com"] = "openjibo.com"
}
};
_devices[_robot.DeviceId] = _robot;
_updates =
[
new UpdateManifest
{
UpdateId = "noop-update-robot",
FromVersion = "unknown",
ToVersion = "unknown",
Changes = "No update available",
Url = "https://api.jibo.com/update/noop",
ShaHash = "noop",
Subsystem = "robot"
}
];
}
public AccountProfile GetAccount() => _account;
public DeviceRegistration GetRobot() => _robot;
public DeviceRegistration GetOrCreateDevice(string deviceId, string? firmwareVersion, string? applicationVersion)
{
return _devices.AddOrUpdate(
deviceId,
_ => new DeviceRegistration
{
DeviceId = deviceId,
RobotId = $"robot-{deviceId}",
FriendlyName = "OpenJibo Registered Robot",
FirmwareVersion = firmwareVersion,
ApplicationVersion = applicationVersion
},
(_, current) => new DeviceRegistration
{
DeviceId = current.DeviceId,
RobotId = current.RobotId,
FriendlyName = current.FriendlyName,
FirmwareVersion = firmwareVersion ?? current.FirmwareVersion,
ApplicationVersion = applicationVersion ?? current.ApplicationVersion,
HostMappings = current.HostMappings
});
}
public string IssueHubToken()
{
var token = $"hub-{_account.AccountId}-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";
_sessionsByToken[token] = new CloudSession
{
Kind = "hub",
AccountId = _account.AccountId,
Token = token,
DeviceId = _robot.DeviceId
};
return token;
}
public string IssueRobotToken(string deviceId)
{
var token = $"token-{deviceId}-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";
_sessionsByToken[token] = new CloudSession
{
Kind = "robot",
AccountId = _account.AccountId,
Token = token,
DeviceId = deviceId
};
return token;
}
public CloudSession OpenSession(string kind, string? deviceId, string? token, string? hostName, string? path)
{
var session = new CloudSession
{
Kind = kind,
AccountId = _account.AccountId,
DeviceId = deviceId ?? _robot.DeviceId,
Token = token,
HostName = hostName,
Path = path
};
if (!string.IsNullOrWhiteSpace(token))
{
_sessionsByToken[token] = session;
}
return session;
}
public CloudSession? FindSessionByToken(string token)
{
return _sessionsByToken.GetValueOrDefault(token);
}
public IReadOnlyList<UpdateManifest> ListUpdates(string? subsystem = null, string? filter = null)
{
return _updates
.Where(update => subsystem is null || update.Subsystem.Equals(subsystem, StringComparison.OrdinalIgnoreCase))
.Where(update => filter is null || string.Equals(update.Filter, filter, StringComparison.OrdinalIgnoreCase))
.ToArray();
}
public UpdateManifest GetUpdateFrom(string? subsystem, string? fromVersion, string? filter)
{
return ListUpdates(subsystem, filter).FirstOrDefault() ?? new UpdateManifest
{
UpdateId = $"noop-update-{subsystem ?? "robot"}-{fromVersion ?? "unknown"}",
FromVersion = fromVersion ?? "unknown",
ToVersion = fromVersion ?? "unknown",
Filter = filter,
Subsystem = subsystem ?? "robot"
};
}
public void UpdateRobot(DeviceRegistration registration)
{
_robot = registration;
_devices[registration.DeviceId] = registration;
}
}

View File

@@ -1,253 +1,67 @@
# Jibo.Cloud.Node
## Overview
## Role
This folder contains the original **Node.js prototype** for the OpenJibo cloud layer.
This folder contains the protocol oracle for OpenJibo.
This implementation started as a fast way to stand up a working "fake cloud" so Jibo could begin talking to a replacement backend again. It has been used to map behavior, discover endpoints, observe payloads, and validate real interactions with a live robot.
The Node server is still the best place to:
This is the experimental proving ground for the broader `Jibo.Cloud` effort.
- observe how a real Jibo talks to the cloud
- discover endpoints, payloads, headers, and timing
- validate hypotheses quickly against hardware
- produce sanitized fixtures for the .NET port
---
It is no longer the intended production runtime.
## Purpose
## What Stays Here
The goals of this Node implementation are:
- reverse-engineering work
- protocol discovery
- capture and replay fixture generation
- narrow experiments needed to unblock the .NET hosted cloud
* Reverse engineer Jibo cloud behavior
* Recreate enough of the original cloud to restore functionality
* Capture real request and response data
* Prototype OTA update delivery paths
* Validate speech, jokes, and interaction flows
* Serve as a reference for the C# / .NET implementation
## What Moves Out
---
- production hosting concerns
- long-term storage and deployment architecture
- hardened runtime orchestration
- Azure-facing operational concerns
Those belong in `../dotnet`.
## Current Capabilities
This server currently supports:
The prototype already demonstrates a meaningful slice of the old cloud:
* HTTPS API handling with `X-Amz-Target` routing
* WebSocket connections for Jibo communication
* Token/session handling (prototype-level)
* Account and robot identity flows (mocked)
* Media, loop, and key endpoints (partial)
* OTA update endpoints (in progress)
* Speech pipeline using:
- HTTPS API routing by `X-Amz-Target`
- WebSocket handling for Jibo communication paths
- token and session bootstrapping
- account, loop, robot, key, media, and update operations
- ASR-oriented audio handling with `ffmpeg` and `whisper.cpp`
- synthetic turn handling for greetings, time, jokes, and basic chat
- extensive request and WebSocket logging for protocol discovery
* Ogg normalization
* ffmpeg conversion
* whisper.cpp transcription
* Basic intent handling (jokes, greetings, time, etc.)
* Skill action responses (speech + simple animations)
## Fixture Workflow
---
Use this implementation as the source of truth for replay fixtures.
## package.json
- capture observed request and response pairs
- sanitize account ids, emails, tokens, hostnames, and secrets
- save fixtures under [fixtures](C:/Projects/JiboExperiments/OpenJibo/src/Jibo.Cloud/node/fixtures)
- use those fixtures to drive the .NET compatibility port
This project uses a minimal Node setup:
## Real Device Reality
```json
:contentReference[oaicite:0]{index=0}
```
Today, a real Jibo still needs a controlled environment to talk to this server.
Install dependencies with:
That means:
```bash
npm install
```
- controlled router or DNS interception
- redirection of legacy Jibo hosts
- RCM/device modification for TLS or host validation where required
---
That reality is documented in [device-bootstrap.md](C:/Projects/JiboExperiments/OpenJibo/docs/device-bootstrap.md). OTA is a future improvement path, not the current bootstrap dependency.
## Running the Server
### Requirements
* Node.js
* `ws` package
* `ffmpeg`
* `whisper.cpp` + model
* TLS certificate and key
### TLS Files
Place in working directory:
```plaintext
cert.pem
key.pem
```
### Environment Variables (optional)
```bash
FFMPEG_BIN=/usr/bin/ffmpeg
WHISPER_CPP_BIN=/path/to/whisper-cli
WHISPER_MODEL=/path/to/model.bin
```
### Start
```bash
node open-jibo-link.js
```
Server listens on:
```
https://0.0.0.0:443
```
---
## Getting Jibo to Talk to This Server
This is the part that matters most.
Jibo does not naturally connect to a custom server, so you need to control its network environment and TLS behavior.
### Network Setup (Mango Travel Router)
A simple and effective approach:
* Use a **Mango travel router (~$30)**
* Connect Jibo to this network
* Block outbound internet access
* Force DNS resolution to your server
### DNS Control
On the router:
* Map the following domains to your server:
```
api.jibo.com
api-socket.jibo.com
neohub.jibo.com
```
* Intercept Google DNS requests (hardcoded in Jibo):
```
8.8.8.8
8.8.4.4
```
These must be redirected or blocked so Jibo cannot bypass your DNS.
---
## TLS / Certificate Handling
Jibo expects valid TLS and will reject unknown/self-signed certificates by default.
Because of the older Node runtime and native binaries used on Jibo, this cannot be fully bypassed at the system level.
### Required Changes
You must modify Jibos runtime to disable certificate validation:
* Update Node-based modules to allow self-signed certs
* Modify any code using:
```js
rejectUnauthorized: true
```
Change to:
```js
rejectUnauthorized: false
```
* Patch any native or binary services that enforce TLS validation
* Set environment variables where possible to disable strict SSL
This typically requires:
* RCM access to Jibo
* Direct file modification on the device
---
## WiFi Setup (QR Code)
Jibo connects to WiFi using a QR code.
You can generate one here:
https://kevinblog.sytes.net/Jibo/WifiGenerator/
This allows you to easily connect Jibo to your controlled network (such as the Mango router).
---
## OTA Update Direction
One of the most important long-term strategies is leveraging Jibos built-in OTA update mechanism.
This server already includes update-related endpoints to support:
* Skill delivery
* Update metadata handling
* Future system updates
The goal is to eventually:
* Deliver updates without requiring device hacking
* Push new functionality directly through Jibos native update flow
* Provide a simple recovery path for non-technical users
---
## Logging and Observability
This server is heavily instrumented for debugging and discovery.
It logs:
* Incoming requests and headers
* Target routing (`X-Amz-Target`)
* Responses
* WebSocket activity
* Audio processing stages
* Transcription results
This makes it both a working cloud stub and a reverse engineering tool.
---
## Limitations
This is still a prototype:
* Many endpoints are partial or mocked
* No persistent storage
* Security is minimal
* Configuration is partially hardcoded
* Designed for experimentation, not production
---
## Future Direction
This implementation will evolve into:
* A full C# / .NET cloud service
* Azure-hosted infrastructure
* Trusted SSL with real domains
* Clean OTA update pipeline
* Integration with OpenJibo runtime
The Node version will remain as:
* A reference implementation
* A fast experimentation environment
---
## Notes
This project is part of the OpenJibo effort and is not affiliated with the original Jibo company.
## Next Job
The main job of this folder now is to keep the .NET port honest.

View File

@@ -0,0 +1,10 @@
# Node Fixtures
These fixtures are sanitized captures derived from the Node protocol oracle and are intended to seed compatibility testing for the .NET port.
Current fixture groups:
- `http/`
Basic `X-Amz-Target` request and response examples for startup flows.
Expand this folder whenever new robot traffic is captured and cleaned.

View File

@@ -0,0 +1,9 @@
{
"host": "api.jibo.com",
"method": "POST",
"path": "/",
"headers": {
"x-amz-target": "Account_20160715.CreateHubToken"
},
"body": {}
}

View File

@@ -0,0 +1,7 @@
{
"statusCode": 200,
"body": {
"token": "hub-usr_test_001-1712700000000",
"expires": 1712703600000
}
}

View File

@@ -0,0 +1,11 @@
{
"host": "api.jibo.com",
"method": "POST",
"path": "/",
"headers": {
"x-amz-target": "Notification_20160715.NewRobotToken"
},
"body": {
"deviceId": "my-robot-serial-number"
}
}

View File

@@ -0,0 +1,6 @@
{
"statusCode": 200,
"body": {
"token": "token-my-robot-serial-number-1712700000000"
}
}

View File

@@ -8,6 +8,8 @@ public sealed class ResponsePlan
public string? IntentName { get; init; }
public string? Topic { get; init; }
public string? DeviceId { get; init; }
public string? TargetHost { get; init; }
public IList<PlanAction> Actions { get; init; } = new List<PlanAction>();
public FollowUpPolicy FollowUp { get; init; } = FollowUpPolicy.None;
@@ -15,4 +17,5 @@ public sealed class ResponsePlan
public string? DebugRoute { get; init; }
public IDictionary<string, object?> Diagnostics { get; init; } = new Dictionary<string, object?>();
}
public IDictionary<string, object?> ProtocolMetadata { get; init; } = new Dictionary<string, object?>();
}

View File

@@ -9,6 +9,13 @@ public sealed class RobotEvent
public string? SessionId { get; init; }
public string? Transcript { get; init; }
public string? WakePhrase { get; init; }
public string? DeviceId { get; init; }
public string? HostName { get; init; }
public string? RequestId { get; init; }
public string? ProtocolService { get; init; }
public string? ProtocolOperation { get; init; }
public string? FirmwareVersion { get; init; }
public string? ApplicationVersion { get; init; }
public IDictionary<string, object?> Payload { get; init; } = new Dictionary<string, object?>();
}
}

View File

@@ -12,10 +12,17 @@ public sealed class TurnContext
public string? WakePhrase { get; init; }
public string? RawTranscript { get; init; }
public string? NormalizedTranscript { get; init; }
public string? DeviceId { get; init; }
public string? HostName { get; init; }
public string? RequestId { get; init; }
public string? ProtocolService { get; init; }
public string? ProtocolOperation { get; init; }
public string? FirmwareVersion { get; init; }
public string? ApplicationVersion { get; init; }
public string? Locale { get; init; } = "en-US";
public string? TimeZone { get; init; }
public bool IsFollowUpEligible { get; init; }
public IDictionary<string, object?> Attributes { get; init; } = new Dictionary<string, object?>();
}
}

View File

@@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenJibo</title>
<link rel="stylesheet" href="site.css">
</head>
<body>
<main class="shell">
<section class="hero">
<p class="eyebrow">OpenJibo</p>
<h1>Bringing Jibo back with a stable open cloud.</h1>
<p class="lede">
OpenJibo is rebuilding the hosted layer Jibo still expects, then using that foothold
to modernize the platform step by step.
</p>
<div class="actions">
<a href="https://github.com/" class="button primary">Source Repos</a>
<a href="../../docs/device-bootstrap.md" class="button secondary">Bootstrap Docs</a>
</div>
</section>
<section class="panel">
<h2>Current Direction</h2>
<p>
The first milestone is a stable Azure-hosted replacement cloud. We support real robots
first through controlled DNS plus targeted device patching, then smooth the path later with OTA.
</p>
</section>
<section class="grid">
<article class="card">
<h3>Cloud First</h3>
<p>Replace the missing hosted services before attempting deeper on-device modernization.</p>
</article>
<article class="card">
<h3>Real Hardware</h3>
<p>Use repeatable device bootstrap steps instead of pretending recovery is already one-click.</p>
</article>
<article class="card">
<h3>Open Path</h3>
<p>Keep the protocol work, docs, and codebase visible so the community can iterate with us.</p>
</article>
</section>
</main>
</body>
</html>

View File

@@ -0,0 +1,120 @@
:root {
--bg: #f2ebdf;
--ink: #1b2d2a;
--accent: #c45b2f;
--accent-dark: #8f3d1c;
--panel: rgba(255, 250, 242, 0.78);
--line: rgba(27, 45, 42, 0.12);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Georgia, "Times New Roman", serif;
color: var(--ink);
background:
radial-gradient(circle at top left, rgba(196, 91, 47, 0.16), transparent 35%),
radial-gradient(circle at bottom right, rgba(27, 45, 42, 0.12), transparent 30%),
linear-gradient(180deg, #fbf7ef 0%, var(--bg) 100%);
min-height: 100vh;
}
.shell {
width: min(1080px, calc(100% - 2rem));
margin: 0 auto;
padding: 3rem 0 4rem;
}
.hero,
.panel,
.card {
backdrop-filter: blur(8px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 24px;
box-shadow: 0 18px 48px rgba(41, 33, 22, 0.08);
}
.hero {
padding: 3rem;
}
.eyebrow {
margin: 0 0 0.75rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent-dark);
font-size: 0.78rem;
}
h1 {
margin: 0;
font-size: clamp(2.5rem, 6vw, 5rem);
line-height: 0.94;
}
.lede {
max-width: 42rem;
font-size: 1.15rem;
line-height: 1.6;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 0.9rem;
margin-top: 1.5rem;
}
.button {
display: inline-block;
padding: 0.9rem 1.2rem;
border-radius: 999px;
text-decoration: none;
font-weight: 600;
}
.button.primary {
background: var(--accent);
color: #fffaf4;
}
.button.secondary {
color: var(--ink);
border: 1px solid var(--line);
}
.panel {
margin-top: 1.5rem;
padding: 1.5rem 1.75rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.card {
padding: 1.4rem;
}
h2,
h3 {
margin-top: 0;
}
@media (max-width: 700px) {
.hero {
padding: 2rem 1.4rem;
}
.panel,
.card {
border-radius: 18px;
}
}

View File

@@ -1,3 +1,13 @@
# JiboExperiments
A place to put code that isn't quite OS worthy, but needs a home.
Experiments, protocol notes, and early implementation work for bringing Jibo back.
The active work lives in [OpenJibo](C:/Projects/JiboExperiments/OpenJibo), where the current direction is:
- build a stable Azure-hosted replacement cloud first
- use the Node prototype as the reverse-engineering oracle
- port the real hosted system to .NET
- support first devices through RCM plus controlled DNS/TLS patching
- use OTA later as the path to a smoother recovery experience
This repo is intentionally a working lab, not a finished product.