polygram 0.17.7 → 0.17.10
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/lib/process/cli-process.js +45 -10
- package/lib/session-key.js +13 -1
- package/package.json +1 -1
|
@@ -350,6 +350,10 @@ class CliProcess extends Process {
|
|
|
350
350
|
// Monotonic count of work hooks (all but the terminal Stop) — the rung-2
|
|
351
351
|
// no-reply backstop snapshots it at Stop capture to detect a later resume.
|
|
352
352
|
this._workHookSeq = 0;
|
|
353
|
+
// In-flight sub-agent starts (pushed on Agent PreToolUse, spliced on SubagentStop).
|
|
354
|
+
// Initialized here so a SubagentStop arriving before any Agent start (a lagged/orphan
|
|
355
|
+
// teardown on a fresh proc) reads a length safely instead of throwing.
|
|
356
|
+
this._pendingSubagentStarts = [];
|
|
353
357
|
// 0.13 D2: the InputLedger — every user-shaped input written to the bridge
|
|
354
358
|
// gets an observable lifecycle: written → seen → resolved | dropped |
|
|
355
359
|
// superseded | fold-suspected. Pre-P3, injectUserMessage minted a turn_id
|
|
@@ -2040,12 +2044,32 @@ class CliProcess extends Process {
|
|
|
2040
2044
|
* sibling's text; otherwise leave the status (nothing more to send).
|
|
2041
2045
|
*/
|
|
2042
2046
|
_resolveTurnDelivery(pending, turnId) {
|
|
2047
|
+
const out = this._computeTurnDelivery(pending, turnId);
|
|
2048
|
+
// 0.17.8 characterize-first: the channels double-delivery (shumorobot Music,
|
|
2049
|
+
// 2026-06-28 — reply tool sent #2147, then the daemon re-sent result.text as
|
|
2050
|
+
// #2149) is a turn that resolved with alreadyDelivered=false despite a reply tool
|
|
2051
|
+
// delivery. Log the chosen branch + counts so the next occurrence pins WHY (the
|
|
2052
|
+
// leading hypothesis: an interrupted turn loses its recorded reply → the
|
|
2053
|
+
// zero-reply Stop-fallback re-delivers last_assistant_message).
|
|
2054
|
+
this._logEvent('cli-resolve-delivery', {
|
|
2055
|
+
turn_id: turnId, session_key: this.sessionKey, backend: this.backend,
|
|
2056
|
+
branch: out.branch,
|
|
2057
|
+
already_delivered: out.alreadyDelivered,
|
|
2058
|
+
reply_count: pending.replies.length,
|
|
2059
|
+
interim_count: pending._interimReplyCount || 0,
|
|
2060
|
+
has_stop_data: !!pending._stopHookData,
|
|
2061
|
+
text_len: (out.text || '').length,
|
|
2062
|
+
});
|
|
2063
|
+
return { text: out.text, alreadyDelivered: out.alreadyDelivered };
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
_computeTurnDelivery(pending, turnId) {
|
|
2043
2067
|
const norm = (s) => (s || '').trim();
|
|
2044
2068
|
const interimText = pending.replies.join('\n\n');
|
|
2045
2069
|
const fallbackText = pending._stopHookData?.lastAssistantMessage || '';
|
|
2046
2070
|
|
|
2047
2071
|
if (this._turnHasFinalReply(pending)) {
|
|
2048
|
-
return { text: interimText, alreadyDelivered: true };
|
|
2072
|
+
return { text: interimText, alreadyDelivered: true, branch: 'final-reply' };
|
|
2049
2073
|
}
|
|
2050
2074
|
if (pending.replies.length === 0) {
|
|
2051
2075
|
// 0.12 Phase 1.7 fallback: no reply tool call landed — use the Stop hook's
|
|
@@ -2068,7 +2092,7 @@ class CliProcess extends Process {
|
|
|
2068
2092
|
rescued_len: text.length, ack_len: norm(pending._consumedByText).length,
|
|
2069
2093
|
});
|
|
2070
2094
|
}
|
|
2071
|
-
return { text, alreadyDelivered };
|
|
2095
|
+
return { text, alreadyDelivered, branch: 'zero-reply' };
|
|
2072
2096
|
}
|
|
2073
2097
|
// Interim-only: the turn delivered ONLY status/progress promises ("give me a
|
|
2074
2098
|
// couple min") and never a final reply. If claude produced a substantive final
|
|
@@ -2084,9 +2108,9 @@ class CliProcess extends Process {
|
|
|
2084
2108
|
turn_id: turnId, session_key: this.sessionKey, backend: this.backend,
|
|
2085
2109
|
rescued_len: fallbackText.length, interim_count: pending.replies.length,
|
|
2086
2110
|
});
|
|
2087
|
-
return { text: fallbackText, alreadyDelivered: false };
|
|
2111
|
+
return { text: fallbackText, alreadyDelivered: false, branch: 'interim-rescue' };
|
|
2088
2112
|
}
|
|
2089
|
-
return { text: interimText, alreadyDelivered: true };
|
|
2113
|
+
return { text: interimText, alreadyDelivered: true, branch: 'interim-noop' };
|
|
2090
2114
|
}
|
|
2091
2115
|
|
|
2092
2116
|
_finalizeTurn(turnId) {
|
|
@@ -2930,15 +2954,26 @@ class CliProcess extends Process {
|
|
|
2930
2954
|
|
|
2931
2955
|
// 0.13 D1: every hook event is same-session ACTIVITY for the finalizer
|
|
2932
2956
|
// ladder (generalizes the 2026-06-08 WA-topic fix, which only extended on
|
|
2933
|
-
// Pre/PostToolUse) — EXCEPT
|
|
2934
|
-
//
|
|
2935
|
-
//
|
|
2936
|
-
|
|
2957
|
+
// Pre/PostToolUse) — EXCEPT terminal signals, which are not work: noting them
|
|
2958
|
+
// as activity would cancel a live attribution grace. parse-error and unknown
|
|
2959
|
+
// are excluded too (stream noise is not evidence of work).
|
|
2960
|
+
//
|
|
2961
|
+
// Stop is always terminal. SubagentStop is terminal ONLY when it's an ORPHAN —
|
|
2962
|
+
// a late/lagged/foreign teardown hook with no matching in-flight sub-agent start.
|
|
2963
|
+
// Such an orphan trails the main Stop (or arrives from a prior cycle) and must not
|
|
2964
|
+
// bump the work-hook counter (the rung-2 no-reply backstop reads a bump as "claude
|
|
2965
|
+
// resumed", withdrawing the captured Stop's delivery) nor count as activity. A
|
|
2966
|
+
// MATCHED SubagentStop (a sub-agent this cycle actually started is finishing) IS
|
|
2967
|
+
// work-relevant — it keeps withdrawing rung-2 eligibility on a boundary Stop, incl.
|
|
2968
|
+
// a tool-less sub-agent whose only post-boundary signal is its SubagentStop.
|
|
2969
|
+
const orphanSubagentStop = ev.type === 'SubagentStop'
|
|
2970
|
+
&& !(this._pendingSubagentStarts && this._pendingSubagentStarts.length);
|
|
2971
|
+
if (ev.type === 'Stop' || orphanSubagentStop) {
|
|
2937
2972
|
this._lastHookEventAt = Date.now();
|
|
2938
2973
|
} else if (ev.type && ev.type !== 'parse-error' && ev.type !== 'unknown') {
|
|
2939
2974
|
this._lastHookEventAt = Date.now();
|
|
2940
|
-
// Monotonic count of WORK hooks (
|
|
2941
|
-
// no-reply backstop snapshots this at Stop capture; a later increment means
|
|
2975
|
+
// Monotonic count of WORK hooks (all but terminal Stop / orphan SubagentStop). The
|
|
2976
|
+
// rung-2 no-reply backstop snapshots this at Stop capture; a later increment means
|
|
2942
2977
|
// claude resumed work, withdrawing the stale Stop's finalize eligibility.
|
|
2943
2978
|
this._workHookSeq = (this._workHookSeq || 0) + 1;
|
|
2944
2979
|
this._noteActivity(`hook:${ev.type}`);
|
package/lib/session-key.js
CHANGED
|
@@ -128,7 +128,19 @@ function getConfigWriteScope(chatConfig, threadId) {
|
|
|
128
128
|
// runs), and report threadId:null so the audit row reflects chat-level reach.
|
|
129
129
|
if (tid && chatConfig?.isolateTopics === true) {
|
|
130
130
|
chatConfig.topics = chatConfig.topics || {};
|
|
131
|
-
|
|
131
|
+
// A topic can be stored in the legacy / hand-edited form where the entry
|
|
132
|
+
// is the bare name string (topics["329"] = "Advertising") rather than the
|
|
133
|
+
// canonical object ({ name: "Advertising" }). getTopicConfig / getTopicName
|
|
134
|
+
// already tolerate that; the write path must too — assigning a property to
|
|
135
|
+
// a string throws under strict mode, which silently swallowed every /model
|
|
136
|
+
// + /effort button tap in such a topic. Normalise to the object form,
|
|
137
|
+
// preserving the name.
|
|
138
|
+
const existing = chatConfig.topics[tid];
|
|
139
|
+
if (typeof existing === 'string') {
|
|
140
|
+
chatConfig.topics[tid] = { name: existing };
|
|
141
|
+
} else if (!existing || typeof existing !== 'object') {
|
|
142
|
+
chatConfig.topics[tid] = {};
|
|
143
|
+
}
|
|
132
144
|
return { scope: chatConfig.topics[tid], threadId: tid };
|
|
133
145
|
}
|
|
134
146
|
return { scope: chatConfig, threadId: null };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.10",
|
|
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": {
|