@khalilgharbaoui/opencode-claude-code-plugin 0.39.0 → 0.40.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.js +285 -161
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -4887,6 +4887,9 @@ function noteInteractiveProcessExit(sessionKey2, ap, code) {
|
|
|
4887
4887
|
if (activeProcesses.get(sessionKey2) !== ap) return;
|
|
4888
4888
|
cancelIdleProcessEviction(sessionKey2);
|
|
4889
4889
|
activeProcesses.delete(sessionKey2);
|
|
4890
|
+
if (getPendingProxyCalls(sessionKey2).length > 0) {
|
|
4891
|
+
rejectAllPendingProxyCallsForSession(sessionKey2, new Error(CHILD_EXITED_MESSAGE));
|
|
4892
|
+
}
|
|
4890
4893
|
if (code !== 0 && code !== null) {
|
|
4891
4894
|
claudeSessions.delete(sessionKey2);
|
|
4892
4895
|
forgetResumePoint(sessionKey2);
|
|
@@ -8772,6 +8775,8 @@ var ClaudeSession = class {
|
|
|
8772
8775
|
trustMoves = 0;
|
|
8773
8776
|
trustMovedAt = 0;
|
|
8774
8777
|
turn = null;
|
|
8778
|
+
/** The running turn's read state, shared with `flushTranscript`. */
|
|
8779
|
+
scan = null;
|
|
8775
8780
|
turnDenied = [];
|
|
8776
8781
|
abandonTurn = false;
|
|
8777
8782
|
o;
|
|
@@ -9084,67 +9089,103 @@ var ClaudeSession = class {
|
|
|
9084
9089
|
this.proc.terminal.write(prompt);
|
|
9085
9090
|
}
|
|
9086
9091
|
await this.submitTurn();
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
+
const scan = {
|
|
9093
|
+
onRecord,
|
|
9094
|
+
stopReason: null,
|
|
9095
|
+
stopSeenAt: 0,
|
|
9096
|
+
end: null,
|
|
9097
|
+
sawUserRecord: false,
|
|
9098
|
+
lastBeat: Date.now()
|
|
9099
|
+
};
|
|
9100
|
+
this.scan = scan;
|
|
9092
9101
|
const deadline = Date.now() + timeout;
|
|
9093
9102
|
while (Date.now() < deadline) {
|
|
9094
9103
|
await delay(this.o.pollMs);
|
|
9095
9104
|
if (this.aborted) throw new Error("aborted mid-turn");
|
|
9096
9105
|
const fatalNow = this.fatalError();
|
|
9097
9106
|
if (fatalNow) throw fatalNow;
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
try {
|
|
9106
|
-
rec = JSON.parse(s);
|
|
9107
|
-
} catch {
|
|
9108
|
-
}
|
|
9109
|
-
onRecord(s, rec);
|
|
9110
|
-
if (!rec) continue;
|
|
9111
|
-
if (rec.type === "assistant" && rec.message) {
|
|
9112
|
-
const reason = rec.message.stop_reason;
|
|
9113
|
-
if (typeof reason === "string") {
|
|
9114
|
-
stopReason = TERMINAL_STOP.has(reason) ? reason : null;
|
|
9115
|
-
}
|
|
9116
|
-
} else if (rec.type === "user") {
|
|
9117
|
-
if (sawUserRecord && isInterruptRecord(rec)) end = "interrupted";
|
|
9118
|
-
sawUserRecord = true;
|
|
9119
|
-
} else if ((sawUserRecord || stopReason) && isTurnDurationRecord(rec)) {
|
|
9120
|
-
end = stopReason ? "stop" : "ended";
|
|
9121
|
-
}
|
|
9122
|
-
this.cursor = i + 1;
|
|
9107
|
+
if (this.scanTranscript() || scan.end) {
|
|
9108
|
+
if (scan.end) {
|
|
9109
|
+
return {
|
|
9110
|
+
stopReason: scan.end === "stop" ? scan.stopReason : null,
|
|
9111
|
+
end: scan.end,
|
|
9112
|
+
denied: this.turnDenied
|
|
9113
|
+
};
|
|
9123
9114
|
}
|
|
9124
|
-
if (!end) this.cursor = lastComplete;
|
|
9125
|
-
lastBeat = Date.now();
|
|
9126
|
-
stopSeenAt = Date.now();
|
|
9127
|
-
if (end) return { stopReason: end === "stop" ? stopReason : null, end, denied: this.turnDenied };
|
|
9128
9115
|
continue;
|
|
9129
9116
|
}
|
|
9130
|
-
if (stopReason && Date.now() - stopSeenAt >= this.o.stopSettleMs) {
|
|
9131
|
-
return { stopReason, end: "stop", denied: this.turnDenied };
|
|
9117
|
+
if (scan.stopReason && Date.now() - scan.stopSeenAt >= this.o.stopSettleMs) {
|
|
9118
|
+
return { stopReason: scan.stopReason, end: "stop", denied: this.turnDenied };
|
|
9132
9119
|
}
|
|
9133
9120
|
if (this.exited) throw new Error(this.failureMessage("claude exited mid-turn", true));
|
|
9134
9121
|
if (this.abandonTurn) return { stopReason: null, end: "interrupted", denied: this.turnDenied };
|
|
9135
9122
|
const now = Date.now();
|
|
9136
|
-
if (onHeartbeat && now - lastBeat >= this.o.heartbeatMs) {
|
|
9137
|
-
lastBeat = now;
|
|
9123
|
+
if (onHeartbeat && now - scan.lastBeat >= this.o.heartbeatMs) {
|
|
9124
|
+
scan.lastBeat = now;
|
|
9138
9125
|
if (now - this.lastDataAt < this.o.heartbeatMs) onHeartbeat();
|
|
9139
9126
|
}
|
|
9140
9127
|
}
|
|
9141
9128
|
return { stopReason: null, end: null, denied: this.turnDenied };
|
|
9142
9129
|
} finally {
|
|
9143
9130
|
this.turn = null;
|
|
9131
|
+
this.scan = null;
|
|
9144
9132
|
this.abandonTurn = false;
|
|
9145
9133
|
settle();
|
|
9146
9134
|
}
|
|
9147
9135
|
}
|
|
9136
|
+
/**
|
|
9137
|
+
* Read every complete transcript record past the cursor into the running
|
|
9138
|
+
* turn. Returns whether anything new was read. The one place a record is
|
|
9139
|
+
* interpreted, shared by the poll loop and `flushTranscript`.
|
|
9140
|
+
*/
|
|
9141
|
+
scanTranscript() {
|
|
9142
|
+
const scan = this.scan;
|
|
9143
|
+
if (!scan || scan.end) return false;
|
|
9144
|
+
const lines = this.readRawLines();
|
|
9145
|
+
const lastComplete = lines.length - 1;
|
|
9146
|
+
if (lastComplete <= this.cursor) return false;
|
|
9147
|
+
for (let i = this.cursor; i < lastComplete && !scan.end; i++) {
|
|
9148
|
+
const s = lines[i];
|
|
9149
|
+
if (!s || !s.trim()) {
|
|
9150
|
+
this.cursor = i + 1;
|
|
9151
|
+
continue;
|
|
9152
|
+
}
|
|
9153
|
+
let rec = null;
|
|
9154
|
+
try {
|
|
9155
|
+
rec = JSON.parse(s);
|
|
9156
|
+
} catch {
|
|
9157
|
+
}
|
|
9158
|
+
scan.onRecord(s, rec);
|
|
9159
|
+
if (rec) {
|
|
9160
|
+
if (rec.type === "assistant" && rec.message) {
|
|
9161
|
+
const reason = rec.message.stop_reason;
|
|
9162
|
+
if (typeof reason === "string") {
|
|
9163
|
+
scan.stopReason = TERMINAL_STOP.has(reason) ? reason : null;
|
|
9164
|
+
}
|
|
9165
|
+
} else if (rec.type === "user") {
|
|
9166
|
+
if (scan.sawUserRecord && isInterruptRecord(rec)) scan.end = "interrupted";
|
|
9167
|
+
scan.sawUserRecord = true;
|
|
9168
|
+
} else if ((scan.sawUserRecord || scan.stopReason) && isTurnDurationRecord(rec)) {
|
|
9169
|
+
scan.end = scan.stopReason ? "stop" : "ended";
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
this.cursor = i + 1;
|
|
9173
|
+
}
|
|
9174
|
+
if (!scan.end) this.cursor = lastComplete;
|
|
9175
|
+
scan.lastBeat = Date.now();
|
|
9176
|
+
scan.stopSeenAt = Date.now();
|
|
9177
|
+
return true;
|
|
9178
|
+
}
|
|
9179
|
+
/**
|
|
9180
|
+
* Hand the running turn every record the TUI has already written, now
|
|
9181
|
+
* rather than at the next poll. The TUI writes a reply's records before it
|
|
9182
|
+
* runs the reply's tools, but a proxied tool's MCP call can reach the
|
|
9183
|
+
* plugin inside one poll interval, and the step it ends must not close
|
|
9184
|
+
* before the text that preceded the call was read. No-op between turns.
|
|
9185
|
+
*/
|
|
9186
|
+
flushTranscript() {
|
|
9187
|
+
this.scanTranscript();
|
|
9188
|
+
}
|
|
9148
9189
|
/**
|
|
9149
9190
|
* Stop the running turn the way the TUI's own Esc does, and keep the
|
|
9150
9191
|
* session alive for the next one. Resolves true once the turn ended, false
|
|
@@ -9316,6 +9357,9 @@ function interactiveExtraArgs(opts) {
|
|
|
9316
9357
|
"--strict-mcp-config"
|
|
9317
9358
|
);
|
|
9318
9359
|
}
|
|
9360
|
+
if (opts.disallowedTools && opts.disallowedTools.length > 0) {
|
|
9361
|
+
extraArgs.push("--disallowedTools", ...opts.disallowedTools);
|
|
9362
|
+
}
|
|
9319
9363
|
for (const dir of opts.pluginDirs ?? []) {
|
|
9320
9364
|
extraArgs.push("--plugin-dir", dir);
|
|
9321
9365
|
}
|
|
@@ -9415,12 +9459,30 @@ function interactiveResultFrame(opts) {
|
|
|
9415
9459
|
duration_ms: 0
|
|
9416
9460
|
});
|
|
9417
9461
|
}
|
|
9462
|
+
function carriesReplyText(line) {
|
|
9463
|
+
try {
|
|
9464
|
+
const rec = JSON.parse(line);
|
|
9465
|
+
if (rec?.type !== "assistant" || !Array.isArray(rec.message?.content)) return false;
|
|
9466
|
+
return rec.message.content.some(
|
|
9467
|
+
(block) => block?.type === "text" && typeof block.text === "string" && block.text.trim()
|
|
9468
|
+
);
|
|
9469
|
+
} catch {
|
|
9470
|
+
return false;
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9418
9473
|
function spawnInteractiveProcess(opts) {
|
|
9419
9474
|
const extraArgs = interactiveExtraArgs(opts);
|
|
9420
9475
|
const openCalls = new OpenToolCalls();
|
|
9421
9476
|
let denials = [];
|
|
9422
9477
|
const lineEmitter = new EventEmitter4();
|
|
9423
9478
|
const proc = new EventEmitter4();
|
|
9479
|
+
let ap;
|
|
9480
|
+
let proxyClosed = false;
|
|
9481
|
+
const closeProxyServer = () => {
|
|
9482
|
+
if (proxyClosed || !opts.proxyServer) return;
|
|
9483
|
+
proxyClosed = true;
|
|
9484
|
+
void opts.proxyServer.close();
|
|
9485
|
+
};
|
|
9424
9486
|
const session = new ClaudeSession({
|
|
9425
9487
|
...opts.tuning,
|
|
9426
9488
|
cwd: opts.cwd,
|
|
@@ -9450,6 +9512,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9450
9512
|
onExit: (code) => {
|
|
9451
9513
|
proc.exitCode = code;
|
|
9452
9514
|
proc.signalCode = code === null ? "SIGTERM" : null;
|
|
9515
|
+
closeProxyServer();
|
|
9453
9516
|
log.info("interactive claude exited", { sessionId: session.sessionId, code });
|
|
9454
9517
|
proc.emit("exit", proc.exitCode, proc.signalCode);
|
|
9455
9518
|
proc.emit("close", proc.exitCode, proc.signalCode);
|
|
@@ -9477,7 +9540,12 @@ function spawnInteractiveProcess(opts) {
|
|
|
9477
9540
|
const runTurn = async (userMsg, turnNumber) => {
|
|
9478
9541
|
const current = () => turnNumber === writes;
|
|
9479
9542
|
const emit2 = (line) => {
|
|
9480
|
-
if (current())
|
|
9543
|
+
if (!current()) return;
|
|
9544
|
+
if (lineEmitter.listenerCount("line") === 0) {
|
|
9545
|
+
if (carriesReplyText(line)) bufferUnattendedLine(ap, line);
|
|
9546
|
+
return;
|
|
9547
|
+
}
|
|
9548
|
+
lineEmitter.emit("line", line);
|
|
9481
9549
|
};
|
|
9482
9550
|
try {
|
|
9483
9551
|
await ensureStarted();
|
|
@@ -9497,7 +9565,13 @@ function spawnInteractiveProcess(opts) {
|
|
|
9497
9565
|
emit2(raw);
|
|
9498
9566
|
},
|
|
9499
9567
|
void 0,
|
|
9500
|
-
|
|
9568
|
+
// Only to a listening turn: it feeds that turn's watchdog, and the
|
|
9569
|
+
// TUI redraws its spinner the whole time it waits on a proxied call.
|
|
9570
|
+
() => {
|
|
9571
|
+
if (lineEmitter.listenerCount("line") > 0) {
|
|
9572
|
+
emit2(heartbeatFrame(session.sessionId, openCalls.newest()));
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9501
9575
|
);
|
|
9502
9576
|
log.info("interactive turn ended", {
|
|
9503
9577
|
end,
|
|
@@ -9570,6 +9644,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9570
9644
|
});
|
|
9571
9645
|
}
|
|
9572
9646
|
}
|
|
9647
|
+
closeProxyServer();
|
|
9573
9648
|
const started = startPromise !== null;
|
|
9574
9649
|
try {
|
|
9575
9650
|
session.dispose();
|
|
@@ -9583,11 +9658,13 @@ function spawnInteractiveProcess(opts) {
|
|
|
9583
9658
|
return true;
|
|
9584
9659
|
}
|
|
9585
9660
|
});
|
|
9586
|
-
|
|
9661
|
+
ap = {
|
|
9587
9662
|
proc,
|
|
9588
9663
|
lineEmitter,
|
|
9589
|
-
proxyServer: null,
|
|
9664
|
+
proxyServer: opts.proxyServer ?? null,
|
|
9590
9665
|
mcpHash: void 0,
|
|
9666
|
+
unattendedLines: [],
|
|
9667
|
+
unattendedDropped: 0,
|
|
9591
9668
|
systemPromptFile: opts.systemPromptFile,
|
|
9592
9669
|
cliPath: opts.cliPath,
|
|
9593
9670
|
interactiveControl: {
|
|
@@ -9597,9 +9674,11 @@ function spawnInteractiveProcess(opts) {
|
|
|
9597
9674
|
interrupt: (timeoutMs) => {
|
|
9598
9675
|
cancelledThrough = writes;
|
|
9599
9676
|
return session.interrupt(timeoutMs);
|
|
9600
|
-
}
|
|
9677
|
+
},
|
|
9678
|
+
flushTranscript: () => session.flushTranscript()
|
|
9601
9679
|
}
|
|
9602
9680
|
};
|
|
9681
|
+
return ap;
|
|
9603
9682
|
}
|
|
9604
9683
|
|
|
9605
9684
|
// src/prompts.ts
|
|
@@ -11162,6 +11241,8 @@ function drainNow(state) {
|
|
|
11162
11241
|
}
|
|
11163
11242
|
if (state.drainBuffer.length === 0) return;
|
|
11164
11243
|
if (state.controllerClosed) return;
|
|
11244
|
+
state.activeProcess?.interactiveControl?.flushTranscript();
|
|
11245
|
+
if (state.controllerClosed) return;
|
|
11165
11246
|
const batch = state.drainBuffer.splice(0, state.drainBuffer.length);
|
|
11166
11247
|
log.info("draining pending proxy calls into stream finish", {
|
|
11167
11248
|
sessionKey: state.sessionKey,
|
|
@@ -13015,13 +13096,145 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13015
13096
|
state.proxyServer = null;
|
|
13016
13097
|
}
|
|
13017
13098
|
}
|
|
13099
|
+
const resolveProxyWiring = async () => {
|
|
13100
|
+
const discovery = self.effectiveMcpConfig(
|
|
13101
|
+
cwd,
|
|
13102
|
+
void 0,
|
|
13103
|
+
runtimeStatus
|
|
13104
|
+
);
|
|
13105
|
+
const mcpResolution = self.resolvedProxyMcpTools(
|
|
13106
|
+
discovery.allEnabledServerNames,
|
|
13107
|
+
options.tools,
|
|
13108
|
+
new Set((resolvedProxy ?? []).map((def) => def.name))
|
|
13109
|
+
);
|
|
13110
|
+
const proxyMcpTools = mcpResolution?.defs ?? null;
|
|
13111
|
+
const excludeServers = mcpResolution ? mcpResolution.coveredServers : void 0;
|
|
13112
|
+
const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
|
|
13113
|
+
const questionProxyEnabled = resolvedProxy?.some((t) => t.name === "question") ?? false;
|
|
13114
|
+
const opencodeToolsRequested = (self.config.proxyOpencodeTools?.length ?? 0) > 0;
|
|
13115
|
+
log.debug("opencode tool forwarding gate", {
|
|
13116
|
+
requested: self.config.proxyOpencodeTools ?? null,
|
|
13117
|
+
opencodeToolsRequested
|
|
13118
|
+
});
|
|
13119
|
+
const liveToolInfo = taskProxyEnabled || questionProxyEnabled || opencodeToolsRequested ? await loadLiveToolInfo() : {
|
|
13120
|
+
resolved: false,
|
|
13121
|
+
taskDescription: void 0,
|
|
13122
|
+
taskParameters: void 0,
|
|
13123
|
+
questionDescription: void 0,
|
|
13124
|
+
hasQuestion: false
|
|
13125
|
+
};
|
|
13126
|
+
let enrichedProxy = resolvedProxy;
|
|
13127
|
+
let backgroundSubagentsSupported = false;
|
|
13128
|
+
if (enrichedProxy && taskProxyEnabled) {
|
|
13129
|
+
enrichedProxy = overlayTaskProxyDescription(
|
|
13130
|
+
enrichedProxy,
|
|
13131
|
+
liveToolInfo.taskDescription
|
|
13132
|
+
);
|
|
13133
|
+
log.info("task proxy description overlay", {
|
|
13134
|
+
applied: Boolean(liveToolInfo.taskDescription),
|
|
13135
|
+
liveDescriptionLength: liveToolInfo.taskDescription?.length ?? 0,
|
|
13136
|
+
listsAgentTypes: Boolean(
|
|
13137
|
+
liveToolInfo.taskDescription?.includes(
|
|
13138
|
+
"Available agent types"
|
|
13139
|
+
)
|
|
13140
|
+
)
|
|
13141
|
+
});
|
|
13142
|
+
const hostDialect = self.config.hostApi === "v2" ? "v2" : "v1";
|
|
13143
|
+
backgroundSubagentsSupported = liveTaskSupportsBackground(
|
|
13144
|
+
liveToolInfo.taskParameters,
|
|
13145
|
+
hostDialect
|
|
13146
|
+
);
|
|
13147
|
+
enrichedProxy = applyBackgroundSubagentSupport(
|
|
13148
|
+
enrichedProxy,
|
|
13149
|
+
backgroundSubagentsSupported,
|
|
13150
|
+
hostDialect
|
|
13151
|
+
);
|
|
13152
|
+
recordBackgroundSubagentGate({
|
|
13153
|
+
supported: backgroundSubagentsSupported,
|
|
13154
|
+
hostApi: hostDialect,
|
|
13155
|
+
registryResolved: liveToolInfo.resolved
|
|
13156
|
+
});
|
|
13157
|
+
log.info("background subagent gate", {
|
|
13158
|
+
supported: backgroundSubagentsSupported,
|
|
13159
|
+
registryResolved: liveToolInfo.resolved,
|
|
13160
|
+
hostApi: hostDialect,
|
|
13161
|
+
note: backgroundSubagentsSupported ? "task accepts `background`; task_status and task_cancel are registered" : "`background` stripped from the task schema; set OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true in opencode's environment to enable it"
|
|
13162
|
+
});
|
|
13163
|
+
}
|
|
13164
|
+
if (enrichedProxy && questionProxyEnabled) {
|
|
13165
|
+
enrichedProxy = overlayQuestionProxyDescription(
|
|
13166
|
+
enrichedProxy,
|
|
13167
|
+
liveToolInfo.hasQuestion ? liveToolInfo.questionDescription : void 0
|
|
13168
|
+
);
|
|
13169
|
+
enrichedProxy = filterQuestionProxyByOpencodeSupport(
|
|
13170
|
+
enrichedProxy,
|
|
13171
|
+
liveToolInfo.hasQuestion
|
|
13172
|
+
);
|
|
13173
|
+
log.info("question proxy version gate", {
|
|
13174
|
+
opencodeHasQuestion: liveToolInfo.hasQuestion,
|
|
13175
|
+
kept: liveToolInfo.hasQuestion
|
|
13176
|
+
});
|
|
13177
|
+
}
|
|
13178
|
+
const opencodeToolDefs = resolveProxyOpencodeToolDefs({
|
|
13179
|
+
requested: self.config.proxyOpencodeTools,
|
|
13180
|
+
items: liveToolInfo.items,
|
|
13181
|
+
modelTools: self.config.hostApi === "v2" ? options.tools ?? [] : void 0,
|
|
13182
|
+
allowCodeExecution: self.config.permissionPreset !== "read-only",
|
|
13183
|
+
taken: new Set(
|
|
13184
|
+
[...enrichedProxy ?? [], ...proxyMcpTools ?? []].map(
|
|
13185
|
+
(t) => t.name
|
|
13186
|
+
)
|
|
13187
|
+
)
|
|
13188
|
+
});
|
|
13189
|
+
if (opencodeToolDefs.length > 0) {
|
|
13190
|
+
log.info("forwarding opencode tools through the proxy", {
|
|
13191
|
+
tools: opencodeToolDefs.map((t) => t.name)
|
|
13192
|
+
});
|
|
13193
|
+
}
|
|
13194
|
+
const combinedList = [
|
|
13195
|
+
...enrichedProxy ?? [],
|
|
13196
|
+
...proxyMcpTools ?? [],
|
|
13197
|
+
...opencodeToolDefs
|
|
13198
|
+
];
|
|
13199
|
+
const combinedProxyTools = combinedList.length > 0 ? combinedList : null;
|
|
13200
|
+
const pluginCompressEnabled = enrichedProxy?.some((t) => t.name === "compress") ?? false;
|
|
13201
|
+
if (!state.proxyServer && combinedProxyTools) {
|
|
13202
|
+
state.proxyServer = await self.ensureProxyServer(
|
|
13203
|
+
combinedProxyTools,
|
|
13204
|
+
sk,
|
|
13205
|
+
pluginCompressEnabled,
|
|
13206
|
+
affinity
|
|
13207
|
+
);
|
|
13208
|
+
}
|
|
13209
|
+
const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
|
|
13210
|
+
const allDisallowed = resolveDisallowedTools({
|
|
13211
|
+
proxyTools: enrichedProxy,
|
|
13212
|
+
extraDisallowedTools: self.config.extraDisallowedTools,
|
|
13213
|
+
disableWebSearch: self.config.webSearch === "disabled"
|
|
13214
|
+
});
|
|
13215
|
+
return {
|
|
13216
|
+
excludeServers,
|
|
13217
|
+
taskProxyEnabled,
|
|
13218
|
+
backgroundSubagentsSupported,
|
|
13219
|
+
questionProxyActive,
|
|
13220
|
+
opencodeToolDefs,
|
|
13221
|
+
pluginCompressEnabled,
|
|
13222
|
+
allDisallowed
|
|
13223
|
+
};
|
|
13224
|
+
};
|
|
13018
13225
|
if (useInteractive && !compactionMode) {
|
|
13019
|
-
const mcp = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
13020
13226
|
if (state.activeProcess) {
|
|
13021
13227
|
state.proc = state.activeProcess.proc;
|
|
13022
13228
|
state.lineEmitter = state.activeProcess.lineEmitter;
|
|
13023
13229
|
log.debug("reusing active interactive session", { sk });
|
|
13024
13230
|
} else {
|
|
13231
|
+
const wiring = await resolveProxyWiring();
|
|
13232
|
+
const mcp = self.effectiveMcpConfig(
|
|
13233
|
+
cwd,
|
|
13234
|
+
state.proxyServer?.configPath(),
|
|
13235
|
+
runtimeStatus,
|
|
13236
|
+
wiring.excludeServers
|
|
13237
|
+
);
|
|
13025
13238
|
const allow = [
|
|
13026
13239
|
...mcp.allEnabledServerNames.map((n) => `mcp__${n}__*`),
|
|
13027
13240
|
"mcp__opencode_proxy__*",
|
|
@@ -13035,12 +13248,27 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13035
13248
|
];
|
|
13036
13249
|
const systemPromptFile = self.config.interactiveSystemPrompt === false ? void 0 : buildAppendedSystemPrompt(
|
|
13037
13250
|
cwd,
|
|
13038
|
-
self.config.multiStepContinuation !== false
|
|
13251
|
+
self.config.multiStepContinuation !== false,
|
|
13039
13252
|
// Do not forward opencode's own system prompt into the
|
|
13040
13253
|
// interactive TUI. Live subscription-account testing
|
|
13041
13254
|
// showed that large forwarded payload can trigger Claude
|
|
13042
13255
|
// Code's third-party-app usage gate, while our static
|
|
13043
|
-
// CLI/AGENTS/continuation prompt remains safe.
|
|
13256
|
+
// CLI/AGENTS/continuation prompt remains safe. The
|
|
13257
|
+
// plugin's own short proxy hints are part of that static
|
|
13258
|
+
// prompt: without them the model narrates a subagent
|
|
13259
|
+
// dispatch instead of calling the proxy (h #g74).
|
|
13260
|
+
[
|
|
13261
|
+
...wiring.taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : [],
|
|
13262
|
+
...wiring.backgroundSubagentsSupported ? [BACKGROUND_SUBAGENT_HINT] : [],
|
|
13263
|
+
...wiring.questionProxyActive ? [QUESTION_PROXY_HINT] : []
|
|
13264
|
+
],
|
|
13265
|
+
{
|
|
13266
|
+
compressEnabled: wiring.pluginCompressEnabled,
|
|
13267
|
+
opencodeCompressEnabled: wiring.opencodeToolDefs.some(
|
|
13268
|
+
(t) => t.name === "compress"
|
|
13269
|
+
),
|
|
13270
|
+
compressionSummary: getCompressionSummary(sk)
|
|
13271
|
+
}
|
|
13044
13272
|
);
|
|
13045
13273
|
if (self.config.interactiveSystemPrompt === false) {
|
|
13046
13274
|
log.warn(
|
|
@@ -13069,6 +13297,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13069
13297
|
mcpConfigPaths: mcp.paths,
|
|
13070
13298
|
pluginDirs: skillPluginDirs,
|
|
13071
13299
|
permissionsAllow: allow,
|
|
13300
|
+
disallowedTools: wiring.allDisallowed,
|
|
13301
|
+
proxyServer: state.proxyServer,
|
|
13072
13302
|
systemPromptFile,
|
|
13073
13303
|
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
13074
13304
|
effort: reasoningEffort,
|
|
@@ -13115,121 +13345,15 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13115
13345
|
cliVersion
|
|
13116
13346
|
});
|
|
13117
13347
|
} else {
|
|
13118
|
-
const
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
);
|
|
13128
|
-
const proxyMcpTools = mcpResolution?.defs ?? null;
|
|
13129
|
-
const excludeServers = mcpResolution ? mcpResolution.coveredServers : void 0;
|
|
13130
|
-
const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
|
|
13131
|
-
const questionProxyEnabled = resolvedProxy?.some((t) => t.name === "question") ?? false;
|
|
13132
|
-
const opencodeToolsRequested = (self.config.proxyOpencodeTools?.length ?? 0) > 0;
|
|
13133
|
-
log.debug("opencode tool forwarding gate", {
|
|
13134
|
-
requested: self.config.proxyOpencodeTools ?? null,
|
|
13135
|
-
opencodeToolsRequested
|
|
13136
|
-
});
|
|
13137
|
-
const liveToolInfo = taskProxyEnabled || questionProxyEnabled || opencodeToolsRequested ? await loadLiveToolInfo() : {
|
|
13138
|
-
resolved: false,
|
|
13139
|
-
taskDescription: void 0,
|
|
13140
|
-
taskParameters: void 0,
|
|
13141
|
-
questionDescription: void 0,
|
|
13142
|
-
hasQuestion: false
|
|
13143
|
-
};
|
|
13144
|
-
let enrichedProxy = resolvedProxy;
|
|
13145
|
-
let backgroundSubagentsSupported = false;
|
|
13146
|
-
if (enrichedProxy && taskProxyEnabled) {
|
|
13147
|
-
enrichedProxy = overlayTaskProxyDescription(
|
|
13148
|
-
enrichedProxy,
|
|
13149
|
-
liveToolInfo.taskDescription
|
|
13150
|
-
);
|
|
13151
|
-
log.info("task proxy description overlay", {
|
|
13152
|
-
applied: Boolean(liveToolInfo.taskDescription),
|
|
13153
|
-
liveDescriptionLength: liveToolInfo.taskDescription?.length ?? 0,
|
|
13154
|
-
listsAgentTypes: Boolean(
|
|
13155
|
-
liveToolInfo.taskDescription?.includes(
|
|
13156
|
-
"Available agent types"
|
|
13157
|
-
)
|
|
13158
|
-
)
|
|
13159
|
-
});
|
|
13160
|
-
const hostDialect = self.config.hostApi === "v2" ? "v2" : "v1";
|
|
13161
|
-
backgroundSubagentsSupported = liveTaskSupportsBackground(
|
|
13162
|
-
liveToolInfo.taskParameters,
|
|
13163
|
-
hostDialect
|
|
13164
|
-
);
|
|
13165
|
-
enrichedProxy = applyBackgroundSubagentSupport(
|
|
13166
|
-
enrichedProxy,
|
|
13167
|
-
backgroundSubagentsSupported,
|
|
13168
|
-
hostDialect
|
|
13169
|
-
);
|
|
13170
|
-
recordBackgroundSubagentGate({
|
|
13171
|
-
supported: backgroundSubagentsSupported,
|
|
13172
|
-
hostApi: hostDialect,
|
|
13173
|
-
registryResolved: liveToolInfo.resolved
|
|
13174
|
-
});
|
|
13175
|
-
log.info("background subagent gate", {
|
|
13176
|
-
supported: backgroundSubagentsSupported,
|
|
13177
|
-
registryResolved: liveToolInfo.resolved,
|
|
13178
|
-
hostApi: hostDialect,
|
|
13179
|
-
note: backgroundSubagentsSupported ? "task accepts `background`; task_status and task_cancel are registered" : "`background` stripped from the task schema; set OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true in opencode's environment to enable it"
|
|
13180
|
-
});
|
|
13181
|
-
}
|
|
13182
|
-
if (enrichedProxy && questionProxyEnabled) {
|
|
13183
|
-
enrichedProxy = overlayQuestionProxyDescription(
|
|
13184
|
-
enrichedProxy,
|
|
13185
|
-
liveToolInfo.hasQuestion ? liveToolInfo.questionDescription : void 0
|
|
13186
|
-
);
|
|
13187
|
-
enrichedProxy = filterQuestionProxyByOpencodeSupport(
|
|
13188
|
-
enrichedProxy,
|
|
13189
|
-
liveToolInfo.hasQuestion
|
|
13190
|
-
);
|
|
13191
|
-
log.info("question proxy version gate", {
|
|
13192
|
-
opencodeHasQuestion: liveToolInfo.hasQuestion,
|
|
13193
|
-
kept: liveToolInfo.hasQuestion
|
|
13194
|
-
});
|
|
13195
|
-
}
|
|
13196
|
-
const opencodeToolDefs = resolveProxyOpencodeToolDefs({
|
|
13197
|
-
requested: self.config.proxyOpencodeTools,
|
|
13198
|
-
items: liveToolInfo.items,
|
|
13199
|
-
modelTools: self.config.hostApi === "v2" ? options.tools ?? [] : void 0,
|
|
13200
|
-
allowCodeExecution: self.config.permissionPreset !== "read-only",
|
|
13201
|
-
taken: new Set(
|
|
13202
|
-
[...enrichedProxy ?? [], ...proxyMcpTools ?? []].map(
|
|
13203
|
-
(t) => t.name
|
|
13204
|
-
)
|
|
13205
|
-
)
|
|
13206
|
-
});
|
|
13207
|
-
if (opencodeToolDefs.length > 0) {
|
|
13208
|
-
log.info("forwarding opencode tools through the proxy", {
|
|
13209
|
-
tools: opencodeToolDefs.map((t) => t.name)
|
|
13210
|
-
});
|
|
13211
|
-
}
|
|
13212
|
-
const combinedList = [
|
|
13213
|
-
...enrichedProxy ?? [],
|
|
13214
|
-
...proxyMcpTools ?? [],
|
|
13215
|
-
...opencodeToolDefs
|
|
13216
|
-
];
|
|
13217
|
-
const combinedProxyTools = combinedList.length > 0 ? combinedList : null;
|
|
13218
|
-
const pluginCompressEnabled = enrichedProxy?.some((t) => t.name === "compress") ?? false;
|
|
13219
|
-
if (!state.proxyServer && combinedProxyTools) {
|
|
13220
|
-
state.proxyServer = await self.ensureProxyServer(
|
|
13221
|
-
combinedProxyTools,
|
|
13222
|
-
sk,
|
|
13223
|
-
pluginCompressEnabled,
|
|
13224
|
-
affinity
|
|
13225
|
-
);
|
|
13226
|
-
}
|
|
13227
|
-
const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
|
|
13228
|
-
const allDisallowed = resolveDisallowedTools({
|
|
13229
|
-
proxyTools: enrichedProxy,
|
|
13230
|
-
extraDisallowedTools: self.config.extraDisallowedTools,
|
|
13231
|
-
disableWebSearch: self.config.webSearch === "disabled"
|
|
13232
|
-
});
|
|
13348
|
+
const {
|
|
13349
|
+
excludeServers,
|
|
13350
|
+
taskProxyEnabled,
|
|
13351
|
+
backgroundSubagentsSupported,
|
|
13352
|
+
questionProxyActive,
|
|
13353
|
+
opencodeToolDefs,
|
|
13354
|
+
pluginCompressEnabled,
|
|
13355
|
+
allDisallowed
|
|
13356
|
+
} = await resolveProxyWiring();
|
|
13233
13357
|
const mcp = self.effectiveMcpConfig(
|
|
13234
13358
|
cwd,
|
|
13235
13359
|
state.proxyServer?.configPath(),
|