claude-threads 1.9.0 → 1.9.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 +9 -0
- package/dist/index.js +62 -47
- package/dist/mcp/permission-server.js +35 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.9.1] - 2026-04-24
|
|
9
|
+
|
|
10
|
+
### Internals
|
|
11
|
+
- **Unified `Executor<TState>` contract.** New interface in `src/operations/executors/types.ts` formalizes what `MessageManager` actually relies on: `getState` / `reset` required, `handleReaction` / `serialize` optional. `BaseExecutor<T>` implements it. A new `contract.test.ts` iterates every executor and asserts the shape — catches drift when someone adds an executor without the required members. (#346)
|
|
12
|
+
- **Uniform `handleReaction` signature across all seven reaction executors** — `(postId, emoji, user, action, ctx) => Promise<boolean>`. Previously `TaskList`, `Subagent`, and `WorktreePrompt` had slightly different shapes. `MessageManager.handleReaction` now dispatches via a `reactionDispatchList()` table instead of an if/else chain. (#346)
|
|
13
|
+
- **`MessageManager.serialize()` aggregates executor state** for `SessionManager.persistSession`. The writer no longer reaches into individual executors via named getters (`getTaskListState()`, `getPendingContextPrompt()`). Legacy getters kept as `@deprecated` shims — they still have non-persistence consumers. (#346)
|
|
14
|
+
- **Byte-identical `sessions.json` guarantee.** New snapshot tests in `manager.test.ts` pin the full payload's field set and run the new and legacy (`CLAUDE_THREADS_SERIALIZE_V2=0`) paths through a parity assertion. No persisted-schema change on disk. (#346)
|
|
15
|
+
- **Rollback hatch:** `CLAUDE_THREADS_SERIALIZE_V2=0` falls back to the pre-refactor per-getter writer for one release. Removed in the next minor. (#346)
|
|
16
|
+
|
|
8
17
|
## [1.9.0] - 2026-04-24
|
|
9
18
|
|
|
10
19
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -58611,7 +58611,15 @@ class TaskListExecutor extends BaseExecutor {
|
|
|
58611
58611
|
await ctx.platform.updatePost(this.state.tasksPostId, displayContent);
|
|
58612
58612
|
} catch {}
|
|
58613
58613
|
}
|
|
58614
|
-
|
|
58614
|
+
serialize() {
|
|
58615
|
+
return {
|
|
58616
|
+
postId: this.state.tasksPostId,
|
|
58617
|
+
content: this.state.lastTasksContent,
|
|
58618
|
+
isMinimized: this.state.tasksMinimized,
|
|
58619
|
+
isCompleted: this.state.tasksCompleted
|
|
58620
|
+
};
|
|
58621
|
+
}
|
|
58622
|
+
async handleReaction(postId, emoji, _user, action, ctx) {
|
|
58615
58623
|
ctx.logger.debug(`TaskListExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji}, action=${action}, tasksPostId=${this.state.tasksPostId?.substring(0, 8) ?? "none"}`);
|
|
58616
58624
|
if (postId !== this.state.tasksPostId) {
|
|
58617
58625
|
ctx.logger.debug(`TaskListExecutor: postId does not match tasksPostId, ignoring`);
|
|
@@ -58880,7 +58888,7 @@ class SubagentExecutor extends BaseExecutor {
|
|
|
58880
58888
|
}
|
|
58881
58889
|
return false;
|
|
58882
58890
|
}
|
|
58883
|
-
async handleReaction(postId, emoji, action, ctx) {
|
|
58891
|
+
async handleReaction(postId, emoji, _user, action, ctx) {
|
|
58884
58892
|
ctx.logger.debug(`SubagentExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji}, action=${action}`);
|
|
58885
58893
|
if (!isMinimizeToggleEmoji(emoji)) {
|
|
58886
58894
|
ctx.logger.debug(`SubagentExecutor: emoji ${emoji} is not minimize toggle, ignoring`);
|
|
@@ -59420,6 +59428,9 @@ class PromptExecutor extends BaseExecutor {
|
|
|
59420
59428
|
getPendingContextPrompt() {
|
|
59421
59429
|
return this.state.pendingContextPrompt;
|
|
59422
59430
|
}
|
|
59431
|
+
serialize() {
|
|
59432
|
+
return this.state.pendingContextPrompt;
|
|
59433
|
+
}
|
|
59423
59434
|
hasPendingContextPrompt() {
|
|
59424
59435
|
return this.state.pendingContextPrompt !== null;
|
|
59425
59436
|
}
|
|
@@ -60782,12 +60793,12 @@ class MessageManager {
|
|
|
60782
60793
|
await this.taskListExecutor.bumpToBottom(this.getExecutorContext());
|
|
60783
60794
|
}
|
|
60784
60795
|
getTaskListState() {
|
|
60785
|
-
|
|
60796
|
+
return this.taskListExecutor.serialize();
|
|
60797
|
+
}
|
|
60798
|
+
serialize() {
|
|
60786
60799
|
return {
|
|
60787
|
-
|
|
60788
|
-
|
|
60789
|
-
isMinimized: state.tasksMinimized,
|
|
60790
|
-
isCompleted: state.tasksCompleted
|
|
60800
|
+
taskList: this.taskListExecutor.serialize(),
|
|
60801
|
+
contextPrompt: this.promptExecutor.serialize()
|
|
60791
60802
|
};
|
|
60792
60803
|
}
|
|
60793
60804
|
hydrateTaskListState(persisted) {
|
|
@@ -60880,33 +60891,27 @@ class MessageManager {
|
|
|
60880
60891
|
getSession() {
|
|
60881
60892
|
return this.session;
|
|
60882
60893
|
}
|
|
60894
|
+
reactionDispatchList() {
|
|
60895
|
+
return [
|
|
60896
|
+
{ name: "QuestionApprovalExecutor", executor: this.questionApprovalExecutor },
|
|
60897
|
+
{ name: "MessageApprovalExecutor", executor: this.messageApprovalExecutor },
|
|
60898
|
+
{ name: "PromptExecutor", executor: this.promptExecutor },
|
|
60899
|
+
{ name: "BugReportExecutor", executor: this.bugReportExecutor },
|
|
60900
|
+
{ name: "TaskListExecutor", executor: this.taskListExecutor },
|
|
60901
|
+
{ name: "SubagentExecutor", executor: this.subagentExecutor }
|
|
60902
|
+
];
|
|
60903
|
+
}
|
|
60883
60904
|
async handleReaction(postId, emoji, user, action) {
|
|
60884
60905
|
const logger = log18.forSession(this.sessionId);
|
|
60885
60906
|
const ctx = this.getExecutorContext();
|
|
60886
60907
|
logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
|
|
60887
|
-
|
|
60888
|
-
|
|
60889
|
-
|
|
60890
|
-
|
|
60891
|
-
|
|
60892
|
-
|
|
60893
|
-
|
|
60894
|
-
}
|
|
60895
|
-
if (await this.promptExecutor.handleReaction(postId, emoji, user, action, ctx)) {
|
|
60896
|
-
logger.debug("Reaction handled by PromptExecutor");
|
|
60897
|
-
return true;
|
|
60898
|
-
}
|
|
60899
|
-
if (await this.bugReportExecutor.handleReaction(postId, emoji, user, action, ctx)) {
|
|
60900
|
-
logger.debug("Reaction handled by BugReportExecutor");
|
|
60901
|
-
return true;
|
|
60902
|
-
}
|
|
60903
|
-
if (await this.taskListExecutor.handleReaction(postId, emoji, action, ctx)) {
|
|
60904
|
-
logger.debug("Reaction handled by TaskListExecutor");
|
|
60905
|
-
return true;
|
|
60906
|
-
}
|
|
60907
|
-
if (await this.subagentExecutor.handleReaction(postId, emoji, action, ctx)) {
|
|
60908
|
-
logger.debug("Reaction handled by SubagentExecutor");
|
|
60909
|
-
return true;
|
|
60908
|
+
for (const { name, executor } of this.reactionDispatchList()) {
|
|
60909
|
+
if (!executor.handleReaction)
|
|
60910
|
+
continue;
|
|
60911
|
+
if (await executor.handleReaction(postId, emoji, user, action, ctx)) {
|
|
60912
|
+
logger.debug(`Reaction handled by ${name}`);
|
|
60913
|
+
return true;
|
|
60914
|
+
}
|
|
60910
60915
|
}
|
|
60911
60916
|
logger.debug("Reaction not handled by any executor");
|
|
60912
60917
|
return false;
|
|
@@ -68910,19 +68915,29 @@ class SessionManager extends EventEmitter4 {
|
|
|
68910
68915
|
this.stopTyping(session);
|
|
68911
68916
|
}
|
|
68912
68917
|
persistSession(session) {
|
|
68913
|
-
|
|
68914
|
-
|
|
68915
|
-
|
|
68916
|
-
|
|
68917
|
-
|
|
68918
|
-
|
|
68919
|
-
|
|
68920
|
-
|
|
68921
|
-
|
|
68922
|
-
|
|
68923
|
-
|
|
68918
|
+
const useSerializeV2 = process.env.CLAUDE_THREADS_SERIALIZE_V2 !== "0";
|
|
68919
|
+
let taskListSnapshot;
|
|
68920
|
+
let contextPromptSnapshot;
|
|
68921
|
+
if (useSerializeV2 && session.messageManager) {
|
|
68922
|
+
const serialized = session.messageManager.serialize();
|
|
68923
|
+
taskListSnapshot = serialized.taskList;
|
|
68924
|
+
if (serialized.contextPrompt) {
|
|
68925
|
+
contextPromptSnapshot = serialized.contextPrompt;
|
|
68926
|
+
}
|
|
68927
|
+
} else {
|
|
68928
|
+
const legacyPrompt = session.messageManager?.getPendingContextPrompt();
|
|
68929
|
+
if (legacyPrompt) {
|
|
68930
|
+
contextPromptSnapshot = {
|
|
68931
|
+
postId: legacyPrompt.postId,
|
|
68932
|
+
queuedPrompt: legacyPrompt.queuedPrompt,
|
|
68933
|
+
queuedFiles: legacyPrompt.queuedFiles,
|
|
68934
|
+
threadMessageCount: legacyPrompt.threadMessageCount,
|
|
68935
|
+
createdAt: legacyPrompt.createdAt,
|
|
68936
|
+
availableOptions: legacyPrompt.availableOptions
|
|
68937
|
+
};
|
|
68938
|
+
}
|
|
68939
|
+
taskListSnapshot = session.messageManager?.getTaskListState();
|
|
68924
68940
|
}
|
|
68925
|
-
const taskState = session.messageManager?.getTaskListState();
|
|
68926
68941
|
const state = {
|
|
68927
68942
|
platformId: session.platformId,
|
|
68928
68943
|
threadId: session.threadId,
|
|
@@ -68937,10 +68952,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
68937
68952
|
sessionAllowedUsers: [...session.sessionAllowedUsers],
|
|
68938
68953
|
forceInteractivePermissions: session.forceInteractivePermissions,
|
|
68939
68954
|
sessionStartPostId: session.sessionStartPostId,
|
|
68940
|
-
tasksPostId:
|
|
68941
|
-
lastTasksContent:
|
|
68942
|
-
tasksCompleted:
|
|
68943
|
-
tasksMinimized:
|
|
68955
|
+
tasksPostId: taskListSnapshot?.postId ?? null,
|
|
68956
|
+
lastTasksContent: taskListSnapshot?.content ?? null,
|
|
68957
|
+
tasksCompleted: taskListSnapshot?.isCompleted ?? false,
|
|
68958
|
+
tasksMinimized: taskListSnapshot?.isMinimized ?? false,
|
|
68944
68959
|
worktreeInfo: session.worktreeInfo,
|
|
68945
68960
|
isWorktreeOwner: session.isWorktreeOwner,
|
|
68946
68961
|
pendingWorktreePrompt: session.pendingWorktreePrompt,
|
|
@@ -68948,7 +68963,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
68948
68963
|
queuedPrompt: session.queuedPrompt,
|
|
68949
68964
|
queuedFiles: session.queuedFiles,
|
|
68950
68965
|
firstPrompt: session.firstPrompt,
|
|
68951
|
-
pendingContextPrompt:
|
|
68966
|
+
pendingContextPrompt: contextPromptSnapshot,
|
|
68952
68967
|
needsContextPromptOnNextMessage: session.needsContextPromptOnNextMessage,
|
|
68953
68968
|
lifecyclePostId: session.lifecyclePostId,
|
|
68954
68969
|
isPaused: session.lifecycle.state === "paused" || session.lifecycle.state === "interrupted",
|
|
@@ -51175,7 +51175,15 @@ class TaskListExecutor extends BaseExecutor {
|
|
|
51175
51175
|
await ctx.platform.updatePost(this.state.tasksPostId, displayContent);
|
|
51176
51176
|
} catch {}
|
|
51177
51177
|
}
|
|
51178
|
-
|
|
51178
|
+
serialize() {
|
|
51179
|
+
return {
|
|
51180
|
+
postId: this.state.tasksPostId,
|
|
51181
|
+
content: this.state.lastTasksContent,
|
|
51182
|
+
isMinimized: this.state.tasksMinimized,
|
|
51183
|
+
isCompleted: this.state.tasksCompleted
|
|
51184
|
+
};
|
|
51185
|
+
}
|
|
51186
|
+
async handleReaction(postId, emoji4, _user, action, ctx) {
|
|
51179
51187
|
ctx.logger.debug(`TaskListExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, action=${action}, tasksPostId=${this.state.tasksPostId?.substring(0, 8) ?? "none"}`);
|
|
51180
51188
|
if (postId !== this.state.tasksPostId) {
|
|
51181
51189
|
ctx.logger.debug(`TaskListExecutor: postId does not match tasksPostId, ignoring`);
|
|
@@ -51444,7 +51452,7 @@ class SubagentExecutor extends BaseExecutor {
|
|
|
51444
51452
|
}
|
|
51445
51453
|
return false;
|
|
51446
51454
|
}
|
|
51447
|
-
async handleReaction(postId, emoji4, action, ctx) {
|
|
51455
|
+
async handleReaction(postId, emoji4, _user, action, ctx) {
|
|
51448
51456
|
ctx.logger.debug(`SubagentExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, action=${action}`);
|
|
51449
51457
|
if (!isMinimizeToggleEmoji(emoji4)) {
|
|
51450
51458
|
ctx.logger.debug(`SubagentExecutor: emoji ${emoji4} is not minimize toggle, ignoring`);
|
|
@@ -51984,6 +51992,9 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51984
51992
|
getPendingContextPrompt() {
|
|
51985
51993
|
return this.state.pendingContextPrompt;
|
|
51986
51994
|
}
|
|
51995
|
+
serialize() {
|
|
51996
|
+
return this.state.pendingContextPrompt;
|
|
51997
|
+
}
|
|
51987
51998
|
hasPendingContextPrompt() {
|
|
51988
51999
|
return this.state.pendingContextPrompt !== null;
|
|
51989
52000
|
}
|
|
@@ -52734,12 +52745,12 @@ class MessageManager {
|
|
|
52734
52745
|
await this.taskListExecutor.bumpToBottom(this.getExecutorContext());
|
|
52735
52746
|
}
|
|
52736
52747
|
getTaskListState() {
|
|
52737
|
-
|
|
52748
|
+
return this.taskListExecutor.serialize();
|
|
52749
|
+
}
|
|
52750
|
+
serialize() {
|
|
52738
52751
|
return {
|
|
52739
|
-
|
|
52740
|
-
|
|
52741
|
-
isMinimized: state.tasksMinimized,
|
|
52742
|
-
isCompleted: state.tasksCompleted
|
|
52752
|
+
taskList: this.taskListExecutor.serialize(),
|
|
52753
|
+
contextPrompt: this.promptExecutor.serialize()
|
|
52743
52754
|
};
|
|
52744
52755
|
}
|
|
52745
52756
|
hydrateTaskListState(persisted) {
|
|
@@ -52832,33 +52843,27 @@ class MessageManager {
|
|
|
52832
52843
|
getSession() {
|
|
52833
52844
|
return this.session;
|
|
52834
52845
|
}
|
|
52846
|
+
reactionDispatchList() {
|
|
52847
|
+
return [
|
|
52848
|
+
{ name: "QuestionApprovalExecutor", executor: this.questionApprovalExecutor },
|
|
52849
|
+
{ name: "MessageApprovalExecutor", executor: this.messageApprovalExecutor },
|
|
52850
|
+
{ name: "PromptExecutor", executor: this.promptExecutor },
|
|
52851
|
+
{ name: "BugReportExecutor", executor: this.bugReportExecutor },
|
|
52852
|
+
{ name: "TaskListExecutor", executor: this.taskListExecutor },
|
|
52853
|
+
{ name: "SubagentExecutor", executor: this.subagentExecutor }
|
|
52854
|
+
];
|
|
52855
|
+
}
|
|
52835
52856
|
async handleReaction(postId, emoji4, user, action) {
|
|
52836
52857
|
const logger = log3.forSession(this.sessionId);
|
|
52837
52858
|
const ctx = this.getExecutorContext();
|
|
52838
52859
|
logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji4}, user=${user}, action=${action}`);
|
|
52839
|
-
|
|
52840
|
-
|
|
52841
|
-
|
|
52842
|
-
|
|
52843
|
-
|
|
52844
|
-
|
|
52845
|
-
|
|
52846
|
-
}
|
|
52847
|
-
if (await this.promptExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
|
|
52848
|
-
logger.debug("Reaction handled by PromptExecutor");
|
|
52849
|
-
return true;
|
|
52850
|
-
}
|
|
52851
|
-
if (await this.bugReportExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
|
|
52852
|
-
logger.debug("Reaction handled by BugReportExecutor");
|
|
52853
|
-
return true;
|
|
52854
|
-
}
|
|
52855
|
-
if (await this.taskListExecutor.handleReaction(postId, emoji4, action, ctx)) {
|
|
52856
|
-
logger.debug("Reaction handled by TaskListExecutor");
|
|
52857
|
-
return true;
|
|
52858
|
-
}
|
|
52859
|
-
if (await this.subagentExecutor.handleReaction(postId, emoji4, action, ctx)) {
|
|
52860
|
-
logger.debug("Reaction handled by SubagentExecutor");
|
|
52861
|
-
return true;
|
|
52860
|
+
for (const { name, executor } of this.reactionDispatchList()) {
|
|
52861
|
+
if (!executor.handleReaction)
|
|
52862
|
+
continue;
|
|
52863
|
+
if (await executor.handleReaction(postId, emoji4, user, action, ctx)) {
|
|
52864
|
+
logger.debug(`Reaction handled by ${name}`);
|
|
52865
|
+
return true;
|
|
52866
|
+
}
|
|
52862
52867
|
}
|
|
52863
52868
|
logger.debug("Reaction not handled by any executor");
|
|
52864
52869
|
return false;
|