@khalilgharbaoui/opencode-claude-code-plugin 0.42.2 → 0.42.3
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 +64 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +5 -1
package/dist/index.js
CHANGED
|
@@ -5862,6 +5862,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5862
5862
|
"stale scratch directory sweep failed",
|
|
5863
5863
|
"task_batch resolving with child results missing",
|
|
5864
5864
|
"the interactive transport has not been measured on this Claude Code version; it reads the TUI's screen and transcript, which a new release can change. If a turn hangs or a dialog is not handled, run /claude-code-doctor bundle and report it",
|
|
5865
|
+
"the interactive turn did not take a forwarded user message",
|
|
5865
5866
|
"this opencode process is running an older plugin build than the one on disk",
|
|
5866
5867
|
"timed out pending proxy call",
|
|
5867
5868
|
"turn abort listener threw",
|
|
@@ -8999,6 +9000,9 @@ var ClaudeSession = class {
|
|
|
8999
9000
|
* (the TUI repaints it while it takes the key, measured on 2.1.288) can
|
|
9000
9001
|
* never stand in for the next call's dialog. */
|
|
9001
9002
|
planDialog = null;
|
|
9003
|
+
/** `queue-operation` enqueue records read, which is how `queueInput` knows
|
|
9004
|
+
* the TUI took a message into the running turn. */
|
|
9005
|
+
enqueued = 0;
|
|
9002
9006
|
/** This turn's `ExitPlanMode` calls already answered. */
|
|
9003
9007
|
answeredPlans = /* @__PURE__ */ new Set();
|
|
9004
9008
|
abandonTurn = false;
|
|
@@ -9057,7 +9061,9 @@ var ClaudeSession = class {
|
|
|
9057
9061
|
// The decision arrives after opencode's own question round trip, by
|
|
9058
9062
|
// which time the dialog has long been drawn; this only bounds a TUI
|
|
9059
9063
|
// that never draws it.
|
|
9060
|
-
planDialogWaitMs: opts.planDialogWaitMs ?? 3e4
|
|
9064
|
+
planDialogWaitMs: opts.planDialogWaitMs ?? 3e4,
|
|
9065
|
+
// 100 times the longest silence measured while Claude works (h #g207).
|
|
9066
|
+
idleEndMs: opts.idleEndMs ?? 2e4
|
|
9061
9067
|
};
|
|
9062
9068
|
}
|
|
9063
9069
|
/** The CLI arguments this session spawns with. */
|
|
@@ -9335,7 +9341,8 @@ var ClaudeSession = class {
|
|
|
9335
9341
|
end: null,
|
|
9336
9342
|
sawUserRecord: false,
|
|
9337
9343
|
lastBeat: Date.now(),
|
|
9338
|
-
awaitingOwnPrompt: this.forkPending ? normalizedPrompt(prompt) : null
|
|
9344
|
+
awaitingOwnPrompt: this.forkPending ? normalizedPrompt(prompt) : null,
|
|
9345
|
+
sawReply: false
|
|
9339
9346
|
};
|
|
9340
9347
|
this.forkPending = false;
|
|
9341
9348
|
this.scan = scan;
|
|
@@ -9358,6 +9365,9 @@ var ClaudeSession = class {
|
|
|
9358
9365
|
if (scan.stopReason && Date.now() - scan.stopSeenAt >= this.o.stopSettleMs) {
|
|
9359
9366
|
return { stopReason: scan.stopReason, end: "stop", denied: this.turnDenied };
|
|
9360
9367
|
}
|
|
9368
|
+
if (scan.sawReply && !this.planApprovalToolUseId && Date.now() - this.lastDataAt >= this.o.idleEndMs && Date.now() - scan.stopSeenAt >= this.o.idleEndMs) {
|
|
9369
|
+
return { stopReason: null, end: "ended", denied: this.turnDenied };
|
|
9370
|
+
}
|
|
9361
9371
|
if (this.exited) throw new Error(this.failureMessage("claude exited mid-turn", true));
|
|
9362
9372
|
if (this.abandonTurn) return { stopReason: null, end: "interrupted", denied: this.turnDenied };
|
|
9363
9373
|
const now = Date.now();
|
|
@@ -9407,9 +9417,11 @@ var ClaudeSession = class {
|
|
|
9407
9417
|
scan.awaitingOwnPrompt = null;
|
|
9408
9418
|
}
|
|
9409
9419
|
if (rec) this.notePlanApproval(rec);
|
|
9420
|
+
if (rec?.type === "queue-operation" && rec.operation === "enqueue") this.enqueued++;
|
|
9410
9421
|
scan.onRecord(s, rec);
|
|
9411
9422
|
if (rec) {
|
|
9412
9423
|
if (rec.type === "assistant" && rec.message) {
|
|
9424
|
+
scan.sawReply = true;
|
|
9413
9425
|
const reason = rec.message.stop_reason;
|
|
9414
9426
|
if (typeof reason === "string") {
|
|
9415
9427
|
scan.stopReason = TERMINAL_STOP.has(reason) ? reason : null;
|
|
@@ -9498,6 +9510,29 @@ var ClaudeSession = class {
|
|
|
9498
9510
|
this.write("\r");
|
|
9499
9511
|
return true;
|
|
9500
9512
|
}
|
|
9513
|
+
/**
|
|
9514
|
+
* Type a message into the running turn's own input queue, not as a turn of
|
|
9515
|
+
* its own (h #g206). Measured on 2.1.288: text pasted and submitted while a
|
|
9516
|
+
* tool call runs is recorded as `queue-operation` enqueue and remove plus a
|
|
9517
|
+
* `queued_command` attachment, answered in the same turn, which still ends
|
|
9518
|
+
* once. Resolves true once the enqueue is in the transcript; false with no
|
|
9519
|
+
* turn running, or with a plan approval dialog up, which would take the
|
|
9520
|
+
* keys as its answer.
|
|
9521
|
+
*/
|
|
9522
|
+
async queueInput(text, timeoutMs = 3e3) {
|
|
9523
|
+
if (!this.turn || !this.proc || this.exited || this.pendingPlanApproval) return false;
|
|
9524
|
+
const before = this.enqueued;
|
|
9525
|
+
this.write(this.o.bracketedPaste ? "\x1B[200~" + text + "\x1B[201~" : text);
|
|
9526
|
+
await delay(this.o.submitMinMs);
|
|
9527
|
+
this.write("\r");
|
|
9528
|
+
const deadline = Date.now() + timeoutMs;
|
|
9529
|
+
while (this.enqueued === before) {
|
|
9530
|
+
if (!this.turn || this.exited || Date.now() >= deadline) return false;
|
|
9531
|
+
await delay(this.o.pollMs);
|
|
9532
|
+
this.scanTranscript();
|
|
9533
|
+
}
|
|
9534
|
+
return true;
|
|
9535
|
+
}
|
|
9501
9536
|
/** Wait until the PTY has been quiet for `permissionQuietMs`, at most 3 s. */
|
|
9502
9537
|
async waitForQuiet() {
|
|
9503
9538
|
const until = Date.now() + 3e3;
|
|
@@ -10123,6 +10158,13 @@ function spawnInteractiveProcess(opts) {
|
|
|
10123
10158
|
},
|
|
10124
10159
|
flushTranscript: () => session.flushTranscript(),
|
|
10125
10160
|
planApprovalPending: () => session.pendingPlanApproval !== null,
|
|
10161
|
+
// Text only: an image in a mid-call message is dropped with the
|
|
10162
|
+
// decoder's own warning rather than staged for a turn that is not ours.
|
|
10163
|
+
queueInput: async (content) => {
|
|
10164
|
+
const text = decodeUserEnvelope(JSON.stringify({ type: "user", message: { role: "user", content } })).trim();
|
|
10165
|
+
if (!text) return false;
|
|
10166
|
+
return session.queueInput(text);
|
|
10167
|
+
},
|
|
10126
10168
|
askAside
|
|
10127
10169
|
}
|
|
10128
10170
|
};
|
|
@@ -14332,9 +14374,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14332
14374
|
state.proc.on("error", procErrorHandler);
|
|
14333
14375
|
if (hasMatchedPendingResults) {
|
|
14334
14376
|
state.cliAskedForWork = true;
|
|
14335
|
-
const forwardTrailingUserMessages = () => {
|
|
14377
|
+
const forwardTrailingUserMessages = async () => {
|
|
14336
14378
|
if (compactionMode || !state.activeProcess) return 0;
|
|
14337
|
-
|
|
14379
|
+
const control = state.activeProcess.interactiveControl;
|
|
14380
|
+
if (control && (!control.queueInput || !control.turnRunning())) return 0;
|
|
14338
14381
|
const trailing = getTrailingUserMessages(effectivePrompt, {
|
|
14339
14382
|
stripContextReminders: self.stripContextRemindersEnabled()
|
|
14340
14383
|
});
|
|
@@ -14345,6 +14388,21 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14345
14388
|
assistantIndex: trailing.assistantIndex,
|
|
14346
14389
|
count: trailing.messages.length
|
|
14347
14390
|
};
|
|
14391
|
+
if (control) {
|
|
14392
|
+
let queued = 0;
|
|
14393
|
+
for (const content of fresh) if (await control.queueInput(content)) queued++;
|
|
14394
|
+
if (queued < fresh.length) {
|
|
14395
|
+
log.warn("the interactive turn did not take a forwarded user message", {
|
|
14396
|
+
sessionKey: sk,
|
|
14397
|
+
messages: fresh.length - queued
|
|
14398
|
+
});
|
|
14399
|
+
}
|
|
14400
|
+
log.info("forwarded user messages into the interactive turn's input queue", {
|
|
14401
|
+
sessionKey: sk,
|
|
14402
|
+
messages: queued
|
|
14403
|
+
});
|
|
14404
|
+
return queued;
|
|
14405
|
+
}
|
|
14348
14406
|
for (const content of fresh) {
|
|
14349
14407
|
state.proc.stdin?.write(
|
|
14350
14408
|
JSON.stringify({ type: "user", message: { role: "user", content } }) + "\n"
|
|
@@ -14356,7 +14414,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14356
14414
|
});
|
|
14357
14415
|
return fresh.length;
|
|
14358
14416
|
};
|
|
14359
|
-
if (!state.unattendedTurnEnded && forwardTrailingUserMessages() > 0) {
|
|
14417
|
+
if (!state.unattendedTurnEnded && await forwardTrailingUserMessages() > 0) {
|
|
14360
14418
|
await new Promise((resolve6) => setTimeout(resolve6, FORWARD_SETTLE_MS));
|
|
14361
14419
|
if (state.controllerClosed) return;
|
|
14362
14420
|
}
|
|
@@ -14391,7 +14449,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14391
14449
|
}
|
|
14392
14450
|
if (state.unattendedTurnEnded) {
|
|
14393
14451
|
deliverPendingCompletions(state);
|
|
14394
|
-
forwardTrailingUserMessages();
|
|
14452
|
+
await forwardTrailingUserMessages();
|
|
14395
14453
|
}
|
|
14396
14454
|
const unemitted = getPendingProxyCalls(sk).filter(
|
|
14397
14455
|
(call) => !call.emitted
|