@agentdeck/bridge 1.0.6 → 1.0.8

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 (80) hide show
  1. package/assets/fm-helper/agentdeck-fm-helper +0 -0
  2. package/dist/apme/collector.d.ts +10 -0
  3. package/dist/apme/collector.d.ts.map +1 -1
  4. package/dist/apme/collector.js +39 -10
  5. package/dist/apme/collector.js.map +1 -1
  6. package/dist/apme/dashboard-html.d.ts.map +1 -1
  7. package/dist/apme/dashboard-html.js +289 -0
  8. package/dist/apme/dashboard-html.js.map +1 -1
  9. package/dist/apme/graph.d.ts +48 -0
  10. package/dist/apme/graph.d.ts.map +1 -0
  11. package/dist/apme/graph.js +226 -0
  12. package/dist/apme/graph.js.map +1 -0
  13. package/dist/apme/http.d.ts.map +1 -1
  14. package/dist/apme/http.js +52 -0
  15. package/dist/apme/http.js.map +1 -1
  16. package/dist/apme/judge-detect.d.ts.map +1 -1
  17. package/dist/apme/judge-detect.js +26 -2
  18. package/dist/apme/judge-detect.js.map +1 -1
  19. package/dist/apme/store.d.ts +69 -1
  20. package/dist/apme/store.d.ts.map +1 -1
  21. package/dist/apme/store.js +238 -4
  22. package/dist/apme/store.js.map +1 -1
  23. package/dist/apme/types.d.ts +1 -1
  24. package/dist/apme/types.d.ts.map +1 -1
  25. package/dist/apme/types.js.map +1 -1
  26. package/dist/ble-sync-spawn.d.ts +25 -2
  27. package/dist/ble-sync-spawn.d.ts.map +1 -1
  28. package/dist/ble-sync-spawn.js +38 -2
  29. package/dist/ble-sync-spawn.js.map +1 -1
  30. package/dist/ble-sync-status.d.ts +77 -0
  31. package/dist/ble-sync-status.d.ts.map +1 -0
  32. package/dist/ble-sync-status.js +157 -0
  33. package/dist/ble-sync-status.js.map +1 -0
  34. package/dist/bridge-core.d.ts.map +1 -1
  35. package/dist/bridge-core.js +7 -1
  36. package/dist/bridge-core.js.map +1 -1
  37. package/dist/check-deps.d.ts.map +1 -1
  38. package/dist/check-deps.js +4 -12
  39. package/dist/check-deps.js.map +1 -1
  40. package/dist/cli.js +0 -0
  41. package/dist/codex-rate-limits-live.d.ts +60 -0
  42. package/dist/codex-rate-limits-live.d.ts.map +1 -0
  43. package/dist/codex-rate-limits-live.js +282 -0
  44. package/dist/codex-rate-limits-live.js.map +1 -0
  45. package/dist/daemon-server.d.ts.map +1 -1
  46. package/dist/daemon-server.js +81 -3
  47. package/dist/daemon-server.js.map +1 -1
  48. package/dist/daemon.js +1 -1
  49. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +8 -0
  50. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
  51. package/dist/idotmatrix/idotmatrix-daemon-sync.js +19 -3
  52. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +3 -4
  55. package/dist/index.js.map +1 -1
  56. package/dist/modules/idotmatrix-module.d.ts.map +1 -1
  57. package/dist/modules/idotmatrix-module.js +12 -1
  58. package/dist/modules/idotmatrix-module.js.map +1 -1
  59. package/dist/modules/timebox-module.d.ts.map +1 -1
  60. package/dist/modules/timebox-module.js +12 -1
  61. package/dist/modules/timebox-module.js.map +1 -1
  62. package/dist/timebox/timebox-daemon-sync.d.ts +8 -0
  63. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
  64. package/dist/timebox/timebox-daemon-sync.js +29 -1
  65. package/dist/timebox/timebox-daemon-sync.js.map +1 -1
  66. package/dist/timeline-summarizer.d.ts +12 -2
  67. package/dist/timeline-summarizer.d.ts.map +1 -1
  68. package/dist/timeline-summarizer.js +66 -4
  69. package/dist/timeline-summarizer.js.map +1 -1
  70. package/dist/voice-assistant.d.ts +2 -2
  71. package/dist/voice-assistant.d.ts.map +1 -1
  72. package/dist/voice-assistant.js +1 -1
  73. package/dist/voice.d.ts +5 -5
  74. package/dist/voice.d.ts.map +1 -1
  75. package/dist/voice.js +8 -10
  76. package/dist/voice.js.map +1 -1
  77. package/package.json +13 -14
  78. package/src/idotmatrix/sync.py +16 -1
  79. package/src/pysync/matrix_sync_common.py +83 -0
  80. package/src/timebox/sync_ble.py +16 -0
