diff --git a/OpenJibo/NuGet.Config b/OpenJibo/NuGet.Config index a3f3aea..765346e 100644 --- a/OpenJibo/NuGet.Config +++ b/OpenJibo/NuGet.Config @@ -2,5 +2,6 @@ + diff --git a/OpenJibo/OpenJibo.slnx b/OpenJibo/OpenJibo.slnx index 2ff0a74..eb24c72 100644 --- a/OpenJibo/OpenJibo.slnx +++ b/OpenJibo/OpenJibo.slnx @@ -1,12 +1,13 @@ + - + @@ -28,6 +29,9 @@ + + + diff --git a/OpenJibo/README.md b/OpenJibo/README.md index 7b0c67a..1af8c17 100644 --- a/OpenJibo/README.md +++ b/OpenJibo/README.md @@ -36,7 +36,7 @@ Jibo device -> OpenJibo cloud -> normalized runtime contracts -> capabilities an The first supported recovery path is enthusiast-friendly, not zero-touch: ```text -QR Wi-Fi -> controlled router/DNS -> redirect legacy Jibo hosts -> +QR Wi-Fi -> inject OpenJibo region config -> set robot region -> RCM/device patch for TLS and host acceptance -> OpenJibo cloud on Azure ``` @@ -47,6 +47,7 @@ That path is documented in [docs/device-bootstrap.md](C:/Projects/JiboExperiment ```text OpenJibo/ docs/ + development-plan.md device-bootstrap.md protocol-inventory.md public-site-plan.md @@ -78,12 +79,14 @@ OpenJibo/ - port required endpoint and WebSocket behavior from Node to .NET - keep protocol captures and replay fixtures current - harden device bootstrap documentation and scripts +- map more endpoints and behaviors beyond the current Node coverage - stand up the initial `openjibo.com` information site ## Important Docs - [Cloud overview](/src/Jibo.Cloud/README.md) +- [Development plan](/docs/development-plan.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) \ No newline at end of file +- [Public site plan](/docs/public-site-plan.md) diff --git a/OpenJibo/docs/development-plan.md b/OpenJibo/docs/development-plan.md new file mode 100644 index 0000000..866a770 --- /dev/null +++ b/OpenJibo/docs/development-plan.md @@ -0,0 +1,65 @@ +# Development Plan + +## Summary + +This document is the working implementation plan after the initial hosted-cloud scaffold. + +It is intentionally broader than the current Node server. The Node server is a protocol oracle and discovery tool, not the complete map of Jibo. + +## Current Scope + +- stable .NET cloud scaffold +- Azure-oriented architecture and data ownership +- normalized runtime contracts for cloud-to-runtime handoff +- bootstrap documentation for region injection and targeted device patching +- starter endpoint coverage for account, notification, robot, loop, update, uploads, and core WebSocket acceptance +- starter xUnit coverage for the .NET application layer + +## Next Implementation Scope + +- expand HTTP `X-Amz-Target` coverage from observed traffic and fixtures +- grow WebSocket compatibility from stub acceptance into realistic turn orchestration +- replace in-memory state with Azure SQL-backed persistence +- add structured fixture replay tests +- harden region/bootstrap docs by software version + +## Discovery Scope + +We still need to map more than the current Node server expresses. Priority discovery areas: + +- all hostnames and service prefixes observed in real startup and turn traffic +- skill launch and skill lifecycle flows +- interactivity command families beyond the current joke flow +- richer embodied speech and animation behaviors +- upload, logging, backup, and key-sharing flows +- per-version configuration differences and region handling + +## Speech, Animation, And ESML + +The current joke flow is only a small foothold into Jibo expressiveness. + +Future work should map: + +- direct speech modifiers +- animation selection and filtering +- embodied speech behaviors +- ESML and SSML subsets +- interactions between speech, visuals, and timing + +Useful external references: + +- [Speak-Tweak Docs](https://hri2024.jibo.media.mit.edu/Speak-Tweak-Docs) +- [ESML PDF](https://hri2024.jibo.media.mit.edu/attachments/SDK-SDK---ESML-121023-203758.pdf) + +## Future Scope + +- full endpoint inventory beyond the current Node mapping +- OTA-driven recovery +- paid hosted plans or donation-supported hosting +- deeper on-device bridge and OS modernization +- more capable skill/runtime integration +- possible LLM or tool-use patterns inspired by workshop-era experimentation + +## MCP-Like Ideas + +Recent MIT workshop materials suggest experimentation around modern AI tooling for Jibo, including an MCP-oriented idea. We should treat that as inspiration for future OpenJibo directions, not as a present dependency or supported integration. diff --git a/OpenJibo/docs/device-bootstrap.md b/OpenJibo/docs/device-bootstrap.md index 9fc5e85..a04dc28 100644 --- a/OpenJibo/docs/device-bootstrap.md +++ b/OpenJibo/docs/device-bootstrap.md @@ -5,7 +5,7 @@ The first supported OpenJibo recovery path is: ```text -QR Wi-Fi -> controlled router/DNS -> redirect Jibo hosts -> +QR Wi-Fi -> inject OpenJibo region config -> set robot region -> RCM/device patch -> Azure-hosted OpenJibo cloud ``` @@ -13,26 +13,44 @@ 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 matches the region-driven configuration seams observed on the robot - 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. +2. Add an OpenJibo region entry to `/etc/jibo-jetstream-service.json`. +3. Set the robot `region` field in `/var/jibo/credentials.json` to the OpenJibo region. 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 +## Region-Driven Configuration -At minimum, watch and validate: +Current findings suggest the preferred OpenJibo bootstrap path is to inject a new region configuration rather than override every hostname manually. + +Confirmed paths: + +- `/etc/jibo-jetstream-service.json` + Add an OpenJibo region definition that points Jibo to our cloud. +- `/var/jibo/credentials.json` + Set the robot `region` field to the injected OpenJibo region. + +Observed additional region-related files worth documenting and auditing: + +- `/etc/jibo-ssm/*.json` +- `/skills/jibo/Jibo/Skills/@be/be/node_modules/language-subtag-registry/data/json/registry.json` +- `/skills/jibo/Jibo/Skills/oobe-config/config.json` + +These should be treated as configuration discovery targets, not yet as the authoritative complete list. + +## Required Hosts + +The currently relevant public hostnames for the OpenJibo cloud path are: - `api.jibo.com` - `api-socket.jibo.com` - `neo-hub.jibo.com` -- `neohub.jibo.com` ## Scripted Helpers @@ -42,7 +60,7 @@ Bootstrap helper scripts live in [scripts/bootstrap](C:/Projects/JiboExperiments - `Generate-JiboDnsOverrides.ps1` - `Test-OpenJiboRouting.ps1` -These are intentionally conservative helpers for discovery and verification, not destructive patch tools. +These are intentionally conservative helpers for discovery and verification, not destructive patch tools. They remain useful for controlled-network testing, even though the preferred long-term device path is region injection. ## TLS And Runtime Patching @@ -53,6 +71,7 @@ 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 +- keep a versioned region-config checklist - do not describe OTA as the primary bootstrap method until the hosted cloud is stable ## Smoke Test Goals diff --git a/OpenJibo/docs/protocol-inventory.md b/OpenJibo/docs/protocol-inventory.md index 9749b4d..af3f7ad 100644 --- a/OpenJibo/docs/protocol-inventory.md +++ b/OpenJibo/docs/protocol-inventory.md @@ -4,6 +4,8 @@ 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. +It is not a claim that the current Node server covers all Jibo endpoints or behaviors. It reflects only the portions mapped so far. + Confidence levels: - `high`: observed in code and currently represented in the .NET scaffold @@ -17,7 +19,20 @@ Confidence levels: | `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 | + +## Region Configuration + +Current robot findings suggest the preferred OpenJibo bootstrap path is to inject a new region configuration rather than treat host overrides as the only integration seam. + +Confirmed or strongly observed files: + +- `/etc/jibo-jetstream-service.json` +- `/var/jibo/credentials.json` +- `/etc/jibo-ssm/*.json` +- `/skills/jibo/Jibo/Skills/@be/be/node_modules/language-subtag-registry/data/json/registry.json` +- `/skills/jibo/Jibo/Skills/oobe-config/config.json` + +The first two are the clearest current OpenJibo injection points. The others should remain on the audit list while endpoint and behavior mapping continues. ## HTTP Dispatch Families @@ -65,6 +80,22 @@ The first .NET hosted milestone should fully support: - basic listen/proactive WebSocket acceptance - normalized turn and reply mapping for simple chat +## Known Beyond Current Node Coverage + +The platform scope is broader than the endpoints currently modeled in `open-jibo-link.js`. Known areas that still need mapping include: + +- broader skill launch and lifecycle behavior +- interactivity command families beyond the joke starter path +- richer animation and expression control +- ESML and embodied speech features +- additional service families and region-specific endpoint behavior +- startup and configuration differences across Jibo software variants + +Useful external references for future mapping: + +- [Speak-Tweak Docs](https://hri2024.jibo.media.mit.edu/Speak-Tweak-Docs) +- [ESML PDF](https://hri2024.jibo.media.mit.edu/attachments/SDK-SDK---ESML-121023-203758.pdf) + ## 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. diff --git a/OpenJibo/scripts/bootstrap/Discover-JiboHosts.ps1 b/OpenJibo/scripts/bootstrap/Discover-JiboHosts.ps1 index eaed8cb..5bf4a1e 100644 --- a/OpenJibo/scripts/bootstrap/Discover-JiboHosts.ps1 +++ b/OpenJibo/scripts/bootstrap/Discover-JiboHosts.ps1 @@ -3,8 +3,7 @@ param( [string[]]$KnownHosts = @( "api.jibo.com", "api-socket.jibo.com", - "neo-hub.jibo.com", - "neohub.jibo.com" + "neo-hub.jibo.com" ) ) diff --git a/OpenJibo/scripts/bootstrap/Generate-JiboDnsOverrides.ps1 b/OpenJibo/scripts/bootstrap/Generate-JiboDnsOverrides.ps1 index ef36c2b..dabdfd1 100644 --- a/OpenJibo/scripts/bootstrap/Generate-JiboDnsOverrides.ps1 +++ b/OpenJibo/scripts/bootstrap/Generate-JiboDnsOverrides.ps1 @@ -3,8 +3,7 @@ param( [string[]]$HostNames = @( "api.jibo.com", "api-socket.jibo.com", - "neo-hub.jibo.com", - "neohub.jibo.com" + "neo-hub.jibo.com" ) ) diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs index f233170..cedb25b 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs @@ -20,8 +20,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore { ["api.jibo.com"] = "openjibo.com", ["api-socket.jibo.com"] = "openjibo.com", - ["neo-hub.jibo.com"] = "openjibo.com", - ["neohub.jibo.com"] = "openjibo.com" + ["neo-hub.jibo.com"] = "openjibo.com" } }; diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/GlobalUsings.cs b/OpenJibo/tests/Jibo.Cloud.Tests/GlobalUsings.cs new file mode 100644 index 0000000..c802f44 --- /dev/null +++ b/OpenJibo/tests/Jibo.Cloud.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/Jibo.Cloud.Tests.csproj b/OpenJibo/tests/Jibo.Cloud.Tests/Jibo.Cloud.Tests.csproj new file mode 100644 index 0000000..2909fbc --- /dev/null +++ b/OpenJibo/tests/Jibo.Cloud.Tests/Jibo.Cloud.Tests.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/Protocol/JiboCloudProtocolServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/Protocol/JiboCloudProtocolServiceTests.cs new file mode 100644 index 0000000..9432ac3 --- /dev/null +++ b/OpenJibo/tests/Jibo.Cloud.Tests/Protocol/JiboCloudProtocolServiceTests.cs @@ -0,0 +1,64 @@ +using System.Text.Json; +using Jibo.Cloud.Application.Services; +using Jibo.Cloud.Domain.Models; +using Jibo.Cloud.Infrastructure.Persistence; + +namespace Jibo.Cloud.Tests.Protocol; + +public sealed class JiboCloudProtocolServiceTests +{ + private readonly JiboCloudProtocolService _service = new(new InMemoryCloudStateStore()); + + [Fact] + public async Task CreateHubToken_ReturnsTokenAndExpiry() + { + var result = await _service.DispatchAsync(new ProtocolEnvelope + { + HostName = "api.jibo.com", + Method = "POST", + ServicePrefix = "Account_20160715", + Operation = "CreateHubToken", + BodyText = "{}" + }); + + using var payload = JsonDocument.Parse(result.BodyText); + Assert.Equal(200, result.StatusCode); + Assert.StartsWith("hub-", payload.RootElement.GetProperty("token").GetString()); + Assert.True(payload.RootElement.GetProperty("expires").GetInt64() > 0); + } + + [Fact] + public async Task NewRobotToken_UsesBodyDeviceId() + { + var result = await _service.DispatchAsync(new ProtocolEnvelope + { + HostName = "api.jibo.com", + Method = "POST", + ServicePrefix = "Notification_20160715", + Operation = "NewRobotToken", + BodyText = """{"deviceId":"robot-123"}""" + }); + + using var payload = JsonDocument.Parse(result.BodyText); + Assert.Equal(200, result.StatusCode); + Assert.Contains("robot-123", payload.RootElement.GetProperty("token").GetString()); + } + + [Fact] + public async Task GetUpdateFrom_ReturnsNoOpUpdate() + { + var result = await _service.DispatchAsync(new ProtocolEnvelope + { + HostName = "api.jibo.com", + Method = "POST", + ServicePrefix = "Update_20160715", + Operation = "GetUpdateFrom", + BodyText = """{"subsystem":"robot","fromVersion":"1.0.0"}""" + }); + + using var payload = JsonDocument.Parse(result.BodyText); + Assert.Equal(200, result.StatusCode); + Assert.Equal("robot", payload.RootElement.GetProperty("subsystem").GetString()); + Assert.True(payload.RootElement.TryGetProperty("url", out _)); + } +} diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs new file mode 100644 index 0000000..861f7f6 --- /dev/null +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using Jibo.Cloud.Application.Services; +using Jibo.Cloud.Domain.Models; +using Jibo.Cloud.Infrastructure.Persistence; + +namespace Jibo.Cloud.Tests.WebSockets; + +public sealed class JiboWebSocketServiceTests +{ + private readonly JiboWebSocketService _service; + + public JiboWebSocketServiceTests() + { + var store = new InMemoryCloudStateStore(); + _service = new JiboWebSocketService( + store, + new ProtocolToTurnContextMapper(), + new DemoConversationBroker(), + new ResponsePlanToSocketMessagesMapper()); + } + + [Fact] + public async Task ListenMessage_ReturnsResponseAndEos() + { + var replies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope + { + HostName = "neo-hub.jibo.com", + Path = "/listen", + Kind = "neo-hub-listen", + Token = "hub-test-token", + Text = """{"type":"LISTEN","data":{"text":"hello jibo"}}""" + }); + + Assert.Equal(2, replies.Count); + Assert.Contains("OPENJIBO_RESPONSE", replies[0].Text); + Assert.Contains("EOS", replies[1].Text); + } + + [Fact] + public async Task BinaryMessage_ReturnsAcknowledgementPayload() + { + var replies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope + { + HostName = "neo-hub.jibo.com", + Path = "/listen", + Kind = "neo-hub-listen", + Token = "hub-test-token", + Binary = [1, 2, 3, 4] + }); + + using var payload = JsonDocument.Parse(replies[0].Text!); + Assert.Equal("OPENJIBO_AUDIO_RECEIVED", payload.RootElement.GetProperty("type").GetString()); + Assert.Equal(4, payload.RootElement.GetProperty("data").GetProperty("bytes").GetInt32()); + } +}