claude-threads 1.24.0 → 1.24.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.
- package/CHANGELOG.md +5 -0
- package/dist/index.js +73 -11
- package/dist/mcp/mcp-server.js +54 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.24.1] - 2026-08-08
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Task lists no longer degrade to "Task #N" placeholders after a bot restart.** Modern CLIs stream tasks incrementally (`TaskCreate`/`TaskUpdate` with ids), and the bot accumulates them in a per-session `TaskTracker` — but that tracker lived only in memory. After a restart + resume, the first `TaskUpdate` of the next turn hit an empty tracker and rendered a placeholder ("Task #1") instead of the real subject, losing every task name for the rest of the session. The tracker's resolved tasks (id → subject/status) are now persisted to `sessions.json` at each turn end and restored on resume. In-flight creates (id not yet resolved from result text) are deliberately dropped at serialize time; pre-1.24.1 persisted sessions simply start with an empty tracker as before. Covered by a red-green integration test that restarts the bot mid-task-list and asserts the post-resume re-render still shows real subjects on both platform paths.
|
|
12
|
+
|
|
8
13
|
## [1.24.0] - 2026-08-08
|
|
9
14
|
|
|
10
15
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -56301,16 +56301,23 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
56301
56301
|
const trimmed = line.trim();
|
|
56302
56302
|
if (!trimmed)
|
|
56303
56303
|
continue;
|
|
56304
|
+
let event;
|
|
56305
|
+
try {
|
|
56306
|
+
event = JSON.parse(trimmed);
|
|
56307
|
+
} catch {
|
|
56308
|
+
continue;
|
|
56309
|
+
}
|
|
56304
56310
|
try {
|
|
56305
|
-
const event = JSON.parse(trimmed);
|
|
56306
56311
|
this.emit("event", event);
|
|
56307
|
-
|
|
56308
|
-
|
|
56309
|
-
|
|
56310
|
-
|
|
56311
|
-
|
|
56312
|
-
|
|
56313
|
-
|
|
56312
|
+
} catch (err) {
|
|
56313
|
+
this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
|
|
56314
|
+
}
|
|
56315
|
+
if (event.type === "result" && isErrorResultEvent(event)) {
|
|
56316
|
+
this.maybeEmitRateLimit(trimmed);
|
|
56317
|
+
}
|
|
56318
|
+
if (event.type === "rate_limit_event") {
|
|
56319
|
+
this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
|
|
56320
|
+
}
|
|
56314
56321
|
}
|
|
56315
56322
|
}
|
|
56316
56323
|
maybeEmitRateLimit(text) {
|
|
@@ -65040,6 +65047,7 @@ function truncateAtWord2(text, maxLength) {
|
|
|
65040
65047
|
return truncated + "...";
|
|
65041
65048
|
}
|
|
65042
65049
|
// src/operations/task-tracker.ts
|
|
65050
|
+
var VALID_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
65043
65051
|
var CREATED_RESULT_RE = /Task #(\S+) created/;
|
|
65044
65052
|
|
|
65045
65053
|
class TaskTracker {
|
|
@@ -65133,6 +65141,38 @@ class TaskTracker {
|
|
|
65133
65141
|
this.pendingCreates.clear();
|
|
65134
65142
|
this.unmatchedCreateResults = 0;
|
|
65135
65143
|
}
|
|
65144
|
+
serialize() {
|
|
65145
|
+
const restorable = this.tasks.filter((t) => Boolean(t.taskId));
|
|
65146
|
+
if (restorable.length === 0)
|
|
65147
|
+
return;
|
|
65148
|
+
return restorable.map((t) => {
|
|
65149
|
+
const out = {
|
|
65150
|
+
taskId: t.taskId,
|
|
65151
|
+
subject: t.subject,
|
|
65152
|
+
status: t.status
|
|
65153
|
+
};
|
|
65154
|
+
if (t.activeForm)
|
|
65155
|
+
out.activeForm = t.activeForm;
|
|
65156
|
+
if (t.isPlaceholder)
|
|
65157
|
+
out.isPlaceholder = true;
|
|
65158
|
+
return out;
|
|
65159
|
+
});
|
|
65160
|
+
}
|
|
65161
|
+
restore(state) {
|
|
65162
|
+
if (!Array.isArray(state)) {
|
|
65163
|
+
this.tasks = [];
|
|
65164
|
+
this.pendingCreates.clear();
|
|
65165
|
+
return;
|
|
65166
|
+
}
|
|
65167
|
+
this.tasks = state.filter((t) => t !== null && typeof t === "object" && typeof t.taskId === "string" && t.taskId.length > 0 && typeof t.subject === "string").map((t) => ({
|
|
65168
|
+
taskId: t.taskId,
|
|
65169
|
+
subject: t.subject || `Task #${t.taskId}`,
|
|
65170
|
+
activeForm: typeof t.activeForm === "string" ? t.activeForm : undefined,
|
|
65171
|
+
status: VALID_STATUSES.has(t.status) ? t.status : "pending",
|
|
65172
|
+
isPlaceholder: t.isPlaceholder === true ? true : undefined
|
|
65173
|
+
}));
|
|
65174
|
+
this.pendingCreates.clear();
|
|
65175
|
+
}
|
|
65136
65176
|
}
|
|
65137
65177
|
|
|
65138
65178
|
// src/operations/executors/base.ts
|
|
@@ -67227,9 +67267,15 @@ class MessageManager {
|
|
|
67227
67267
|
serialize() {
|
|
67228
67268
|
return {
|
|
67229
67269
|
taskList: this.taskListExecutor.serialize(),
|
|
67270
|
+
taskTracker: this.taskTracker.serialize(),
|
|
67230
67271
|
contextPrompt: this.promptExecutor.serialize()
|
|
67231
67272
|
};
|
|
67232
67273
|
}
|
|
67274
|
+
restoreTaskTracker(state) {
|
|
67275
|
+
if (state && state.length > 0) {
|
|
67276
|
+
this.taskTracker.restore(state);
|
|
67277
|
+
}
|
|
67278
|
+
}
|
|
67233
67279
|
hydrateTaskListState(persisted) {
|
|
67234
67280
|
this.taskListExecutor.hydrateState(persisted);
|
|
67235
67281
|
}
|
|
@@ -68904,7 +68950,7 @@ function handleEventPreProcessing(session, event, ctx) {
|
|
|
68904
68950
|
trackEvent(session, "tool_use", tool.name);
|
|
68905
68951
|
}
|
|
68906
68952
|
}
|
|
68907
|
-
function handleEventPostProcessing(session, event, ctx) {
|
|
68953
|
+
function handleEventPostProcessing(session, event, ctx, mainHandling) {
|
|
68908
68954
|
if (event.type === "assistant") {
|
|
68909
68955
|
const msg = event.message;
|
|
68910
68956
|
for (const block of msg?.content || []) {
|
|
@@ -68921,6 +68967,11 @@ function handleEventPostProcessing(session, event, ctx) {
|
|
|
68921
68967
|
session.isProcessing = false;
|
|
68922
68968
|
ctx.ops.emitSessionUpdate(session.sessionId, { status: getSessionStatus(session) });
|
|
68923
68969
|
updateUsageStats(session, event, ctx);
|
|
68970
|
+
if (mainHandling) {
|
|
68971
|
+
mainHandling.catch(() => {}).then(() => ctx.ops.persistSession(session));
|
|
68972
|
+
} else {
|
|
68973
|
+
ctx.ops.persistSession(session);
|
|
68974
|
+
}
|
|
68924
68975
|
}
|
|
68925
68976
|
if (event.type === "user" && !isSidechainEvent(event)) {
|
|
68926
68977
|
const msg = event.message;
|
|
@@ -70877,6 +70928,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
70877
70928
|
tasksCompleted: state.tasksCompleted,
|
|
70878
70929
|
tasksMinimized: state.tasksMinimized
|
|
70879
70930
|
});
|
|
70931
|
+
session.messageManager.restoreTaskTracker(state.taskTrackerState);
|
|
70880
70932
|
const persistedWithInteractive = state;
|
|
70881
70933
|
if (persistedWithInteractive.pendingQuestionSet || persistedWithInteractive.pendingApproval) {
|
|
70882
70934
|
session.messageManager.hydrateInteractiveState({
|
|
@@ -71827,8 +71879,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
71827
71879
|
if (!session || !session.messageManager)
|
|
71828
71880
|
return;
|
|
71829
71881
|
handleEventPreProcessing(session, event, this.getContext());
|
|
71830
|
-
session.messageManager.handleEvent(event);
|
|
71831
|
-
handleEventPostProcessing(session, event, this.getContext());
|
|
71882
|
+
const mainHandling = session.messageManager.handleEvent(event);
|
|
71883
|
+
handleEventPostProcessing(session, event, this.getContext(), mainHandling);
|
|
71832
71884
|
}
|
|
71833
71885
|
async handleExit(sessionId, code, source) {
|
|
71834
71886
|
await handleExit(sessionId, code, this.getContext(), source);
|
|
@@ -71866,11 +71918,20 @@ class SessionManager extends EventEmitter4 {
|
|
|
71866
71918
|
this.stopTyping(session);
|
|
71867
71919
|
}
|
|
71868
71920
|
persistSession(session) {
|
|
71921
|
+
try {
|
|
71922
|
+
this.persistSessionUnsafe(session);
|
|
71923
|
+
} catch (err) {
|
|
71924
|
+
log35.error(`Failed to persist session ${session.sessionId}: ${err}`);
|
|
71925
|
+
}
|
|
71926
|
+
}
|
|
71927
|
+
persistSessionUnsafe(session) {
|
|
71869
71928
|
let taskListSnapshot;
|
|
71870
71929
|
let contextPromptSnapshot;
|
|
71930
|
+
let taskTrackerSnapshot;
|
|
71871
71931
|
if (session.messageManager) {
|
|
71872
71932
|
const serialized = session.messageManager.serialize();
|
|
71873
71933
|
taskListSnapshot = serialized.taskList;
|
|
71934
|
+
taskTrackerSnapshot = serialized.taskTracker;
|
|
71874
71935
|
if (serialized.contextPrompt) {
|
|
71875
71936
|
contextPromptSnapshot = serialized.contextPrompt;
|
|
71876
71937
|
}
|
|
@@ -71895,6 +71956,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
71895
71956
|
lastTasksContent: taskListSnapshot?.content ?? null,
|
|
71896
71957
|
tasksCompleted: taskListSnapshot?.isCompleted ?? false,
|
|
71897
71958
|
tasksMinimized: taskListSnapshot?.isMinimized ?? false,
|
|
71959
|
+
taskTrackerState: taskTrackerSnapshot,
|
|
71898
71960
|
worktreeInfo: session.worktreeInfo,
|
|
71899
71961
|
isWorktreeOwner: session.isWorktreeOwner,
|
|
71900
71962
|
pendingWorktreePrompt: session.pendingWorktreePrompt,
|
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -49412,6 +49412,7 @@ function truncateAtWord(text, maxLength) {
|
|
|
49412
49412
|
return truncated + "...";
|
|
49413
49413
|
}
|
|
49414
49414
|
// src/operations/task-tracker.ts
|
|
49415
|
+
var VALID_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
49415
49416
|
var CREATED_RESULT_RE = /Task #(\S+) created/;
|
|
49416
49417
|
|
|
49417
49418
|
class TaskTracker {
|
|
@@ -49505,6 +49506,38 @@ class TaskTracker {
|
|
|
49505
49506
|
this.pendingCreates.clear();
|
|
49506
49507
|
this.unmatchedCreateResults = 0;
|
|
49507
49508
|
}
|
|
49509
|
+
serialize() {
|
|
49510
|
+
const restorable = this.tasks.filter((t) => Boolean(t.taskId));
|
|
49511
|
+
if (restorable.length === 0)
|
|
49512
|
+
return;
|
|
49513
|
+
return restorable.map((t) => {
|
|
49514
|
+
const out = {
|
|
49515
|
+
taskId: t.taskId,
|
|
49516
|
+
subject: t.subject,
|
|
49517
|
+
status: t.status
|
|
49518
|
+
};
|
|
49519
|
+
if (t.activeForm)
|
|
49520
|
+
out.activeForm = t.activeForm;
|
|
49521
|
+
if (t.isPlaceholder)
|
|
49522
|
+
out.isPlaceholder = true;
|
|
49523
|
+
return out;
|
|
49524
|
+
});
|
|
49525
|
+
}
|
|
49526
|
+
restore(state) {
|
|
49527
|
+
if (!Array.isArray(state)) {
|
|
49528
|
+
this.tasks = [];
|
|
49529
|
+
this.pendingCreates.clear();
|
|
49530
|
+
return;
|
|
49531
|
+
}
|
|
49532
|
+
this.tasks = state.filter((t) => t !== null && typeof t === "object" && typeof t.taskId === "string" && t.taskId.length > 0 && typeof t.subject === "string").map((t) => ({
|
|
49533
|
+
taskId: t.taskId,
|
|
49534
|
+
subject: t.subject || `Task #${t.taskId}`,
|
|
49535
|
+
activeForm: typeof t.activeForm === "string" ? t.activeForm : undefined,
|
|
49536
|
+
status: VALID_STATUSES.has(t.status) ? t.status : "pending",
|
|
49537
|
+
isPlaceholder: t.isPlaceholder === true ? true : undefined
|
|
49538
|
+
}));
|
|
49539
|
+
this.pendingCreates.clear();
|
|
49540
|
+
}
|
|
49508
49541
|
}
|
|
49509
49542
|
|
|
49510
49543
|
// src/operations/executors/base.ts
|
|
@@ -51908,9 +51941,15 @@ class MessageManager {
|
|
|
51908
51941
|
serialize() {
|
|
51909
51942
|
return {
|
|
51910
51943
|
taskList: this.taskListExecutor.serialize(),
|
|
51944
|
+
taskTracker: this.taskTracker.serialize(),
|
|
51911
51945
|
contextPrompt: this.promptExecutor.serialize()
|
|
51912
51946
|
};
|
|
51913
51947
|
}
|
|
51948
|
+
restoreTaskTracker(state) {
|
|
51949
|
+
if (state && state.length > 0) {
|
|
51950
|
+
this.taskTracker.restore(state);
|
|
51951
|
+
}
|
|
51952
|
+
}
|
|
51914
51953
|
hydrateTaskListState(persisted) {
|
|
51915
51954
|
this.taskListExecutor.hydrateState(persisted);
|
|
51916
51955
|
}
|
|
@@ -56409,16 +56448,23 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
56409
56448
|
const trimmed = line.trim();
|
|
56410
56449
|
if (!trimmed)
|
|
56411
56450
|
continue;
|
|
56451
|
+
let event;
|
|
56452
|
+
try {
|
|
56453
|
+
event = JSON.parse(trimmed);
|
|
56454
|
+
} catch {
|
|
56455
|
+
continue;
|
|
56456
|
+
}
|
|
56412
56457
|
try {
|
|
56413
|
-
const event = JSON.parse(trimmed);
|
|
56414
56458
|
this.emit("event", event);
|
|
56415
|
-
|
|
56416
|
-
|
|
56417
|
-
|
|
56418
|
-
|
|
56419
|
-
|
|
56420
|
-
|
|
56421
|
-
|
|
56459
|
+
} catch (err) {
|
|
56460
|
+
this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
|
|
56461
|
+
}
|
|
56462
|
+
if (event.type === "result" && isErrorResultEvent(event)) {
|
|
56463
|
+
this.maybeEmitRateLimit(trimmed);
|
|
56464
|
+
}
|
|
56465
|
+
if (event.type === "rate_limit_event") {
|
|
56466
|
+
this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
|
|
56467
|
+
}
|
|
56422
56468
|
}
|
|
56423
56469
|
}
|
|
56424
56470
|
maybeEmitRateLimit(text) {
|
package/package.json
CHANGED