@@ -46,6 +46,13 @@ const OBSERVED_APPROVAL_ENABLED = process.env.AGENTDECK_OBSERVED_APPROVAL !== '0
46
46
  * call to Claude's own permission flow. Must stay well under the hook curl's
47
47
  * --max-time 60 so the release reaches Claude before curl gives up. */
48
48
  const OBSERVED_APPROVAL_HOLD_MS = Math.min(50_000, Math.max(5_000, Number(process.env.AGENTDECK_APPROVAL_HOLD_MS) || 25_000));
49
+ /** Inactivity after which an unclosed APME run is treated as abandoned and
50
+ * finalized. Deliberately generous: an orphan stays reapable forever, so
51
+ * closing it late costs nothing, while reaping a live run corrupts the unit
52
+ * being measured. Session bridges own their own collector state and are
53
+ * invisible to this daemon, so only elapsed silence protects them (runs this
54
+ * daemon still holds are skipped via `collector.isLiveRun`). */
55
+ const APME_ABANDONED_RUN_STALE_SEC = Math.max(600, Number(process.env.AGENTDECK_APME_ABANDON_SEC) || 7200);
49
56
  import { VoiceManager } from './voice.js';
50
57
  import { VoiceAssistantManager } from './voice-assistant.js';
51
58
  import { listActive as listActiveSessions, findAvailablePort, findExistingDaemon, DAEMON_DEFAULT_PORT, probeDaemonHealth, requestDaemonShutdown, scanDaemonPortWindow, shouldConcedePortToOccupant, waitForDaemonExit, writeDaemonInfo, ensureDaemonInfo, removeDaemonInfo, readDaemonInfo, removeDaemonSession, getCandidateDataDirs, getOwnTimelineFile, } from './session-registry.js';
@@ -788,8 +795,16 @@ function buildNodeModuleHealth(startedModules) {
788
795
  modules.timebox = timebox.statusSnapshot();
789
796
  }
