robopark 3.3.27 → 3.3.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/package.json +1 -1
  2. package/scheduler/main.py +105 -6
  3. package/ui/standalone/.next/BUILD_ID +1 -1
  4. package/ui/standalone/.next/app-build-manifest.json +115 -115
  5. package/ui/standalone/.next/app-path-routes-manifest.json +14 -14
  6. package/ui/standalone/.next/build-manifest.json +3 -3
  7. package/ui/standalone/.next/prerender-manifest.json +3 -3
  8. package/ui/standalone/.next/required-server-files.json +3 -3
  9. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js +1 -1
  10. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route_client-reference-manifest.js +1 -1
  11. package/ui/standalone/.next/server/app/(app)/page.js +2 -2
  12. package/ui/standalone/.next/server/app/(app)/page.js.nft.json +1 -1
  13. package/ui/standalone/.next/server/app/(app)/page_client-reference-manifest.js +1 -1
  14. package/ui/standalone/.next/server/app/(app)/voice/page.js +3 -3
  15. package/ui/standalone/.next/server/app/(app)/voice/page.js.nft.json +1 -1
  16. package/ui/standalone/.next/server/app/(app)/voice/page_client-reference-manifest.js +1 -1
  17. package/ui/standalone/.next/server/app/_not-found/page.js +2 -2
  18. package/ui/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/ui/standalone/.next/server/app/api/connection-details/route.js +1 -1
  20. package/ui/standalone/.next/server/app/api/connection-details/route_client-reference-manifest.js +1 -1
  21. package/ui/standalone/.next/server/app/api/device-character/route.js +1 -1
  22. package/ui/standalone/.next/server/app/api/device-character/route_client-reference-manifest.js +1 -1
  23. package/ui/standalone/.next/server/app/api/download-piper-model/route.js +1 -1
  24. package/ui/standalone/.next/server/app/api/download-piper-model/route_client-reference-manifest.js +1 -1
  25. package/ui/standalone/.next/server/app/api/kiosk-hardware/route.js +1 -1
  26. package/ui/standalone/.next/server/app/api/kiosk-hardware/route_client-reference-manifest.js +1 -1
  27. package/ui/standalone/.next/server/app/api/model-status/route.js +1 -1
  28. package/ui/standalone/.next/server/app/api/model-status/route_client-reference-manifest.js +1 -1
  29. package/ui/standalone/.next/server/app/api/models/route.js +1 -1
  30. package/ui/standalone/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  31. package/ui/standalone/.next/server/app/api/prewarm/route.js +1 -1
  32. package/ui/standalone/.next/server/app/api/prewarm/route_client-reference-manifest.js +1 -1
  33. package/ui/standalone/.next/server/app/api/prompt/route.js +1 -1
  34. package/ui/standalone/.next/server/app/api/prompt/route_client-reference-manifest.js +1 -1
  35. package/ui/standalone/.next/server/app/api/reload-tools/route.js +1 -1
  36. package/ui/standalone/.next/server/app/api/reload-tools/route_client-reference-manifest.js +1 -1
  37. package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js +1 -1
  38. package/ui/standalone/.next/server/app/api/robopark/camera-token/route_client-reference-manifest.js +1 -1
  39. package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js +1 -1
  40. package/ui/standalone/.next/server/app/api/robopark/character-ai/route_client-reference-manifest.js +1 -1
  41. package/ui/standalone/.next/server/app/api/robopark/device-access/route.js +1 -1
  42. package/ui/standalone/.next/server/app/api/robopark/device-access/route_client-reference-manifest.js +1 -1
  43. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js +1 -1
  44. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route_client-reference-manifest.js +1 -1
  45. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js +1 -1
  46. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route_client-reference-manifest.js +1 -1
  47. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js +1 -1
  48. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route_client-reference-manifest.js +1 -1
  49. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js +1 -1
  50. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route_client-reference-manifest.js +1 -1
  51. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js +1 -1
  52. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route_client-reference-manifest.js +1 -1
  53. package/ui/standalone/.next/server/app/api/robopark/robots/route.js +1 -1
  54. package/ui/standalone/.next/server/app/api/robopark/robots/route_client-reference-manifest.js +1 -1
  55. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js +1 -1
  56. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route_client-reference-manifest.js +1 -1
  57. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js +1 -1
  58. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route_client-reference-manifest.js +1 -1
  59. package/ui/standalone/.next/server/app/api/robopark/servers/route.js +1 -1
  60. package/ui/standalone/.next/server/app/api/robopark/servers/route_client-reference-manifest.js +1 -1
  61. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js +1 -1
  62. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route_client-reference-manifest.js +1 -1
  63. package/ui/standalone/.next/server/app/api/robopark/sessions/route.js +1 -1
  64. package/ui/standalone/.next/server/app/api/robopark/sessions/route_client-reference-manifest.js +1 -1
  65. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js +1 -1
  66. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route_client-reference-manifest.js +1 -1
  67. package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js +1 -1
  68. package/ui/standalone/.next/server/app/api/robopark/transcripts/route_client-reference-manifest.js +1 -1
  69. package/ui/standalone/.next/server/app/api/robopark/vision-caption/route.js +1 -1
  70. package/ui/standalone/.next/server/app/api/robopark/vision-caption/route_client-reference-manifest.js +1 -1
  71. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js +1 -1
  72. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route_client-reference-manifest.js +1 -1
  73. package/ui/standalone/.next/server/app/api/settings/route.js +1 -1
  74. package/ui/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  75. package/ui/standalone/.next/server/app/api/setup/complete/route.js +1 -1
  76. package/ui/standalone/.next/server/app/api/setup/complete/route_client-reference-manifest.js +1 -1
  77. package/ui/standalone/.next/server/app/api/setup/status/route.js +1 -1
  78. package/ui/standalone/.next/server/app/api/setup/status/route_client-reference-manifest.js +1 -1
  79. package/ui/standalone/.next/server/app/api/setup/test-groq/route.js +1 -1
  80. package/ui/standalone/.next/server/app/api/setup/test-groq/route_client-reference-manifest.js +1 -1
  81. package/ui/standalone/.next/server/app/api/setup/test-hass/route.js +1 -1
  82. package/ui/standalone/.next/server/app/api/setup/test-hass/route_client-reference-manifest.js +1 -1
  83. package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js +1 -1
  84. package/ui/standalone/.next/server/app/api/setup/test-n8n/route_client-reference-manifest.js +1 -1
  85. package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js +1 -1
  86. package/ui/standalone/.next/server/app/api/setup/test-ollama/route_client-reference-manifest.js +1 -1
  87. package/ui/standalone/.next/server/app/api/test-tts/route.js +1 -1
  88. package/ui/standalone/.next/server/app/api/test-tts/route_client-reference-manifest.js +1 -1
  89. package/ui/standalone/.next/server/app/api/turn-credentials/route.js +1 -1
  90. package/ui/standalone/.next/server/app/api/turn-credentials/route_client-reference-manifest.js +1 -1
  91. package/ui/standalone/.next/server/app/api/voices/route.js +1 -1
  92. package/ui/standalone/.next/server/app/api/voices/route_client-reference-manifest.js +1 -1
  93. package/ui/standalone/.next/server/app/api/wake/route.js +1 -1
  94. package/ui/standalone/.next/server/app/api/wake/route_client-reference-manifest.js +1 -1
  95. package/ui/standalone/.next/server/app/api/wake-word/models/route.js +1 -1
  96. package/ui/standalone/.next/server/app/api/wake-word/models/route_client-reference-manifest.js +1 -1
  97. package/ui/standalone/.next/server/app/api/wake-word/status/route.js +1 -1
  98. package/ui/standalone/.next/server/app/api/wake-word/status/route_client-reference-manifest.js +1 -1
  99. package/ui/standalone/.next/server/app/api/wake-word/upload/route.js +1 -1
  100. package/ui/standalone/.next/server/app/api/wake-word/upload/route_client-reference-manifest.js +1 -1
  101. package/ui/standalone/.next/server/app/camera-grid/route.js +1 -1
  102. package/ui/standalone/.next/server/app/camera-grid/route_client-reference-manifest.js +1 -1
  103. package/ui/standalone/.next/server/app/favicon.ico/route.js +1 -1
  104. package/ui/standalone/.next/server/app/fed/[...path]/route.js +2 -2
  105. package/ui/standalone/.next/server/app/fed/[...path]/route_client-reference-manifest.js +1 -1
  106. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js +1 -1
  107. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route_client-reference-manifest.js +1 -1
  108. package/ui/standalone/.next/server/app/robopark/[...path]/route.js +1 -1
  109. package/ui/standalone/.next/server/app/robopark/[...path]/route_client-reference-manifest.js +1 -1
  110. package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js +1 -1
  111. package/ui/standalone/.next/server/app/robopark/api/[...path]/route_client-reference-manifest.js +1 -1
  112. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js +1 -1
  113. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route_client-reference-manifest.js +1 -1
  114. package/ui/standalone/.next/server/app/robopark/page.js +2 -2
  115. package/ui/standalone/.next/server/app/robopark/page.js.nft.json +1 -1
  116. package/ui/standalone/.next/server/app/robopark/page_client-reference-manifest.js +1 -1
  117. package/ui/standalone/.next/server/app/tailnet/route.js +1 -1
  118. package/ui/standalone/.next/server/app/tailnet/route_client-reference-manifest.js +1 -1
  119. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js +1 -1
  120. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route_client-reference-manifest.js +1 -1
  121. package/ui/standalone/.next/server/app/voice-tester/page.js +2 -2
  122. package/ui/standalone/.next/server/app/voice-tester/page.js.nft.json +1 -1
  123. package/ui/standalone/.next/server/app/voice-tester/page_client-reference-manifest.js +1 -1
  124. package/ui/standalone/.next/server/app-paths-manifest.json +14 -14
  125. package/ui/standalone/.next/server/chunks/5232.js +4 -4
  126. package/ui/standalone/.next/server/chunks/5455.js +11 -0
  127. package/ui/standalone/.next/server/chunks/9077.js +11 -0
  128. package/ui/standalone/.next/server/functions-config-manifest.json +1 -1
  129. package/ui/standalone/.next/server/middleware-build-manifest.js +1 -1
  130. package/ui/standalone/.next/server/pages/500.html +1 -1
  131. package/ui/standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/ui/standalone/.next/static/KwY1YC79lI9nQqOVYXOmY/_buildManifest.js +1 -0
  133. package/ui/standalone/.next/static/KwY1YC79lI9nQqOVYXOmY/_ssgManifest.js +1 -0
  134. package/ui/standalone/.next/static/chunks/app/(app)/voice/page-72d72d9a70d9c1d4.js +1 -0
  135. package/ui/standalone/.next/static/chunks/app/_not-found/page-cfd7daa82d71540c.js +1 -0
  136. package/ui/standalone/.next/static/chunks/app/layout-fcce963da1d8220f.js +1 -0
  137. package/ui/standalone/.next/static/chunks/app/voice-tester/page-ae699e048e0eb834.js +1 -0
  138. package/ui/standalone/.next/static/chunks/main-app-a1f59168303c7f84.js +1 -0
  139. package/ui/standalone/server.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robopark",
