@khalilgharbaoui/opencode-claude-code-plugin 0.4.22 → 0.4.23

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/index.js CHANGED
@@ -124,6 +124,91 @@ var log = {
124
124
  }
125
125
  };
126
126
 
127
+ // src/todo-ledger.ts
128
+ var ledgers = /* @__PURE__ */ new Map();
129
+ var PENDING_CREATE_TTL_MS = 6e4;
130
+ var TASK_CREATED_PATTERN = /Task\s*#?\s*(\d+)\s+created/i;
131
+ var VALID_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
132
+ function getOrCreate(sessionId) {
133
+ let ledger = ledgers.get(sessionId);
134
+ if (!ledger) {
135
+ ledger = { todos: /* @__PURE__ */ new Map(), pendingCreates: /* @__PURE__ */ new Map() };
136
+ ledgers.set(sessionId, ledger);
137
+ }
138
+ return ledger;
139
+ }
140
+ function prunePending(ledger) {
141
+ const cutoff = Date.now() - PENDING_CREATE_TTL_MS;
142
+ for (const [id, pending] of ledger.pendingCreates) {
143
+ if (pending.createdAt < cutoff) ledger.pendingCreates.delete(id);
144
+ }
145
+ }
146
+ function materialize(ledger) {
147
+ return Array.from(ledger.todos.values());
148
+ }
149
+ function resolveSubject(input) {
150
+ const subject = typeof input?.subject === "string" ? input.subject.trim() : "";
151
+ if (subject) return subject;
152
+ const description = typeof input?.description === "string" ? input.description.trim() : "";
153
+ if (description) return description;
154
+ return "(no subject)";
155
+ }
156
+ function applyTaskCreateToolUse(sessionId, toolUseId, input) {
157
+ if (!sessionId || !toolUseId) return;
158
+ const ledger = getOrCreate(sessionId);
159
+ prunePending(ledger);
160
+ ledger.pendingCreates.set(toolUseId, {
161
+ subject: resolveSubject(input),
162
+ createdAt: Date.now()
163
+ });
164
+ }
165
+ function applyTaskCreateToolResult(sessionId, toolUseId, resultText) {
166
+ if (!sessionId || !toolUseId) return null;
167
+ const ledger = ledgers.get(sessionId);
168
+ if (!ledger) return null;
169
+ const pending = ledger.pendingCreates.get(toolUseId);
170
+ if (!pending) return null;
171
+ ledger.pendingCreates.delete(toolUseId);
172
+ const match = typeof resultText === "string" ? resultText.match(TASK_CREATED_PATTERN) : null;
173
+ if (!match) {
174
+ log.debug("TaskCreate result did not match expected format", { sessionId, toolUseId, resultText });
175
+ return null;
176
+ }
177
+ const claudeId = match[1];
178
+ if (ledger.todos.has(claudeId)) {
179
+ log.debug("TaskCreate result for already-known claude id; overwriting", { sessionId, claudeId });
180
+ }
181
+ ledger.todos.set(claudeId, { id: claudeId, content: pending.subject, status: "pending" });
182
+ return materialize(ledger);
183
+ }
184
+ function applyTaskUpdate(sessionId, input) {
185
+ if (!sessionId) return null;
186
+ const taskId = typeof input?.taskId === "string" ? input.taskId : null;
187
+ if (!taskId) return null;
188
+ const ledger = ledgers.get(sessionId);
189
+ if (!ledger) return null;
190
+ const entry = ledger.todos.get(taskId);
191
+ if (!entry) {
192
+ log.debug("TaskUpdate for unknown task id", { sessionId, taskId });
193
+ return null;
194
+ }
195
+ if (input?.status === "deleted") {
196
+ ledger.todos.delete(taskId);
197
+ return materialize(ledger);
198
+ }
199
+ if (typeof input?.status === "string" && VALID_STATUSES.has(input.status)) {
200
+ entry.status = input.status;
201
+ }
202
+ if (typeof input?.subject === "string" && input.subject.trim().length > 0) {
203
+ entry.content = input.subject.trim();
204
+ }
205
+ return materialize(ledger);
206
+ }
207
+ function clearLedger(sessionId) {
208
+ if (!sessionId) return;
209
+ ledgers.delete(sessionId);
210
+ }
211
+
127
212
  // src/tool-mapping.ts
