@openduo/duoduo 0.3.7 → 0.4.0
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.
- package/bootstrap/dashboard.html +32 -3
- package/bootstrap/var/DUODUO.md +2 -2
- package/bootstrap/var/sessions/DUODUO.md +54 -0
- package/dist/release/cli.js +452 -423
- package/dist/release/daemon.js +280 -251
- package/dist/release/feishu-gateway.js +28 -28
- package/dist/release/stdio.js +30 -30
- package/package.json +2 -2
package/bootstrap/dashboard.html
CHANGED
|
@@ -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
|
-
|
|
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 ---
|
package/bootstrap/var/DUODUO.md
CHANGED
|
@@ -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
|
|
13
|
+
- `sessions/` — per-session state, mailboxes, and metadata (see sessions/DUODUO.md)
|
|
14
14
|
- `events/` — Spine (append-only event log, my history)
|
|
15
|
-
- `registry/` —
|
|
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
|