@messenger-agent/claude-agent 0.24.0-alpha.2
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/dist/app.d.ts +3 -0
- package/dist/app.js +15 -0
- package/dist/assets/codex-home/AGENTS.md +50 -0
- package/dist/claude.d.ts +1 -0
- package/dist/claude.js +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +147 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +19 -0
- package/dist/input-queue.d.ts +8 -0
- package/dist/input-queue.js +40 -0
- package/dist/live-session.d.ts +69 -0
- package/dist/live-session.js +225 -0
- package/dist/llm-proxy-bindings.d.ts +3 -0
- package/dist/llm-proxy-bindings.js +45 -0
- package/dist/managed-task-mcp.d.ts +7 -0
- package/dist/managed-task-mcp.js +41 -0
- package/dist/pending-asks.d.ts +85 -0
- package/dist/pending-asks.js +283 -0
- package/dist/platform-instructions.d.ts +2 -0
- package/dist/platform-instructions.js +42 -0
- package/dist/routes/chat.d.ts +27 -0
- package/dist/routes/chat.js +1229 -0
- package/dist/schemas.d.ts +188 -0
- package/dist/schemas.js +89 -0
- package/dist/session-manager.d.ts +14 -0
- package/dist/session-manager.js +57 -0
- package/dist/task-aggregator.d.ts +69 -0
- package/dist/task-aggregator.js +143 -0
- package/dist/token-usage.d.ts +23 -0
- package/dist/token-usage.js +75 -0
- package/dist/tunnel-client.d.ts +15 -0
- package/dist/tunnel-client.js +223 -0
- package/dist/workspace-files.d.ts +8 -0
- package/dist/workspace-files.js +56 -0
- package/package.json +33 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ChatTextPartSchema: z.ZodObject<{
|
|
3
|
+
type: z.ZodLiteral<"text">;
|
|
4
|
+
text: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const ChatFilePartSchema: z.ZodObject<{
|
|
7
|
+
type: z.ZodLiteral<"file">;
|
|
8
|
+
mediaType: z.ZodString;
|
|
9
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
10
|
+
url: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export declare const ChatStepStartPartSchema: z.ZodObject<{
|
|
13
|
+
type: z.ZodLiteral<"step-start">;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export declare const ChatToolPartSchema: z.ZodObject<{
|
|
16
|
+
type: z.ZodLiteral<"tool">;
|
|
17
|
+
toolCallId: z.ZodString;
|
|
18
|
+
toolName: z.ZodString;
|
|
19
|
+
title: z.ZodOptional<z.ZodString>;
|
|
20
|
+
state: z.ZodString;
|
|
21
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
22
|
+
output: z.ZodOptional<z.ZodUnknown>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export declare const ClaudeAskUserQuestionOutputSchema: z.ZodObject<{
|
|
25
|
+
answers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export declare const AskUserQuestionToolPartSchema: z.ZodObject<{
|
|
28
|
+
type: z.ZodLiteral<"tool">;
|
|
29
|
+
toolCallId: z.ZodString;
|
|
30
|
+
toolName: z.ZodEnum<{
|
|
31
|
+
AskUserQuestion: "AskUserQuestion";
|
|
32
|
+
ask_user_question: "ask_user_question";
|
|
33
|
+
}>;
|
|
34
|
+
state: z.ZodLiteral<"output-available">;
|
|
35
|
+
output: z.ZodObject<{
|
|
36
|
+
answers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export declare const ExitPlanModeToolPartSchema: z.ZodObject<{
|
|
40
|
+
type: z.ZodLiteral<"tool">;
|
|
41
|
+
toolCallId: z.ZodString;
|
|
42
|
+
toolName: z.ZodEnum<{
|
|
43
|
+
ExitPlanMode: "ExitPlanMode";
|
|
44
|
+
exit_plan_mode: "exit_plan_mode";
|
|
45
|
+
}>;
|
|
46
|
+
state: z.ZodLiteral<"output-available">;
|
|
47
|
+
output: z.ZodUnknown;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export declare const ToolApprovalSchema: z.ZodObject<{
|
|
50
|
+
id: z.ZodString;
|
|
51
|
+
approved: z.ZodBoolean;
|
|
52
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export declare const ToolApprovalPartSchema: z.ZodObject<{
|
|
55
|
+
type: z.ZodLiteral<"tool">;
|
|
56
|
+
toolCallId: z.ZodString;
|
|
57
|
+
toolName: z.ZodString;
|
|
58
|
+
state: z.ZodLiteral<"approval-responded">;
|
|
59
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
60
|
+
approval: z.ZodObject<{
|
|
61
|
+
id: z.ZodString;
|
|
62
|
+
approved: z.ZodBoolean;
|
|
63
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
}, z.core.$strip>;
|
|
66
|
+
export declare const AgentAnswerToolPartSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
67
|
+
type: z.ZodLiteral<"tool">;
|
|
68
|
+
toolCallId: z.ZodString;
|
|
69
|
+
toolName: z.ZodEnum<{
|
|
70
|
+
AskUserQuestion: "AskUserQuestion";
|
|
71
|
+
ask_user_question: "ask_user_question";
|
|
72
|
+
}>;
|
|
73
|
+
state: z.ZodLiteral<"output-available">;
|
|
74
|
+
output: z.ZodObject<{
|
|
75
|
+
answers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
78
|
+
type: z.ZodLiteral<"tool">;
|
|
79
|
+
toolCallId: z.ZodString;
|
|
80
|
+
toolName: z.ZodEnum<{
|
|
81
|
+
ExitPlanMode: "ExitPlanMode";
|
|
82
|
+
exit_plan_mode: "exit_plan_mode";
|
|
83
|
+
}>;
|
|
84
|
+
state: z.ZodLiteral<"output-available">;
|
|
85
|
+
output: z.ZodUnknown;
|
|
86
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
87
|
+
type: z.ZodLiteral<"tool">;
|
|
88
|
+
toolCallId: z.ZodString;
|
|
89
|
+
toolName: z.ZodString;
|
|
90
|
+
state: z.ZodLiteral<"approval-responded">;
|
|
91
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
92
|
+
approval: z.ZodObject<{
|
|
93
|
+
id: z.ZodString;
|
|
94
|
+
approved: z.ZodBoolean;
|
|
95
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
}, z.core.$strip>]>;
|
|
98
|
+
export declare const ChatCustomPartSchema: z.ZodObject<{
|
|
99
|
+
type: z.ZodString;
|
|
100
|
+
}, z.core.$loose>;
|
|
101
|
+
export declare const CodexChatForkSchema: z.ZodObject<{
|
|
102
|
+
threadId: z.ZodString;
|
|
103
|
+
turnId: z.ZodString;
|
|
104
|
+
}, z.core.$strict>;
|
|
105
|
+
export declare const ClaudeChatForkSchema: z.ZodObject<{
|
|
106
|
+
sessionId: z.ZodString;
|
|
107
|
+
messageId: z.ZodString;
|
|
108
|
+
}, z.core.$strict>;
|
|
109
|
+
export declare const ChatForkSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
110
|
+
threadId: z.ZodString;
|
|
111
|
+
turnId: z.ZodString;
|
|
112
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
113
|
+
sessionId: z.ZodString;
|
|
114
|
+
messageId: z.ZodString;
|
|
115
|
+
}, z.core.$strict>]>;
|
|
116
|
+
export declare const ChatBodySchema: z.ZodObject<{
|
|
117
|
+
conversationId: z.ZodOptional<z.ZodString>;
|
|
118
|
+
fork: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
119
|
+
threadId: z.ZodString;
|
|
120
|
+
turnId: z.ZodString;
|
|
121
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
122
|
+
sessionId: z.ZodString;
|
|
123
|
+
messageId: z.ZodString;
|
|
124
|
+
}, z.core.$strict>]>>;
|
|
125
|
+
model: z.ZodString;
|
|
126
|
+
message: z.ZodObject<{
|
|
127
|
+
id: z.ZodOptional<z.ZodString>;
|
|
128
|
+
role: z.ZodOptional<z.ZodEnum<{
|
|
129
|
+
user: "user";
|
|
130
|
+
assistant: "assistant";
|
|
131
|
+
}>>;
|
|
132
|
+
parts: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
133
|
+
type: z.ZodLiteral<"text">;
|
|
134
|
+
text: z.ZodString;
|
|
135
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
136
|
+
type: z.ZodLiteral<"file">;
|
|
137
|
+
mediaType: z.ZodString;
|
|
138
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
139
|
+
url: z.ZodString;
|
|
140
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
141
|
+
type: z.ZodLiteral<"step-start">;
|
|
142
|
+
}, z.core.$strip>, z.ZodUnion<readonly [z.ZodObject<{
|
|
143
|
+
type: z.ZodLiteral<"tool">;
|
|
144
|
+
toolCallId: z.ZodString;
|
|
145
|
+
toolName: z.ZodEnum<{
|
|
146
|
+
AskUserQuestion: "AskUserQuestion";
|
|
147
|
+
ask_user_question: "ask_user_question";
|
|
148
|
+
}>;
|
|
149
|
+
state: z.ZodLiteral<"output-available">;
|
|
150
|
+
output: z.ZodObject<{
|
|
151
|
+
answers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
152
|
+
}, z.core.$strip>;
|
|
153
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
154
|
+
type: z.ZodLiteral<"tool">;
|
|
155
|
+
toolCallId: z.ZodString;
|
|
156
|
+
toolName: z.ZodEnum<{
|
|
157
|
+
ExitPlanMode: "ExitPlanMode";
|
|
158
|
+
exit_plan_mode: "exit_plan_mode";
|
|
159
|
+
}>;
|
|
160
|
+
state: z.ZodLiteral<"output-available">;
|
|
161
|
+
output: z.ZodUnknown;
|
|
162
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
163
|
+
type: z.ZodLiteral<"tool">;
|
|
164
|
+
toolCallId: z.ZodString;
|
|
165
|
+
toolName: z.ZodString;
|
|
166
|
+
state: z.ZodLiteral<"approval-responded">;
|
|
167
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
168
|
+
approval: z.ZodObject<{
|
|
169
|
+
id: z.ZodString;
|
|
170
|
+
approved: z.ZodBoolean;
|
|
171
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
172
|
+
}, z.core.$strip>;
|
|
173
|
+
}, z.core.$strip>]>, z.ZodObject<{
|
|
174
|
+
type: z.ZodLiteral<"tool">;
|
|
175
|
+
toolCallId: z.ZodString;
|
|
176
|
+
toolName: z.ZodString;
|
|
177
|
+
title: z.ZodOptional<z.ZodString>;
|
|
178
|
+
state: z.ZodString;
|
|
179
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
180
|
+
output: z.ZodOptional<z.ZodUnknown>;
|
|
181
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
182
|
+
type: z.ZodString;
|
|
183
|
+
}, z.core.$loose>]>>;
|
|
184
|
+
}, z.core.$strip>;
|
|
185
|
+
}, z.core.$strip>;
|
|
186
|
+
export type ChatBody = z.infer<typeof ChatBodySchema>;
|
|
187
|
+
export type ChatMessagePart = ChatBody["message"]["parts"][number];
|
|
188
|
+
export type AgentAnswerToolPart = z.infer<typeof AgentAnswerToolPartSchema>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const ChatTextPartSchema = z.object({
|
|
3
|
+
type: z.literal("text"),
|
|
4
|
+
text: z.string(),
|
|
5
|
+
});
|
|
6
|
+
export const ChatFilePartSchema = z.object({
|
|
7
|
+
type: z.literal("file"),
|
|
8
|
+
mediaType: z.string(),
|
|
9
|
+
filename: z.string().optional(),
|
|
10
|
+
url: z.string(),
|
|
11
|
+
});
|
|
12
|
+
export const ChatStepStartPartSchema = z.object({
|
|
13
|
+
type: z.literal("step-start"),
|
|
14
|
+
});
|
|
15
|
+
export const ChatToolPartSchema = z.object({
|
|
16
|
+
type: z.literal("tool"),
|
|
17
|
+
toolCallId: z.string(),
|
|
18
|
+
toolName: z.string(),
|
|
19
|
+
title: z.string().optional(),
|
|
20
|
+
state: z.string(),
|
|
21
|
+
input: z.unknown().optional(),
|
|
22
|
+
output: z.unknown().optional(),
|
|
23
|
+
});
|
|
24
|
+
export const ClaudeAskUserQuestionOutputSchema = z.object({
|
|
25
|
+
answers: z.record(z.string(), z.string()),
|
|
26
|
+
});
|
|
27
|
+
export const AskUserQuestionToolPartSchema = z.object({
|
|
28
|
+
type: z.literal("tool"),
|
|
29
|
+
toolCallId: z.string(),
|
|
30
|
+
// Accept the camelCase SDK tool names (now passed through downstream) plus the
|
|
31
|
+
// legacy snake_case aliases for backward compatibility.
|
|
32
|
+
toolName: z.enum(["AskUserQuestion", "ask_user_question"]),
|
|
33
|
+
state: z.literal("output-available"),
|
|
34
|
+
output: ClaudeAskUserQuestionOutputSchema,
|
|
35
|
+
});
|
|
36
|
+
export const ExitPlanModeToolPartSchema = z.object({
|
|
37
|
+
type: z.literal("tool"),
|
|
38
|
+
toolCallId: z.string(),
|
|
39
|
+
toolName: z.enum(["ExitPlanMode", "exit_plan_mode"]),
|
|
40
|
+
state: z.literal("output-available"),
|
|
41
|
+
output: z.unknown(),
|
|
42
|
+
});
|
|
43
|
+
export const ToolApprovalSchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
approved: z.boolean(),
|
|
46
|
+
reason: z.string().optional(),
|
|
47
|
+
});
|
|
48
|
+
export const ToolApprovalPartSchema = z.object({
|
|
49
|
+
type: z.literal("tool"),
|
|
50
|
+
toolCallId: z.string(),
|
|
51
|
+
toolName: z.string(),
|
|
52
|
+
state: z.literal("approval-responded"),
|
|
53
|
+
input: z.unknown().optional(),
|
|
54
|
+
approval: ToolApprovalSchema,
|
|
55
|
+
});
|
|
56
|
+
export const AgentAnswerToolPartSchema = z.union([
|
|
57
|
+
AskUserQuestionToolPartSchema,
|
|
58
|
+
ExitPlanModeToolPartSchema,
|
|
59
|
+
ToolApprovalPartSchema,
|
|
60
|
+
]);
|
|
61
|
+
export const ChatCustomPartSchema = z.looseObject({
|
|
62
|
+
type: z.string(),
|
|
63
|
+
});
|
|
64
|
+
export const CodexChatForkSchema = z.strictObject({
|
|
65
|
+
threadId: z.string().min(1),
|
|
66
|
+
turnId: z.string().min(1),
|
|
67
|
+
});
|
|
68
|
+
export const ClaudeChatForkSchema = z.strictObject({
|
|
69
|
+
sessionId: z.string().min(1),
|
|
70
|
+
messageId: z.string().min(1),
|
|
71
|
+
});
|
|
72
|
+
export const ChatForkSchema = z.union([CodexChatForkSchema, ClaudeChatForkSchema]);
|
|
73
|
+
export const ChatBodySchema = z.object({
|
|
74
|
+
conversationId: z.string().optional(),
|
|
75
|
+
fork: ChatForkSchema.optional(),
|
|
76
|
+
model: z.string(),
|
|
77
|
+
message: z.object({
|
|
78
|
+
id: z.string().optional(),
|
|
79
|
+
role: z.enum(["user", "assistant"]).optional(),
|
|
80
|
+
parts: z.array(z.union([
|
|
81
|
+
ChatTextPartSchema,
|
|
82
|
+
ChatFilePartSchema,
|
|
83
|
+
ChatStepStartPartSchema,
|
|
84
|
+
AgentAnswerToolPartSchema,
|
|
85
|
+
ChatToolPartSchema,
|
|
86
|
+
ChatCustomPartSchema,
|
|
87
|
+
])),
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LiveSession, type LiveSessionInit } from "./live-session.js";
|
|
2
|
+
export type CreateSessionParams = Omit<LiveSessionInit, "idleTimeoutMs" | "onRecycle">;
|
|
3
|
+
export declare class SessionManager {
|
|
4
|
+
private readonly idleTimeoutMs;
|
|
5
|
+
private readonly sessions;
|
|
6
|
+
constructor(idleTimeoutMs: () => number);
|
|
7
|
+
get(conversationId: string | undefined): LiveSession | undefined;
|
|
8
|
+
create(params: CreateSessionParams): LiveSession;
|
|
9
|
+
bind(session: LiveSession, conversationId: string): void;
|
|
10
|
+
cancel(conversationId: string): boolean;
|
|
11
|
+
get size(): number;
|
|
12
|
+
private recycle;
|
|
13
|
+
private removeByValue;
|
|
14
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { LiveSession } from "./live-session.js";
|
|
2
|
+
export class SessionManager {
|
|
3
|
+
idleTimeoutMs;
|
|
4
|
+
sessions = new Map();
|
|
5
|
+
constructor(idleTimeoutMs) {
|
|
6
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
7
|
+
}
|
|
8
|
+
get(conversationId) {
|
|
9
|
+
if (!conversationId)
|
|
10
|
+
return undefined;
|
|
11
|
+
const session = this.sessions.get(conversationId);
|
|
12
|
+
if (session?.isDead) {
|
|
13
|
+
this.removeByValue(session);
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return session;
|
|
17
|
+
}
|
|
18
|
+
create(params) {
|
|
19
|
+
const session = new LiveSession({
|
|
20
|
+
...params,
|
|
21
|
+
idleTimeoutMs: this.idleTimeoutMs(),
|
|
22
|
+
onRecycle: (candidate, generation) => this.recycle(candidate, generation),
|
|
23
|
+
});
|
|
24
|
+
this.sessions.set(params.conversationId, session);
|
|
25
|
+
return session;
|
|
26
|
+
}
|
|
27
|
+
bind(session, conversationId) {
|
|
28
|
+
if (session.isDead)
|
|
29
|
+
return;
|
|
30
|
+
const previousId = session.conversationId;
|
|
31
|
+
session.bindConversation(conversationId);
|
|
32
|
+
this.sessions.set(conversationId, session);
|
|
33
|
+
if (previousId !== conversationId && this.sessions.get(previousId) === session)
|
|
34
|
+
this.sessions.delete(previousId);
|
|
35
|
+
}
|
|
36
|
+
cancel(conversationId) {
|
|
37
|
+
const session = this.get(conversationId);
|
|
38
|
+
if (!session)
|
|
39
|
+
return false;
|
|
40
|
+
session.abort();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
get size() {
|
|
44
|
+
return new Set(this.sessions.values()).size;
|
|
45
|
+
}
|
|
46
|
+
recycle(session, generation) {
|
|
47
|
+
if (!session.isDead || generation !== session.currentGeneration)
|
|
48
|
+
return;
|
|
49
|
+
this.removeByValue(session);
|
|
50
|
+
}
|
|
51
|
+
removeByValue(session) {
|
|
52
|
+
for (const [key, candidate] of this.sessions) {
|
|
53
|
+
if (candidate === session)
|
|
54
|
+
this.sessions.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregates Claude Code's incremental task tools (TaskCreate / TaskUpdate /
|
|
3
|
+
* TaskList / TaskGet / TaskStop / TaskOutput) into a single todo list that is
|
|
4
|
+
* compatible with the legacy `TodoWrite` event shape consumed downstream:
|
|
5
|
+
*
|
|
6
|
+
* { todos: [{ content, status, activeForm? }] }
|
|
7
|
+
*
|
|
8
|
+
* Claude Code replaced the old all-at-once `TodoWrite` tool with a set of
|
|
9
|
+
* incremental CRUD tools that persist each task as a JSON file under
|
|
10
|
+
* `<claude config dir>/tasks/<session_id>/<N>.json` (N starts at 1):
|
|
11
|
+
*
|
|
12
|
+
* {
|
|
13
|
+
* "id": "1",
|
|
14
|
+
* "subject": "...",
|
|
15
|
+
* "description": "...",
|
|
16
|
+
* "activeForm": "...",
|
|
17
|
+
* "status": "pending" | "in_progress" | "completed",
|
|
18
|
+
* "blocks": [],
|
|
19
|
+
* "blockedBy": []
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Rather than replaying tool_use / tool_result payloads (whose shapes are not
|
|
23
|
+
* guaranteed by the minified SDK), this aggregator reads those files directly
|
|
24
|
+
* after each Task* tool call completes, which is the canonical source of truth.
|
|
25
|
+
*/
|
|
26
|
+
export type TodoStatus = "pending" | "in_progress" | "completed";
|
|
27
|
+
export interface TodoItem {
|
|
28
|
+
content: string;
|
|
29
|
+
status: TodoStatus;
|
|
30
|
+
activeForm?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Root directory under which per-session task files are persisted. */
|
|
33
|
+
export declare function defaultTasksRoot(): string;
|
|
34
|
+
/** Reads and orders the task files for a single session directory. */
|
|
35
|
+
export declare function readSessionTasks(tasksRoot: string, sessionId: string): TodoItem[];
|
|
36
|
+
export interface TaskAggregator {
|
|
37
|
+
/** Learn the active session id so subsequent refreshes read the right dir. */
|
|
38
|
+
setSessionId(sessionId: string | undefined): void;
|
|
39
|
+
/** Returns true if the tool is a Task* tool (and is consumed by the aggregator). */
|
|
40
|
+
isTaskTool(name: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Record a Task* tool_use. The persisted file is usually written by the time
|
|
43
|
+
* the matching tool_result arrives, so this mostly registers the call id for
|
|
44
|
+
* downstream suppression. Returns true if the on-disk list changed.
|
|
45
|
+
*/
|
|
46
|
+
applyToolUse(toolCallId: string, name: string, input: unknown): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Record a Task* tool_result and re-read the session's task files. Returns
|
|
49
|
+
* true if the on-disk list changed since the last snapshot.
|
|
50
|
+
*/
|
|
51
|
+
applyToolResult(toolCallId: string, result: unknown): boolean;
|
|
52
|
+
/** Whether the given tool_use_id belonged to a Task* tool call. */
|
|
53
|
+
isTaskToolCall(toolCallId: string): boolean;
|
|
54
|
+
/** Re-read task files for the active session; returns true if changed. */
|
|
55
|
+
refresh(): boolean;
|
|
56
|
+
/** Current ordered todo list. */
|
|
57
|
+
snapshot(): {
|
|
58
|
+
todos: TodoItem[];
|
|
59
|
+
};
|
|
60
|
+
/** Whether the list changed since the last `clearDirty()`. */
|
|
61
|
+
dirty(): boolean;
|
|
62
|
+
/** Reset the dirty flag (call after emitting an aggregated event). */
|
|
63
|
+
clearDirty(): void;
|
|
64
|
+
}
|
|
65
|
+
export interface TaskAggregatorOptions {
|
|
66
|
+
tasksRoot?: string;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function createTaskAggregator(options?: TaskAggregatorOptions): TaskAggregator;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregates Claude Code's incremental task tools (TaskCreate / TaskUpdate /
|
|
3
|
+
* TaskList / TaskGet / TaskStop / TaskOutput) into a single todo list that is
|
|
4
|
+
* compatible with the legacy `TodoWrite` event shape consumed downstream:
|
|
5
|
+
*
|
|
6
|
+
* { todos: [{ content, status, activeForm? }] }
|
|
7
|
+
*
|
|
8
|
+
* Claude Code replaced the old all-at-once `TodoWrite` tool with a set of
|
|
9
|
+
* incremental CRUD tools that persist each task as a JSON file under
|
|
10
|
+
* `<claude config dir>/tasks/<session_id>/<N>.json` (N starts at 1):
|
|
11
|
+
*
|
|
12
|
+
* {
|
|
13
|
+
* "id": "1",
|
|
14
|
+
* "subject": "...",
|
|
15
|
+
* "description": "...",
|
|
16
|
+
* "activeForm": "...",
|
|
17
|
+
* "status": "pending" | "in_progress" | "completed",
|
|
18
|
+
* "blocks": [],
|
|
19
|
+
* "blockedBy": []
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Rather than replaying tool_use / tool_result payloads (whose shapes are not
|
|
23
|
+
* guaranteed by the minified SDK), this aggregator reads those files directly
|
|
24
|
+
* after each Task* tool call completes, which is the canonical source of truth.
|
|
25
|
+
*/
|
|
26
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
const TASK_TOOL_NAMES = new Set(["TaskCreate", "TaskUpdate", "TaskList", "TaskGet", "TaskStop", "TaskOutput"]);
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return Boolean(value && typeof value === "object");
|
|
32
|
+
}
|
|
33
|
+
function asString(value) {
|
|
34
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
function normalizeStatus(value) {
|
|
37
|
+
if (value === "pending" || value === "in_progress" || value === "completed")
|
|
38
|
+
return value;
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Root directory under which per-session task files are persisted. */
|
|
42
|
+
export function defaultTasksRoot() {
|
|
43
|
+
const base = process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
|
|
44
|
+
return join(base, "tasks");
|
|
45
|
+
}
|
|
46
|
+
function toTodo(raw) {
|
|
47
|
+
if (!isRecord(raw))
|
|
48
|
+
return undefined;
|
|
49
|
+
const status = normalizeStatus(raw.status);
|
|
50
|
+
// Files may exist for tasks in states we do not surface; skip unknown ones.
|
|
51
|
+
if (!status)
|
|
52
|
+
return undefined;
|
|
53
|
+
const content = asString(raw.subject) ?? asString(raw.activeForm) ?? asString(raw.description);
|
|
54
|
+
if (!content)
|
|
55
|
+
return undefined;
|
|
56
|
+
const activeForm = asString(raw.activeForm);
|
|
57
|
+
return { content, status, ...(activeForm ? { activeForm } : null) };
|
|
58
|
+
}
|
|
59
|
+
/** Reads and orders the task files for a single session directory. */
|
|
60
|
+
export function readSessionTasks(tasksRoot, sessionId) {
|
|
61
|
+
const dir = join(tasksRoot, sessionId);
|
|
62
|
+
let files;
|
|
63
|
+
try {
|
|
64
|
+
files = readdirSync(dir);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const ordered = files
|
|
70
|
+
.filter((name) => name.endsWith(".json"))
|
|
71
|
+
.map((name) => ({ name, n: Number.parseInt(name, 10) }))
|
|
72
|
+
.filter((entry) => Number.isFinite(entry.n))
|
|
73
|
+
.sort((a, b) => a.n - b.n);
|
|
74
|
+
const todos = [];
|
|
75
|
+
for (const { name } of ordered) {
|
|
76
|
+
try {
|
|
77
|
+
const raw = JSON.parse(readFileSync(join(dir, name), "utf-8"));
|
|
78
|
+
const todo = toTodo(raw);
|
|
79
|
+
if (todo)
|
|
80
|
+
todos.push(todo);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore unreadable / partially-written files; the next refresh recovers.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return todos;
|
|
87
|
+
}
|
|
88
|
+
export function createTaskAggregator(options = {}) {
|
|
89
|
+
const tasksRoot = options.tasksRoot ?? defaultTasksRoot();
|
|
90
|
+
// All tool_use_ids that belonged to Task* tools, so their raw tool input /
|
|
91
|
+
// result can be suppressed downstream in favor of the aggregated event.
|
|
92
|
+
const taskToolCallIds = new Set();
|
|
93
|
+
let sessionId = options.sessionId;
|
|
94
|
+
let todos = [];
|
|
95
|
+
let lastSerialized = JSON.stringify(todos);
|
|
96
|
+
let isDirty = false;
|
|
97
|
+
const refresh = () => {
|
|
98
|
+
if (!sessionId)
|
|
99
|
+
return false;
|
|
100
|
+
const next = readSessionTasks(tasksRoot, sessionId);
|
|
101
|
+
const serialized = JSON.stringify(next);
|
|
102
|
+
if (serialized === lastSerialized)
|
|
103
|
+
return false;
|
|
104
|
+
todos = next;
|
|
105
|
+
lastSerialized = serialized;
|
|
106
|
+
isDirty = true;
|
|
107
|
+
return true;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
setSessionId(next) {
|
|
111
|
+
if (next && next !== sessionId) {
|
|
112
|
+
sessionId = next;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
isTaskTool(name) {
|
|
116
|
+
return TASK_TOOL_NAMES.has(name);
|
|
117
|
+
},
|
|
118
|
+
applyToolUse(toolCallId, name) {
|
|
119
|
+
if (!TASK_TOOL_NAMES.has(name))
|
|
120
|
+
return false;
|
|
121
|
+
taskToolCallIds.add(toolCallId);
|
|
122
|
+
return refresh();
|
|
123
|
+
},
|
|
124
|
+
applyToolResult(toolCallId) {
|
|
125
|
+
if (!taskToolCallIds.has(toolCallId))
|
|
126
|
+
return false;
|
|
127
|
+
return refresh();
|
|
128
|
+
},
|
|
129
|
+
isTaskToolCall(toolCallId) {
|
|
130
|
+
return taskToolCallIds.has(toolCallId);
|
|
131
|
+
},
|
|
132
|
+
refresh,
|
|
133
|
+
snapshot() {
|
|
134
|
+
return { todos: todos.map((todo) => ({ ...todo })) };
|
|
135
|
+
},
|
|
136
|
+
dirty() {
|
|
137
|
+
return isDirty;
|
|
138
|
+
},
|
|
139
|
+
clearDirty() {
|
|
140
|
+
isDirty = false;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SDKResultMessage } from "./claude.js";
|
|
2
|
+
type TokenBreakdown = {
|
|
3
|
+
input_tokens: number;
|
|
4
|
+
output_tokens: number;
|
|
5
|
+
total_tokens: number;
|
|
6
|
+
};
|
|
7
|
+
export type ClaudeTokenUsage = {
|
|
8
|
+
last: TokenBreakdown;
|
|
9
|
+
context?: {
|
|
10
|
+
used_tokens: number;
|
|
11
|
+
window_tokens: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type ClaudeContextSnapshot = {
|
|
15
|
+
usedTokens: number;
|
|
16
|
+
model?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function claudeContextSnapshot(message: {
|
|
19
|
+
usage?: unknown;
|
|
20
|
+
model?: unknown;
|
|
21
|
+
}): ClaudeContextSnapshot | undefined;
|
|
22
|
+
export declare function claudeTokenUsage(message: SDKResultMessage, contextSnapshot: ClaudeContextSnapshot | undefined): ClaudeTokenUsage | undefined;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function sumTokens(...values) {
|
|
2
|
+
let total = 0;
|
|
3
|
+
for (const value of values) {
|
|
4
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
5
|
+
return undefined;
|
|
6
|
+
total += value;
|
|
7
|
+
if (!Number.isSafeInteger(total))
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
return total;
|
|
11
|
+
}
|
|
12
|
+
function resultBreakdown(message) {
|
|
13
|
+
const modelUsages = Object.values(message.modelUsage ?? {});
|
|
14
|
+
if (modelUsages.length === 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
let result;
|
|
17
|
+
for (const usage of modelUsages) {
|
|
18
|
+
const inputTokens = sumTokens(usage.inputTokens, usage.cacheCreationInputTokens, usage.cacheReadInputTokens);
|
|
19
|
+
const outputTokens = sumTokens(usage.outputTokens);
|
|
20
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
const totalTokens = sumTokens(inputTokens, outputTokens);
|
|
23
|
+
if (totalTokens === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
result = addBreakdowns(result, {
|
|
26
|
+
input_tokens: inputTokens,
|
|
27
|
+
output_tokens: outputTokens,
|
|
28
|
+
total_tokens: totalTokens,
|
|
29
|
+
});
|
|
30
|
+
if (!result)
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
function addBreakdowns(left, right) {
|
|
36
|
+
const inputTokens = sumTokens(left?.input_tokens ?? 0, right.input_tokens);
|
|
37
|
+
const outputTokens = sumTokens(left?.output_tokens ?? 0, right.output_tokens);
|
|
38
|
+
const totalTokens = sumTokens(left?.total_tokens ?? 0, right.total_tokens);
|
|
39
|
+
if (inputTokens === undefined || outputTokens === undefined || totalTokens === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
return { input_tokens: inputTokens, output_tokens: outputTokens, total_tokens: totalTokens };
|
|
42
|
+
}
|
|
43
|
+
export function claudeContextSnapshot(message) {
|
|
44
|
+
const rawUsage = message.usage;
|
|
45
|
+
if (!rawUsage || typeof rawUsage !== "object")
|
|
46
|
+
return undefined;
|
|
47
|
+
const usage = rawUsage;
|
|
48
|
+
const usedTokens = sumTokens(usage.input_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens, usage.output_tokens);
|
|
49
|
+
if (usedTokens === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
return {
|
|
52
|
+
usedTokens,
|
|
53
|
+
...(typeof message.model === "string" && message.model ? { model: message.model } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function modelContextWindow(message, model) {
|
|
57
|
+
const entries = Object.entries(message.modelUsage ?? {});
|
|
58
|
+
const selected = entries.length === 1 ? entries[0][1] : model ? entries.find(([name]) => name === model)?.[1] : undefined;
|
|
59
|
+
const contextWindow = selected?.contextWindow;
|
|
60
|
+
return typeof contextWindow === "number" && Number.isSafeInteger(contextWindow) && contextWindow > 0
|
|
61
|
+
? contextWindow
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
64
|
+
export function claudeTokenUsage(message, contextSnapshot) {
|
|
65
|
+
const last = resultBreakdown(message);
|
|
66
|
+
if (!last)
|
|
67
|
+
return undefined;
|
|
68
|
+
const contextWindow = modelContextWindow(message, contextSnapshot?.model);
|
|
69
|
+
return {
|
|
70
|
+
last,
|
|
71
|
+
...(contextSnapshot && contextWindow !== undefined
|
|
72
|
+
? { context: { used_tokens: contextSnapshot.usedTokens, window_tokens: contextWindow } }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AppConfig } from "./config.js";
|
|
2
|
+
export type TunnelClientConfig = AppConfig["tunnel"];
|
|
3
|
+
export declare class ClaudeTunnelClient {
|
|
4
|
+
private readonly config;
|
|
5
|
+
private stopped;
|
|
6
|
+
private socket;
|
|
7
|
+
private running;
|
|
8
|
+
constructor(config: TunnelClientConfig);
|
|
9
|
+
start(): Promise<void>;
|
|
10
|
+
stop(): void;
|
|
11
|
+
private connectOnce;
|
|
12
|
+
private send;
|
|
13
|
+
private handleRequest;
|
|
14
|
+
}
|
|
15
|
+
export declare function startClaudeTunnelClient(config?: TunnelClientConfig): ClaudeTunnelClient | undefined;
|