790
797
  else if (configuredTimebox.length > 0) {
798
+ // Configured but the module never started — say so explicitly. An omitted
799
+ // `connected` reads as "no information" to retain-on-absent clients, and a
800
+ // panel nobody is driving must not inherit a stale connected:true.
791
801
  modules.timebox = {
792
802
  configuredDeviceCount: configuredTimebox.length,
803
+ connected: false,
804
+ statusReason: 'sync module not started',
805
+ displayDimmed: false,
806
+ hasFrame: false,
807
+ lastError: null,
793
808
  devices: configuredTimebox.map((d) => ({
794
809
  address: d.address,
795
810
  name: d.name ?? 'Timebox Mini',
@@ -803,8 +818,14 @@ function buildNodeModuleHealth(startedModules) {
803
818
  modules.idotmatrix = idotmatrix.statusSnapshot();
804
819
  }
805
820
  else if (configuredIDotMatrix.length > 0) {
821
+ // Configured but the module never started — see the Timebox note above.
806
822
  modules.idotmatrix = {
807
823
  configuredDeviceCount: configuredIDotMatrix.length,
824
+ connected: false,
825
+ statusReason: 'sync module not started',
826
+ displayDimmed: false,
827
+ hasFrame: false,
828
+ lastError: null,
808
829
  devices: configuredIDotMatrix.map((d) => ({
809
830
  address: d.address,
810
831
  name: d.name ?? 'iDotMatrix',
@@ -1998,6 +2019,27 @@ export async function startDaemon(opts) {
1998
2019
  const respRaw = responseText.length > 0
1999
2020
  ? cleanRawText(responseText.length > 200 ? responseText.slice(0, 197) + '...' : responseText)
2000
2021
  : '';
2022
+ // APME: the text below is the assistant half of the turn — hand it to
2023
+ // the collector on the SAME branch that decides the timeline row.
2024
+ // `setTurnResponse` was reachable only from the PTY session bridge
2025
+ // (bridge/src/index.ts), which hook-observed sessions (direct
2026
+ // `claude` / `codex` / standalone `opencode`) never go through, so
2027
+ // every such turn archived a prompt and its tool calls but no reply:
2028
+ // `turns.response` stayed NULL and no `assistant_message` trajectory
2029
+ // event was ever written. The dashboard therefore could not replay a
2030
+ // conversation the timeline shows in full, and the judge scored those
2031
+ // turns against silence. Empty text is still worth recording — it
2032
+ // tags `response_kind` tool_only/empty so the runner skips judging.
2033
+ if (apme) {
2034
+ if (turnOpen)
2035
+ apme.collector.setTurnResponse(hookSid, responseText);
2036
+ else if (responseText.length > 0) {
2037
+ // No open turn: a missed prompt hook or a late/duplicate stop.
2038
+ // The last-closed fallback refuses to overwrite an existing
2039
+ // response, so a duplicate stop is a no-op instead of a clobber.
2040
+ apme.collector.setLastClosedTurnResponse(hookSid, responseText);
2041
+ }
2042
+ }
2001
2043
  if (turnOpen && lastStart) {
2002
2044
  const now = Date.now();
2003
2045
  const taskId = (apme
@@ -3155,8 +3197,8 @@ export async function startDaemon(opts) {
3155
3197
  let previousDaemonState = State.IDLE;
3156
3198
  if (wakeWordEnabled) {
3157
3199
  voiceManager = new VoiceManager();
3158
- voiceManager.connectToServer().catch((err) => {
3159
- debug('daemon', `whisper-server connection failed: ${err}`);
3200
+ voiceManager.probeSpeechHelper().catch((err) => {
3201
+ debug('daemon', `speech helper probe failed: ${err}`);
3160
3202
  });
3161
3203
  voiceAssistant = new VoiceAssistantManager({
3162
3204
  sendPrompt: (text) => {
@@ -4723,6 +4765,43 @@ export async function startDaemon(opts) {
4723
4765
  for (const id of orphans) {
4724
4766
  apme.store.updateRun(id, { endedAt: Date.now(), taskCategory: '_empty' });
4725
4767
  }
4768
+ // 5. Reap ABANDONED runs — the ones step 4 cannot see. A daemon restart
4769
+ // (or crash) drops the in-memory session→run map, so every run that
4770
+ // was mid-session is left with ended_at NULL forever. Those carry real
4771
+ // prompts and turns, so they fail step 4's empty-shell predicate, and
4772
+ // because their TASK also never closes they are never evaluated: the
4773
+ // store had accumulated 65 open tasks against 9 closed ones.
4774
+ // Closing the run makes step 1 pick it up next tick; the task eval has
4775
+ // to be enqueued here, since its normal trigger (`onTaskClosed`) fires
4776
+ // from the collector's in-memory close path, which is exactly what
4777
+ // went missing.
4778
+ // Batched small: `enqueueTask` dispatches immediately with no
4779
+ // concurrency cap, so a backlog is drained a few per tick rather than
4780
+ // fired at the judge all at once.
4781
+ const abandoned = apme.store.listAbandonedRuns(APME_ABANDONED_RUN_STALE_SEC, 5);
4782
+ for (const run of abandoned) {
4783
+ if (apme.collector.isLiveRun(run.id))
4784
+ continue; // still owned by us
4785
+ const closedTasks = apme.store.reapAbandonedRun(run.id, run.lastActivity);
4786
+ for (const task of closedTasks) {
4787
+ // Judge only what there is something to judge. The backlog this
4788
+ // reaper drains predates the response-capture fix, so most of those
4789
+ // tasks hold prompts and tool calls but no reply — scoring them would
4790
+ // push hundreds of "judged against silence" rows into the scorecard,
4791
+ // the same noise `response_kind` exists to keep out. Closing the row
4792
+ // is the data-hygiene win; the eval is a bonus when it can be earned.
4793
+ const hasReply = apme.store.listTurnsForTask(task.id)
4794
+ .some((t) => typeof t.response === 'string' && t.response.trim().length > 0);
4795
+ if (!hasReply)
4796
+ continue;
4797
+ apme.runner.enqueueTask({
4798
+ runId: run.id, taskId: task.id,
4799
+ ...(task.category ? { category: task.category } : {}),
4800
+ boundarySignal: 'orphaned',
4801
+ });
4802
+ }
4803
+ debug('APME', `reaped abandoned run ${run.id.slice(0, 8)} — ${closedTasks.length} task(s) closed`);
4804
+ }
4726
4805
  }, 30_000); // every 30s
4727
4806
  core.addInterval(apmeEvalTimer);
4728
4807
  }
@@ -4752,7 +4831,6 @@ export async function startDaemon(opts) {
4752
4831
  focusRelay.stop();
4753
4832
  timelineRelay.stop();
4754
4833
  voiceAssistant?.stop();
4755
- voiceManager?.disconnectFromServer();
4756
4834
  bridgeLogStream.stop();
4757
4835
  // Flush synchronously — the process exits a few lines below and a pending
4758
4836
  // debounce timer would take the last turn's entries with it.