oh-my-opencode-slim 0.8.6 → 0.9.1
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/README.md +73 -2
- package/dist/background/background-manager.d.ts +5 -2
- package/dist/background/index.d.ts +1 -1
- package/dist/background/multiplexer-session-manager.d.ts +68 -0
- package/dist/cli/index.js +20 -4
- package/dist/config/council-schema.d.ts +17 -1
- package/dist/config/schema.d.ts +69 -1
- package/dist/council/council-manager.d.ts +13 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1264 -499
- package/dist/mcp/index.d.ts +4 -2
- package/dist/mcp/websearch.d.ts +5 -2
- package/dist/multiplexer/factory.d.ts +22 -0
- package/dist/multiplexer/index.d.ts +8 -0
- package/dist/multiplexer/tmux/index.d.ts +20 -0
- package/dist/multiplexer/types.d.ts +54 -0
- package/dist/multiplexer/zellij/index.d.ts +36 -0
- package/dist/tools/background.d.ts +3 -3
- package/dist/tools/lsp/client.d.ts +38 -0
- package/dist/tools/lsp/config.d.ts +2 -1
- package/dist/tools/lsp/types.d.ts +10 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/session.d.ts +11 -2
- package/oh-my-opencode-slim.schema.json +66 -0
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -3529,7 +3529,7 @@ var RECOMMENDED_SKILLS = [
|
|
|
3529
3529
|
name: "simplify",
|
|
3530
3530
|
repo: "https://github.com/brianlovin/claude-config",
|
|
3531
3531
|
skillName: "simplify",
|
|
3532
|
-
allowedAgents: ["
|
|
3532
|
+
allowedAgents: ["oracle"],
|
|
3533
3533
|
description: "YAGNI code simplification expert"
|
|
3534
3534
|
},
|
|
3535
3535
|
{
|
|
@@ -17217,6 +17217,7 @@ var CouncilMasterConfigSchema = exports_external.object({
|
|
|
17217
17217
|
variant: exports_external.string().optional(),
|
|
17218
17218
|
prompt: exports_external.string().optional().describe("Optional role/guidance injected into the master synthesis prompt")
|
|
17219
17219
|
});
|
|
17220
|
+
var CouncillorExecutionModeSchema = exports_external.enum(["parallel", "serial"]).default("parallel").describe('Execution mode for councillors. Use "serial" for single-model systems to avoid conflicts. ' + 'Use "parallel" for multi-model systems for faster execution.');
|
|
17220
17221
|
var CouncilConfigSchema = exports_external.object({
|
|
17221
17222
|
master: CouncilMasterConfigSchema,
|
|
17222
17223
|
presets: exports_external.record(exports_external.string(), CouncilPresetSchema),
|
|
@@ -17231,7 +17232,9 @@ var CouncilConfigSchema = exports_external.object({
|
|
|
17231
17232
|
return unique;
|
|
17232
17233
|
}
|
|
17233
17234
|
return val;
|
|
17234
|
-
}).describe("Fallback models for the council master. Tried in order if the primary model fails. " + 'Example: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4"]')
|
|
17235
|
+
}).describe("Fallback models for the council master. Tried in order if the primary model fails. " + 'Example: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4"]'),
|
|
17236
|
+
councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
|
|
17237
|
+
councillor_retries: exports_external.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors and master that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries.")
|
|
17235
17238
|
});
|
|
17236
17239
|
// src/config/loader.ts
|
|
17237
17240
|
import * as fs from "fs";
|
|
@@ -17261,7 +17264,7 @@ function parseList(items, allAvailable) {
|
|
|
17261
17264
|
if (allow.includes("*")) {
|
|
17262
17265
|
return allAvailable.filter((item) => !deny.includes(item));
|
|
17263
17266
|
}
|
|
17264
|
-
return allow.filter((item) => !deny.includes(item));
|
|
17267
|
+
return allow.filter((item) => !deny.includes(item) && allAvailable.includes(item));
|
|
17265
17268
|
}
|
|
17266
17269
|
function getAgentMcpList(agentName, config2) {
|
|
17267
17270
|
const agentConfig = getAgentOverride(config2, agentName);
|
|
@@ -17333,19 +17336,29 @@ var AgentOverrideConfigSchema = exports_external.object({
|
|
|
17333
17336
|
skills: exports_external.array(exports_external.string()).optional(),
|
|
17334
17337
|
mcps: exports_external.array(exports_external.string()).optional()
|
|
17335
17338
|
});
|
|
17336
|
-
var
|
|
17339
|
+
var MultiplexerTypeSchema = exports_external.enum(["auto", "tmux", "zellij", "none"]);
|
|
17340
|
+
var MultiplexerLayoutSchema = exports_external.enum([
|
|
17337
17341
|
"main-horizontal",
|
|
17338
17342
|
"main-vertical",
|
|
17339
17343
|
"tiled",
|
|
17340
17344
|
"even-horizontal",
|
|
17341
17345
|
"even-vertical"
|
|
17342
17346
|
]);
|
|
17347
|
+
var TmuxLayoutSchema = MultiplexerLayoutSchema;
|
|
17348
|
+
var MultiplexerConfigSchema = exports_external.object({
|
|
17349
|
+
type: MultiplexerTypeSchema.default("none"),
|
|
17350
|
+
layout: MultiplexerLayoutSchema.default("main-vertical"),
|
|
17351
|
+
main_pane_size: exports_external.number().min(20).max(80).default(60)
|
|
17352
|
+
});
|
|
17343
17353
|
var TmuxConfigSchema = exports_external.object({
|
|
17344
17354
|
enabled: exports_external.boolean().default(false),
|
|
17345
17355
|
layout: TmuxLayoutSchema.default("main-vertical"),
|
|
17346
17356
|
main_pane_size: exports_external.number().min(20).max(80).default(60)
|
|
17347
17357
|
});
|
|
17348
17358
|
var PresetSchema = exports_external.record(exports_external.string(), AgentOverrideConfigSchema);
|
|
17359
|
+
var WebsearchConfigSchema = exports_external.object({
|
|
17360
|
+
provider: exports_external.enum(["exa", "tavily"]).default("exa")
|
|
17361
|
+
});
|
|
17349
17362
|
var McpNameSchema = exports_external.enum(["websearch", "context7", "grep_app"]);
|
|
17350
17363
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
17351
17364
|
maxConcurrentStarts: exports_external.number().min(1).max(50).default(10)
|
|
@@ -17354,7 +17367,8 @@ var FailoverConfigSchema = exports_external.object({
|
|
|
17354
17367
|
enabled: exports_external.boolean().default(true),
|
|
17355
17368
|
timeoutMs: exports_external.number().min(0).default(15000),
|
|
17356
17369
|
retryDelayMs: exports_external.number().min(0).default(500),
|
|
17357
|
-
chains: FallbackChainsSchema.default({})
|
|
17370
|
+
chains: FallbackChainsSchema.default({}),
|
|
17371
|
+
retry_on_empty: exports_external.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
|
|
17358
17372
|
});
|
|
17359
17373
|
var PluginConfigSchema = exports_external.object({
|
|
17360
17374
|
preset: exports_external.string().optional(),
|
|
@@ -17365,7 +17379,9 @@ var PluginConfigSchema = exports_external.object({
|
|
|
17365
17379
|
presets: exports_external.record(exports_external.string(), PresetSchema).optional(),
|
|
17366
17380
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
17367
17381
|
disabled_mcps: exports_external.array(exports_external.string()).optional(),
|
|
17382
|
+
multiplexer: MultiplexerConfigSchema.optional(),
|
|
17368
17383
|
tmux: TmuxConfigSchema.optional(),
|
|
17384
|
+
websearch: WebsearchConfigSchema.optional(),
|
|
17369
17385
|
background: BackgroundTaskConfigSchema.optional(),
|
|
17370
17386
|
fallback: FailoverConfigSchema.optional(),
|
|
17371
17387
|
council: CouncilConfigSchema.optional()
|
|
@@ -17440,9 +17456,12 @@ function loadPluginConfig(directory) {
|
|
|
17440
17456
|
...projectConfig,
|
|
17441
17457
|
agents: deepMerge(config2.agents, projectConfig.agents),
|
|
17442
17458
|
tmux: deepMerge(config2.tmux, projectConfig.tmux),
|
|
17443
|
-
|
|
17459
|
+
multiplexer: deepMerge(config2.multiplexer, projectConfig.multiplexer),
|
|
17460
|
+
fallback: deepMerge(config2.fallback, projectConfig.fallback),
|
|
17461
|
+
council: deepMerge(config2.council, projectConfig.council)
|
|
17444
17462
|
};
|
|
17445
17463
|
}
|
|
17464
|
+
config2 = migrateTmuxToMultiplexer(config2);
|
|
17446
17465
|
const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
|
|
17447
17466
|
if (envPreset) {
|
|
17448
17467
|
config2.preset = envPreset;
|
|
@@ -17484,6 +17503,22 @@ function loadAgentPrompt(agentName, preset) {
|
|
|
17484
17503
|
result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
|
|
17485
17504
|
return result;
|
|
17486
17505
|
}
|
|
17506
|
+
function migrateTmuxToMultiplexer(config2) {
|
|
17507
|
+
if (config2.multiplexer?.type && config2.multiplexer.type !== "none") {
|
|
17508
|
+
return config2;
|
|
17509
|
+
}
|
|
17510
|
+
if (config2.tmux?.enabled) {
|
|
17511
|
+
return {
|
|
17512
|
+
...config2,
|
|
17513
|
+
multiplexer: {
|
|
17514
|
+
type: "tmux",
|
|
17515
|
+
layout: config2.tmux.layout ?? "main-vertical",
|
|
17516
|
+
main_pane_size: config2.tmux.main_pane_size ?? 60
|
|
17517
|
+
}
|
|
17518
|
+
};
|
|
17519
|
+
}
|
|
17520
|
+
return config2;
|
|
17521
|
+
}
|
|
17487
17522
|
// src/config/utils.ts
|
|
17488
17523
|
function getAgentOverride(config2, name) {
|
|
17489
17524
|
const overrides = config2?.agents ?? {};
|
|
@@ -17542,9 +17577,10 @@ async function extractSessionResult(client, sessionId, options) {
|
|
|
17542
17577
|
}
|
|
17543
17578
|
}
|
|
17544
17579
|
}
|
|
17545
|
-
|
|
17580
|
+
const text = extractedContent.filter((t) => t.length > 0).join(`
|
|
17546
17581
|
|
|
17547
17582
|
`);
|
|
17583
|
+
return { text, empty: text.length === 0 };
|
|
17548
17584
|
}
|
|
17549
17585
|
|
|
17550
17586
|
// src/agents/orchestrator.ts
|
|
@@ -17581,16 +17617,16 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
|
|
|
17581
17617
|
@oracle
|
|
17582
17618
|
- Role: Strategic advisor for high-stakes decisions and persistent problems, code reviewer
|
|
17583
17619
|
- Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
|
|
17584
|
-
- Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review
|
|
17585
|
-
- **Delegate when:** Major architectural decisions with long-term impact \u2022 Problems persisting after 2+ fix attempts \u2022 High-risk multi-system refactors \u2022 Costly trade-offs (performance vs maintainability) \u2022 Complex debugging with unclear root cause \u2022 Security/scalability/data integrity decisions \u2022 Genuinely uncertain and cost of wrong choice is high \u2022 When a workflow calls for a **reviewer** subagent
|
|
17620
|
+
- Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review, simplification, maintainability review
|
|
17621
|
+
- **Delegate when:** Major architectural decisions with long-term impact \u2022 Problems persisting after 2+ fix attempts \u2022 High-risk multi-system refactors \u2022 Costly trade-offs (performance vs maintainability) \u2022 Complex debugging with unclear root cause \u2022 Security/scalability/data integrity decisions \u2022 Genuinely uncertain and cost of wrong choice is high \u2022 When a workflow calls for a **reviewer** subagent \u2022 Code needs simplification or YAGNI scrutiny
|
|
17586
17622
|
- **Don't delegate when:** Routine decisions you're confident about \u2022 First bug fix attempt \u2022 Straightforward trade-offs \u2022 Tactical "how" vs strategic "should" \u2022 Time-sensitive good-enough decisions \u2022 Quick research/testing can answer
|
|
17587
|
-
- **Rule of thumb:** Need senior architect review? \u2192 @oracle. Need code review? \u2192 @oracle. Just do it and PR? \u2192 yourself.
|
|
17623
|
+
- **Rule of thumb:** Need senior architect review? \u2192 @oracle. Need code review or simplification? \u2192 @oracle. Just do it and PR? \u2192 yourself.
|
|
17588
17624
|
|
|
17589
17625
|
@designer
|
|
17590
17626
|
- Role: UI/UX specialist for intentional, polished experiences
|
|
17591
17627
|
- Stats: 10x better UI/UX than orchestrator
|
|
17592
|
-
- Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent
|
|
17593
|
-
- **Delegate when:** User-facing interfaces needing polish \u2022 Responsive layouts \u2022 UX-critical components (forms, nav, dashboards) \u2022 Visual consistency systems \u2022 Animations/micro-interactions \u2022 Landing/marketing pages \u2022 Refining functional\u2192delightful
|
|
17628
|
+
- Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent, UI/UX review
|
|
17629
|
+
- **Delegate when:** User-facing interfaces needing polish \u2022 Responsive layouts \u2022 UX-critical components (forms, nav, dashboards) \u2022 Visual consistency systems \u2022 Animations/micro-interactions \u2022 Landing/marketing pages \u2022 Refining functional\u2192delightful \u2022 Reviewing existing UI/UX quality
|
|
17594
17630
|
- **Don't delegate when:** Backend/logic with no visual \u2022 Quick prototypes where design doesn't matter yet
|
|
17595
17631
|
- **Rule of thumb:** Users see it and polish matters? \u2192 @designer. Headless/functional? \u2192 yourself.
|
|
17596
17632
|
|
|
@@ -17598,9 +17634,9 @@ You are an AI coding orchestrator that optimizes for quality, speed, cost, and r
|
|
|
17598
17634
|
- Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
|
|
17599
17635
|
- Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
|
|
17600
17636
|
- Tools/Constraints: Execution-focused\u2014no research, no architectural decisions
|
|
17601
|
-
- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer
|
|
17637
|
+
- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer \u2022 Writing or updating tests \u2022 Tasks that touch test files, fixtures, mocks, or test helpers
|
|
17602
17638
|
- **Don't delegate when:** Needs discovery/research/decisions \u2022 Single small change (<20 lines, one file) \u2022 Unclear requirements needing iteration \u2022 Explaining to fixer > doing \u2022 Tight integration with your current work \u2022 Sequential dependencies
|
|
17603
|
-
- **Rule of thumb:** Explaining > doing? \u2192 yourself. Orchestrator paths selection is vastly improved by Fixer. eg it can reduce overall speed if Orchestrator splits what's usually a single task into multiple subtasks and parallelize it with fixer.
|
|
17639
|
+
- **Rule of thumb:** Explaining > doing? \u2192 yourself. Test file modifications and bounded implementation work usually go to @fixer. Orchestrator paths selection is vastly improved by Fixer. eg it can reduce overall speed if Orchestrator splits what's usually a single task into multiple subtasks and parallelize it with fixer.
|
|
17604
17640
|
|
|
17605
17641
|
@council
|
|
17606
17642
|
- Role: Multi-LLM consensus engine for high-confidence answers
|
|
@@ -17648,9 +17684,17 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
|
|
|
17648
17684
|
4. Integrate results
|
|
17649
17685
|
5. Adjust if needed
|
|
17650
17686
|
|
|
17687
|
+
### Validation routing
|
|
17688
|
+
- Validation is a workflow stage owned by the Orchestrator, not a separate specialist
|
|
17689
|
+
- Route UI/UX validation and review to @designer
|
|
17690
|
+
- Route code review, simplification, maintainability review, and YAGNI checks to @oracle
|
|
17691
|
+
- Route test writing, test updates, and changes touching test files to @fixer
|
|
17692
|
+
- If a request spans multiple lanes, delegate only the lanes that add clear value
|
|
17693
|
+
|
|
17651
17694
|
## 6. Verify
|
|
17652
17695
|
- Run \`lsp_diagnostics\` for errors
|
|
17653
|
-
-
|
|
17696
|
+
- Use validation routing when applicable instead of doing all review work yourself
|
|
17697
|
+
- If test files are involved, prefer @fixer for bounded test changes and @oracle only for test strategy or quality review
|
|
17654
17698
|
- Confirm specialists completed successfully
|
|
17655
17699
|
- Verify solution meets requirements
|
|
17656
17700
|
|
|
@@ -17901,9 +17945,9 @@ function createCouncillorAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17901
17945
|
}
|
|
17902
17946
|
|
|
17903
17947
|
// src/agents/designer.ts
|
|
17904
|
-
var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates intentional, polished experiences.
|
|
17948
|
+
var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates and reviews intentional, polished experiences.
|
|
17905
17949
|
|
|
17906
|
-
**Role**: Craft cohesive UI/UX that balances visual impact with usability.
|
|
17950
|
+
**Role**: Craft and review cohesive UI/UX that balances visual impact with usability.
|
|
17907
17951
|
|
|
17908
17952
|
## Design Principles
|
|
17909
17953
|
|
|
@@ -17949,6 +17993,11 @@ var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who crea
|
|
|
17949
17993
|
- Leverage component libraries where available
|
|
17950
17994
|
- Prioritize visual excellence\u2014code perfection comes second
|
|
17951
17995
|
|
|
17996
|
+
## Review Responsibilities
|
|
17997
|
+
- Review existing UI for usability, responsiveness, visual consistency, and polish when asked
|
|
17998
|
+
- Call out concrete UX issues and improvements, not just abstract design advice
|
|
17999
|
+
- When validating, focus on what users actually see and feel
|
|
18000
|
+
|
|
17952
18001
|
## Output Quality
|
|
17953
18002
|
You're capable of extraordinary creative work. Commit fully to distinctive visions and show what's possible when breaking conventions thoughtfully.`;
|
|
17954
18003
|
function createDesignerAgent(model, customPrompt, customAppendPrompt) {
|
|
@@ -17962,7 +18011,7 @@ ${customAppendPrompt}`;
|
|
|
17962
18011
|
}
|
|
17963
18012
|
return {
|
|
17964
18013
|
name: "designer",
|
|
17965
|
-
description: "UI/UX design and implementation. Use for styling, responsive design, component architecture and visual polish.",
|
|
18014
|
+
description: "UI/UX design, review, and implementation. Use for styling, responsive design, component architecture and visual polish.",
|
|
17966
18015
|
config: {
|
|
17967
18016
|
model,
|
|
17968
18017
|
temperature: 0.7,
|
|
@@ -18030,6 +18079,7 @@ var FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
|
|
|
18030
18079
|
- Use the research context (file paths, documentation, patterns) provided
|
|
18031
18080
|
- Read files before using edit/write tools and gather exact content before making changes
|
|
18032
18081
|
- Be fast and direct - no research, no delegation, No multi-step research/planning; minimal execution sequence ok
|
|
18082
|
+
- Write or update tests when requested, especially for bounded tasks involving test files, fixtures, mocks, or test helpers
|
|
18033
18083
|
- Run tests/lsp_diagnostics when relevant or requested (otherwise note as skipped with reason)
|
|
18034
18084
|
- Report completion with summary of changes
|
|
18035
18085
|
|
|
@@ -18039,6 +18089,7 @@ var FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
|
|
|
18039
18089
|
- No multi-step research/planning; minimal execution sequence ok
|
|
18040
18090
|
- If context is insufficient: use grep/glob/lsp_diagnostics directly \u2014 do not delegate
|
|
18041
18091
|
- Only ask for missing inputs you truly cannot retrieve yourself
|
|
18092
|
+
- Do not act as the primary reviewer; implement requested changes and surface obvious issues briefly
|
|
18042
18093
|
|
|
18043
18094
|
**Output Format**:
|
|
18044
18095
|
<summary>
|
|
@@ -18123,14 +18174,15 @@ ${customAppendPrompt}`;
|
|
|
18123
18174
|
}
|
|
18124
18175
|
|
|
18125
18176
|
// src/agents/oracle.ts
|
|
18126
|
-
var ORACLE_PROMPT = `You are Oracle - a strategic technical advisor.
|
|
18177
|
+
var ORACLE_PROMPT = `You are Oracle - a strategic technical advisor and code reviewer.
|
|
18127
18178
|
|
|
18128
|
-
**Role**: High-IQ debugging, architecture decisions, code review, and engineering guidance.
|
|
18179
|
+
**Role**: High-IQ debugging, architecture decisions, code review, simplification, and engineering guidance.
|
|
18129
18180
|
|
|
18130
18181
|
**Capabilities**:
|
|
18131
18182
|
- Analyze complex codebases and identify root causes
|
|
18132
18183
|
- Propose architectural solutions with tradeoffs
|
|
18133
|
-
- Review code for correctness, performance, and
|
|
18184
|
+
- Review code for correctness, performance, maintainability, and unnecessary complexity
|
|
18185
|
+
- Enforce YAGNI and suggest simpler designs when abstractions are not pulling their weight
|
|
18134
18186
|
- Guide debugging when standard approaches fail
|
|
18135
18187
|
|
|
18136
18188
|
**Behavior**:
|
|
@@ -18138,6 +18190,7 @@ var ORACLE_PROMPT = `You are Oracle - a strategic technical advisor.
|
|
|
18138
18190
|
- Provide actionable recommendations
|
|
18139
18191
|
- Explain reasoning briefly
|
|
18140
18192
|
- Acknowledge uncertainty when present
|
|
18193
|
+
- Prefer simpler designs unless complexity clearly earns its keep
|
|
18141
18194
|
|
|
18142
18195
|
**Constraints**:
|
|
18143
18196
|
- READ-ONLY: You advise, you don't implement
|
|
@@ -18154,7 +18207,7 @@ ${customAppendPrompt}`;
|
|
|
18154
18207
|
}
|
|
18155
18208
|
return {
|
|
18156
18209
|
name: "oracle",
|
|
18157
|
-
description: "Strategic technical advisor. Use for architecture decisions, complex debugging, code review, and engineering guidance.",
|
|
18210
|
+
description: "Strategic technical advisor. Use for architecture decisions, complex debugging, code review, simplification, and engineering guidance.",
|
|
18158
18211
|
config: {
|
|
18159
18212
|
model,
|
|
18160
18213
|
temperature: 0.1,
|
|
@@ -18217,6 +18270,9 @@ function createAgents(config2) {
|
|
|
18217
18270
|
}
|
|
18218
18271
|
return librarianModel ?? DEFAULT_MODELS.librarian;
|
|
18219
18272
|
}
|
|
18273
|
+
if ((name === "council" || name === "council-master") && config2?.council?.master?.model) {
|
|
18274
|
+
return config2.council.master.model;
|
|
18275
|
+
}
|
|
18220
18276
|
return DEFAULT_MODELS[name];
|
|
18221
18277
|
};
|
|
18222
18278
|
const protoSubAgents = Object.entries(SUBAGENT_FACTORIES).map(([name, factory]) => {
|
|
@@ -18267,7 +18323,10 @@ function getAgentConfigs(config2) {
|
|
|
18267
18323
|
import * as fs2 from "fs";
|
|
18268
18324
|
import * as os from "os";
|
|
18269
18325
|
import * as path2 from "path";
|
|
18270
|
-
var logFile = path2.join(os.tmpdir(), "oh-my-opencode-slim.log");
|
|
18326
|
+
var logFile = path2.join(process.env.HOME || os.tmpdir(), ".local/share/opencode/oh-my-opencode-slim.log");
|
|
18327
|
+
try {
|
|
18328
|
+
fs2.mkdirSync(path2.dirname(logFile), { recursive: true });
|
|
18329
|
+
} catch {}
|
|
18271
18330
|
function log(message, data) {
|
|
18272
18331
|
try {
|
|
18273
18332
|
const timestamp = new Date().toISOString();
|
|
@@ -18277,293 +18336,608 @@ function log(message, data) {
|
|
|
18277
18336
|
} catch {}
|
|
18278
18337
|
}
|
|
18279
18338
|
|
|
18280
|
-
// src/
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18339
|
+
// src/multiplexer/tmux/index.ts
|
|
18340
|
+
var {spawn } = globalThis.Bun;
|
|
18341
|
+
class TmuxMultiplexer {
|
|
18342
|
+
type = "tmux";
|
|
18343
|
+
binaryPath = null;
|
|
18344
|
+
hasChecked = false;
|
|
18345
|
+
storedLayout;
|
|
18346
|
+
storedMainPaneSize;
|
|
18347
|
+
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
18348
|
+
this.storedLayout = layout;
|
|
18349
|
+
this.storedMainPaneSize = mainPaneSize;
|
|
18350
|
+
}
|
|
18351
|
+
async isAvailable() {
|
|
18352
|
+
if (this.hasChecked) {
|
|
18353
|
+
return this.binaryPath !== null;
|
|
18354
|
+
}
|
|
18355
|
+
this.binaryPath = await this.findBinary();
|
|
18356
|
+
this.hasChecked = true;
|
|
18357
|
+
return this.binaryPath !== null;
|
|
18358
|
+
}
|
|
18359
|
+
isInsideSession() {
|
|
18360
|
+
return !!process.env.TMUX;
|
|
18361
|
+
}
|
|
18362
|
+
async spawnPane(sessionId, description, serverUrl) {
|
|
18363
|
+
const tmux = await this.getBinary();
|
|
18364
|
+
if (!tmux) {
|
|
18365
|
+
log("[tmux] spawnPane: tmux binary not found");
|
|
18366
|
+
return { success: false };
|
|
18367
|
+
}
|
|
18368
|
+
try {
|
|
18369
|
+
const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
|
|
18370
|
+
const args = [
|
|
18371
|
+
"split-window",
|
|
18372
|
+
"-h",
|
|
18373
|
+
"-d",
|
|
18374
|
+
"-P",
|
|
18375
|
+
"-F",
|
|
18376
|
+
"#{pane_id}",
|
|
18377
|
+
opencodeCmd
|
|
18378
|
+
];
|
|
18379
|
+
log("[tmux] spawnPane: executing", { tmux, args });
|
|
18380
|
+
const proc = spawn([tmux, ...args], {
|
|
18381
|
+
stdout: "pipe",
|
|
18382
|
+
stderr: "pipe"
|
|
18383
|
+
});
|
|
18384
|
+
const exitCode = await proc.exited;
|
|
18385
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18386
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18387
|
+
const paneId = stdout.trim();
|
|
18388
|
+
log("[tmux] spawnPane: result", {
|
|
18389
|
+
exitCode,
|
|
18390
|
+
paneId,
|
|
18391
|
+
stderr: stderr.trim()
|
|
18392
|
+
});
|
|
18393
|
+
if (exitCode === 0 && paneId) {
|
|
18394
|
+
const renameProc = spawn([tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" });
|
|
18395
|
+
await renameProc.exited;
|
|
18396
|
+
await this.applyLayout(this.storedLayout, this.storedMainPaneSize);
|
|
18397
|
+
log("[tmux] spawnPane: SUCCESS", { paneId });
|
|
18398
|
+
return { success: true, paneId };
|
|
18399
|
+
}
|
|
18400
|
+
return { success: false };
|
|
18401
|
+
} catch (err) {
|
|
18402
|
+
log("[tmux] spawnPane: exception", { error: String(err) });
|
|
18403
|
+
return { success: false };
|
|
18404
|
+
}
|
|
18290
18405
|
}
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18406
|
+
async closePane(paneId) {
|
|
18407
|
+
if (!paneId) {
|
|
18408
|
+
log("[tmux] closePane: no paneId provided");
|
|
18409
|
+
return false;
|
|
18410
|
+
}
|
|
18411
|
+
const tmux = await this.getBinary();
|
|
18412
|
+
if (!tmux) {
|
|
18413
|
+
log("[tmux] closePane: tmux binary not found");
|
|
18414
|
+
return false;
|
|
18415
|
+
}
|
|
18416
|
+
try {
|
|
18417
|
+
log("[tmux] closePane: sending Ctrl+C", { paneId });
|
|
18418
|
+
const ctrlCProc = spawn([tmux, "send-keys", "-t", paneId, "C-c"], {
|
|
18419
|
+
stdout: "pipe",
|
|
18420
|
+
stderr: "pipe"
|
|
18421
|
+
});
|
|
18422
|
+
await ctrlCProc.exited;
|
|
18423
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
18424
|
+
log("[tmux] closePane: killing pane", { paneId });
|
|
18425
|
+
const proc = spawn([tmux, "kill-pane", "-t", paneId], {
|
|
18426
|
+
stdout: "pipe",
|
|
18427
|
+
stderr: "pipe"
|
|
18428
|
+
});
|
|
18429
|
+
const exitCode = await proc.exited;
|
|
18430
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18431
|
+
log("[tmux] closePane: result", { exitCode, stderr: stderr.trim() });
|
|
18432
|
+
if (exitCode === 0) {
|
|
18433
|
+
await this.applyLayout(this.storedLayout, this.storedMainPaneSize);
|
|
18434
|
+
return true;
|
|
18435
|
+
}
|
|
18436
|
+
log("[tmux] closePane: failed (pane may already be closed)", { paneId });
|
|
18437
|
+
return false;
|
|
18438
|
+
} catch (err) {
|
|
18439
|
+
log("[tmux] closePane: exception", { error: String(err) });
|
|
18440
|
+
return false;
|
|
18441
|
+
}
|
|
18294
18442
|
}
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18298
|
-
|
|
18299
|
-
|
|
18300
|
-
|
|
18443
|
+
async applyLayout(layout, mainPaneSize) {
|
|
18444
|
+
const tmux = await this.getBinary();
|
|
18445
|
+
if (!tmux)
|
|
18446
|
+
return;
|
|
18447
|
+
this.storedLayout = layout;
|
|
18448
|
+
this.storedMainPaneSize = mainPaneSize;
|
|
18449
|
+
try {
|
|
18450
|
+
const layoutProc = spawn([tmux, "select-layout", layout], {
|
|
18451
|
+
stdout: "pipe",
|
|
18452
|
+
stderr: "pipe"
|
|
18453
|
+
});
|
|
18454
|
+
await layoutProc.exited;
|
|
18455
|
+
if (layout === "main-horizontal" || layout === "main-vertical") {
|
|
18456
|
+
const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
|
|
18457
|
+
const sizeProc = spawn([tmux, "set-window-option", sizeOption, `${mainPaneSize}%`], {
|
|
18458
|
+
stdout: "pipe",
|
|
18459
|
+
stderr: "pipe"
|
|
18460
|
+
});
|
|
18461
|
+
await sizeProc.exited;
|
|
18462
|
+
const reapplyProc = spawn([tmux, "select-layout", layout], {
|
|
18463
|
+
stdout: "pipe",
|
|
18464
|
+
stderr: "pipe"
|
|
18465
|
+
});
|
|
18466
|
+
await reapplyProc.exited;
|
|
18467
|
+
}
|
|
18468
|
+
log("[tmux] applyLayout: applied", { layout, mainPaneSize });
|
|
18469
|
+
} catch (err) {
|
|
18470
|
+
log("[tmux] applyLayout: exception", { error: String(err) });
|
|
18471
|
+
}
|
|
18301
18472
|
}
|
|
18302
|
-
|
|
18303
|
-
|
|
18473
|
+
async getBinary() {
|
|
18474
|
+
await this.isAvailable();
|
|
18475
|
+
return this.binaryPath;
|
|
18476
|
+
}
|
|
18477
|
+
async findBinary() {
|
|
18478
|
+
const isWindows = process.platform === "win32";
|
|
18479
|
+
const cmd = isWindows ? "where" : "which";
|
|
18480
|
+
try {
|
|
18481
|
+
const proc = spawn([cmd, "tmux"], {
|
|
18482
|
+
stdout: "pipe",
|
|
18483
|
+
stderr: "pipe"
|
|
18484
|
+
});
|
|
18485
|
+
const exitCode = await proc.exited;
|
|
18486
|
+
if (exitCode !== 0) {
|
|
18487
|
+
log("[tmux] findBinary: 'which tmux' failed", { exitCode });
|
|
18488
|
+
return null;
|
|
18489
|
+
}
|
|
18490
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18491
|
+
const path3 = stdout.trim().split(`
|
|
18492
|
+
`)[0];
|
|
18493
|
+
if (!path3) {
|
|
18494
|
+
log("[tmux] findBinary: no path in output");
|
|
18495
|
+
return null;
|
|
18496
|
+
}
|
|
18497
|
+
const verifyProc = spawn([path3, "-V"], {
|
|
18498
|
+
stdout: "pipe",
|
|
18499
|
+
stderr: "pipe"
|
|
18500
|
+
});
|
|
18501
|
+
const verifyExit = await verifyProc.exited;
|
|
18502
|
+
if (verifyExit !== 0) {
|
|
18503
|
+
log("[tmux] findBinary: tmux -V failed", { path: path3, verifyExit });
|
|
18504
|
+
return null;
|
|
18505
|
+
}
|
|
18506
|
+
log("[tmux] findBinary: found", { path: path3 });
|
|
18507
|
+
return path3;
|
|
18508
|
+
} catch (err) {
|
|
18509
|
+
log("[tmux] findBinary: exception", { error: String(err) });
|
|
18510
|
+
return null;
|
|
18511
|
+
}
|
|
18304
18512
|
}
|
|
18305
|
-
return { ...body, variant };
|
|
18306
|
-
}
|
|
18307
|
-
// src/utils/internal-initiator.ts
|
|
18308
|
-
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
18309
|
-
function isRecord(value) {
|
|
18310
|
-
return typeof value === "object" && value !== null;
|
|
18311
|
-
}
|
|
18312
|
-
function createInternalAgentTextPart(text) {
|
|
18313
|
-
return {
|
|
18314
|
-
type: "text",
|
|
18315
|
-
text: `${text}
|
|
18316
|
-
${SLIM_INTERNAL_INITIATOR_MARKER}`
|
|
18317
|
-
};
|
|
18318
18513
|
}
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18514
|
+
|
|
18515
|
+
// src/multiplexer/zellij/index.ts
|
|
18516
|
+
var {spawn: spawn2 } = globalThis.Bun;
|
|
18517
|
+
|
|
18518
|
+
class ZellijMultiplexer {
|
|
18519
|
+
type = "zellij";
|
|
18520
|
+
binaryPath = null;
|
|
18521
|
+
hasChecked = false;
|
|
18522
|
+
storedLayout;
|
|
18523
|
+
storedMainPaneSize;
|
|
18524
|
+
agentTabId = null;
|
|
18525
|
+
firstPaneId = null;
|
|
18526
|
+
firstPaneUsed = false;
|
|
18527
|
+
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
18528
|
+
this.storedLayout = layout;
|
|
18529
|
+
this.storedMainPaneSize = mainPaneSize;
|
|
18322
18530
|
}
|
|
18323
|
-
|
|
18324
|
-
|
|
18531
|
+
async isAvailable() {
|
|
18532
|
+
if (this.hasChecked) {
|
|
18533
|
+
return this.binaryPath !== null;
|
|
18534
|
+
}
|
|
18535
|
+
this.binaryPath = await this.findBinary();
|
|
18536
|
+
this.hasChecked = true;
|
|
18537
|
+
return this.binaryPath !== null;
|
|
18325
18538
|
}
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
// src/utils/tmux.ts
|
|
18329
|
-
var {spawn } = globalThis.Bun;
|
|
18330
|
-
var tmuxPath = null;
|
|
18331
|
-
var tmuxChecked = false;
|
|
18332
|
-
var storedConfig = null;
|
|
18333
|
-
var serverAvailable = null;
|
|
18334
|
-
var serverCheckUrl = null;
|
|
18335
|
-
async function isServerRunning(serverUrl) {
|
|
18336
|
-
if (serverCheckUrl === serverUrl && serverAvailable === true) {
|
|
18337
|
-
return true;
|
|
18539
|
+
isInsideSession() {
|
|
18540
|
+
return !!process.env.ZELLIJ;
|
|
18338
18541
|
}
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
const controller = new AbortController;
|
|
18344
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
18345
|
-
let response = null;
|
|
18542
|
+
async spawnPane(sessionId, description, serverUrl) {
|
|
18543
|
+
const zellij = await this.getBinary();
|
|
18544
|
+
if (!zellij)
|
|
18545
|
+
return { success: false };
|
|
18346
18546
|
try {
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18547
|
+
if (!this.agentTabId) {
|
|
18548
|
+
const result = await this.ensureAgentTab(zellij);
|
|
18549
|
+
if (!result)
|
|
18550
|
+
return { success: false };
|
|
18551
|
+
this.agentTabId = result.tabId;
|
|
18552
|
+
this.firstPaneId = result.firstPaneId;
|
|
18553
|
+
}
|
|
18554
|
+
if (!this.firstPaneUsed && this.firstPaneId) {
|
|
18555
|
+
const success2 = await this.runInPane(zellij, this.firstPaneId, sessionId, serverUrl, description);
|
|
18556
|
+
if (success2) {
|
|
18557
|
+
this.firstPaneUsed = true;
|
|
18558
|
+
return { success: true, paneId: this.firstPaneId };
|
|
18559
|
+
}
|
|
18560
|
+
}
|
|
18561
|
+
return await this.createPaneInAgentTab(zellij, sessionId, serverUrl, description);
|
|
18562
|
+
} catch {
|
|
18563
|
+
return { success: false };
|
|
18350
18564
|
}
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18565
|
+
}
|
|
18566
|
+
async createPaneInAgentTab(zellij, sessionId, serverUrl, description) {
|
|
18567
|
+
const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
|
|
18568
|
+
const paneName = description.slice(0, 30).replace(/"/g, "\\\"");
|
|
18569
|
+
const currentTabId = await this.getCurrentTabId(zellij);
|
|
18570
|
+
const inAgentTab = currentTabId === this.agentTabId;
|
|
18571
|
+
if (inAgentTab) {
|
|
18572
|
+
const args2 = [
|
|
18573
|
+
"action",
|
|
18574
|
+
"new-pane",
|
|
18575
|
+
"--name",
|
|
18576
|
+
paneName,
|
|
18577
|
+
"--close-on-exit",
|
|
18578
|
+
"--",
|
|
18579
|
+
"sh",
|
|
18580
|
+
"-c",
|
|
18581
|
+
opencodeCmd
|
|
18582
|
+
];
|
|
18583
|
+
const proc2 = spawn2([zellij, ...args2], {
|
|
18584
|
+
stdout: "pipe",
|
|
18585
|
+
stderr: "pipe"
|
|
18586
|
+
});
|
|
18587
|
+
const exitCode2 = await proc2.exited;
|
|
18588
|
+
const stdout2 = await new Response(proc2.stdout).text();
|
|
18589
|
+
const paneId2 = stdout2.trim();
|
|
18590
|
+
if (exitCode2 === 0 && paneId2?.startsWith("terminal_")) {
|
|
18591
|
+
return { success: true, paneId: paneId2 };
|
|
18592
|
+
}
|
|
18593
|
+
return { success: false };
|
|
18357
18594
|
}
|
|
18358
|
-
if (
|
|
18359
|
-
|
|
18595
|
+
if (!this.agentTabId) {
|
|
18596
|
+
return { success: false };
|
|
18360
18597
|
}
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
|
|
18364
|
-
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18598
|
+
const originalTab = await this.getCurrentTabId(zellij);
|
|
18599
|
+
await spawn2([zellij, "action", "go-to-tab-by-id", this.agentTabId], {
|
|
18600
|
+
stdout: "ignore",
|
|
18601
|
+
stderr: "ignore"
|
|
18602
|
+
}).exited;
|
|
18603
|
+
const args = [
|
|
18604
|
+
"action",
|
|
18605
|
+
"new-pane",
|
|
18606
|
+
"--name",
|
|
18607
|
+
paneName,
|
|
18608
|
+
"--close-on-exit",
|
|
18609
|
+
"--",
|
|
18610
|
+
"sh",
|
|
18611
|
+
"-c",
|
|
18612
|
+
opencodeCmd
|
|
18613
|
+
];
|
|
18614
|
+
const proc = spawn2([zellij, ...args], {
|
|
18370
18615
|
stdout: "pipe",
|
|
18371
18616
|
stderr: "pipe"
|
|
18372
18617
|
});
|
|
18373
18618
|
const exitCode = await proc.exited;
|
|
18374
|
-
|
|
18375
|
-
|
|
18619
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18620
|
+
const paneId = stdout.trim();
|
|
18621
|
+
if (originalTab) {
|
|
18622
|
+
await spawn2([zellij, "action", "go-to-tab-by-id", String(originalTab)], {
|
|
18623
|
+
stdout: "ignore",
|
|
18624
|
+
stderr: "ignore"
|
|
18625
|
+
}).exited;
|
|
18626
|
+
}
|
|
18627
|
+
if (exitCode === 0 && paneId?.startsWith("terminal_")) {
|
|
18628
|
+
return { success: true, paneId };
|
|
18629
|
+
}
|
|
18630
|
+
return { success: false };
|
|
18631
|
+
}
|
|
18632
|
+
async runInPane(zellij, paneId, sessionId, serverUrl, description) {
|
|
18633
|
+
try {
|
|
18634
|
+
const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
|
|
18635
|
+
await spawn2([zellij, "action", "focus-pane", "--pane-id", paneId], {
|
|
18636
|
+
stdout: "ignore",
|
|
18637
|
+
stderr: "ignore"
|
|
18638
|
+
}).exited;
|
|
18639
|
+
await spawn2([zellij, "action", "rename-pane", "--name", description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" }).exited;
|
|
18640
|
+
await spawn2([zellij, "action", "write-chars", opencodeCmd], {
|
|
18641
|
+
stdout: "ignore",
|
|
18642
|
+
stderr: "ignore"
|
|
18643
|
+
}).exited;
|
|
18644
|
+
await spawn2([zellij, "action", "write-chars", `
|
|
18645
|
+
`], {
|
|
18646
|
+
stdout: "ignore",
|
|
18647
|
+
stderr: "ignore"
|
|
18648
|
+
}).exited;
|
|
18649
|
+
return true;
|
|
18650
|
+
} catch {
|
|
18651
|
+
return false;
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
async ensureAgentTab(zellij) {
|
|
18655
|
+
try {
|
|
18656
|
+
const existingTab = await this.findTabByName(zellij, "opencode-agents");
|
|
18657
|
+
if (existingTab) {
|
|
18658
|
+
const firstPane = await this.getFirstPaneInTab(zellij, existingTab.tabId);
|
|
18659
|
+
return {
|
|
18660
|
+
tabId: existingTab.tabId,
|
|
18661
|
+
firstPaneId: firstPane || "terminal_0"
|
|
18662
|
+
};
|
|
18663
|
+
}
|
|
18664
|
+
const beforePanes = await this.listPanes(zellij);
|
|
18665
|
+
const createProc = spawn2([zellij, "action", "new-tab", "--name", "opencode-agents"], { stdout: "pipe", stderr: "pipe" });
|
|
18666
|
+
const createExit = await createProc.exited;
|
|
18667
|
+
if (createExit !== 0)
|
|
18668
|
+
return null;
|
|
18669
|
+
const newTab = await this.findTabByName(zellij, "opencode-agents");
|
|
18670
|
+
if (!newTab)
|
|
18671
|
+
return null;
|
|
18672
|
+
const afterPanes = await this.listPanes(zellij);
|
|
18673
|
+
const newPane = afterPanes.find((p) => !beforePanes.includes(p));
|
|
18674
|
+
return { tabId: newTab.tabId, firstPaneId: newPane || "terminal_0" };
|
|
18675
|
+
} catch {
|
|
18376
18676
|
return null;
|
|
18377
18677
|
}
|
|
18378
|
-
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18678
|
+
}
|
|
18679
|
+
async getFirstPaneInTab(zellij, tabId) {
|
|
18680
|
+
const originalTab = await this.getCurrentTabId(zellij);
|
|
18681
|
+
await spawn2([zellij, "action", "go-to-tab-by-id", tabId], {
|
|
18682
|
+
stdout: "ignore",
|
|
18683
|
+
stderr: "ignore"
|
|
18684
|
+
}).exited;
|
|
18685
|
+
const panes = await this.listPanes(zellij);
|
|
18686
|
+
if (originalTab) {
|
|
18687
|
+
await spawn2([zellij, "action", "go-to-tab-by-id", String(originalTab)], {
|
|
18688
|
+
stdout: "ignore",
|
|
18689
|
+
stderr: "ignore"
|
|
18690
|
+
}).exited;
|
|
18691
|
+
}
|
|
18692
|
+
return panes[0] || null;
|
|
18693
|
+
}
|
|
18694
|
+
async findTabByName(zellij, name) {
|
|
18695
|
+
try {
|
|
18696
|
+
const proc = spawn2([zellij, "action", "list-tabs", "--json"], {
|
|
18697
|
+
stdout: "pipe",
|
|
18698
|
+
stderr: "pipe"
|
|
18699
|
+
});
|
|
18700
|
+
const exitCode = await proc.exited;
|
|
18701
|
+
if (exitCode !== 0)
|
|
18702
|
+
return this.findTabByNameText(zellij, name);
|
|
18703
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18704
|
+
try {
|
|
18705
|
+
const tabs = JSON.parse(stdout);
|
|
18706
|
+
for (const tab of tabs) {
|
|
18707
|
+
if (tab.name === name) {
|
|
18708
|
+
return { tabId: String(tab.tab_id), name: tab.name };
|
|
18709
|
+
}
|
|
18710
|
+
}
|
|
18711
|
+
} catch {
|
|
18712
|
+
return this.findTabByNameText(zellij, name);
|
|
18713
|
+
}
|
|
18714
|
+
return null;
|
|
18715
|
+
} catch {
|
|
18383
18716
|
return null;
|
|
18384
18717
|
}
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18718
|
+
}
|
|
18719
|
+
async findTabByNameText(zellij, name) {
|
|
18720
|
+
try {
|
|
18721
|
+
const proc = spawn2([zellij, "action", "list-tabs"], {
|
|
18722
|
+
stdout: "pipe",
|
|
18723
|
+
stderr: "pipe"
|
|
18724
|
+
});
|
|
18725
|
+
const exitCode = await proc.exited;
|
|
18726
|
+
if (exitCode !== 0)
|
|
18727
|
+
return null;
|
|
18728
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18729
|
+
const lines = stdout.split(`
|
|
18730
|
+
`);
|
|
18731
|
+
for (const line of lines) {
|
|
18732
|
+
const parts = line.trim().split(/\s+/);
|
|
18733
|
+
if (parts.length >= 3 && parts[2] === name) {
|
|
18734
|
+
return { tabId: parts[0], name: parts[2] };
|
|
18735
|
+
}
|
|
18736
|
+
}
|
|
18737
|
+
return null;
|
|
18738
|
+
} catch {
|
|
18392
18739
|
return null;
|
|
18393
18740
|
}
|
|
18394
|
-
log("[tmux] findTmuxPath: found tmux", { path: path3 });
|
|
18395
|
-
return path3;
|
|
18396
|
-
} catch (err) {
|
|
18397
|
-
log("[tmux] findTmuxPath: exception", { error: String(err) });
|
|
18398
|
-
return null;
|
|
18399
18741
|
}
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18742
|
+
async getCurrentTabId(zellij) {
|
|
18743
|
+
try {
|
|
18744
|
+
const proc = spawn2([zellij, "action", "current-tab-info", "--json"], {
|
|
18745
|
+
stdout: "pipe",
|
|
18746
|
+
stderr: "pipe"
|
|
18747
|
+
});
|
|
18748
|
+
const exitCode = await proc.exited;
|
|
18749
|
+
if (exitCode !== 0)
|
|
18750
|
+
return null;
|
|
18751
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18752
|
+
try {
|
|
18753
|
+
const info = JSON.parse(stdout);
|
|
18754
|
+
return String(info.tab_id);
|
|
18755
|
+
} catch {
|
|
18756
|
+
return null;
|
|
18757
|
+
}
|
|
18758
|
+
} catch {
|
|
18759
|
+
return null;
|
|
18760
|
+
}
|
|
18404
18761
|
}
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
return tmuxPath;
|
|
18409
|
-
}
|
|
18410
|
-
function isInsideTmux() {
|
|
18411
|
-
return !!process.env.TMUX;
|
|
18412
|
-
}
|
|
18413
|
-
async function applyLayout(tmux, layout, mainPaneSize) {
|
|
18414
|
-
try {
|
|
18415
|
-
const layoutProc = spawn([tmux, "select-layout", layout], {
|
|
18416
|
-
stdout: "pipe",
|
|
18417
|
-
stderr: "pipe"
|
|
18418
|
-
});
|
|
18419
|
-
await layoutProc.exited;
|
|
18420
|
-
if (layout === "main-horizontal" || layout === "main-vertical") {
|
|
18421
|
-
const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
|
|
18422
|
-
const sizeProc = spawn([tmux, "set-window-option", sizeOption, `${mainPaneSize}%`], {
|
|
18762
|
+
async listPanes(zellij) {
|
|
18763
|
+
try {
|
|
18764
|
+
const proc = spawn2([zellij, "action", "list-panes"], {
|
|
18423
18765
|
stdout: "pipe",
|
|
18424
18766
|
stderr: "pipe"
|
|
18425
18767
|
});
|
|
18426
|
-
await
|
|
18427
|
-
|
|
18768
|
+
const exitCode = await proc.exited;
|
|
18769
|
+
if (exitCode !== 0)
|
|
18770
|
+
return [];
|
|
18771
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18772
|
+
return stdout.split(`
|
|
18773
|
+
`).slice(1).map((line) => line.trim().split(/\s+/)[0]).filter((id) => id?.startsWith("terminal_"));
|
|
18774
|
+
} catch {
|
|
18775
|
+
return [];
|
|
18776
|
+
}
|
|
18777
|
+
}
|
|
18778
|
+
async closePane(paneId) {
|
|
18779
|
+
if (!paneId || paneId === "unknown")
|
|
18780
|
+
return true;
|
|
18781
|
+
const zellij = await this.getBinary();
|
|
18782
|
+
if (!zellij)
|
|
18783
|
+
return false;
|
|
18784
|
+
try {
|
|
18785
|
+
await spawn2([zellij, "action", "write", "--pane-id", paneId, "\x03"], {
|
|
18786
|
+
stdout: "ignore",
|
|
18787
|
+
stderr: "ignore"
|
|
18788
|
+
}).exited;
|
|
18789
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
18790
|
+
const proc = spawn2([zellij, "action", "close-pane", "--pane-id", paneId], { stdout: "pipe", stderr: "pipe" });
|
|
18791
|
+
const exitCode = await proc.exited;
|
|
18792
|
+
return exitCode === 0 || exitCode === 1;
|
|
18793
|
+
} catch {
|
|
18794
|
+
return false;
|
|
18795
|
+
}
|
|
18796
|
+
}
|
|
18797
|
+
async applyLayout(_layout, _mainPaneSize) {}
|
|
18798
|
+
async getBinary() {
|
|
18799
|
+
await this.isAvailable();
|
|
18800
|
+
return this.binaryPath;
|
|
18801
|
+
}
|
|
18802
|
+
async findBinary() {
|
|
18803
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
18804
|
+
try {
|
|
18805
|
+
const proc = spawn2([cmd, "zellij"], {
|
|
18428
18806
|
stdout: "pipe",
|
|
18429
18807
|
stderr: "pipe"
|
|
18430
18808
|
});
|
|
18431
|
-
await
|
|
18809
|
+
if (await proc.exited !== 0)
|
|
18810
|
+
return null;
|
|
18811
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18812
|
+
return stdout.trim().split(`
|
|
18813
|
+
`)[0] || null;
|
|
18814
|
+
} catch {
|
|
18815
|
+
return null;
|
|
18432
18816
|
}
|
|
18433
|
-
log("[tmux] applyLayout: applied", { layout, mainPaneSize });
|
|
18434
|
-
} catch (err) {
|
|
18435
|
-
log("[tmux] applyLayout: exception", { error: String(err) });
|
|
18436
18817
|
}
|
|
18437
18818
|
}
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
if (!config2.enabled) {
|
|
18446
|
-
log("[tmux] spawnTmuxPane: config.enabled is false, skipping");
|
|
18447
|
-
return { success: false };
|
|
18819
|
+
|
|
18820
|
+
// src/multiplexer/factory.ts
|
|
18821
|
+
var multiplexerCache = new Map;
|
|
18822
|
+
function getMultiplexer(config2) {
|
|
18823
|
+
const { type } = config2;
|
|
18824
|
+
if (type === "none") {
|
|
18825
|
+
return null;
|
|
18448
18826
|
}
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
return
|
|
18827
|
+
const cached2 = multiplexerCache.get(type);
|
|
18828
|
+
if (cached2) {
|
|
18829
|
+
return cached2;
|
|
18452
18830
|
}
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
|
|
18460
|
-
|
|
18831
|
+
let multiplexer;
|
|
18832
|
+
let actualType;
|
|
18833
|
+
switch (type) {
|
|
18834
|
+
case "tmux":
|
|
18835
|
+
multiplexer = new TmuxMultiplexer(config2.layout, config2.main_pane_size);
|
|
18836
|
+
actualType = "tmux";
|
|
18837
|
+
break;
|
|
18838
|
+
case "zellij":
|
|
18839
|
+
multiplexer = new ZellijMultiplexer(config2.layout, config2.main_pane_size);
|
|
18840
|
+
actualType = "zellij";
|
|
18841
|
+
break;
|
|
18842
|
+
case "auto": {
|
|
18843
|
+
if (process.env.TMUX) {
|
|
18844
|
+
multiplexer = new TmuxMultiplexer(config2.layout, config2.main_pane_size);
|
|
18845
|
+
actualType = "tmux";
|
|
18846
|
+
} else if (process.env.ZELLIJ) {
|
|
18847
|
+
multiplexer = new ZellijMultiplexer(config2.layout, config2.main_pane_size);
|
|
18848
|
+
actualType = "zellij";
|
|
18849
|
+
} else {
|
|
18850
|
+
log("[multiplexer] auto: not inside any session, disabling");
|
|
18851
|
+
return null;
|
|
18852
|
+
}
|
|
18853
|
+
break;
|
|
18854
|
+
}
|
|
18855
|
+
default:
|
|
18856
|
+
log(`[multiplexer] Unknown type: ${type}`);
|
|
18857
|
+
return null;
|
|
18461
18858
|
}
|
|
18462
|
-
|
|
18463
|
-
|
|
18464
|
-
|
|
18465
|
-
|
|
18859
|
+
multiplexerCache.set(actualType, multiplexer);
|
|
18860
|
+
log(`[multiplexer] Created ${actualType} instance`);
|
|
18861
|
+
return multiplexer;
|
|
18862
|
+
}
|
|
18863
|
+
function startAvailabilityCheck(config2) {
|
|
18864
|
+
const multiplexer = getMultiplexer(config2);
|
|
18865
|
+
if (multiplexer) {
|
|
18866
|
+
multiplexer.isAvailable().catch(() => {});
|
|
18466
18867
|
}
|
|
18467
|
-
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
log("[tmux] spawnTmuxPane: executing", { tmux, args, opencodeCmd });
|
|
18480
|
-
const proc = spawn([tmux, ...args], {
|
|
18481
|
-
stdout: "pipe",
|
|
18482
|
-
stderr: "pipe"
|
|
18483
|
-
});
|
|
18484
|
-
const exitCode = await proc.exited;
|
|
18485
|
-
const stdout = await new Response(proc.stdout).text();
|
|
18486
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18487
|
-
const paneId = stdout.trim();
|
|
18488
|
-
log("[tmux] spawnTmuxPane: split result", {
|
|
18489
|
-
exitCode,
|
|
18490
|
-
paneId,
|
|
18491
|
-
stderr: stderr.trim()
|
|
18492
|
-
});
|
|
18493
|
-
if (exitCode === 0 && paneId) {
|
|
18494
|
-
const renameProc = spawn([tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" });
|
|
18495
|
-
await renameProc.exited;
|
|
18496
|
-
const layout = config2.layout ?? "main-vertical";
|
|
18497
|
-
const mainPaneSize = config2.main_pane_size ?? 60;
|
|
18498
|
-
await applyLayout(tmux, layout, mainPaneSize);
|
|
18499
|
-
log("[tmux] spawnTmuxPane: SUCCESS, pane created and layout applied", {
|
|
18500
|
-
paneId,
|
|
18501
|
-
layout
|
|
18502
|
-
});
|
|
18503
|
-
return { success: true, paneId };
|
|
18868
|
+
}
|
|
18869
|
+
// src/multiplexer/types.ts
|
|
18870
|
+
async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
18871
|
+
const healthUrl = new URL("/health", serverUrl).toString();
|
|
18872
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
18873
|
+
const controller = new AbortController;
|
|
18874
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
18875
|
+
let response = null;
|
|
18876
|
+
try {
|
|
18877
|
+
response = await fetch(healthUrl, { signal: controller.signal }).catch(() => null);
|
|
18878
|
+
} finally {
|
|
18879
|
+
clearTimeout(timeout);
|
|
18504
18880
|
}
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
|
|
18881
|
+
if (response?.ok) {
|
|
18882
|
+
return true;
|
|
18883
|
+
}
|
|
18884
|
+
if (attempt < maxAttempts) {
|
|
18885
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
18886
|
+
}
|
|
18887
|
+
}
|
|
18888
|
+
return false;
|
|
18889
|
+
}
|
|
18890
|
+
// src/utils/agent-variant.ts
|
|
18891
|
+
function normalizeAgentName(agentName) {
|
|
18892
|
+
const trimmed = agentName.trim();
|
|
18893
|
+
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
18894
|
+
}
|
|
18895
|
+
function resolveAgentVariant(config2, agentName) {
|
|
18896
|
+
const normalized = normalizeAgentName(agentName);
|
|
18897
|
+
const rawVariant = config2?.agents?.[normalized]?.variant;
|
|
18898
|
+
if (typeof rawVariant !== "string") {
|
|
18899
|
+
return;
|
|
18900
|
+
}
|
|
18901
|
+
const trimmed = rawVariant.trim();
|
|
18902
|
+
if (trimmed.length === 0) {
|
|
18903
|
+
return;
|
|
18509
18904
|
}
|
|
18905
|
+
log(`[variant] resolved variant="${trimmed}" for agent "${normalized}"`);
|
|
18906
|
+
return trimmed;
|
|
18510
18907
|
}
|
|
18511
|
-
|
|
18512
|
-
|
|
18513
|
-
|
|
18514
|
-
log("[tmux] closeTmuxPane: no paneId provided");
|
|
18515
|
-
return false;
|
|
18908
|
+
function applyAgentVariant(variant, body) {
|
|
18909
|
+
if (!variant) {
|
|
18910
|
+
return body;
|
|
18516
18911
|
}
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
log("[tmux] closeTmuxPane: tmux binary not found");
|
|
18520
|
-
return false;
|
|
18912
|
+
if (body.variant) {
|
|
18913
|
+
return body;
|
|
18521
18914
|
}
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18539
|
-
log("[tmux] closeTmuxPane: result", { exitCode, stderr: stderr.trim() });
|
|
18540
|
-
if (exitCode === 0) {
|
|
18541
|
-
log("[tmux] closeTmuxPane: SUCCESS, pane closed", { paneId });
|
|
18542
|
-
if (storedConfig) {
|
|
18543
|
-
const layout = storedConfig.layout ?? "main-vertical";
|
|
18544
|
-
const mainPaneSize = storedConfig.main_pane_size ?? 60;
|
|
18545
|
-
await applyLayout(tmux, layout, mainPaneSize);
|
|
18546
|
-
log("[tmux] closeTmuxPane: layout reapplied", { layout });
|
|
18547
|
-
}
|
|
18548
|
-
return true;
|
|
18549
|
-
}
|
|
18550
|
-
log("[tmux] closeTmuxPane: failed (pane may already be closed)", {
|
|
18551
|
-
paneId
|
|
18552
|
-
});
|
|
18553
|
-
return false;
|
|
18554
|
-
} catch (err) {
|
|
18555
|
-
log("[tmux] closeTmuxPane: exception", { error: String(err) });
|
|
18915
|
+
return { ...body, variant };
|
|
18916
|
+
}
|
|
18917
|
+
// src/utils/internal-initiator.ts
|
|
18918
|
+
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
18919
|
+
function isRecord(value) {
|
|
18920
|
+
return typeof value === "object" && value !== null;
|
|
18921
|
+
}
|
|
18922
|
+
function createInternalAgentTextPart(text) {
|
|
18923
|
+
return {
|
|
18924
|
+
type: "text",
|
|
18925
|
+
text: `${text}
|
|
18926
|
+
${SLIM_INTERNAL_INITIATOR_MARKER}`
|
|
18927
|
+
};
|
|
18928
|
+
}
|
|
18929
|
+
function hasInternalInitiatorMarker(part) {
|
|
18930
|
+
if (!isRecord(part) || part.type !== "text") {
|
|
18556
18931
|
return false;
|
|
18557
18932
|
}
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
if (!tmuxChecked) {
|
|
18561
|
-
getTmuxPath().catch(() => {});
|
|
18933
|
+
if (typeof part.text !== "string") {
|
|
18934
|
+
return false;
|
|
18562
18935
|
}
|
|
18936
|
+
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
18563
18937
|
}
|
|
18564
18938
|
// src/utils/zip-extractor.ts
|
|
18565
18939
|
import { release } from "os";
|
|
18566
|
-
var {spawn:
|
|
18940
|
+
var {spawn: spawn3, spawnSync } = globalThis.Bun;
|
|
18567
18941
|
var WINDOWS_BUILD_WITH_TAR = 17134;
|
|
18568
18942
|
function getWindowsBuildNumber() {
|
|
18569
18943
|
if (process.platform !== "win32")
|
|
@@ -18604,13 +18978,13 @@ async function extractZip(archivePath, destDir) {
|
|
|
18604
18978
|
const extractor = getWindowsZipExtractor();
|
|
18605
18979
|
switch (extractor) {
|
|
18606
18980
|
case "tar":
|
|
18607
|
-
proc =
|
|
18981
|
+
proc = spawn3(["tar", "-xf", archivePath, "-C", destDir], {
|
|
18608
18982
|
stdout: "ignore",
|
|
18609
18983
|
stderr: "pipe"
|
|
18610
18984
|
});
|
|
18611
18985
|
break;
|
|
18612
18986
|
case "pwsh":
|
|
18613
|
-
proc =
|
|
18987
|
+
proc = spawn3([
|
|
18614
18988
|
"pwsh",
|
|
18615
18989
|
"-Command",
|
|
18616
18990
|
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
@@ -18620,7 +18994,7 @@ async function extractZip(archivePath, destDir) {
|
|
|
18620
18994
|
});
|
|
18621
18995
|
break;
|
|
18622
18996
|
default:
|
|
18623
|
-
proc =
|
|
18997
|
+
proc = spawn3([
|
|
18624
18998
|
"powershell",
|
|
18625
18999
|
"-Command",
|
|
18626
19000
|
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
@@ -18631,7 +19005,7 @@ async function extractZip(archivePath, destDir) {
|
|
|
18631
19005
|
break;
|
|
18632
19006
|
}
|
|
18633
19007
|
} else {
|
|
18634
|
-
proc =
|
|
19008
|
+
proc = spawn3(["unzip", "-o", archivePath, "-d", destDir], {
|
|
18635
19009
|
stdout: "ignore",
|
|
18636
19010
|
stderr: "pipe"
|
|
18637
19011
|
});
|
|
@@ -18702,10 +19076,10 @@ class BackgroundTaskManager {
|
|
|
18702
19076
|
activeStarts = 0;
|
|
18703
19077
|
maxConcurrentStarts;
|
|
18704
19078
|
completionResolvers = new Map;
|
|
18705
|
-
constructor(ctx,
|
|
19079
|
+
constructor(ctx, multiplexerConfig, config2) {
|
|
18706
19080
|
this.client = ctx.client;
|
|
18707
19081
|
this.directory = ctx.directory;
|
|
18708
|
-
this.tmuxEnabled =
|
|
19082
|
+
this.tmuxEnabled = multiplexerConfig !== undefined && multiplexerConfig.type !== "none" && multiplexerConfig.type !== undefined && getMultiplexer(multiplexerConfig) !== null;
|
|
18709
19083
|
this.config = config2;
|
|
18710
19084
|
this.backgroundConfig = config2?.background ?? {
|
|
18711
19085
|
maxConcurrentStarts: 10
|
|
@@ -18843,6 +19217,7 @@ class BackgroundTaskManager {
|
|
|
18843
19217
|
const errors3 = [];
|
|
18844
19218
|
let succeeded = false;
|
|
18845
19219
|
const sessionId = session2.data.id;
|
|
19220
|
+
const retryOnEmpty = this.config?.fallback?.retry_on_empty ?? true;
|
|
18846
19221
|
for (let i = 0;i < attemptModels.length; i++) {
|
|
18847
19222
|
const model = attemptModels[i];
|
|
18848
19223
|
const modelLabel = model ?? "default-model";
|
|
@@ -18866,6 +19241,10 @@ class BackgroundTaskManager {
|
|
|
18866
19241
|
body,
|
|
18867
19242
|
query: promptQuery
|
|
18868
19243
|
}, timeoutMs);
|
|
19244
|
+
const extraction = await extractSessionResult(this.client, sessionId);
|
|
19245
|
+
if (retryOnEmpty && extraction.empty) {
|
|
19246
|
+
throw new Error("Empty response from provider");
|
|
19247
|
+
}
|
|
18869
19248
|
succeeded = true;
|
|
18870
19249
|
break;
|
|
18871
19250
|
} catch (error48) {
|
|
@@ -18945,12 +19324,13 @@ class BackgroundTaskManager {
|
|
|
18945
19324
|
async extractAndCompleteTask(task) {
|
|
18946
19325
|
if (!task.sessionId)
|
|
18947
19326
|
return;
|
|
19327
|
+
const retryOnEmpty = this.config?.fallback?.retry_on_empty ?? true;
|
|
18948
19328
|
try {
|
|
18949
|
-
const
|
|
18950
|
-
if (
|
|
18951
|
-
this.completeTask(task, "
|
|
19329
|
+
const extraction = await extractSessionResult(this.client, task.sessionId);
|
|
19330
|
+
if (extraction.empty && retryOnEmpty) {
|
|
19331
|
+
this.completeTask(task, "failed", "Empty response from provider");
|
|
18952
19332
|
} else {
|
|
18953
|
-
this.completeTask(task, "completed",
|
|
19333
|
+
this.completeTask(task, "completed", extraction.text);
|
|
18954
19334
|
}
|
|
18955
19335
|
} catch (error48) {
|
|
18956
19336
|
this.completeTask(task, "failed", error48 instanceof Error ? error48.message : String(error48));
|
|
@@ -19068,31 +19448,31 @@ class BackgroundTaskManager {
|
|
|
19068
19448
|
return this.depthTracker;
|
|
19069
19449
|
}
|
|
19070
19450
|
}
|
|
19071
|
-
// src/background/
|
|
19451
|
+
// src/background/multiplexer-session-manager.ts
|
|
19072
19452
|
var SESSION_TIMEOUT_MS = 10 * 60 * 1000;
|
|
19073
19453
|
var SESSION_MISSING_GRACE_MS = POLL_INTERVAL_BACKGROUND_MS * 3;
|
|
19074
19454
|
|
|
19075
|
-
class
|
|
19455
|
+
class MultiplexerSessionManager {
|
|
19076
19456
|
client;
|
|
19077
|
-
tmuxConfig;
|
|
19078
19457
|
serverUrl;
|
|
19458
|
+
multiplexer = null;
|
|
19079
19459
|
sessions = new Map;
|
|
19080
19460
|
pollInterval;
|
|
19081
19461
|
enabled = false;
|
|
19082
|
-
constructor(ctx,
|
|
19462
|
+
constructor(ctx, config2) {
|
|
19083
19463
|
this.client = ctx.client;
|
|
19084
|
-
this.tmuxConfig = tmuxConfig;
|
|
19085
19464
|
const defaultPort = process.env.OPENCODE_PORT ?? "4096";
|
|
19086
19465
|
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`;
|
|
19087
|
-
this.
|
|
19088
|
-
|
|
19466
|
+
this.multiplexer = getMultiplexer(config2);
|
|
19467
|
+
this.enabled = config2.type !== "none" && this.multiplexer !== null && this.multiplexer.isInsideSession();
|
|
19468
|
+
log("[multiplexer-session-manager] initialized", {
|
|
19089
19469
|
enabled: this.enabled,
|
|
19090
|
-
|
|
19470
|
+
type: config2.type,
|
|
19091
19471
|
serverUrl: this.serverUrl
|
|
19092
19472
|
});
|
|
19093
19473
|
}
|
|
19094
19474
|
async onSessionCreated(event) {
|
|
19095
|
-
if (!this.enabled)
|
|
19475
|
+
if (!this.enabled || !this.multiplexer)
|
|
19096
19476
|
return;
|
|
19097
19477
|
if (event.type !== "session.created")
|
|
19098
19478
|
return;
|
|
@@ -19104,16 +19484,25 @@ class TmuxSessionManager {
|
|
|
19104
19484
|
const parentId = info.parentID;
|
|
19105
19485
|
const title = info.title ?? "Subagent";
|
|
19106
19486
|
if (this.sessions.has(sessionId)) {
|
|
19107
|
-
log("[
|
|
19487
|
+
log("[multiplexer-session-manager] session already tracked", {
|
|
19488
|
+
sessionId
|
|
19489
|
+
});
|
|
19490
|
+
return;
|
|
19491
|
+
}
|
|
19492
|
+
const serverRunning = await isServerRunning(this.serverUrl);
|
|
19493
|
+
if (!serverRunning) {
|
|
19494
|
+
log("[multiplexer-session-manager] server not running, skipping", {
|
|
19495
|
+
serverUrl: this.serverUrl
|
|
19496
|
+
});
|
|
19108
19497
|
return;
|
|
19109
19498
|
}
|
|
19110
|
-
log("[
|
|
19499
|
+
log("[multiplexer-session-manager] child session created, spawning pane", {
|
|
19111
19500
|
sessionId,
|
|
19112
19501
|
parentId,
|
|
19113
19502
|
title
|
|
19114
19503
|
});
|
|
19115
|
-
const paneResult = await
|
|
19116
|
-
log("[
|
|
19504
|
+
const paneResult = await this.multiplexer.spawnPane(sessionId, title, this.serverUrl).catch((err) => {
|
|
19505
|
+
log("[multiplexer-session-manager] failed to spawn pane", {
|
|
19117
19506
|
error: String(err)
|
|
19118
19507
|
});
|
|
19119
19508
|
return { success: false, paneId: undefined };
|
|
@@ -19128,7 +19517,7 @@ class TmuxSessionManager {
|
|
|
19128
19517
|
createdAt: now,
|
|
19129
19518
|
lastSeenAt: now
|
|
19130
19519
|
});
|
|
19131
|
-
log("[
|
|
19520
|
+
log("[multiplexer-session-manager] pane spawned", {
|
|
19132
19521
|
sessionId,
|
|
19133
19522
|
paneId: paneResult.paneId
|
|
19134
19523
|
});
|
|
@@ -19155,7 +19544,7 @@ class TmuxSessionManager {
|
|
|
19155
19544
|
const sessionId = event.properties?.sessionID;
|
|
19156
19545
|
if (!sessionId)
|
|
19157
19546
|
return;
|
|
19158
|
-
log("[
|
|
19547
|
+
log("[multiplexer-session-manager] session deleted, closing pane", {
|
|
19159
19548
|
sessionId
|
|
19160
19549
|
});
|
|
19161
19550
|
await this.closeSession(sessionId);
|
|
@@ -19164,13 +19553,13 @@ class TmuxSessionManager {
|
|
|
19164
19553
|
if (this.pollInterval)
|
|
19165
19554
|
return;
|
|
19166
19555
|
this.pollInterval = setInterval(() => this.pollSessions(), POLL_INTERVAL_BACKGROUND_MS);
|
|
19167
|
-
log("[
|
|
19556
|
+
log("[multiplexer-session-manager] polling started");
|
|
19168
19557
|
}
|
|
19169
19558
|
stopPolling() {
|
|
19170
19559
|
if (this.pollInterval) {
|
|
19171
19560
|
clearInterval(this.pollInterval);
|
|
19172
19561
|
this.pollInterval = undefined;
|
|
19173
|
-
log("[
|
|
19562
|
+
log("[multiplexer-session-manager] polling stopped");
|
|
19174
19563
|
}
|
|
19175
19564
|
}
|
|
19176
19565
|
async pollSessions() {
|
|
@@ -19202,18 +19591,18 @@ class TmuxSessionManager {
|
|
|
19202
19591
|
await this.closeSession(sessionId);
|
|
19203
19592
|
}
|
|
19204
19593
|
} catch (err) {
|
|
19205
|
-
log("[
|
|
19594
|
+
log("[multiplexer-session-manager] poll error", { error: String(err) });
|
|
19206
19595
|
}
|
|
19207
19596
|
}
|
|
19208
19597
|
async closeSession(sessionId) {
|
|
19209
19598
|
const tracked = this.sessions.get(sessionId);
|
|
19210
|
-
if (!tracked)
|
|
19599
|
+
if (!tracked || !this.multiplexer)
|
|
19211
19600
|
return;
|
|
19212
|
-
log("[
|
|
19601
|
+
log("[multiplexer-session-manager] closing session pane", {
|
|
19213
19602
|
sessionId,
|
|
19214
19603
|
paneId: tracked.paneId
|
|
19215
19604
|
});
|
|
19216
|
-
await
|
|
19605
|
+
await this.multiplexer.closePane(tracked.paneId);
|
|
19217
19606
|
this.sessions.delete(sessionId);
|
|
19218
19607
|
if (this.sessions.size === 0) {
|
|
19219
19608
|
this.stopPolling();
|
|
@@ -19221,18 +19610,19 @@ class TmuxSessionManager {
|
|
|
19221
19610
|
}
|
|
19222
19611
|
async cleanup() {
|
|
19223
19612
|
this.stopPolling();
|
|
19224
|
-
if (this.sessions.size > 0) {
|
|
19225
|
-
log("[
|
|
19613
|
+
if (this.sessions.size > 0 && this.multiplexer) {
|
|
19614
|
+
log("[multiplexer-session-manager] closing all panes", {
|
|
19226
19615
|
count: this.sessions.size
|
|
19227
19616
|
});
|
|
19228
|
-
const
|
|
19617
|
+
const multiplexer = this.multiplexer;
|
|
19618
|
+
const closePromises = Array.from(this.sessions.values()).map((s) => multiplexer.closePane(s.paneId).catch((err) => log("[multiplexer-session-manager] cleanup error for pane", {
|
|
19229
19619
|
paneId: s.paneId,
|
|
19230
19620
|
error: String(err)
|
|
19231
19621
|
})));
|
|
19232
19622
|
await Promise.all(closePromises);
|
|
19233
19623
|
this.sessions.clear();
|
|
19234
19624
|
}
|
|
19235
|
-
log("[
|
|
19625
|
+
log("[multiplexer-session-manager] cleanup complete");
|
|
19236
19626
|
}
|
|
19237
19627
|
}
|
|
19238
19628
|
// src/council/council-manager.ts
|
|
@@ -19277,10 +19667,11 @@ class CouncilManager {
|
|
|
19277
19667
|
const resolvedPreset = presetName ?? councilConfig.default_preset ?? "default";
|
|
19278
19668
|
const preset = councilConfig.presets[resolvedPreset];
|
|
19279
19669
|
if (!preset) {
|
|
19670
|
+
const available = Object.keys(councilConfig.presets).join(", ");
|
|
19280
19671
|
log(`[council-manager] Preset "${resolvedPreset}" not found`);
|
|
19281
19672
|
return {
|
|
19282
19673
|
success: false,
|
|
19283
|
-
error: `Preset "${resolvedPreset}" not
|
|
19674
|
+
error: `Preset "${resolvedPreset}" does not exist. Omit the preset parameter to use the default, or call again with one of: ${available}`,
|
|
19284
19675
|
councillorResults: []
|
|
19285
19676
|
};
|
|
19286
19677
|
}
|
|
@@ -19294,6 +19685,8 @@ class CouncilManager {
|
|
|
19294
19685
|
}
|
|
19295
19686
|
const councillorsTimeout = councilConfig.councillors_timeout ?? 180000;
|
|
19296
19687
|
const masterTimeout = councilConfig.master_timeout ?? 300000;
|
|
19688
|
+
const executionMode = councilConfig.councillor_execution_mode ?? "parallel";
|
|
19689
|
+
const maxRetries = councilConfig.councillor_retries ?? 3;
|
|
19297
19690
|
const councillorCount = Object.keys(preset.councillors).length;
|
|
19298
19691
|
log(`[council-manager] Starting council with preset "${resolvedPreset}"`, {
|
|
19299
19692
|
councillors: Object.keys(preset.councillors)
|
|
@@ -19303,7 +19696,7 @@ class CouncilManager {
|
|
|
19303
19696
|
error: err instanceof Error ? err.message : String(err)
|
|
19304
19697
|
});
|
|
19305
19698
|
});
|
|
19306
|
-
const councillorResults = await this.runCouncillors(prompt, preset.councillors, parentSessionId, councillorsTimeout);
|
|
19699
|
+
const councillorResults = await this.runCouncillors(prompt, preset.councillors, parentSessionId, councillorsTimeout, executionMode, maxRetries);
|
|
19307
19700
|
const completedCount = councillorResults.filter((r) => r.status === "completed").length;
|
|
19308
19701
|
log(`[council-manager] Councillors completed: ${completedCount}/${councillorResults.length}`);
|
|
19309
19702
|
if (completedCount === 0) {
|
|
@@ -19391,10 +19784,16 @@ ${bestResult.result}` : undefined,
|
|
|
19391
19784
|
body,
|
|
19392
19785
|
query: { directory: this.directory }
|
|
19393
19786
|
}, options.timeout);
|
|
19394
|
-
const
|
|
19787
|
+
const extraction = await extractSessionResult(this.client, sessionId, {
|
|
19395
19788
|
includeReasoning: options.includeReasoning
|
|
19396
19789
|
});
|
|
19397
|
-
|
|
19790
|
+
if (extraction.empty) {
|
|
19791
|
+
const retryOnEmpty = this.config?.fallback?.retry_on_empty ?? true;
|
|
19792
|
+
if (retryOnEmpty) {
|
|
19793
|
+
throw new Error("Empty response from provider");
|
|
19794
|
+
}
|
|
19795
|
+
}
|
|
19796
|
+
return extraction.text;
|
|
19398
19797
|
} finally {
|
|
19399
19798
|
if (sessionId) {
|
|
19400
19799
|
this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
|
|
@@ -19404,13 +19803,51 @@ ${bestResult.result}` : undefined,
|
|
|
19404
19803
|
}
|
|
19405
19804
|
}
|
|
19406
19805
|
}
|
|
19407
|
-
async runCouncillors(prompt, councillors, parentSessionId, timeout) {
|
|
19806
|
+
async runCouncillors(prompt, councillors, parentSessionId, timeout, executionMode = "parallel", maxRetries = 1) {
|
|
19408
19807
|
const entries = Object.entries(councillors);
|
|
19409
|
-
const
|
|
19410
|
-
|
|
19411
|
-
|
|
19808
|
+
const results = [];
|
|
19809
|
+
if (executionMode === "serial") {
|
|
19810
|
+
for (const [name, config2] of entries) {
|
|
19811
|
+
results.push(await this.runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries));
|
|
19812
|
+
}
|
|
19813
|
+
} else {
|
|
19814
|
+
const promises = entries.map(([name, config2], index) => (async () => {
|
|
19815
|
+
if (index > 0) {
|
|
19816
|
+
await new Promise((r) => setTimeout(r, index * COUNCILLOR_STAGGER_MS));
|
|
19817
|
+
}
|
|
19818
|
+
return this.runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries);
|
|
19819
|
+
})());
|
|
19820
|
+
const settled = await Promise.allSettled(promises);
|
|
19821
|
+
for (let index = 0;index < settled.length; index++) {
|
|
19822
|
+
const result = settled[index];
|
|
19823
|
+
const [name, cfg] = entries[index];
|
|
19824
|
+
if (result.status === "fulfilled") {
|
|
19825
|
+
results.push({
|
|
19826
|
+
name,
|
|
19827
|
+
model: cfg.model,
|
|
19828
|
+
status: result.value.status,
|
|
19829
|
+
result: result.value.result,
|
|
19830
|
+
error: result.value.error
|
|
19831
|
+
});
|
|
19832
|
+
} else {
|
|
19833
|
+
results.push({
|
|
19834
|
+
name,
|
|
19835
|
+
model: cfg.model,
|
|
19836
|
+
status: "failed",
|
|
19837
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
19838
|
+
});
|
|
19839
|
+
}
|
|
19840
|
+
}
|
|
19841
|
+
}
|
|
19842
|
+
return results;
|
|
19843
|
+
}
|
|
19844
|
+
async runCouncillorWithRetry(name, config2, prompt, parentSessionId, timeout, maxRetries) {
|
|
19845
|
+
const modelLabel = shortModelLabel(config2.model);
|
|
19846
|
+
const totalAttempts = 1 + maxRetries;
|
|
19847
|
+
for (let attempt = 1;attempt <= totalAttempts; attempt++) {
|
|
19848
|
+
if (attempt > 1) {
|
|
19849
|
+
log(`[council-manager] Retrying councillor "${name}" (${modelLabel}), attempt ${attempt}/${totalAttempts}`);
|
|
19412
19850
|
}
|
|
19413
|
-
const modelLabel = shortModelLabel(config2.model);
|
|
19414
19851
|
try {
|
|
19415
19852
|
const result = await this.runAgentSession({
|
|
19416
19853
|
parentSessionId,
|
|
@@ -19430,33 +19867,51 @@ ${bestResult.result}` : undefined,
|
|
|
19430
19867
|
};
|
|
19431
19868
|
} catch (error48) {
|
|
19432
19869
|
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19870
|
+
const isEmptyResponse = msg.includes("Empty response from provider");
|
|
19871
|
+
const canRetry = attempt < totalAttempts && isEmptyResponse;
|
|
19872
|
+
if (!canRetry) {
|
|
19873
|
+
return {
|
|
19874
|
+
name,
|
|
19875
|
+
model: config2.model,
|
|
19876
|
+
status: msg.includes("timed out") ? "timed_out" : "failed",
|
|
19877
|
+
error: `Councillor "${name}": ${msg}`
|
|
19878
|
+
};
|
|
19879
|
+
}
|
|
19439
19880
|
}
|
|
19440
|
-
}
|
|
19441
|
-
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19881
|
+
}
|
|
19882
|
+
return {
|
|
19883
|
+
name,
|
|
19884
|
+
model: config2.model,
|
|
19885
|
+
status: "failed",
|
|
19886
|
+
error: `Councillor "${name}": max retries exhausted`
|
|
19887
|
+
};
|
|
19888
|
+
}
|
|
19889
|
+
async runMasterModelWithRetry(parentSessionId, model, modelLabel, promptText, variant, timeout, maxRetries) {
|
|
19890
|
+
const totalAttempts = 1 + maxRetries;
|
|
19891
|
+
for (let attempt = 1;attempt <= totalAttempts; attempt++) {
|
|
19892
|
+
if (attempt > 1) {
|
|
19893
|
+
log(`[council-manager] Retrying master (${modelLabel}), attempt ${attempt}/${totalAttempts}`);
|
|
19452
19894
|
}
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19457
|
-
|
|
19458
|
-
|
|
19459
|
-
|
|
19895
|
+
try {
|
|
19896
|
+
return await this.runAgentSession({
|
|
19897
|
+
parentSessionId,
|
|
19898
|
+
title: `Council Master (${modelLabel})`,
|
|
19899
|
+
agent: "council-master",
|
|
19900
|
+
model,
|
|
19901
|
+
promptText,
|
|
19902
|
+
variant,
|
|
19903
|
+
timeout
|
|
19904
|
+
});
|
|
19905
|
+
} catch (error48) {
|
|
19906
|
+
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
19907
|
+
const isEmptyResponse = msg.includes("Empty response from provider");
|
|
19908
|
+
const canRetry = attempt < totalAttempts && isEmptyResponse;
|
|
19909
|
+
if (!canRetry) {
|
|
19910
|
+
throw error48;
|
|
19911
|
+
}
|
|
19912
|
+
}
|
|
19913
|
+
}
|
|
19914
|
+
throw new Error(`Master model ${modelLabel}: max retries exhausted`);
|
|
19460
19915
|
}
|
|
19461
19916
|
async runMaster(prompt, councillorResults, councilConfig, parentSessionId, timeout, presetMasterOverride) {
|
|
19462
19917
|
const masterConfig = councilConfig.master;
|
|
@@ -19466,6 +19921,7 @@ ${bestResult.result}` : undefined,
|
|
|
19466
19921
|
const effectivePrompt = presetMasterOverride?.prompt ?? masterConfig.prompt;
|
|
19467
19922
|
const attemptModels = [effectiveModel, ...fallbackModels];
|
|
19468
19923
|
const synthesisPrompt = formatMasterSynthesisPrompt(prompt, councillorResults, effectivePrompt);
|
|
19924
|
+
const maxRetries = councilConfig.councillor_retries ?? 3;
|
|
19469
19925
|
const errors3 = [];
|
|
19470
19926
|
for (let i = 0;i < attemptModels.length; i++) {
|
|
19471
19927
|
const model = attemptModels[i];
|
|
@@ -19474,15 +19930,7 @@ ${bestResult.result}` : undefined,
|
|
|
19474
19930
|
if (i > 0) {
|
|
19475
19931
|
log(`[council-manager] master fallback ${i}/${attemptModels.length - 1}: ${currentLabel}`);
|
|
19476
19932
|
}
|
|
19477
|
-
const result = await this.
|
|
19478
|
-
parentSessionId,
|
|
19479
|
-
title: `Council Master (${currentLabel})`,
|
|
19480
|
-
agent: "council-master",
|
|
19481
|
-
model,
|
|
19482
|
-
promptText: synthesisPrompt,
|
|
19483
|
-
variant: effectiveVariant,
|
|
19484
|
-
timeout
|
|
19485
|
-
});
|
|
19933
|
+
const result = await this.runMasterModelWithRetry(parentSessionId, model, currentLabel, synthesisPrompt, effectiveVariant, timeout, maxRetries);
|
|
19486
19934
|
return { success: true, result };
|
|
19487
19935
|
} catch (error48) {
|
|
19488
19936
|
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
@@ -19735,30 +20183,6 @@ function getCachedVersion() {
|
|
|
19735
20183
|
}
|
|
19736
20184
|
return null;
|
|
19737
20185
|
}
|
|
19738
|
-
function updatePinnedVersion(configPath, oldEntry, newVersion) {
|
|
19739
|
-
try {
|
|
19740
|
-
if (!fs4.existsSync(configPath))
|
|
19741
|
-
return false;
|
|
19742
|
-
const content = fs4.readFileSync(configPath, "utf-8");
|
|
19743
|
-
const newEntry = `${PACKAGE_NAME}@${newVersion}`;
|
|
19744
|
-
const escapedOldEntry = oldEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19745
|
-
const entryRegex = new RegExp(`(["'])${escapedOldEntry}\\1`, "g");
|
|
19746
|
-
if (!entryRegex.test(content)) {
|
|
19747
|
-
log(`[auto-update-checker] Entry "${oldEntry}" not found in ${configPath}`);
|
|
19748
|
-
return false;
|
|
19749
|
-
}
|
|
19750
|
-
const updatedContent = content.replace(entryRegex, `$1${newEntry}$1`);
|
|
19751
|
-
if (updatedContent === content) {
|
|
19752
|
-
return false;
|
|
19753
|
-
}
|
|
19754
|
-
fs4.writeFileSync(configPath, updatedContent, "utf-8");
|
|
19755
|
-
log(`[auto-update-checker] Updated ${configPath}: ${oldEntry} \u2192 ${newEntry}`);
|
|
19756
|
-
return true;
|
|
19757
|
-
} catch (err) {
|
|
19758
|
-
log(`[auto-update-checker] Failed to update config file ${configPath}:`, err);
|
|
19759
|
-
return false;
|
|
19760
|
-
}
|
|
19761
|
-
}
|
|
19762
20186
|
async function getLatestVersion(channel = "latest") {
|
|
19763
20187
|
const controller = new AbortController;
|
|
19764
20188
|
const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
|
|
@@ -19825,6 +20249,10 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
|
19825
20249
|
log("[auto-update-checker] No version found (cached or pinned)");
|
|
19826
20250
|
return;
|
|
19827
20251
|
}
|
|
20252
|
+
if (pluginInfo.isPinned) {
|
|
20253
|
+
log(`[auto-update-checker] Version is pinned; skipping update check.`);
|
|
20254
|
+
return;
|
|
20255
|
+
}
|
|
19828
20256
|
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
|
|
19829
20257
|
const latestVersion = await getLatestVersion(channel);
|
|
19830
20258
|
if (!latestVersion) {
|
|
@@ -19841,15 +20269,6 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
|
19841
20269
|
log("[auto-update-checker] Auto-update disabled, notification only");
|
|
19842
20270
|
return;
|
|
19843
20271
|
}
|
|
19844
|
-
if (pluginInfo.isPinned) {
|
|
19845
|
-
const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
|
|
19846
|
-
if (!updated) {
|
|
19847
|
-
showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available. Restart to apply.`, "info", 8000);
|
|
19848
|
-
log("[auto-update-checker] Failed to update pinned version in config");
|
|
19849
|
-
return;
|
|
19850
|
-
}
|
|
19851
|
-
log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
|
|
19852
|
-
}
|
|
19853
20272
|
invalidatePackage(PACKAGE_NAME);
|
|
19854
20273
|
const installSuccess = await runBunInstallSafe(ctx);
|
|
19855
20274
|
if (installSuccess) {
|
|
@@ -20046,6 +20465,76 @@ ${buildRetryGuidance(detected)}`;
|
|
|
20046
20465
|
}
|
|
20047
20466
|
};
|
|
20048
20467
|
}
|
|
20468
|
+
// src/hooks/filter-available-skills/index.ts
|
|
20469
|
+
var AVAILABLE_SKILLS_BLOCK_REGEX = /<available_skills>\s*([\s\S]*?)\s*<\/available_skills>/g;
|
|
20470
|
+
var SKILL_NAME_REGEX = /<name>([^<]+)<\/name>/;
|
|
20471
|
+
function getCurrentAgent(messages) {
|
|
20472
|
+
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
20473
|
+
const message = messages[index];
|
|
20474
|
+
if (message.info.role === "user") {
|
|
20475
|
+
return message.info.agent ?? "orchestrator";
|
|
20476
|
+
}
|
|
20477
|
+
}
|
|
20478
|
+
return "orchestrator";
|
|
20479
|
+
}
|
|
20480
|
+
function extractSkillEntries(blockContent) {
|
|
20481
|
+
const entries = [];
|
|
20482
|
+
const skillEntryRegex = /<skill>\s*([\s\S]*?)\s*<\/skill>/g;
|
|
20483
|
+
for (const match of blockContent.matchAll(skillEntryRegex)) {
|
|
20484
|
+
const block = match[0];
|
|
20485
|
+
const nameMatch = block.match(SKILL_NAME_REGEX);
|
|
20486
|
+
if (!nameMatch) {
|
|
20487
|
+
continue;
|
|
20488
|
+
}
|
|
20489
|
+
entries.push({
|
|
20490
|
+
name: nameMatch[1].trim(),
|
|
20491
|
+
block
|
|
20492
|
+
});
|
|
20493
|
+
}
|
|
20494
|
+
return entries;
|
|
20495
|
+
}
|
|
20496
|
+
function isSkillAllowed(skillName, permissionRules) {
|
|
20497
|
+
const specificRule = permissionRules[skillName];
|
|
20498
|
+
if (specificRule !== undefined) {
|
|
20499
|
+
return specificRule === "allow";
|
|
20500
|
+
}
|
|
20501
|
+
return permissionRules["*"] === "allow";
|
|
20502
|
+
}
|
|
20503
|
+
function filterAvailableSkillsText(text, permissionRules) {
|
|
20504
|
+
return text.replace(AVAILABLE_SKILLS_BLOCK_REGEX, (_fullMatch, blockContent) => {
|
|
20505
|
+
const allowedEntries = extractSkillEntries(blockContent).filter((entry) => isSkillAllowed(entry.name, permissionRules));
|
|
20506
|
+
if (allowedEntries.length === 0) {
|
|
20507
|
+
return `<available_skills>
|
|
20508
|
+
No skills available.
|
|
20509
|
+
</available_skills>`;
|
|
20510
|
+
}
|
|
20511
|
+
return `<available_skills>
|
|
20512
|
+
${allowedEntries.map((entry) => entry.block).join(`
|
|
20513
|
+
`)}
|
|
20514
|
+
</available_skills>`;
|
|
20515
|
+
});
|
|
20516
|
+
}
|
|
20517
|
+
function createFilterAvailableSkillsHook(_ctx, config2) {
|
|
20518
|
+
return {
|
|
20519
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
20520
|
+
const { messages } = output;
|
|
20521
|
+
if (messages.length === 0) {
|
|
20522
|
+
return;
|
|
20523
|
+
}
|
|
20524
|
+
const agentName = getCurrentAgent(messages);
|
|
20525
|
+
const configuredSkills = getAgentOverride(config2, agentName)?.skills;
|
|
20526
|
+
const permissionRules = getSkillPermissionsForAgent(agentName, configuredSkills);
|
|
20527
|
+
for (const message of messages) {
|
|
20528
|
+
for (const part of message.parts) {
|
|
20529
|
+
if (part.type !== "text" || !part.text || !part.text.includes("<available_skills>")) {
|
|
20530
|
+
continue;
|
|
20531
|
+
}
|
|
20532
|
+
part.text = filterAvailableSkillsText(part.text, permissionRules);
|
|
20533
|
+
}
|
|
20534
|
+
}
|
|
20535
|
+
}
|
|
20536
|
+
};
|
|
20537
|
+
}
|
|
20049
20538
|
// src/hooks/foreground-fallback/index.ts
|
|
20050
20539
|
var RATE_LIMIT_PATTERNS = [
|
|
20051
20540
|
/\b429\b/,
|
|
@@ -20381,12 +20870,31 @@ var grep_app = {
|
|
|
20381
20870
|
};
|
|
20382
20871
|
|
|
20383
20872
|
// src/mcp/websearch.ts
|
|
20384
|
-
|
|
20385
|
-
|
|
20386
|
-
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20873
|
+
function createWebsearchConfig(config2) {
|
|
20874
|
+
const provider = config2?.provider || "exa";
|
|
20875
|
+
if (provider === "tavily") {
|
|
20876
|
+
const tavilyKey = process.env.TAVILY_API_KEY;
|
|
20877
|
+
if (!tavilyKey) {
|
|
20878
|
+
throw new Error("TAVILY_API_KEY environment variable is required for Tavily provider");
|
|
20879
|
+
}
|
|
20880
|
+
return {
|
|
20881
|
+
type: "remote",
|
|
20882
|
+
url: "https://mcp.tavily.com/mcp/",
|
|
20883
|
+
headers: {
|
|
20884
|
+
Authorization: `Bearer ${tavilyKey}`
|
|
20885
|
+
},
|
|
20886
|
+
oauth: false
|
|
20887
|
+
};
|
|
20888
|
+
}
|
|
20889
|
+
const exaKey = process.env.EXA_API_KEY;
|
|
20890
|
+
const exaUrl = exaKey ? `https://mcp.exa.ai/mcp?tools=web_search_exa&exaApiKey=${encodeURIComponent(exaKey)}` : "https://mcp.exa.ai/mcp?tools=web_search_exa";
|
|
20891
|
+
return {
|
|
20892
|
+
type: "remote",
|
|
20893
|
+
url: exaUrl,
|
|
20894
|
+
oauth: false
|
|
20895
|
+
};
|
|
20896
|
+
}
|
|
20897
|
+
var websearch = createWebsearchConfig();
|
|
20390
20898
|
|
|
20391
20899
|
// src/mcp/index.ts
|
|
20392
20900
|
var allBuiltinMcps = {
|
|
@@ -20394,8 +20902,12 @@ var allBuiltinMcps = {
|
|
|
20394
20902
|
context7,
|
|
20395
20903
|
grep_app
|
|
20396
20904
|
};
|
|
20397
|
-
function createBuiltinMcps(disabledMcps = []) {
|
|
20398
|
-
|
|
20905
|
+
function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
20906
|
+
const mcps = Object.fromEntries(Object.entries(allBuiltinMcps).filter(([name]) => !disabledMcps.includes(name)));
|
|
20907
|
+
if (!disabledMcps.includes("websearch")) {
|
|
20908
|
+
mcps.websearch = createWebsearchConfig(websearchConfig);
|
|
20909
|
+
}
|
|
20910
|
+
return mcps;
|
|
20399
20911
|
}
|
|
20400
20912
|
|
|
20401
20913
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
@@ -32721,15 +33233,15 @@ tool.schema = exports_external2;
|
|
|
32721
33233
|
|
|
32722
33234
|
// src/tools/ast-grep/cli.ts
|
|
32723
33235
|
import { existsSync as existsSync6 } from "fs";
|
|
32724
|
-
var {spawn:
|
|
33236
|
+
var {spawn: spawn4 } = globalThis.Bun;
|
|
32725
33237
|
|
|
32726
33238
|
// src/tools/ast-grep/constants.ts
|
|
32727
33239
|
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
32728
33240
|
import { createRequire as createRequire2 } from "module";
|
|
32729
|
-
import { dirname as
|
|
33241
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
32730
33242
|
|
|
32731
33243
|
// src/tools/ast-grep/downloader.ts
|
|
32732
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync, unlinkSync } from "fs";
|
|
33244
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
32733
33245
|
import { createRequire } from "module";
|
|
32734
33246
|
import { homedir as homedir3 } from "os";
|
|
32735
33247
|
import { join as join7 } from "path";
|
|
@@ -32789,7 +33301,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
32789
33301
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
32790
33302
|
try {
|
|
32791
33303
|
if (!existsSync4(cacheDir)) {
|
|
32792
|
-
|
|
33304
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
32793
33305
|
}
|
|
32794
33306
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
32795
33307
|
if (!response.ok) {
|
|
@@ -32883,7 +33395,7 @@ function findSgCliPathSync() {
|
|
|
32883
33395
|
try {
|
|
32884
33396
|
const require2 = createRequire2(import.meta.url);
|
|
32885
33397
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
32886
|
-
const cliDir =
|
|
33398
|
+
const cliDir = dirname4(cliPkgPath);
|
|
32887
33399
|
const sgPath = join8(cliDir, binaryName);
|
|
32888
33400
|
if (existsSync5(sgPath) && isValidBinary(sgPath)) {
|
|
32889
33401
|
return sgPath;
|
|
@@ -32894,7 +33406,7 @@ function findSgCliPathSync() {
|
|
|
32894
33406
|
try {
|
|
32895
33407
|
const require2 = createRequire2(import.meta.url);
|
|
32896
33408
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
32897
|
-
const pkgDir =
|
|
33409
|
+
const pkgDir = dirname4(pkgPath);
|
|
32898
33410
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
32899
33411
|
const binaryPath = join8(pkgDir, astGrepName);
|
|
32900
33412
|
if (existsSync5(binaryPath) && isValidBinary(binaryPath)) {
|
|
@@ -32988,7 +33500,7 @@ async function runSg(options) {
|
|
|
32988
33500
|
}
|
|
32989
33501
|
}
|
|
32990
33502
|
const timeout = DEFAULT_TIMEOUT_MS2;
|
|
32991
|
-
const proc =
|
|
33503
|
+
const proc = spawn4([cliPath, ...args], {
|
|
32992
33504
|
stdout: "pipe",
|
|
32993
33505
|
stderr: "pipe"
|
|
32994
33506
|
});
|
|
@@ -33268,7 +33780,7 @@ var ast_grep_replace = tool({
|
|
|
33268
33780
|
});
|
|
33269
33781
|
// src/tools/background.ts
|
|
33270
33782
|
var z2 = tool.schema;
|
|
33271
|
-
function createBackgroundTools(_ctx, manager,
|
|
33783
|
+
function createBackgroundTools(_ctx, manager, _multiplexerConfig, _pluginConfig) {
|
|
33272
33784
|
const agentNames = SUBAGENT_NAMES.join(", ");
|
|
33273
33785
|
const background_task = tool({
|
|
33274
33786
|
description: `Launch background agent task. Returns task_id immediately.
|
|
@@ -33438,16 +33950,16 @@ Returns the synthesized result with councillor summary.`,
|
|
|
33438
33950
|
// src/tools/lsp/client.ts
|
|
33439
33951
|
var import_node = __toESM(require_main(), 1);
|
|
33440
33952
|
import { readFileSync as readFileSync4 } from "fs";
|
|
33441
|
-
import { extname, resolve as
|
|
33953
|
+
import { extname, resolve as resolve3 } from "path";
|
|
33442
33954
|
import { Readable, Writable } from "stream";
|
|
33443
33955
|
import { pathToFileURL } from "url";
|
|
33444
|
-
var {spawn:
|
|
33956
|
+
var {spawn: spawn5 } = globalThis.Bun;
|
|
33445
33957
|
|
|
33446
33958
|
// src/tools/lsp/config.ts
|
|
33447
33959
|
var import_which = __toESM(require_lib(), 1);
|
|
33448
33960
|
import { existsSync as existsSync8 } from "fs";
|
|
33449
33961
|
import { homedir as homedir4 } from "os";
|
|
33450
|
-
import { join as join9 } from "path";
|
|
33962
|
+
import { dirname as dirname6, join as join9, resolve as resolve2 } from "path";
|
|
33451
33963
|
|
|
33452
33964
|
// src/tools/lsp/config-store.ts
|
|
33453
33965
|
var userConfig = new Map;
|
|
@@ -33478,7 +33990,7 @@ function hasUserLspConfig() {
|
|
|
33478
33990
|
|
|
33479
33991
|
// src/tools/lsp/constants.ts
|
|
33480
33992
|
import { existsSync as existsSync7, readdirSync, statSync as statSync3 } from "fs";
|
|
33481
|
-
import { dirname as
|
|
33993
|
+
import { dirname as dirname5, resolve } from "path";
|
|
33482
33994
|
var SEVERITY_MAP = {
|
|
33483
33995
|
1: "error",
|
|
33484
33996
|
2: "warning",
|
|
@@ -33498,10 +34010,10 @@ function* walkUpDirectories(start, stop) {
|
|
|
33498
34010
|
let dir = resolve(start);
|
|
33499
34011
|
try {
|
|
33500
34012
|
if (!statSync3(dir).isDirectory()) {
|
|
33501
|
-
dir =
|
|
34013
|
+
dir = dirname5(dir);
|
|
33502
34014
|
}
|
|
33503
34015
|
} catch {
|
|
33504
|
-
dir =
|
|
34016
|
+
dir = dirname5(dir);
|
|
33505
34017
|
}
|
|
33506
34018
|
let prevDir = "";
|
|
33507
34019
|
while (dir !== prevDir && dir !== "/") {
|
|
@@ -33509,7 +34021,7 @@ function* walkUpDirectories(start, stop) {
|
|
|
33509
34021
|
prevDir = dir;
|
|
33510
34022
|
if (dir === stop)
|
|
33511
34023
|
break;
|
|
33512
|
-
dir =
|
|
34024
|
+
dir = dirname5(dir);
|
|
33513
34025
|
}
|
|
33514
34026
|
}
|
|
33515
34027
|
function NearestRoot(includePatterns, excludePatterns) {
|
|
@@ -34050,39 +34562,79 @@ function buildMergedServers() {
|
|
|
34050
34562
|
}
|
|
34051
34563
|
return servers;
|
|
34052
34564
|
}
|
|
34053
|
-
function
|
|
34054
|
-
|
|
34055
|
-
|
|
34056
|
-
|
|
34057
|
-
|
|
34058
|
-
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34062
|
-
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
|
|
34066
|
-
|
|
34067
|
-
|
|
34068
|
-
|
|
34565
|
+
function getServerWorkspace(config3, filePath) {
|
|
34566
|
+
if (!filePath) {
|
|
34567
|
+
return;
|
|
34568
|
+
}
|
|
34569
|
+
if (!config3.root) {
|
|
34570
|
+
return dirname6(resolve2(filePath));
|
|
34571
|
+
}
|
|
34572
|
+
return config3.root(filePath);
|
|
34573
|
+
}
|
|
34574
|
+
function shouldSkipServer(config3, filePath) {
|
|
34575
|
+
if (!filePath) {
|
|
34576
|
+
return false;
|
|
34577
|
+
}
|
|
34578
|
+
return config3.id === "deno" && getServerWorkspace(config3, filePath) === undefined;
|
|
34579
|
+
}
|
|
34580
|
+
function toResolvedServer(config3, command) {
|
|
34581
|
+
return {
|
|
34582
|
+
id: config3.id,
|
|
34583
|
+
command: command ?? config3.command,
|
|
34584
|
+
extensions: config3.extensions,
|
|
34585
|
+
root: config3.root,
|
|
34586
|
+
env: config3.env,
|
|
34587
|
+
initialization: config3.initialization
|
|
34588
|
+
};
|
|
34589
|
+
}
|
|
34590
|
+
function findInstalledServer(configs, filePath) {
|
|
34591
|
+
let firstNotInstalled = null;
|
|
34592
|
+
for (const config3 of configs) {
|
|
34593
|
+
const workspace = getServerWorkspace(config3, filePath);
|
|
34594
|
+
const resolvedCommand = resolveServerCommand(config3.command, workspace ?? (filePath ? dirname6(resolve2(filePath)) : undefined));
|
|
34595
|
+
const server = toResolvedServer(config3, resolvedCommand ?? undefined);
|
|
34596
|
+
log(`[LSP] Considering server for ${config3.extensions.join(", ")}: ${config3.id} with command ${config3.command.join(" ")}`);
|
|
34597
|
+
if (resolvedCommand) {
|
|
34598
|
+
return { status: "found", server };
|
|
34599
|
+
}
|
|
34600
|
+
if (!firstNotInstalled) {
|
|
34601
|
+
log(`[LSP] Server ${config3.id} not found in PATH or local node_modules`);
|
|
34602
|
+
firstNotInstalled = {
|
|
34069
34603
|
status: "not_installed",
|
|
34070
34604
|
server,
|
|
34071
34605
|
installHint: LSP_INSTALL_HINTS[config3.id] || `Install '${config3.command[0]}' and add to PATH`
|
|
34072
34606
|
};
|
|
34073
34607
|
}
|
|
34074
34608
|
}
|
|
34609
|
+
return firstNotInstalled ?? undefined;
|
|
34610
|
+
}
|
|
34611
|
+
function findServerForExtension(ext, filePath) {
|
|
34612
|
+
const servers = [...buildMergedServers().values()].filter((config3) => config3.extensions.includes(ext));
|
|
34613
|
+
if (servers.length === 0) {
|
|
34614
|
+
log(`[LSP] No server config found for ${ext}`);
|
|
34615
|
+
return { status: "not_configured", extension: ext };
|
|
34616
|
+
}
|
|
34617
|
+
const candidateServers = servers.filter((config3) => !shouldSkipServer(config3, filePath));
|
|
34618
|
+
if (candidateServers.length === 0) {
|
|
34619
|
+
log(`[LSP] No applicable server config found for ${ext} at ${filePath}`);
|
|
34620
|
+
return { status: "not_configured", extension: ext };
|
|
34621
|
+
}
|
|
34622
|
+
const result = findInstalledServer(candidateServers, filePath);
|
|
34623
|
+
if (result) {
|
|
34624
|
+
return result;
|
|
34625
|
+
}
|
|
34626
|
+
log(`[LSP] No applicable server config found for ${ext}`);
|
|
34075
34627
|
return { status: "not_configured", extension: ext };
|
|
34076
34628
|
}
|
|
34077
34629
|
function getLanguageId(ext) {
|
|
34078
34630
|
return LANGUAGE_EXTENSIONS[ext] || "plaintext";
|
|
34079
34631
|
}
|
|
34080
|
-
function
|
|
34632
|
+
function resolveServerCommand(command, cwd) {
|
|
34081
34633
|
if (command.length === 0)
|
|
34082
|
-
return
|
|
34083
|
-
const cmd = command
|
|
34634
|
+
return null;
|
|
34635
|
+
const [cmd, ...args] = command;
|
|
34084
34636
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
34085
|
-
return existsSync8(cmd);
|
|
34637
|
+
return existsSync8(cmd) ? command : null;
|
|
34086
34638
|
}
|
|
34087
34639
|
const isWindows = process.platform === "win32";
|
|
34088
34640
|
const ext = isWindows ? ".exe" : "";
|
|
@@ -34094,17 +34646,94 @@ function isServerInstalled(command) {
|
|
|
34094
34646
|
nothrow: true
|
|
34095
34647
|
});
|
|
34096
34648
|
if (result !== null) {
|
|
34097
|
-
return
|
|
34649
|
+
return [result, ...args];
|
|
34098
34650
|
}
|
|
34099
|
-
const
|
|
34100
|
-
const localBin = join9(
|
|
34101
|
-
if (existsSync8(localBin)
|
|
34102
|
-
return
|
|
34651
|
+
const localBinRoot = cwd ?? process.cwd();
|
|
34652
|
+
const localBin = join9(localBinRoot, "node_modules", ".bin", cmd);
|
|
34653
|
+
if (existsSync8(localBin)) {
|
|
34654
|
+
return [localBin, ...args];
|
|
34103
34655
|
}
|
|
34104
|
-
|
|
34656
|
+
if (existsSync8(localBin + ext)) {
|
|
34657
|
+
return [localBin + ext, ...args];
|
|
34658
|
+
}
|
|
34659
|
+
return null;
|
|
34105
34660
|
}
|
|
34106
34661
|
|
|
34107
34662
|
// src/tools/lsp/client.ts
|
|
34663
|
+
var START_TIMEOUT_MS = 5000;
|
|
34664
|
+
var REQUEST_TIMEOUT_MS = 5000;
|
|
34665
|
+
var OPEN_FILE_DELAY_MS = 250;
|
|
34666
|
+
var INITIALIZE_DELAY_MS = 100;
|
|
34667
|
+
var DIAGNOSTIC_SETTLE_DELAY_MS = 250;
|
|
34668
|
+
var LSP_TIMEOUTS = {
|
|
34669
|
+
start: START_TIMEOUT_MS,
|
|
34670
|
+
request: REQUEST_TIMEOUT_MS,
|
|
34671
|
+
openFileDelay: OPEN_FILE_DELAY_MS,
|
|
34672
|
+
initializeDelay: INITIALIZE_DELAY_MS,
|
|
34673
|
+
diagnosticSettleDelay: DIAGNOSTIC_SETTLE_DELAY_MS
|
|
34674
|
+
};
|
|
34675
|
+
function getDiagnosticsCapabilitySummary({
|
|
34676
|
+
diagnosticProvider,
|
|
34677
|
+
publishDiagnosticsObserved = false,
|
|
34678
|
+
workspaceConfigurationRequested = false
|
|
34679
|
+
}) {
|
|
34680
|
+
const pull = Boolean(diagnosticProvider);
|
|
34681
|
+
const workspaceDiagnostics = Boolean(diagnosticProvider?.workspaceDiagnostics);
|
|
34682
|
+
const interFileDependencies = Boolean(diagnosticProvider?.interFileDependencies);
|
|
34683
|
+
const availableModes = [
|
|
34684
|
+
...pull ? ["pull", "pull/full", "pull/unchanged"] : ["push"],
|
|
34685
|
+
...workspaceDiagnostics ? ["workspace-pull"] : [],
|
|
34686
|
+
...publishDiagnosticsObserved ? ["push"] : []
|
|
34687
|
+
];
|
|
34688
|
+
return {
|
|
34689
|
+
availableModes: Array.from(new Set(availableModes)),
|
|
34690
|
+
preferredMode: pull ? "pull" : "push",
|
|
34691
|
+
inferredTransport: pull && publishDiagnosticsObserved ? "hybrid" : pull ? "pull" : "push",
|
|
34692
|
+
pull,
|
|
34693
|
+
pushObserved: publishDiagnosticsObserved,
|
|
34694
|
+
pullResultTracking: pull,
|
|
34695
|
+
workspaceDiagnostics,
|
|
34696
|
+
interFileDependencies,
|
|
34697
|
+
workspaceConfiguration: workspaceConfigurationRequested
|
|
34698
|
+
};
|
|
34699
|
+
}
|
|
34700
|
+
function withTimeout(promise3, ms, label, onTimeout) {
|
|
34701
|
+
return new Promise((resolve4, reject) => {
|
|
34702
|
+
let settled = false;
|
|
34703
|
+
const timer = setTimeout(() => {
|
|
34704
|
+
if (settled) {
|
|
34705
|
+
return;
|
|
34706
|
+
}
|
|
34707
|
+
settled = true;
|
|
34708
|
+
Promise.resolve(onTimeout?.()).catch(() => {});
|
|
34709
|
+
reject(new Error(`${label} timeout after ${ms}ms`));
|
|
34710
|
+
}, ms);
|
|
34711
|
+
promise3.then((value) => {
|
|
34712
|
+
if (settled) {
|
|
34713
|
+
return;
|
|
34714
|
+
}
|
|
34715
|
+
settled = true;
|
|
34716
|
+
clearTimeout(timer);
|
|
34717
|
+
resolve4(value);
|
|
34718
|
+
}, (error92) => {
|
|
34719
|
+
if (settled) {
|
|
34720
|
+
return;
|
|
34721
|
+
}
|
|
34722
|
+
settled = true;
|
|
34723
|
+
clearTimeout(timer);
|
|
34724
|
+
reject(error92);
|
|
34725
|
+
});
|
|
34726
|
+
});
|
|
34727
|
+
}
|
|
34728
|
+
function getWorkspaceConfiguration(items) {
|
|
34729
|
+
return items.map((item) => {
|
|
34730
|
+
if (item?.section === "json") {
|
|
34731
|
+
return { validate: { enable: true } };
|
|
34732
|
+
}
|
|
34733
|
+
return null;
|
|
34734
|
+
});
|
|
34735
|
+
}
|
|
34736
|
+
|
|
34108
34737
|
class LSPServerManager {
|
|
34109
34738
|
static instance;
|
|
34110
34739
|
clients = new Map;
|
|
@@ -34269,17 +34898,27 @@ class LSPClient {
|
|
|
34269
34898
|
stderrBuffer = [];
|
|
34270
34899
|
processExited = false;
|
|
34271
34900
|
diagnosticsStore = new Map;
|
|
34901
|
+
diagnosticResultIds = new Map;
|
|
34902
|
+
documents = new Map;
|
|
34903
|
+
diagnosticProvider = null;
|
|
34904
|
+
publishDiagnosticsObserved = false;
|
|
34905
|
+
supportsPullDiagnostics = false;
|
|
34906
|
+
workspaceConfigurationRequested = false;
|
|
34272
34907
|
constructor(root, server) {
|
|
34273
34908
|
this.root = root;
|
|
34274
34909
|
this.server = server;
|
|
34275
34910
|
}
|
|
34276
34911
|
async start() {
|
|
34912
|
+
const command = resolveServerCommand(this.server.command, this.root);
|
|
34913
|
+
if (!command) {
|
|
34914
|
+
throw new Error(`Failed to resolve LSP server command: ${this.server.command.join(" ")}`);
|
|
34915
|
+
}
|
|
34277
34916
|
log("[lsp] LSPClient.start: spawning server", {
|
|
34278
34917
|
server: this.server.id,
|
|
34279
|
-
command:
|
|
34918
|
+
command: command.join(" "),
|
|
34280
34919
|
root: this.root
|
|
34281
34920
|
});
|
|
34282
|
-
this.proc =
|
|
34921
|
+
this.proc = spawn5(command, {
|
|
34283
34922
|
stdin: "pipe",
|
|
34284
34923
|
stdout: "pipe",
|
|
34285
34924
|
stderr: "pipe",
|
|
@@ -34329,18 +34968,35 @@ class LSPClient {
|
|
|
34329
34968
|
});
|
|
34330
34969
|
this.connection = import_node.createMessageConnection(new import_node.StreamMessageReader(nodeReadable), new import_node.StreamMessageWriter(nodeWritable));
|
|
34331
34970
|
this.connection.onNotification("textDocument/publishDiagnostics", (params) => {
|
|
34971
|
+
if (!this.publishDiagnosticsObserved) {
|
|
34972
|
+
this.publishDiagnosticsObserved = true;
|
|
34973
|
+
log("[lsp] diagnostics capabilities: publishDiagnostics observed", {
|
|
34974
|
+
server: this.server.id,
|
|
34975
|
+
...getDiagnosticsCapabilitySummary({
|
|
34976
|
+
diagnosticProvider: this.diagnosticProvider,
|
|
34977
|
+
publishDiagnosticsObserved: this.publishDiagnosticsObserved,
|
|
34978
|
+
workspaceConfigurationRequested: this.workspaceConfigurationRequested
|
|
34979
|
+
})
|
|
34980
|
+
});
|
|
34981
|
+
}
|
|
34332
34982
|
if (params.uri) {
|
|
34333
34983
|
this.diagnosticsStore.set(params.uri, params.diagnostics ?? []);
|
|
34334
34984
|
}
|
|
34335
34985
|
});
|
|
34336
34986
|
this.connection.onRequest("workspace/configuration", (params) => {
|
|
34337
|
-
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
|
|
34341
|
-
|
|
34342
|
-
|
|
34343
|
-
|
|
34987
|
+
if (!this.workspaceConfigurationRequested) {
|
|
34988
|
+
this.workspaceConfigurationRequested = true;
|
|
34989
|
+
log("[lsp] diagnostics capabilities: workspace configuration requested", {
|
|
34990
|
+
server: this.server.id,
|
|
34991
|
+
sections: (params.items ?? []).map((item) => item && typeof item === "object" && ("section" in item) ? item.section ?? null : null),
|
|
34992
|
+
...getDiagnosticsCapabilitySummary({
|
|
34993
|
+
diagnosticProvider: this.diagnosticProvider,
|
|
34994
|
+
publishDiagnosticsObserved: this.publishDiagnosticsObserved,
|
|
34995
|
+
workspaceConfigurationRequested: this.workspaceConfigurationRequested
|
|
34996
|
+
})
|
|
34997
|
+
});
|
|
34998
|
+
}
|
|
34999
|
+
return getWorkspaceConfiguration(params.items ?? []);
|
|
34344
35000
|
});
|
|
34345
35001
|
this.connection.onRequest("client/registerCapability", () => null);
|
|
34346
35002
|
this.connection.onRequest("window/workDoneProgress/create", () => null);
|
|
@@ -34348,7 +35004,7 @@ class LSPClient {
|
|
|
34348
35004
|
this.processExited = true;
|
|
34349
35005
|
});
|
|
34350
35006
|
this.connection.listen();
|
|
34351
|
-
await new Promise((
|
|
35007
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
34352
35008
|
if (this.proc.exitCode !== null) {
|
|
34353
35009
|
const stderr = this.stderrBuffer.join(`
|
|
34354
35010
|
`);
|
|
@@ -34391,13 +35047,14 @@ stderr: ${stderr}` : ""));
|
|
|
34391
35047
|
root: this.root
|
|
34392
35048
|
});
|
|
34393
35049
|
const rootUri = pathToFileURL(this.root).href;
|
|
34394
|
-
await this.connection.sendRequest("initialize", {
|
|
35050
|
+
const result = await withTimeout(this.connection.sendRequest("initialize", {
|
|
34395
35051
|
processId: process.pid,
|
|
34396
35052
|
rootUri,
|
|
34397
35053
|
rootPath: this.root,
|
|
34398
35054
|
workspaceFolders: [{ uri: rootUri, name: "workspace" }],
|
|
34399
35055
|
capabilities: {
|
|
34400
35056
|
textDocument: {
|
|
35057
|
+
diagnostic: {},
|
|
34401
35058
|
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
34402
35059
|
definition: { linkSupport: true },
|
|
34403
35060
|
references: {},
|
|
@@ -34418,76 +35075,163 @@ stderr: ${stderr}` : ""));
|
|
|
34418
35075
|
}
|
|
34419
35076
|
},
|
|
34420
35077
|
...this.server.initialization
|
|
35078
|
+
}), LSP_TIMEOUTS.request, `LSP initialize (${this.server.id})`);
|
|
35079
|
+
const capabilities = result && typeof result === "object" && "capabilities" in result && result.capabilities && typeof result.capabilities === "object" ? result.capabilities : undefined;
|
|
35080
|
+
this.diagnosticProvider = capabilities && "diagnosticProvider" in capabilities ? capabilities.diagnosticProvider : null;
|
|
35081
|
+
this.supportsPullDiagnostics = Boolean(this.diagnosticProvider);
|
|
35082
|
+
log("[lsp] diagnostics capabilities negotiated", {
|
|
35083
|
+
server: this.server.id,
|
|
35084
|
+
diagnosticProvider: this.diagnosticProvider,
|
|
35085
|
+
...getDiagnosticsCapabilitySummary({
|
|
35086
|
+
diagnosticProvider: this.diagnosticProvider,
|
|
35087
|
+
publishDiagnosticsObserved: this.publishDiagnosticsObserved,
|
|
35088
|
+
workspaceConfigurationRequested: this.workspaceConfigurationRequested
|
|
35089
|
+
})
|
|
34421
35090
|
});
|
|
34422
|
-
this.connection.sendNotification("initialized");
|
|
34423
|
-
await new Promise((r) => setTimeout(r,
|
|
35091
|
+
this.connection.sendNotification("initialized", {});
|
|
35092
|
+
await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.initializeDelay));
|
|
34424
35093
|
log("[lsp] LSPClient.initialize: complete", { server: this.server.id });
|
|
34425
35094
|
}
|
|
34426
|
-
async
|
|
34427
|
-
const
|
|
34428
|
-
if (
|
|
34429
|
-
|
|
34430
|
-
|
|
35095
|
+
async waitForPublishedDiagnostics(uri, timeoutMs = LSP_TIMEOUTS.request) {
|
|
35096
|
+
const cachedDiagnostics = this.diagnosticsStore.get(uri);
|
|
35097
|
+
if (cachedDiagnostics) {
|
|
35098
|
+
return cachedDiagnostics;
|
|
35099
|
+
}
|
|
35100
|
+
const startedAt = Date.now();
|
|
35101
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
35102
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
35103
|
+
const diagnostics = this.diagnosticsStore.get(uri);
|
|
35104
|
+
if (diagnostics) {
|
|
35105
|
+
return diagnostics;
|
|
35106
|
+
}
|
|
34431
35107
|
}
|
|
35108
|
+
return this.diagnosticsStore.get(uri);
|
|
35109
|
+
}
|
|
35110
|
+
async openFile(filePath) {
|
|
35111
|
+
await this.ensureDocumentSynced(filePath);
|
|
35112
|
+
}
|
|
35113
|
+
async ensureDocumentSynced(filePath) {
|
|
35114
|
+
const absPath = resolve3(filePath);
|
|
35115
|
+
const uri = pathToFileURL(absPath).href;
|
|
34432
35116
|
const text = readFileSync4(absPath, "utf-8");
|
|
34433
35117
|
const ext = extname(absPath);
|
|
34434
35118
|
const languageId = getLanguageId(ext);
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
|
|
34438
|
-
|
|
34439
|
-
});
|
|
34440
|
-
this.connection?.sendNotification("textDocument/didOpen", {
|
|
34441
|
-
textDocument: {
|
|
34442
|
-
uri: pathToFileURL(absPath).href,
|
|
35119
|
+
const existing = this.documents.get(uri);
|
|
35120
|
+
if (!existing) {
|
|
35121
|
+
log("[lsp] ensureDocumentSynced: didOpen", {
|
|
35122
|
+
filePath: absPath,
|
|
34443
35123
|
languageId,
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34448
|
-
|
|
34449
|
-
|
|
35124
|
+
size: text.length
|
|
35125
|
+
});
|
|
35126
|
+
this.connection?.sendNotification("textDocument/didOpen", {
|
|
35127
|
+
textDocument: { uri, languageId, version: 1, text }
|
|
35128
|
+
});
|
|
35129
|
+
this.documents.set(uri, { version: 1, text, languageId });
|
|
35130
|
+
this.openedFiles.add(absPath);
|
|
35131
|
+
await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.openFileDelay));
|
|
35132
|
+
return;
|
|
35133
|
+
}
|
|
35134
|
+
if (existing.text !== text) {
|
|
35135
|
+
const newVersion = existing.version + 1;
|
|
35136
|
+
log("[lsp] ensureDocumentSynced: didChange", {
|
|
35137
|
+
filePath: absPath,
|
|
35138
|
+
languageId,
|
|
35139
|
+
oldVersion: existing.version,
|
|
35140
|
+
newVersion,
|
|
35141
|
+
size: text.length
|
|
35142
|
+
});
|
|
35143
|
+
this.connection?.sendNotification("textDocument/didChange", {
|
|
35144
|
+
textDocument: { uri, version: newVersion },
|
|
35145
|
+
contentChanges: [{ text }]
|
|
35146
|
+
});
|
|
35147
|
+
this.documents.set(uri, { version: newVersion, text, languageId });
|
|
35148
|
+
this.diagnosticsStore.delete(uri);
|
|
35149
|
+
this.diagnosticResultIds.delete(uri);
|
|
35150
|
+
await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.openFileDelay));
|
|
35151
|
+
} else {
|
|
35152
|
+
log("[lsp] ensureDocumentSynced: already synced", { filePath: absPath });
|
|
35153
|
+
}
|
|
34450
35154
|
}
|
|
34451
35155
|
async definition(filePath, line, character) {
|
|
34452
|
-
const absPath =
|
|
35156
|
+
const absPath = resolve3(filePath);
|
|
34453
35157
|
await this.openFile(absPath);
|
|
34454
|
-
return this.connection
|
|
35158
|
+
return this.connection ? withTimeout(this.connection.sendRequest("textDocument/definition", {
|
|
34455
35159
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
34456
35160
|
position: { line: line - 1, character }
|
|
34457
|
-
});
|
|
35161
|
+
}), LSP_TIMEOUTS.request, `LSP definition (${this.server.id})`) : undefined;
|
|
34458
35162
|
}
|
|
34459
35163
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
34460
|
-
const absPath =
|
|
35164
|
+
const absPath = resolve3(filePath);
|
|
34461
35165
|
await this.openFile(absPath);
|
|
34462
|
-
return this.connection
|
|
35166
|
+
return this.connection ? withTimeout(this.connection.sendRequest("textDocument/references", {
|
|
34463
35167
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
34464
35168
|
position: { line: line - 1, character },
|
|
34465
35169
|
context: { includeDeclaration }
|
|
34466
|
-
});
|
|
35170
|
+
}), LSP_TIMEOUTS.request, `LSP references (${this.server.id})`) : undefined;
|
|
34467
35171
|
}
|
|
34468
35172
|
async diagnostics(filePath) {
|
|
34469
|
-
const absPath =
|
|
35173
|
+
const absPath = resolve3(filePath);
|
|
34470
35174
|
const uri = pathToFileURL(absPath).href;
|
|
34471
35175
|
await this.openFile(absPath);
|
|
34472
|
-
await new Promise((r) => setTimeout(r,
|
|
34473
|
-
|
|
34474
|
-
|
|
34475
|
-
|
|
34476
|
-
|
|
34477
|
-
|
|
34478
|
-
|
|
35176
|
+
await new Promise((r) => setTimeout(r, LSP_TIMEOUTS.diagnosticSettleDelay));
|
|
35177
|
+
log("[lsp] diagnostics mode selected", {
|
|
35178
|
+
server: this.server.id,
|
|
35179
|
+
filePath: absPath,
|
|
35180
|
+
activeMode: this.supportsPullDiagnostics ? "pull" : "push",
|
|
35181
|
+
...getDiagnosticsCapabilitySummary({
|
|
35182
|
+
diagnosticProvider: this.diagnosticProvider,
|
|
35183
|
+
publishDiagnosticsObserved: this.publishDiagnosticsObserved,
|
|
35184
|
+
workspaceConfigurationRequested: this.workspaceConfigurationRequested
|
|
35185
|
+
})
|
|
35186
|
+
});
|
|
35187
|
+
if (this.supportsPullDiagnostics) {
|
|
35188
|
+
try {
|
|
35189
|
+
const result = this.connection ? await withTimeout(this.connection.sendRequest("textDocument/diagnostic", {
|
|
35190
|
+
textDocument: { uri },
|
|
35191
|
+
previousResultId: this.diagnosticResultIds.get(uri)
|
|
35192
|
+
}), LSP_TIMEOUTS.request, `LSP diagnostics (${this.server.id})`) : undefined;
|
|
35193
|
+
const report = result;
|
|
35194
|
+
if (report?.kind === "full") {
|
|
35195
|
+
if (report.resultId) {
|
|
35196
|
+
this.diagnosticResultIds.set(uri, report.resultId);
|
|
35197
|
+
} else {
|
|
35198
|
+
this.diagnosticResultIds.delete(uri);
|
|
35199
|
+
}
|
|
35200
|
+
this.diagnosticsStore.set(uri, report.items);
|
|
35201
|
+
return { items: report.items };
|
|
35202
|
+
}
|
|
35203
|
+
if (report?.kind === "unchanged") {
|
|
35204
|
+
if (report.resultId) {
|
|
35205
|
+
this.diagnosticResultIds.set(uri, report.resultId);
|
|
35206
|
+
}
|
|
35207
|
+
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
35208
|
+
}
|
|
35209
|
+
if (result && typeof result === "object" && "items" in result) {
|
|
35210
|
+
const legacyResult = result;
|
|
35211
|
+
this.diagnosticsStore.set(uri, legacyResult.items);
|
|
35212
|
+
return legacyResult;
|
|
35213
|
+
}
|
|
35214
|
+
} catch (error92) {
|
|
35215
|
+
log("[lsp] diagnostics: falling back to cached publishDiagnostics", {
|
|
35216
|
+
server: this.server.id,
|
|
35217
|
+
error: String(error92)
|
|
35218
|
+
});
|
|
34479
35219
|
}
|
|
34480
|
-
}
|
|
34481
|
-
|
|
35220
|
+
}
|
|
35221
|
+
const cachedDiagnostics = await this.waitForPublishedDiagnostics(uri);
|
|
35222
|
+
if (cachedDiagnostics) {
|
|
35223
|
+
return { items: cachedDiagnostics };
|
|
35224
|
+
}
|
|
35225
|
+
throw new Error(`Unable to retrieve diagnostics from ${this.server.id}: request timed out or is unsupported.`);
|
|
34482
35226
|
}
|
|
34483
35227
|
async rename(filePath, line, character, newName) {
|
|
34484
|
-
const absPath =
|
|
35228
|
+
const absPath = resolve3(filePath);
|
|
34485
35229
|
await this.openFile(absPath);
|
|
34486
|
-
return this.connection
|
|
35230
|
+
return this.connection ? withTimeout(this.connection.sendRequest("textDocument/rename", {
|
|
34487
35231
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
34488
35232
|
position: { line: line - 1, character },
|
|
34489
35233
|
newName
|
|
34490
|
-
});
|
|
35234
|
+
}), LSP_TIMEOUTS.request, `LSP rename (${this.server.id})`) : undefined;
|
|
34491
35235
|
}
|
|
34492
35236
|
isAlive() {
|
|
34493
35237
|
return this.proc !== null && !this.processExited && this.proc.exitCode === null;
|
|
@@ -34496,7 +35240,7 @@ stderr: ${stderr}` : ""));
|
|
|
34496
35240
|
log("[lsp] LSPClient.stop: stopping", { server: this.server.id });
|
|
34497
35241
|
try {
|
|
34498
35242
|
if (this.connection) {
|
|
34499
|
-
await this.connection.sendRequest("shutdown");
|
|
35243
|
+
await withTimeout(this.connection.sendRequest("shutdown"), 1000, `LSP shutdown (${this.server.id})`);
|
|
34500
35244
|
this.connection.sendNotification("exit");
|
|
34501
35245
|
this.connection.dispose();
|
|
34502
35246
|
}
|
|
@@ -34505,7 +35249,13 @@ stderr: ${stderr}` : ""));
|
|
|
34505
35249
|
this.proc = null;
|
|
34506
35250
|
this.connection = null;
|
|
34507
35251
|
this.processExited = true;
|
|
35252
|
+
this.diagnosticProvider = null;
|
|
35253
|
+
this.publishDiagnosticsObserved = false;
|
|
35254
|
+
this.supportsPullDiagnostics = false;
|
|
35255
|
+
this.workspaceConfigurationRequested = false;
|
|
34508
35256
|
this.diagnosticsStore.clear();
|
|
35257
|
+
this.diagnosticResultIds.clear();
|
|
35258
|
+
this.documents.clear();
|
|
34509
35259
|
log("[lsp] LSPClient.stop: complete", { server: this.server.id });
|
|
34510
35260
|
}
|
|
34511
35261
|
}
|
|
@@ -34517,13 +35267,13 @@ import {
|
|
|
34517
35267
|
unlinkSync as unlinkSync2,
|
|
34518
35268
|
writeFileSync as writeFileSync3
|
|
34519
35269
|
} from "fs";
|
|
34520
|
-
import { dirname as
|
|
35270
|
+
import { dirname as dirname7, extname as extname2, join as join10, resolve as resolve4 } from "path";
|
|
34521
35271
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
34522
35272
|
function findServerProjectRoot(filePath, server) {
|
|
34523
35273
|
if (server.root) {
|
|
34524
|
-
return server.root(filePath) ??
|
|
35274
|
+
return server.root(filePath) ?? dirname7(resolve4(filePath));
|
|
34525
35275
|
}
|
|
34526
|
-
return
|
|
35276
|
+
return dirname7(resolve4(filePath));
|
|
34527
35277
|
}
|
|
34528
35278
|
function uriToPath(uri) {
|
|
34529
35279
|
return fileURLToPath2(uri);
|
|
@@ -34542,9 +35292,9 @@ function formatServerLookupError(result) {
|
|
|
34542
35292
|
return `No LSP server configured for extension: ${result.extension}`;
|
|
34543
35293
|
}
|
|
34544
35294
|
async function withLspClient(filePath, fn) {
|
|
34545
|
-
const absPath =
|
|
35295
|
+
const absPath = resolve4(filePath);
|
|
34546
35296
|
const ext = extname2(absPath);
|
|
34547
|
-
const result = findServerForExtension(ext);
|
|
35297
|
+
const result = findServerForExtension(ext, absPath);
|
|
34548
35298
|
if (result.status !== "found") {
|
|
34549
35299
|
log("[lsp] withLspClient: server not found", {
|
|
34550
35300
|
filePath: absPath,
|
|
@@ -34553,7 +35303,14 @@ async function withLspClient(filePath, fn) {
|
|
|
34553
35303
|
throw new Error(formatServerLookupError(result));
|
|
34554
35304
|
}
|
|
34555
35305
|
const server = result.server;
|
|
34556
|
-
const root = findServerProjectRoot(absPath, server) ??
|
|
35306
|
+
const root = findServerProjectRoot(absPath, server) ?? dirname7(absPath);
|
|
35307
|
+
log("[lsp] withLspClient: selected server", {
|
|
35308
|
+
filePath: absPath,
|
|
35309
|
+
extension: ext,
|
|
35310
|
+
server: server.id,
|
|
35311
|
+
command: server.command.join(" "),
|
|
35312
|
+
root
|
|
35313
|
+
});
|
|
34557
35314
|
log("[lsp] withLspClient: acquiring client", {
|
|
34558
35315
|
filePath: absPath,
|
|
34559
35316
|
server: server.id,
|
|
@@ -34913,29 +35670,32 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
34913
35670
|
runtimeChains[agentName] = existing;
|
|
34914
35671
|
}
|
|
34915
35672
|
}
|
|
34916
|
-
const
|
|
34917
|
-
|
|
34918
|
-
layout: config3.
|
|
34919
|
-
main_pane_size: config3.
|
|
35673
|
+
const multiplexerConfig = {
|
|
35674
|
+
type: config3.multiplexer?.type ?? "none",
|
|
35675
|
+
layout: config3.multiplexer?.layout ?? "main-vertical",
|
|
35676
|
+
main_pane_size: config3.multiplexer?.main_pane_size ?? 60
|
|
34920
35677
|
};
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
35678
|
+
const multiplexer = getMultiplexer(multiplexerConfig);
|
|
35679
|
+
const multiplexerEnabled = multiplexerConfig.type !== "none" && multiplexer !== null;
|
|
35680
|
+
log("[plugin] initialized with multiplexer config", {
|
|
35681
|
+
multiplexerConfig,
|
|
35682
|
+
enabled: multiplexerEnabled,
|
|
34924
35683
|
directory: ctx.directory
|
|
34925
35684
|
});
|
|
34926
|
-
if (
|
|
34927
|
-
|
|
35685
|
+
if (multiplexerEnabled) {
|
|
35686
|
+
startAvailabilityCheck(multiplexerConfig);
|
|
34928
35687
|
}
|
|
34929
|
-
const backgroundManager = new BackgroundTaskManager(ctx,
|
|
34930
|
-
const backgroundTools = createBackgroundTools(ctx, backgroundManager,
|
|
34931
|
-
const councilTools = config3.council ? createCouncilTool(ctx, new CouncilManager(ctx, config3, backgroundManager.getDepthTracker(),
|
|
34932
|
-
const mcps = createBuiltinMcps(config3.disabled_mcps);
|
|
34933
|
-
const
|
|
35688
|
+
const backgroundManager = new BackgroundTaskManager(ctx, multiplexerConfig, config3);
|
|
35689
|
+
const backgroundTools = createBackgroundTools(ctx, backgroundManager, multiplexerConfig, config3);
|
|
35690
|
+
const councilTools = config3.council ? createCouncilTool(ctx, new CouncilManager(ctx, config3, backgroundManager.getDepthTracker(), multiplexerEnabled)) : {};
|
|
35691
|
+
const mcps = createBuiltinMcps(config3.disabled_mcps, config3.websearch);
|
|
35692
|
+
const multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig);
|
|
34934
35693
|
const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
34935
35694
|
showStartupToast: true,
|
|
34936
35695
|
autoUpdate: true
|
|
34937
35696
|
});
|
|
34938
35697
|
const phaseReminderHook = createPhaseReminderHook();
|
|
35698
|
+
const filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config3);
|
|
34939
35699
|
const postFileToolNudgeHook = createPostFileToolNudgeHook();
|
|
34940
35700
|
const chatHeadersHook = createChatHeadersHook(ctx);
|
|
34941
35701
|
const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
|
|
@@ -35026,7 +35786,8 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
35026
35786
|
} else {
|
|
35027
35787
|
Object.assign(configMcp, mcps);
|
|
35028
35788
|
}
|
|
35029
|
-
const
|
|
35789
|
+
const mergedMcpConfig = opencodeConfig.mcp;
|
|
35790
|
+
const allMcpNames = Object.keys(mergedMcpConfig ?? mcps);
|
|
35030
35791
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
35031
35792
|
const agentMcps = agentConfig?.mcps;
|
|
35032
35793
|
if (!agentMcps)
|
|
@@ -35051,14 +35812,18 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
35051
35812
|
event: async (input) => {
|
|
35052
35813
|
await foregroundFallback.handleEvent(input.event);
|
|
35053
35814
|
await autoUpdateChecker.event(input);
|
|
35054
|
-
await
|
|
35815
|
+
await multiplexerSessionManager.onSessionCreated(input.event);
|
|
35055
35816
|
await backgroundManager.handleSessionStatus(input.event);
|
|
35056
|
-
await
|
|
35817
|
+
await multiplexerSessionManager.onSessionStatus(input.event);
|
|
35057
35818
|
await backgroundManager.handleSessionDeleted(input.event);
|
|
35058
|
-
await
|
|
35819
|
+
await multiplexerSessionManager.onSessionDeleted(input.event);
|
|
35059
35820
|
},
|
|
35060
35821
|
"chat.headers": chatHeadersHook["chat.headers"],
|
|
35061
|
-
"experimental.chat.messages.transform":
|
|
35822
|
+
"experimental.chat.messages.transform": async (input, output) => {
|
|
35823
|
+
const typedOutput = output;
|
|
35824
|
+
await phaseReminderHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
35825
|
+
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
35826
|
+
},
|
|
35062
35827
|
"tool.execute.after": async (input, output) => {
|
|
35063
35828
|
await delegateTaskRetryHook["tool.execute.after"](input, output);
|
|
35064
35829
|
await jsonErrorRecoveryHook["tool.execute.after"](input, output);
|