3
- "version": "3.3.27",
3
+ "version": "3.3.30",
4
4
  "description": "RoboPark fleet control CLI — scheduler, control UI, and character voice calls. Standalone: no infinicode runtime.",
5
5
  "type": "module",
6
6
  "bin": {
package/scheduler/main.py CHANGED
@@ -18,11 +18,11 @@ import uuid
18
18
  from datetime import datetime, timedelta
19
19
  from html import escape as html_escape
20
20
  from pathlib import Path
21
- from typing import Any, Optional, List
21
+ from typing import Any, Dict, Optional, List
22
22
  from urllib.parse import quote, urlparse
23
23
  from contextlib import asynccontextmanager
24
24
 
25
- from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query, Header, Request
25
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query, Header, Request, Body
26
26
  from fastapi.middleware.cors import CORSMiddleware
27
27
  from fastapi.staticfiles import StaticFiles
28
28
  from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse
@@ -1963,12 +1963,41 @@ async def _claim_character_device(
1963
1963
  """INSERT INTO robots (id, name, character_id, status, last_heartbeat)
1964
1964
  VALUES (?, ?, ?, 'online', ?)
1965
1965
  ON CONFLICT(id) DO UPDATE SET
1966
+ name = excluded.name,
1966
1967
  character_id = excluded.character_id,
1967
1968
  status = 'online',
1968
1969
  last_heartbeat = excluded.last_heartbeat""",
1969
1970
  (device_id, label, character_id, stamp),
1970
1971
  )
1972
+ # Retire every OTHER auto-claim for this character.
1973
+ #
1974
+ # The claim key is the join link, so each regenerated share link mints a
1975
+ # fresh claim device for the same character and nothing ever retired the
1976
+ # old ones. Three live claim_* rows for one character meant three
1977
+ # robopark-<device> rooms: the publisher joined the newest while the camera
1978
+ # grid was still watching an older twin, which is why a feed visible on the
1979
+ # call page showed as "waiting for camera track" everywhere else. Only
1980
+ # auto-claimed rows are touched -- enrolled hardware is never retired.
1981
+ async with db.execute(
1982
+ "SELECT id FROM devices WHERE lower(character_id) = lower(?) AND id != ? "
1983
+ "AND notes LIKE 'auto-claimed via join link%'",
1984
+ (character_id or "", device_id),
1985
+ ) as cursor:
1986
+ twins = [str(row[0]) for row in await cursor.fetchall()]
1987
+ for twin in twins:
1988
+ await db.execute(
1989
+ "UPDATE sessions SET ended_at = ?, end_reason = ? WHERE robot_id = ? AND ended_at IS NULL",
1990
+ (stamp, "claim_superseded", twin),
1991
+ )
1992
+ await db.execute("UPDATE devices SET status = 'offline' WHERE id = ?", (twin,))
1993
+ await db.execute(
1994
+ "UPDATE robots SET status = 'idle', current_session_id = NULL, "
1995
+ "connected_server_id = NULL WHERE id = ?",
1996
+ (twin,),
1997
+ )
1971
1998
  await db.commit()
1999
+ for twin in twins:
2000
+ await broadcast("device_status_changed", {"device_id": twin, "status": "offline"})
1972
2001
  # No MJPEG ref: a claimed device publishes its own browser camera rather than
1973
2002
  # exposing a robot-side vision server, so the page falls back to local video.
1974
2003
  return device_id, None
@@ -2356,6 +2385,70 @@ async def session_joined(session_id: str,
2356
2385
  await _log_history("session", "session", session_id, "joined", "robot", f"latency_ms={latency_ms}")
2357
2386
  return {"status": "ok", "joined_at": now, "latency_ms": latency_ms}
2358
2387
 
2388
+ @app.post("/api/sessions/{session_id}/heartbeat")
2389
+ async def session_heartbeat(session_id: str,
2390
+ payload: Optional[Dict[str, Any]] = Body(default=None),
2391
+ authorization: Optional[str] = Header(default=None),
2392
+ session_token: Optional[str] = Header(default=None, alias="X-RoboPark-Session-Token")):
2393
+ """Liveness beat from the browser join page, every 30s for the whole call.
2394
+
2395
+ This route is the one the public join page has always called. It did not
2396
+ exist, so every beat 404'd and nothing refreshed liveness for a browser
2397
+ call. Three minutes in, _reap_robot_session_if_stale saw last_activity_at
2398
+ still equal to started_at, ended the session as 'stale_activity' and set
2399
+ robots.status='idle'/current_session_id=NULL; the device then fell out of
2400
+ health_checker's live-session exclusion and flipped offline 90s later.
2401
+ The call itself stayed up the whole time — only every operator view lost
2402
+ it, which is why robots vanished from the park map and the camera grid
2403
+ dropped feeds partway through a working call.
2404
+
2405
+ Bumps all three clocks the operator views read: the session's activity,
2406
+ the device heartbeat and the robot heartbeat.
2407
+ """
2408
+ if not await _session_event_authorized(session_id, session_token, authorization, None):
2409
+ raise HTTPException(401, "Invalid or missing session token")
2410
+ now = datetime.utcnow().isoformat()
2411
+ source = str((payload or {}).get("source") or "")[:40]
2412
+ async with aiosqlite.connect(DB_PATH) as db:
2413
+ db.row_factory = aiosqlite.Row
2414
+ async with db.execute(
2415
+ "SELECT robot_id, ended_at FROM sessions WHERE id = ?", (session_id,)
2416
+ ) as cursor:
2417
+ session = await cursor.fetchone()
2418
+ if not session:
2419
+ raise HTTPException(404, "Session not found")
2420
+ if session["ended_at"]:
2421
+ # Do not resurrect a closed session; tell the page so it can stop.
2422
+ return {"status": "ended", "ended_at": session["ended_at"]}
2423
+ await db.execute(
2424
+ "UPDATE sessions SET last_activity_at = ? WHERE id = ? AND ended_at IS NULL",
2425
+ (now, session_id),
2426
+ )
2427
+ robot_id = session["robot_id"]
2428
+ was_offline = False
2429
+ if robot_id:
2430
+ async with db.execute(
2431
+ "SELECT status FROM devices WHERE id = ?", (robot_id,)
2432
+ ) as cursor:
2433
+ device = await cursor.fetchone()
2434
+ was_offline = bool(device) and device["status"] != "online"
2435
+ await db.execute(
2436
+ "UPDATE devices SET status = 'online', last_heartbeat = ? WHERE id = ?",
2437
+ (now, robot_id),
2438
+ )
2439
+ # Re-point the robot at this session as well. A beat that arrives
2440
+ # after the reaper already released the pointer has to restore it,
2441
+ # otherwise the session stays live but invisible until it ends.
2442
+ await db.execute(
2443
+ "UPDATE robots SET status = 'running', current_session_id = ?, last_heartbeat = ? "
2444
+ "WHERE id = ?",
2445
+ (session_id, now, robot_id),
2446
+ )
2447
+ await db.commit()
2448
+ if robot_id and was_offline:
2449
+ await broadcast("device_status_changed", {"device_id": robot_id, "status": "online"})
2450
+ return {"status": "ok", "last_activity_at": now, "source": source}
2451
+
2359
2452
  @app.post("/api/sessions/{session_id}/keepalive")
2360
2453
  async def session_keepalive(session_id: str,
2361
2454
  authorization: Optional[str] = Header(default=None),
@@ -3528,11 +3621,17 @@ def _lk_public_url(url: str, request: Optional[Request] = None) -> str:
3528
3621
  return url # the caller IS local; localhost is correct for them
3529
3622
 
3530
3623
  proto = (request.headers.get("x-forwarded-proto") or request.url.scheme or "").lower()
3531
- # ws:// from an https:// page is mixed content and is blocked outright,
3532
- # so a TLS-fronted request must be told wss.
3533
- scheme = "wss" if proto == "https" else "ws"
3624
+ if proto == "https":
3625
+ # ws:// from an https:// page is mixed content and is blocked, so a
3626
+ # TLS-fronted caller must get wss. But LiveKit's own port serves
3627
+ # plain ws — pointing wss at it just fails the TLS handshake, which
3628
+ # is what this fallback did on its first outing. TLS comes from a
3629
+ # separate front door (`tailscale serve --https=3443`), and the
3630
+ # control center already assumes 3443, so use that port.
3631
+ tls_port = (os.getenv("ROBOPARK_LIVEKIT_TLS_PORT", "").strip() or "3443")
3632
+ return f"wss://{caller}:{tls_port}"
3534
3633
  port = f":{parsed.port}" if parsed.port else ""
3535
- return f"{scheme}://{caller}{port}"
3634
+ return f"ws://{caller}{port}"
3536
3635
  except Exception:
3537
3636
  return url
3538
3637
 
@@ -1 +1 @@
1
- tDI_bBJ7vZjqqxcXg1GZn
1
+ KwY1YC79lI9nQqOVYXOmY