oh-my-opencode 3.0.0-beta.7 → 3.0.0-beta.8
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 +7 -5
- package/README.zh-cn.md +5 -0
- package/bin/oh-my-opencode.js +80 -0
- package/bin/platform.js +38 -0
- package/bin/platform.test.ts +148 -0
- package/dist/agents/sisyphus-junior.d.ts +1 -1
- package/dist/cli/config-manager.d.ts +9 -1
- package/dist/cli/index.js +172 -119
- package/dist/config/schema.d.ts +2 -0
- package/dist/features/background-agent/manager.d.ts +5 -0
- package/dist/features/hook-message-injector/index.d.ts +1 -1
- package/dist/features/opencode-skill-loader/skill-content.d.ts +10 -0
- package/dist/features/skill-mcp-manager/manager.d.ts +10 -0
- package/dist/features/task-toast-manager/index.d.ts +1 -1
- package/dist/features/task-toast-manager/manager.d.ts +2 -1
- package/dist/features/task-toast-manager/types.d.ts +5 -0
- package/dist/hooks/comment-checker/cli.d.ts +0 -1
- package/dist/hooks/comment-checker/cli.test.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/sisyphus-task-retry/index.d.ts +24 -0
- package/dist/hooks/sisyphus-task-retry/index.test.d.ts +1 -0
- package/dist/index.js +2300 -413
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/session-cursor.d.ts +13 -0
- package/dist/shared/session-cursor.test.d.ts +1 -0
- package/dist/shared/shell-env.d.ts +41 -0
- package/dist/shared/shell-env.test.d.ts +1 -0
- package/dist/tools/look-at/tools.d.ts +7 -0
- package/dist/tools/look-at/tools.test.d.ts +1 -0
- package/dist/tools/lsp/client.d.ts +0 -7
- package/dist/tools/lsp/constants.d.ts +0 -3
- package/dist/tools/lsp/tools.d.ts +0 -3
- package/dist/tools/lsp/types.d.ts +0 -56
- package/dist/tools/lsp/utils.d.ts +1 -8
- package/package.json +18 -3
- package/postinstall.mjs +43 -0
package/dist/index.js
CHANGED
|
@@ -4245,7 +4245,8 @@ var init_migration = __esm(() => {
|
|
|
4245
4245
|
explore: "explore",
|
|
4246
4246
|
"frontend-ui-ux-engineer": "frontend-ui-ux-engineer",
|
|
4247
4247
|
"document-writer": "document-writer",
|
|
4248
|
-
"multimodal-looker": "multimodal-looker"
|
|
4248
|
+
"multimodal-looker": "multimodal-looker",
|
|
4249
|
+
"orchestrator-sisyphus": "orchestrator-sisyphus"
|
|
4249
4250
|
};
|
|
4250
4251
|
BUILTIN_AGENT_NAMES = new Set([
|
|
4251
4252
|
"Sisyphus",
|
|
@@ -4629,6 +4630,115 @@ function applyAgentVariant(config, agentName, message) {
|
|
|
4629
4630
|
}
|
|
4630
4631
|
}
|
|
4631
4632
|
|
|
4633
|
+
// src/shared/session-cursor.ts
|
|
4634
|
+
function buildMessageKey(message, index) {
|
|
4635
|
+
const id = message.info?.id;
|
|
4636
|
+
if (id)
|
|
4637
|
+
return `id:${id}`;
|
|
4638
|
+
const time = message.info?.time;
|
|
4639
|
+
if (typeof time === "number" || typeof time === "string") {
|
|
4640
|
+
return `t:${time}:${index}`;
|
|
4641
|
+
}
|
|
4642
|
+
const created = time?.created;
|
|
4643
|
+
if (typeof created === "number") {
|
|
4644
|
+
return `t:${created}:${index}`;
|
|
4645
|
+
}
|
|
4646
|
+
if (typeof created === "string") {
|
|
4647
|
+
return `t:${created}:${index}`;
|
|
4648
|
+
}
|
|
4649
|
+
return `i:${index}`;
|
|
4650
|
+
}
|
|
4651
|
+
function consumeNewMessages(sessionID, messages) {
|
|
4652
|
+
if (!sessionID)
|
|
4653
|
+
return messages;
|
|
4654
|
+
const keys = messages.map((message, index) => buildMessageKey(message, index));
|
|
4655
|
+
const cursor = sessionCursors.get(sessionID);
|
|
4656
|
+
let startIndex = 0;
|
|
4657
|
+
if (cursor) {
|
|
4658
|
+
if (cursor.lastCount > messages.length) {
|
|
4659
|
+
startIndex = 0;
|
|
4660
|
+
} else if (cursor.lastKey) {
|
|
4661
|
+
const lastIndex = keys.lastIndexOf(cursor.lastKey);
|
|
4662
|
+
if (lastIndex >= 0) {
|
|
4663
|
+
startIndex = lastIndex + 1;
|
|
4664
|
+
} else {
|
|
4665
|
+
startIndex = 0;
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
if (messages.length === 0) {
|
|
4670
|
+
sessionCursors.delete(sessionID);
|
|
4671
|
+
} else {
|
|
4672
|
+
sessionCursors.set(sessionID, {
|
|
4673
|
+
lastKey: keys[keys.length - 1],
|
|
4674
|
+
lastCount: messages.length
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
return messages.slice(startIndex);
|
|
4678
|
+
}
|
|
4679
|
+
function resetMessageCursor(sessionID) {
|
|
4680
|
+
if (sessionID) {
|
|
4681
|
+
sessionCursors.delete(sessionID);
|
|
4682
|
+
return;
|
|
4683
|
+
}
|
|
4684
|
+
sessionCursors.clear();
|
|
4685
|
+
}
|
|
4686
|
+
var sessionCursors;
|
|
4687
|
+
var init_session_cursor = __esm(() => {
|
|
4688
|
+
sessionCursors = new Map;
|
|
4689
|
+
});
|
|
4690
|
+
|
|
4691
|
+
// src/shared/shell-env.ts
|
|
4692
|
+
function detectShellType() {
|
|
4693
|
+
if (process.env.PSModulePath) {
|
|
4694
|
+
return "powershell";
|
|
4695
|
+
}
|
|
4696
|
+
if (process.env.SHELL) {
|
|
4697
|
+
return "unix";
|
|
4698
|
+
}
|
|
4699
|
+
return process.platform === "win32" ? "cmd" : "unix";
|
|
4700
|
+
}
|
|
4701
|
+
function shellEscape(value, shellType) {
|
|
4702
|
+
if (value === "") {
|
|
4703
|
+
return shellType === "cmd" ? '""' : "''";
|
|
4704
|
+
}
|
|
4705
|
+
switch (shellType) {
|
|
4706
|
+
case "unix":
|
|
4707
|
+
if (/[^a-zA-Z0-9_\-.:\/]/.test(value)) {
|
|
4708
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
4709
|
+
}
|
|
4710
|
+
return value;
|
|
4711
|
+
case "powershell":
|
|
4712
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
4713
|
+
case "cmd":
|
|
4714
|
+
return `"${value.replace(/%/g, "%%").replace(/"/g, '""')}"`;
|
|
4715
|
+
default:
|
|
4716
|
+
return value;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
function buildEnvPrefix(env, shellType) {
|
|
4720
|
+
const entries = Object.entries(env);
|
|
4721
|
+
if (entries.length === 0) {
|
|
4722
|
+
return "";
|
|
4723
|
+
}
|
|
4724
|
+
switch (shellType) {
|
|
4725
|
+
case "unix": {
|
|
4726
|
+
const assignments = entries.map(([key, value]) => `${key}=${shellEscape(value, shellType)}`).join(" ");
|
|
4727
|
+
return `export ${assignments};`;
|
|
4728
|
+
}
|
|
4729
|
+
case "powershell": {
|
|
4730
|
+
const assignments = entries.map(([key, value]) => `$env:${key}=${shellEscape(value, shellType)}`).join("; ");
|
|
4731
|
+
return `${assignments};`;
|
|
4732
|
+
}
|
|
4733
|
+
case "cmd": {
|
|
4734
|
+
const assignments = entries.map(([key, value]) => `set ${key}=${shellEscape(value, shellType)}`).join(" && ");
|
|
4735
|
+
return `${assignments} &&`;
|
|
4736
|
+
}
|
|
4737
|
+
default:
|
|
4738
|
+
return "";
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4632
4742
|
// src/shared/index.ts
|
|
4633
4743
|
var init_shared = __esm(() => {
|
|
4634
4744
|
init_frontmatter();
|
|
@@ -4651,6 +4761,7 @@ var init_shared = __esm(() => {
|
|
|
4651
4761
|
init_permission_compat();
|
|
4652
4762
|
init_external_plugin_detector();
|
|
4653
4763
|
init_zip_extractor();
|
|
4764
|
+
init_session_cursor();
|
|
4654
4765
|
});
|
|
4655
4766
|
|
|
4656
4767
|
// node_modules/picomatch/lib/constants.js
|
|
@@ -14097,7 +14208,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
14097
14208
|
}
|
|
14098
14209
|
}).catch(() => {});
|
|
14099
14210
|
}
|
|
14100
|
-
async function injectContinuation(sessionID, incompleteCount, total) {
|
|
14211
|
+
async function injectContinuation(sessionID, incompleteCount, total, resolvedInfo) {
|
|
14101
14212
|
const state2 = sessions.get(sessionID);
|
|
14102
14213
|
if (state2?.isRecovering) {
|
|
14103
14214
|
log(`[${HOOK_NAME}] Skipped injection: in recovery`, { sessionID });
|
|
@@ -14121,29 +14232,37 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
14121
14232
|
log(`[${HOOK_NAME}] Skipped injection: no incomplete todos`, { sessionID });
|
|
14122
14233
|
return;
|
|
14123
14234
|
}
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14235
|
+
let agentName = resolvedInfo?.agent;
|
|
14236
|
+
let model = resolvedInfo?.model;
|
|
14237
|
+
let tools = resolvedInfo?.tools;
|
|
14238
|
+
if (!agentName || !model) {
|
|
14239
|
+
const messageDir = getMessageDir(sessionID);
|
|
14240
|
+
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
14241
|
+
agentName = agentName ?? prevMessage?.agent;
|
|
14242
|
+
model = model ?? (prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined);
|
|
14243
|
+
tools = tools ?? prevMessage?.tools;
|
|
14244
|
+
}
|
|
14127
14245
|
if (agentName && skipAgents.includes(agentName)) {
|
|
14128
14246
|
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
14129
14247
|
return;
|
|
14130
14248
|
}
|
|
14131
|
-
const editPermission =
|
|
14132
|
-
const writePermission =
|
|
14133
|
-
const hasWritePermission = !
|
|
14249
|
+
const editPermission = tools?.edit;
|
|
14250
|
+
const writePermission = tools?.write;
|
|
14251
|
+
const hasWritePermission = !tools || editPermission !== false && editPermission !== "deny" && (writePermission !== false && writePermission !== "deny");
|
|
14134
14252
|
if (!hasWritePermission) {
|
|
14135
|
-
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent:
|
|
14253
|
+
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
14136
14254
|
return;
|
|
14137
14255
|
}
|
|
14138
14256
|
const prompt = `${CONTINUATION_PROMPT}
|
|
14139
14257
|
|
|
14140
14258
|
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]`;
|
|
14141
14259
|
try {
|
|
14142
|
-
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent:
|
|
14260
|
+
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: agentName, model, incompleteCount: freshIncompleteCount });
|
|
14143
14261
|
await ctx.client.session.prompt({
|
|
14144
14262
|
path: { id: sessionID },
|
|
14145
14263
|
body: {
|
|
14146
|
-
agent:
|
|
14264
|
+
agent: agentName,
|
|
14265
|
+
...model !== undefined ? { model } : {},
|
|
14147
14266
|
parts: [{ type: "text", text: prompt }]
|
|
14148
14267
|
},
|
|
14149
14268
|
query: { directory: ctx.directory }
|
|
@@ -14153,7 +14272,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
14153
14272
|
log(`[${HOOK_NAME}] Injection failed`, { sessionID, error: String(err) });
|
|
14154
14273
|
}
|
|
14155
14274
|
}
|
|
14156
|
-
function startCountdown(sessionID, incompleteCount, total) {
|
|
14275
|
+
function startCountdown(sessionID, incompleteCount, total, resolvedInfo) {
|
|
14157
14276
|
const state2 = getState(sessionID);
|
|
14158
14277
|
cancelCountdown(sessionID);
|
|
14159
14278
|
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
@@ -14167,7 +14286,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
14167
14286
|
}, 1000);
|
|
14168
14287
|
state2.countdownTimer = setTimeout(() => {
|
|
14169
14288
|
cancelCountdown(sessionID);
|
|
14170
|
-
injectContinuation(sessionID, incompleteCount, total);
|
|
14289
|
+
injectContinuation(sessionID, incompleteCount, total, resolvedInfo);
|
|
14171
14290
|
}, COUNTDOWN_SECONDS * 1000);
|
|
14172
14291
|
log(`[${HOOK_NAME}] Countdown started`, { sessionID, seconds: COUNTDOWN_SECONDS, incompleteCount });
|
|
14173
14292
|
}
|
|
@@ -14249,27 +14368,32 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
14249
14368
|
log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
|
|
14250
14369
|
return;
|
|
14251
14370
|
}
|
|
14252
|
-
let
|
|
14371
|
+
let resolvedInfo;
|
|
14253
14372
|
try {
|
|
14254
14373
|
const messagesResp = await ctx.client.session.messages({
|
|
14255
14374
|
path: { id: sessionID }
|
|
14256
14375
|
});
|
|
14257
14376
|
const messages = messagesResp.data ?? [];
|
|
14258
14377
|
for (let i = messages.length - 1;i >= 0; i--) {
|
|
14259
|
-
|
|
14260
|
-
|
|
14378
|
+
const info = messages[i].info;
|
|
14379
|
+
if (info?.agent || info?.model) {
|
|
14380
|
+
resolvedInfo = {
|
|
14381
|
+
agent: info.agent,
|
|
14382
|
+
model: info.model,
|
|
14383
|
+
tools: info.tools
|
|
14384
|
+
};
|
|
14261
14385
|
break;
|
|
14262
14386
|
}
|
|
14263
14387
|
}
|
|
14264
14388
|
} catch (err) {
|
|
14265
14389
|
log(`[${HOOK_NAME}] Failed to fetch messages for agent check`, { sessionID, error: String(err) });
|
|
14266
14390
|
}
|
|
14267
|
-
log(`[${HOOK_NAME}] Agent check`, { sessionID, agentName, skipAgents });
|
|
14268
|
-
if (
|
|
14269
|
-
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent:
|
|
14391
|
+
log(`[${HOOK_NAME}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents });
|
|
14392
|
+
if (resolvedInfo?.agent && skipAgents.includes(resolvedInfo.agent)) {
|
|
14393
|
+
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedInfo.agent });
|
|
14270
14394
|
return;
|
|
14271
14395
|
}
|
|
14272
|
-
startCountdown(sessionID, incompleteCount, todos.length);
|
|
14396
|
+
startCountdown(sessionID, incompleteCount, todos.length, resolvedInfo);
|
|
14273
14397
|
return;
|
|
14274
14398
|
}
|
|
14275
14399
|
if (event.type === "message.updated") {
|
|
@@ -15510,6 +15634,9 @@ async function getCommentCheckerPath() {
|
|
|
15510
15634
|
})();
|
|
15511
15635
|
return initPromise;
|
|
15512
15636
|
}
|
|
15637
|
+
function getCommentCheckerPathSync() {
|
|
15638
|
+
return resolvedCliPath ?? findCommentCheckerPathSync();
|
|
15639
|
+
}
|
|
15513
15640
|
function startBackgroundInit() {
|
|
15514
15641
|
if (!initPromise) {
|
|
15515
15642
|
initPromise = getCommentCheckerPath();
|
|
@@ -15520,9 +15647,8 @@ function startBackgroundInit() {
|
|
|
15520
15647
|
});
|
|
15521
15648
|
}
|
|
15522
15649
|
}
|
|
15523
|
-
var COMMENT_CHECKER_CLI_PATH = findCommentCheckerPathSync();
|
|
15524
15650
|
async function runCommentChecker(input, cliPath, customPrompt) {
|
|
15525
|
-
const binaryPath = cliPath ?? resolvedCliPath ??
|
|
15651
|
+
const binaryPath = cliPath ?? resolvedCliPath ?? getCommentCheckerPathSync();
|
|
15526
15652
|
if (!binaryPath) {
|
|
15527
15653
|
debugLog2("comment-checker binary not found");
|
|
15528
15654
|
return { hasComments: false, message: "" };
|
|
@@ -15698,8 +15824,6 @@ var TRUNCATABLE_TOOLS = [
|
|
|
15698
15824
|
"glob",
|
|
15699
15825
|
"Glob",
|
|
15700
15826
|
"safe_glob",
|
|
15701
|
-
"lsp_find_references",
|
|
15702
|
-
"lsp_symbols",
|
|
15703
15827
|
"lsp_diagnostics",
|
|
15704
15828
|
"ast_grep_search",
|
|
15705
15829
|
"interactive_bash",
|
|
@@ -19901,7 +20025,7 @@ THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTIN
|
|
|
19901
20025
|
}
|
|
19902
20026
|
var KEYWORD_DETECTORS = [
|
|
19903
20027
|
{
|
|
19904
|
-
pattern:
|
|
20028
|
+
pattern: /\b(ultrawork|ulw)\b/i,
|
|
19905
20029
|
message: getUltraworkMessage
|
|
19906
20030
|
},
|
|
19907
20031
|
{
|
|
@@ -20067,6 +20191,23 @@ EOF`,
|
|
|
20067
20191
|
}
|
|
20068
20192
|
};
|
|
20069
20193
|
|
|
20194
|
+
// src/hooks/non-interactive-env/detector.ts
|
|
20195
|
+
function isNonInteractive() {
|
|
20196
|
+
if (process.env.CI === "true" || process.env.CI === "1") {
|
|
20197
|
+
return true;
|
|
20198
|
+
}
|
|
20199
|
+
if (process.env.OPENCODE_RUN === "true" || process.env.OPENCODE_NON_INTERACTIVE === "true") {
|
|
20200
|
+
return true;
|
|
20201
|
+
}
|
|
20202
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
20203
|
+
return true;
|
|
20204
|
+
}
|
|
20205
|
+
if (process.stdout.isTTY !== true) {
|
|
20206
|
+
return true;
|
|
20207
|
+
}
|
|
20208
|
+
return false;
|
|
20209
|
+
}
|
|
20210
|
+
|
|
20070
20211
|
// src/hooks/non-interactive-env/index.ts
|
|
20071
20212
|
init_shared();
|
|
20072
20213
|
var BANNED_COMMAND_PATTERNS = SHELL_COMMAND_PATTERNS.banned.filter((cmd) => !cmd.includes("(")).map((cmd) => new RegExp(`\\b${cmd}\\b`));
|
|
@@ -20078,18 +20219,6 @@ function detectBannedCommand(command) {
|
|
|
20078
20219
|
}
|
|
20079
20220
|
return;
|
|
20080
20221
|
}
|
|
20081
|
-
function shellEscape(value) {
|
|
20082
|
-
if (value === "")
|
|
20083
|
-
return "''";
|
|
20084
|
-
if (/[^a-zA-Z0-9_\-.:\/]/.test(value)) {
|
|
20085
|
-
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
20086
|
-
}
|
|
20087
|
-
return value;
|
|
20088
|
-
}
|
|
20089
|
-
function buildEnvPrefix(env) {
|
|
20090
|
-
const exports = Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" ");
|
|
20091
|
-
return `export ${exports};`;
|
|
20092
|
-
}
|
|
20093
20222
|
function createNonInteractiveEnvHook(_ctx) {
|
|
20094
20223
|
return {
|
|
20095
20224
|
"tool.execute.before": async (input, output) => {
|
|
@@ -20108,7 +20237,11 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
20108
20237
|
if (!isGitCommand) {
|
|
20109
20238
|
return;
|
|
20110
20239
|
}
|
|
20111
|
-
|
|
20240
|
+
if (!isNonInteractive()) {
|
|
20241
|
+
return;
|
|
20242
|
+
}
|
|
20243
|
+
const shellType = detectShellType();
|
|
20244
|
+
const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, shellType);
|
|
20112
20245
|
output.args.command = `${envPrefix} ${command}`;
|
|
20113
20246
|
log(`[${HOOK_NAME2}] Prepended non-interactive env vars to git command`, {
|
|
20114
20247
|
sessionID: input.sessionID,
|
|
@@ -20819,10 +20952,25 @@ function createRalphLoopHook(ctx, options) {
|
|
|
20819
20952
|
}
|
|
20820
20953
|
}).catch(() => {});
|
|
20821
20954
|
try {
|
|
20822
|
-
|
|
20823
|
-
|
|
20824
|
-
|
|
20825
|
-
|
|
20955
|
+
let agent;
|
|
20956
|
+
let model;
|
|
20957
|
+
try {
|
|
20958
|
+
const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } });
|
|
20959
|
+
const messages = messagesResp.data ?? [];
|
|
20960
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
20961
|
+
const info = messages[i2].info;
|
|
20962
|
+
if (info?.agent || info?.model) {
|
|
20963
|
+
agent = info.agent;
|
|
20964
|
+
model = info.model;
|
|
20965
|
+
break;
|
|
20966
|
+
}
|
|
20967
|
+
}
|
|
20968
|
+
} catch {
|
|
20969
|
+
const messageDir = getMessageDir9(sessionID);
|
|
20970
|
+
const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
20971
|
+
agent = currentMessage?.agent;
|
|
20972
|
+
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
|
|
20973
|
+
}
|
|
20826
20974
|
await ctx.client.session.prompt({
|
|
20827
20975
|
path: { id: sessionID },
|
|
20828
20976
|
body: {
|
|
@@ -20935,7 +21083,7 @@ function extractPromptText3(parts) {
|
|
|
20935
21083
|
// src/hooks/auto-slash-command/executor.ts
|
|
20936
21084
|
init_shared();
|
|
20937
21085
|
init_file_utils();
|
|
20938
|
-
import { existsSync as existsSync36, readdirSync as readdirSync12, readFileSync as
|
|
21086
|
+
import { existsSync as existsSync36, readdirSync as readdirSync12, readFileSync as readFileSync25 } from "fs";
|
|
20939
21087
|
import { join as join45, basename as basename2, dirname as dirname8 } from "path";
|
|
20940
21088
|
import { homedir as homedir14 } from "os";
|
|
20941
21089
|
// src/features/opencode-skill-loader/loader.ts
|
|
@@ -22573,6 +22721,46 @@ function createBuiltinSkills() {
|
|
|
22573
22721
|
}
|
|
22574
22722
|
|
|
22575
22723
|
// src/features/opencode-skill-loader/skill-content.ts
|
|
22724
|
+
init_frontmatter();
|
|
22725
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
22726
|
+
var cachedSkills = null;
|
|
22727
|
+
async function getAllSkills() {
|
|
22728
|
+
if (cachedSkills)
|
|
22729
|
+
return cachedSkills;
|
|
22730
|
+
const [discoveredSkills, builtinSkillDefs] = await Promise.all([
|
|
22731
|
+
discoverSkills({ includeClaudeCodePaths: true }),
|
|
22732
|
+
Promise.resolve(createBuiltinSkills())
|
|
22733
|
+
]);
|
|
22734
|
+
const builtinSkillsAsLoaded = builtinSkillDefs.map((skill) => ({
|
|
22735
|
+
name: skill.name,
|
|
22736
|
+
definition: {
|
|
22737
|
+
name: skill.name,
|
|
22738
|
+
description: skill.description,
|
|
22739
|
+
template: skill.template,
|
|
22740
|
+
model: skill.model,
|
|
22741
|
+
agent: skill.agent,
|
|
22742
|
+
subtask: skill.subtask
|
|
22743
|
+
},
|
|
22744
|
+
scope: "builtin",
|
|
22745
|
+
license: skill.license,
|
|
22746
|
+
compatibility: skill.compatibility,
|
|
22747
|
+
metadata: skill.metadata,
|
|
22748
|
+
allowedTools: skill.allowedTools,
|
|
22749
|
+
mcpConfig: skill.mcpConfig
|
|
22750
|
+
}));
|
|
22751
|
+
const discoveredNames = new Set(discoveredSkills.map((s) => s.name));
|
|
22752
|
+
const uniqueBuiltins = builtinSkillsAsLoaded.filter((s) => !discoveredNames.has(s.name));
|
|
22753
|
+
cachedSkills = [...discoveredSkills, ...uniqueBuiltins];
|
|
22754
|
+
return cachedSkills;
|
|
22755
|
+
}
|
|
22756
|
+
async function extractSkillTemplate(skill) {
|
|
22757
|
+
if (skill.path) {
|
|
22758
|
+
const content = readFileSync24(skill.path, "utf-8");
|
|
22759
|
+
const { body } = parseFrontmatter(content);
|
|
22760
|
+
return body.trim();
|
|
22761
|
+
}
|
|
22762
|
+
return skill.definition.template || "";
|
|
22763
|
+
}
|
|
22576
22764
|
function injectGitMasterConfig(template, config) {
|
|
22577
22765
|
if (!config)
|
|
22578
22766
|
return template;
|
|
@@ -22608,6 +22796,29 @@ function resolveMultipleSkills(skillNames, options) {
|
|
|
22608
22796
|
}
|
|
22609
22797
|
return { resolved, notFound };
|
|
22610
22798
|
}
|
|
22799
|
+
async function resolveMultipleSkillsAsync(skillNames, options) {
|
|
22800
|
+
const allSkills = await getAllSkills();
|
|
22801
|
+
const skillMap = new Map;
|
|
22802
|
+
for (const skill of allSkills) {
|
|
22803
|
+
skillMap.set(skill.name, skill);
|
|
22804
|
+
}
|
|
22805
|
+
const resolved = new Map;
|
|
22806
|
+
const notFound = [];
|
|
22807
|
+
for (const name of skillNames) {
|
|
22808
|
+
const skill = skillMap.get(name);
|
|
22809
|
+
if (skill) {
|
|
22810
|
+
const template = await extractSkillTemplate(skill);
|
|
22811
|
+
if (name === "git-master" && options?.gitMasterConfig) {
|
|
22812
|
+
resolved.set(name, injectGitMasterConfig(template, options.gitMasterConfig));
|
|
22813
|
+
} else {
|
|
22814
|
+
resolved.set(name, template);
|
|
22815
|
+
}
|
|
22816
|
+
} else {
|
|
22817
|
+
notFound.push(name);
|
|
22818
|
+
}
|
|
22819
|
+
}
|
|
22820
|
+
return { resolved, notFound };
|
|
22821
|
+
}
|
|
22611
22822
|
// src/hooks/auto-slash-command/executor.ts
|
|
22612
22823
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
22613
22824
|
if (!existsSync36(commandsDir)) {
|
|
@@ -22621,7 +22832,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
22621
22832
|
const commandPath = join45(commandsDir, entry.name);
|
|
22622
22833
|
const commandName = basename2(entry.name, ".md");
|
|
22623
22834
|
try {
|
|
22624
|
-
const content =
|
|
22835
|
+
const content = readFileSync25(commandPath, "utf-8");
|
|
22625
22836
|
const { data, body } = parseFrontmatter(content);
|
|
22626
22837
|
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
22627
22838
|
const metadata = {
|
|
@@ -23001,7 +23212,7 @@ var NOTEPAD_DIR = "notepads";
|
|
|
23001
23212
|
var NOTEPAD_BASE_PATH = `${BOULDER_DIR}/${NOTEPAD_DIR}`;
|
|
23002
23213
|
var PROMETHEUS_PLANS_DIR = ".sisyphus/plans";
|
|
23003
23214
|
// src/features/boulder-state/storage.ts
|
|
23004
|
-
import { existsSync as existsSync38, readFileSync as
|
|
23215
|
+
import { existsSync as existsSync38, readFileSync as readFileSync26, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync14 } from "fs";
|
|
23005
23216
|
import { dirname as dirname9, join as join47, basename as basename3 } from "path";
|
|
23006
23217
|
function getBoulderFilePath(directory) {
|
|
23007
23218
|
return join47(directory, BOULDER_DIR, BOULDER_FILE);
|
|
@@ -23012,7 +23223,7 @@ function readBoulderState(directory) {
|
|
|
23012
23223
|
return null;
|
|
23013
23224
|
}
|
|
23014
23225
|
try {
|
|
23015
|
-
const content =
|
|
23226
|
+
const content = readFileSync26(filePath, "utf-8");
|
|
23016
23227
|
return JSON.parse(content);
|
|
23017
23228
|
} catch {
|
|
23018
23229
|
return null;
|
|
@@ -23043,6 +23254,18 @@ function appendSessionId(directory, sessionId) {
|
|
|
23043
23254
|
}
|
|
23044
23255
|
return state2;
|
|
23045
23256
|
}
|
|
23257
|
+
function clearBoulderState(directory) {
|
|
23258
|
+
const filePath = getBoulderFilePath(directory);
|
|
23259
|
+
try {
|
|
23260
|
+
if (existsSync38(filePath)) {
|
|
23261
|
+
const { unlinkSync: unlinkSync10 } = __require("fs");
|
|
23262
|
+
unlinkSync10(filePath);
|
|
23263
|
+
}
|
|
23264
|
+
return true;
|
|
23265
|
+
} catch {
|
|
23266
|
+
return false;
|
|
23267
|
+
}
|
|
23268
|
+
}
|
|
23046
23269
|
function findPrometheusPlans(directory) {
|
|
23047
23270
|
const plansDir = join47(directory, PROMETHEUS_PLANS_DIR);
|
|
23048
23271
|
if (!existsSync38(plansDir)) {
|
|
@@ -23064,7 +23287,7 @@ function getPlanProgress(planPath) {
|
|
|
23064
23287
|
return { total: 0, completed: 0, isComplete: true };
|
|
23065
23288
|
}
|
|
23066
23289
|
try {
|
|
23067
|
-
const content =
|
|
23290
|
+
const content = readFileSync26(planPath, "utf-8");
|
|
23068
23291
|
const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [];
|
|
23069
23292
|
const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [];
|
|
23070
23293
|
const total = uncheckedMatches.length + checkedMatches.length;
|
|
@@ -23092,6 +23315,25 @@ function createBoulderState(planPath, sessionId) {
|
|
|
23092
23315
|
// src/hooks/start-work/index.ts
|
|
23093
23316
|
init_logger();
|
|
23094
23317
|
var HOOK_NAME5 = "start-work";
|
|
23318
|
+
var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
|
|
23319
|
+
function extractUserRequestPlanName(promptText) {
|
|
23320
|
+
const userRequestMatch = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
|
|
23321
|
+
if (!userRequestMatch)
|
|
23322
|
+
return null;
|
|
23323
|
+
const rawArg = userRequestMatch[1].trim();
|
|
23324
|
+
if (!rawArg)
|
|
23325
|
+
return null;
|
|
23326
|
+
const cleanedArg = rawArg.replace(KEYWORD_PATTERN, "").trim();
|
|
23327
|
+
return cleanedArg || null;
|
|
23328
|
+
}
|
|
23329
|
+
function findPlanByName(plans, requestedName) {
|
|
23330
|
+
const lowerName = requestedName.toLowerCase();
|
|
23331
|
+
const exactMatch = plans.find((p) => getPlanName(p).toLowerCase() === lowerName);
|
|
23332
|
+
if (exactMatch)
|
|
23333
|
+
return exactMatch;
|
|
23334
|
+
const partialMatch = plans.find((p) => getPlanName(p).toLowerCase().includes(lowerName));
|
|
23335
|
+
return partialMatch || null;
|
|
23336
|
+
}
|
|
23095
23337
|
function createStartWorkHook(ctx) {
|
|
23096
23338
|
return {
|
|
23097
23339
|
"chat.message": async (input, output) => {
|
|
@@ -23109,7 +23351,64 @@ function createStartWorkHook(ctx) {
|
|
|
23109
23351
|
const sessionId = input.sessionID;
|
|
23110
23352
|
const timestamp2 = new Date().toISOString();
|
|
23111
23353
|
let contextInfo = "";
|
|
23112
|
-
|
|
23354
|
+
const explicitPlanName = extractUserRequestPlanName(promptText);
|
|
23355
|
+
if (explicitPlanName) {
|
|
23356
|
+
log(`[${HOOK_NAME5}] Explicit plan name requested: ${explicitPlanName}`, {
|
|
23357
|
+
sessionID: input.sessionID
|
|
23358
|
+
});
|
|
23359
|
+
const allPlans = findPrometheusPlans(ctx.directory);
|
|
23360
|
+
const matchedPlan = findPlanByName(allPlans, explicitPlanName);
|
|
23361
|
+
if (matchedPlan) {
|
|
23362
|
+
const progress = getPlanProgress(matchedPlan);
|
|
23363
|
+
if (progress.isComplete) {
|
|
23364
|
+
contextInfo = `
|
|
23365
|
+
## Plan Already Complete
|
|
23366
|
+
|
|
23367
|
+
The requested plan "${getPlanName(matchedPlan)}" has been completed.
|
|
23368
|
+
All ${progress.total} tasks are done. Create a new plan with: /plan "your task"`;
|
|
23369
|
+
} else {
|
|
23370
|
+
if (existingState) {
|
|
23371
|
+
clearBoulderState(ctx.directory);
|
|
23372
|
+
}
|
|
23373
|
+
const newState = createBoulderState(matchedPlan, sessionId);
|
|
23374
|
+
writeBoulderState(ctx.directory, newState);
|
|
23375
|
+
contextInfo = `
|
|
23376
|
+
## Auto-Selected Plan
|
|
23377
|
+
|
|
23378
|
+
**Plan**: ${getPlanName(matchedPlan)}
|
|
23379
|
+
**Path**: ${matchedPlan}
|
|
23380
|
+
**Progress**: ${progress.completed}/${progress.total} tasks
|
|
23381
|
+
**Session ID**: ${sessionId}
|
|
23382
|
+
**Started**: ${timestamp2}
|
|
23383
|
+
|
|
23384
|
+
boulder.json has been created. Read the plan and begin execution.`;
|
|
23385
|
+
}
|
|
23386
|
+
} else {
|
|
23387
|
+
const incompletePlans = allPlans.filter((p) => !getPlanProgress(p).isComplete);
|
|
23388
|
+
if (incompletePlans.length > 0) {
|
|
23389
|
+
const planList = incompletePlans.map((p, i2) => {
|
|
23390
|
+
const prog = getPlanProgress(p);
|
|
23391
|
+
return `${i2 + 1}. [${getPlanName(p)}] - Progress: ${prog.completed}/${prog.total}`;
|
|
23392
|
+
}).join(`
|
|
23393
|
+
`);
|
|
23394
|
+
contextInfo = `
|
|
23395
|
+
## Plan Not Found
|
|
23396
|
+
|
|
23397
|
+
Could not find a plan matching "${explicitPlanName}".
|
|
23398
|
+
|
|
23399
|
+
Available incomplete plans:
|
|
23400
|
+
${planList}
|
|
23401
|
+
|
|
23402
|
+
Ask the user which plan to work on.`;
|
|
23403
|
+
} else {
|
|
23404
|
+
contextInfo = `
|
|
23405
|
+
## Plan Not Found
|
|
23406
|
+
|
|
23407
|
+
Could not find a plan matching "${explicitPlanName}".
|
|
23408
|
+
No incomplete plans available. Create a new plan with: /plan "your task"`;
|
|
23409
|
+
}
|
|
23410
|
+
}
|
|
23411
|
+
} else if (existingState) {
|
|
23113
23412
|
const progress = getPlanProgress(existingState.active_plan);
|
|
23114
23413
|
if (!progress.isComplete) {
|
|
23115
23414
|
appendSessionId(ctx.directory, sessionId);
|
|
@@ -23133,7 +23432,7 @@ The previous plan (${existingState.plan_name}) has been completed.
|
|
|
23133
23432
|
Looking for new plans...`;
|
|
23134
23433
|
}
|
|
23135
23434
|
}
|
|
23136
|
-
if (!existingState || getPlanProgress(existingState.active_plan).isComplete) {
|
|
23435
|
+
if (!existingState && !explicitPlanName || existingState && !explicitPlanName && getPlanProgress(existingState.active_plan).isComplete) {
|
|
23137
23436
|
const plans = findPrometheusPlans(ctx.directory);
|
|
23138
23437
|
const incompletePlans = plans.filter((p) => !getPlanProgress(p).isComplete);
|
|
23139
23438
|
if (plans.length === 0) {
|
|
@@ -23249,34 +23548,45 @@ RULES:
|
|
|
23249
23548
|
- Use the notepad at .sisyphus/notepads/{PLAN_NAME}/ to record learnings
|
|
23250
23549
|
- Do not stop until all tasks are complete
|
|
23251
23550
|
- If blocked, document the blocker and move to the next task`;
|
|
23252
|
-
var VERIFICATION_REMINDER = `**MANDATORY
|
|
23551
|
+
var VERIFICATION_REMINDER = `**MANDATORY: WHAT YOU MUST DO RIGHT NOW**
|
|
23552
|
+
|
|
23553
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
23253
23554
|
|
|
23254
|
-
Subagents FREQUENTLY
|
|
23255
|
-
-
|
|
23256
|
-
- Code has type/lint ERRORS
|
|
23257
|
-
- Implementation is INCOMPLETE
|
|
23258
|
-
- Patterns were NOT followed
|
|
23555
|
+
\u26A0\uFE0F CRITICAL: Subagents FREQUENTLY LIE about completion.
|
|
23556
|
+
Tests FAILING, code has ERRORS, implementation INCOMPLETE - but they say "done".
|
|
23259
23557
|
|
|
23260
|
-
|
|
23558
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
23261
23559
|
|
|
23262
|
-
1
|
|
23263
|
-
2. Run tests yourself - Must PASS (not "agent said it passed")
|
|
23264
|
-
3. Read the actual code - Must match requirements
|
|
23265
|
-
4. Check build/typecheck - Must succeed
|
|
23560
|
+
**STEP 1: VERIFY WITH YOUR OWN TOOL CALLS (DO THIS NOW)**
|
|
23266
23561
|
|
|
23267
|
-
|
|
23268
|
-
|
|
23562
|
+
Run these commands YOURSELF - do NOT trust agent's claims:
|
|
23563
|
+
1. \`lsp_diagnostics\` on changed files \u2192 Must be CLEAN
|
|
23564
|
+
2. \`bash\` to run tests \u2192 Must PASS
|
|
23565
|
+
3. \`bash\` to run build/typecheck \u2192 Must succeed
|
|
23566
|
+
4. \`Read\` the actual code \u2192 Must match requirements
|
|
23269
23567
|
|
|
23270
|
-
**HANDS-ON QA
|
|
23568
|
+
**STEP 2: DETERMINE IF HANDS-ON QA IS NEEDED**
|
|
23271
23569
|
|
|
23272
|
-
| Deliverable Type |
|
|
23273
|
-
|
|
23274
|
-
| **Frontend/UI** | \`/playwright\` skill |
|
|
23275
|
-
| **TUI/CLI** | \`interactive_bash\` (tmux) |
|
|
23276
|
-
| **API/Backend** | \`bash\` with curl |
|
|
23570
|
+
| Deliverable Type | QA Method | Tool |
|
|
23571
|
+
|------------------|-----------|------|
|
|
23572
|
+
| **Frontend/UI** | Browser interaction | \`/playwright\` skill |
|
|
23573
|
+
| **TUI/CLI** | Run interactively | \`interactive_bash\` (tmux) |
|
|
23574
|
+
| **API/Backend** | Send real requests | \`bash\` with curl |
|
|
23277
23575
|
|
|
23278
|
-
Static analysis CANNOT catch: visual bugs, animation issues, user flow breakages
|
|
23279
|
-
|
|
23576
|
+
Static analysis CANNOT catch: visual bugs, animation issues, user flow breakages.
|
|
23577
|
+
|
|
23578
|
+
**STEP 3: IF QA IS NEEDED - ADD TO TODO IMMEDIATELY**
|
|
23579
|
+
|
|
23580
|
+
\`\`\`
|
|
23581
|
+
todowrite([
|
|
23582
|
+
{ id: "qa-X", content: "HANDS-ON QA: [specific verification action]", status: "pending", priority: "high" }
|
|
23583
|
+
])
|
|
23584
|
+
\`\`\`
|
|
23585
|
+
|
|
23586
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
23587
|
+
|
|
23588
|
+
**BLOCKING: DO NOT proceed to next task until Steps 1-3 are complete.**
|
|
23589
|
+
**FAILURE TO DO QA = INCOMPLETE WORK = USER WILL REJECT.**`;
|
|
23280
23590
|
var ORCHESTRATOR_DELEGATION_REQUIRED = `
|
|
23281
23591
|
|
|
23282
23592
|
---
|
|
@@ -23365,19 +23675,37 @@ function buildOrchestratorReminder(planName, progress, sessionId) {
|
|
|
23365
23675
|
return `
|
|
23366
23676
|
---
|
|
23367
23677
|
|
|
23368
|
-
**
|
|
23678
|
+
**BOULDER STATE:** Plan: \`${planName}\` | \u2705 ${progress.completed}/${progress.total} done | \u23F3 ${remaining} remaining
|
|
23369
23679
|
|
|
23370
23680
|
---
|
|
23371
23681
|
|
|
23372
23682
|
${buildVerificationReminder(sessionId)}
|
|
23373
23683
|
|
|
23374
|
-
|
|
23684
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
23685
|
+
|
|
23686
|
+
**AFTER VERIFICATION PASSES - YOUR NEXT ACTIONS (IN ORDER):**
|
|
23687
|
+
|
|
23688
|
+
1. **COMMIT** atomic unit (only verified changes)
|
|
23689
|
+
2. **MARK** \`[x]\` in plan file for completed task
|
|
23690
|
+
3. **PROCEED** to next task immediately
|
|
23691
|
+
|
|
23692
|
+
**DO NOT STOP. ${remaining} tasks remain. Keep bouldering.**`;
|
|
23375
23693
|
}
|
|
23376
23694
|
function buildStandaloneVerificationReminder(sessionId) {
|
|
23377
23695
|
return `
|
|
23378
23696
|
---
|
|
23379
23697
|
|
|
23380
|
-
${buildVerificationReminder(sessionId)}
|
|
23698
|
+
${buildVerificationReminder(sessionId)}
|
|
23699
|
+
|
|
23700
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
23701
|
+
|
|
23702
|
+
**AFTER VERIFICATION - CHECK YOUR TODO LIST:**
|
|
23703
|
+
|
|
23704
|
+
1. Run \`todoread\` to see remaining tasks
|
|
23705
|
+
2. If QA tasks exist \u2192 execute them BEFORE marking complete
|
|
23706
|
+
3. Mark completed tasks \u2192 proceed to next pending task
|
|
23707
|
+
|
|
23708
|
+
**NO TODO = NO TRACKING = INCOMPLETE WORK. Use todowrite aggressively.**`;
|
|
23381
23709
|
}
|
|
23382
23710
|
function extractSessionIdFromOutput(output) {
|
|
23383
23711
|
const match = output.match(/Session ID:\s*(ses_[a-zA-Z0-9]+)/);
|
|
@@ -23539,9 +23867,22 @@ function createSisyphusOrchestratorHook(ctx, options) {
|
|
|
23539
23867
|
[Status: ${total - remaining}/${total} completed, ${remaining} remaining]`;
|
|
23540
23868
|
try {
|
|
23541
23869
|
log(`[${HOOK_NAME6}] Injecting boulder continuation`, { sessionID, planName, remaining });
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23870
|
+
let model;
|
|
23871
|
+
try {
|
|
23872
|
+
const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } });
|
|
23873
|
+
const messages = messagesResp.data ?? [];
|
|
23874
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
23875
|
+
const msgModel = messages[i2].info?.model;
|
|
23876
|
+
if (msgModel?.providerID && msgModel?.modelID) {
|
|
23877
|
+
model = { providerID: msgModel.providerID, modelID: msgModel.modelID };
|
|
23878
|
+
break;
|
|
23879
|
+
}
|
|
23880
|
+
}
|
|
23881
|
+
} catch {
|
|
23882
|
+
const messageDir = getMessageDir11(sessionID);
|
|
23883
|
+
const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
23884
|
+
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
|
|
23885
|
+
}
|
|
23545
23886
|
await ctx.client.session.prompt({
|
|
23546
23887
|
path: { id: sessionID },
|
|
23547
23888
|
body: {
|
|
@@ -23762,6 +24103,118 @@ ${buildStandaloneVerificationReminder(subagentSessionId)}
|
|
|
23762
24103
|
}
|
|
23763
24104
|
};
|
|
23764
24105
|
}
|
|
24106
|
+
// src/hooks/sisyphus-task-retry/index.ts
|
|
24107
|
+
var SISYPHUS_TASK_ERROR_PATTERNS = [
|
|
24108
|
+
{
|
|
24109
|
+
pattern: "run_in_background",
|
|
24110
|
+
errorType: "missing_run_in_background",
|
|
24111
|
+
fixHint: "Add run_in_background=false (for delegation) or run_in_background=true (for parallel exploration)"
|
|
24112
|
+
},
|
|
24113
|
+
{
|
|
24114
|
+
pattern: "skills",
|
|
24115
|
+
errorType: "missing_skills",
|
|
24116
|
+
fixHint: "Add skills=[] parameter (empty array if no skills needed)"
|
|
24117
|
+
},
|
|
24118
|
+
{
|
|
24119
|
+
pattern: "category OR subagent_type",
|
|
24120
|
+
errorType: "mutual_exclusion",
|
|
24121
|
+
fixHint: "Provide ONLY one of: category (e.g., 'general', 'quick') OR subagent_type (e.g., 'oracle', 'explore')"
|
|
24122
|
+
},
|
|
24123
|
+
{
|
|
24124
|
+
pattern: "Must provide either category or subagent_type",
|
|
24125
|
+
errorType: "missing_category_or_agent",
|
|
24126
|
+
fixHint: "Add either category='general' OR subagent_type='explore'"
|
|
24127
|
+
},
|
|
24128
|
+
{
|
|
24129
|
+
pattern: "Unknown category",
|
|
24130
|
+
errorType: "unknown_category",
|
|
24131
|
+
fixHint: "Use a valid category from the Available list in the error message"
|
|
24132
|
+
},
|
|
24133
|
+
{
|
|
24134
|
+
pattern: "Agent name cannot be empty",
|
|
24135
|
+
errorType: "empty_agent",
|
|
24136
|
+
fixHint: "Provide a non-empty subagent_type value"
|
|
24137
|
+
},
|
|
24138
|
+
{
|
|
24139
|
+
pattern: "Unknown agent",
|
|
24140
|
+
errorType: "unknown_agent",
|
|
24141
|
+
fixHint: "Use a valid agent from the Available agents list in the error message"
|
|
24142
|
+
},
|
|
24143
|
+
{
|
|
24144
|
+
pattern: "Cannot call primary agent",
|
|
24145
|
+
errorType: "primary_agent",
|
|
24146
|
+
fixHint: "Primary agents cannot be called via sisyphus_task. Use a subagent like 'explore', 'oracle', or 'librarian'"
|
|
24147
|
+
},
|
|
24148
|
+
{
|
|
24149
|
+
pattern: "Skills not found",
|
|
24150
|
+
errorType: "unknown_skills",
|
|
24151
|
+
fixHint: "Use valid skill names from the Available list in the error message"
|
|
24152
|
+
}
|
|
24153
|
+
];
|
|
24154
|
+
function detectSisyphusTaskError(output) {
|
|
24155
|
+
if (!output.includes("\u274C"))
|
|
24156
|
+
return null;
|
|
24157
|
+
for (const errorPattern of SISYPHUS_TASK_ERROR_PATTERNS) {
|
|
24158
|
+
if (output.includes(errorPattern.pattern)) {
|
|
24159
|
+
return {
|
|
24160
|
+
errorType: errorPattern.errorType,
|
|
24161
|
+
originalOutput: output
|
|
24162
|
+
};
|
|
24163
|
+
}
|
|
24164
|
+
}
|
|
24165
|
+
return null;
|
|
24166
|
+
}
|
|
24167
|
+
function extractAvailableList(output) {
|
|
24168
|
+
const availableMatch = output.match(/Available[^:]*:\s*(.+)$/m);
|
|
24169
|
+
return availableMatch ? availableMatch[1].trim() : null;
|
|
24170
|
+
}
|
|
24171
|
+
function buildRetryGuidance(errorInfo) {
|
|
24172
|
+
const pattern = SISYPHUS_TASK_ERROR_PATTERNS.find((p) => p.errorType === errorInfo.errorType);
|
|
24173
|
+
if (!pattern) {
|
|
24174
|
+
return `[sisyphus_task ERROR] Fix the error and retry with correct parameters.`;
|
|
24175
|
+
}
|
|
24176
|
+
let guidance = `
|
|
24177
|
+
[sisyphus_task CALL FAILED - IMMEDIATE RETRY REQUIRED]
|
|
24178
|
+
|
|
24179
|
+
**Error Type**: ${errorInfo.errorType}
|
|
24180
|
+
**Fix**: ${pattern.fixHint}
|
|
24181
|
+
`;
|
|
24182
|
+
const availableList = extractAvailableList(errorInfo.originalOutput);
|
|
24183
|
+
if (availableList) {
|
|
24184
|
+
guidance += `
|
|
24185
|
+
**Available Options**: ${availableList}
|
|
24186
|
+
`;
|
|
24187
|
+
}
|
|
24188
|
+
guidance += `
|
|
24189
|
+
**Action**: Retry sisyphus_task NOW with corrected parameters.
|
|
24190
|
+
|
|
24191
|
+
Example of CORRECT call:
|
|
24192
|
+
\`\`\`
|
|
24193
|
+
sisyphus_task(
|
|
24194
|
+
description="Task description",
|
|
24195
|
+
prompt="Detailed prompt...",
|
|
24196
|
+
category="general", // OR subagent_type="explore"
|
|
24197
|
+
run_in_background=false,
|
|
24198
|
+
skills=[]
|
|
24199
|
+
)
|
|
24200
|
+
\`\`\`
|
|
24201
|
+
`;
|
|
24202
|
+
return guidance;
|
|
24203
|
+
}
|
|
24204
|
+
function createSisyphusTaskRetryHook(_ctx) {
|
|
24205
|
+
return {
|
|
24206
|
+
"tool.execute.after": async (input, output) => {
|
|
24207
|
+
if (input.tool.toLowerCase() !== "sisyphus_task")
|
|
24208
|
+
return;
|
|
24209
|
+
const errorInfo = detectSisyphusTaskError(output.output);
|
|
24210
|
+
if (errorInfo) {
|
|
24211
|
+
const guidance = buildRetryGuidance(errorInfo);
|
|
24212
|
+
output.output += `
|
|
24213
|
+
${guidance}`;
|
|
24214
|
+
}
|
|
24215
|
+
}
|
|
24216
|
+
};
|
|
24217
|
+
}
|
|
23765
24218
|
// src/features/context-injector/collector.ts
|
|
23766
24219
|
var PRIORITY_ORDER = {
|
|
23767
24220
|
critical: 0,
|
|
@@ -23962,7 +24415,7 @@ function createFirstMessageVariantGate() {
|
|
|
23962
24415
|
}
|
|
23963
24416
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
23964
24417
|
init_shared();
|
|
23965
|
-
import { existsSync as existsSync40, readFileSync as
|
|
24418
|
+
import { existsSync as existsSync40, readFileSync as readFileSync27 } from "fs";
|
|
23966
24419
|
import { join as join49 } from "path";
|
|
23967
24420
|
|
|
23968
24421
|
// src/features/claude-code-mcp-loader/env-expander.ts
|
|
@@ -24057,7 +24510,7 @@ function getSystemMcpServerNames() {
|
|
|
24057
24510
|
if (!existsSync40(path8))
|
|
24058
24511
|
continue;
|
|
24059
24512
|
try {
|
|
24060
|
-
const content =
|
|
24513
|
+
const content = readFileSync27(path8, "utf-8");
|
|
24061
24514
|
const config = JSON.parse(content);
|
|
24062
24515
|
if (!config?.mcpServers)
|
|
24063
24516
|
continue;
|
|
@@ -24102,42 +24555,12 @@ async function loadMcpConfigs() {
|
|
|
24102
24555
|
return { servers, loadedServers };
|
|
24103
24556
|
}
|
|
24104
24557
|
// src/tools/lsp/constants.ts
|
|
24105
|
-
var SYMBOL_KIND_MAP = {
|
|
24106
|
-
1: "File",
|
|
24107
|
-
2: "Module",
|
|
24108
|
-
3: "Namespace",
|
|
24109
|
-
4: "Package",
|
|
24110
|
-
5: "Class",
|
|
24111
|
-
6: "Method",
|
|
24112
|
-
7: "Property",
|
|
24113
|
-
8: "Field",
|
|
24114
|
-
9: "Constructor",
|
|
24115
|
-
10: "Enum",
|
|
24116
|
-
11: "Interface",
|
|
24117
|
-
12: "Function",
|
|
24118
|
-
13: "Variable",
|
|
24119
|
-
14: "Constant",
|
|
24120
|
-
15: "String",
|
|
24121
|
-
16: "Number",
|
|
24122
|
-
17: "Boolean",
|
|
24123
|
-
18: "Array",
|
|
24124
|
-
19: "Object",
|
|
24125
|
-
20: "Key",
|
|
24126
|
-
21: "Null",
|
|
24127
|
-
22: "EnumMember",
|
|
24128
|
-
23: "Struct",
|
|
24129
|
-
24: "Event",
|
|
24130
|
-
25: "Operator",
|
|
24131
|
-
26: "TypeParameter"
|
|
24132
|
-
};
|
|
24133
24558
|
var SEVERITY_MAP = {
|
|
24134
24559
|
1: "error",
|
|
24135
24560
|
2: "warning",
|
|
24136
24561
|
3: "information",
|
|
24137
24562
|
4: "hint"
|
|
24138
24563
|
};
|
|
24139
|
-
var DEFAULT_MAX_REFERENCES = 200;
|
|
24140
|
-
var DEFAULT_MAX_SYMBOLS = 200;
|
|
24141
24564
|
var DEFAULT_MAX_DIAGNOSTICS = 200;
|
|
24142
24565
|
var LSP_INSTALL_HINTS = {
|
|
24143
24566
|
typescript: "npm install -g typescript-language-server typescript",
|
|
@@ -24178,7 +24601,8 @@ var LSP_INSTALL_HINTS = {
|
|
|
24178
24601
|
nixd: "nix profile install nixpkgs#nixd",
|
|
24179
24602
|
tinymist: "See https://github.com/Myriad-Dreamin/tinymist",
|
|
24180
24603
|
"haskell-language-server": "ghcup install hls",
|
|
24181
|
-
bash: "npm install -g bash-language-server"
|
|
24604
|
+
bash: "npm install -g bash-language-server",
|
|
24605
|
+
"kotlin-ls": "See https://github.com/Kotlin/kotlin-lsp"
|
|
24182
24606
|
};
|
|
24183
24607
|
var BUILTIN_SERVERS = {
|
|
24184
24608
|
typescript: {
|
|
@@ -24354,6 +24778,10 @@ var BUILTIN_SERVERS = {
|
|
|
24354
24778
|
"haskell-language-server": {
|
|
24355
24779
|
command: ["haskell-language-server-wrapper", "--lsp"],
|
|
24356
24780
|
extensions: [".hs", ".lhs"]
|
|
24781
|
+
},
|
|
24782
|
+
"kotlin-ls": {
|
|
24783
|
+
command: ["kotlin-lsp"],
|
|
24784
|
+
extensions: [".kt", ".kts"]
|
|
24357
24785
|
}
|
|
24358
24786
|
};
|
|
24359
24787
|
var EXT_TO_LANG = {
|
|
@@ -24489,14 +24917,14 @@ var EXT_TO_LANG = {
|
|
|
24489
24917
|
".gql": "graphql"
|
|
24490
24918
|
};
|
|
24491
24919
|
// src/tools/lsp/config.ts
|
|
24492
|
-
import { existsSync as existsSync41, readFileSync as
|
|
24920
|
+
import { existsSync as existsSync41, readFileSync as readFileSync28 } from "fs";
|
|
24493
24921
|
import { join as join50 } from "path";
|
|
24494
24922
|
import { homedir as homedir15 } from "os";
|
|
24495
24923
|
function loadJsonFile(path8) {
|
|
24496
24924
|
if (!existsSync41(path8))
|
|
24497
24925
|
return null;
|
|
24498
24926
|
try {
|
|
24499
|
-
return JSON.parse(
|
|
24927
|
+
return JSON.parse(readFileSync28(path8, "utf-8"));
|
|
24500
24928
|
} catch {
|
|
24501
24929
|
return null;
|
|
24502
24930
|
}
|
|
@@ -24695,7 +25123,7 @@ function getAllServers() {
|
|
|
24695
25123
|
}
|
|
24696
25124
|
// src/tools/lsp/client.ts
|
|
24697
25125
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
24698
|
-
import { readFileSync as
|
|
25126
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
24699
25127
|
import { extname, resolve as resolve8 } from "path";
|
|
24700
25128
|
import { pathToFileURL } from "url";
|
|
24701
25129
|
class LSPServerManager {
|
|
@@ -25145,7 +25573,7 @@ ${msg}`);
|
|
|
25145
25573
|
const absPath = resolve8(filePath);
|
|
25146
25574
|
if (this.openedFiles.has(absPath))
|
|
25147
25575
|
return;
|
|
25148
|
-
const text =
|
|
25576
|
+
const text = readFileSync29(absPath, "utf-8");
|
|
25149
25577
|
const ext = extname(absPath);
|
|
25150
25578
|
const languageId = getLanguageId(ext);
|
|
25151
25579
|
this.notify("textDocument/didOpen", {
|
|
@@ -25159,41 +25587,6 @@ ${msg}`);
|
|
|
25159
25587
|
this.openedFiles.add(absPath);
|
|
25160
25588
|
await new Promise((r) => setTimeout(r, 1000));
|
|
25161
25589
|
}
|
|
25162
|
-
async hover(filePath, line, character) {
|
|
25163
|
-
const absPath = resolve8(filePath);
|
|
25164
|
-
await this.openFile(absPath);
|
|
25165
|
-
return this.send("textDocument/hover", {
|
|
25166
|
-
textDocument: { uri: pathToFileURL(absPath).href },
|
|
25167
|
-
position: { line: line - 1, character }
|
|
25168
|
-
});
|
|
25169
|
-
}
|
|
25170
|
-
async definition(filePath, line, character) {
|
|
25171
|
-
const absPath = resolve8(filePath);
|
|
25172
|
-
await this.openFile(absPath);
|
|
25173
|
-
return this.send("textDocument/definition", {
|
|
25174
|
-
textDocument: { uri: pathToFileURL(absPath).href },
|
|
25175
|
-
position: { line: line - 1, character }
|
|
25176
|
-
});
|
|
25177
|
-
}
|
|
25178
|
-
async references(filePath, line, character, includeDeclaration = true) {
|
|
25179
|
-
const absPath = resolve8(filePath);
|
|
25180
|
-
await this.openFile(absPath);
|
|
25181
|
-
return this.send("textDocument/references", {
|
|
25182
|
-
textDocument: { uri: pathToFileURL(absPath).href },
|
|
25183
|
-
position: { line: line - 1, character },
|
|
25184
|
-
context: { includeDeclaration }
|
|
25185
|
-
});
|
|
25186
|
-
}
|
|
25187
|
-
async documentSymbols(filePath) {
|
|
25188
|
-
const absPath = resolve8(filePath);
|
|
25189
|
-
await this.openFile(absPath);
|
|
25190
|
-
return this.send("textDocument/documentSymbol", {
|
|
25191
|
-
textDocument: { uri: pathToFileURL(absPath).href }
|
|
25192
|
-
});
|
|
25193
|
-
}
|
|
25194
|
-
async workspaceSymbols(query) {
|
|
25195
|
-
return this.send("workspace/symbol", { query });
|
|
25196
|
-
}
|
|
25197
25590
|
async diagnostics(filePath) {
|
|
25198
25591
|
const absPath = resolve8(filePath);
|
|
25199
25592
|
const uri = pathToFileURL(absPath).href;
|
|
@@ -25226,24 +25619,6 @@ ${msg}`);
|
|
|
25226
25619
|
newName
|
|
25227
25620
|
});
|
|
25228
25621
|
}
|
|
25229
|
-
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
25230
|
-
const absPath = resolve8(filePath);
|
|
25231
|
-
await this.openFile(absPath);
|
|
25232
|
-
return this.send("textDocument/codeAction", {
|
|
25233
|
-
textDocument: { uri: pathToFileURL(absPath).href },
|
|
25234
|
-
range: {
|
|
25235
|
-
start: { line: startLine - 1, character: startChar },
|
|
25236
|
-
end: { line: endLine - 1, character: endChar }
|
|
25237
|
-
},
|
|
25238
|
-
context: {
|
|
25239
|
-
diagnostics: [],
|
|
25240
|
-
only
|
|
25241
|
-
}
|
|
25242
|
-
});
|
|
25243
|
-
}
|
|
25244
|
-
async codeActionResolve(codeAction) {
|
|
25245
|
-
return this.send("codeAction/resolve", codeAction);
|
|
25246
|
-
}
|
|
25247
25622
|
isAlive() {
|
|
25248
25623
|
return this.proc !== null && !this.processExited && this.proc.exitCode === null;
|
|
25249
25624
|
}
|
|
@@ -25261,7 +25636,7 @@ ${msg}`);
|
|
|
25261
25636
|
// src/tools/lsp/utils.ts
|
|
25262
25637
|
import { extname as extname2, resolve as resolve9 } from "path";
|
|
25263
25638
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
25264
|
-
import { existsSync as existsSync42, readFileSync as
|
|
25639
|
+
import { existsSync as existsSync42, readFileSync as readFileSync30, writeFileSync as writeFileSync16 } from "fs";
|
|
25265
25640
|
function findWorkspaceRoot(filePath) {
|
|
25266
25641
|
let dir = resolve9(filePath);
|
|
25267
25642
|
if (!existsSync42(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
@@ -25341,45 +25716,11 @@ async function withLspClient(filePath, fn) {
|
|
|
25341
25716
|
lspManager.releaseClient(root, server.id);
|
|
25342
25717
|
}
|
|
25343
25718
|
}
|
|
25344
|
-
function formatLocation(loc) {
|
|
25345
|
-
if ("targetUri" in loc) {
|
|
25346
|
-
const uri2 = uriToPath(loc.targetUri);
|
|
25347
|
-
const line2 = loc.targetRange.start.line + 1;
|
|
25348
|
-
const char2 = loc.targetRange.start.character;
|
|
25349
|
-
return `${uri2}:${line2}:${char2}`;
|
|
25350
|
-
}
|
|
25351
|
-
const uri = uriToPath(loc.uri);
|
|
25352
|
-
const line = loc.range.start.line + 1;
|
|
25353
|
-
const char = loc.range.start.character;
|
|
25354
|
-
return `${uri}:${line}:${char}`;
|
|
25355
|
-
}
|
|
25356
|
-
function formatSymbolKind(kind) {
|
|
25357
|
-
return SYMBOL_KIND_MAP[kind] || `Unknown(${kind})`;
|
|
25358
|
-
}
|
|
25359
25719
|
function formatSeverity(severity) {
|
|
25360
25720
|
if (!severity)
|
|
25361
25721
|
return "unknown";
|
|
25362
25722
|
return SEVERITY_MAP[severity] || `unknown(${severity})`;
|
|
25363
25723
|
}
|
|
25364
|
-
function formatDocumentSymbol(symbol, indent = 0) {
|
|
25365
|
-
const prefix = " ".repeat(indent);
|
|
25366
|
-
const kind = formatSymbolKind(symbol.kind);
|
|
25367
|
-
const line = symbol.range.start.line + 1;
|
|
25368
|
-
let result = `${prefix}${symbol.name} (${kind}) - line ${line}`;
|
|
25369
|
-
if (symbol.children && symbol.children.length > 0) {
|
|
25370
|
-
for (const child of symbol.children) {
|
|
25371
|
-
result += `
|
|
25372
|
-
` + formatDocumentSymbol(child, indent + 1);
|
|
25373
|
-
}
|
|
25374
|
-
}
|
|
25375
|
-
return result;
|
|
25376
|
-
}
|
|
25377
|
-
function formatSymbolInfo(symbol) {
|
|
25378
|
-
const kind = formatSymbolKind(symbol.kind);
|
|
25379
|
-
const loc = formatLocation(symbol.location);
|
|
25380
|
-
const container = symbol.containerName ? ` (in ${symbol.containerName})` : "";
|
|
25381
|
-
return `${symbol.name} (${kind})${container} - ${loc}`;
|
|
25382
|
-
}
|
|
25383
25724
|
function formatDiagnostic(diag) {
|
|
25384
25725
|
const severity = formatSeverity(diag.severity);
|
|
25385
25726
|
const line = diag.range.start.line + 1;
|
|
@@ -25426,7 +25767,7 @@ function formatPrepareRenameResult(result) {
|
|
|
25426
25767
|
}
|
|
25427
25768
|
function applyTextEditsToFile(filePath, edits) {
|
|
25428
25769
|
try {
|
|
25429
|
-
let content =
|
|
25770
|
+
let content = readFileSync30(filePath, "utf-8");
|
|
25430
25771
|
const lines = content.split(`
|
|
25431
25772
|
`);
|
|
25432
25773
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
@@ -25492,7 +25833,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
25492
25833
|
try {
|
|
25493
25834
|
const oldPath = uriToPath(change.oldUri);
|
|
25494
25835
|
const newPath = uriToPath(change.newUri);
|
|
25495
|
-
const content =
|
|
25836
|
+
const content = readFileSync30(oldPath, "utf-8");
|
|
25496
25837
|
writeFileSync16(newPath, content, "utf-8");
|
|
25497
25838
|
__require("fs").unlinkSync(oldPath);
|
|
25498
25839
|
result.filesModified.push(newPath);
|
|
@@ -37866,128 +38207,6 @@ function tool(input) {
|
|
|
37866
38207
|
tool.schema = exports_external;
|
|
37867
38208
|
|
|
37868
38209
|
// src/tools/lsp/tools.ts
|
|
37869
|
-
var lsp_goto_definition = tool({
|
|
37870
|
-
description: "Jump to symbol definition. Find WHERE something is defined.",
|
|
37871
|
-
args: {
|
|
37872
|
-
filePath: tool.schema.string(),
|
|
37873
|
-
line: tool.schema.number().min(1).describe("1-based"),
|
|
37874
|
-
character: tool.schema.number().min(0).describe("0-based")
|
|
37875
|
-
},
|
|
37876
|
-
execute: async (args, context) => {
|
|
37877
|
-
try {
|
|
37878
|
-
const result = await withLspClient(args.filePath, async (client) => {
|
|
37879
|
-
return await client.definition(args.filePath, args.line, args.character);
|
|
37880
|
-
});
|
|
37881
|
-
if (!result) {
|
|
37882
|
-
const output2 = "No definition found";
|
|
37883
|
-
return output2;
|
|
37884
|
-
}
|
|
37885
|
-
const locations = Array.isArray(result) ? result : [result];
|
|
37886
|
-
if (locations.length === 0) {
|
|
37887
|
-
const output2 = "No definition found";
|
|
37888
|
-
return output2;
|
|
37889
|
-
}
|
|
37890
|
-
const output = locations.map(formatLocation).join(`
|
|
37891
|
-
`);
|
|
37892
|
-
return output;
|
|
37893
|
-
} catch (e) {
|
|
37894
|
-
const output = `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
37895
|
-
return output;
|
|
37896
|
-
}
|
|
37897
|
-
}
|
|
37898
|
-
});
|
|
37899
|
-
var lsp_find_references = tool({
|
|
37900
|
-
description: "Find ALL usages/references of a symbol across the entire workspace.",
|
|
37901
|
-
args: {
|
|
37902
|
-
filePath: tool.schema.string(),
|
|
37903
|
-
line: tool.schema.number().min(1).describe("1-based"),
|
|
37904
|
-
character: tool.schema.number().min(0).describe("0-based"),
|
|
37905
|
-
includeDeclaration: tool.schema.boolean().optional().describe("Include the declaration itself")
|
|
37906
|
-
},
|
|
37907
|
-
execute: async (args, context) => {
|
|
37908
|
-
try {
|
|
37909
|
-
const result = await withLspClient(args.filePath, async (client) => {
|
|
37910
|
-
return await client.references(args.filePath, args.line, args.character, args.includeDeclaration ?? true);
|
|
37911
|
-
});
|
|
37912
|
-
if (!result || result.length === 0) {
|
|
37913
|
-
const output2 = "No references found";
|
|
37914
|
-
return output2;
|
|
37915
|
-
}
|
|
37916
|
-
const total = result.length;
|
|
37917
|
-
const truncated = total > DEFAULT_MAX_REFERENCES;
|
|
37918
|
-
const limited = truncated ? result.slice(0, DEFAULT_MAX_REFERENCES) : result;
|
|
37919
|
-
const lines = limited.map(formatLocation);
|
|
37920
|
-
if (truncated) {
|
|
37921
|
-
lines.unshift(`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`);
|
|
37922
|
-
}
|
|
37923
|
-
const output = lines.join(`
|
|
37924
|
-
`);
|
|
37925
|
-
return output;
|
|
37926
|
-
} catch (e) {
|
|
37927
|
-
const output = `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
37928
|
-
return output;
|
|
37929
|
-
}
|
|
37930
|
-
}
|
|
37931
|
-
});
|
|
37932
|
-
var lsp_symbols = tool({
|
|
37933
|
-
description: "Get symbols from file (document) or search across workspace. Use scope='document' for file outline, scope='workspace' for project-wide symbol search.",
|
|
37934
|
-
args: {
|
|
37935
|
-
filePath: tool.schema.string().describe("File path for LSP context"),
|
|
37936
|
-
scope: tool.schema.enum(["document", "workspace"]).default("document").describe("'document' for file symbols, 'workspace' for project-wide search"),
|
|
37937
|
-
query: tool.schema.string().optional().describe("Symbol name to search (required for workspace scope)"),
|
|
37938
|
-
limit: tool.schema.number().optional().describe("Max results (default 50)")
|
|
37939
|
-
},
|
|
37940
|
-
execute: async (args, context) => {
|
|
37941
|
-
try {
|
|
37942
|
-
const scope = args.scope ?? "document";
|
|
37943
|
-
if (scope === "workspace") {
|
|
37944
|
-
if (!args.query) {
|
|
37945
|
-
return "Error: 'query' is required for workspace scope";
|
|
37946
|
-
}
|
|
37947
|
-
const result = await withLspClient(args.filePath, async (client) => {
|
|
37948
|
-
return await client.workspaceSymbols(args.query);
|
|
37949
|
-
});
|
|
37950
|
-
if (!result || result.length === 0) {
|
|
37951
|
-
return "No symbols found";
|
|
37952
|
-
}
|
|
37953
|
-
const total = result.length;
|
|
37954
|
-
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS);
|
|
37955
|
-
const truncated = total > limit;
|
|
37956
|
-
const limited = result.slice(0, limit);
|
|
37957
|
-
const lines = limited.map(formatSymbolInfo);
|
|
37958
|
-
if (truncated) {
|
|
37959
|
-
lines.unshift(`Found ${total} symbols (showing first ${limit}):`);
|
|
37960
|
-
}
|
|
37961
|
-
return lines.join(`
|
|
37962
|
-
`);
|
|
37963
|
-
} else {
|
|
37964
|
-
const result = await withLspClient(args.filePath, async (client) => {
|
|
37965
|
-
return await client.documentSymbols(args.filePath);
|
|
37966
|
-
});
|
|
37967
|
-
if (!result || result.length === 0) {
|
|
37968
|
-
return "No symbols found";
|
|
37969
|
-
}
|
|
37970
|
-
const total = result.length;
|
|
37971
|
-
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS);
|
|
37972
|
-
const truncated = total > limit;
|
|
37973
|
-
const limited = truncated ? result.slice(0, limit) : result;
|
|
37974
|
-
const lines = [];
|
|
37975
|
-
if (truncated) {
|
|
37976
|
-
lines.push(`Found ${total} symbols (showing first ${limit}):`);
|
|
37977
|
-
}
|
|
37978
|
-
if ("range" in limited[0]) {
|
|
37979
|
-
lines.push(...limited.map((s) => formatDocumentSymbol(s)));
|
|
37980
|
-
} else {
|
|
37981
|
-
lines.push(...limited.map(formatSymbolInfo));
|
|
37982
|
-
}
|
|
37983
|
-
return lines.join(`
|
|
37984
|
-
`);
|
|
37985
|
-
}
|
|
37986
|
-
} catch (e) {
|
|
37987
|
-
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
37988
|
-
}
|
|
37989
|
-
}
|
|
37990
|
-
});
|
|
37991
38210
|
var lsp_diagnostics = tool({
|
|
37992
38211
|
description: "Get errors, warnings, hints from language server BEFORE running build.",
|
|
37993
38212
|
args: {
|
|
@@ -39222,7 +39441,7 @@ var glob = tool({
|
|
|
39222
39441
|
init_shared();
|
|
39223
39442
|
init_file_utils();
|
|
39224
39443
|
init_shared();
|
|
39225
|
-
import { existsSync as existsSync48, readdirSync as readdirSync17, readFileSync as
|
|
39444
|
+
import { existsSync as existsSync48, readdirSync as readdirSync17, readFileSync as readFileSync31 } from "fs";
|
|
39226
39445
|
import { join as join55, basename as basename4, dirname as dirname12 } from "path";
|
|
39227
39446
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
39228
39447
|
if (!existsSync48(commandsDir)) {
|
|
@@ -39236,7 +39455,7 @@ function discoverCommandsFromDir2(commandsDir, scope) {
|
|
|
39236
39455
|
const commandPath = join55(commandsDir, entry.name);
|
|
39237
39456
|
const commandName = basename4(entry.name, ".md");
|
|
39238
39457
|
try {
|
|
39239
|
-
const content =
|
|
39458
|
+
const content = readFileSync31(commandPath, "utf-8");
|
|
39240
39459
|
const { data, body } = parseFrontmatter(content);
|
|
39241
39460
|
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
39242
39461
|
const metadata = {
|
|
@@ -39363,7 +39582,7 @@ ${commandListForDescription}
|
|
|
39363
39582
|
}
|
|
39364
39583
|
function createSlashcommandTool(options = {}) {
|
|
39365
39584
|
let cachedCommands = options.commands ?? null;
|
|
39366
|
-
let
|
|
39585
|
+
let cachedSkills2 = options.skills ?? null;
|
|
39367
39586
|
let cachedDescription = null;
|
|
39368
39587
|
const getCommands = () => {
|
|
39369
39588
|
if (cachedCommands)
|
|
@@ -39372,10 +39591,10 @@ function createSlashcommandTool(options = {}) {
|
|
|
39372
39591
|
return cachedCommands;
|
|
39373
39592
|
};
|
|
39374
39593
|
const getSkills = async () => {
|
|
39375
|
-
if (
|
|
39376
|
-
return
|
|
39377
|
-
|
|
39378
|
-
return
|
|
39594
|
+
if (cachedSkills2)
|
|
39595
|
+
return cachedSkills2;
|
|
39596
|
+
cachedSkills2 = await discoverAllSkills();
|
|
39597
|
+
return cachedSkills2;
|
|
39379
39598
|
};
|
|
39380
39599
|
const getAllItems = async () => {
|
|
39381
39600
|
const commands = getCommands();
|
|
@@ -40116,7 +40335,28 @@ var interactive_bash = tool({
|
|
|
40116
40335
|
}
|
|
40117
40336
|
const subcommand = parts[0].toLowerCase();
|
|
40118
40337
|
if (BLOCKED_TMUX_SUBCOMMANDS.includes(subcommand)) {
|
|
40119
|
-
|
|
40338
|
+
const sessionIdx = parts.findIndex((p) => p === "-t" || p.startsWith("-t"));
|
|
40339
|
+
let sessionName = "omo-session";
|
|
40340
|
+
if (sessionIdx !== -1) {
|
|
40341
|
+
if (parts[sessionIdx] === "-t" && parts[sessionIdx + 1]) {
|
|
40342
|
+
sessionName = parts[sessionIdx + 1];
|
|
40343
|
+
} else if (parts[sessionIdx].startsWith("-t")) {
|
|
40344
|
+
sessionName = parts[sessionIdx].slice(2);
|
|
40345
|
+
}
|
|
40346
|
+
}
|
|
40347
|
+
return `Error: '${parts[0]}' is blocked in interactive_bash.
|
|
40348
|
+
|
|
40349
|
+
**USE BASH TOOL INSTEAD:**
|
|
40350
|
+
|
|
40351
|
+
\`\`\`bash
|
|
40352
|
+
# Capture terminal output
|
|
40353
|
+
tmux capture-pane -p -t ${sessionName}
|
|
40354
|
+
|
|
40355
|
+
# Or capture with history (last 1000 lines)
|
|
40356
|
+
tmux capture-pane -p -t ${sessionName} -S -1000
|
|
40357
|
+
\`\`\`
|
|
40358
|
+
|
|
40359
|
+
The Bash tool can execute these commands directly. Do NOT retry with interactive_bash.`;
|
|
40120
40360
|
}
|
|
40121
40361
|
const proc = Bun.spawn([tmuxPath2, ...parts], {
|
|
40122
40362
|
stdout: "pipe",
|
|
@@ -40155,8 +40395,6 @@ Skills provide specialized knowledge and step-by-step guidance.
|
|
|
40155
40395
|
Use this when a task matches an available skill's description.`;
|
|
40156
40396
|
// src/tools/skill/tools.ts
|
|
40157
40397
|
import { dirname as dirname13 } from "path";
|
|
40158
|
-
import { readFileSync as readFileSync31 } from "fs";
|
|
40159
|
-
init_frontmatter();
|
|
40160
40398
|
function loadedSkillToInfo(skill) {
|
|
40161
40399
|
return {
|
|
40162
40400
|
name: skill.name,
|
|
@@ -40199,9 +40437,7 @@ async function extractSkillBody(skill) {
|
|
|
40199
40437
|
return templateMatch2 ? templateMatch2[1].trim() : fullTemplate;
|
|
40200
40438
|
}
|
|
40201
40439
|
if (skill.path) {
|
|
40202
|
-
|
|
40203
|
-
const { body } = parseFrontmatter(content);
|
|
40204
|
-
return body.trim();
|
|
40440
|
+
return extractSkillTemplate(skill);
|
|
40205
40441
|
}
|
|
40206
40442
|
const templateMatch = skill.definition.template?.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
40207
40443
|
return templateMatch ? templateMatch[1].trim() : skill.definition.template || "";
|
|
@@ -40267,15 +40503,15 @@ async function formatMcpCapabilities(skill, manager, sessionID) {
|
|
|
40267
40503
|
`);
|
|
40268
40504
|
}
|
|
40269
40505
|
function createSkillTool(options = {}) {
|
|
40270
|
-
let
|
|
40506
|
+
let cachedSkills2 = null;
|
|
40271
40507
|
let cachedDescription = null;
|
|
40272
40508
|
const getSkills = async () => {
|
|
40273
40509
|
if (options.skills)
|
|
40274
40510
|
return options.skills;
|
|
40275
|
-
if (
|
|
40276
|
-
return
|
|
40277
|
-
|
|
40278
|
-
return
|
|
40511
|
+
if (cachedSkills2)
|
|
40512
|
+
return cachedSkills2;
|
|
40513
|
+
cachedSkills2 = await getAllSkills();
|
|
40514
|
+
return cachedSkills2;
|
|
40279
40515
|
};
|
|
40280
40516
|
const getDescription = async () => {
|
|
40281
40517
|
if (cachedDescription)
|
|
@@ -40474,6 +40710,7 @@ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=
|
|
|
40474
40710
|
|
|
40475
40711
|
// src/tools/background-task/tools.ts
|
|
40476
40712
|
init_logger();
|
|
40713
|
+
init_session_cursor();
|
|
40477
40714
|
function formatDuration(start, end) {
|
|
40478
40715
|
const duration3 = (end ?? new Date).getTime() - start.getTime();
|
|
40479
40716
|
const seconds = Math.floor(duration3 / 1000);
|
|
@@ -40580,8 +40817,22 @@ Session ID: ${task.sessionID}
|
|
|
40580
40817
|
const timeB = String(b.info?.time ?? "");
|
|
40581
40818
|
return timeA.localeCompare(timeB);
|
|
40582
40819
|
});
|
|
40820
|
+
const newMessages = consumeNewMessages(task.sessionID, sortedMessages);
|
|
40821
|
+
if (newMessages.length === 0) {
|
|
40822
|
+
const duration4 = formatDuration(task.startedAt, task.completedAt);
|
|
40823
|
+
return `Task Result
|
|
40824
|
+
|
|
40825
|
+
Task ID: ${task.id}
|
|
40826
|
+
Description: ${task.description}
|
|
40827
|
+
Duration: ${duration4}
|
|
40828
|
+
Session ID: ${task.sessionID}
|
|
40829
|
+
|
|
40830
|
+
---
|
|
40831
|
+
|
|
40832
|
+
(No new output since last check)`;
|
|
40833
|
+
}
|
|
40583
40834
|
const extractedContent = [];
|
|
40584
|
-
for (const message of
|
|
40835
|
+
for (const message of newMessages) {
|
|
40585
40836
|
for (const part of message.parts ?? []) {
|
|
40586
40837
|
if ((part.type === "text" || part.type === "reasoning") && part.text) {
|
|
40587
40838
|
extractedContent.push(part.text);
|
|
@@ -40735,6 +40986,7 @@ Pass \`resume=session_id\` to continue previous agent with full context. Prompts
|
|
|
40735
40986
|
import { existsSync as existsSync50, readdirSync as readdirSync19 } from "fs";
|
|
40736
40987
|
import { join as join58 } from "path";
|
|
40737
40988
|
init_logger();
|
|
40989
|
+
init_session_cursor();
|
|
40738
40990
|
function getMessageDir13(sessionID) {
|
|
40739
40991
|
if (!existsSync50(MESSAGE_STORAGE))
|
|
40740
40992
|
return null;
|
|
@@ -40966,8 +41218,16 @@ session_id: ${sessionID}
|
|
|
40966
41218
|
const timeB = b.info?.time?.created ?? 0;
|
|
40967
41219
|
return timeA - timeB;
|
|
40968
41220
|
});
|
|
41221
|
+
const newMessages = consumeNewMessages(sessionID, sortedMessages);
|
|
41222
|
+
if (newMessages.length === 0) {
|
|
41223
|
+
return `No new output since last check.
|
|
41224
|
+
|
|
41225
|
+
<task_metadata>
|
|
41226
|
+
session_id: ${sessionID}
|
|
41227
|
+
</task_metadata>`;
|
|
41228
|
+
}
|
|
40969
41229
|
const extractedContent = [];
|
|
40970
|
-
for (const message of
|
|
41230
|
+
for (const message of newMessages) {
|
|
40971
41231
|
for (const part of message.parts ?? []) {
|
|
40972
41232
|
if ((part.type === "text" || part.type === "reasoning") && part.text) {
|
|
40973
41233
|
extractedContent.push(part.text);
|
|
@@ -41002,6 +41262,21 @@ var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams) that req
|
|
|
41002
41262
|
import { extname as extname3, basename as basename5 } from "path";
|
|
41003
41263
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
41004
41264
|
init_logger();
|
|
41265
|
+
function normalizeArgs(args) {
|
|
41266
|
+
return {
|
|
41267
|
+
file_path: args.file_path ?? args.path ?? "",
|
|
41268
|
+
goal: args.goal ?? ""
|
|
41269
|
+
};
|
|
41270
|
+
}
|
|
41271
|
+
function validateArgs(args) {
|
|
41272
|
+
if (!args.file_path) {
|
|
41273
|
+
return `Error: Missing required parameter 'file_path'. Usage: look_at(file_path="/path/to/file", goal="what to extract")`;
|
|
41274
|
+
}
|
|
41275
|
+
if (!args.goal) {
|
|
41276
|
+
return `Error: Missing required parameter 'goal'. Usage: look_at(file_path="/path/to/file", goal="what to extract")`;
|
|
41277
|
+
}
|
|
41278
|
+
return null;
|
|
41279
|
+
}
|
|
41005
41280
|
function inferMimeType(filePath) {
|
|
41006
41281
|
const ext = extname3(filePath).toLowerCase();
|
|
41007
41282
|
const mimeTypes = {
|
|
@@ -41046,7 +41321,13 @@ function createLookAt(ctx) {
|
|
|
41046
41321
|
file_path: tool.schema.string().describe("Absolute path to the file to analyze"),
|
|
41047
41322
|
goal: tool.schema.string().describe("What specific information to extract from the file")
|
|
41048
41323
|
},
|
|
41049
|
-
async execute(
|
|
41324
|
+
async execute(rawArgs, toolContext) {
|
|
41325
|
+
const args = normalizeArgs(rawArgs);
|
|
41326
|
+
const validationError = validateArgs(args);
|
|
41327
|
+
if (validationError) {
|
|
41328
|
+
log(`[look_at] Validation failed: ${validationError}`);
|
|
41329
|
+
return validationError;
|
|
41330
|
+
}
|
|
41050
41331
|
log(`[look_at] Analyzing file: ${args.file_path}, goal: ${args.goal}`);
|
|
41051
41332
|
const mimeType = inferMimeType(args.file_path);
|
|
41052
41333
|
const filename = basename5(args.file_path);
|
|
@@ -41143,7 +41424,8 @@ class TaskToastManager {
|
|
|
41143
41424
|
status: task.status ?? "running",
|
|
41144
41425
|
startedAt: new Date,
|
|
41145
41426
|
isBackground: task.isBackground,
|
|
41146
|
-
skills: task.skills
|
|
41427
|
+
skills: task.skills,
|
|
41428
|
+
modelInfo: task.modelInfo
|
|
41147
41429
|
};
|
|
41148
41430
|
this.tasks.set(task.id, trackedTask);
|
|
41149
41431
|
this.showTaskListToast(trackedTask);
|
|
@@ -41190,6 +41472,17 @@ class TaskToastManager {
|
|
|
41190
41472
|
const queued = this.getQueuedTasks();
|
|
41191
41473
|
const concurrencyInfo = this.getConcurrencyInfo();
|
|
41192
41474
|
const lines = [];
|
|
41475
|
+
if (newTask.modelInfo && newTask.modelInfo.type !== "user-defined") {
|
|
41476
|
+
const icon = "\u26A0\uFE0F";
|
|
41477
|
+
const suffixMap = {
|
|
41478
|
+
inherited: " (inherited)",
|
|
41479
|
+
"category-default": " (category default)",
|
|
41480
|
+
"system-default": " (system default)"
|
|
41481
|
+
};
|
|
41482
|
+
const suffix = suffixMap[newTask.modelInfo.type] ?? "";
|
|
41483
|
+
lines.push(`${icon} Model: ${newTask.modelInfo.model}${suffix}`);
|
|
41484
|
+
lines.push("");
|
|
41485
|
+
}
|
|
41193
41486
|
if (running.length > 0) {
|
|
41194
41487
|
lines.push(`Running (${running.length}):${concurrencyInfo}`);
|
|
41195
41488
|
for (const task of running) {
|
|
@@ -41296,17 +41589,55 @@ function formatDuration2(start, end) {
|
|
|
41296
41589
|
return `${minutes}m ${seconds % 60}s`;
|
|
41297
41590
|
return `${seconds}s`;
|
|
41298
41591
|
}
|
|
41299
|
-
function
|
|
41592
|
+
function formatDetailedError(error45, ctx) {
|
|
41593
|
+
const message = error45 instanceof Error ? error45.message : String(error45);
|
|
41594
|
+
const stack = error45 instanceof Error ? error45.stack : undefined;
|
|
41595
|
+
const lines = [
|
|
41596
|
+
`\u274C ${ctx.operation} failed`,
|
|
41597
|
+
"",
|
|
41598
|
+
`**Error**: ${message}`
|
|
41599
|
+
];
|
|
41600
|
+
if (ctx.sessionID) {
|
|
41601
|
+
lines.push(`**Session ID**: ${ctx.sessionID}`);
|
|
41602
|
+
}
|
|
41603
|
+
if (ctx.agent) {
|
|
41604
|
+
lines.push(`**Agent**: ${ctx.agent}${ctx.category ? ` (category: ${ctx.category})` : ""}`);
|
|
41605
|
+
}
|
|
41606
|
+
if (ctx.args) {
|
|
41607
|
+
lines.push("", "**Arguments**:");
|
|
41608
|
+
lines.push(`- description: "${ctx.args.description}"`);
|
|
41609
|
+
lines.push(`- category: ${ctx.args.category ?? "(none)"}`);
|
|
41610
|
+
lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`);
|
|
41611
|
+
lines.push(`- run_in_background: ${ctx.args.run_in_background}`);
|
|
41612
|
+
lines.push(`- skills: [${ctx.args.skills?.join(", ") ?? ""}]`);
|
|
41613
|
+
if (ctx.args.resume) {
|
|
41614
|
+
lines.push(`- resume: ${ctx.args.resume}`);
|
|
41615
|
+
}
|
|
41616
|
+
}
|
|
41617
|
+
if (stack) {
|
|
41618
|
+
lines.push("", "**Stack Trace**:");
|
|
41619
|
+
lines.push("```");
|
|
41620
|
+
lines.push(stack.split(`
|
|
41621
|
+
`).slice(0, 10).join(`
|
|
41622
|
+
`));
|
|
41623
|
+
lines.push("```");
|
|
41624
|
+
}
|
|
41625
|
+
return lines.join(`
|
|
41626
|
+
`);
|
|
41627
|
+
}
|
|
41628
|
+
function resolveCategoryConfig(categoryName, options) {
|
|
41629
|
+
const { userCategories, parentModelString, systemDefaultModel } = options;
|
|
41300
41630
|
const defaultConfig = DEFAULT_CATEGORIES[categoryName];
|
|
41301
41631
|
const userConfig = userCategories?.[categoryName];
|
|
41302
41632
|
const defaultPromptAppend = CATEGORY_PROMPT_APPENDS[categoryName] ?? "";
|
|
41303
41633
|
if (!defaultConfig && !userConfig) {
|
|
41304
41634
|
return null;
|
|
41305
41635
|
}
|
|
41636
|
+
const model = userConfig?.model ?? parentModelString ?? defaultConfig?.model ?? systemDefaultModel;
|
|
41306
41637
|
const config3 = {
|
|
41307
41638
|
...defaultConfig,
|
|
41308
41639
|
...userConfig,
|
|
41309
|
-
model
|
|
41640
|
+
model
|
|
41310
41641
|
};
|
|
41311
41642
|
let promptAppend = defaultPromptAppend;
|
|
41312
41643
|
if (userConfig?.prompt_append) {
|
|
@@ -41314,7 +41645,7 @@ function resolveCategoryConfig(categoryName, userCategories) {
|
|
|
41314
41645
|
|
|
41315
41646
|
` + userConfig.prompt_append : userConfig.prompt_append;
|
|
41316
41647
|
}
|
|
41317
|
-
return { config: config3, promptAppend };
|
|
41648
|
+
return { config: config3, promptAppend, model };
|
|
41318
41649
|
}
|
|
41319
41650
|
function buildSystemContent(input) {
|
|
41320
41651
|
const { skillContent, categoryPromptAppend } = input;
|
|
@@ -41352,9 +41683,10 @@ function createSisyphusTask(options) {
|
|
|
41352
41683
|
const runInBackground = args.run_in_background === true;
|
|
41353
41684
|
let skillContent;
|
|
41354
41685
|
if (args.skills.length > 0) {
|
|
41355
|
-
const { resolved, notFound } =
|
|
41686
|
+
const { resolved, notFound } = await resolveMultipleSkillsAsync(args.skills, { gitMasterConfig });
|
|
41356
41687
|
if (notFound.length > 0) {
|
|
41357
|
-
const
|
|
41688
|
+
const allSkills = await discoverSkills({ includeClaudeCodePaths: true });
|
|
41689
|
+
const available = allSkills.map((s) => s.name).join(", ");
|
|
41358
41690
|
return `\u274C Skills not found: ${notFound.join(", ")}. Available: ${available}`;
|
|
41359
41691
|
}
|
|
41360
41692
|
skillContent = Array.from(resolved.values()).join(`
|
|
@@ -41402,8 +41734,11 @@ Status: ${task.status}
|
|
|
41402
41734
|
Agent continues with full previous context preserved.
|
|
41403
41735
|
Use \`background_output\` with task_id="${task.id}" to check progress.`;
|
|
41404
41736
|
} catch (error45) {
|
|
41405
|
-
|
|
41406
|
-
|
|
41737
|
+
return formatDetailedError(error45, {
|
|
41738
|
+
operation: "Resume background task",
|
|
41739
|
+
args,
|
|
41740
|
+
sessionID: args.resume
|
|
41741
|
+
});
|
|
41407
41742
|
}
|
|
41408
41743
|
}
|
|
41409
41744
|
const toastManager2 = getTaskToastManager();
|
|
@@ -41422,10 +41757,25 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`;
|
|
|
41422
41757
|
metadata: { sessionId: args.resume, sync: true }
|
|
41423
41758
|
});
|
|
41424
41759
|
try {
|
|
41425
|
-
|
|
41426
|
-
|
|
41427
|
-
|
|
41428
|
-
|
|
41760
|
+
let resumeAgent;
|
|
41761
|
+
let resumeModel;
|
|
41762
|
+
try {
|
|
41763
|
+
const messagesResp = await client2.session.messages({ path: { id: args.resume } });
|
|
41764
|
+
const messages2 = messagesResp.data ?? [];
|
|
41765
|
+
for (let i2 = messages2.length - 1;i2 >= 0; i2--) {
|
|
41766
|
+
const info = messages2[i2].info;
|
|
41767
|
+
if (info?.agent || info?.model) {
|
|
41768
|
+
resumeAgent = info.agent;
|
|
41769
|
+
resumeModel = info.model;
|
|
41770
|
+
break;
|
|
41771
|
+
}
|
|
41772
|
+
}
|
|
41773
|
+
} catch {
|
|
41774
|
+
const resumeMessageDir = getMessageDir14(args.resume);
|
|
41775
|
+
const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null;
|
|
41776
|
+
resumeAgent = resumeMessage?.agent;
|
|
41777
|
+
resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID ? { providerID: resumeMessage.model.providerID, modelID: resumeMessage.model.modelID } : undefined;
|
|
41778
|
+
}
|
|
41429
41779
|
await client2.session.prompt({
|
|
41430
41780
|
path: { id: args.resume },
|
|
41431
41781
|
body: {
|
|
@@ -41511,23 +41861,60 @@ ${textContent || "(No text output)"}`;
|
|
|
41511
41861
|
if (!args.category && !args.subagent_type) {
|
|
41512
41862
|
return `\u274C Invalid arguments: Must provide either category or subagent_type.`;
|
|
41513
41863
|
}
|
|
41864
|
+
let systemDefaultModel;
|
|
41865
|
+
try {
|
|
41866
|
+
const openCodeConfig = await client2.config.get();
|
|
41867
|
+
systemDefaultModel = openCodeConfig?.model;
|
|
41868
|
+
} catch {
|
|
41869
|
+
systemDefaultModel = undefined;
|
|
41870
|
+
}
|
|
41514
41871
|
let agentToUse;
|
|
41515
41872
|
let categoryModel;
|
|
41516
41873
|
let categoryPromptAppend;
|
|
41874
|
+
const parentModelString = parentModel ? `${parentModel.providerID}/${parentModel.modelID}` : undefined;
|
|
41875
|
+
let modelInfo;
|
|
41517
41876
|
if (args.category) {
|
|
41518
|
-
const resolved = resolveCategoryConfig(args.category,
|
|
41877
|
+
const resolved = resolveCategoryConfig(args.category, {
|
|
41878
|
+
userCategories,
|
|
41879
|
+
parentModelString,
|
|
41880
|
+
systemDefaultModel
|
|
41881
|
+
});
|
|
41519
41882
|
if (!resolved) {
|
|
41520
41883
|
return `\u274C Unknown category: "${args.category}". Available: ${Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories }).join(", ")}`;
|
|
41521
41884
|
}
|
|
41885
|
+
const actualModel = resolved.model;
|
|
41886
|
+
const userDefinedModel = userCategories?.[args.category]?.model;
|
|
41887
|
+
const categoryDefaultModel = DEFAULT_CATEGORIES[args.category]?.model;
|
|
41888
|
+
if (!actualModel) {
|
|
41889
|
+
return `\u274C No model configured. Set a model in your OpenCode config, plugin config, or use a category with a default model.`;
|
|
41890
|
+
}
|
|
41891
|
+
if (!parseModelString(actualModel)) {
|
|
41892
|
+
return `\u274C Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-5").`;
|
|
41893
|
+
}
|
|
41894
|
+
switch (actualModel) {
|
|
41895
|
+
case userDefinedModel:
|
|
41896
|
+
modelInfo = { model: actualModel, type: "user-defined" };
|
|
41897
|
+
break;
|
|
41898
|
+
case parentModelString:
|
|
41899
|
+
modelInfo = { model: actualModel, type: "inherited" };
|
|
41900
|
+
break;
|
|
41901
|
+
case categoryDefaultModel:
|
|
41902
|
+
modelInfo = { model: actualModel, type: "category-default" };
|
|
41903
|
+
break;
|
|
41904
|
+
case systemDefaultModel:
|
|
41905
|
+
modelInfo = { model: actualModel, type: "system-default" };
|
|
41906
|
+
break;
|
|
41907
|
+
}
|
|
41522
41908
|
agentToUse = SISYPHUS_JUNIOR_AGENT;
|
|
41523
|
-
const parsedModel = parseModelString(
|
|
41909
|
+
const parsedModel = parseModelString(actualModel);
|
|
41524
41910
|
categoryModel = parsedModel ? resolved.config.variant ? { ...parsedModel, variant: resolved.config.variant } : parsedModel : undefined;
|
|
41525
41911
|
categoryPromptAppend = resolved.promptAppend || undefined;
|
|
41526
41912
|
} else {
|
|
41527
|
-
|
|
41528
|
-
if (!agentToUse) {
|
|
41913
|
+
if (!args.subagent_type?.trim()) {
|
|
41529
41914
|
return `\u274C Agent name cannot be empty.`;
|
|
41530
41915
|
}
|
|
41916
|
+
const agentName = args.subagent_type.trim();
|
|
41917
|
+
agentToUse = agentName;
|
|
41531
41918
|
try {
|
|
41532
41919
|
const agentsResult = await client2.app.agents();
|
|
41533
41920
|
const agents = agentsResult.data ?? agentsResult;
|
|
@@ -41572,8 +41959,12 @@ Status: ${task.status}
|
|
|
41572
41959
|
|
|
41573
41960
|
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.`;
|
|
41574
41961
|
} catch (error45) {
|
|
41575
|
-
|
|
41576
|
-
|
|
41962
|
+
return formatDetailedError(error45, {
|
|
41963
|
+
operation: "Launch background task",
|
|
41964
|
+
args,
|
|
41965
|
+
agent: agentToUse,
|
|
41966
|
+
category: args.category
|
|
41967
|
+
});
|
|
41577
41968
|
}
|
|
41578
41969
|
}
|
|
41579
41970
|
const toastManager = getTaskToastManager();
|
|
@@ -41605,7 +41996,8 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|
|
41605
41996
|
description: args.description,
|
|
41606
41997
|
agent: agentToUse,
|
|
41607
41998
|
isBackground: false,
|
|
41608
|
-
skills: args.skills
|
|
41999
|
+
skills: args.skills,
|
|
42000
|
+
modelInfo
|
|
41609
42001
|
});
|
|
41610
42002
|
}
|
|
41611
42003
|
ctx.metadata?.({
|
|
@@ -41633,13 +42025,21 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|
|
41633
42025
|
}
|
|
41634
42026
|
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError);
|
|
41635
42027
|
if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
|
|
41636
|
-
return
|
|
41637
|
-
|
|
41638
|
-
|
|
42028
|
+
return formatDetailedError(new Error(`Agent "${agentToUse}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`), {
|
|
42029
|
+
operation: "Send prompt to agent",
|
|
42030
|
+
args,
|
|
42031
|
+
sessionID,
|
|
42032
|
+
agent: agentToUse,
|
|
42033
|
+
category: args.category
|
|
42034
|
+
});
|
|
41639
42035
|
}
|
|
41640
|
-
return
|
|
41641
|
-
|
|
41642
|
-
|
|
42036
|
+
return formatDetailedError(promptError, {
|
|
42037
|
+
operation: "Send prompt",
|
|
42038
|
+
args,
|
|
42039
|
+
sessionID,
|
|
42040
|
+
agent: agentToUse,
|
|
42041
|
+
category: args.category
|
|
42042
|
+
});
|
|
41643
42043
|
}
|
|
41644
42044
|
const POLL_INTERVAL_MS = 500;
|
|
41645
42045
|
const MAX_POLL_TIME_MS = 10 * 60 * 1000;
|
|
@@ -41739,8 +42139,13 @@ ${textContent || "(No text output)"}`;
|
|
|
41739
42139
|
if (syncSessionID) {
|
|
41740
42140
|
subagentSessions.delete(syncSessionID);
|
|
41741
42141
|
}
|
|
41742
|
-
|
|
41743
|
-
|
|
42142
|
+
return formatDetailedError(error45, {
|
|
42143
|
+
operation: "Execute task",
|
|
42144
|
+
args,
|
|
42145
|
+
sessionID: syncSessionID,
|
|
42146
|
+
agent: agentToUse,
|
|
42147
|
+
category: args.category
|
|
42148
|
+
});
|
|
41744
42149
|
}
|
|
41745
42150
|
}
|
|
41746
42151
|
});
|
|
@@ -41756,9 +42161,6 @@ function createBackgroundTools(manager, client2) {
|
|
|
41756
42161
|
};
|
|
41757
42162
|
}
|
|
41758
42163
|
var builtinTools = {
|
|
41759
|
-
lsp_goto_definition,
|
|
41760
|
-
lsp_find_references,
|
|
41761
|
-
lsp_symbols,
|
|
41762
42164
|
lsp_diagnostics,
|
|
41763
42165
|
lsp_servers,
|
|
41764
42166
|
lsp_prepare_rename,
|
|
@@ -41963,6 +42365,7 @@ class BackgroundManager {
|
|
|
41963
42365
|
existingTask.completedAt = new Date;
|
|
41964
42366
|
if (existingTask.concurrencyKey) {
|
|
41965
42367
|
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
42368
|
+
existingTask.concurrencyKey = undefined;
|
|
41966
42369
|
}
|
|
41967
42370
|
this.markForNotification(existingTask);
|
|
41968
42371
|
this.notifyParentSession(existingTask).catch((err) => {
|
|
@@ -42040,6 +42443,7 @@ class BackgroundManager {
|
|
|
42040
42443
|
existingTask.parentMessageID = input.parentMessageID;
|
|
42041
42444
|
existingTask.parentModel = input.parentModel;
|
|
42042
42445
|
existingTask.parentAgent = input.parentAgent;
|
|
42446
|
+
existingTask.startedAt = new Date;
|
|
42043
42447
|
existingTask.progress = {
|
|
42044
42448
|
toolCalls: existingTask.progress?.toolCalls ?? 0,
|
|
42045
42449
|
lastUpdate: new Date
|
|
@@ -42081,6 +42485,10 @@ class BackgroundManager {
|
|
|
42081
42485
|
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
42082
42486
|
existingTask.error = errorMessage;
|
|
42083
42487
|
existingTask.completedAt = new Date;
|
|
42488
|
+
if (existingTask.concurrencyKey) {
|
|
42489
|
+
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
42490
|
+
existingTask.concurrencyKey = undefined;
|
|
42491
|
+
}
|
|
42084
42492
|
this.markForNotification(existingTask);
|
|
42085
42493
|
this.notifyParentSession(existingTask).catch((err) => {
|
|
42086
42494
|
log("[background-agent] Failed to notify on resume error:", err);
|
|
@@ -42151,6 +42559,11 @@ class BackgroundManager {
|
|
|
42151
42559
|
}
|
|
42152
42560
|
task.status = "completed";
|
|
42153
42561
|
task.completedAt = new Date;
|
|
42562
|
+
if (task.concurrencyKey) {
|
|
42563
|
+
this.concurrencyManager.release(task.concurrencyKey);
|
|
42564
|
+
task.concurrencyKey = undefined;
|
|
42565
|
+
}
|
|
42566
|
+
this.cleanupPendingByParent(task);
|
|
42154
42567
|
this.markForNotification(task);
|
|
42155
42568
|
await this.notifyParentSession(task);
|
|
42156
42569
|
log("[background-agent] Task completed via session.idle event:", task.id);
|
|
@@ -42173,7 +42586,9 @@ class BackgroundManager {
|
|
|
42173
42586
|
}
|
|
42174
42587
|
if (task.concurrencyKey) {
|
|
42175
42588
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
42589
|
+
task.concurrencyKey = undefined;
|
|
42176
42590
|
}
|
|
42591
|
+
this.cleanupPendingByParent(task);
|
|
42177
42592
|
this.tasks.delete(task.id);
|
|
42178
42593
|
this.clearNotificationsForTask(task.id);
|
|
42179
42594
|
subagentSessions.delete(sessionID);
|
|
@@ -42227,6 +42642,17 @@ class BackgroundManager {
|
|
|
42227
42642
|
}
|
|
42228
42643
|
}
|
|
42229
42644
|
}
|
|
42645
|
+
cleanupPendingByParent(task) {
|
|
42646
|
+
if (!task.parentSessionID)
|
|
42647
|
+
return;
|
|
42648
|
+
const pending = this.pendingByParent.get(task.parentSessionID);
|
|
42649
|
+
if (pending) {
|
|
42650
|
+
pending.delete(task.id);
|
|
42651
|
+
if (pending.size === 0) {
|
|
42652
|
+
this.pendingByParent.delete(task.parentSessionID);
|
|
42653
|
+
}
|
|
42654
|
+
}
|
|
42655
|
+
}
|
|
42230
42656
|
startPolling() {
|
|
42231
42657
|
if (this.pollingInterval)
|
|
42232
42658
|
return;
|
|
@@ -42305,15 +42731,27 @@ Do NOT poll - continue productive work.
|
|
|
42305
42731
|
Use \`background_output(task_id="${task.id}")\` to retrieve this result when ready.
|
|
42306
42732
|
</system-reminder>`;
|
|
42307
42733
|
}
|
|
42308
|
-
|
|
42309
|
-
|
|
42310
|
-
|
|
42311
|
-
|
|
42734
|
+
let agent = task.parentAgent;
|
|
42735
|
+
let model;
|
|
42736
|
+
try {
|
|
42737
|
+
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } });
|
|
42738
|
+
const messages = messagesResp.data ?? [];
|
|
42739
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
42740
|
+
const info = messages[i2].info;
|
|
42741
|
+
if (info?.agent || info?.model) {
|
|
42742
|
+
agent = info.agent ?? task.parentAgent;
|
|
42743
|
+
model = info.model;
|
|
42744
|
+
break;
|
|
42745
|
+
}
|
|
42746
|
+
}
|
|
42747
|
+
} catch {
|
|
42748
|
+
const messageDir = getMessageDir15(task.parentSessionID);
|
|
42749
|
+
const currentMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
42750
|
+
agent = currentMessage?.agent ?? task.parentAgent;
|
|
42751
|
+
model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
|
|
42752
|
+
}
|
|
42312
42753
|
log("[background-agent] notifyParentSession context:", {
|
|
42313
42754
|
taskId: task.id,
|
|
42314
|
-
messageDir: !!messageDir,
|
|
42315
|
-
currentAgent: currentMessage?.agent,
|
|
42316
|
-
currentModel: currentMessage?.model,
|
|
42317
42755
|
resolvedAgent: agent,
|
|
42318
42756
|
resolvedModel: model
|
|
42319
42757
|
});
|
|
@@ -42372,7 +42810,9 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
42372
42810
|
task.completedAt = new Date;
|
|
42373
42811
|
if (task.concurrencyKey) {
|
|
42374
42812
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
42813
|
+
task.concurrencyKey = undefined;
|
|
42375
42814
|
}
|
|
42815
|
+
this.cleanupPendingByParent(task);
|
|
42376
42816
|
this.clearNotificationsForTask(taskId);
|
|
42377
42817
|
this.tasks.delete(taskId);
|
|
42378
42818
|
subagentSessions.delete(task.sessionID);
|
|
@@ -42416,6 +42856,11 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
42416
42856
|
}
|
|
42417
42857
|
task.status = "completed";
|
|
42418
42858
|
task.completedAt = new Date;
|
|
42859
|
+
if (task.concurrencyKey) {
|
|
42860
|
+
this.concurrencyManager.release(task.concurrencyKey);
|
|
42861
|
+
task.concurrencyKey = undefined;
|
|
42862
|
+
}
|
|
42863
|
+
this.cleanupPendingByParent(task);
|
|
42419
42864
|
this.markForNotification(task);
|
|
42420
42865
|
await this.notifyParentSession(task);
|
|
42421
42866
|
log("[background-agent] Task completed via polling:", task.id);
|
|
@@ -42467,6 +42912,11 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
42467
42912
|
if (!hasIncompleteTodos2) {
|
|
42468
42913
|
task.status = "completed";
|
|
42469
42914
|
task.completedAt = new Date;
|
|
42915
|
+
if (task.concurrencyKey) {
|
|
42916
|
+
this.concurrencyManager.release(task.concurrencyKey);
|
|
42917
|
+
task.concurrencyKey = undefined;
|
|
42918
|
+
}
|
|
42919
|
+
this.cleanupPendingByParent(task);
|
|
42470
42920
|
this.markForNotification(task);
|
|
42471
42921
|
await this.notifyParentSession(task);
|
|
42472
42922
|
log("[background-agent] Task completed via stability detection:", task.id);
|
|
@@ -42764,6 +43214,7 @@ var InitializedNotificationSchema = NotificationSchema.extend({
|
|
|
42764
43214
|
method: literal("notifications/initialized"),
|
|
42765
43215
|
params: NotificationsParamsSchema.optional()
|
|
42766
43216
|
});
|
|
43217
|
+
var isInitializedNotification = (value) => InitializedNotificationSchema.safeParse(value).success;
|
|
42767
43218
|
var PingRequestSchema = RequestSchema.extend({
|
|
42768
43219
|
method: literal("ping"),
|
|
42769
43220
|
params: BaseRequestParamsSchema.optional()
|
|
@@ -45037,6 +45488,1317 @@ function isElectron() {
|
|
|
45037
45488
|
return "type" in process2;
|
|
45038
45489
|
}
|
|
45039
45490
|
|
|
45491
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js
|
|
45492
|
+
function normalizeHeaders(headers) {
|
|
45493
|
+
if (!headers)
|
|
45494
|
+
return {};
|
|
45495
|
+
if (headers instanceof Headers) {
|
|
45496
|
+
return Object.fromEntries(headers.entries());
|
|
45497
|
+
}
|
|
45498
|
+
if (Array.isArray(headers)) {
|
|
45499
|
+
return Object.fromEntries(headers);
|
|
45500
|
+
}
|
|
45501
|
+
return { ...headers };
|
|
45502
|
+
}
|
|
45503
|
+
function createFetchWithInit(baseFetch = fetch, baseInit) {
|
|
45504
|
+
if (!baseInit) {
|
|
45505
|
+
return baseFetch;
|
|
45506
|
+
}
|
|
45507
|
+
return async (url2, init) => {
|
|
45508
|
+
const mergedInit = {
|
|
45509
|
+
...baseInit,
|
|
45510
|
+
...init,
|
|
45511
|
+
headers: init?.headers ? { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) } : baseInit.headers
|
|
45512
|
+
};
|
|
45513
|
+
return baseFetch(url2, mergedInit);
|
|
45514
|
+
};
|
|
45515
|
+
}
|
|
45516
|
+
|
|
45517
|
+
// node_modules/pkce-challenge/dist/index.node.js
|
|
45518
|
+
var crypto2;
|
|
45519
|
+
crypto2 = globalThis.crypto?.webcrypto ?? globalThis.crypto ?? import("crypto").then((m) => m.webcrypto);
|
|
45520
|
+
async function getRandomValues(size) {
|
|
45521
|
+
return (await crypto2).getRandomValues(new Uint8Array(size));
|
|
45522
|
+
}
|
|
45523
|
+
async function random(size) {
|
|
45524
|
+
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
45525
|
+
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
45526
|
+
let result = "";
|
|
45527
|
+
while (result.length < size) {
|
|
45528
|
+
const randomBytes = await getRandomValues(size - result.length);
|
|
45529
|
+
for (const randomByte of randomBytes) {
|
|
45530
|
+
if (randomByte < evenDistCutoff) {
|
|
45531
|
+
result += mask[randomByte % mask.length];
|
|
45532
|
+
}
|
|
45533
|
+
}
|
|
45534
|
+
}
|
|
45535
|
+
return result;
|
|
45536
|
+
}
|
|
45537
|
+
async function generateVerifier(length) {
|
|
45538
|
+
return await random(length);
|
|
45539
|
+
}
|
|
45540
|
+
async function generateChallenge(code_verifier) {
|
|
45541
|
+
const buffer = await (await crypto2).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
45542
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
45543
|
+
}
|
|
45544
|
+
async function pkceChallenge(length) {
|
|
45545
|
+
if (!length)
|
|
45546
|
+
length = 43;
|
|
45547
|
+
if (length < 43 || length > 128) {
|
|
45548
|
+
throw `Expected a length between 43 and 128. Received ${length}.`;
|
|
45549
|
+
}
|
|
45550
|
+
const verifier = await generateVerifier(length);
|
|
45551
|
+
const challenge = await generateChallenge(verifier);
|
|
45552
|
+
return {
|
|
45553
|
+
code_verifier: verifier,
|
|
45554
|
+
code_challenge: challenge
|
|
45555
|
+
};
|
|
45556
|
+
}
|
|
45557
|
+
|
|
45558
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js
|
|
45559
|
+
var SafeUrlSchema = url().superRefine((val, ctx) => {
|
|
45560
|
+
if (!URL.canParse(val)) {
|
|
45561
|
+
ctx.addIssue({
|
|
45562
|
+
code: ZodIssueCode.custom,
|
|
45563
|
+
message: "URL must be parseable",
|
|
45564
|
+
fatal: true
|
|
45565
|
+
});
|
|
45566
|
+
return NEVER;
|
|
45567
|
+
}
|
|
45568
|
+
}).refine((url2) => {
|
|
45569
|
+
const u = new URL(url2);
|
|
45570
|
+
return u.protocol !== "javascript:" && u.protocol !== "data:" && u.protocol !== "vbscript:";
|
|
45571
|
+
}, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
|
|
45572
|
+
var OAuthProtectedResourceMetadataSchema = looseObject({
|
|
45573
|
+
resource: string2().url(),
|
|
45574
|
+
authorization_servers: array(SafeUrlSchema).optional(),
|
|
45575
|
+
jwks_uri: string2().url().optional(),
|
|
45576
|
+
scopes_supported: array(string2()).optional(),
|
|
45577
|
+
bearer_methods_supported: array(string2()).optional(),
|
|
45578
|
+
resource_signing_alg_values_supported: array(string2()).optional(),
|
|
45579
|
+
resource_name: string2().optional(),
|
|
45580
|
+
resource_documentation: string2().optional(),
|
|
45581
|
+
resource_policy_uri: string2().url().optional(),
|
|
45582
|
+
resource_tos_uri: string2().url().optional(),
|
|
45583
|
+
tls_client_certificate_bound_access_tokens: boolean2().optional(),
|
|
45584
|
+
authorization_details_types_supported: array(string2()).optional(),
|
|
45585
|
+
dpop_signing_alg_values_supported: array(string2()).optional(),
|
|
45586
|
+
dpop_bound_access_tokens_required: boolean2().optional()
|
|
45587
|
+
});
|
|
45588
|
+
var OAuthMetadataSchema = looseObject({
|
|
45589
|
+
issuer: string2(),
|
|
45590
|
+
authorization_endpoint: SafeUrlSchema,
|
|
45591
|
+
token_endpoint: SafeUrlSchema,
|
|
45592
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
45593
|
+
scopes_supported: array(string2()).optional(),
|
|
45594
|
+
response_types_supported: array(string2()),
|
|
45595
|
+
response_modes_supported: array(string2()).optional(),
|
|
45596
|
+
grant_types_supported: array(string2()).optional(),
|
|
45597
|
+
token_endpoint_auth_methods_supported: array(string2()).optional(),
|
|
45598
|
+
token_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
|
|
45599
|
+
service_documentation: SafeUrlSchema.optional(),
|
|
45600
|
+
revocation_endpoint: SafeUrlSchema.optional(),
|
|
45601
|
+
revocation_endpoint_auth_methods_supported: array(string2()).optional(),
|
|
45602
|
+
revocation_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
|
|
45603
|
+
introspection_endpoint: string2().optional(),
|
|
45604
|
+
introspection_endpoint_auth_methods_supported: array(string2()).optional(),
|
|
45605
|
+
introspection_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
|
|
45606
|
+
code_challenge_methods_supported: array(string2()).optional(),
|
|
45607
|
+
client_id_metadata_document_supported: boolean2().optional()
|
|
45608
|
+
});
|
|
45609
|
+
var OpenIdProviderMetadataSchema = looseObject({
|
|
45610
|
+
issuer: string2(),
|
|
45611
|
+
authorization_endpoint: SafeUrlSchema,
|
|
45612
|
+
token_endpoint: SafeUrlSchema,
|
|
45613
|
+
userinfo_endpoint: SafeUrlSchema.optional(),
|
|
45614
|
+
jwks_uri: SafeUrlSchema,
|
|
45615
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
45616
|
+
scopes_supported: array(string2()).optional(),
|
|
45617
|
+
response_types_supported: array(string2()),
|
|
45618
|
+
response_modes_supported: array(string2()).optional(),
|
|
45619
|
+
grant_types_supported: array(string2()).optional(),
|
|
45620
|
+
acr_values_supported: array(string2()).optional(),
|
|
45621
|
+
subject_types_supported: array(string2()),
|
|
45622
|
+
id_token_signing_alg_values_supported: array(string2()),
|
|
45623
|
+
id_token_encryption_alg_values_supported: array(string2()).optional(),
|
|
45624
|
+
id_token_encryption_enc_values_supported: array(string2()).optional(),
|
|
45625
|
+
userinfo_signing_alg_values_supported: array(string2()).optional(),
|
|
45626
|
+
userinfo_encryption_alg_values_supported: array(string2()).optional(),
|
|
45627
|
+
userinfo_encryption_enc_values_supported: array(string2()).optional(),
|
|
45628
|
+
request_object_signing_alg_values_supported: array(string2()).optional(),
|
|
45629
|
+
request_object_encryption_alg_values_supported: array(string2()).optional(),
|
|
45630
|
+
request_object_encryption_enc_values_supported: array(string2()).optional(),
|
|
45631
|
+
token_endpoint_auth_methods_supported: array(string2()).optional(),
|
|
45632
|
+
token_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
|
|
45633
|
+
display_values_supported: array(string2()).optional(),
|
|
45634
|
+
claim_types_supported: array(string2()).optional(),
|
|
45635
|
+
claims_supported: array(string2()).optional(),
|
|
45636
|
+
service_documentation: string2().optional(),
|
|
45637
|
+
claims_locales_supported: array(string2()).optional(),
|
|
45638
|
+
ui_locales_supported: array(string2()).optional(),
|
|
45639
|
+
claims_parameter_supported: boolean2().optional(),
|
|
45640
|
+
request_parameter_supported: boolean2().optional(),
|
|
45641
|
+
request_uri_parameter_supported: boolean2().optional(),
|
|
45642
|
+
require_request_uri_registration: boolean2().optional(),
|
|
45643
|
+
op_policy_uri: SafeUrlSchema.optional(),
|
|
45644
|
+
op_tos_uri: SafeUrlSchema.optional(),
|
|
45645
|
+
client_id_metadata_document_supported: boolean2().optional()
|
|
45646
|
+
});
|
|
45647
|
+
var OpenIdProviderDiscoveryMetadataSchema = object({
|
|
45648
|
+
...OpenIdProviderMetadataSchema.shape,
|
|
45649
|
+
...OAuthMetadataSchema.pick({
|
|
45650
|
+
code_challenge_methods_supported: true
|
|
45651
|
+
}).shape
|
|
45652
|
+
});
|
|
45653
|
+
var OAuthTokensSchema = object({
|
|
45654
|
+
access_token: string2(),
|
|
45655
|
+
id_token: string2().optional(),
|
|
45656
|
+
token_type: string2(),
|
|
45657
|
+
expires_in: exports_coerce.number().optional(),
|
|
45658
|
+
scope: string2().optional(),
|
|
45659
|
+
refresh_token: string2().optional()
|
|
45660
|
+
}).strip();
|
|
45661
|
+
var OAuthErrorResponseSchema = object({
|
|
45662
|
+
error: string2(),
|
|
45663
|
+
error_description: string2().optional(),
|
|
45664
|
+
error_uri: string2().optional()
|
|
45665
|
+
});
|
|
45666
|
+
var OptionalSafeUrlSchema = SafeUrlSchema.optional().or(literal("").transform(() => {
|
|
45667
|
+
return;
|
|
45668
|
+
}));
|
|
45669
|
+
var OAuthClientMetadataSchema = object({
|
|
45670
|
+
redirect_uris: array(SafeUrlSchema),
|
|
45671
|
+
token_endpoint_auth_method: string2().optional(),
|
|
45672
|
+
grant_types: array(string2()).optional(),
|
|
45673
|
+
response_types: array(string2()).optional(),
|
|
45674
|
+
client_name: string2().optional(),
|
|
45675
|
+
client_uri: SafeUrlSchema.optional(),
|
|
45676
|
+
logo_uri: OptionalSafeUrlSchema,
|
|
45677
|
+
scope: string2().optional(),
|
|
45678
|
+
contacts: array(string2()).optional(),
|
|
45679
|
+
tos_uri: OptionalSafeUrlSchema,
|
|
45680
|
+
policy_uri: string2().optional(),
|
|
45681
|
+
jwks_uri: SafeUrlSchema.optional(),
|
|
45682
|
+
jwks: any().optional(),
|
|
45683
|
+
software_id: string2().optional(),
|
|
45684
|
+
software_version: string2().optional(),
|
|
45685
|
+
software_statement: string2().optional()
|
|
45686
|
+
}).strip();
|
|
45687
|
+
var OAuthClientInformationSchema = object({
|
|
45688
|
+
client_id: string2(),
|
|
45689
|
+
client_secret: string2().optional(),
|
|
45690
|
+
client_id_issued_at: number2().optional(),
|
|
45691
|
+
client_secret_expires_at: number2().optional()
|
|
45692
|
+
}).strip();
|
|
45693
|
+
var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
|
|
45694
|
+
var OAuthClientRegistrationErrorSchema = object({
|
|
45695
|
+
error: string2(),
|
|
45696
|
+
error_description: string2().optional()
|
|
45697
|
+
}).strip();
|
|
45698
|
+
var OAuthTokenRevocationRequestSchema = object({
|
|
45699
|
+
token: string2(),
|
|
45700
|
+
token_type_hint: string2().optional()
|
|
45701
|
+
}).strip();
|
|
45702
|
+
|
|
45703
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth-utils.js
|
|
45704
|
+
function resourceUrlFromServerUrl(url2) {
|
|
45705
|
+
const resourceURL = typeof url2 === "string" ? new URL(url2) : new URL(url2.href);
|
|
45706
|
+
resourceURL.hash = "";
|
|
45707
|
+
return resourceURL;
|
|
45708
|
+
}
|
|
45709
|
+
function checkResourceAllowed({ requestedResource, configuredResource }) {
|
|
45710
|
+
const requested = typeof requestedResource === "string" ? new URL(requestedResource) : new URL(requestedResource.href);
|
|
45711
|
+
const configured = typeof configuredResource === "string" ? new URL(configuredResource) : new URL(configuredResource.href);
|
|
45712
|
+
if (requested.origin !== configured.origin) {
|
|
45713
|
+
return false;
|
|
45714
|
+
}
|
|
45715
|
+
if (requested.pathname.length < configured.pathname.length) {
|
|
45716
|
+
return false;
|
|
45717
|
+
}
|
|
45718
|
+
const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : requested.pathname + "/";
|
|
45719
|
+
const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : configured.pathname + "/";
|
|
45720
|
+
return requestedPath.startsWith(configuredPath);
|
|
45721
|
+
}
|
|
45722
|
+
|
|
45723
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js
|
|
45724
|
+
class OAuthError extends Error {
|
|
45725
|
+
constructor(message, errorUri) {
|
|
45726
|
+
super(message);
|
|
45727
|
+
this.errorUri = errorUri;
|
|
45728
|
+
this.name = this.constructor.name;
|
|
45729
|
+
}
|
|
45730
|
+
toResponseObject() {
|
|
45731
|
+
const response = {
|
|
45732
|
+
error: this.errorCode,
|
|
45733
|
+
error_description: this.message
|
|
45734
|
+
};
|
|
45735
|
+
if (this.errorUri) {
|
|
45736
|
+
response.error_uri = this.errorUri;
|
|
45737
|
+
}
|
|
45738
|
+
return response;
|
|
45739
|
+
}
|
|
45740
|
+
get errorCode() {
|
|
45741
|
+
return this.constructor.errorCode;
|
|
45742
|
+
}
|
|
45743
|
+
}
|
|
45744
|
+
|
|
45745
|
+
class InvalidRequestError extends OAuthError {
|
|
45746
|
+
}
|
|
45747
|
+
InvalidRequestError.errorCode = "invalid_request";
|
|
45748
|
+
|
|
45749
|
+
class InvalidClientError extends OAuthError {
|
|
45750
|
+
}
|
|
45751
|
+
InvalidClientError.errorCode = "invalid_client";
|
|
45752
|
+
|
|
45753
|
+
class InvalidGrantError extends OAuthError {
|
|
45754
|
+
}
|
|
45755
|
+
InvalidGrantError.errorCode = "invalid_grant";
|
|
45756
|
+
|
|
45757
|
+
class UnauthorizedClientError extends OAuthError {
|
|
45758
|
+
}
|
|
45759
|
+
UnauthorizedClientError.errorCode = "unauthorized_client";
|
|
45760
|
+
|
|
45761
|
+
class UnsupportedGrantTypeError extends OAuthError {
|
|
45762
|
+
}
|
|
45763
|
+
UnsupportedGrantTypeError.errorCode = "unsupported_grant_type";
|
|
45764
|
+
|
|
45765
|
+
class InvalidScopeError extends OAuthError {
|
|
45766
|
+
}
|
|
45767
|
+
InvalidScopeError.errorCode = "invalid_scope";
|
|
45768
|
+
|
|
45769
|
+
class AccessDeniedError extends OAuthError {
|
|
45770
|
+
}
|
|
45771
|
+
AccessDeniedError.errorCode = "access_denied";
|
|
45772
|
+
|
|
45773
|
+
class ServerError extends OAuthError {
|
|
45774
|
+
}
|
|
45775
|
+
ServerError.errorCode = "server_error";
|
|
45776
|
+
|
|
45777
|
+
class TemporarilyUnavailableError extends OAuthError {
|
|
45778
|
+
}
|
|
45779
|
+
TemporarilyUnavailableError.errorCode = "temporarily_unavailable";
|
|
45780
|
+
|
|
45781
|
+
class UnsupportedResponseTypeError extends OAuthError {
|
|
45782
|
+
}
|
|
45783
|
+
UnsupportedResponseTypeError.errorCode = "unsupported_response_type";
|
|
45784
|
+
|
|
45785
|
+
class UnsupportedTokenTypeError extends OAuthError {
|
|
45786
|
+
}
|
|
45787
|
+
UnsupportedTokenTypeError.errorCode = "unsupported_token_type";
|
|
45788
|
+
|
|
45789
|
+
class InvalidTokenError extends OAuthError {
|
|
45790
|
+
}
|
|
45791
|
+
InvalidTokenError.errorCode = "invalid_token";
|
|
45792
|
+
|
|
45793
|
+
class MethodNotAllowedError extends OAuthError {
|
|
45794
|
+
}
|
|
45795
|
+
MethodNotAllowedError.errorCode = "method_not_allowed";
|
|
45796
|
+
|
|
45797
|
+
class TooManyRequestsError extends OAuthError {
|
|
45798
|
+
}
|
|
45799
|
+
TooManyRequestsError.errorCode = "too_many_requests";
|
|
45800
|
+
|
|
45801
|
+
class InvalidClientMetadataError extends OAuthError {
|
|
45802
|
+
}
|
|
45803
|
+
InvalidClientMetadataError.errorCode = "invalid_client_metadata";
|
|
45804
|
+
|
|
45805
|
+
class InsufficientScopeError extends OAuthError {
|
|
45806
|
+
}
|
|
45807
|
+
InsufficientScopeError.errorCode = "insufficient_scope";
|
|
45808
|
+
|
|
45809
|
+
class InvalidTargetError extends OAuthError {
|
|
45810
|
+
}
|
|
45811
|
+
InvalidTargetError.errorCode = "invalid_target";
|
|
45812
|
+
var OAUTH_ERRORS = {
|
|
45813
|
+
[InvalidRequestError.errorCode]: InvalidRequestError,
|
|
45814
|
+
[InvalidClientError.errorCode]: InvalidClientError,
|
|
45815
|
+
[InvalidGrantError.errorCode]: InvalidGrantError,
|
|
45816
|
+
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
|
|
45817
|
+
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
|
|
45818
|
+
[InvalidScopeError.errorCode]: InvalidScopeError,
|
|
45819
|
+
[AccessDeniedError.errorCode]: AccessDeniedError,
|
|
45820
|
+
[ServerError.errorCode]: ServerError,
|
|
45821
|
+
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
|
|
45822
|
+
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
|
|
45823
|
+
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
|
|
45824
|
+
[InvalidTokenError.errorCode]: InvalidTokenError,
|
|
45825
|
+
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
|
|
45826
|
+
[TooManyRequestsError.errorCode]: TooManyRequestsError,
|
|
45827
|
+
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
|
|
45828
|
+
[InsufficientScopeError.errorCode]: InsufficientScopeError,
|
|
45829
|
+
[InvalidTargetError.errorCode]: InvalidTargetError
|
|
45830
|
+
};
|
|
45831
|
+
|
|
45832
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js
|
|
45833
|
+
class UnauthorizedError extends Error {
|
|
45834
|
+
constructor(message) {
|
|
45835
|
+
super(message ?? "Unauthorized");
|
|
45836
|
+
}
|
|
45837
|
+
}
|
|
45838
|
+
function isClientAuthMethod(method) {
|
|
45839
|
+
return ["client_secret_basic", "client_secret_post", "none"].includes(method);
|
|
45840
|
+
}
|
|
45841
|
+
var AUTHORIZATION_CODE_RESPONSE_TYPE = "code";
|
|
45842
|
+
var AUTHORIZATION_CODE_CHALLENGE_METHOD = "S256";
|
|
45843
|
+
function selectClientAuthMethod(clientInformation, supportedMethods) {
|
|
45844
|
+
const hasClientSecret = clientInformation.client_secret !== undefined;
|
|
45845
|
+
if (supportedMethods.length === 0) {
|
|
45846
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
45847
|
+
}
|
|
45848
|
+
if ("token_endpoint_auth_method" in clientInformation && clientInformation.token_endpoint_auth_method && isClientAuthMethod(clientInformation.token_endpoint_auth_method) && supportedMethods.includes(clientInformation.token_endpoint_auth_method)) {
|
|
45849
|
+
return clientInformation.token_endpoint_auth_method;
|
|
45850
|
+
}
|
|
45851
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
|
|
45852
|
+
return "client_secret_basic";
|
|
45853
|
+
}
|
|
45854
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
|
|
45855
|
+
return "client_secret_post";
|
|
45856
|
+
}
|
|
45857
|
+
if (supportedMethods.includes("none")) {
|
|
45858
|
+
return "none";
|
|
45859
|
+
}
|
|
45860
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
45861
|
+
}
|
|
45862
|
+
function applyClientAuthentication(method, clientInformation, headers, params) {
|
|
45863
|
+
const { client_id, client_secret } = clientInformation;
|
|
45864
|
+
switch (method) {
|
|
45865
|
+
case "client_secret_basic":
|
|
45866
|
+
applyBasicAuth(client_id, client_secret, headers);
|
|
45867
|
+
return;
|
|
45868
|
+
case "client_secret_post":
|
|
45869
|
+
applyPostAuth(client_id, client_secret, params);
|
|
45870
|
+
return;
|
|
45871
|
+
case "none":
|
|
45872
|
+
applyPublicAuth(client_id, params);
|
|
45873
|
+
return;
|
|
45874
|
+
default:
|
|
45875
|
+
throw new Error(`Unsupported client authentication method: ${method}`);
|
|
45876
|
+
}
|
|
45877
|
+
}
|
|
45878
|
+
function applyBasicAuth(clientId, clientSecret, headers) {
|
|
45879
|
+
if (!clientSecret) {
|
|
45880
|
+
throw new Error("client_secret_basic authentication requires a client_secret");
|
|
45881
|
+
}
|
|
45882
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
45883
|
+
headers.set("Authorization", `Basic ${credentials}`);
|
|
45884
|
+
}
|
|
45885
|
+
function applyPostAuth(clientId, clientSecret, params) {
|
|
45886
|
+
params.set("client_id", clientId);
|
|
45887
|
+
if (clientSecret) {
|
|
45888
|
+
params.set("client_secret", clientSecret);
|
|
45889
|
+
}
|
|
45890
|
+
}
|
|
45891
|
+
function applyPublicAuth(clientId, params) {
|
|
45892
|
+
params.set("client_id", clientId);
|
|
45893
|
+
}
|
|
45894
|
+
async function parseErrorResponse(input) {
|
|
45895
|
+
const statusCode = input instanceof Response ? input.status : undefined;
|
|
45896
|
+
const body = input instanceof Response ? await input.text() : input;
|
|
45897
|
+
try {
|
|
45898
|
+
const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
|
|
45899
|
+
const { error: error45, error_description, error_uri } = result;
|
|
45900
|
+
const errorClass = OAUTH_ERRORS[error45] || ServerError;
|
|
45901
|
+
return new errorClass(error_description || "", error_uri);
|
|
45902
|
+
} catch (error45) {
|
|
45903
|
+
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error45}. Raw body: ${body}`;
|
|
45904
|
+
return new ServerError(errorMessage);
|
|
45905
|
+
}
|
|
45906
|
+
}
|
|
45907
|
+
async function auth(provider, options) {
|
|
45908
|
+
try {
|
|
45909
|
+
return await authInternal(provider, options);
|
|
45910
|
+
} catch (error45) {
|
|
45911
|
+
if (error45 instanceof InvalidClientError || error45 instanceof UnauthorizedClientError) {
|
|
45912
|
+
await provider.invalidateCredentials?.("all");
|
|
45913
|
+
return await authInternal(provider, options);
|
|
45914
|
+
} else if (error45 instanceof InvalidGrantError) {
|
|
45915
|
+
await provider.invalidateCredentials?.("tokens");
|
|
45916
|
+
return await authInternal(provider, options);
|
|
45917
|
+
}
|
|
45918
|
+
throw error45;
|
|
45919
|
+
}
|
|
45920
|
+
}
|
|
45921
|
+
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
|
|
45922
|
+
let resourceMetadata;
|
|
45923
|
+
let authorizationServerUrl;
|
|
45924
|
+
try {
|
|
45925
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl }, fetchFn);
|
|
45926
|
+
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
|
|
45927
|
+
authorizationServerUrl = resourceMetadata.authorization_servers[0];
|
|
45928
|
+
}
|
|
45929
|
+
} catch {}
|
|
45930
|
+
if (!authorizationServerUrl) {
|
|
45931
|
+
authorizationServerUrl = new URL("/", serverUrl);
|
|
45932
|
+
}
|
|
45933
|
+
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
45934
|
+
const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
45935
|
+
fetchFn
|
|
45936
|
+
});
|
|
45937
|
+
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
45938
|
+
if (!clientInformation) {
|
|
45939
|
+
if (authorizationCode !== undefined) {
|
|
45940
|
+
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
|
|
45941
|
+
}
|
|
45942
|
+
const supportsUrlBasedClientId = metadata?.client_id_metadata_document_supported === true;
|
|
45943
|
+
const clientMetadataUrl = provider.clientMetadataUrl;
|
|
45944
|
+
if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
|
|
45945
|
+
throw new InvalidClientMetadataError(`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`);
|
|
45946
|
+
}
|
|
45947
|
+
const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
|
|
45948
|
+
if (shouldUseUrlBasedClientId) {
|
|
45949
|
+
clientInformation = {
|
|
45950
|
+
client_id: clientMetadataUrl
|
|
45951
|
+
};
|
|
45952
|
+
await provider.saveClientInformation?.(clientInformation);
|
|
45953
|
+
} else {
|
|
45954
|
+
if (!provider.saveClientInformation) {
|
|
45955
|
+
throw new Error("OAuth client information must be saveable for dynamic registration");
|
|
45956
|
+
}
|
|
45957
|
+
const fullInformation = await registerClient(authorizationServerUrl, {
|
|
45958
|
+
metadata,
|
|
45959
|
+
clientMetadata: provider.clientMetadata,
|
|
45960
|
+
fetchFn
|
|
45961
|
+
});
|
|
45962
|
+
await provider.saveClientInformation(fullInformation);
|
|
45963
|
+
clientInformation = fullInformation;
|
|
45964
|
+
}
|
|
45965
|
+
}
|
|
45966
|
+
const nonInteractiveFlow = !provider.redirectUrl;
|
|
45967
|
+
if (authorizationCode !== undefined || nonInteractiveFlow) {
|
|
45968
|
+
const tokens2 = await fetchToken(provider, authorizationServerUrl, {
|
|
45969
|
+
metadata,
|
|
45970
|
+
resource,
|
|
45971
|
+
authorizationCode,
|
|
45972
|
+
fetchFn
|
|
45973
|
+
});
|
|
45974
|
+
await provider.saveTokens(tokens2);
|
|
45975
|
+
return "AUTHORIZED";
|
|
45976
|
+
}
|
|
45977
|
+
const tokens = await provider.tokens();
|
|
45978
|
+
if (tokens?.refresh_token) {
|
|
45979
|
+
try {
|
|
45980
|
+
const newTokens = await refreshAuthorization(authorizationServerUrl, {
|
|
45981
|
+
metadata,
|
|
45982
|
+
clientInformation,
|
|
45983
|
+
refreshToken: tokens.refresh_token,
|
|
45984
|
+
resource,
|
|
45985
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
45986
|
+
fetchFn
|
|
45987
|
+
});
|
|
45988
|
+
await provider.saveTokens(newTokens);
|
|
45989
|
+
return "AUTHORIZED";
|
|
45990
|
+
} catch (error45) {
|
|
45991
|
+
if (!(error45 instanceof OAuthError) || error45 instanceof ServerError) {} else {
|
|
45992
|
+
throw error45;
|
|
45993
|
+
}
|
|
45994
|
+
}
|
|
45995
|
+
}
|
|
45996
|
+
const state2 = provider.state ? await provider.state() : undefined;
|
|
45997
|
+
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
|
|
45998
|
+
metadata,
|
|
45999
|
+
clientInformation,
|
|
46000
|
+
state: state2,
|
|
46001
|
+
redirectUrl: provider.redirectUrl,
|
|
46002
|
+
scope: scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope,
|
|
46003
|
+
resource
|
|
46004
|
+
});
|
|
46005
|
+
await provider.saveCodeVerifier(codeVerifier);
|
|
46006
|
+
await provider.redirectToAuthorization(authorizationUrl);
|
|
46007
|
+
return "REDIRECT";
|
|
46008
|
+
}
|
|
46009
|
+
function isHttpsUrl(value) {
|
|
46010
|
+
if (!value)
|
|
46011
|
+
return false;
|
|
46012
|
+
try {
|
|
46013
|
+
const url2 = new URL(value);
|
|
46014
|
+
return url2.protocol === "https:" && url2.pathname !== "/";
|
|
46015
|
+
} catch {
|
|
46016
|
+
return false;
|
|
46017
|
+
}
|
|
46018
|
+
}
|
|
46019
|
+
async function selectResourceURL(serverUrl, provider, resourceMetadata) {
|
|
46020
|
+
const defaultResource = resourceUrlFromServerUrl(serverUrl);
|
|
46021
|
+
if (provider.validateResourceURL) {
|
|
46022
|
+
return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
|
|
46023
|
+
}
|
|
46024
|
+
if (!resourceMetadata) {
|
|
46025
|
+
return;
|
|
46026
|
+
}
|
|
46027
|
+
if (!checkResourceAllowed({ requestedResource: defaultResource, configuredResource: resourceMetadata.resource })) {
|
|
46028
|
+
throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
|
|
46029
|
+
}
|
|
46030
|
+
return new URL(resourceMetadata.resource);
|
|
46031
|
+
}
|
|
46032
|
+
function extractWWWAuthenticateParams(res) {
|
|
46033
|
+
const authenticateHeader = res.headers.get("WWW-Authenticate");
|
|
46034
|
+
if (!authenticateHeader) {
|
|
46035
|
+
return {};
|
|
46036
|
+
}
|
|
46037
|
+
const [type2, scheme] = authenticateHeader.split(" ");
|
|
46038
|
+
if (type2.toLowerCase() !== "bearer" || !scheme) {
|
|
46039
|
+
return {};
|
|
46040
|
+
}
|
|
46041
|
+
const resourceMetadataMatch = extractFieldFromWwwAuth(res, "resource_metadata") || undefined;
|
|
46042
|
+
let resourceMetadataUrl;
|
|
46043
|
+
if (resourceMetadataMatch) {
|
|
46044
|
+
try {
|
|
46045
|
+
resourceMetadataUrl = new URL(resourceMetadataMatch);
|
|
46046
|
+
} catch {}
|
|
46047
|
+
}
|
|
46048
|
+
const scope = extractFieldFromWwwAuth(res, "scope") || undefined;
|
|
46049
|
+
const error45 = extractFieldFromWwwAuth(res, "error") || undefined;
|
|
46050
|
+
return {
|
|
46051
|
+
resourceMetadataUrl,
|
|
46052
|
+
scope,
|
|
46053
|
+
error: error45
|
|
46054
|
+
};
|
|
46055
|
+
}
|
|
46056
|
+
function extractFieldFromWwwAuth(response, fieldName) {
|
|
46057
|
+
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
46058
|
+
if (!wwwAuthHeader) {
|
|
46059
|
+
return null;
|
|
46060
|
+
}
|
|
46061
|
+
const pattern = new RegExp(`${fieldName}=(?:"([^"]+)"|([^\\s,]+))`);
|
|
46062
|
+
const match = wwwAuthHeader.match(pattern);
|
|
46063
|
+
if (match) {
|
|
46064
|
+
return match[1] || match[2];
|
|
46065
|
+
}
|
|
46066
|
+
return null;
|
|
46067
|
+
}
|
|
46068
|
+
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
|
|
46069
|
+
const response = await discoverMetadataWithFallback(serverUrl, "oauth-protected-resource", fetchFn, {
|
|
46070
|
+
protocolVersion: opts?.protocolVersion,
|
|
46071
|
+
metadataUrl: opts?.resourceMetadataUrl
|
|
46072
|
+
});
|
|
46073
|
+
if (!response || response.status === 404) {
|
|
46074
|
+
await response?.body?.cancel();
|
|
46075
|
+
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
|
|
46076
|
+
}
|
|
46077
|
+
if (!response.ok) {
|
|
46078
|
+
await response.body?.cancel();
|
|
46079
|
+
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
|
|
46080
|
+
}
|
|
46081
|
+
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
|
|
46082
|
+
}
|
|
46083
|
+
async function fetchWithCorsRetry(url2, headers, fetchFn = fetch) {
|
|
46084
|
+
try {
|
|
46085
|
+
return await fetchFn(url2, { headers });
|
|
46086
|
+
} catch (error45) {
|
|
46087
|
+
if (error45 instanceof TypeError) {
|
|
46088
|
+
if (headers) {
|
|
46089
|
+
return fetchWithCorsRetry(url2, undefined, fetchFn);
|
|
46090
|
+
} else {
|
|
46091
|
+
return;
|
|
46092
|
+
}
|
|
46093
|
+
}
|
|
46094
|
+
throw error45;
|
|
46095
|
+
}
|
|
46096
|
+
}
|
|
46097
|
+
function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
|
|
46098
|
+
if (pathname.endsWith("/")) {
|
|
46099
|
+
pathname = pathname.slice(0, -1);
|
|
46100
|
+
}
|
|
46101
|
+
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
46102
|
+
}
|
|
46103
|
+
async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
|
|
46104
|
+
const headers = {
|
|
46105
|
+
"MCP-Protocol-Version": protocolVersion
|
|
46106
|
+
};
|
|
46107
|
+
return await fetchWithCorsRetry(url2, headers, fetchFn);
|
|
46108
|
+
}
|
|
46109
|
+
function shouldAttemptFallback(response, pathname) {
|
|
46110
|
+
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
46111
|
+
}
|
|
46112
|
+
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
46113
|
+
const issuer = new URL(serverUrl);
|
|
46114
|
+
const protocolVersion = opts?.protocolVersion ?? LATEST_PROTOCOL_VERSION;
|
|
46115
|
+
let url2;
|
|
46116
|
+
if (opts?.metadataUrl) {
|
|
46117
|
+
url2 = new URL(opts.metadataUrl);
|
|
46118
|
+
} else {
|
|
46119
|
+
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
|
|
46120
|
+
url2 = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
|
|
46121
|
+
url2.search = issuer.search;
|
|
46122
|
+
}
|
|
46123
|
+
let response = await tryMetadataDiscovery(url2, protocolVersion, fetchFn);
|
|
46124
|
+
if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) {
|
|
46125
|
+
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
46126
|
+
response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
|
|
46127
|
+
}
|
|
46128
|
+
return response;
|
|
46129
|
+
}
|
|
46130
|
+
function buildDiscoveryUrls(authorizationServerUrl) {
|
|
46131
|
+
const url2 = typeof authorizationServerUrl === "string" ? new URL(authorizationServerUrl) : authorizationServerUrl;
|
|
46132
|
+
const hasPath = url2.pathname !== "/";
|
|
46133
|
+
const urlsToTry = [];
|
|
46134
|
+
if (!hasPath) {
|
|
46135
|
+
urlsToTry.push({
|
|
46136
|
+
url: new URL("/.well-known/oauth-authorization-server", url2.origin),
|
|
46137
|
+
type: "oauth"
|
|
46138
|
+
});
|
|
46139
|
+
urlsToTry.push({
|
|
46140
|
+
url: new URL(`/.well-known/openid-configuration`, url2.origin),
|
|
46141
|
+
type: "oidc"
|
|
46142
|
+
});
|
|
46143
|
+
return urlsToTry;
|
|
46144
|
+
}
|
|
46145
|
+
let pathname = url2.pathname;
|
|
46146
|
+
if (pathname.endsWith("/")) {
|
|
46147
|
+
pathname = pathname.slice(0, -1);
|
|
46148
|
+
}
|
|
46149
|
+
urlsToTry.push({
|
|
46150
|
+
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url2.origin),
|
|
46151
|
+
type: "oauth"
|
|
46152
|
+
});
|
|
46153
|
+
urlsToTry.push({
|
|
46154
|
+
url: new URL(`/.well-known/openid-configuration${pathname}`, url2.origin),
|
|
46155
|
+
type: "oidc"
|
|
46156
|
+
});
|
|
46157
|
+
urlsToTry.push({
|
|
46158
|
+
url: new URL(`${pathname}/.well-known/openid-configuration`, url2.origin),
|
|
46159
|
+
type: "oidc"
|
|
46160
|
+
});
|
|
46161
|
+
return urlsToTry;
|
|
46162
|
+
}
|
|
46163
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION } = {}) {
|
|
46164
|
+
const headers = {
|
|
46165
|
+
"MCP-Protocol-Version": protocolVersion,
|
|
46166
|
+
Accept: "application/json"
|
|
46167
|
+
};
|
|
46168
|
+
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
46169
|
+
for (const { url: endpointUrl, type: type2 } of urlsToTry) {
|
|
46170
|
+
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
|
|
46171
|
+
if (!response) {
|
|
46172
|
+
continue;
|
|
46173
|
+
}
|
|
46174
|
+
if (!response.ok) {
|
|
46175
|
+
await response.body?.cancel();
|
|
46176
|
+
if (response.status >= 400 && response.status < 500) {
|
|
46177
|
+
continue;
|
|
46178
|
+
}
|
|
46179
|
+
throw new Error(`HTTP ${response.status} trying to load ${type2 === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
|
|
46180
|
+
}
|
|
46181
|
+
if (type2 === "oauth") {
|
|
46182
|
+
return OAuthMetadataSchema.parse(await response.json());
|
|
46183
|
+
} else {
|
|
46184
|
+
return OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
|
|
46185
|
+
}
|
|
46186
|
+
}
|
|
46187
|
+
return;
|
|
46188
|
+
}
|
|
46189
|
+
async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state: state2, resource }) {
|
|
46190
|
+
let authorizationUrl;
|
|
46191
|
+
if (metadata) {
|
|
46192
|
+
authorizationUrl = new URL(metadata.authorization_endpoint);
|
|
46193
|
+
if (!metadata.response_types_supported.includes(AUTHORIZATION_CODE_RESPONSE_TYPE)) {
|
|
46194
|
+
throw new Error(`Incompatible auth server: does not support response type ${AUTHORIZATION_CODE_RESPONSE_TYPE}`);
|
|
46195
|
+
}
|
|
46196
|
+
if (metadata.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes(AUTHORIZATION_CODE_CHALLENGE_METHOD)) {
|
|
46197
|
+
throw new Error(`Incompatible auth server: does not support code challenge method ${AUTHORIZATION_CODE_CHALLENGE_METHOD}`);
|
|
46198
|
+
}
|
|
46199
|
+
} else {
|
|
46200
|
+
authorizationUrl = new URL("/authorize", authorizationServerUrl);
|
|
46201
|
+
}
|
|
46202
|
+
const challenge = await pkceChallenge();
|
|
46203
|
+
const codeVerifier = challenge.code_verifier;
|
|
46204
|
+
const codeChallenge = challenge.code_challenge;
|
|
46205
|
+
authorizationUrl.searchParams.set("response_type", AUTHORIZATION_CODE_RESPONSE_TYPE);
|
|
46206
|
+
authorizationUrl.searchParams.set("client_id", clientInformation.client_id);
|
|
46207
|
+
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
|
|
46208
|
+
authorizationUrl.searchParams.set("code_challenge_method", AUTHORIZATION_CODE_CHALLENGE_METHOD);
|
|
46209
|
+
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
|
|
46210
|
+
if (state2) {
|
|
46211
|
+
authorizationUrl.searchParams.set("state", state2);
|
|
46212
|
+
}
|
|
46213
|
+
if (scope) {
|
|
46214
|
+
authorizationUrl.searchParams.set("scope", scope);
|
|
46215
|
+
}
|
|
46216
|
+
if (scope?.includes("offline_access")) {
|
|
46217
|
+
authorizationUrl.searchParams.append("prompt", "consent");
|
|
46218
|
+
}
|
|
46219
|
+
if (resource) {
|
|
46220
|
+
authorizationUrl.searchParams.set("resource", resource.href);
|
|
46221
|
+
}
|
|
46222
|
+
return { authorizationUrl, codeVerifier };
|
|
46223
|
+
}
|
|
46224
|
+
function prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri) {
|
|
46225
|
+
return new URLSearchParams({
|
|
46226
|
+
grant_type: "authorization_code",
|
|
46227
|
+
code: authorizationCode,
|
|
46228
|
+
code_verifier: codeVerifier,
|
|
46229
|
+
redirect_uri: String(redirectUri)
|
|
46230
|
+
});
|
|
46231
|
+
}
|
|
46232
|
+
async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequestParams, clientInformation, addClientAuthentication, resource, fetchFn }) {
|
|
46233
|
+
const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
|
|
46234
|
+
const headers = new Headers({
|
|
46235
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
46236
|
+
Accept: "application/json"
|
|
46237
|
+
});
|
|
46238
|
+
if (resource) {
|
|
46239
|
+
tokenRequestParams.set("resource", resource.href);
|
|
46240
|
+
}
|
|
46241
|
+
if (addClientAuthentication) {
|
|
46242
|
+
await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
|
|
46243
|
+
} else if (clientInformation) {
|
|
46244
|
+
const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
|
|
46245
|
+
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
46246
|
+
applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
|
|
46247
|
+
}
|
|
46248
|
+
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
46249
|
+
method: "POST",
|
|
46250
|
+
headers,
|
|
46251
|
+
body: tokenRequestParams
|
|
46252
|
+
});
|
|
46253
|
+
if (!response.ok) {
|
|
46254
|
+
throw await parseErrorResponse(response);
|
|
46255
|
+
}
|
|
46256
|
+
return OAuthTokensSchema.parse(await response.json());
|
|
46257
|
+
}
|
|
46258
|
+
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
|
|
46259
|
+
const tokenRequestParams = new URLSearchParams({
|
|
46260
|
+
grant_type: "refresh_token",
|
|
46261
|
+
refresh_token: refreshToken
|
|
46262
|
+
});
|
|
46263
|
+
const tokens = await executeTokenRequest(authorizationServerUrl, {
|
|
46264
|
+
metadata,
|
|
46265
|
+
tokenRequestParams,
|
|
46266
|
+
clientInformation,
|
|
46267
|
+
addClientAuthentication,
|
|
46268
|
+
resource,
|
|
46269
|
+
fetchFn
|
|
46270
|
+
});
|
|
46271
|
+
return { refresh_token: refreshToken, ...tokens };
|
|
46272
|
+
}
|
|
46273
|
+
async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
|
|
46274
|
+
const scope = provider.clientMetadata.scope;
|
|
46275
|
+
let tokenRequestParams;
|
|
46276
|
+
if (provider.prepareTokenRequest) {
|
|
46277
|
+
tokenRequestParams = await provider.prepareTokenRequest(scope);
|
|
46278
|
+
}
|
|
46279
|
+
if (!tokenRequestParams) {
|
|
46280
|
+
if (!authorizationCode) {
|
|
46281
|
+
throw new Error("Either provider.prepareTokenRequest() or authorizationCode is required");
|
|
46282
|
+
}
|
|
46283
|
+
if (!provider.redirectUrl) {
|
|
46284
|
+
throw new Error("redirectUrl is required for authorization_code flow");
|
|
46285
|
+
}
|
|
46286
|
+
const codeVerifier = await provider.codeVerifier();
|
|
46287
|
+
tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
|
|
46288
|
+
}
|
|
46289
|
+
const clientInformation = await provider.clientInformation();
|
|
46290
|
+
return executeTokenRequest(authorizationServerUrl, {
|
|
46291
|
+
metadata,
|
|
46292
|
+
tokenRequestParams,
|
|
46293
|
+
clientInformation: clientInformation ?? undefined,
|
|
46294
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
46295
|
+
resource,
|
|
46296
|
+
fetchFn
|
|
46297
|
+
});
|
|
46298
|
+
}
|
|
46299
|
+
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn }) {
|
|
46300
|
+
let registrationUrl;
|
|
46301
|
+
if (metadata) {
|
|
46302
|
+
if (!metadata.registration_endpoint) {
|
|
46303
|
+
throw new Error("Incompatible auth server: does not support dynamic client registration");
|
|
46304
|
+
}
|
|
46305
|
+
registrationUrl = new URL(metadata.registration_endpoint);
|
|
46306
|
+
} else {
|
|
46307
|
+
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
46308
|
+
}
|
|
46309
|
+
const response = await (fetchFn ?? fetch)(registrationUrl, {
|
|
46310
|
+
method: "POST",
|
|
46311
|
+
headers: {
|
|
46312
|
+
"Content-Type": "application/json"
|
|
46313
|
+
},
|
|
46314
|
+
body: JSON.stringify(clientMetadata)
|
|
46315
|
+
});
|
|
46316
|
+
if (!response.ok) {
|
|
46317
|
+
throw await parseErrorResponse(response);
|
|
46318
|
+
}
|
|
46319
|
+
return OAuthClientInformationFullSchema.parse(await response.json());
|
|
46320
|
+
}
|
|
46321
|
+
|
|
46322
|
+
// node_modules/eventsource-parser/dist/index.js
|
|
46323
|
+
class ParseError2 extends Error {
|
|
46324
|
+
constructor(message, options) {
|
|
46325
|
+
super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
|
|
46326
|
+
}
|
|
46327
|
+
}
|
|
46328
|
+
function noop(_arg) {}
|
|
46329
|
+
function createParser(callbacks) {
|
|
46330
|
+
if (typeof callbacks == "function")
|
|
46331
|
+
throw new TypeError("`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?");
|
|
46332
|
+
const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks;
|
|
46333
|
+
let incompleteLine = "", isFirstChunk = true, id, data = "", eventType = "";
|
|
46334
|
+
function feed(newChunk) {
|
|
46335
|
+
const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`);
|
|
46336
|
+
for (const line of complete)
|
|
46337
|
+
parseLine(line);
|
|
46338
|
+
incompleteLine = incomplete, isFirstChunk = false;
|
|
46339
|
+
}
|
|
46340
|
+
function parseLine(line) {
|
|
46341
|
+
if (line === "") {
|
|
46342
|
+
dispatchEvent();
|
|
46343
|
+
return;
|
|
46344
|
+
}
|
|
46345
|
+
if (line.startsWith(":")) {
|
|
46346
|
+
onComment && onComment(line.slice(line.startsWith(": ") ? 2 : 1));
|
|
46347
|
+
return;
|
|
46348
|
+
}
|
|
46349
|
+
const fieldSeparatorIndex = line.indexOf(":");
|
|
46350
|
+
if (fieldSeparatorIndex !== -1) {
|
|
46351
|
+
const field = line.slice(0, fieldSeparatorIndex), offset = line[fieldSeparatorIndex + 1] === " " ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
|
|
46352
|
+
processField(field, value, line);
|
|
46353
|
+
return;
|
|
46354
|
+
}
|
|
46355
|
+
processField(line, "", line);
|
|
46356
|
+
}
|
|
46357
|
+
function processField(field, value, line) {
|
|
46358
|
+
switch (field) {
|
|
46359
|
+
case "event":
|
|
46360
|
+
eventType = value;
|
|
46361
|
+
break;
|
|
46362
|
+
case "data":
|
|
46363
|
+
data = `${data}${value}
|
|
46364
|
+
`;
|
|
46365
|
+
break;
|
|
46366
|
+
case "id":
|
|
46367
|
+
id = value.includes("\x00") ? undefined : value;
|
|
46368
|
+
break;
|
|
46369
|
+
case "retry":
|
|
46370
|
+
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(new ParseError2(`Invalid \`retry\` value: "${value}"`, {
|
|
46371
|
+
type: "invalid-retry",
|
|
46372
|
+
value,
|
|
46373
|
+
line
|
|
46374
|
+
}));
|
|
46375
|
+
break;
|
|
46376
|
+
default:
|
|
46377
|
+
onError(new ParseError2(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, { type: "unknown-field", field, value, line }));
|
|
46378
|
+
break;
|
|
46379
|
+
}
|
|
46380
|
+
}
|
|
46381
|
+
function dispatchEvent() {
|
|
46382
|
+
data.length > 0 && onEvent({
|
|
46383
|
+
id,
|
|
46384
|
+
event: eventType || undefined,
|
|
46385
|
+
data: data.endsWith(`
|
|
46386
|
+
`) ? data.slice(0, -1) : data
|
|
46387
|
+
}), id = undefined, data = "", eventType = "";
|
|
46388
|
+
}
|
|
46389
|
+
function reset(options = {}) {
|
|
46390
|
+
incompleteLine && options.consume && parseLine(incompleteLine), isFirstChunk = true, id = undefined, data = "", eventType = "", incompleteLine = "";
|
|
46391
|
+
}
|
|
46392
|
+
return { feed, reset };
|
|
46393
|
+
}
|
|
46394
|
+
function splitLines(chunk) {
|
|
46395
|
+
const lines = [];
|
|
46396
|
+
let incompleteLine = "", searchIndex = 0;
|
|
46397
|
+
for (;searchIndex < chunk.length; ) {
|
|
46398
|
+
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
46399
|
+
`, searchIndex);
|
|
46400
|
+
let lineEnd = -1;
|
|
46401
|
+
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
|
|
46402
|
+
incompleteLine = chunk.slice(searchIndex);
|
|
46403
|
+
break;
|
|
46404
|
+
} else {
|
|
46405
|
+
const line = chunk.slice(searchIndex, lineEnd);
|
|
46406
|
+
lines.push(line), searchIndex = lineEnd + 1, chunk[searchIndex - 1] === "\r" && chunk[searchIndex] === `
|
|
46407
|
+
` && searchIndex++;
|
|
46408
|
+
}
|
|
46409
|
+
}
|
|
46410
|
+
return [lines, incompleteLine];
|
|
46411
|
+
}
|
|
46412
|
+
|
|
46413
|
+
// node_modules/eventsource-parser/dist/stream.js
|
|
46414
|
+
class EventSourceParserStream extends TransformStream {
|
|
46415
|
+
constructor({ onError, onRetry, onComment } = {}) {
|
|
46416
|
+
let parser;
|
|
46417
|
+
super({
|
|
46418
|
+
start(controller) {
|
|
46419
|
+
parser = createParser({
|
|
46420
|
+
onEvent: (event) => {
|
|
46421
|
+
controller.enqueue(event);
|
|
46422
|
+
},
|
|
46423
|
+
onError(error45) {
|
|
46424
|
+
onError === "terminate" ? controller.error(error45) : typeof onError == "function" && onError(error45);
|
|
46425
|
+
},
|
|
46426
|
+
onRetry,
|
|
46427
|
+
onComment
|
|
46428
|
+
});
|
|
46429
|
+
},
|
|
46430
|
+
transform(chunk) {
|
|
46431
|
+
parser.feed(chunk);
|
|
46432
|
+
}
|
|
46433
|
+
});
|
|
46434
|
+
}
|
|
46435
|
+
}
|
|
46436
|
+
|
|
46437
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js
|
|
46438
|
+
var DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
|
|
46439
|
+
initialReconnectionDelay: 1000,
|
|
46440
|
+
maxReconnectionDelay: 30000,
|
|
46441
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
46442
|
+
maxRetries: 2
|
|
46443
|
+
};
|
|
46444
|
+
|
|
46445
|
+
class StreamableHTTPError extends Error {
|
|
46446
|
+
constructor(code, message) {
|
|
46447
|
+
super(`Streamable HTTP error: ${message}`);
|
|
46448
|
+
this.code = code;
|
|
46449
|
+
}
|
|
46450
|
+
}
|
|
46451
|
+
|
|
46452
|
+
class StreamableHTTPClientTransport {
|
|
46453
|
+
constructor(url2, opts) {
|
|
46454
|
+
this._hasCompletedAuthFlow = false;
|
|
46455
|
+
this._url = url2;
|
|
46456
|
+
this._resourceMetadataUrl = undefined;
|
|
46457
|
+
this._scope = undefined;
|
|
46458
|
+
this._requestInit = opts?.requestInit;
|
|
46459
|
+
this._authProvider = opts?.authProvider;
|
|
46460
|
+
this._fetch = opts?.fetch;
|
|
46461
|
+
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
|
46462
|
+
this._sessionId = opts?.sessionId;
|
|
46463
|
+
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
|
46464
|
+
}
|
|
46465
|
+
async _authThenStart() {
|
|
46466
|
+
if (!this._authProvider) {
|
|
46467
|
+
throw new UnauthorizedError("No auth provider");
|
|
46468
|
+
}
|
|
46469
|
+
let result;
|
|
46470
|
+
try {
|
|
46471
|
+
result = await auth(this._authProvider, {
|
|
46472
|
+
serverUrl: this._url,
|
|
46473
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
46474
|
+
scope: this._scope,
|
|
46475
|
+
fetchFn: this._fetchWithInit
|
|
46476
|
+
});
|
|
46477
|
+
} catch (error45) {
|
|
46478
|
+
this.onerror?.(error45);
|
|
46479
|
+
throw error45;
|
|
46480
|
+
}
|
|
46481
|
+
if (result !== "AUTHORIZED") {
|
|
46482
|
+
throw new UnauthorizedError;
|
|
46483
|
+
}
|
|
46484
|
+
return await this._startOrAuthSse({ resumptionToken: undefined });
|
|
46485
|
+
}
|
|
46486
|
+
async _commonHeaders() {
|
|
46487
|
+
const headers = {};
|
|
46488
|
+
if (this._authProvider) {
|
|
46489
|
+
const tokens = await this._authProvider.tokens();
|
|
46490
|
+
if (tokens) {
|
|
46491
|
+
headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
46492
|
+
}
|
|
46493
|
+
}
|
|
46494
|
+
if (this._sessionId) {
|
|
46495
|
+
headers["mcp-session-id"] = this._sessionId;
|
|
46496
|
+
}
|
|
46497
|
+
if (this._protocolVersion) {
|
|
46498
|
+
headers["mcp-protocol-version"] = this._protocolVersion;
|
|
46499
|
+
}
|
|
46500
|
+
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
|
|
46501
|
+
return new Headers({
|
|
46502
|
+
...headers,
|
|
46503
|
+
...extraHeaders
|
|
46504
|
+
});
|
|
46505
|
+
}
|
|
46506
|
+
async _startOrAuthSse(options) {
|
|
46507
|
+
const { resumptionToken } = options;
|
|
46508
|
+
try {
|
|
46509
|
+
const headers = await this._commonHeaders();
|
|
46510
|
+
headers.set("Accept", "text/event-stream");
|
|
46511
|
+
if (resumptionToken) {
|
|
46512
|
+
headers.set("last-event-id", resumptionToken);
|
|
46513
|
+
}
|
|
46514
|
+
const response = await (this._fetch ?? fetch)(this._url, {
|
|
46515
|
+
method: "GET",
|
|
46516
|
+
headers,
|
|
46517
|
+
signal: this._abortController?.signal
|
|
46518
|
+
});
|
|
46519
|
+
if (!response.ok) {
|
|
46520
|
+
await response.body?.cancel();
|
|
46521
|
+
if (response.status === 401 && this._authProvider) {
|
|
46522
|
+
return await this._authThenStart();
|
|
46523
|
+
}
|
|
46524
|
+
if (response.status === 405) {
|
|
46525
|
+
return;
|
|
46526
|
+
}
|
|
46527
|
+
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
46528
|
+
}
|
|
46529
|
+
this._handleSseStream(response.body, options, true);
|
|
46530
|
+
} catch (error45) {
|
|
46531
|
+
this.onerror?.(error45);
|
|
46532
|
+
throw error45;
|
|
46533
|
+
}
|
|
46534
|
+
}
|
|
46535
|
+
_getNextReconnectionDelay(attempt) {
|
|
46536
|
+
if (this._serverRetryMs !== undefined) {
|
|
46537
|
+
return this._serverRetryMs;
|
|
46538
|
+
}
|
|
46539
|
+
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
|
|
46540
|
+
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
|
|
46541
|
+
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
|
|
46542
|
+
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
|
|
46543
|
+
}
|
|
46544
|
+
_scheduleReconnection(options, attemptCount = 0) {
|
|
46545
|
+
const maxRetries = this._reconnectionOptions.maxRetries;
|
|
46546
|
+
if (attemptCount >= maxRetries) {
|
|
46547
|
+
this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
|
|
46548
|
+
return;
|
|
46549
|
+
}
|
|
46550
|
+
const delay2 = this._getNextReconnectionDelay(attemptCount);
|
|
46551
|
+
this._reconnectionTimeout = setTimeout(() => {
|
|
46552
|
+
this._startOrAuthSse(options).catch((error45) => {
|
|
46553
|
+
this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error45 instanceof Error ? error45.message : String(error45)}`));
|
|
46554
|
+
this._scheduleReconnection(options, attemptCount + 1);
|
|
46555
|
+
});
|
|
46556
|
+
}, delay2);
|
|
46557
|
+
}
|
|
46558
|
+
_handleSseStream(stream, options, isReconnectable) {
|
|
46559
|
+
if (!stream) {
|
|
46560
|
+
return;
|
|
46561
|
+
}
|
|
46562
|
+
const { onresumptiontoken, replayMessageId } = options;
|
|
46563
|
+
let lastEventId;
|
|
46564
|
+
let hasPrimingEvent = false;
|
|
46565
|
+
let receivedResponse = false;
|
|
46566
|
+
const processStream = async () => {
|
|
46567
|
+
try {
|
|
46568
|
+
const reader = stream.pipeThrough(new TextDecoderStream).pipeThrough(new EventSourceParserStream({
|
|
46569
|
+
onRetry: (retryMs) => {
|
|
46570
|
+
this._serverRetryMs = retryMs;
|
|
46571
|
+
}
|
|
46572
|
+
})).getReader();
|
|
46573
|
+
while (true) {
|
|
46574
|
+
const { value: event, done } = await reader.read();
|
|
46575
|
+
if (done) {
|
|
46576
|
+
break;
|
|
46577
|
+
}
|
|
46578
|
+
if (event.id) {
|
|
46579
|
+
lastEventId = event.id;
|
|
46580
|
+
hasPrimingEvent = true;
|
|
46581
|
+
onresumptiontoken?.(event.id);
|
|
46582
|
+
}
|
|
46583
|
+
if (!event.data) {
|
|
46584
|
+
continue;
|
|
46585
|
+
}
|
|
46586
|
+
if (!event.event || event.event === "message") {
|
|
46587
|
+
try {
|
|
46588
|
+
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
|
46589
|
+
if (isJSONRPCResultResponse(message)) {
|
|
46590
|
+
receivedResponse = true;
|
|
46591
|
+
if (replayMessageId !== undefined) {
|
|
46592
|
+
message.id = replayMessageId;
|
|
46593
|
+
}
|
|
46594
|
+
}
|
|
46595
|
+
this.onmessage?.(message);
|
|
46596
|
+
} catch (error45) {
|
|
46597
|
+
this.onerror?.(error45);
|
|
46598
|
+
}
|
|
46599
|
+
}
|
|
46600
|
+
}
|
|
46601
|
+
const canResume = isReconnectable || hasPrimingEvent;
|
|
46602
|
+
const needsReconnect = canResume && !receivedResponse;
|
|
46603
|
+
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
|
46604
|
+
this._scheduleReconnection({
|
|
46605
|
+
resumptionToken: lastEventId,
|
|
46606
|
+
onresumptiontoken,
|
|
46607
|
+
replayMessageId
|
|
46608
|
+
}, 0);
|
|
46609
|
+
}
|
|
46610
|
+
} catch (error45) {
|
|
46611
|
+
this.onerror?.(new Error(`SSE stream disconnected: ${error45}`));
|
|
46612
|
+
const canResume = isReconnectable || hasPrimingEvent;
|
|
46613
|
+
const needsReconnect = canResume && !receivedResponse;
|
|
46614
|
+
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
|
46615
|
+
try {
|
|
46616
|
+
this._scheduleReconnection({
|
|
46617
|
+
resumptionToken: lastEventId,
|
|
46618
|
+
onresumptiontoken,
|
|
46619
|
+
replayMessageId
|
|
46620
|
+
}, 0);
|
|
46621
|
+
} catch (error46) {
|
|
46622
|
+
this.onerror?.(new Error(`Failed to reconnect: ${error46 instanceof Error ? error46.message : String(error46)}`));
|
|
46623
|
+
}
|
|
46624
|
+
}
|
|
46625
|
+
}
|
|
46626
|
+
};
|
|
46627
|
+
processStream();
|
|
46628
|
+
}
|
|
46629
|
+
async start() {
|
|
46630
|
+
if (this._abortController) {
|
|
46631
|
+
throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
46632
|
+
}
|
|
46633
|
+
this._abortController = new AbortController;
|
|
46634
|
+
}
|
|
46635
|
+
async finishAuth(authorizationCode) {
|
|
46636
|
+
if (!this._authProvider) {
|
|
46637
|
+
throw new UnauthorizedError("No auth provider");
|
|
46638
|
+
}
|
|
46639
|
+
const result = await auth(this._authProvider, {
|
|
46640
|
+
serverUrl: this._url,
|
|
46641
|
+
authorizationCode,
|
|
46642
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
46643
|
+
scope: this._scope,
|
|
46644
|
+
fetchFn: this._fetchWithInit
|
|
46645
|
+
});
|
|
46646
|
+
if (result !== "AUTHORIZED") {
|
|
46647
|
+
throw new UnauthorizedError("Failed to authorize");
|
|
46648
|
+
}
|
|
46649
|
+
}
|
|
46650
|
+
async close() {
|
|
46651
|
+
if (this._reconnectionTimeout) {
|
|
46652
|
+
clearTimeout(this._reconnectionTimeout);
|
|
46653
|
+
this._reconnectionTimeout = undefined;
|
|
46654
|
+
}
|
|
46655
|
+
this._abortController?.abort();
|
|
46656
|
+
this.onclose?.();
|
|
46657
|
+
}
|
|
46658
|
+
async send(message, options) {
|
|
46659
|
+
try {
|
|
46660
|
+
const { resumptionToken, onresumptiontoken } = options || {};
|
|
46661
|
+
if (resumptionToken) {
|
|
46662
|
+
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch((err) => this.onerror?.(err));
|
|
46663
|
+
return;
|
|
46664
|
+
}
|
|
46665
|
+
const headers = await this._commonHeaders();
|
|
46666
|
+
headers.set("content-type", "application/json");
|
|
46667
|
+
headers.set("accept", "application/json, text/event-stream");
|
|
46668
|
+
const init = {
|
|
46669
|
+
...this._requestInit,
|
|
46670
|
+
method: "POST",
|
|
46671
|
+
headers,
|
|
46672
|
+
body: JSON.stringify(message),
|
|
46673
|
+
signal: this._abortController?.signal
|
|
46674
|
+
};
|
|
46675
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
46676
|
+
const sessionId = response.headers.get("mcp-session-id");
|
|
46677
|
+
if (sessionId) {
|
|
46678
|
+
this._sessionId = sessionId;
|
|
46679
|
+
}
|
|
46680
|
+
if (!response.ok) {
|
|
46681
|
+
const text = await response.text().catch(() => null);
|
|
46682
|
+
if (response.status === 401 && this._authProvider) {
|
|
46683
|
+
if (this._hasCompletedAuthFlow) {
|
|
46684
|
+
throw new StreamableHTTPError(401, "Server returned 401 after successful authentication");
|
|
46685
|
+
}
|
|
46686
|
+
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
|
46687
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
46688
|
+
this._scope = scope;
|
|
46689
|
+
const result = await auth(this._authProvider, {
|
|
46690
|
+
serverUrl: this._url,
|
|
46691
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
46692
|
+
scope: this._scope,
|
|
46693
|
+
fetchFn: this._fetchWithInit
|
|
46694
|
+
});
|
|
46695
|
+
if (result !== "AUTHORIZED") {
|
|
46696
|
+
throw new UnauthorizedError;
|
|
46697
|
+
}
|
|
46698
|
+
this._hasCompletedAuthFlow = true;
|
|
46699
|
+
return this.send(message);
|
|
46700
|
+
}
|
|
46701
|
+
if (response.status === 403 && this._authProvider) {
|
|
46702
|
+
const { resourceMetadataUrl, scope, error: error45 } = extractWWWAuthenticateParams(response);
|
|
46703
|
+
if (error45 === "insufficient_scope") {
|
|
46704
|
+
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
46705
|
+
if (this._lastUpscopingHeader === wwwAuthHeader) {
|
|
46706
|
+
throw new StreamableHTTPError(403, "Server returned 403 after trying upscoping");
|
|
46707
|
+
}
|
|
46708
|
+
if (scope) {
|
|
46709
|
+
this._scope = scope;
|
|
46710
|
+
}
|
|
46711
|
+
if (resourceMetadataUrl) {
|
|
46712
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
46713
|
+
}
|
|
46714
|
+
this._lastUpscopingHeader = wwwAuthHeader ?? undefined;
|
|
46715
|
+
const result = await auth(this._authProvider, {
|
|
46716
|
+
serverUrl: this._url,
|
|
46717
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
46718
|
+
scope: this._scope,
|
|
46719
|
+
fetchFn: this._fetch
|
|
46720
|
+
});
|
|
46721
|
+
if (result !== "AUTHORIZED") {
|
|
46722
|
+
throw new UnauthorizedError;
|
|
46723
|
+
}
|
|
46724
|
+
return this.send(message);
|
|
46725
|
+
}
|
|
46726
|
+
}
|
|
46727
|
+
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
|
46728
|
+
}
|
|
46729
|
+
this._hasCompletedAuthFlow = false;
|
|
46730
|
+
this._lastUpscopingHeader = undefined;
|
|
46731
|
+
if (response.status === 202) {
|
|
46732
|
+
await response.body?.cancel();
|
|
46733
|
+
if (isInitializedNotification(message)) {
|
|
46734
|
+
this._startOrAuthSse({ resumptionToken: undefined }).catch((err) => this.onerror?.(err));
|
|
46735
|
+
}
|
|
46736
|
+
return;
|
|
46737
|
+
}
|
|
46738
|
+
const messages = Array.isArray(message) ? message : [message];
|
|
46739
|
+
const hasRequests = messages.filter((msg) => ("method" in msg) && ("id" in msg) && msg.id !== undefined).length > 0;
|
|
46740
|
+
const contentType = response.headers.get("content-type");
|
|
46741
|
+
if (hasRequests) {
|
|
46742
|
+
if (contentType?.includes("text/event-stream")) {
|
|
46743
|
+
this._handleSseStream(response.body, { onresumptiontoken }, false);
|
|
46744
|
+
} else if (contentType?.includes("application/json")) {
|
|
46745
|
+
const data = await response.json();
|
|
46746
|
+
const responseMessages = Array.isArray(data) ? data.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(data)];
|
|
46747
|
+
for (const msg of responseMessages) {
|
|
46748
|
+
this.onmessage?.(msg);
|
|
46749
|
+
}
|
|
46750
|
+
} else {
|
|
46751
|
+
await response.body?.cancel();
|
|
46752
|
+
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
|
|
46753
|
+
}
|
|
46754
|
+
} else {
|
|
46755
|
+
await response.body?.cancel();
|
|
46756
|
+
}
|
|
46757
|
+
} catch (error45) {
|
|
46758
|
+
this.onerror?.(error45);
|
|
46759
|
+
throw error45;
|
|
46760
|
+
}
|
|
46761
|
+
}
|
|
46762
|
+
get sessionId() {
|
|
46763
|
+
return this._sessionId;
|
|
46764
|
+
}
|
|
46765
|
+
async terminateSession() {
|
|
46766
|
+
if (!this._sessionId) {
|
|
46767
|
+
return;
|
|
46768
|
+
}
|
|
46769
|
+
try {
|
|
46770
|
+
const headers = await this._commonHeaders();
|
|
46771
|
+
const init = {
|
|
46772
|
+
...this._requestInit,
|
|
46773
|
+
method: "DELETE",
|
|
46774
|
+
headers,
|
|
46775
|
+
signal: this._abortController?.signal
|
|
46776
|
+
};
|
|
46777
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
46778
|
+
await response.body?.cancel();
|
|
46779
|
+
if (!response.ok && response.status !== 405) {
|
|
46780
|
+
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
|
|
46781
|
+
}
|
|
46782
|
+
this._sessionId = undefined;
|
|
46783
|
+
} catch (error45) {
|
|
46784
|
+
this.onerror?.(error45);
|
|
46785
|
+
throw error45;
|
|
46786
|
+
}
|
|
46787
|
+
}
|
|
46788
|
+
setProtocolVersion(version2) {
|
|
46789
|
+
this._protocolVersion = version2;
|
|
46790
|
+
}
|
|
46791
|
+
get protocolVersion() {
|
|
46792
|
+
return this._protocolVersion;
|
|
46793
|
+
}
|
|
46794
|
+
async resumeStream(lastEventId, options) {
|
|
46795
|
+
await this._startOrAuthSse({
|
|
46796
|
+
resumptionToken: lastEventId,
|
|
46797
|
+
onresumptiontoken: options?.onresumptiontoken
|
|
46798
|
+
});
|
|
46799
|
+
}
|
|
46800
|
+
}
|
|
46801
|
+
|
|
45040
46802
|
// src/features/skill-mcp-manager/env-cleaner.ts
|
|
45041
46803
|
var EXCLUDED_ENV_PATTERNS = [
|
|
45042
46804
|
/^NPM_CONFIG_/i,
|
|
@@ -45060,6 +46822,22 @@ function createCleanMcpEnvironment(customEnv = {}) {
|
|
|
45060
46822
|
}
|
|
45061
46823
|
|
|
45062
46824
|
// src/features/skill-mcp-manager/manager.ts
|
|
46825
|
+
function getConnectionType(config3) {
|
|
46826
|
+
if (config3.type === "http" || config3.type === "sse") {
|
|
46827
|
+
return "http";
|
|
46828
|
+
}
|
|
46829
|
+
if (config3.type === "stdio") {
|
|
46830
|
+
return "stdio";
|
|
46831
|
+
}
|
|
46832
|
+
if (config3.url) {
|
|
46833
|
+
return "http";
|
|
46834
|
+
}
|
|
46835
|
+
if (config3.command) {
|
|
46836
|
+
return "stdio";
|
|
46837
|
+
}
|
|
46838
|
+
return null;
|
|
46839
|
+
}
|
|
46840
|
+
|
|
45063
46841
|
class SkillMcpManager {
|
|
45064
46842
|
clients = new Map;
|
|
45065
46843
|
pendingConnections = new Map;
|
|
@@ -45122,17 +46900,88 @@ class SkillMcpManager {
|
|
|
45122
46900
|
}
|
|
45123
46901
|
}
|
|
45124
46902
|
async createClient(info, config3) {
|
|
46903
|
+
const connectionType = getConnectionType(config3);
|
|
46904
|
+
if (!connectionType) {
|
|
46905
|
+
throw new Error(`MCP server "${info.serverName}" has no valid connection configuration.
|
|
46906
|
+
|
|
46907
|
+
` + `The MCP configuration in skill "${info.skillName}" must specify either:
|
|
46908
|
+
` + ` - A URL for HTTP connection (remote MCP server)
|
|
46909
|
+
` + ` - A command for stdio connection (local MCP process)
|
|
46910
|
+
|
|
46911
|
+
` + `Examples:
|
|
46912
|
+
` + ` HTTP:
|
|
46913
|
+
` + ` mcp:
|
|
46914
|
+
` + ` ${info.serverName}:
|
|
46915
|
+
` + ` url: https://mcp.example.com/mcp
|
|
46916
|
+
` + ` headers:
|
|
46917
|
+
` + ` Authorization: Bearer \${API_KEY}
|
|
46918
|
+
|
|
46919
|
+
` + ` Stdio:
|
|
46920
|
+
` + ` mcp:
|
|
46921
|
+
` + ` ${info.serverName}:
|
|
46922
|
+
` + ` command: npx
|
|
46923
|
+
` + ` args: [-y, @some/mcp-server]`);
|
|
46924
|
+
}
|
|
46925
|
+
if (connectionType === "http") {
|
|
46926
|
+
return this.createHttpClient(info, config3);
|
|
46927
|
+
} else {
|
|
46928
|
+
return this.createStdioClient(info, config3);
|
|
46929
|
+
}
|
|
46930
|
+
}
|
|
46931
|
+
async createHttpClient(info, config3) {
|
|
45125
46932
|
const key = this.getClientKey(info);
|
|
45126
|
-
if (!config3.
|
|
45127
|
-
throw new Error(`MCP server "${info.serverName}" is missing
|
|
46933
|
+
if (!config3.url) {
|
|
46934
|
+
throw new Error(`MCP server "${info.serverName}" is configured for HTTP but missing 'url' field.`);
|
|
46935
|
+
}
|
|
46936
|
+
let url2;
|
|
46937
|
+
try {
|
|
46938
|
+
url2 = new URL(config3.url);
|
|
46939
|
+
} catch {
|
|
46940
|
+
throw new Error(`MCP server "${info.serverName}" has invalid URL: ${config3.url}
|
|
45128
46941
|
|
|
45129
|
-
` + `
|
|
46942
|
+
` + `Expected a valid URL like: https://mcp.example.com/mcp`);
|
|
46943
|
+
}
|
|
46944
|
+
this.registerProcessCleanup();
|
|
46945
|
+
const requestInit = {};
|
|
46946
|
+
if (config3.headers && Object.keys(config3.headers).length > 0) {
|
|
46947
|
+
requestInit.headers = config3.headers;
|
|
46948
|
+
}
|
|
46949
|
+
const transport = new StreamableHTTPClientTransport(url2, {
|
|
46950
|
+
requestInit: Object.keys(requestInit).length > 0 ? requestInit : undefined
|
|
46951
|
+
});
|
|
46952
|
+
const client2 = new Client({ name: `skill-mcp-${info.skillName}-${info.serverName}`, version: "1.0.0" }, { capabilities: {} });
|
|
46953
|
+
try {
|
|
46954
|
+
await client2.connect(transport);
|
|
46955
|
+
} catch (error45) {
|
|
46956
|
+
try {
|
|
46957
|
+
await transport.close();
|
|
46958
|
+
} catch {}
|
|
46959
|
+
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
46960
|
+
throw new Error(`Failed to connect to MCP server "${info.serverName}".
|
|
45130
46961
|
|
|
45131
|
-
` + `
|
|
45132
|
-
` + `
|
|
45133
|
-
|
|
45134
|
-
` + `
|
|
45135
|
-
` + `
|
|
46962
|
+
` + `URL: ${config3.url}
|
|
46963
|
+
` + `Reason: ${errorMessage}
|
|
46964
|
+
|
|
46965
|
+
` + `Hints:
|
|
46966
|
+
` + ` - Verify the URL is correct and the server is running
|
|
46967
|
+
` + ` - Check if authentication headers are required
|
|
46968
|
+
` + ` - Ensure the server supports MCP over HTTP`);
|
|
46969
|
+
}
|
|
46970
|
+
const managedClient = {
|
|
46971
|
+
client: client2,
|
|
46972
|
+
transport,
|
|
46973
|
+
skillName: info.skillName,
|
|
46974
|
+
lastUsedAt: Date.now(),
|
|
46975
|
+
connectionType: "http"
|
|
46976
|
+
};
|
|
46977
|
+
this.clients.set(key, managedClient);
|
|
46978
|
+
this.startCleanupTimer();
|
|
46979
|
+
return client2;
|
|
46980
|
+
}
|
|
46981
|
+
async createStdioClient(info, config3) {
|
|
46982
|
+
const key = this.getClientKey(info);
|
|
46983
|
+
if (!config3.command) {
|
|
46984
|
+
throw new Error(`MCP server "${info.serverName}" is configured for stdio but missing 'command' field.`);
|
|
45136
46985
|
}
|
|
45137
46986
|
const command = config3.command;
|
|
45138
46987
|
const args = config3.args || [];
|
|
@@ -45162,7 +47011,14 @@ class SkillMcpManager {
|
|
|
45162
47011
|
` + ` - Check if the MCP server package exists
|
|
45163
47012
|
` + ` - Verify the args are correct for this server`);
|
|
45164
47013
|
}
|
|
45165
|
-
|
|
47014
|
+
const managedClient = {
|
|
47015
|
+
client: client2,
|
|
47016
|
+
transport,
|
|
47017
|
+
skillName: info.skillName,
|
|
47018
|
+
lastUsedAt: Date.now(),
|
|
47019
|
+
connectionType: "stdio"
|
|
47020
|
+
};
|
|
47021
|
+
this.clients.set(key, managedClient);
|
|
45166
47022
|
this.startCleanupTimer();
|
|
45167
47023
|
return client2;
|
|
45168
47024
|
}
|
|
@@ -45365,6 +47221,7 @@ var HookNameSchema = exports_external.enum([
|
|
|
45365
47221
|
"claude-code-hooks",
|
|
45366
47222
|
"auto-slash-command",
|
|
45367
47223
|
"edit-error-recovery",
|
|
47224
|
+
"sisyphus-task-retry",
|
|
45368
47225
|
"prometheus-md-only",
|
|
45369
47226
|
"start-work",
|
|
45370
47227
|
"sisyphus-orchestrator"
|
|
@@ -45828,28 +47685,55 @@ function buildFrontendSection(agents) {
|
|
|
45828
47685
|
const frontendAgent = agents.find((a) => a.name === "frontend-ui-ux-engineer");
|
|
45829
47686
|
if (!frontendAgent)
|
|
45830
47687
|
return "";
|
|
45831
|
-
return `### Frontend Files:
|
|
47688
|
+
return `### Frontend Files: VISUAL = HARD BLOCK (zero tolerance)
|
|
45832
47689
|
|
|
45833
|
-
|
|
47690
|
+
**DEFAULT ASSUMPTION**: Any frontend file change is VISUAL until proven otherwise.
|
|
45834
47691
|
|
|
45835
|
-
####
|
|
47692
|
+
#### HARD BLOCK: Visual Changes (NEVER touch directly)
|
|
45836
47693
|
|
|
45837
|
-
|
|
|
45838
|
-
|
|
45839
|
-
|
|
|
45840
|
-
|
|
|
45841
|
-
|
|
|
47694
|
+
| Pattern | Action | No Exceptions |
|
|
47695
|
+
|---------|--------|---------------|
|
|
47696
|
+
| \`.tsx\`, \`.jsx\` with styling | DELEGATE | Even "just add className" |
|
|
47697
|
+
| \`.vue\`, \`.svelte\` | DELEGATE | Even single prop change |
|
|
47698
|
+
| \`.css\`, \`.scss\`, \`.sass\`, \`.less\` | DELEGATE | Even color/margin tweak |
|
|
47699
|
+
| Any file with visual keywords | DELEGATE | See keyword list below |
|
|
45842
47700
|
|
|
45843
|
-
####
|
|
47701
|
+
#### Keyword Detection (INSTANT DELEGATE)
|
|
45844
47702
|
|
|
45845
|
-
|
|
45846
|
-
> "Is this change about **how it LOOKS** or **how it WORKS**?"
|
|
47703
|
+
If your change involves **ANY** of these keywords \u2192 **STOP. DELEGATE.**
|
|
45847
47704
|
|
|
45848
|
-
|
|
45849
|
-
|
|
47705
|
+
\`\`\`
|
|
47706
|
+
style, className, tailwind, css, color, background, border, shadow,
|
|
47707
|
+
margin, padding, width, height, flex, grid, animation, transition,
|
|
47708
|
+
hover, responsive, font-size, font-weight, icon, svg, image, layout,
|
|
47709
|
+
position, display, opacity, z-index, transform, gradient, theme
|
|
47710
|
+
\`\`\`
|
|
45850
47711
|
|
|
45851
|
-
|
|
45852
|
-
|
|
47712
|
+
**YOU CANNOT**:
|
|
47713
|
+
- "Just quickly fix this style"
|
|
47714
|
+
- "It's only one className"
|
|
47715
|
+
- "Too simple to delegate"
|
|
47716
|
+
|
|
47717
|
+
#### EXCEPTION: Pure Logic Only
|
|
47718
|
+
|
|
47719
|
+
You MAY handle directly **ONLY IF ALL** conditions are met:
|
|
47720
|
+
1. Change is **100% logic** (API, state, event handlers, types, utils)
|
|
47721
|
+
2. **Zero** visual keywords in your diff
|
|
47722
|
+
3. No styling, layout, or appearance changes whatsoever
|
|
47723
|
+
|
|
47724
|
+
| Pure Logic Examples | Visual Examples (DELEGATE) |
|
|
47725
|
+
|---------------------|---------------------------|
|
|
47726
|
+
| Add onClick API call | Change button color |
|
|
47727
|
+
| Fix pagination logic | Add loading spinner animation |
|
|
47728
|
+
| Add form validation | Make modal responsive |
|
|
47729
|
+
| Update state management | Adjust spacing/margins |
|
|
47730
|
+
|
|
47731
|
+
#### Mixed Changes \u2192 SPLIT
|
|
47732
|
+
|
|
47733
|
+
If change has BOTH logic AND visual:
|
|
47734
|
+
1. Handle logic yourself
|
|
47735
|
+
2. DELEGATE visual part to \`frontend-ui-ux-engineer\`
|
|
47736
|
+
3. **Never** combine them into one edit`;
|
|
45853
47737
|
}
|
|
45854
47738
|
function buildOracleSection(agents) {
|
|
45855
47739
|
const oracleAgent = agents.find((a) => a.name === "oracle");
|
|
@@ -45889,7 +47773,7 @@ function buildHardBlocksSection(agents) {
|
|
|
45889
47773
|
"| Leave code in broken state after failures | Never |"
|
|
45890
47774
|
];
|
|
45891
47775
|
if (frontendAgent) {
|
|
45892
|
-
blocks.unshift("| Frontend VISUAL changes (styling, layout, animation) | Always delegate to `frontend-ui-ux-engineer
|
|
47776
|
+
blocks.unshift("| Frontend VISUAL changes (styling, className, layout, animation, any visual keyword) | **HARD BLOCK** - Always delegate to `frontend-ui-ux-engineer`. Zero tolerance. |");
|
|
45893
47777
|
}
|
|
45894
47778
|
return `## Hard Blocks (NEVER violate)
|
|
45895
47779
|
|
|
@@ -45908,7 +47792,7 @@ function buildAntiPatternsSection(agents) {
|
|
|
45908
47792
|
"| **Debugging** | Shotgun debugging, random changes |"
|
|
45909
47793
|
];
|
|
45910
47794
|
if (frontendAgent) {
|
|
45911
|
-
patterns.splice(4, 0, "| **Frontend** |
|
|
47795
|
+
patterns.splice(4, 0, "| **Frontend** | ANY direct edit to visual/styling code. Keyword detected = DELEGATE. Pure logic only = OK |");
|
|
45912
47796
|
}
|
|
45913
47797
|
return `## Anti-Patterns (BLOCKING violations)
|
|
45914
47798
|
|
|
@@ -49618,7 +51502,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
49618
51502
|
}
|
|
49619
51503
|
if (!disabledAgents.includes("orchestrator-sisyphus")) {
|
|
49620
51504
|
const orchestratorOverride = agentOverrides["orchestrator-sisyphus"];
|
|
49621
|
-
const orchestratorModel = orchestratorOverride?.model;
|
|
51505
|
+
const orchestratorModel = orchestratorOverride?.model ?? systemDefaultModel;
|
|
49622
51506
|
let orchestratorConfig = createOrchestratorSisyphusAgent({
|
|
49623
51507
|
model: orchestratorModel,
|
|
49624
51508
|
availableAgents
|
|
@@ -49705,11 +51589,11 @@ var SISYPHUS_JUNIOR_DEFAULTS = {
|
|
|
49705
51589
|
model: "anthropic/claude-sonnet-4-5",
|
|
49706
51590
|
temperature: 0.1
|
|
49707
51591
|
};
|
|
49708
|
-
function createSisyphusJuniorAgentWithOverrides(override) {
|
|
51592
|
+
function createSisyphusJuniorAgentWithOverrides(override, systemDefaultModel) {
|
|
49709
51593
|
if (override?.disable) {
|
|
49710
51594
|
override = undefined;
|
|
49711
51595
|
}
|
|
49712
|
-
const model = override?.model ?? SISYPHUS_JUNIOR_DEFAULTS.model;
|
|
51596
|
+
const model = override?.model ?? systemDefaultModel ?? SISYPHUS_JUNIOR_DEFAULTS.model;
|
|
49713
51597
|
const temperature = override?.temperature ?? SISYPHUS_JUNIOR_DEFAULTS.temperature;
|
|
49714
51598
|
const promptAppend = override?.prompt_append;
|
|
49715
51599
|
const prompt = buildSisyphusJuniorPrompt(promptAppend);
|
|
@@ -52485,7 +54369,7 @@ function createConfigHandler(deps) {
|
|
|
52485
54369
|
const agentConfig = {
|
|
52486
54370
|
Sisyphus: builtinAgents.Sisyphus
|
|
52487
54371
|
};
|
|
52488
|
-
agentConfig["Sisyphus-Junior"] = createSisyphusJuniorAgentWithOverrides(pluginConfig.agents?.["Sisyphus-Junior"]);
|
|
54372
|
+
agentConfig["Sisyphus-Junior"] = createSisyphusJuniorAgentWithOverrides(pluginConfig.agents?.["Sisyphus-Junior"], config3.model);
|
|
52489
54373
|
if (builderEnabled) {
|
|
52490
54374
|
const { name: _buildName, ...buildConfigWithoutName } = configAgent?.build ?? {};
|
|
52491
54375
|
const migratedBuildConfig = migrateAgentConfig(buildConfigWithoutName);
|
|
@@ -52697,6 +54581,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
52697
54581
|
checkSessionExists: async (sessionId) => sessionExists(sessionId)
|
|
52698
54582
|
}) : null;
|
|
52699
54583
|
const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
|
|
54584
|
+
const sisyphusTaskRetry = isHookEnabled("sisyphus-task-retry") ? createSisyphusTaskRetryHook(ctx) : null;
|
|
52700
54585
|
const startWork = isHookEnabled("start-work") ? createStartWorkHook(ctx) : null;
|
|
52701
54586
|
const sisyphusOrchestrator = isHookEnabled("sisyphus-orchestrator") ? createSisyphusOrchestratorHook(ctx) : null;
|
|
52702
54587
|
const prometheusMdOnly = isHookEnabled("prometheus-md-only") ? createPrometheusMdOnlyHook(ctx) : null;
|
|
@@ -52859,6 +54744,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
52859
54744
|
}
|
|
52860
54745
|
if (sessionInfo?.id) {
|
|
52861
54746
|
clearSessionAgent(sessionInfo.id);
|
|
54747
|
+
resetMessageCursor(sessionInfo.id);
|
|
52862
54748
|
firstMessageVariantGate.clear(sessionInfo.id);
|
|
52863
54749
|
await skillMcpManager.disconnectSession(sessionInfo.id);
|
|
52864
54750
|
await lspManager.cleanupTempDirectoryClients();
|
|
@@ -52943,6 +54829,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
52943
54829
|
await agentUsageReminder?.["tool.execute.after"](input, output);
|
|
52944
54830
|
await interactiveBashSession?.["tool.execute.after"](input, output);
|
|
52945
54831
|
await editErrorRecovery?.["tool.execute.after"](input, output);
|
|
54832
|
+
await sisyphusTaskRetry?.["tool.execute.after"](input, output);
|
|
52946
54833
|
await sisyphusOrchestrator?.["tool.execute.after"]?.(input, output);
|
|
52947
54834
|
await taskResumeInfo["tool.execute.after"](input, output);
|
|
52948
54835
|
}
|