@songsid/agend 2.1.4-beta.45 → 2.1.4-beta.47

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-management
3
- description: Session stores, forking, and auth-pause recovery
3
+ description: Session stores, forking, cross-backend session recovery, and auth-pause recovery
4
4
  roles: [general]
5
5
  ---
6
6
 
@@ -12,17 +12,180 @@ When AgEnD sees `auth_error` it **pauses** that instance (`pausePending` sticky)
12
12
  - Messages while paused stay in the **queue** — do not re-send.
13
13
  - After the user re-auths: `wake` / normal wake clears `pausePending`.
14
14
 
15
- ## Where sessions live
15
+ ## Session recovery: what each backend can do
16
16
 
17
- | | kiro-cli | claude-code |
18
- |---|---|---|
19
- | Store | `~/.kiro/sessions/cli/<uuid>.json` | `~/.claude/projects/<path-encoded>/*.jsonl` |
20
- | Reload | `/chat load <file>` | `--continue` / `--resume <id>` |
17
+ Verified by running each one, not read from docs.
21
18
 
22
- `<path-encoded>` = absolute cwd with `/` → `-`.
19
+ | Backend | List all sessions | Restore a *specific* session | Read summary only |
20
+ |---|---|---|---|
21
+ | **kiro-cli** | ✅ `conversations_v2` | ✅ export → `/chat load` | ✅ `latest_summary` |
22
+ | **grok** | ✅ `session_search.sqlite` (FTS5) | ✅ `grok --resume <id>` | ✅ `summary.json` |
23
+ | **claude-code** | ✅ list `*.jsonl` in project dir | ✅ `claude -r <id>` | ✅ `ai-title` line |
24
+ | **codex** | ⚠️ no index — parse rollout files | ✅ `codex exec resume <id>` | ❌ must parse the rollout |
25
+ | **antigravity** | ❌ index covers ~23%, 2 months stale | ✅ `agy --conversation <id>` | ⚠️ `title` empty, use `preview` |
26
+
27
+ **Restoring a specific session is a manual operator action, not an AgEnD feature.** AgEnD always launches a backend on its *most recent* session (`--continue` / `--last` / `--resume`). To reach any other session someone must drive the pane or the CLI by hand.
28
+
29
+ **kiro is the exception worth knowing:** its export → `/chat load` works on a **live, idle instance** with no restart. Every other backend needs the instance stopped (or the CLI run manually) because the session is chosen by a launch flag.
30
+
31
+ ## Safety — applies to every backend below
32
+
33
+ 1. **Reads are read-only.** Open SQLite with `readonly` and never write to these databases; they belong to a running CLI, and the `-wal`/`-shm` files are live.
34
+ 2. **Restore through the CLI's own mechanism** — a resume flag, or kiro's `/chat load`. Never edit a session DB or JSONL to "fix" a conversation.
35
+ 3. **Confirm the target instance is idle first** (tmux shows the ready prompt). Restoring into a working pane interrupts a turn.
36
+ 4. Nothing here needs `sudo` or touches another user's files.
37
+
38
+ ---
39
+
40
+ ## kiro-cli
41
+
42
+ - **Store:** `~/.kiro/sessions/cli/<uuid>.json`
43
+ - **DB:** `~/.local/share/kiro-cli/data.sqlite3`, table `conversations_v2`
44
+ - `key` — the instance's working directory
45
+ - `conversation_id` — session ID
46
+ - `value` — full session state JSON (**same format as `/chat save`**)
47
+ - `created_at` / `updated_at` — epoch ms
48
+
49
+ **List sessions for an instance**
50
+ ```python
51
+ import sqlite3, os
52
+ db = os.path.expanduser('~/.local/share/kiro-cli/data.sqlite3')
53
+ cur = sqlite3.connect(f'file:{db}?mode=ro', uri=True).cursor()
54
+ cur.execute(
55
+ "SELECT conversation_id, updated_at FROM conversations_v2 WHERE key LIKE ? ORDER BY updated_at DESC LIMIT 5",
56
+ ('%<instance-name>%',)
57
+ )
58
+ # Most recent = currently active. De-duplicate by conversation_id.
59
+ ```
60
+
61
+ **Restore (works on a live idle instance — no restart)**
62
+ ```python
63
+ cur.execute("SELECT value FROM conversations_v2 WHERE conversation_id = ?", (target_cid,))
64
+ open('<instance-workspace>/restore.json', 'w').write(cur.fetchone()[0])
65
+ ```
66
+ ```bash
67
+ tmux send-keys -t agend:<instance> '/chat load restore.json' Enter
68
+ # Success: "✔ Imported chat session state", context % jumps up
69
+ ```
70
+
71
+ **Summary without restoring:** the session JSON has `latest_summary` (list; `[1]` is the text) and `history`.
72
+
73
+ ---
74
+
75
+ ## grok
76
+
77
+ The most capable backend here. It also ships its own manual at `~/.grok/docs/user-guide/17-sessions.md`.
78
+
79
+ - **Store:** `~/.grok/sessions/<URL-encoded cwd>/<session-id>/`
80
+ - `summary.json` — index entry: summary, timestamps, model, message counts
81
+ - `updates.jsonl` — the authoritative conversation log that drives resume
82
+ - also `chat_history.jsonl`, `plan.json`, `rewind_points.jsonl`, `signals.json`
83
+ - **Index:** `~/.grok/sessions/session_search.sqlite` → `session_docs(session_id, cwd, updated_at, title, content)` plus a `session_docs_fts` FTS5 table, so you can full-text search past conversations.
84
+
85
+ **List / search**
86
+ ```python
87
+ import sqlite3, os
88
+ db = os.path.expanduser('~/.grok/sessions/session_search.sqlite')
89
+ cur = sqlite3.connect(f'file:{db}?mode=ro', uri=True).cursor()
90
+ cur.execute("SELECT session_id, title, updated_at FROM session_docs ORDER BY updated_at DESC LIMIT 10")
91
+ # Full-text over conversation content:
92
+ cur.execute("SELECT session_id FROM session_docs_fts WHERE session_docs_fts MATCH ?", ('deploy',))
93
+ ```
94
+
95
+ **Restore:** `grok --resume <session-id-or-title>` (a UUID is always treated as an ID; anything else matches a title in the current directory). Bare `grok --resume` takes the most recent for that cwd. In the TUI, `/resume` opens a picker that searches conversation content as you type.
96
+
97
+ **Summary without restoring:** read `summary.json` directly.
98
+
99
+ > ⚠️ **Titles can be ours, not the user's.** Grok auto-titles from the first prompt, and AgEnD sometimes injects a session snapshot as that first prompt — so a title may read `[system:session-snapshot] ## Previous session…`. Filter that prefix before showing titles to a user, and fall back to `updated_at` + message count.
100
+
101
+ ---
102
+
103
+ ## claude-code
104
+
105
+ - **Store:** `~/.claude/projects/<path-encoded>/<session-uuid>.jsonl` — **the filename is the session ID**.
106
+ - `<path-encoded>` = the absolute cwd with every `/` replaced by `-`.
107
+
108
+ **List sessions for an instance**
109
+ ```bash
110
+ enc=$(echo "<instance-working-dir>" | sed 's|/|-|g')
111
+ ls -t ~/.claude/projects/"$enc"/*.jsonl # newest first; basename = session id
112
+ ```
113
+
114
+ **Summary without restoring** — the transcript contains auto-title lines; take the last one:
115
+ ```bash
116
+ grep -h '"type":"ai-title"' <file>.jsonl | tail -1
117
+ # {"type":"ai-title","aiTitle":"Review updated instructions","sessionId":"..."}
118
+ ```
119
+ A session with no `ai-title` line is simply untitled — say so rather than inventing a label.
120
+
121
+ **Restore:** `claude -r <session-id>` (or `claude --resume` for a picker that accepts a search term). `-c` / `--continue` takes the most recent for the cwd — that is what AgEnD launches with.
122
+
123
+ > ⚠️ **Check the ID exists before using it.** AgEnD guards `--continue` precisely because resuming a session that isn't there sends claude into a restart loop. Confirm the `<id>.jsonl` file is present in the encoded project dir first.
124
+
125
+ ---
126
+
127
+ ## codex
128
+
129
+ Restoring works well; **listing is the weak part — there is no index table.**
130
+
131
+ - **Store:** `~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl`
132
+ - Line 0 of every rollout is the header:
133
+ `{"type":"session_meta","payload":{"session_id":…,"cwd":…,"timestamp":…}}`
134
+
135
+ **List sessions for a working directory** — walk the tree and read only the first line of each file:
136
+ ```python
137
+ import json, os, pathlib
138
+ root = pathlib.Path(os.path.expanduser('~/.codex/sessions'))
139
+ want = '<instance-working-dir>'
140
+ out = []
141
+ for p in root.rglob('*.jsonl'):
142
+ try:
143
+ head = json.loads(p.open(encoding='utf-8').readline())
144
+ except Exception:
145
+ continue
146
+ if head.get('type') == 'session_meta' and head['payload'].get('cwd') == want:
147
+ out.append((head['payload']['timestamp'], head['payload']['session_id']))
148
+ for ts, sid in sorted(out, reverse=True):
149
+ print(ts, sid)
150
+ ```
151
+ It opens many files, but only one line each, so it stays cheap.
152
+
153
+ **Restore:** `codex exec resume <session-id> "<prompt>"` non-interactively, or `codex resume <session-id>` for the TUI (bare `codex resume` opens a picker). `codex resume --last` is what AgEnD launches with. The ID argument also accepts a session *name*.
154
+
155
+ **Summary:** ❌ none available without parsing. `session_meta` carries only id/cwd/timestamp; to describe a session you must read further `event_msg` lines. Tell the user the timestamp and let them pick, rather than guessing at a topic.
156
+
157
+ ---
158
+
159
+ ## antigravity (agy)
160
+
161
+ Restore is reliable; **the session list is not — do not present it as complete.**
162
+
163
+ - **Store:** `~/.gemini/antigravity-cli/conversations/<uuid>.db` — one SQLite database per conversation.
164
+ - **Index:** `~/.gemini/antigravity-cli/conversation_summaries.db`, table `conversation_summaries`
165
+ (`conversation_id`, `title`, `preview`, `step_count`, `last_modified_time`, `workspace_uris`, …)
166
+
167
+ **List (with the caveat below)**
168
+ ```python
169
+ import sqlite3, os
170
+ db = os.path.expanduser('~/.gemini/antigravity-cli/conversation_summaries.db')
171
+ cur = sqlite3.connect(f'file:{db}?mode=ro', uri=True).cursor()
172
+ cur.execute("SELECT conversation_id, preview, step_count, last_modified_time "
173
+ "FROM conversation_summaries ORDER BY last_modified_time DESC")
174
+ ```
175
+
176
+ **Restore:** `agy --conversation <conversation-id>`. Verified: it loads the full prior conversation, not just a stub. `-c` / `--continue` takes the most recent — that is what AgEnD launches with.
177
+
178
+ > ⚠️ **The index is badly incomplete.** Measured on a live machine: 31 conversation databases on disk but only 14 index rows, of which just **7** matched a real conversation — about 23% coverage — and the newest row was **two months old**. Whatever you list, say plainly that it is a partial view and that older or recent conversations may be missing entirely. If the user knows a conversation ID, `--conversation` still works even when the index does not show it.
179
+ >
180
+ > ⚠️ **`title` is empty in practice** — use `preview` (the first user message) as the label, plus `step_count` for size.
181
+ >
182
+ > ⚠️ **`agy -p` (print mode) records nothing.** A non-interactive run leaves no conversation behind, so don't expect one to show up afterwards.
183
+
184
+ ---
23
185
 
