@khalilgharbaoui/opencode-claude-code-plugin 0.58.0 → 0.59.0
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.d.ts +22 -4
- package/dist/index.js +306 -71
- package/dist/index.js.map +1 -1
- package/dist/tui.js +72 -0
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +7 -1
package/dist/index.d.ts
CHANGED
|
@@ -128,13 +128,26 @@ interface V2CommandDefinition {
|
|
|
128
128
|
readonly description?: string;
|
|
129
129
|
readonly execute: (input: V2CommandInvocation) => Promise<void>;
|
|
130
130
|
}
|
|
131
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* A bus event. Acted on: `session.deleted`, `session.execution.started` /
|
|
133
|
+
* `succeeded` / `failed` / `interrupted` (with `reason`, measured on 2.0.31,
|
|
134
|
+
* h #g253), and the never-emitted-there `session.status` / `session.idle`.
|
|
135
|
+
*/
|
|
132
136
|
interface V2Event {
|
|
133
137
|
readonly type?: string;
|
|
134
138
|
readonly data?: {
|
|
135
139
|
readonly sessionID?: string;
|
|
136
140
|
} & Record<string, unknown>;
|
|
137
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* `ctx.rpc.register`'s result (`@opencode/plugin` 2.0.31 `RpcRegistration`):
|
|
144
|
+
* a registration whose events travel on the bus as `rpc.<id>.<event>`.
|
|
145
|
+
*/
|
|
146
|
+
interface V2RpcRegistration extends V2Registration {
|
|
147
|
+
readonly events: {
|
|
148
|
+
emit(name: string, data: Record<string, unknown>): Promise<void>;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
138
151
|
interface V2Context {
|
|
139
152
|
readonly app: {
|
|
140
153
|
readonly version: string;
|
|
@@ -167,6 +180,14 @@ interface V2Context {
|
|
|
167
180
|
signal?: AbortSignal;
|
|
168
181
|
}): AsyncIterable<V2Event>;
|
|
169
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* Absent before 2.0.31's measured shape: the definition is plain JSON
|
|
185
|
+
* Schema, which `Rpc.PortableDefinition` accepts, and the handlers object is
|
|
186
|
+
* empty because the definition declares no methods.
|
|
187
|
+
*/
|
|
188
|
+
readonly rpc?: {
|
|
189
|
+
register(definition: unknown, handlers: Record<string, never>): Promise<V2RpcRegistration>;
|
|
190
|
+
};
|
|
170
191
|
readonly skill?: {
|
|
171
192
|
transform(callback: (editor: {
|
|
172
193
|
get(id: string): unknown;
|
|
@@ -1549,8 +1570,6 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
1549
1570
|
* 3. `"default"`.
|
|
1550
1571
|
*/
|
|
1551
1572
|
private sessionAffinity;
|
|
1552
|
-
private controlRequestBehaviorForTool;
|
|
1553
|
-
private writeControlResponse;
|
|
1554
1573
|
/**
|
|
1555
1574
|
* Handle Claude stream-json control requests (`can_use_tool`, etc.) and
|
|
1556
1575
|
* respond via stdin with a matching `control_response`.
|
|
@@ -1567,7 +1586,6 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
1567
1586
|
*/
|
|
1568
1587
|
private resolveCompactionModel;
|
|
1569
1588
|
private thinkingCliOptions;
|
|
1570
|
-
private latestUserText;
|
|
1571
1589
|
private synthesizeTitle;
|
|
1572
1590
|
/**
|
|
1573
1591
|
* Aggregates the one turn implementation into a `doGenerate` result.
|
package/dist/index.js
CHANGED
|
@@ -272,6 +272,8 @@ var MAX_TOASTED_MESSAGES = 200;
|
|
|
272
272
|
var toastBurstLimit = 3;
|
|
273
273
|
var toastBurstWindowMs = 2e3;
|
|
274
274
|
var activeSink = null;
|
|
275
|
+
var activeNotifier = null;
|
|
276
|
+
var bufferedNotices = [];
|
|
275
277
|
var bufferedEntries = [];
|
|
276
278
|
var droppedBeforeSink = 0;
|
|
277
279
|
var toastedMessages = /* @__PURE__ */ new Set();
|
|
@@ -296,6 +298,13 @@ function setLogSink(sink) {
|
|
|
296
298
|
});
|
|
297
299
|
}
|
|
298
300
|
}
|
|
301
|
+
function setToastNotifier(notifier) {
|
|
302
|
+
activeNotifier = notifier;
|
|
303
|
+
if (!notifier) return;
|
|
304
|
+
const pending2 = bufferedNotices;
|
|
305
|
+
bufferedNotices = [];
|
|
306
|
+
for (const entry of pending2) maybeToast(entry, notifier);
|
|
307
|
+
}
|
|
299
308
|
function routeToSink(entry) {
|
|
300
309
|
if (!activeSink) {
|
|
301
310
|
if (bufferedEntries.length >= MAX_BUFFERED_SINK_ENTRIES) {
|
|
@@ -314,18 +323,34 @@ function deliverToSink(entry) {
|
|
|
314
323
|
sink.log(entry);
|
|
315
324
|
} catch {
|
|
316
325
|
}
|
|
326
|
+
maybeToast(entry, (toast) => sink.toast(toast));
|
|
327
|
+
}
|
|
328
|
+
function maybeToast(entry, send) {
|
|
317
329
|
if (entry.level !== "warn" && entry.level !== "error") return;
|
|
318
330
|
if (toastedMessages.has(entry.message)) return;
|
|
319
331
|
if (toastedMessages.size < MAX_TOASTED_MESSAGES) toastedMessages.add(entry.message);
|
|
320
332
|
if (!allowToastNow()) return;
|
|
321
|
-
sendToast(
|
|
333
|
+
sendToast(send, entry);
|
|
322
334
|
}
|
|
323
|
-
function sendToast(
|
|
335
|
+
function sendToast(send, entry) {
|
|
324
336
|
try {
|
|
325
|
-
|
|
337
|
+
send(entry);
|
|
326
338
|
} catch {
|
|
327
339
|
}
|
|
328
340
|
}
|
|
341
|
+
function toastTarget() {
|
|
342
|
+
const sink = activeSink;
|
|
343
|
+
if (sink) return (entry) => sink.toast(entry);
|
|
344
|
+
return activeNotifier;
|
|
345
|
+
}
|
|
346
|
+
function routeToNotifier(entry) {
|
|
347
|
+
const notifier = activeNotifier;
|
|
348
|
+
if (notifier) {
|
|
349
|
+
maybeToast(entry, notifier);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (bufferedNotices.length < MAX_BUFFERED_SINK_ENTRIES) bufferedNotices.push(entry);
|
|
353
|
+
}
|
|
329
354
|
function allowToastNow() {
|
|
330
355
|
const now = Date.now();
|
|
331
356
|
if (now - burstWindowStart >= toastBurstWindowMs) {
|
|
@@ -347,10 +372,11 @@ function armBurstSummary() {
|
|
|
347
372
|
const suppressed = burstSuppressed;
|
|
348
373
|
burstSuppressed = 0;
|
|
349
374
|
if (suppressed <= 0) return;
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
352
|
-
const
|
|
353
|
-
|
|
375
|
+
const target = toastTarget();
|
|
376
|
+
if (!target) return;
|
|
377
|
+
const where = activeSink ? "opencode's log" : "the plugin log file";
|
|
378
|
+
const message = `${suppressed} more claude-code warning(s) were not shown; the full text is in ${where}`;
|
|
379
|
+
sendToast(target, { level: "warn", message, line: fmt("WARN", message) });
|
|
354
380
|
}, toastBurstWindowMs);
|
|
355
381
|
burstSummaryTimer.unref?.();
|
|
356
382
|
}
|
|
@@ -366,6 +392,7 @@ function emit(level, msg, data) {
|
|
|
366
392
|
routeToSink({ level, message: msg, data, line });
|
|
367
393
|
} else {
|
|
368
394
|
console.error(line);
|
|
395
|
+
if (level === "warn" || level === "error") routeToNotifier({ level, message: msg, data, line });
|
|
369
396
|
}
|
|
370
397
|
}
|
|
371
398
|
writeToFile(line);
|
|
@@ -6374,10 +6401,71 @@ function watchChildSession(sessionID) {
|
|
|
6374
6401
|
});
|
|
6375
6402
|
return () => answer;
|
|
6376
6403
|
}
|
|
6404
|
+
var eventRunStates = /* @__PURE__ */ new Map();
|
|
6405
|
+
var MAX_EVENT_RUN_STATES = 512;
|
|
6406
|
+
var openEventFeeds = 0;
|
|
6407
|
+
function openSessionRunEventFeed() {
|
|
6408
|
+
openEventFeeds += 1;
|
|
6409
|
+
let closed = false;
|
|
6410
|
+
return () => {
|
|
6411
|
+
if (closed) return;
|
|
6412
|
+
closed = true;
|
|
6413
|
+
openEventFeeds = Math.max(0, openEventFeeds - 1);
|
|
6414
|
+
eventRunStates.clear();
|
|
6415
|
+
};
|
|
6416
|
+
}
|
|
6417
|
+
function noteSessionRunEvent(event) {
|
|
6418
|
+
if (openEventFeeds === 0 || !event || typeof event !== "object") return;
|
|
6419
|
+
const { type, data } = event;
|
|
6420
|
+
if (typeof type !== "string" || !data || typeof data !== "object") return;
|
|
6421
|
+
const sessionID = data.sessionID;
|
|
6422
|
+
if (typeof sessionID !== "string" || !sessionID || sessionID === "default") return;
|
|
6423
|
+
if (type === "session.deleted") {
|
|
6424
|
+
eventRunStates.delete(sessionID);
|
|
6425
|
+
return;
|
|
6426
|
+
}
|
|
6427
|
+
let next;
|
|
6428
|
+
switch (type) {
|
|
6429
|
+
case "session.execution.started":
|
|
6430
|
+
next = "busy";
|
|
6431
|
+
break;
|
|
6432
|
+
case "session.execution.succeeded":
|
|
6433
|
+
case "session.execution.failed":
|
|
6434
|
+
case "session.execution.interrupted":
|
|
6435
|
+
case "session.idle":
|
|
6436
|
+
next = "idle";
|
|
6437
|
+
break;
|
|
6438
|
+
case "session.status": {
|
|
6439
|
+
const status = data.status?.type;
|
|
6440
|
+
if (typeof status === "string") next = status === "idle" ? "idle" : "busy";
|
|
6441
|
+
break;
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
if (!next) return;
|
|
6445
|
+
eventRunStates.delete(sessionID);
|
|
6446
|
+
eventRunStates.set(sessionID, next);
|
|
6447
|
+
while (eventRunStates.size > MAX_EVENT_RUN_STATES) {
|
|
6448
|
+
const oldest = eventRunStates.keys().next().value;
|
|
6449
|
+
if (oldest === void 0) break;
|
|
6450
|
+
eventRunStates.delete(oldest);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
function sessionRunStateFromEvents(sessionID) {
|
|
6454
|
+
if (openEventFeeds === 0) return "unknown";
|
|
6455
|
+
return eventRunStates.get(sessionID) ?? "unknown";
|
|
6456
|
+
}
|
|
6457
|
+
function busySessionsFromEvents() {
|
|
6458
|
+
if (openEventFeeds === 0) return void 0;
|
|
6459
|
+
const busy = {};
|
|
6460
|
+
for (const [sessionID, state] of eventRunStates) {
|
|
6461
|
+
if (state === "busy") busy[sessionID] = { type: "busy" };
|
|
6462
|
+
}
|
|
6463
|
+
return busy;
|
|
6464
|
+
}
|
|
6377
6465
|
async function fetchSessionRunState(sessionID, directory) {
|
|
6378
6466
|
if (!sessionID || sessionID === "default") return "unknown";
|
|
6379
6467
|
const status = opencodeClient?.session?.status;
|
|
6380
|
-
if (!status) return
|
|
6468
|
+
if (!status) return sessionRunStateFromEvents(sessionID);
|
|
6381
6469
|
try {
|
|
6382
6470
|
const dir = directory ?? await fetchSessionDirectory(sessionID);
|
|
6383
6471
|
const res = await status.call(
|
|
@@ -7734,6 +7822,27 @@ function deleteActiveProcessesForSession(sessionID) {
|
|
|
7734
7822
|
forgetDispatchChoicesForSession(sessionID);
|
|
7735
7823
|
return released;
|
|
7736
7824
|
}
|
|
7825
|
+
var STOPPED_TURN_MESSAGE = "opencode stopped the turn while it was running its proxy tool calls";
|
|
7826
|
+
function releaseParkedProxyCallsForSession(sessionID, reason = STOPPED_TURN_MESSAGE) {
|
|
7827
|
+
if (!sessionID || sessionID === "default") return [];
|
|
7828
|
+
const released = [];
|
|
7829
|
+
for (const [key, ap] of [...activeProcesses]) {
|
|
7830
|
+
const owned = ap.opencodeSessionID === sessionID || describeSessionKey(key).session === sessionID;
|
|
7831
|
+
if (!owned) continue;
|
|
7832
|
+
if (ap.lineEmitter.listenerCount("line") > 0) continue;
|
|
7833
|
+
if (getPendingProxyCalls(key).length === 0) continue;
|
|
7834
|
+
log.info("opencode stopped the turn at a proxy tool boundary; releasing pending calls", {
|
|
7835
|
+
sessionKey: key,
|
|
7836
|
+
sessionID
|
|
7837
|
+
});
|
|
7838
|
+
void interruptTurn(ap).then((idle) => {
|
|
7839
|
+
log.info("interrupt sent for aborted turn", { sk: key, idle });
|
|
7840
|
+
});
|
|
7841
|
+
rejectAllPendingProxyCallsForSession(key, new Error(reason));
|
|
7842
|
+
released.push(key);
|
|
7843
|
+
}
|
|
7844
|
+
return released;
|
|
7845
|
+
}
|
|
7737
7846
|
function killAllActiveProcesses() {
|
|
7738
7847
|
const keys = [...activeProcesses.keys()];
|
|
7739
7848
|
for (const key of keys) deleteActiveProcess(key);
|
|
@@ -8373,17 +8482,14 @@ async function fetchAsideHistory(client, sessionID, question) {
|
|
|
8373
8482
|
return [];
|
|
8374
8483
|
}
|
|
8375
8484
|
}
|
|
8376
|
-
async function
|
|
8485
|
+
async function runBtwCommand(client, input, options = {}) {
|
|
8377
8486
|
const question = input.arguments.trim();
|
|
8378
|
-
if (!question)
|
|
8379
|
-
showToast(client, { title: "btw", message: SIDE_QUESTION_USAGE, variant: "warning", duration: 6e3 });
|
|
8380
|
-
throw new BtwHandledError("/btw needs a question.");
|
|
8381
|
-
}
|
|
8487
|
+
if (!question) return "bare";
|
|
8382
8488
|
const active = await waitForAsideProcess(client, input.sessionID, options);
|
|
8383
8489
|
const transport = active?.asideTransport;
|
|
8384
8490
|
if (!active || !transport) {
|
|
8385
8491
|
if (active) log.info("btw: process has no aside transport, leaving it to the turn", { sessionID: input.sessionID });
|
|
8386
|
-
return;
|
|
8492
|
+
return "release";
|
|
8387
8493
|
}
|
|
8388
8494
|
let busy = false;
|
|
8389
8495
|
let inlineDone = false;
|
|
@@ -8444,7 +8550,7 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
8444
8550
|
}
|
|
8445
8551
|
);
|
|
8446
8552
|
}
|
|
8447
|
-
if (!busy) return;
|
|
8553
|
+
if (!busy) return "release";
|
|
8448
8554
|
const started2 = Date.now();
|
|
8449
8555
|
const outcome = await Promise.race([
|
|
8450
8556
|
inlineDelivered,
|
|
@@ -8457,18 +8563,46 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
8457
8563
|
sessionID: input.sessionID,
|
|
8458
8564
|
waitedMs: Date.now() - started2
|
|
8459
8565
|
});
|
|
8460
|
-
|
|
8566
|
+
return "inline";
|
|
8461
8567
|
}
|
|
8462
8568
|
log.info("btw: turn over, releasing the /btw message", {
|
|
8463
8569
|
sessionID: input.sessionID,
|
|
8464
8570
|
idle: outcome === "idle",
|
|
8465
8571
|
waitedMs: Date.now() - started2
|
|
8466
8572
|
});
|
|
8573
|
+
return outcome === "timeout" ? "timeout" : "release";
|
|
8574
|
+
}
|
|
8575
|
+
async function handleBtwCommand(client, input, options = {}) {
|
|
8576
|
+
const outcome = await runBtwCommand(client, input, options);
|
|
8577
|
+
if (outcome === "bare") {
|
|
8578
|
+
showToast(client, { title: "btw", message: SIDE_QUESTION_USAGE, variant: "warning", duration: 6e3 });
|
|
8579
|
+
throw new BtwHandledError("/btw needs a question.");
|
|
8580
|
+
}
|
|
8581
|
+
if (outcome === "inline") throw new BtwHandledError(BTW_INLINE_HANDLED_MESSAGE);
|
|
8467
8582
|
if (outcome === "timeout") {
|
|
8468
8583
|
showToast(client, { title: "btw", message: BTW_TURN_TOO_LONG_MESSAGE, variant: "warning", duration: 8e3 });
|
|
8469
8584
|
throw new BtwHandledError(BTW_TURN_TOO_LONG_MESSAGE);
|
|
8470
8585
|
}
|
|
8471
8586
|
}
|
|
8587
|
+
async function askBtwThenRelease(client, input, release2, options = {}) {
|
|
8588
|
+
let outcome;
|
|
8589
|
+
try {
|
|
8590
|
+
outcome = await runBtwCommand(client, input, options);
|
|
8591
|
+
} catch (error) {
|
|
8592
|
+
log.warn("btw: the early aside failed; sending the /btw message as it is", {
|
|
8593
|
+
sessionID: input.sessionID,
|
|
8594
|
+
error: errorText(error)
|
|
8595
|
+
});
|
|
8596
|
+
outcome = "failed";
|
|
8597
|
+
}
|
|
8598
|
+
if (outcome === "inline") return outcome;
|
|
8599
|
+
try {
|
|
8600
|
+
await release2();
|
|
8601
|
+
} catch (error) {
|
|
8602
|
+
log.warn("btw: could not send the /btw message", { sessionID: input.sessionID, error: errorText(error) });
|
|
8603
|
+
}
|
|
8604
|
+
return outcome;
|
|
8605
|
+
}
|
|
8472
8606
|
|
|
8473
8607
|
// src/budget-ledger.ts
|
|
8474
8608
|
var BUDGET_MARKER = "\u258C **budget:**";
|
|
@@ -8638,7 +8772,9 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
8638
8772
|
"automatic account switch skipped an account whose runtime could not be prepared",
|
|
8639
8773
|
"background dispatch refused by maxBackgroundSubagents",
|
|
8640
8774
|
"btw: a turn is running but no claude process appeared for it",
|
|
8775
|
+
"btw: could not send the /btw message",
|
|
8641
8776
|
"btw: early aside failed; the message will ask again",
|
|
8777
|
+
"btw: the early aside failed; sending the /btw message as it is",
|
|
8642
8778
|
"budget option refused: not a positive number of dollars",
|
|
8643
8779
|
"budgetWarnAt refused: expected percentages between 0 and 100",
|
|
8644
8780
|
"cannot interrupt this transport; waiting for the turn to end",
|
|
@@ -8727,7 +8863,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
8727
8863
|
"no configured account could take this turn; it ends here",
|
|
8728
8864
|
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
8729
8865
|
"no stdout after envelope write; respawning claude process to resume conversation",
|
|
8730
|
-
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction",
|
|
8866
|
+
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction, and stopped turns keep their proxy calls until the next message",
|
|
8731
8867
|
"opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
|
|
8732
8868
|
"pending proxy call arrived after stream close; rejecting",
|
|
8733
8869
|
'permissionMode "plan" is enforced: claude cannot edit files or run commands, and --dangerously-skip-permissions is deliberately not passed so it stays that way. Headless Claude Code is not offered an ExitPlanMode tool, so nothing releases plan mode mid-session; approving a plan in chat does not unlock writes. Leaving plan mode means changing the config and restarting opencode.',
|
|
@@ -17064,12 +17200,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17064
17200
|
this.config.provider
|
|
17065
17201
|
);
|
|
17066
17202
|
}
|
|
17067
|
-
controlRequestBehaviorForTool(toolName) {
|
|
17068
|
-
return controlRequestBehaviorForTool(this.config, toolName);
|
|
17069
|
-
}
|
|
17070
|
-
writeControlResponse(proc, requestId, response) {
|
|
17071
|
-
writeControlResponse(proc, requestId, response);
|
|
17072
|
-
}
|
|
17073
17203
|
/**
|
|
17074
17204
|
* Handle Claude stream-json control requests (`can_use_tool`, etc.) and
|
|
17075
17205
|
* respond via stdin with a matching `control_response`.
|
|
@@ -17118,9 +17248,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17118
17248
|
thinkingDisplay: process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0 ? "summarized" : void 0
|
|
17119
17249
|
};
|
|
17120
17250
|
}
|
|
17121
|
-
latestUserText(prompt) {
|
|
17122
|
-
return latestUserText(prompt);
|
|
17123
|
-
}
|
|
17124
17251
|
synthesizeTitle(prompt) {
|
|
17125
17252
|
return synthesizeTitle(prompt);
|
|
17126
17253
|
}
|
|
@@ -19295,38 +19422,40 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19295
19422
|
dropped: unattended.dropped
|
|
19296
19423
|
});
|
|
19297
19424
|
let partialText = false;
|
|
19298
|
-
|
|
19425
|
+
const narration = [];
|
|
19426
|
+
for (const line of unattended.lines) {
|
|
19427
|
+
try {
|
|
19428
|
+
const outer = JSON.parse(line);
|
|
19429
|
+
const msg = outer.type === "stream_event" && outer.event ? outer.event : outer;
|
|
19430
|
+
let text = "";
|
|
19431
|
+
if (msg.type === "content_block_delta" && msg.delta?.type === "text_delta") {
|
|
19432
|
+
text = msg.delta.text ?? "";
|
|
19433
|
+
partialText = true;
|
|
19434
|
+
} else if (msg.type === "assistant") {
|
|
19435
|
+
if (!partialText) text = (msg.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
19436
|
+
partialText = false;
|
|
19437
|
+
} else if (msg.type === "result") {
|
|
19438
|
+
state.unattendedTurnEnded = true;
|
|
19439
|
+
for (const entry of state.activeProcess.pendingProxyCompletions?.values() ?? []) {
|
|
19440
|
+
if (isPendingProxyCallChannelClosed(entry.call)) entry.recoveryRequired = true;
|
|
19441
|
+
}
|
|
19442
|
+
if (outer.session_id) setClaudeSessionId(sk, outer.session_id);
|
|
19443
|
+
if (msg.is_error && msg.result) text = msg.result;
|
|
19444
|
+
}
|
|
19445
|
+
if (text) narration.push(text);
|
|
19446
|
+
} catch {
|
|
19447
|
+
}
|
|
19448
|
+
}
|
|
19449
|
+
if (narration.length > 0 || unattended.dropped > 0) {
|
|
19299
19450
|
const replayId = state.startTextBlock();
|
|
19300
19451
|
controller.enqueue({
|
|
19301
19452
|
type: "text-delta",
|
|
19302
19453
|
id: replayId,
|
|
19303
19454
|
delta: formatUnattendedReplayNote(unattended.dropped)
|
|
19304
19455
|
});
|
|
19305
|
-
for (const
|
|
19306
|
-
|
|
19307
|
-
const outer = JSON.parse(line);
|
|
19308
|
-
const msg = outer.type === "stream_event" && outer.event ? outer.event : outer;
|
|
19309
|
-
let text = "";
|
|
19310
|
-
if (msg.type === "content_block_delta" && msg.delta?.type === "text_delta") {
|
|
19311
|
-
text = msg.delta.text ?? "";
|
|
19312
|
-
partialText = true;
|
|
19313
|
-
} else if (msg.type === "assistant") {
|
|
19314
|
-
if (!partialText) text = (msg.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
19315
|
-
partialText = false;
|
|
19316
|
-
} else if (msg.type === "result") {
|
|
19317
|
-
state.unattendedTurnEnded = true;
|
|
19318
|
-
for (const entry of state.activeProcess.pendingProxyCompletions?.values() ?? []) {
|
|
19319
|
-
if (isPendingProxyCallChannelClosed(entry.call)) entry.recoveryRequired = true;
|
|
19320
|
-
}
|
|
19321
|
-
if (outer.session_id) setClaudeSessionId(sk, outer.session_id);
|
|
19322
|
-
if (msg.is_error && msg.result) text = msg.result;
|
|
19323
|
-
}
|
|
19324
|
-
if (text) controller.enqueue({ type: "text-delta", id: state.currentTextId ?? state.startTextBlock(), delta: text });
|
|
19325
|
-
} catch {
|
|
19326
|
-
}
|
|
19327
|
-
}
|
|
19456
|
+
for (const text of narration) controller.enqueue({ type: "text-delta", id: replayId, delta: text });
|
|
19457
|
+
state.endTextBlock();
|
|
19328
19458
|
}
|
|
19329
|
-
state.endTextBlock();
|
|
19330
19459
|
clearFallbackTimer(state);
|
|
19331
19460
|
state.hasReceivedContent = false;
|
|
19332
19461
|
}
|
|
@@ -19754,11 +19883,42 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
19754
19883
|
}
|
|
19755
19884
|
}
|
|
19756
19885
|
|
|
19757
|
-
// src/
|
|
19758
|
-
var
|
|
19886
|
+
// src/plugin-notice.ts
|
|
19887
|
+
var NOTICE_RPC_ID = "opencode-claude-code-plugin";
|
|
19888
|
+
var NOTICE_EVENT = "notice";
|
|
19889
|
+
var NOTICE_EVENT_TYPE = `rpc.${NOTICE_RPC_ID}.${NOTICE_EVENT}`;
|
|
19890
|
+
var NOTICE_RPC_DEFINITION = {
|
|
19891
|
+
id: NOTICE_RPC_ID,
|
|
19892
|
+
methods: {},
|
|
19893
|
+
events: {
|
|
19894
|
+
[NOTICE_EVENT]: {
|
|
19895
|
+
schema: {
|
|
19896
|
+
type: "object",
|
|
19897
|
+
properties: {
|
|
19898
|
+
id: { type: "string" },
|
|
19899
|
+
level: { type: "string", enum: ["warn", "error"] },
|
|
19900
|
+
message: { type: "string" }
|
|
19901
|
+
},
|
|
19902
|
+
required: ["id", "level", "message"],
|
|
19903
|
+
additionalProperties: false
|
|
19904
|
+
}
|
|
19905
|
+
}
|
|
19906
|
+
}
|
|
19907
|
+
};
|
|
19759
19908
|
var TOAST_TITLE = "claude-code";
|
|
19760
19909
|
var TOAST_DURATION_MS = 1e4;
|
|
19761
19910
|
var MAX_TOAST_CHARS = 240;
|
|
19911
|
+
function toastMessage(message) {
|
|
19912
|
+
const flat = message.replace(/\s+/g, " ").trim();
|
|
19913
|
+
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
19914
|
+
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
19915
|
+
}
|
|
19916
|
+
function pluginNotice(id, level, message) {
|
|
19917
|
+
return { id, level, message: toastMessage(message) };
|
|
19918
|
+
}
|
|
19919
|
+
|
|
19920
|
+
// src/tui-log-sink.ts
|
|
19921
|
+
var LOG_SERVICE_NAME = "opencode-claude-code";
|
|
19762
19922
|
var APP_LOG_LEVEL = {
|
|
19763
19923
|
debug: "debug",
|
|
19764
19924
|
info: "info",
|
|
@@ -19766,11 +19926,6 @@ var APP_LOG_LEVEL = {
|
|
|
19766
19926
|
warn: "warn",
|
|
19767
19927
|
error: "error"
|
|
19768
19928
|
};
|
|
19769
|
-
function toastMessage(message) {
|
|
19770
|
-
const flat = message.replace(/\s+/g, " ").trim();
|
|
19771
|
-
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
19772
|
-
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
19773
|
-
}
|
|
19774
19929
|
function appLogBody(entry) {
|
|
19775
19930
|
return {
|
|
19776
19931
|
service: LOG_SERVICE_NAME,
|
|
@@ -19820,6 +19975,9 @@ function registerOpencodeLogSink(client) {
|
|
|
19820
19975
|
return true;
|
|
19821
19976
|
}
|
|
19822
19977
|
|
|
19978
|
+
// src/v2.ts
|
|
19979
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
19980
|
+
|
|
19823
19981
|
// src/v2-client.ts
|
|
19824
19982
|
function formatAgentTypeList(agents) {
|
|
19825
19983
|
const lines = agents.flatMap((agent) => {
|
|
@@ -20109,24 +20267,89 @@ function deletedSessionIdV2(event) {
|
|
|
20109
20267
|
const id = event.data?.sessionID;
|
|
20110
20268
|
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
20111
20269
|
}
|
|
20112
|
-
|
|
20270
|
+
function stoppedSessionIdV2(event) {
|
|
20271
|
+
if (!event || event.type !== "session.execution.interrupted") return void 0;
|
|
20272
|
+
if (event.data?.reason !== "user") return void 0;
|
|
20273
|
+
const id = event.data?.sessionID;
|
|
20274
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
20275
|
+
}
|
|
20276
|
+
async function followSessionEvents(subscribe, signal) {
|
|
20277
|
+
const closeFeed = openSessionRunEventFeed();
|
|
20113
20278
|
try {
|
|
20114
20279
|
for await (const event of subscribe({ signal })) {
|
|
20115
20280
|
if (signal.aborted) break;
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20281
|
+
noteSessionRunEvent(event);
|
|
20282
|
+
const deleted = deletedSessionIdV2(event);
|
|
20283
|
+
if (deleted) {
|
|
20284
|
+
const released = deleteActiveProcessesForSession(deleted);
|
|
20285
|
+
if (released.length > 0) {
|
|
20286
|
+
log.info("released claude state for deleted session", { sessionID: deleted, released });
|
|
20287
|
+
}
|
|
20288
|
+
continue;
|
|
20121
20289
|
}
|
|
20290
|
+
const stopped = stoppedSessionIdV2(event);
|
|
20291
|
+
if (stopped) releaseParkedProxyCallsForSession(stopped);
|
|
20122
20292
|
}
|
|
20123
20293
|
} catch (err) {
|
|
20124
20294
|
if (!signal.aborted) {
|
|
20125
|
-
log.warn(
|
|
20126
|
-
|
|
20127
|
-
|
|
20295
|
+
log.warn(
|
|
20296
|
+
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction, and stopped turns keep their proxy calls until the next message",
|
|
20297
|
+
{ error: err instanceof Error ? err.message : String(err) }
|
|
20298
|
+
);
|
|
20128
20299
|
}
|
|
20300
|
+
} finally {
|
|
20301
|
+
closeFeed();
|
|
20302
|
+
}
|
|
20303
|
+
}
|
|
20304
|
+
var noticeEmitters = /* @__PURE__ */ new Set();
|
|
20305
|
+
var NOTICE_PROCESS_TAG = randomUUID8().slice(0, 8);
|
|
20306
|
+
var noticeCounter = 0;
|
|
20307
|
+
function emitNotice(entry) {
|
|
20308
|
+
if (entry.level !== "warn" && entry.level !== "error") return;
|
|
20309
|
+
noticeCounter += 1;
|
|
20310
|
+
const notice = pluginNotice(`${NOTICE_PROCESS_TAG}-${noticeCounter}`, entry.level, entry.message);
|
|
20311
|
+
for (const emit2 of noticeEmitters) emit2(notice);
|
|
20312
|
+
}
|
|
20313
|
+
async function registerNoticeChannel(ctx) {
|
|
20314
|
+
const rpc = ctx.rpc;
|
|
20315
|
+
if (typeof rpc?.register !== "function") return void 0;
|
|
20316
|
+
let registration;
|
|
20317
|
+
try {
|
|
20318
|
+
registration = await rpc.register(NOTICE_RPC_DEFINITION, {});
|
|
20319
|
+
} catch (err) {
|
|
20320
|
+
log.debug("opencode 2 refused the notice channel; warnings stay in the plugin log", {
|
|
20321
|
+
error: err instanceof Error ? err.message : String(err)
|
|
20322
|
+
});
|
|
20323
|
+
return void 0;
|
|
20129
20324
|
}
|
|
20325
|
+
const emitter2 = (notice) => {
|
|
20326
|
+
try {
|
|
20327
|
+
void Promise.resolve(registration.events.emit(NOTICE_EVENT, { ...notice })).catch(() => void 0);
|
|
20328
|
+
} catch {
|
|
20329
|
+
}
|
|
20330
|
+
};
|
|
20331
|
+
noticeEmitters.add(emitter2);
|
|
20332
|
+
setToastNotifier(emitNotice);
|
|
20333
|
+
return {
|
|
20334
|
+
dispose: async () => {
|
|
20335
|
+
noticeEmitters.delete(emitter2);
|
|
20336
|
+
if (noticeEmitters.size === 0) setToastNotifier(null);
|
|
20337
|
+
await registration.dispose();
|
|
20338
|
+
}
|
|
20339
|
+
};
|
|
20340
|
+
}
|
|
20341
|
+
function v2BtwClient(shim) {
|
|
20342
|
+
const shimSession = shim.session;
|
|
20343
|
+
const messages = shimSession?.messages;
|
|
20344
|
+
const status = async () => ({ data: busySessionsFromEvents() ?? {} });
|
|
20345
|
+
return {
|
|
20346
|
+
session: {
|
|
20347
|
+
...typeof messages === "function" ? { messages: (options) => messages.call(shimSession, options) } : {},
|
|
20348
|
+
get status() {
|
|
20349
|
+
return busySessionsFromEvents() === void 0 ? void 0 : status;
|
|
20350
|
+
}
|
|
20351
|
+
}
|
|
20352
|
+
};
|
|
20130
20353
|
}
|
|
20131
20354
|
function createV2Setup(deps) {
|
|
20132
20355
|
return async (ctx) => {
|
|
@@ -20137,9 +20360,13 @@ function createV2Setup(deps) {
|
|
|
20137
20360
|
return () => void 0;
|
|
20138
20361
|
}
|
|
20139
20362
|
ensureProcessExitCleanup();
|
|
20140
|
-
|
|
20363
|
+
const shim = createV1ClientShim(ctx);
|
|
20364
|
+
setOpencodeClient(shim);
|
|
20141
20365
|
const directory = ctx.location?.directory;
|
|
20142
20366
|
setOpencodeProjectDirectory(isUsableDirectory(directory) ? directory : void 0);
|
|
20367
|
+
const registrations = [];
|
|
20368
|
+
const noticeChannel = await registerNoticeChannel(ctx);
|
|
20369
|
+
if (noticeChannel) registrations.push(noticeChannel);
|
|
20143
20370
|
if (deps.buildAgentRegistry) {
|
|
20144
20371
|
try {
|
|
20145
20372
|
const config = deps.loadConfig(directory ?? process.cwd());
|
|
@@ -20159,7 +20386,6 @@ function createV2Setup(deps) {
|
|
|
20159
20386
|
}
|
|
20160
20387
|
}
|
|
20161
20388
|
const planned = /* @__PURE__ */ new Map();
|
|
20162
|
-
const registrations = [];
|
|
20163
20389
|
registrations.push(
|
|
20164
20390
|
await ctx.provider.transform((editor) => {
|
|
20165
20391
|
const seed = editor.get(BASE_PROVIDER_ID);
|
|
@@ -20215,6 +20441,7 @@ function createV2Setup(deps) {
|
|
|
20215
20441
|
})
|
|
20216
20442
|
);
|
|
20217
20443
|
const prompt = ctx.session.prompt;
|
|
20444
|
+
const btwClient = v2BtwClient(shim);
|
|
20218
20445
|
if (ctx.command?.transform && typeof prompt === "function") {
|
|
20219
20446
|
const send = (input, command, delivery) => prompt.call(ctx.session, {
|
|
20220
20447
|
...input.prompt,
|
|
@@ -20232,7 +20459,15 @@ function createV2Setup(deps) {
|
|
|
20232
20459
|
editor.add({
|
|
20233
20460
|
name: "btw",
|
|
20234
20461
|
description: BTW_COMMAND_DESCRIPTION,
|
|
20235
|
-
execute: (input) =>
|
|
20462
|
+
execute: (input) => {
|
|
20463
|
+
void askBtwThenRelease(
|
|
20464
|
+
btwClient,
|
|
20465
|
+
{ command: "btw", sessionID: input.sessionID, arguments: input.prompt.text ?? "" },
|
|
20466
|
+
() => send(input, "btw", "queue"),
|
|
20467
|
+
deps.btwWaitOptions
|
|
20468
|
+
);
|
|
20469
|
+
return Promise.resolve();
|
|
20470
|
+
}
|
|
20236
20471
|
});
|
|
20237
20472
|
})
|
|
20238
20473
|
);
|
|
@@ -20253,7 +20488,7 @@ function createV2Setup(deps) {
|
|
|
20253
20488
|
}
|
|
20254
20489
|
const events = new AbortController();
|
|
20255
20490
|
if (typeof ctx.event?.subscribe === "function") {
|
|
20256
|
-
void
|
|
20491
|
+
void followSessionEvents(ctx.event.subscribe.bind(ctx.event), events.signal);
|
|
20257
20492
|
}
|
|
20258
20493
|
log.info("claude-code plugin set up for opencode 2", {
|
|
20259
20494
|
opencode: ctx.app?.version,
|