@openduo/duoduo 0.3.7 → 0.4.1

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.
@@ -658,8 +658,8 @@ body{
658
658
  if (cortex.length > 0) {
659
659
  var h = '<span class="sig-label">cortex</span>';
660
660
  cortex.forEach(function(s){
661
- var color = s.status==="active"?"running":s.status==="error"?"alert":s.status==="ended"?"off":"standby";
662
- var tip = s.session_key+"\n"+s.status+" \u2022 "+(s.health||"ok")+"\nlast: "+timeAgo(s.last_event_at)+"\ncreated: "+timeAgo(s.created_at)+(s.cwd?"\ncwd: "+s.cwd:"");
661
+ var color = s.last_error?"alert":s.status==="active"?"running":s.status==="error"?"alert":s.status==="ended"?"off":"standby";
662
+ var tip = s.session_key+"\n"+s.status+" \u2022 "+(s.health||"ok")+"\nlast: "+timeAgo(s.last_event_at)+"\ncreated: "+timeAgo(s.created_at)+(s.cwd?"\ncwd: "+s.cwd:"")+(s.last_error?"\n\u26A0 error: "+s.last_error.message+" ("+timeAgo(s.last_error.at)+")":"");
663
663
  h+='<div class="ind circle '+color+'" data-key="'+esc(s.session_key)+'" data-tip="'+esc(tip)+'"></div>';
664
664
  });
665
665
  groups.push(h);
@@ -706,7 +706,36 @@ body{
706
706
  var tip = "last tick: "+timeAgo(status.cadence.last_tick)+"\ninterval: "+(status.cadence.interval_ms/1000)+"s";
707
707
  groups.push('<span class="sig-label" data-tip="'+esc(tip)+'">\u23F1 '+timeAgo(status.cadence.last_tick)+'</span>');
708
708
  }
709
- signalEl.innerHTML = groups.map(function(g){return'<div class="sig-group">'+g+'</div>'}).join('<div class="sig-sep"></div>');
709
+ var newHtml = groups.map(function(g){return'<div class="sig-group">'+g+'</div>'}).join('<div class="sig-sep"></div>');
710
+ // Diff-update: preserve existing DOM nodes to keep blink animations alive.
711
+ // Only replace innerHTML when the structural content (ignoring blink class) changes.
712
+ var stripped = function(html) { return html.replace(/ blink/g, ""); };
713
+ if (stripped(signalEl.innerHTML) !== stripped(newHtml)) {
714
+ // Save active blink keys before rebuild
715
+ var blinking = new Set();
716
+ signalEl.querySelectorAll(".blink").forEach(function(el) { if (el.dataset.key) blinking.add(el.dataset.key); });
717
+ signalEl.innerHTML = newHtml;
718
+ // Restore blink on rebuilt nodes
719
+ blinking.forEach(function(key) {
720
+ var dot = signalEl.querySelector('[data-key="' + CSS.escape(key) + '"]');
721
+ if (dot) dot.classList.add("blink");
722
+ });
723
+ } else {
724
+ // Structure unchanged — patch individual dot colors/tips in-place
725
+ signalEl.querySelectorAll("[data-key]").forEach(function(dot) {
726
+ var key = dot.dataset.key;
727
+ var tmp = document.createElement("div");
728
+ tmp.innerHTML = newHtml;
729
+ var newDot = tmp.querySelector('[data-key="' + CSS.escape(key) + '"]');
730
+ if (newDot) {
731
+ // Update color class without touching blink
732
+ var hadBlink = dot.classList.contains("blink");
733
+ dot.className = newDot.className;
734
+ if (hadBlink) dot.classList.add("blink");
735
+ dot.dataset.tip = newDot.dataset.tip || "";
736
+ }
737
+ });
738
+ }
710
739
  }
711
740
 
712
741
  // --- Tooltip ---
@@ -10,9 +10,9 @@ Look for `DUODUO.md` in subdirectories to learn how each part works.
10
10
  - `cadence/` — background task queue and rhythm system
11
11
  - `channels/` — per-channel instance config, inbox, outbox, sessions
12
12
  - `jobs/` — scheduled recurring tasks (cron-based)
13
- - `sessions/` — per-session mailboxes and state
13
+ - `sessions/` — per-session state, mailboxes, and metadata (see sessions/DUODUO.md)
14
14
  - `events/` — Spine (append-only event log, my history)
15
- - `registry/` — runtime status snapshots
15
+ - `registry/` — session status snapshots for dashboard and external tools
16
16
  - `outbox/` — pending egress messages
17
17
  - `ingress/` — raw channel inputs before normalization
18
18
  - `telemetry/` — structured runtime latency samples for offline aggregation
@@ -0,0 +1,54 @@
1
+ # Sessions — Per-Session Runtime State
2
+
3
+ Each session gets a directory under `var/sessions/` named by the SHA-256
4
+ hash of its session key. This is where my per-session persistent state lives.
5
+
6
+ ## Directory Layout
7
+
8
+ ```
9
+ var/sessions/{sha256-hash}/
10
+ ├── state.json ← persistent session state (source of truth)
11
+ ├── meta.md ← session metadata (routes, display name, kind)
12
+ ├── inbox/ ← raw incoming items (*.pending files)
13
+ └── mailbox/
14
+ ├── mailbox.md ← rendered mailbox for SDK consumption
15
+ ├── pending/ ← merged items awaiting drain (*.item.json)
16
+ └── notes.jsonl ← append-only drain notes
17
+ ```
18
+
19
+ ## state.json
20
+
21
+ The single source of truth for persistent session data:
22
+
23
+ - **Identity** (immutable after creation): `session_key`, `cwd`, `plane`,
24
+ `permission_profile`, `created_at`
25
+ - **SDK session**: `sdk_session_id` — the Claude Agent SDK session ID for
26
+ conversation resume. Written when SDK confirms a new session, cleared on
27
+ `/clear` or `/reset`.
28
+ - **Drain watermark**: `last_event_id`, `last_event_at` — crash recovery
29
+ markers. Ensures the `outbox write → watermark → mailbox finalize`
30
+ ordering survives process crashes.
31
+ - **Channel capabilities**: declared by `channel.pull` consumers.
32
+ - **Pending injections**: gateway notices, interrupted context, skip rewind,
33
+ outbound attachments — drain-scoped data consumed on the next turn.
34
+ - **Last error**: `last_error` — most recent drain failure. Cleared on next
35
+ successful drain. Used by dashboard for error observability.
36
+
37
+ ## Mailbox Pipeline
38
+
39
+ Messages flow through two stages:
40
+
41
+ 1. `inbox/*.pending` → raw items from ingress (one file per message)
42
+ 2. `mailbox/pending/*.item.json` → merged items ready for SDK consumption
43
+
44
+ The drain loop merges inbox into mailbox, processes items, then deletes
45
+ completed files. A crash at any stage is recoverable: the watermark in
46
+ state.json tells the runner which items were already processed.
47
+
48
+ ## meta.md
49
+
50
+ YAML frontmatter with session metadata:
51
+
52
+ - `session_key`, `display_name`, `kind` (channel/job/meta/system)
53
+ - `routes` — delivery routes for job result notifications
54
+ - `owner_session` — parent session for job sessions