24
186
  ## Fork (source must be idle)
25
187
 
26
188
  - **kiro:** `/chat save name.json -f` → `create_instance` → copy workspace file → `/chat load name.json`
27
- - **claude-code:** copy newest `*.jsonl` into target's encoded project dir → start (uses `--continue`)
189
+ - **claude-code:** copy the newest `*.jsonl` into the target's encoded project dir → start (uses `--continue`)
190
+ - **grok:** `/fork` inside the TUI branches the conversation into a peer session
28
191
  - Prefer `replace_instance` when the whole session is poisoned (see instance-lifecycle)
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pane-text helpers for TUIs that DROP an Enter arriving while they are busy
3
+ * but keep the typed text as typeahead (Kiro's legacy UI, verified live on
4
+ * kiro-cli 2.21.0). For such a TUI, output silence is not readiness: a shell or
5
+ * MCP tool can run for many seconds without painting anything, and the daemon's
6
+ * 2s-silence idle gate would paste into that window. The text then surfaces in
7
+ * the prompt row after the turn, unsubmitted, and the next delivery's Enter
8
+ * submits both messages as one.
9
+ *
10
+ * Everything here is a pure function over a `capture-pane -p` snapshot so the
11
+ * exact frames captured during the reproduction can be replayed in tests.
12
+ */
13
+ /** The last row with any non-whitespace content, or null for a blank pane. */
14
+ export declare function lastNonBlankRow(pane: string): string | null;
15
+ /**
16
+ * Bottom-anchored readiness: the prompt marker must be on the LAST non-blank
17
+ * row. A prompt higher up is history; during a tool call Kiro's bottom row is
18
+ * the tool banner ("Purpose: …"), and while generating it is the spinner.
19
+ */
20
+ export declare function bottomRowIsReady(pane: string, promptPattern: RegExp): boolean;
21
+ /**
22
+ * Text sitting in the input area: everything from the LAST prompt row to the end
23
+ * of the pane, with the prompt prefix removed and continuation rows (a long
24
+ * paste wraps) joined. Returns null when no prompt row is on screen.
25
+ *
26
+ * Callers must first rule out "busy": while generating, the bottom rows are the
27
+ * assistant's output and the last prompt row is history, so this text would be
28
+ * the previous turn's echo, not pending input.
29
+ */
30
+ export declare function inputAreaText(pane: string, promptPattern: RegExp): string | null;
31
+ /**
32
+ * A distinctive head of what we pasted: the first 24 non-space characters of
33
+ * the message BODY — i.e. after the routing header every AgEnD message starts
34
+ * with (`[user:… id:…]` / `[from:…]`). Two messages from the same sender share
35
+ * that header verbatim, so a header-based signature could not tell "our text is
36
+ * still there" from "a different message is there". Long enough to be specific,
37
+ * short enough to survive the TUI re-wrapping the row at the terminal width.
38
+ */
39
+ export declare function pastedTextSignature(formatted: string): string;
40
+ /**
41
+ * True when the text we just pasted is still sitting in the input area — i.e.
42
+ * the Enter did not submit it. Compares whitespace-insensitively because the
43
+ * TUI re-wraps pasted text to the pane width.
44
+ */
45
+ export declare function pasteLeftInInput(pane: string, promptPattern: RegExp, formatted: string): boolean;
46
+ /**
47
+ * True when the input area holds an AgEnD message left behind by an earlier
48
+ * delivery whose Enter was dropped (e.g. before a daemon restart, when the exact
49
+ * text is no longer known). Placeholder hints Kiro prints in the prompt row
50
+ * ("Not sure where to start? …") never carry the marker.
51
+ */
52
+ export declare function strandedAgendMessageInInput(pane: string, promptPattern: RegExp): boolean;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Pane-text helpers for TUIs that DROP an Enter arriving while they are busy
3
+ * but keep the typed text as typeahead (Kiro's legacy UI, verified live on
4
+ * kiro-cli 2.21.0). For such a TUI, output silence is not readiness: a shell or
5
+ * MCP tool can run for many seconds without painting anything, and the daemon's
6
+ * 2s-silence idle gate would paste into that window. The text then surfaces in
7
+ * the prompt row after the turn, unsubmitted, and the next delivery's Enter
8
+ * submits both messages as one.
9
+ *
10
+ * Everything here is a pure function over a `capture-pane -p` snapshot so the
11
+ * exact frames captured during the reproduction can be replayed in tests.
12
+ */
13
+ /** Marker every AgEnD-pasted message starts with (see formatInboundMessage). */
14
+ const AGEND_MESSAGE_MARKER = /\[(?:user|from|system):/;
15
+ function rows(pane) {
16
+ return pane.replace(/\r/g, "").split("\n");
17
+ }
18
+ /** The last row with any non-whitespace content, or null for a blank pane. */
19
+ export function lastNonBlankRow(pane) {
20
+ const all = rows(pane);
21
+ for (let i = all.length - 1; i >= 0; i--) {
22
+ if (all[i].trim())
23
+ return all[i];
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Bottom-anchored readiness: the prompt marker must be on the LAST non-blank
29
+ * row. A prompt higher up is history; during a tool call Kiro's bottom row is
30
+ * the tool banner ("Purpose: …"), and while generating it is the spinner.
31
+ */
32
+ export function bottomRowIsReady(pane, promptPattern) {
33
+ const row = lastNonBlankRow(pane);
34
+ return row != null && rowMatches(row, promptPattern);
35
+ }
36
+ /**
37
+ * Text sitting in the input area: everything from the LAST prompt row to the end
38
+ * of the pane, with the prompt prefix removed and continuation rows (a long
39
+ * paste wraps) joined. Returns null when no prompt row is on screen.
40
+ *
41
+ * Callers must first rule out "busy": while generating, the bottom rows are the
42
+ * assistant's output and the last prompt row is history, so this text would be
43
+ * the previous turn's echo, not pending input.
44
+ */
45
+ export function inputAreaText(pane, promptPattern) {
46
+ const all = rows(pane);
47
+ let promptIndex = -1;
48
+ for (let i = all.length - 1; i >= 0; i--) {
49
+ if (rowMatches(all[i], promptPattern)) {
50
+ promptIndex = i;
51
+ break;
52
+ }
53
+ }
54
+ if (promptIndex < 0)
55
+ return null;
56
+ const promptRow = all[promptIndex];
57
+ const match = promptRow.match(withoutMultiline(promptPattern));
58
+ const afterPrompt = match ? promptRow.slice((match.index ?? 0) + match[0].length) : promptRow;
59
+ const tail = all.slice(promptIndex + 1).map(r => r.trimEnd()).filter(r => r.trim());
60
+ return [afterPrompt.trim(), ...tail].join("\n").trim();
61
+ }
62
+ /**
63
+ * A distinctive head of what we pasted: the first 24 non-space characters of
64
+ * the message BODY — i.e. after the routing header every AgEnD message starts
65
+ * with (`[user:… id:…]` / `[from:…]`). Two messages from the same sender share
66
+ * that header verbatim, so a header-based signature could not tell "our text is
67
+ * still there" from "a different message is there". Long enough to be specific,
68
+ * short enough to survive the TUI re-wrapping the row at the terminal width.
69
+ */
70
+ export function pastedTextSignature(formatted) {
71
+ const firstLine = formatted.split(/\r?\n/).find(l => l.trim()) ?? "";
72
+ const body = firstLine.replace(/^\s*(?:\[[^\]]*\]\s*)+/, "");
73
+ const source = body.trim() ? body : firstLine;
74
+ return source.replace(/\s+/g, "").slice(0, 24);
75
+ }
76
+ /**
77
+ * True when the text we just pasted is still sitting in the input area — i.e.
78
+ * the Enter did not submit it. Compares whitespace-insensitively because the
79
+ * TUI re-wraps pasted text to the pane width.
80
+ */
81
+ export function pasteLeftInInput(pane, promptPattern, formatted) {
82
+ const input = inputAreaText(pane, promptPattern);
83
+ if (!input)
84
+ return false;
85
+ const signature = pastedTextSignature(formatted);
86
+ if (!signature)
87
+ return false;
88
+ return input.replace(/\s+/g, "").includes(signature);
89
+ }
90
+ /**
91
+ * True when the input area holds an AgEnD message left behind by an earlier
92
+ * delivery whose Enter was dropped (e.g. before a daemon restart, when the exact
93
+ * text is no longer known). Placeholder hints Kiro prints in the prompt row
94
+ * ("Not sure where to start? …") never carry the marker.
95
+ */
96
+ export function strandedAgendMessageInInput(pane, promptPattern) {
97
+ const input = inputAreaText(pane, promptPattern);
98
+ return input != null && AGEND_MESSAGE_MARKER.test(input);
99
+ }
100
+ function rowMatches(row, pattern) {
101
+ return withoutMultiline(pattern).test(row);
102
+ }
103
+ /** Row-local test: strip flags that would let `^`/`$` span rows or keep state. */
104
+ function withoutMultiline(pattern) {
105
+ return new RegExp(pattern.source, pattern.flags.replace(/[gmy]/g, ""));
106
+ }
107
+ //# sourceMappingURL=pane-input-residue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pane-input-residue.js","sourceRoot":"","sources":["../src/pane-input-residue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,gFAAgF;AAChF,MAAM,oBAAoB,GAAG,yBAAyB,CAAC;AAEvD,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,aAAqB;IAClE,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,IAAI,IAAI,UAAU,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,aAAqB;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,CAAC;YAAC,WAAW,GAAG,CAAC,CAAC;YAAC,MAAM;QAAC,CAAC;IACpE,CAAC;IACD,IAAI,WAAW,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9F,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACpF,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;IACrE,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9C,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,aAAqB,EAAE,SAAiB;IACrF,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,SAAS,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAY,EAAE,aAAqB;IAC7E,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACjD,OAAO,KAAK,IAAI,IAAI,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,OAAe;IAC9C,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,kFAAkF;AAClF,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;AACzE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.1.4-beta.45",
3
+ "version": "2.1.4-beta.47",
4
4
  "description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
5
5
  "type": "module",
6
6
  "bin": {