polygram 0.10.0-rc.27 → 0.10.0-rc.28
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.10.0-rc.
|
|
4
|
+
"version": "0.10.0-rc.28",
|
|
5
5
|
"description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
|
@@ -770,6 +770,12 @@ class TmuxProcess extends Process {
|
|
|
770
770
|
// The interrupt signal still wins here too — Bug 3: an
|
|
771
771
|
// interrupted tool turn writes no terminal JSONL `result`, so
|
|
772
772
|
// without this racer it would hang to `turnTimeoutMs`.
|
|
773
|
+
//
|
|
774
|
+
// B10: an outstanding `Agent` subagent counts as "tool in
|
|
775
|
+
// flight" exactly like a foreground `Bash` — its `tool-use`
|
|
776
|
+
// already set `toolUsedThisTurn`, so this branch catches the
|
|
777
|
+
// common case. The race where capture wins BEFORE the `Agent`
|
|
778
|
+
// tool_use line is tailed is handled by the §6 re-check below.
|
|
773
779
|
if (winner.kind === 'capture' && turn.toolUsedThisTurn) {
|
|
774
780
|
winner = await Promise.race([
|
|
775
781
|
turn.resultPromise.then((ev) => ({ kind: 'jsonl', ev })),
|
|
@@ -835,11 +841,41 @@ class TmuxProcess extends Process {
|
|
|
835
841
|
text = turn.text;
|
|
836
842
|
} else {
|
|
837
843
|
const lateGraceMs = this.lateGraceMs ?? 1500;
|
|
838
|
-
|
|
844
|
+
let late = await Promise.race([
|
|
839
845
|
turn.resultPromise.then((ev) => ({ kind: 'jsonl-late', ev })),
|
|
840
846
|
new Promise((r) => setTimeout(() => r({ kind: 'no-jsonl' }), lateGraceMs)),
|
|
841
847
|
]);
|
|
842
|
-
|
|
848
|
+
// B10 (shumorobot Music topic, 2026-05-20): the main agent
|
|
849
|
+
// delegated to an `Agent` subagent within ~7 s, then the main
|
|
850
|
+
// pane went quiescent for MINUTES while the subagent ran in
|
|
851
|
+
// its own sidechain. capture-pane read that quiescence as
|
|
852
|
+
// "done"; the main agent had emitted only the `Agent` call so
|
|
853
|
+
// no JSONL reply text existed yet, and the §6 fail-loud below
|
|
854
|
+
// fired ~grace-window in — closing a turn that was genuinely
|
|
855
|
+
// in flight. A subagent is still running iff its `Agent`
|
|
856
|
+
// tool_use has no matching `tool-result` yet. While one is
|
|
857
|
+
// outstanding, capture-pane quiescence of the MAIN pane is
|
|
858
|
+
// meaningless — the turn completes only when the subagent
|
|
859
|
+
// returns and the main agent emits its real terminal reply.
|
|
860
|
+
// Wait for that JSONL `result`, bounded by the absolute turn
|
|
861
|
+
// deadline so a genuinely wedged turn still fails loud.
|
|
862
|
+
if (late.kind === 'no-jsonl' && turn.outstandingSubagents.size > 0) {
|
|
863
|
+
this.emit('subagent-wait', {
|
|
864
|
+
outstanding: turn.outstandingSubagents.size,
|
|
865
|
+
turnId: turn.turnId,
|
|
866
|
+
});
|
|
867
|
+
late = await Promise.race([
|
|
868
|
+
turn.resultPromise.then((ev) => ({ kind: 'jsonl-late', ev })),
|
|
869
|
+
turnDeadlineP,
|
|
870
|
+
turn.interruptP.then(() => ({ kind: 'interrupt' })),
|
|
871
|
+
]);
|
|
872
|
+
}
|
|
873
|
+
if (late.kind === 'interrupt') {
|
|
874
|
+
turn.interrupted = true;
|
|
875
|
+
text = turn.text || '';
|
|
876
|
+
resultSubtype = 'interrupted';
|
|
877
|
+
stopReason = 'interrupted';
|
|
878
|
+
} else if (late.kind === 'jsonl-late') {
|
|
843
879
|
resolvedVia = 'jsonl-late';
|
|
844
880
|
text = turn.text || late.ev.text || '';
|
|
845
881
|
resultSubtype = late.ev.subtype || 'success';
|
|
@@ -968,6 +1004,13 @@ class TmuxProcess extends Process {
|
|
|
968
1004
|
text: '',
|
|
969
1005
|
toolUses: 0,
|
|
970
1006
|
toolUsedThisTurn: false,
|
|
1007
|
+
// B10: outstanding `Agent` (subagent/Task) tool_use ids — a
|
|
1008
|
+
// tool_use with no matching tool_result yet. A non-empty set
|
|
1009
|
+
// means a subagent is running in its own sidechain context: the
|
|
1010
|
+
// main pane goes quiescent for MINUTES while it works, and that
|
|
1011
|
+
// quiescence must NOT be read as turn completion. Cleared when
|
|
1012
|
+
// the matching `tool-result` arrives.
|
|
1013
|
+
outstandingSubagents: new Set(),
|
|
971
1014
|
stopReason: null,
|
|
972
1015
|
resultEvent: null,
|
|
973
1016
|
via: null, // autosteer: 'fold' | 'new-turn'
|
|
@@ -1145,8 +1188,25 @@ class TmuxProcess extends Process {
|
|
|
1145
1188
|
// the flag here so a transient capture-pane "ready" between
|
|
1146
1189
|
// tool calls cannot resolve a still-working turn.
|
|
1147
1190
|
t.toolUsedThisTurn = true;
|
|
1191
|
+
// B10: an `Agent` (subagent/Task) tool_use spawns a subagent
|
|
1192
|
+
// that runs for MINUTES in its own sidechain context while the
|
|
1193
|
+
// main pane sits quiescent. Track its id as outstanding until
|
|
1194
|
+
// the matching `tool-result` returns — `_runTurn` treats an
|
|
1195
|
+
// outstanding subagent as "turn still in flight" so the main
|
|
1196
|
+
// pane's quiescence cannot trip the §6 fail-loud.
|
|
1197
|
+
if (ev.name === 'Agent' && typeof ev.id === 'string') {
|
|
1198
|
+
t.outstandingSubagents.add(ev.id);
|
|
1199
|
+
}
|
|
1148
1200
|
}
|
|
1149
1201
|
this.emit('tool-use', ev.name);
|
|
1202
|
+
} else if (ev.type === 'tool-result') {
|
|
1203
|
+
// B10: a subagent returned. Clear the outstanding `Agent` call
|
|
1204
|
+
// it answers across every turn in the active group. A
|
|
1205
|
+
// tool-result for a non-Agent tool (or an id we never tracked)
|
|
1206
|
+
// is a harmless no-op — the set only ever held `Agent` ids.
|
|
1207
|
+
for (const t of this._activeGroup.turns) {
|
|
1208
|
+
t.outstandingSubagents.delete(ev.toolUseId);
|
|
1209
|
+
}
|
|
1150
1210
|
} else if (ev.type === 'usage') {
|
|
1151
1211
|
// Token-usage snapshot from JSONL. Cache for getContextUsage().
|
|
1152
1212
|
// Each assistant message carries the cumulative usage; latest
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* (ONCE per message.id, on finalize)
|
|
51
51
|
* - last-prompt → 'last-prompt' (fallback complete signal)
|
|
52
52
|
* - user (top-level string) → 'user-message' { text, parentUuid, promptId }
|
|
53
|
+
* - user tool_result block → 'tool-result' { toolUseId, isError }
|
|
53
54
|
* - queue-operation → 'queue-operation' { operation, content }
|
|
54
55
|
*
|
|
55
56
|
* Robust against malformed lines: skips them.
|
|
@@ -128,6 +129,31 @@ function extractContentBlocks(content) {
|
|
|
128
129
|
return { textParts, toolUses };
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Pull `tool_result` blocks out of a user message's `content` array.
|
|
134
|
+
* A user message with array content carries API-shaped tool feedback
|
|
135
|
+
* (NOT a user prompt). Each `tool_result` block names the `tool_use`
|
|
136
|
+
* it answers via `tool_use_id` — the matcher polygram's turn ledger
|
|
137
|
+
* uses to clear an outstanding `Agent`/subagent call.
|
|
138
|
+
*
|
|
139
|
+
* @returns {object[]} `tool-result` events, possibly empty.
|
|
140
|
+
*/
|
|
141
|
+
function extractToolResults(content) {
|
|
142
|
+
const out = [];
|
|
143
|
+
if (!Array.isArray(content)) return out;
|
|
144
|
+
for (const block of content) {
|
|
145
|
+
if (!block || typeof block !== 'object') continue;
|
|
146
|
+
if (block.type === 'tool_result' && typeof block.tool_use_id === 'string') {
|
|
147
|
+
out.push({
|
|
148
|
+
type: 'tool-result',
|
|
149
|
+
toolUseId: block.tool_use_id,
|
|
150
|
+
isError: block.is_error === true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
131
157
|
/**
|
|
132
158
|
* Join assistant text blocks the way the SDK backend's
|
|
133
159
|
* `extractAssistantText` does (rc.8 cross-backend parity): blocks
|
|
@@ -204,12 +230,15 @@ function parseLine(line) {
|
|
|
204
230
|
} else if (obj.type === 'last-prompt') {
|
|
205
231
|
out.push({ type: 'last-prompt', text: obj.lastPrompt ?? '' });
|
|
206
232
|
} else if (obj.type === 'user' && obj.message) {
|
|
207
|
-
// Top-level user message
|
|
208
|
-
//
|
|
209
|
-
//
|
|
233
|
+
// Top-level user message. String content is a user prompt. Array
|
|
234
|
+
// content carries API-shaped `tool_result` blocks (tool feedback,
|
|
235
|
+
// NOT a prompt) — those surface as `tool-result` events so the
|
|
236
|
+
// turn ledger can clear an outstanding `Agent`/subagent call.
|
|
210
237
|
const content = obj.message.content;
|
|
211
238
|
if (typeof content === 'string' && content.length > 0) {
|
|
212
239
|
out.push({ type: 'user-message', text: content });
|
|
240
|
+
} else {
|
|
241
|
+
out.push(...extractToolResults(content));
|
|
213
242
|
}
|
|
214
243
|
} else if (obj.type === 'attachment' && obj.attachment) {
|
|
215
244
|
const a = obj.attachment;
|
|
@@ -321,6 +350,13 @@ class SessionEventAggregator {
|
|
|
321
350
|
parentUuid: obj.parentUuid ?? null,
|
|
322
351
|
promptId: obj.promptId ?? null,
|
|
323
352
|
});
|
|
353
|
+
} else {
|
|
354
|
+
// Array content — API-shaped `tool_result` blocks. A subagent
|
|
355
|
+
// (`Agent` tool) returning to the main agent surfaces here;
|
|
356
|
+
// the turn ledger keys on `toolUseId` to clear the outstanding
|
|
357
|
+
// subagent call so capture-pane quiescence of the main pane is
|
|
358
|
+
// not mistaken for turn completion while the subagent runs.
|
|
359
|
+
out.push(...extractToolResults(content));
|
|
324
360
|
}
|
|
325
361
|
} else if (obj.type === 'last-prompt') {
|
|
326
362
|
out.push({ type: 'last-prompt', text: obj.lastPrompt ?? '' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.10.0-rc.
|
|
3
|
+
"version": "0.10.0-rc.28",
|
|
4
4
|
"description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
|
|
5
5
|
"main": "lib/ipc/client.js",
|
|
6
6
|
"bin": {
|