128
213
  function mapToolInput(name, input) {
129
214
  if (!input) return input;
@@ -199,17 +284,42 @@ var CLAUDE_INTERNAL_TOOLS = /* @__PURE__ */ new Set([
199
284
  "ToolSearch",
200
285
  "Agent",
201
286
  "AskFollowupQuestion",
202
- "TaskCreate",
203
- "TaskUpdate",
204
287
  "TaskList",
205
288
  "TaskGet",
206
289
  "TaskStop"
207
290
  ]);
291
+ function emitTodoWrite(todos) {
292
+ return {
293
+ name: "todowrite",
294
+ input: {
295
+ todos: todos.map((todo) => ({
296
+ id: todo.id,
297
+ content: todo.content,
298
+ status: todo.status,
299
+ priority: "medium"
300
+ }))
301
+ },
302
+ executed: false
303
+ };
304
+ }
208
305
  function mapTool(name, input, opts) {
209
306
  if (CLAUDE_INTERNAL_TOOLS.has(name)) {
210
307
  log.debug("skipping Claude CLI internal tool", { name });
211
308
  return { name, input, executed: true, skip: true };
212
309
  }
310
+ if (name === "TaskCreate") {
311
+ if (opts?.sessionId && opts?.toolUseId) {
312
+ applyTaskCreateToolUse(opts.sessionId, opts.toolUseId, input);
313
+ }
314
+ return { name, input, executed: true, skip: true };
315
+ }
316
+ if (name === "TaskUpdate") {
317
+ if (opts?.sessionId) {
318
+ const list = applyTaskUpdate(opts.sessionId, input);
319
+ if (list !== null) return emitTodoWrite(list);
320
+ }
321
+ return { name, input, executed: true, skip: true };
322
+ }
213
323
  if (name === "EnterPlanMode") return { name: "plan_enter", input: {}, executed: false };
214
324
  if (name === "ExitPlanMode") return { name: "plan_exit", input, executed: false };
215
325
  if (name === "TodoWrite") {
@@ -1181,6 +1291,8 @@ function setClaudeSessionId(key, sessionId) {
1181
1291
  claudeSessions.set(key, sessionId);
1182
1292
  }
1183
1293
  function deleteClaudeSessionId(key) {
1294
+ const claudeSessionId = claudeSessions.get(key);
1295
+ if (claudeSessionId) clearLedger(claudeSessionId);
1184
1296
  claudeSessions.delete(key);
1185
1297
  }
1186
1298
  function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile) {
@@ -2703,7 +2815,11 @@ ${plan}
2703
2815
  input: mappedInput,
2704
2816
  executed,
2705
2817
  skip
2706
- } = mapTool(tc.name, tc.args, { webSearch: this.config.webSearch });
2818
+ } = mapTool(tc.name, tc.args, {
2819
+ webSearch: this.config.webSearch,
2820
+ sessionId: getClaudeSessionId(sk),
2821
+ toolUseId: tc.id
2822
+ });
2707
2823
  if (skip) continue;
2708
2824
  content.push({
2709
2825
  type: "tool-call",
@@ -3146,7 +3262,11 @@ ${plan}
3146
3262
  const { name: mappedName, skip, executed } = mapTool(
3147
3263
  block.name,
3148
3264
  void 0,
3149
- { webSearch: self.config.webSearch }
3265
+ {
3266
+ webSearch: self.config.webSearch,
3267
+ sessionId: getClaudeSessionId(sk),
3268
+ toolUseId: block.id
3269
+ }
3150
3270
  );
3151
3271
  if (!skip) {
3152
3272
  controller.enqueue({
@@ -3280,7 +3400,11 @@ ${plan}
3280
3400
  input: mappedInput,
3281
3401
  executed,
3282
3402
  skip
3283
- } = mapTool(tc.name, parsedInput, { webSearch: self.config.webSearch });
3403
+ } = mapTool(tc.name, parsedInput, {
3404
+ webSearch: self.config.webSearch,
3405
+ sessionId: getClaudeSessionId(sk),
3406
+ toolUseId: tc.id
3407
+ });
3284
3408
  if (!skip) {
3285
3409
  toolCallsById.set(tc.id, {
3286
3410
  id: tc.id,
@@ -3446,7 +3570,11 @@ ${plan}
3446
3570
  input: mappedInput,
3447
3571
  executed,
3448
3572
  skip
3449
- } = mapTool(block.name, parsedInput, { webSearch: self.config.webSearch });
3573
+ } = mapTool(block.name, parsedInput, {
3574
+ webSearch: self.config.webSearch,
3575
+ sessionId: getClaudeSessionId(sk),
3576
+ toolUseId: block.id
3577
+ });
3450
3578
  if (!skip) {
3451
3579
  if (!executed) skipResultForIds.add(block.id);
3452
3580
  controller.enqueue({
@@ -3487,16 +3615,48 @@ ${plan}
3487
3615
  });
3488
3616
  continue;
3489
3617
  }
3618
+ let resultText = "";
3619
+ if (typeof block.content === "string") {
3620
+ resultText = block.content;
3621
+ } else if (Array.isArray(block.content)) {
3622
+ resultText = block.content.filter(
3623
+ (c) => c.type === "text" && typeof c.text === "string"
3624
+ ).map((c) => c.text).join("\n");
3625
+ }
3626
+ const claudeSessionId = getClaudeSessionId(sk);
3627
+ if (claudeSessionId) {
3628
+ const list = applyTaskCreateToolResult(
3629
+ claudeSessionId,
3630
+ block.tool_use_id,
3631
+ resultText
3632
+ );
3633
+ if (list) {
3634
+ const synthId = `todowrite_${block.tool_use_id}`;
3635
+ controller.enqueue({
3636
+ type: "tool-input-start",
3637
+ id: synthId,
3638
+ toolName: "todowrite",
3639
+ providerExecuted: false
3640
+ });
3641
+ controller.enqueue({
3642
+ type: "tool-call",
3643
+ toolCallId: synthId,
3644
+ toolName: "todowrite",
3645
+ input: JSON.stringify({
3646
+ todos: list.map((t) => ({
3647
+ id: t.id,
3648
+ content: t.content,
3649
+ status: t.status,
3650
+ priority: "medium"
3651
+ }))
3652
+ }),
3653
+ providerExecuted: false
3654
+ });
3655
+ noteToolActivity();
3656
+ }
3657
+ }
3490
3658
  const toolCall = toolCallsById.get(block.tool_use_id);
3491
3659
  if (toolCall) {
3492
- let resultText = "";
3493
- if (typeof block.content === "string") {
3494
- resultText = block.content;
3495
- } else if (Array.isArray(block.content)) {
3496
- resultText = block.content.filter(
3497
- (c) => c.type === "text" && typeof c.text === "string"
3498
- ).map((c) => c.text).join("\n");
3499
- }
3500
3660
  controller.enqueue({
3501
3661
  type: "tool-result",
3502
3662
  toolCallId: block.tool_use_id,