oh-my-opencode 2.6.2 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +81 -5
- package/README.ko.md +84 -5
- package/README.md +20 -4
- package/README.zh-cn.md +85 -5
- package/dist/auth/antigravity/project.d.ts +1 -0
- package/dist/auth/antigravity/token.d.ts +17 -20
- package/dist/auth/antigravity/types.d.ts +19 -0
- package/dist/cli/index.js +10 -4
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +16 -2
- package/dist/features/builtin-commands/commands.d.ts +2 -0
- package/dist/features/builtin-commands/index.d.ts +2 -0
- package/dist/features/builtin-commands/templates/init-deep.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +6 -0
- package/dist/google-auth.js +152 -22
- package/dist/hooks/comment-checker/cli.d.ts +2 -1
- package/dist/hooks/comment-checker/index.d.ts +2 -1
- package/dist/hooks/todo-continuation-enforcer.test.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1398 -681
- package/dist/shared/data-path.d.ts +6 -7
- package/dist/tools/ast-grep/constants.d.ts +0 -1
- package/dist/tools/lsp/client.d.ts +1 -2
- package/dist/tools/lsp/config.d.ts +2 -9
- package/dist/tools/lsp/constants.d.ts +1 -0
- package/dist/tools/lsp/types.d.ts +25 -0
- package/dist/tools/lsp/utils.d.ts +4 -1
- package/dist/tools/session-manager/storage.d.ts +5 -5
- package/dist/tools/session-manager/utils.d.ts +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -222,8 +222,8 @@ var require_utils = __commonJS((exports) => {
|
|
|
222
222
|
}
|
|
223
223
|
return output;
|
|
224
224
|
};
|
|
225
|
-
exports.basename = (
|
|
226
|
-
const segs =
|
|
225
|
+
exports.basename = (path4, { windows } = {}) => {
|
|
226
|
+
const segs = path4.split(windows ? /[\\/]/ : "/");
|
|
227
227
|
const last = segs[segs.length - 1];
|
|
228
228
|
if (last === "") {
|
|
229
229
|
return segs[segs.length - 2];
|
|
@@ -3276,9 +3276,6 @@ function getUserConfigDir() {
|
|
|
3276
3276
|
import * as path3 from "path";
|
|
3277
3277
|
import * as os3 from "os";
|
|
3278
3278
|
function getDataDir() {
|
|
3279
|
-
if (process.platform === "win32") {
|
|
3280
|
-
return process.env.LOCALAPPDATA ?? path3.join(os3.homedir(), "AppData", "Local");
|
|
3281
|
-
}
|
|
3282
3279
|
return process.env.XDG_DATA_HOME ?? path3.join(os3.homedir(), ".local", "share");
|
|
3283
3280
|
}
|
|
3284
3281
|
function getOpenCodeStorageDir() {
|
|
@@ -4374,23 +4371,6 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
|
|
|
4374
4371
|
return false;
|
|
4375
4372
|
}
|
|
4376
4373
|
}
|
|
4377
|
-
// src/hooks/non-interactive-env/detector.ts
|
|
4378
|
-
function isNonInteractive() {
|
|
4379
|
-
if (process.env.CI === "true" || process.env.CI === "1") {
|
|
4380
|
-
return true;
|
|
4381
|
-
}
|
|
4382
|
-
if (process.env.OPENCODE_RUN === "true" || process.env.OPENCODE_NON_INTERACTIVE === "true") {
|
|
4383
|
-
return true;
|
|
4384
|
-
}
|
|
4385
|
-
if (process.env.GITHUB_ACTIONS === "true") {
|
|
4386
|
-
return true;
|
|
4387
|
-
}
|
|
4388
|
-
if (process.stdout.isTTY !== true) {
|
|
4389
|
-
return true;
|
|
4390
|
-
}
|
|
4391
|
-
return false;
|
|
4392
|
-
}
|
|
4393
|
-
|
|
4394
4374
|
// src/hooks/todo-continuation-enforcer.ts
|
|
4395
4375
|
var HOOK_NAME = "todo-continuation-enforcer";
|
|
4396
4376
|
var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
|
|
@@ -4400,6 +4380,9 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
4400
4380
|
- Proceed without asking for permission
|
|
4401
4381
|
- Mark each task complete when finished
|
|
4402
4382
|
- Do not stop until all tasks are done`;
|
|
4383
|
+
var COUNTDOWN_SECONDS = 2;
|
|
4384
|
+
var TOAST_DURATION_MS = 900;
|
|
4385
|
+
var ERROR_COOLDOWN_MS = 3000;
|
|
4403
4386
|
function getMessageDir(sessionID) {
|
|
4404
4387
|
if (!existsSync6(MESSAGE_STORAGE))
|
|
4405
4388
|
return null;
|
|
@@ -4413,7 +4396,7 @@ function getMessageDir(sessionID) {
|
|
|
4413
4396
|
}
|
|
4414
4397
|
return null;
|
|
4415
4398
|
}
|
|
4416
|
-
function
|
|
4399
|
+
function isAbortError(error) {
|
|
4417
4400
|
if (!error)
|
|
4418
4401
|
return false;
|
|
4419
4402
|
if (typeof error === "object") {
|
|
@@ -4433,270 +4416,233 @@ function detectInterrupt(error) {
|
|
|
4433
4416
|
}
|
|
4434
4417
|
return false;
|
|
4435
4418
|
}
|
|
4436
|
-
|
|
4437
|
-
|
|
4419
|
+
function getIncompleteCount(todos) {
|
|
4420
|
+
return todos.filter((t) => t.status !== "completed" && t.status !== "cancelled").length;
|
|
4421
|
+
}
|
|
4438
4422
|
function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
4439
4423
|
const { backgroundManager } = options;
|
|
4440
|
-
const
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4424
|
+
const sessions = new Map;
|
|
4425
|
+
function getState(sessionID) {
|
|
4426
|
+
let state2 = sessions.get(sessionID);
|
|
4427
|
+
if (!state2) {
|
|
4428
|
+
state2 = {};
|
|
4429
|
+
sessions.set(sessionID, state2);
|
|
4430
|
+
}
|
|
4431
|
+
return state2;
|
|
4432
|
+
}
|
|
4433
|
+
function cancelCountdown(sessionID) {
|
|
4434
|
+
const state2 = sessions.get(sessionID);
|
|
4435
|
+
if (!state2)
|
|
4436
|
+
return;
|
|
4437
|
+
if (state2.countdownTimer) {
|
|
4438
|
+
clearTimeout(state2.countdownTimer);
|
|
4439
|
+
state2.countdownTimer = undefined;
|
|
4440
|
+
}
|
|
4441
|
+
if (state2.countdownInterval) {
|
|
4442
|
+
clearInterval(state2.countdownInterval);
|
|
4443
|
+
state2.countdownInterval = undefined;
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
function cleanup(sessionID) {
|
|
4447
|
+
cancelCountdown(sessionID);
|
|
4448
|
+
sessions.delete(sessionID);
|
|
4449
|
+
}
|
|
4446
4450
|
const markRecovering = (sessionID) => {
|
|
4447
|
-
|
|
4451
|
+
const state2 = getState(sessionID);
|
|
4452
|
+
state2.isRecovering = true;
|
|
4453
|
+
cancelCountdown(sessionID);
|
|
4454
|
+
log(`[${HOOK_NAME}] Session marked as recovering`, { sessionID });
|
|
4448
4455
|
};
|
|
4449
4456
|
const markRecoveryComplete = (sessionID) => {
|
|
4450
|
-
|
|
4457
|
+
const state2 = sessions.get(sessionID);
|
|
4458
|
+
if (state2) {
|
|
4459
|
+
state2.isRecovering = false;
|
|
4460
|
+
log(`[${HOOK_NAME}] Session recovery complete`, { sessionID });
|
|
4461
|
+
}
|
|
4451
4462
|
};
|
|
4463
|
+
async function showCountdownToast(seconds, incompleteCount) {
|
|
4464
|
+
await ctx.client.tui.showToast({
|
|
4465
|
+
body: {
|
|
4466
|
+
title: "Todo Continuation",
|
|
4467
|
+
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
4468
|
+
variant: "warning",
|
|
4469
|
+
duration: TOAST_DURATION_MS
|
|
4470
|
+
}
|
|
4471
|
+
}).catch(() => {});
|
|
4472
|
+
}
|
|
4473
|
+
async function injectContinuation(sessionID, incompleteCount, total) {
|
|
4474
|
+
const state2 = sessions.get(sessionID);
|
|
4475
|
+
if (state2?.isRecovering) {
|
|
4476
|
+
log(`[${HOOK_NAME}] Skipped injection: in recovery`, { sessionID });
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
if (state2?.lastErrorAt && Date.now() - state2.lastErrorAt < ERROR_COOLDOWN_MS) {
|
|
4480
|
+
log(`[${HOOK_NAME}] Skipped injection: recent error`, { sessionID });
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4484
|
+
if (hasRunningBgTasks) {
|
|
4485
|
+
log(`[${HOOK_NAME}] Skipped injection: background tasks running`, { sessionID });
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
let todos = [];
|
|
4489
|
+
try {
|
|
4490
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4491
|
+
todos = response.data ?? response;
|
|
4492
|
+
} catch (err) {
|
|
4493
|
+
log(`[${HOOK_NAME}] Failed to fetch todos`, { sessionID, error: String(err) });
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
const freshIncompleteCount = getIncompleteCount(todos);
|
|
4497
|
+
if (freshIncompleteCount === 0) {
|
|
4498
|
+
log(`[${HOOK_NAME}] Skipped injection: no incomplete todos`, { sessionID });
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
const messageDir = getMessageDir(sessionID);
|
|
4502
|
+
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
4503
|
+
const hasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
4504
|
+
if (!hasWritePermission) {
|
|
4505
|
+
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: prevMessage?.agent });
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
const agentName = prevMessage?.agent?.toLowerCase() ?? "";
|
|
4509
|
+
if (agentName === "plan" || agentName === "planner-sisyphus") {
|
|
4510
|
+
log(`[${HOOK_NAME}] Skipped: plan mode agent`, { sessionID, agent: prevMessage?.agent });
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
const prompt = `${CONTINUATION_PROMPT}
|
|
4514
|
+
|
|
4515
|
+
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]`;
|
|
4516
|
+
try {
|
|
4517
|
+
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: prevMessage?.agent, incompleteCount: freshIncompleteCount });
|
|
4518
|
+
await ctx.client.session.prompt({
|
|
4519
|
+
path: { id: sessionID },
|
|
4520
|
+
body: {
|
|
4521
|
+
agent: prevMessage?.agent,
|
|
4522
|
+
parts: [{ type: "text", text: prompt }]
|
|
4523
|
+
},
|
|
4524
|
+
query: { directory: ctx.directory }
|
|
4525
|
+
});
|
|
4526
|
+
log(`[${HOOK_NAME}] Injection successful`, { sessionID });
|
|
4527
|
+
} catch (err) {
|
|
4528
|
+
log(`[${HOOK_NAME}] Injection failed`, { sessionID, error: String(err) });
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
function startCountdown(sessionID, incompleteCount, total) {
|
|
4532
|
+
const state2 = getState(sessionID);
|
|
4533
|
+
cancelCountdown(sessionID);
|
|
4534
|
+
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
4535
|
+
showCountdownToast(secondsRemaining, incompleteCount);
|
|
4536
|
+
state2.countdownInterval = setInterval(() => {
|
|
4537
|
+
secondsRemaining--;
|
|
4538
|
+
if (secondsRemaining > 0) {
|
|
4539
|
+
showCountdownToast(secondsRemaining, incompleteCount);
|
|
4540
|
+
}
|
|
4541
|
+
}, 1000);
|
|
4542
|
+
state2.countdownTimer = setTimeout(() => {
|
|
4543
|
+
cancelCountdown(sessionID);
|
|
4544
|
+
injectContinuation(sessionID, incompleteCount, total);
|
|
4545
|
+
}, COUNTDOWN_SECONDS * 1000);
|
|
4546
|
+
log(`[${HOOK_NAME}] Countdown started`, { sessionID, seconds: COUNTDOWN_SECONDS, incompleteCount });
|
|
4547
|
+
}
|
|
4452
4548
|
const handler = async ({ event }) => {
|
|
4453
4549
|
const props = event.properties;
|
|
4454
4550
|
if (event.type === "session.error") {
|
|
4455
4551
|
const sessionID = props?.sessionID;
|
|
4456
|
-
if (sessionID)
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
log(`[${HOOK_NAME}] session.error received`, { sessionID, isInterrupt, error: props?.error });
|
|
4463
|
-
const countdown = pendingCountdowns.get(sessionID);
|
|
4464
|
-
if (countdown) {
|
|
4465
|
-
clearInterval(countdown.intervalId);
|
|
4466
|
-
pendingCountdowns.delete(sessionID);
|
|
4467
|
-
}
|
|
4468
|
-
}
|
|
4552
|
+
if (!sessionID)
|
|
4553
|
+
return;
|
|
4554
|
+
const state2 = getState(sessionID);
|
|
4555
|
+
state2.lastErrorAt = Date.now();
|
|
4556
|
+
cancelCountdown(sessionID);
|
|
4557
|
+
log(`[${HOOK_NAME}] session.error`, { sessionID, isAbort: isAbortError(props?.error) });
|
|
4469
4558
|
return;
|
|
4470
4559
|
}
|
|
4471
4560
|
if (event.type === "session.idle") {
|
|
4472
4561
|
const sessionID = props?.sessionID;
|
|
4473
4562
|
if (!sessionID)
|
|
4474
4563
|
return;
|
|
4475
|
-
log(`[${HOOK_NAME}] session.idle
|
|
4564
|
+
log(`[${HOOK_NAME}] session.idle`, { sessionID });
|
|
4476
4565
|
const mainSessionID2 = getMainSessionID();
|
|
4477
4566
|
if (mainSessionID2 && sessionID !== mainSessionID2) {
|
|
4478
|
-
log(`[${HOOK_NAME}] Skipped: not main session`, { sessionID
|
|
4567
|
+
log(`[${HOOK_NAME}] Skipped: not main session`, { sessionID });
|
|
4479
4568
|
return;
|
|
4480
4569
|
}
|
|
4481
|
-
const
|
|
4482
|
-
if (
|
|
4483
|
-
|
|
4484
|
-
pendingCountdowns.delete(sessionID);
|
|
4485
|
-
log(`[${HOOK_NAME}] Cancelled existing countdown`, { sessionID });
|
|
4486
|
-
}
|
|
4487
|
-
if (recoveringSessions.has(sessionID)) {
|
|
4488
|
-
log(`[${HOOK_NAME}] Skipped: session in recovery mode`, { sessionID });
|
|
4570
|
+
const state2 = getState(sessionID);
|
|
4571
|
+
if (state2.isRecovering) {
|
|
4572
|
+
log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID });
|
|
4489
4573
|
return;
|
|
4490
4574
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
interruptedSessions.delete(sessionID);
|
|
4494
|
-
errorSessions.delete(sessionID);
|
|
4495
|
-
log(`[${HOOK_NAME}] Skipped: error/interrupt bypass`, { sessionID });
|
|
4575
|
+
if (state2.lastErrorAt && Date.now() - state2.lastErrorAt < ERROR_COOLDOWN_MS) {
|
|
4576
|
+
log(`[${HOOK_NAME}] Skipped: recent error (cooldown)`, { sessionID });
|
|
4496
4577
|
return;
|
|
4497
4578
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4579
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4580
|
+
if (hasRunningBgTasks) {
|
|
4581
|
+
log(`[${HOOK_NAME}] Skipped: background tasks running`, { sessionID });
|
|
4500
4582
|
return;
|
|
4501
4583
|
}
|
|
4502
4584
|
let todos = [];
|
|
4503
4585
|
try {
|
|
4504
|
-
|
|
4505
|
-
const response = await ctx.client.session.todo({
|
|
4506
|
-
path: { id: sessionID }
|
|
4507
|
-
});
|
|
4586
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4508
4587
|
todos = response.data ?? response;
|
|
4509
|
-
log(`[${HOOK_NAME}] Todo API response`, { sessionID, todosCount: todos?.length ?? 0 });
|
|
4510
4588
|
} catch (err) {
|
|
4511
|
-
log(`[${HOOK_NAME}] Todo
|
|
4589
|
+
log(`[${HOOK_NAME}] Todo fetch failed`, { sessionID, error: String(err) });
|
|
4512
4590
|
return;
|
|
4513
4591
|
}
|
|
4514
4592
|
if (!todos || todos.length === 0) {
|
|
4515
|
-
log(`[${HOOK_NAME}] No todos
|
|
4593
|
+
log(`[${HOOK_NAME}] No todos`, { sessionID });
|
|
4516
4594
|
return;
|
|
4517
4595
|
}
|
|
4518
|
-
const
|
|
4519
|
-
if (
|
|
4520
|
-
log(`[${HOOK_NAME}] All todos
|
|
4596
|
+
const incompleteCount = getIncompleteCount(todos);
|
|
4597
|
+
if (incompleteCount === 0) {
|
|
4598
|
+
log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
|
|
4521
4599
|
return;
|
|
4522
4600
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
await ctx.client.tui.showToast({
|
|
4526
|
-
body: {
|
|
4527
|
-
title: "Todo Continuation",
|
|
4528
|
-
message: `Resuming in ${seconds}s... (${incomplete.length} tasks remaining)`,
|
|
4529
|
-
variant: "warning",
|
|
4530
|
-
duration: TOAST_DURATION_MS
|
|
4531
|
-
}
|
|
4532
|
-
}).catch(() => {});
|
|
4533
|
-
};
|
|
4534
|
-
const executeAfterCountdown = async () => {
|
|
4535
|
-
pendingCountdowns.delete(sessionID);
|
|
4536
|
-
log(`[${HOOK_NAME}] Countdown finished, executing continuation`, { sessionID });
|
|
4537
|
-
if (recoveringSessions.has(sessionID)) {
|
|
4538
|
-
log(`[${HOOK_NAME}] Abort: session entered recovery mode during countdown`, { sessionID });
|
|
4539
|
-
return;
|
|
4540
|
-
}
|
|
4541
|
-
if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
|
|
4542
|
-
log(`[${HOOK_NAME}] Abort: error/interrupt occurred during countdown`, { sessionID });
|
|
4543
|
-
interruptedSessions.delete(sessionID);
|
|
4544
|
-
errorSessions.delete(sessionID);
|
|
4545
|
-
return;
|
|
4546
|
-
}
|
|
4547
|
-
let freshTodos = [];
|
|
4548
|
-
try {
|
|
4549
|
-
log(`[${HOOK_NAME}] Re-verifying todos after countdown`, { sessionID });
|
|
4550
|
-
const response = await ctx.client.session.todo({
|
|
4551
|
-
path: { id: sessionID }
|
|
4552
|
-
});
|
|
4553
|
-
freshTodos = response.data ?? response;
|
|
4554
|
-
log(`[${HOOK_NAME}] Fresh todo count`, { sessionID, todosCount: freshTodos?.length ?? 0 });
|
|
4555
|
-
} catch (err) {
|
|
4556
|
-
log(`[${HOOK_NAME}] Failed to re-verify todos`, { sessionID, error: String(err) });
|
|
4557
|
-
return;
|
|
4558
|
-
}
|
|
4559
|
-
const freshIncomplete = freshTodos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
|
|
4560
|
-
if (freshIncomplete.length === 0) {
|
|
4561
|
-
log(`[${HOOK_NAME}] Abort: no incomplete todos after countdown`, { sessionID, total: freshTodos.length });
|
|
4562
|
-
return;
|
|
4563
|
-
}
|
|
4564
|
-
log(`[${HOOK_NAME}] Confirmed incomplete todos, proceeding with injection`, { sessionID, incomplete: freshIncomplete.length, total: freshTodos.length });
|
|
4565
|
-
remindedSessions.add(sessionID);
|
|
4566
|
-
try {
|
|
4567
|
-
const messageDir = getMessageDir(sessionID);
|
|
4568
|
-
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
4569
|
-
const agentHasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
4570
|
-
if (!agentHasWritePermission) {
|
|
4571
|
-
log(`[${HOOK_NAME}] Skipped: previous agent lacks write permission`, { sessionID, agent: prevMessage?.agent, tools: prevMessage?.tools });
|
|
4572
|
-
remindedSessions.delete(sessionID);
|
|
4573
|
-
return;
|
|
4574
|
-
}
|
|
4575
|
-
log(`[${HOOK_NAME}] Injecting continuation prompt`, { sessionID, agent: prevMessage?.agent });
|
|
4576
|
-
await ctx.client.session.prompt({
|
|
4577
|
-
path: { id: sessionID },
|
|
4578
|
-
body: {
|
|
4579
|
-
agent: prevMessage?.agent,
|
|
4580
|
-
parts: [
|
|
4581
|
-
{
|
|
4582
|
-
type: "text",
|
|
4583
|
-
text: `${CONTINUATION_PROMPT}
|
|
4584
|
-
|
|
4585
|
-
[Status: ${freshTodos.length - freshIncomplete.length}/${freshTodos.length} completed, ${freshIncomplete.length} remaining]`
|
|
4586
|
-
}
|
|
4587
|
-
]
|
|
4588
|
-
},
|
|
4589
|
-
query: { directory: ctx.directory }
|
|
4590
|
-
});
|
|
4591
|
-
log(`[${HOOK_NAME}] Continuation prompt injected successfully`, { sessionID });
|
|
4592
|
-
} catch (err) {
|
|
4593
|
-
log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
|
|
4594
|
-
remindedSessions.delete(sessionID);
|
|
4595
|
-
}
|
|
4596
|
-
};
|
|
4597
|
-
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
4598
|
-
showCountdownToast(secondsRemaining).catch(() => {});
|
|
4599
|
-
const intervalId = setInterval(() => {
|
|
4600
|
-
secondsRemaining--;
|
|
4601
|
-
if (secondsRemaining <= 0) {
|
|
4602
|
-
clearInterval(intervalId);
|
|
4603
|
-
pendingCountdowns.delete(sessionID);
|
|
4604
|
-
executeAfterCountdown();
|
|
4605
|
-
return;
|
|
4606
|
-
}
|
|
4607
|
-
const countdown = pendingCountdowns.get(sessionID);
|
|
4608
|
-
if (!countdown) {
|
|
4609
|
-
clearInterval(intervalId);
|
|
4610
|
-
return;
|
|
4611
|
-
}
|
|
4612
|
-
countdown.secondsRemaining = secondsRemaining;
|
|
4613
|
-
showCountdownToast(secondsRemaining).catch(() => {});
|
|
4614
|
-
}, 1000);
|
|
4615
|
-
pendingCountdowns.set(sessionID, { secondsRemaining, intervalId });
|
|
4601
|
+
startCountdown(sessionID, incompleteCount, todos.length);
|
|
4602
|
+
return;
|
|
4616
4603
|
}
|
|
4617
4604
|
if (event.type === "message.updated") {
|
|
4618
4605
|
const info = props?.info;
|
|
4619
4606
|
const sessionID = info?.sessionID;
|
|
4620
4607
|
const role = info?.role;
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
if (
|
|
4624
|
-
const
|
|
4625
|
-
if (
|
|
4626
|
-
|
|
4627
|
-
pendingCountdowns.delete(sessionID);
|
|
4628
|
-
log(`[${HOOK_NAME}] Cancelled countdown on user message`, { sessionID });
|
|
4629
|
-
}
|
|
4630
|
-
remindedSessions.delete(sessionID);
|
|
4631
|
-
preemptivelyInjectedSessions.delete(sessionID);
|
|
4632
|
-
}
|
|
4633
|
-
if (sessionID && role === "assistant" && finish) {
|
|
4634
|
-
remindedSessions.delete(sessionID);
|
|
4635
|
-
preemptivelyInjectedSessions.delete(sessionID);
|
|
4636
|
-
log(`[${HOOK_NAME}] Cleared reminded/preemptive state on assistant finish`, { sessionID });
|
|
4637
|
-
const isTerminalFinish = finish && !["tool-calls", "unknown"].includes(finish);
|
|
4638
|
-
if (isTerminalFinish && isNonInteractive()) {
|
|
4639
|
-
log(`[${HOOK_NAME}] Terminal finish in non-interactive mode`, { sessionID, finish });
|
|
4640
|
-
const mainSessionID2 = getMainSessionID();
|
|
4641
|
-
if (mainSessionID2 && sessionID !== mainSessionID2) {
|
|
4642
|
-
log(`[${HOOK_NAME}] Skipped preemptive: not main session`, { sessionID, mainSessionID: mainSessionID2 });
|
|
4643
|
-
return;
|
|
4644
|
-
}
|
|
4645
|
-
if (preemptivelyInjectedSessions.has(sessionID)) {
|
|
4646
|
-
log(`[${HOOK_NAME}] Skipped preemptive: already injected`, { sessionID });
|
|
4647
|
-
return;
|
|
4648
|
-
}
|
|
4649
|
-
if (recoveringSessions.has(sessionID) || errorSessions.has(sessionID) || interruptedSessions.has(sessionID)) {
|
|
4650
|
-
log(`[${HOOK_NAME}] Skipped preemptive: session in error/recovery state`, { sessionID });
|
|
4651
|
-
return;
|
|
4652
|
-
}
|
|
4653
|
-
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4654
|
-
let hasIncompleteTodos = false;
|
|
4655
|
-
try {
|
|
4656
|
-
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4657
|
-
const todos = response.data ?? response;
|
|
4658
|
-
hasIncompleteTodos = todos?.some((t) => t.status !== "completed" && t.status !== "cancelled") ?? false;
|
|
4659
|
-
} catch {
|
|
4660
|
-
log(`[${HOOK_NAME}] Failed to fetch todos for preemptive check`, { sessionID });
|
|
4661
|
-
}
|
|
4662
|
-
if (hasRunningBgTasks || hasIncompleteTodos) {
|
|
4663
|
-
log(`[${HOOK_NAME}] Preemptive injection needed`, { sessionID, hasRunningBgTasks, hasIncompleteTodos });
|
|
4664
|
-
preemptivelyInjectedSessions.add(sessionID);
|
|
4665
|
-
try {
|
|
4666
|
-
const messageDir = getMessageDir(sessionID);
|
|
4667
|
-
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
4668
|
-
const prompt = hasRunningBgTasks ? "[SYSTEM] Background tasks are still running. Wait for their completion before proceeding." : CONTINUATION_PROMPT;
|
|
4669
|
-
await ctx.client.session.prompt({
|
|
4670
|
-
path: { id: sessionID },
|
|
4671
|
-
body: {
|
|
4672
|
-
agent: prevMessage?.agent,
|
|
4673
|
-
parts: [{ type: "text", text: prompt }]
|
|
4674
|
-
},
|
|
4675
|
-
query: { directory: ctx.directory }
|
|
4676
|
-
});
|
|
4677
|
-
log(`[${HOOK_NAME}] Preemptive injection successful`, { sessionID });
|
|
4678
|
-
} catch (err) {
|
|
4679
|
-
log(`[${HOOK_NAME}] Preemptive injection failed`, { sessionID, error: String(err) });
|
|
4680
|
-
preemptivelyInjectedSessions.delete(sessionID);
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4608
|
+
if (!sessionID)
|
|
4609
|
+
return;
|
|
4610
|
+
if (role === "user") {
|
|
4611
|
+
const state2 = sessions.get(sessionID);
|
|
4612
|
+
if (state2) {
|
|
4613
|
+
state2.lastErrorAt = undefined;
|
|
4683
4614
|
}
|
|
4615
|
+
cancelCountdown(sessionID);
|
|
4616
|
+
log(`[${HOOK_NAME}] User message: cleared error state`, { sessionID });
|
|
4617
|
+
}
|
|
4618
|
+
if (role === "assistant") {
|
|
4619
|
+
cancelCountdown(sessionID);
|
|
4620
|
+
}
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
if (event.type === "message.part.updated") {
|
|
4624
|
+
const info = props?.info;
|
|
4625
|
+
const sessionID = info?.sessionID;
|
|
4626
|
+
const role = info?.role;
|
|
4627
|
+
if (sessionID && role === "assistant") {
|
|
4628
|
+
cancelCountdown(sessionID);
|
|
4629
|
+
}
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4632
|
+
if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
|
|
4633
|
+
const sessionID = props?.sessionID;
|
|
4634
|
+
if (sessionID) {
|
|
4635
|
+
cancelCountdown(sessionID);
|
|
4684
4636
|
}
|
|
4637
|
+
return;
|
|
4685
4638
|
}
|
|
4686
4639
|
if (event.type === "session.deleted") {
|
|
4687
4640
|
const sessionInfo = props?.info;
|
|
4688
4641
|
if (sessionInfo?.id) {
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
errorSessions.delete(sessionInfo.id);
|
|
4692
|
-
recoveringSessions.delete(sessionInfo.id);
|
|
4693
|
-
preemptivelyInjectedSessions.delete(sessionInfo.id);
|
|
4694
|
-
const countdown = pendingCountdowns.get(sessionInfo.id);
|
|
4695
|
-
if (countdown) {
|
|
4696
|
-
clearInterval(countdown.intervalId);
|
|
4697
|
-
pendingCountdowns.delete(sessionInfo.id);
|
|
4698
|
-
}
|
|
4642
|
+
cleanup(sessionInfo.id);
|
|
4643
|
+
log(`[${HOOK_NAME}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
4699
4644
|
}
|
|
4645
|
+
return;
|
|
4700
4646
|
}
|
|
4701
4647
|
};
|
|
4702
4648
|
return {
|
|
@@ -5154,28 +5100,7 @@ import { join as join10 } from "path";
|
|
|
5154
5100
|
|
|
5155
5101
|
// src/hooks/session-recovery/constants.ts
|
|
5156
5102
|
import { join as join9 } from "path";
|
|
5157
|
-
|
|
5158
|
-
// node_modules/xdg-basedir/index.js
|
|
5159
|
-
import os4 from "os";
|
|
5160
|
-
import path4 from "path";
|
|
5161
|
-
var homeDirectory = os4.homedir();
|
|
5162
|
-
var { env } = process;
|
|
5163
|
-
var xdgData = env.XDG_DATA_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "share") : undefined);
|
|
5164
|
-
var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path4.join(homeDirectory, ".config") : undefined);
|
|
5165
|
-
var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "state") : undefined);
|
|
5166
|
-
var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path4.join(homeDirectory, ".cache") : undefined);
|
|
5167
|
-
var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
|
|
5168
|
-
var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
|
|
5169
|
-
if (xdgData) {
|
|
5170
|
-
xdgDataDirectories.unshift(xdgData);
|
|
5171
|
-
}
|
|
5172
|
-
var xdgConfigDirectories = (env.XDG_CONFIG_DIRS || "/etc/xdg").split(":");
|
|
5173
|
-
if (xdgConfig) {
|
|
5174
|
-
xdgConfigDirectories.unshift(xdgConfig);
|
|
5175
|
-
}
|
|
5176
|
-
|
|
5177
|
-
// src/hooks/session-recovery/constants.ts
|
|
5178
|
-
var OPENCODE_STORAGE2 = join9(xdgData ?? "", "opencode", "storage");
|
|
5103
|
+
var OPENCODE_STORAGE2 = getOpenCodeStorageDir();
|
|
5179
5104
|
var MESSAGE_STORAGE2 = join9(OPENCODE_STORAGE2, "message");
|
|
5180
5105
|
var PART_STORAGE2 = join9(OPENCODE_STORAGE2, "part");
|
|
5181
5106
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
@@ -5297,7 +5222,16 @@ function findEmptyMessages(sessionID) {
|
|
|
5297
5222
|
}
|
|
5298
5223
|
function findEmptyMessageByIndex(sessionID, targetIndex) {
|
|
5299
5224
|
const messages = readMessages(sessionID);
|
|
5300
|
-
const indicesToTry = [
|
|
5225
|
+
const indicesToTry = [
|
|
5226
|
+
targetIndex,
|
|
5227
|
+
targetIndex - 1,
|
|
5228
|
+
targetIndex + 1,
|
|
5229
|
+
targetIndex - 2,
|
|
5230
|
+
targetIndex + 2,
|
|
5231
|
+
targetIndex - 3,
|
|
5232
|
+
targetIndex - 4,
|
|
5233
|
+
targetIndex - 5
|
|
5234
|
+
];
|
|
5301
5235
|
for (const idx of indicesToTry) {
|
|
5302
5236
|
if (idx < 0 || idx >= messages.length)
|
|
5303
5237
|
continue;
|
|
@@ -5723,8 +5657,8 @@ var PLATFORM_MAP = {
|
|
|
5723
5657
|
"win32-x64": { os: "windows", arch: "amd64", ext: "zip" }
|
|
5724
5658
|
};
|
|
5725
5659
|
function getCacheDir() {
|
|
5726
|
-
const
|
|
5727
|
-
const base =
|
|
5660
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
5661
|
+
const base = xdgCache || join11(homedir5(), ".cache");
|
|
5728
5662
|
return join11(base, "oh-my-opencode", "bin");
|
|
5729
5663
|
}
|
|
5730
5664
|
function getBinaryName() {
|
|
@@ -5785,8 +5719,8 @@ async function downloadCommentChecker() {
|
|
|
5785
5719
|
return binaryPath;
|
|
5786
5720
|
}
|
|
5787
5721
|
const version = getPackageVersion();
|
|
5788
|
-
const { os:
|
|
5789
|
-
const assetName = `comment-checker_v${version}_${
|
|
5722
|
+
const { os: os4, arch, ext } = platformInfo;
|
|
5723
|
+
const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
|
|
5790
5724
|
const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
|
|
5791
5725
|
debugLog(`Downloading from: ${downloadUrl}`);
|
|
5792
5726
|
console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
|
|
@@ -5898,15 +5832,15 @@ async function getCommentCheckerPath() {
|
|
|
5898
5832
|
function startBackgroundInit() {
|
|
5899
5833
|
if (!initPromise) {
|
|
5900
5834
|
initPromise = getCommentCheckerPath();
|
|
5901
|
-
initPromise.then((
|
|
5902
|
-
debugLog2("background init complete:",
|
|
5835
|
+
initPromise.then((path4) => {
|
|
5836
|
+
debugLog2("background init complete:", path4 || "no binary");
|
|
5903
5837
|
}).catch((err) => {
|
|
5904
5838
|
debugLog2("background init error:", err);
|
|
5905
5839
|
});
|
|
5906
5840
|
}
|
|
5907
5841
|
}
|
|
5908
5842
|
var COMMENT_CHECKER_CLI_PATH = findCommentCheckerPathSync();
|
|
5909
|
-
async function runCommentChecker(input, cliPath) {
|
|
5843
|
+
async function runCommentChecker(input, cliPath, customPrompt) {
|
|
5910
5844
|
const binaryPath = cliPath ?? resolvedCliPath ?? COMMENT_CHECKER_CLI_PATH;
|
|
5911
5845
|
if (!binaryPath) {
|
|
5912
5846
|
debugLog2("comment-checker binary not found");
|
|
@@ -5919,7 +5853,11 @@ async function runCommentChecker(input, cliPath) {
|
|
|
5919
5853
|
const jsonInput = JSON.stringify(input);
|
|
5920
5854
|
debugLog2("running comment-checker with input:", jsonInput.substring(0, 200));
|
|
5921
5855
|
try {
|
|
5922
|
-
const
|
|
5856
|
+
const args = [binaryPath];
|
|
5857
|
+
if (customPrompt) {
|
|
5858
|
+
args.push("--prompt", customPrompt);
|
|
5859
|
+
}
|
|
5860
|
+
const proc = spawn4(args, {
|
|
5923
5861
|
stdin: "pipe",
|
|
5924
5862
|
stdout: "pipe",
|
|
5925
5863
|
stderr: "pipe"
|
|
@@ -5961,6 +5899,7 @@ function debugLog3(...args) {
|
|
|
5961
5899
|
var pendingCalls = new Map;
|
|
5962
5900
|
var PENDING_CALL_TTL = 60000;
|
|
5963
5901
|
var cliPathPromise = null;
|
|
5902
|
+
var cleanupIntervalStarted = false;
|
|
5964
5903
|
function cleanupOldPendingCalls() {
|
|
5965
5904
|
const now = Date.now();
|
|
5966
5905
|
for (const [callID, call] of pendingCalls) {
|
|
@@ -5969,13 +5908,16 @@ function cleanupOldPendingCalls() {
|
|
|
5969
5908
|
}
|
|
5970
5909
|
}
|
|
5971
5910
|
}
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5911
|
+
function createCommentCheckerHooks(config) {
|
|
5912
|
+
debugLog3("createCommentCheckerHooks called", { config });
|
|
5913
|
+
if (!cleanupIntervalStarted) {
|
|
5914
|
+
cleanupIntervalStarted = true;
|
|
5915
|
+
setInterval(cleanupOldPendingCalls, 1e4);
|
|
5916
|
+
}
|
|
5975
5917
|
startBackgroundInit();
|
|
5976
5918
|
cliPathPromise = getCommentCheckerPath();
|
|
5977
|
-
cliPathPromise.then((
|
|
5978
|
-
debugLog3("CLI path resolved:",
|
|
5919
|
+
cliPathPromise.then((path4) => {
|
|
5920
|
+
debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
|
|
5979
5921
|
}).catch((err) => {
|
|
5980
5922
|
debugLog3("CLI path resolution error:", err);
|
|
5981
5923
|
});
|
|
@@ -6031,14 +5973,14 @@ function createCommentCheckerHooks() {
|
|
|
6031
5973
|
return;
|
|
6032
5974
|
}
|
|
6033
5975
|
debugLog3("using CLI:", cliPath);
|
|
6034
|
-
await processWithCli(input, pendingCall, output, cliPath);
|
|
5976
|
+
await processWithCli(input, pendingCall, output, cliPath, config?.custom_prompt);
|
|
6035
5977
|
} catch (err) {
|
|
6036
5978
|
debugLog3("tool.execute.after failed:", err);
|
|
6037
5979
|
}
|
|
6038
5980
|
}
|
|
6039
5981
|
};
|
|
6040
5982
|
}
|
|
6041
|
-
async function processWithCli(input, pendingCall, output, cliPath) {
|
|
5983
|
+
async function processWithCli(input, pendingCall, output, cliPath, customPrompt) {
|
|
6042
5984
|
debugLog3("using CLI mode with path:", cliPath);
|
|
6043
5985
|
const hookInput = {
|
|
6044
5986
|
session_id: pendingCall.sessionID,
|
|
@@ -6054,7 +5996,7 @@ async function processWithCli(input, pendingCall, output, cliPath) {
|
|
|
6054
5996
|
edits: pendingCall.edits
|
|
6055
5997
|
}
|
|
6056
5998
|
};
|
|
6057
|
-
const result = await runCommentChecker(hookInput, cliPath);
|
|
5999
|
+
const result = await runCommentChecker(hookInput, cliPath, customPrompt);
|
|
6058
6000
|
if (result.hasComments && result.message) {
|
|
6059
6001
|
debugLog3("CLI detected comments, appending message");
|
|
6060
6002
|
output.output += `
|
|
@@ -6113,7 +6055,7 @@ import { join as join15 } from "path";
|
|
|
6113
6055
|
|
|
6114
6056
|
// src/hooks/directory-agents-injector/constants.ts
|
|
6115
6057
|
import { join as join14 } from "path";
|
|
6116
|
-
var OPENCODE_STORAGE3 =
|
|
6058
|
+
var OPENCODE_STORAGE3 = getOpenCodeStorageDir();
|
|
6117
6059
|
var AGENTS_INJECTOR_STORAGE = join14(OPENCODE_STORAGE3, "directory-agents");
|
|
6118
6060
|
var AGENTS_FILENAME = "AGENTS.md";
|
|
6119
6061
|
|
|
@@ -6162,12 +6104,12 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
6162
6104
|
}
|
|
6163
6105
|
return sessionCaches.get(sessionID);
|
|
6164
6106
|
}
|
|
6165
|
-
function resolveFilePath2(
|
|
6166
|
-
if (!
|
|
6107
|
+
function resolveFilePath2(path4) {
|
|
6108
|
+
if (!path4)
|
|
6167
6109
|
return null;
|
|
6168
|
-
if (
|
|
6169
|
-
return
|
|
6170
|
-
return resolve2(ctx.directory,
|
|
6110
|
+
if (path4.startsWith("/"))
|
|
6111
|
+
return path4;
|
|
6112
|
+
return resolve2(ctx.directory, path4);
|
|
6171
6113
|
}
|
|
6172
6114
|
function findAgentsMdUp(startDir) {
|
|
6173
6115
|
const found = [];
|
|
@@ -6285,7 +6227,7 @@ import { join as join18 } from "path";
|
|
|
6285
6227
|
|
|
6286
6228
|
// src/hooks/directory-readme-injector/constants.ts
|
|
6287
6229
|
import { join as join17 } from "path";
|
|
6288
|
-
var OPENCODE_STORAGE4 =
|
|
6230
|
+
var OPENCODE_STORAGE4 = getOpenCodeStorageDir();
|
|
6289
6231
|
var README_INJECTOR_STORAGE = join17(OPENCODE_STORAGE4, "directory-readme");
|
|
6290
6232
|
var README_FILENAME = "README.md";
|
|
6291
6233
|
|
|
@@ -6334,12 +6276,12 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
6334
6276
|
}
|
|
6335
6277
|
return sessionCaches.get(sessionID);
|
|
6336
6278
|
}
|
|
6337
|
-
function resolveFilePath2(
|
|
6338
|
-
if (!
|
|
6279
|
+
function resolveFilePath2(path4) {
|
|
6280
|
+
if (!path4)
|
|
6339
6281
|
return null;
|
|
6340
|
-
if (
|
|
6341
|
-
return
|
|
6342
|
-
return resolve3(ctx.directory,
|
|
6282
|
+
if (path4.startsWith("/"))
|
|
6283
|
+
return path4;
|
|
6284
|
+
return resolve3(ctx.directory, path4);
|
|
6343
6285
|
}
|
|
6344
6286
|
function findReadmeMdUp(startDir) {
|
|
6345
6287
|
const found = [];
|
|
@@ -6478,7 +6420,8 @@ var TOKEN_LIMIT_KEYWORDS = [
|
|
|
6478
6420
|
"token limit",
|
|
6479
6421
|
"context length",
|
|
6480
6422
|
"too many tokens",
|
|
6481
|
-
"non-empty content"
|
|
6423
|
+
"non-empty content",
|
|
6424
|
+
"invalid_request_error"
|
|
6482
6425
|
];
|
|
6483
6426
|
var MESSAGE_INDEX_PATTERN = /messages\.(\d+)/;
|
|
6484
6427
|
function extractTokensFromMessage(message) {
|
|
@@ -6566,9 +6509,9 @@ function parseAnthropicTokenLimitError(err) {
|
|
|
6566
6509
|
if (typeof responseBody === "string") {
|
|
6567
6510
|
try {
|
|
6568
6511
|
const jsonPatterns = [
|
|
6569
|
-
/data:\s*(\{[\s\S]
|
|
6570
|
-
/(\{"type"\s*:\s*"error"[\s\S]
|
|
6571
|
-
/(\{[\s\S]
|
|
6512
|
+
/data:\s*(\{[\s\S]*\})\s*$/m,
|
|
6513
|
+
/(\{"type"\s*:\s*"error"[\s\S]*\})/,
|
|
6514
|
+
/(\{[\s\S]*"error"[\s\S]*\})/
|
|
6572
6515
|
];
|
|
6573
6516
|
for (const pattern of jsonPatterns) {
|
|
6574
6517
|
const dataMatch = responseBody.match(pattern);
|
|
@@ -6655,7 +6598,6 @@ function estimateTokens2(text) {
|
|
|
6655
6598
|
}
|
|
6656
6599
|
|
|
6657
6600
|
// src/hooks/anthropic-auto-compact/pruning-deduplication.ts
|
|
6658
|
-
var MESSAGE_STORAGE3 = join20(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6659
6601
|
function createToolSignature(toolName, input) {
|
|
6660
6602
|
const sortedInput = sortObject(input);
|
|
6661
6603
|
return `${toolName}::${JSON.stringify(sortedInput)}`;
|
|
@@ -6675,13 +6617,13 @@ function sortObject(obj) {
|
|
|
6675
6617
|
return sorted;
|
|
6676
6618
|
}
|
|
6677
6619
|
function getMessageDir3(sessionID) {
|
|
6678
|
-
if (!existsSync15(
|
|
6620
|
+
if (!existsSync15(MESSAGE_STORAGE))
|
|
6679
6621
|
return null;
|
|
6680
|
-
const directPath = join20(
|
|
6622
|
+
const directPath = join20(MESSAGE_STORAGE, sessionID);
|
|
6681
6623
|
if (existsSync15(directPath))
|
|
6682
6624
|
return directPath;
|
|
6683
|
-
for (const dir of readdirSync4(
|
|
6684
|
-
const sessionPath = join20(
|
|
6625
|
+
for (const dir of readdirSync4(MESSAGE_STORAGE)) {
|
|
6626
|
+
const sessionPath = join20(MESSAGE_STORAGE, dir, sessionID);
|
|
6685
6627
|
if (existsSync15(sessionPath))
|
|
6686
6628
|
return sessionPath;
|
|
6687
6629
|
}
|
|
@@ -6793,15 +6735,14 @@ function findToolOutput(messages, callID) {
|
|
|
6793
6735
|
// src/hooks/anthropic-auto-compact/pruning-supersede.ts
|
|
6794
6736
|
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
6795
6737
|
import { join as join21 } from "path";
|
|
6796
|
-
var MESSAGE_STORAGE4 = join21(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6797
6738
|
function getMessageDir4(sessionID) {
|
|
6798
|
-
if (!existsSync16(
|
|
6739
|
+
if (!existsSync16(MESSAGE_STORAGE))
|
|
6799
6740
|
return null;
|
|
6800
|
-
const directPath = join21(
|
|
6741
|
+
const directPath = join21(MESSAGE_STORAGE, sessionID);
|
|
6801
6742
|
if (existsSync16(directPath))
|
|
6802
6743
|
return directPath;
|
|
6803
|
-
for (const dir of readdirSync5(
|
|
6804
|
-
const sessionPath = join21(
|
|
6744
|
+
for (const dir of readdirSync5(MESSAGE_STORAGE)) {
|
|
6745
|
+
const sessionPath = join21(MESSAGE_STORAGE, dir, sessionID);
|
|
6805
6746
|
if (existsSync16(sessionPath))
|
|
6806
6747
|
return sessionPath;
|
|
6807
6748
|
}
|
|
@@ -6956,15 +6897,14 @@ function findToolInput(messages, callID) {
|
|
|
6956
6897
|
// src/hooks/anthropic-auto-compact/pruning-purge-errors.ts
|
|
6957
6898
|
import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
6958
6899
|
import { join as join22 } from "path";
|
|
6959
|
-
var MESSAGE_STORAGE5 = join22(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6960
6900
|
function getMessageDir5(sessionID) {
|
|
6961
|
-
if (!existsSync17(
|
|
6901
|
+
if (!existsSync17(MESSAGE_STORAGE))
|
|
6962
6902
|
return null;
|
|
6963
|
-
const directPath = join22(
|
|
6903
|
+
const directPath = join22(MESSAGE_STORAGE, sessionID);
|
|
6964
6904
|
if (existsSync17(directPath))
|
|
6965
6905
|
return directPath;
|
|
6966
|
-
for (const dir of readdirSync6(
|
|
6967
|
-
const sessionPath = join22(
|
|
6906
|
+
for (const dir of readdirSync6(MESSAGE_STORAGE)) {
|
|
6907
|
+
const sessionPath = join22(MESSAGE_STORAGE, dir, sessionID);
|
|
6968
6908
|
if (existsSync17(sessionPath))
|
|
6969
6909
|
return sessionPath;
|
|
6970
6910
|
}
|
|
@@ -7062,15 +7002,14 @@ function executePurgeErrors(sessionID, state2, config, protectedTools) {
|
|
|
7062
7002
|
// src/hooks/anthropic-auto-compact/pruning-storage.ts
|
|
7063
7003
|
import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
7064
7004
|
import { join as join23 } from "path";
|
|
7065
|
-
var MESSAGE_STORAGE6 = join23(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
7066
7005
|
function getMessageDir6(sessionID) {
|
|
7067
|
-
if (!existsSync18(
|
|
7006
|
+
if (!existsSync18(MESSAGE_STORAGE))
|
|
7068
7007
|
return null;
|
|
7069
|
-
const directPath = join23(
|
|
7008
|
+
const directPath = join23(MESSAGE_STORAGE, sessionID);
|
|
7070
7009
|
if (existsSync18(directPath))
|
|
7071
7010
|
return directPath;
|
|
7072
|
-
for (const dir of readdirSync7(
|
|
7073
|
-
const sessionPath = join23(
|
|
7011
|
+
for (const dir of readdirSync7(MESSAGE_STORAGE)) {
|
|
7012
|
+
const sessionPath = join23(MESSAGE_STORAGE, dir, sessionID);
|
|
7074
7013
|
if (existsSync18(sessionPath))
|
|
7075
7014
|
return sessionPath;
|
|
7076
7015
|
}
|
|
@@ -7212,27 +7151,20 @@ async function executeDynamicContextPruning(sessionID, config, client) {
|
|
|
7212
7151
|
|
|
7213
7152
|
// src/hooks/anthropic-auto-compact/storage.ts
|
|
7214
7153
|
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7215
|
-
import { homedir as homedir6 } from "os";
|
|
7216
7154
|
import { join as join24 } from "path";
|
|
7217
|
-
var OPENCODE_STORAGE5 =
|
|
7218
|
-
|
|
7219
|
-
const localShare = join24(homedir6(), ".local", "share", "opencode", "storage");
|
|
7220
|
-
if (existsSync19(localShare)) {
|
|
7221
|
-
OPENCODE_STORAGE5 = localShare;
|
|
7222
|
-
}
|
|
7223
|
-
}
|
|
7224
|
-
var MESSAGE_STORAGE7 = join24(OPENCODE_STORAGE5, "message");
|
|
7155
|
+
var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
|
|
7156
|
+
var MESSAGE_STORAGE3 = join24(OPENCODE_STORAGE5, "message");
|
|
7225
7157
|
var PART_STORAGE3 = join24(OPENCODE_STORAGE5, "part");
|
|
7226
7158
|
var TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]";
|
|
7227
7159
|
function getMessageDir7(sessionID) {
|
|
7228
|
-
if (!existsSync19(
|
|
7160
|
+
if (!existsSync19(MESSAGE_STORAGE3))
|
|
7229
7161
|
return "";
|
|
7230
|
-
const directPath = join24(
|
|
7162
|
+
const directPath = join24(MESSAGE_STORAGE3, sessionID);
|
|
7231
7163
|
if (existsSync19(directPath)) {
|
|
7232
7164
|
return directPath;
|
|
7233
7165
|
}
|
|
7234
|
-
for (const dir of readdirSync8(
|
|
7235
|
-
const sessionPath = join24(
|
|
7166
|
+
for (const dir of readdirSync8(MESSAGE_STORAGE3)) {
|
|
7167
|
+
const sessionPath = join24(MESSAGE_STORAGE3, dir, sessionID);
|
|
7236
7168
|
if (existsSync19(sessionPath)) {
|
|
7237
7169
|
return sessionPath;
|
|
7238
7170
|
}
|
|
@@ -7360,6 +7292,7 @@ function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRa
|
|
|
7360
7292
|
}
|
|
7361
7293
|
|
|
7362
7294
|
// src/hooks/anthropic-auto-compact/executor.ts
|
|
7295
|
+
var PLACEHOLDER_TEXT = "[user interrupted]";
|
|
7363
7296
|
function getOrCreateRetryState(autoCompactState, sessionID) {
|
|
7364
7297
|
let state2 = autoCompactState.retryStateBySession.get(sessionID);
|
|
7365
7298
|
if (!state2) {
|
|
@@ -7392,6 +7325,32 @@ function getOrCreateDcpState(autoCompactState, sessionID) {
|
|
|
7392
7325
|
}
|
|
7393
7326
|
return state2;
|
|
7394
7327
|
}
|
|
7328
|
+
function sanitizeEmptyMessagesBeforeSummarize(sessionID) {
|
|
7329
|
+
const emptyMessageIds = findEmptyMessages(sessionID);
|
|
7330
|
+
if (emptyMessageIds.length === 0) {
|
|
7331
|
+
return 0;
|
|
7332
|
+
}
|
|
7333
|
+
let fixedCount = 0;
|
|
7334
|
+
for (const messageID of emptyMessageIds) {
|
|
7335
|
+
const replaced = replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT);
|
|
7336
|
+
if (replaced) {
|
|
7337
|
+
fixedCount++;
|
|
7338
|
+
} else {
|
|
7339
|
+
const injected = injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT);
|
|
7340
|
+
if (injected) {
|
|
7341
|
+
fixedCount++;
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7345
|
+
if (fixedCount > 0) {
|
|
7346
|
+
log("[auto-compact] pre-summarize sanitization fixed empty messages", {
|
|
7347
|
+
sessionID,
|
|
7348
|
+
fixedCount,
|
|
7349
|
+
totalEmpty: emptyMessageIds.length
|
|
7350
|
+
});
|
|
7351
|
+
}
|
|
7352
|
+
return fixedCount;
|
|
7353
|
+
}
|
|
7395
7354
|
async function getLastMessagePair(sessionID, client, directory) {
|
|
7396
7355
|
try {
|
|
7397
7356
|
const resp = await client.session.messages({
|
|
@@ -7547,6 +7506,77 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7547
7506
|
try {
|
|
7548
7507
|
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
7549
7508
|
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
7509
|
+
const dcpState = getOrCreateDcpState(autoCompactState, sessionID);
|
|
7510
|
+
if (experimental?.dcp_for_compaction && !dcpState.attempted && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
7511
|
+
dcpState.attempted = true;
|
|
7512
|
+
log("[auto-compact] DCP triggered FIRST on token limit error", {
|
|
7513
|
+
sessionID,
|
|
7514
|
+
currentTokens: errorData.currentTokens,
|
|
7515
|
+
maxTokens: errorData.maxTokens
|
|
7516
|
+
});
|
|
7517
|
+
const dcpConfig = experimental.dynamic_context_pruning ?? {
|
|
7518
|
+
enabled: true,
|
|
7519
|
+
notification: "detailed",
|
|
7520
|
+
protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"]
|
|
7521
|
+
};
|
|
7522
|
+
try {
|
|
7523
|
+
const pruningResult = await executeDynamicContextPruning(sessionID, dcpConfig, client);
|
|
7524
|
+
if (pruningResult.itemsPruned > 0) {
|
|
7525
|
+
dcpState.itemsPruned = pruningResult.itemsPruned;
|
|
7526
|
+
log("[auto-compact] DCP successful, proceeding to compaction", {
|
|
7527
|
+
itemsPruned: pruningResult.itemsPruned,
|
|
7528
|
+
tokensSaved: pruningResult.totalTokensSaved
|
|
7529
|
+
});
|
|
7530
|
+
await client.tui.showToast({
|
|
7531
|
+
body: {
|
|
7532
|
+
title: "Dynamic Context Pruning",
|
|
7533
|
+
message: `Pruned ${pruningResult.itemsPruned} items (~${Math.round(pruningResult.totalTokensSaved / 1000)}k tokens). Running compaction...`,
|
|
7534
|
+
variant: "success",
|
|
7535
|
+
duration: 3000
|
|
7536
|
+
}
|
|
7537
|
+
}).catch(() => {});
|
|
7538
|
+
const providerID = msg.providerID;
|
|
7539
|
+
const modelID = msg.modelID;
|
|
7540
|
+
if (providerID && modelID) {
|
|
7541
|
+
try {
|
|
7542
|
+
sanitizeEmptyMessagesBeforeSummarize(sessionID);
|
|
7543
|
+
await client.tui.showToast({
|
|
7544
|
+
body: {
|
|
7545
|
+
title: "Auto Compact",
|
|
7546
|
+
message: "Summarizing session after DCP...",
|
|
7547
|
+
variant: "warning",
|
|
7548
|
+
duration: 3000
|
|
7549
|
+
}
|
|
7550
|
+
}).catch(() => {});
|
|
7551
|
+
await client.session.summarize({
|
|
7552
|
+
path: { id: sessionID },
|
|
7553
|
+
body: { providerID, modelID },
|
|
7554
|
+
query: { directory }
|
|
7555
|
+
});
|
|
7556
|
+
clearSessionState(autoCompactState, sessionID);
|
|
7557
|
+
setTimeout(async () => {
|
|
7558
|
+
try {
|
|
7559
|
+
await client.session.prompt_async({
|
|
7560
|
+
path: { sessionID },
|
|
7561
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
7562
|
+
query: { directory }
|
|
7563
|
+
});
|
|
7564
|
+
} catch {}
|
|
7565
|
+
}, 500);
|
|
7566
|
+
return;
|
|
7567
|
+
} catch (summarizeError) {
|
|
7568
|
+
log("[auto-compact] summarize after DCP failed, continuing recovery", {
|
|
7569
|
+
error: String(summarizeError)
|
|
7570
|
+
});
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
} else {
|
|
7574
|
+
log("[auto-compact] DCP did not prune any items", { sessionID });
|
|
7575
|
+
}
|
|
7576
|
+
} catch (error) {
|
|
7577
|
+
log("[auto-compact] DCP failed", { error: String(error) });
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7550
7580
|
if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
7551
7581
|
log("[auto-compact] aggressive truncation triggered (experimental)", {
|
|
7552
7582
|
currentTokens: errorData.currentTokens,
|
|
@@ -7673,6 +7703,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7673
7703
|
const modelID = msg.modelID;
|
|
7674
7704
|
if (providerID && modelID) {
|
|
7675
7705
|
try {
|
|
7706
|
+
sanitizeEmptyMessagesBeforeSummarize(sessionID);
|
|
7676
7707
|
await client.tui.showToast({
|
|
7677
7708
|
body: {
|
|
7678
7709
|
title: "Auto Compact",
|
|
@@ -7715,45 +7746,6 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7715
7746
|
}).catch(() => {});
|
|
7716
7747
|
}
|
|
7717
7748
|
}
|
|
7718
|
-
const dcpState = getOrCreateDcpState(autoCompactState, sessionID);
|
|
7719
|
-
if (experimental?.dcp_on_compaction_failure && !dcpState.attempted) {
|
|
7720
|
-
dcpState.attempted = true;
|
|
7721
|
-
log("[auto-compact] attempting DCP after summarize failed", { sessionID });
|
|
7722
|
-
const dcpConfig = experimental.dynamic_context_pruning ?? {
|
|
7723
|
-
enabled: true,
|
|
7724
|
-
notification: "detailed",
|
|
7725
|
-
protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"]
|
|
7726
|
-
};
|
|
7727
|
-
try {
|
|
7728
|
-
const pruningResult = await executeDynamicContextPruning(sessionID, dcpConfig, client);
|
|
7729
|
-
if (pruningResult.itemsPruned > 0) {
|
|
7730
|
-
dcpState.itemsPruned = pruningResult.itemsPruned;
|
|
7731
|
-
log("[auto-compact] DCP successful, retrying compaction", {
|
|
7732
|
-
itemsPruned: pruningResult.itemsPruned,
|
|
7733
|
-
tokensSaved: pruningResult.totalTokensSaved
|
|
7734
|
-
});
|
|
7735
|
-
await client.tui.showToast({
|
|
7736
|
-
body: {
|
|
7737
|
-
title: "Dynamic Context Pruning",
|
|
7738
|
-
message: `Pruned ${pruningResult.itemsPruned} items (~${Math.round(pruningResult.totalTokensSaved / 1000)}k tokens). Retrying compaction...`,
|
|
7739
|
-
variant: "success",
|
|
7740
|
-
duration: 3000
|
|
7741
|
-
}
|
|
7742
|
-
}).catch(() => {});
|
|
7743
|
-
retryState.attempt = 0;
|
|
7744
|
-
setTimeout(() => {
|
|
7745
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
7746
|
-
}, 500);
|
|
7747
|
-
return;
|
|
7748
|
-
} else {
|
|
7749
|
-
log("[auto-compact] DCP did not prune any items, continuing to revert", { sessionID });
|
|
7750
|
-
}
|
|
7751
|
-
} catch (error) {
|
|
7752
|
-
log("[auto-compact] DCP failed, continuing to revert", {
|
|
7753
|
-
error: String(error)
|
|
7754
|
-
});
|
|
7755
|
-
}
|
|
7756
|
-
}
|
|
7757
7749
|
const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
|
|
7758
7750
|
if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
|
|
7759
7751
|
const pair = await getLastMessagePair(sessionID, client, directory);
|
|
@@ -8506,21 +8498,21 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
8506
8498
|
|
|
8507
8499
|
// src/hooks/claude-code-hooks/config-loader.ts
|
|
8508
8500
|
import { existsSync as existsSync22 } from "fs";
|
|
8509
|
-
import { homedir as
|
|
8501
|
+
import { homedir as homedir6 } from "os";
|
|
8510
8502
|
import { join as join27 } from "path";
|
|
8511
|
-
var USER_CONFIG_PATH = join27(
|
|
8503
|
+
var USER_CONFIG_PATH = join27(homedir6(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
8512
8504
|
function getProjectConfigPath() {
|
|
8513
8505
|
return join27(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
8514
8506
|
}
|
|
8515
|
-
async function loadConfigFromPath(
|
|
8516
|
-
if (!existsSync22(
|
|
8507
|
+
async function loadConfigFromPath(path4) {
|
|
8508
|
+
if (!existsSync22(path4)) {
|
|
8517
8509
|
return null;
|
|
8518
8510
|
}
|
|
8519
8511
|
try {
|
|
8520
|
-
const content = await Bun.file(
|
|
8512
|
+
const content = await Bun.file(path4).text();
|
|
8521
8513
|
return JSON.parse(content);
|
|
8522
8514
|
} catch (error) {
|
|
8523
|
-
log("Failed to load config", { path:
|
|
8515
|
+
log("Failed to load config", { path: path4, error });
|
|
8524
8516
|
return null;
|
|
8525
8517
|
}
|
|
8526
8518
|
}
|
|
@@ -8708,10 +8700,10 @@ function ensureTranscriptDir() {
|
|
|
8708
8700
|
}
|
|
8709
8701
|
function appendTranscriptEntry(sessionId, entry) {
|
|
8710
8702
|
ensureTranscriptDir();
|
|
8711
|
-
const
|
|
8703
|
+
const path4 = getTranscriptPath(sessionId);
|
|
8712
8704
|
const line = JSON.stringify(entry) + `
|
|
8713
8705
|
`;
|
|
8714
|
-
appendFileSync5(
|
|
8706
|
+
appendFileSync5(path4, line);
|
|
8715
8707
|
}
|
|
8716
8708
|
function recordToolUse(sessionId, toolName, toolInput) {
|
|
8717
8709
|
appendTranscriptEntry(sessionId, {
|
|
@@ -8818,11 +8810,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
8818
8810
|
}
|
|
8819
8811
|
}
|
|
8820
8812
|
}
|
|
8821
|
-
function deleteTempTranscript(
|
|
8822
|
-
if (!
|
|
8813
|
+
function deleteTempTranscript(path4) {
|
|
8814
|
+
if (!path4)
|
|
8823
8815
|
return;
|
|
8824
8816
|
try {
|
|
8825
|
-
unlinkSync5(
|
|
8817
|
+
unlinkSync5(path4);
|
|
8826
8818
|
} catch {}
|
|
8827
8819
|
}
|
|
8828
8820
|
|
|
@@ -9449,7 +9441,7 @@ ${result.message}`;
|
|
|
9449
9441
|
}
|
|
9450
9442
|
// src/hooks/rules-injector/index.ts
|
|
9451
9443
|
import { readFileSync as readFileSync15 } from "fs";
|
|
9452
|
-
import { homedir as
|
|
9444
|
+
import { homedir as homedir7 } from "os";
|
|
9453
9445
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
9454
9446
|
|
|
9455
9447
|
// src/hooks/rules-injector/finder.ts
|
|
@@ -9463,7 +9455,7 @@ import { dirname as dirname4, join as join31, relative } from "path";
|
|
|
9463
9455
|
|
|
9464
9456
|
// src/hooks/rules-injector/constants.ts
|
|
9465
9457
|
import { join as join30 } from "path";
|
|
9466
|
-
var OPENCODE_STORAGE6 =
|
|
9458
|
+
var OPENCODE_STORAGE6 = getOpenCodeStorageDir();
|
|
9467
9459
|
var RULES_INJECTOR_STORAGE = join30(OPENCODE_STORAGE6, "rules-injector");
|
|
9468
9460
|
var PROJECT_MARKERS = [
|
|
9469
9461
|
".git",
|
|
@@ -9804,12 +9796,12 @@ function createRulesInjectorHook(ctx) {
|
|
|
9804
9796
|
}
|
|
9805
9797
|
return sessionCaches.get(sessionID);
|
|
9806
9798
|
}
|
|
9807
|
-
function resolveFilePath2(
|
|
9808
|
-
if (!
|
|
9799
|
+
function resolveFilePath2(path4) {
|
|
9800
|
+
if (!path4)
|
|
9809
9801
|
return null;
|
|
9810
|
-
if (
|
|
9811
|
-
return
|
|
9812
|
-
return resolve4(ctx.directory,
|
|
9802
|
+
if (path4.startsWith("/"))
|
|
9803
|
+
return path4;
|
|
9804
|
+
return resolve4(ctx.directory, path4);
|
|
9813
9805
|
}
|
|
9814
9806
|
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
9815
9807
|
const resolved = resolveFilePath2(filePath);
|
|
@@ -9817,7 +9809,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
9817
9809
|
return;
|
|
9818
9810
|
const projectRoot = findProjectRoot(resolved);
|
|
9819
9811
|
const cache2 = getSessionCache(sessionID);
|
|
9820
|
-
const home =
|
|
9812
|
+
const home = homedir7();
|
|
9821
9813
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved);
|
|
9822
9814
|
const toInject = [];
|
|
9823
9815
|
for (const candidate of ruleFileCandidates) {
|
|
@@ -9932,33 +9924,33 @@ function createBackgroundNotificationHook(manager) {
|
|
|
9932
9924
|
}
|
|
9933
9925
|
// src/hooks/auto-update-checker/checker.ts
|
|
9934
9926
|
import * as fs5 from "fs";
|
|
9935
|
-
import * as
|
|
9927
|
+
import * as path5 from "path";
|
|
9936
9928
|
import { fileURLToPath } from "url";
|
|
9937
9929
|
|
|
9938
9930
|
// src/hooks/auto-update-checker/constants.ts
|
|
9939
|
-
import * as
|
|
9940
|
-
import * as
|
|
9931
|
+
import * as path4 from "path";
|
|
9932
|
+
import * as os4 from "os";
|
|
9941
9933
|
var PACKAGE_NAME = "oh-my-opencode";
|
|
9942
9934
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
9943
9935
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
9944
9936
|
function getCacheDir2() {
|
|
9945
9937
|
if (process.platform === "win32") {
|
|
9946
|
-
return
|
|
9938
|
+
return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
|
|
9947
9939
|
}
|
|
9948
|
-
return
|
|
9940
|
+
return path4.join(os4.homedir(), ".cache", "opencode");
|
|
9949
9941
|
}
|
|
9950
9942
|
var CACHE_DIR = getCacheDir2();
|
|
9951
|
-
var VERSION_FILE =
|
|
9952
|
-
var INSTALLED_PACKAGE_JSON =
|
|
9943
|
+
var VERSION_FILE = path4.join(CACHE_DIR, "version");
|
|
9944
|
+
var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
9953
9945
|
function getUserConfigDir2() {
|
|
9954
9946
|
if (process.platform === "win32") {
|
|
9955
|
-
return process.env.APPDATA ??
|
|
9947
|
+
return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
|
|
9956
9948
|
}
|
|
9957
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
9949
|
+
return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
|
|
9958
9950
|
}
|
|
9959
9951
|
var USER_CONFIG_DIR = getUserConfigDir2();
|
|
9960
|
-
var USER_OPENCODE_CONFIG =
|
|
9961
|
-
var USER_OPENCODE_CONFIG_JSONC =
|
|
9952
|
+
var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
|
|
9953
|
+
var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
|
|
9962
9954
|
|
|
9963
9955
|
// src/hooks/auto-update-checker/checker.ts
|
|
9964
9956
|
function stripJsonComments(json) {
|
|
@@ -9966,8 +9958,8 @@ function stripJsonComments(json) {
|
|
|
9966
9958
|
}
|
|
9967
9959
|
function getConfigPaths(directory) {
|
|
9968
9960
|
return [
|
|
9969
|
-
|
|
9970
|
-
|
|
9961
|
+
path5.join(directory, ".opencode", "opencode.json"),
|
|
9962
|
+
path5.join(directory, ".opencode", "opencode.jsonc"),
|
|
9971
9963
|
USER_OPENCODE_CONFIG,
|
|
9972
9964
|
USER_OPENCODE_CONFIG_JSONC
|
|
9973
9965
|
];
|
|
@@ -9998,9 +9990,9 @@ function getLocalDevPath(directory) {
|
|
|
9998
9990
|
function findPackageJsonUp(startPath) {
|
|
9999
9991
|
try {
|
|
10000
9992
|
const stat = fs5.statSync(startPath);
|
|
10001
|
-
let dir = stat.isDirectory() ? startPath :
|
|
9993
|
+
let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
|
|
10002
9994
|
for (let i = 0;i < 10; i++) {
|
|
10003
|
-
const pkgPath =
|
|
9995
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
10004
9996
|
if (fs5.existsSync(pkgPath)) {
|
|
10005
9997
|
try {
|
|
10006
9998
|
const content = fs5.readFileSync(pkgPath, "utf-8");
|
|
@@ -10009,7 +10001,7 @@ function findPackageJsonUp(startPath) {
|
|
|
10009
10001
|
return pkgPath;
|
|
10010
10002
|
} catch {}
|
|
10011
10003
|
}
|
|
10012
|
-
const parent =
|
|
10004
|
+
const parent = path5.dirname(dir);
|
|
10013
10005
|
if (parent === dir)
|
|
10014
10006
|
break;
|
|
10015
10007
|
dir = parent;
|
|
@@ -10066,7 +10058,7 @@ function getCachedVersion() {
|
|
|
10066
10058
|
}
|
|
10067
10059
|
} catch {}
|
|
10068
10060
|
try {
|
|
10069
|
-
const currentDir =
|
|
10061
|
+
const currentDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
10070
10062
|
const pkgPath = findPackageJsonUp(currentDir);
|
|
10071
10063
|
if (pkgPath) {
|
|
10072
10064
|
const content = fs5.readFileSync(pkgPath, "utf-8");
|
|
@@ -10142,12 +10134,12 @@ async function getLatestVersion() {
|
|
|
10142
10134
|
|
|
10143
10135
|
// src/hooks/auto-update-checker/cache.ts
|
|
10144
10136
|
import * as fs6 from "fs";
|
|
10145
|
-
import * as
|
|
10137
|
+
import * as path6 from "path";
|
|
10146
10138
|
function stripTrailingCommas(json) {
|
|
10147
10139
|
return json.replace(/,(\s*[}\]])/g, "$1");
|
|
10148
10140
|
}
|
|
10149
10141
|
function removeFromBunLock(packageName) {
|
|
10150
|
-
const lockPath =
|
|
10142
|
+
const lockPath = path6.join(CACHE_DIR, "bun.lock");
|
|
10151
10143
|
if (!fs6.existsSync(lockPath))
|
|
10152
10144
|
return false;
|
|
10153
10145
|
try {
|
|
@@ -10173,8 +10165,8 @@ function removeFromBunLock(packageName) {
|
|
|
10173
10165
|
}
|
|
10174
10166
|
function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
10175
10167
|
try {
|
|
10176
|
-
const pkgDir =
|
|
10177
|
-
const pkgJsonPath =
|
|
10168
|
+
const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
|
|
10169
|
+
const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
|
|
10178
10170
|
let packageRemoved = false;
|
|
10179
10171
|
let dependencyRemoved = false;
|
|
10180
10172
|
let lockRemoved = false;
|
|
@@ -10372,7 +10364,7 @@ import { join as join37 } from "path";
|
|
|
10372
10364
|
|
|
10373
10365
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
10374
10366
|
import { join as join36 } from "path";
|
|
10375
|
-
var OPENCODE_STORAGE7 =
|
|
10367
|
+
var OPENCODE_STORAGE7 = getOpenCodeStorageDir();
|
|
10376
10368
|
var AGENT_USAGE_REMINDER_STORAGE = join36(OPENCODE_STORAGE7, "agent-usage-reminder");
|
|
10377
10369
|
var TARGET_TOOLS = new Set([
|
|
10378
10370
|
"grep",
|
|
@@ -10756,7 +10748,7 @@ import { join as join39 } from "path";
|
|
|
10756
10748
|
|
|
10757
10749
|
// src/hooks/interactive-bash-session/constants.ts
|
|
10758
10750
|
import { join as join38 } from "path";
|
|
10759
|
-
var OPENCODE_STORAGE8 =
|
|
10751
|
+
var OPENCODE_STORAGE8 = getOpenCodeStorageDir();
|
|
10760
10752
|
var INTERACTIVE_BASH_SESSION_STORAGE = join38(OPENCODE_STORAGE8, "interactive-bash-session");
|
|
10761
10753
|
var OMO_SESSION_PREFIX = "omo-";
|
|
10762
10754
|
function buildSessionReminderMessage(sessions) {
|
|
@@ -10979,7 +10971,7 @@ function createInteractiveBashSessionHook(_ctx) {
|
|
|
10979
10971
|
};
|
|
10980
10972
|
}
|
|
10981
10973
|
// src/hooks/empty-message-sanitizer/index.ts
|
|
10982
|
-
var
|
|
10974
|
+
var PLACEHOLDER_TEXT2 = "[user interrupted]";
|
|
10983
10975
|
function hasTextContent(part) {
|
|
10984
10976
|
if (part.type === "text") {
|
|
10985
10977
|
const text = part.text;
|
|
@@ -10998,8 +10990,11 @@ function createEmptyMessageSanitizerHook() {
|
|
|
10998
10990
|
return {
|
|
10999
10991
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
11000
10992
|
const { messages } = output;
|
|
11001
|
-
for (
|
|
11002
|
-
|
|
10993
|
+
for (let i = 0;i < messages.length; i++) {
|
|
10994
|
+
const message = messages[i];
|
|
10995
|
+
const isLastMessage = i === messages.length - 1;
|
|
10996
|
+
const isAssistant = message.info.role === "assistant";
|
|
10997
|
+
if (isLastMessage && isAssistant)
|
|
11003
10998
|
continue;
|
|
11004
10999
|
const parts = message.parts;
|
|
11005
11000
|
if (!hasValidContent(parts)) {
|
|
@@ -11008,7 +11003,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11008
11003
|
if (part.type === "text") {
|
|
11009
11004
|
const textPart = part;
|
|
11010
11005
|
if (!textPart.text || !textPart.text.trim()) {
|
|
11011
|
-
textPart.text =
|
|
11006
|
+
textPart.text = PLACEHOLDER_TEXT2;
|
|
11012
11007
|
textPart.synthetic = true;
|
|
11013
11008
|
injected = true;
|
|
11014
11009
|
break;
|
|
@@ -11022,7 +11017,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11022
11017
|
messageID: message.info.id,
|
|
11023
11018
|
sessionID: message.info.sessionID ?? "",
|
|
11024
11019
|
type: "text",
|
|
11025
|
-
text:
|
|
11020
|
+
text: PLACEHOLDER_TEXT2,
|
|
11026
11021
|
synthetic: true
|
|
11027
11022
|
};
|
|
11028
11023
|
if (insertIndex === -1) {
|
|
@@ -11036,7 +11031,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11036
11031
|
if (part.type === "text") {
|
|
11037
11032
|
const textPart = part;
|
|
11038
11033
|
if (textPart.text !== undefined && textPart.text.trim() === "") {
|
|
11039
|
-
textPart.text =
|
|
11034
|
+
textPart.text = PLACEHOLDER_TEXT2;
|
|
11040
11035
|
textPart.synthetic = true;
|
|
11041
11036
|
}
|
|
11042
11037
|
}
|
|
@@ -11055,12 +11050,12 @@ function isExtendedThinkingModel(modelID) {
|
|
|
11055
11050
|
}
|
|
11056
11051
|
return lower.includes("claude-sonnet-4") || lower.includes("claude-opus-4") || lower.includes("claude-3");
|
|
11057
11052
|
}
|
|
11058
|
-
function
|
|
11053
|
+
function hasContentParts(parts) {
|
|
11059
11054
|
if (!parts || parts.length === 0)
|
|
11060
11055
|
return false;
|
|
11061
11056
|
return parts.some((part) => {
|
|
11062
11057
|
const type = part.type;
|
|
11063
|
-
return type === "tool" || type === "tool_use";
|
|
11058
|
+
return type === "tool" || type === "tool_use" || type === "text";
|
|
11064
11059
|
});
|
|
11065
11060
|
}
|
|
11066
11061
|
function startsWithThinkingBlock(parts) {
|
|
@@ -11119,7 +11114,7 @@ function createThinkingBlockValidatorHook() {
|
|
|
11119
11114
|
const msg = messages[i];
|
|
11120
11115
|
if (msg.info.role !== "assistant")
|
|
11121
11116
|
continue;
|
|
11122
|
-
if (
|
|
11117
|
+
if (hasContentParts(msg.parts) && !startsWithThinkingBlock(msg.parts)) {
|
|
11123
11118
|
const previousThinking = findPreviousThinkingContent(messages, i);
|
|
11124
11119
|
const thinkingContent = previousThinking || "[Continuing from previous reasoning]";
|
|
11125
11120
|
prependThinkingBlock(msg, thinkingContent);
|
|
@@ -11392,10 +11387,66 @@ function startCallbackServer(timeoutMs = 5 * 60 * 1000) {
|
|
|
11392
11387
|
};
|
|
11393
11388
|
}
|
|
11394
11389
|
// src/auth/antigravity/token.ts
|
|
11390
|
+
class AntigravityTokenRefreshError extends Error {
|
|
11391
|
+
code;
|
|
11392
|
+
description;
|
|
11393
|
+
status;
|
|
11394
|
+
statusText;
|
|
11395
|
+
responseBody;
|
|
11396
|
+
constructor(options) {
|
|
11397
|
+
super(options.message);
|
|
11398
|
+
this.name = "AntigravityTokenRefreshError";
|
|
11399
|
+
this.code = options.code;
|
|
11400
|
+
this.description = options.description;
|
|
11401
|
+
this.status = options.status;
|
|
11402
|
+
this.statusText = options.statusText;
|
|
11403
|
+
this.responseBody = options.responseBody;
|
|
11404
|
+
}
|
|
11405
|
+
get isInvalidGrant() {
|
|
11406
|
+
return this.code === "invalid_grant";
|
|
11407
|
+
}
|
|
11408
|
+
get isNetworkError() {
|
|
11409
|
+
return this.status === 0;
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
function parseOAuthErrorPayload(text) {
|
|
11413
|
+
if (!text) {
|
|
11414
|
+
return {};
|
|
11415
|
+
}
|
|
11416
|
+
try {
|
|
11417
|
+
const payload = JSON.parse(text);
|
|
11418
|
+
let code;
|
|
11419
|
+
if (typeof payload.error === "string") {
|
|
11420
|
+
code = payload.error;
|
|
11421
|
+
} else if (payload.error && typeof payload.error === "object") {
|
|
11422
|
+
code = payload.error.status ?? payload.error.code;
|
|
11423
|
+
}
|
|
11424
|
+
return {
|
|
11425
|
+
code,
|
|
11426
|
+
description: payload.error_description
|
|
11427
|
+
};
|
|
11428
|
+
} catch {
|
|
11429
|
+
return { description: text };
|
|
11430
|
+
}
|
|
11431
|
+
}
|
|
11395
11432
|
function isTokenExpired(tokens) {
|
|
11396
11433
|
const expirationTime = tokens.timestamp + tokens.expires_in * 1000;
|
|
11397
11434
|
return Date.now() >= expirationTime - ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS;
|
|
11398
11435
|
}
|
|
11436
|
+
var MAX_REFRESH_RETRIES = 3;
|
|
11437
|
+
var INITIAL_RETRY_DELAY_MS = 1000;
|
|
11438
|
+
function calculateRetryDelay(attempt) {
|
|
11439
|
+
return Math.min(INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt), 1e4);
|
|
11440
|
+
}
|
|
11441
|
+
function isRetryableError(status) {
|
|
11442
|
+
if (status === 0)
|
|
11443
|
+
return true;
|
|
11444
|
+
if (status === 429)
|
|
11445
|
+
return true;
|
|
11446
|
+
if (status >= 500 && status < 600)
|
|
11447
|
+
return true;
|
|
11448
|
+
return false;
|
|
11449
|
+
}
|
|
11399
11450
|
async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID, clientSecret = ANTIGRAVITY_CLIENT_SECRET) {
|
|
11400
11451
|
const params = new URLSearchParams({
|
|
11401
11452
|
grant_type: "refresh_token",
|
|
@@ -11403,24 +11454,67 @@ async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID
|
|
|
11403
11454
|
client_id: clientId,
|
|
11404
11455
|
client_secret: clientSecret
|
|
11405
11456
|
});
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11457
|
+
let lastError;
|
|
11458
|
+
for (let attempt = 0;attempt <= MAX_REFRESH_RETRIES; attempt++) {
|
|
11459
|
+
try {
|
|
11460
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
11461
|
+
method: "POST",
|
|
11462
|
+
headers: {
|
|
11463
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
11464
|
+
},
|
|
11465
|
+
body: params
|
|
11466
|
+
});
|
|
11467
|
+
if (response.ok) {
|
|
11468
|
+
const data = await response.json();
|
|
11469
|
+
return {
|
|
11470
|
+
access_token: data.access_token,
|
|
11471
|
+
refresh_token: data.refresh_token || refreshToken,
|
|
11472
|
+
expires_in: data.expires_in,
|
|
11473
|
+
token_type: data.token_type
|
|
11474
|
+
};
|
|
11475
|
+
}
|
|
11476
|
+
const responseBody = await response.text().catch(() => {
|
|
11477
|
+
return;
|
|
11478
|
+
});
|
|
11479
|
+
const parsed = parseOAuthErrorPayload(responseBody);
|
|
11480
|
+
lastError = new AntigravityTokenRefreshError({
|
|
11481
|
+
message: parsed.description || `Token refresh failed: ${response.status} ${response.statusText}`,
|
|
11482
|
+
code: parsed.code,
|
|
11483
|
+
description: parsed.description,
|
|
11484
|
+
status: response.status,
|
|
11485
|
+
statusText: response.statusText,
|
|
11486
|
+
responseBody
|
|
11487
|
+
});
|
|
11488
|
+
if (parsed.code === "invalid_grant") {
|
|
11489
|
+
throw lastError;
|
|
11490
|
+
}
|
|
11491
|
+
if (!isRetryableError(response.status)) {
|
|
11492
|
+
throw lastError;
|
|
11493
|
+
}
|
|
11494
|
+
if (attempt < MAX_REFRESH_RETRIES) {
|
|
11495
|
+
const delay = calculateRetryDelay(attempt);
|
|
11496
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
11497
|
+
}
|
|
11498
|
+
} catch (error) {
|
|
11499
|
+
if (error instanceof AntigravityTokenRefreshError) {
|
|
11500
|
+
throw error;
|
|
11501
|
+
}
|
|
11502
|
+
lastError = new AntigravityTokenRefreshError({
|
|
11503
|
+
message: error instanceof Error ? error.message : "Network error during token refresh",
|
|
11504
|
+
status: 0,
|
|
11505
|
+
statusText: "Network Error"
|
|
11506
|
+
});
|
|
11507
|
+
if (attempt < MAX_REFRESH_RETRIES) {
|
|
11508
|
+
const delay = calculateRetryDelay(attempt);
|
|
11509
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
11510
|
+
}
|
|
11511
|
+
}
|
|
11416
11512
|
}
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
token_type: data.token_type
|
|
11423
|
-
};
|
|
11513
|
+
throw lastError || new AntigravityTokenRefreshError({
|
|
11514
|
+
message: "Token refresh failed after all retries",
|
|
11515
|
+
status: 0,
|
|
11516
|
+
statusText: "Max Retries Exceeded"
|
|
11517
|
+
});
|
|
11424
11518
|
}
|
|
11425
11519
|
function parseStoredToken(stored) {
|
|
11426
11520
|
const parts = stored.split("|");
|
|
@@ -11645,6 +11739,10 @@ function clearProjectContextCache(accessToken) {
|
|
|
11645
11739
|
projectContextCache.clear();
|
|
11646
11740
|
}
|
|
11647
11741
|
}
|
|
11742
|
+
function invalidateProjectContextByRefreshToken(_refreshToken) {
|
|
11743
|
+
projectContextCache.clear();
|
|
11744
|
+
debugLog4(`[invalidateProjectContextByRefreshToken] Cleared all project context cache due to refresh token invalidation`);
|
|
11745
|
+
}
|
|
11648
11746
|
// src/auth/antigravity/request.ts
|
|
11649
11747
|
function buildRequestHeaders(accessToken) {
|
|
11650
11748
|
return {
|
|
@@ -12359,7 +12457,7 @@ function debugLog7(message) {
|
|
|
12359
12457
|
console.log(`[antigravity-fetch] ${message}`);
|
|
12360
12458
|
}
|
|
12361
12459
|
}
|
|
12362
|
-
function
|
|
12460
|
+
function isRetryableError2(status) {
|
|
12363
12461
|
if (status === 0)
|
|
12364
12462
|
return true;
|
|
12365
12463
|
if (status === 429)
|
|
@@ -12377,11 +12475,11 @@ var GCP_PERMISSION_ERROR_PATTERNS = [
|
|
|
12377
12475
|
function isGcpPermissionError(text) {
|
|
12378
12476
|
return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
|
|
12379
12477
|
}
|
|
12380
|
-
function
|
|
12478
|
+
function calculateRetryDelay2(attempt) {
|
|
12381
12479
|
return Math.min(200 * Math.pow(2, attempt), 2000);
|
|
12382
12480
|
}
|
|
12383
12481
|
async function isRetryableResponse(response) {
|
|
12384
|
-
if (
|
|
12482
|
+
if (isRetryableError2(response.status))
|
|
12385
12483
|
return true;
|
|
12386
12484
|
if (response.status === 403) {
|
|
12387
12485
|
try {
|
|
@@ -12460,7 +12558,7 @@ async function attemptFetch(options) {
|
|
|
12460
12558
|
const text = await response.clone().text();
|
|
12461
12559
|
if (isGcpPermissionError(text)) {
|
|
12462
12560
|
if (attempt < maxPermissionRetries) {
|
|
12463
|
-
const delay =
|
|
12561
|
+
const delay = calculateRetryDelay2(attempt);
|
|
12464
12562
|
debugLog7(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
|
|
12465
12563
|
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
12466
12564
|
continue;
|
|
@@ -12581,6 +12679,14 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
|
|
|
12581
12679
|
});
|
|
12582
12680
|
debugLog7("Token refreshed successfully");
|
|
12583
12681
|
} catch (error) {
|
|
12682
|
+
if (error instanceof AntigravityTokenRefreshError) {
|
|
12683
|
+
if (error.isInvalidGrant) {
|
|
12684
|
+
debugLog7(`[REFRESH] Token revoked (invalid_grant), clearing caches`);
|
|
12685
|
+
invalidateProjectContextByRefreshToken(refreshParts.refreshToken);
|
|
12686
|
+
clearProjectContextCache();
|
|
12687
|
+
}
|
|
12688
|
+
throw new Error(`Antigravity: Token refresh failed: ${error.description || error.message}${error.code ? ` (${error.code})` : ""}`);
|
|
12689
|
+
}
|
|
12584
12690
|
throw new Error(`Antigravity: Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
12585
12691
|
}
|
|
12586
12692
|
}
|
|
@@ -12662,10 +12768,29 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
|
|
|
12662
12768
|
debugLog7("[401] Token refreshed, retrying request...");
|
|
12663
12769
|
return executeWithEndpoints();
|
|
12664
12770
|
} catch (refreshError) {
|
|
12771
|
+
if (refreshError instanceof AntigravityTokenRefreshError) {
|
|
12772
|
+
if (refreshError.isInvalidGrant) {
|
|
12773
|
+
debugLog7(`[401] Token revoked (invalid_grant), clearing caches`);
|
|
12774
|
+
invalidateProjectContextByRefreshToken(refreshParts.refreshToken);
|
|
12775
|
+
clearProjectContextCache();
|
|
12776
|
+
}
|
|
12777
|
+
debugLog7(`[401] Token refresh failed: ${refreshError.description || refreshError.message}`);
|
|
12778
|
+
return new Response(JSON.stringify({
|
|
12779
|
+
error: {
|
|
12780
|
+
message: refreshError.description || refreshError.message,
|
|
12781
|
+
type: refreshError.isInvalidGrant ? "token_revoked" : "unauthorized",
|
|
12782
|
+
code: refreshError.code || "token_refresh_failed"
|
|
12783
|
+
}
|
|
12784
|
+
}), {
|
|
12785
|
+
status: 401,
|
|
12786
|
+
statusText: "Unauthorized",
|
|
12787
|
+
headers: { "Content-Type": "application/json" }
|
|
12788
|
+
});
|
|
12789
|
+
}
|
|
12665
12790
|
debugLog7(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
|
|
12666
12791
|
return new Response(JSON.stringify({
|
|
12667
12792
|
error: {
|
|
12668
|
-
message:
|
|
12793
|
+
message: refreshError instanceof Error ? refreshError.message : "Unknown error",
|
|
12669
12794
|
type: "unauthorized",
|
|
12670
12795
|
code: "token_refresh_failed"
|
|
12671
12796
|
}
|
|
@@ -12827,81 +12952,504 @@ async function createGoogleAntigravityAuthPlugin({
|
|
|
12827
12952
|
auth: authHook
|
|
12828
12953
|
};
|
|
12829
12954
|
}
|
|
12830
|
-
// src/features/claude-code-command-loader/loader.ts
|
|
12831
|
-
import { existsSync as existsSync30, readdirSync as readdirSync11, readFileSync as readFileSync20 } from "fs";
|
|
12832
|
-
import { join as join40, basename } from "path";
|
|
12833
|
-
function loadCommandsFromDir(commandsDir, scope) {
|
|
12834
|
-
if (!existsSync30(commandsDir)) {
|
|
12835
|
-
return [];
|
|
12836
|
-
}
|
|
12837
|
-
const entries = readdirSync11(commandsDir, { withFileTypes: true });
|
|
12838
|
-
const commands = [];
|
|
12839
|
-
for (const entry of entries) {
|
|
12840
|
-
if (!isMarkdownFile(entry))
|
|
12841
|
-
continue;
|
|
12842
|
-
const commandPath = join40(commandsDir, entry.name);
|
|
12843
|
-
const commandName = basename(entry.name, ".md");
|
|
12844
|
-
try {
|
|
12845
|
-
const content = readFileSync20(commandPath, "utf-8");
|
|
12846
|
-
const { data, body } = parseFrontmatter(content);
|
|
12847
|
-
const wrappedTemplate = `<command-instruction>
|
|
12848
|
-
${body.trim()}
|
|
12955
|
+
// src/features/claude-code-command-loader/loader.ts
|
|
12956
|
+
import { existsSync as existsSync30, readdirSync as readdirSync11, readFileSync as readFileSync20 } from "fs";
|
|
12957
|
+
import { join as join40, basename } from "path";
|
|
12958
|
+
function loadCommandsFromDir(commandsDir, scope) {
|
|
12959
|
+
if (!existsSync30(commandsDir)) {
|
|
12960
|
+
return [];
|
|
12961
|
+
}
|
|
12962
|
+
const entries = readdirSync11(commandsDir, { withFileTypes: true });
|
|
12963
|
+
const commands = [];
|
|
12964
|
+
for (const entry of entries) {
|
|
12965
|
+
if (!isMarkdownFile(entry))
|
|
12966
|
+
continue;
|
|
12967
|
+
const commandPath = join40(commandsDir, entry.name);
|
|
12968
|
+
const commandName = basename(entry.name, ".md");
|
|
12969
|
+
try {
|
|
12970
|
+
const content = readFileSync20(commandPath, "utf-8");
|
|
12971
|
+
const { data, body } = parseFrontmatter(content);
|
|
12972
|
+
const wrappedTemplate = `<command-instruction>
|
|
12973
|
+
${body.trim()}
|
|
12974
|
+
</command-instruction>
|
|
12975
|
+
|
|
12976
|
+
<user-request>
|
|
12977
|
+
$ARGUMENTS
|
|
12978
|
+
</user-request>`;
|
|
12979
|
+
const formattedDescription = `(${scope}) ${data.description || ""}`;
|
|
12980
|
+
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
12981
|
+
const definition = {
|
|
12982
|
+
name: commandName,
|
|
12983
|
+
description: formattedDescription,
|
|
12984
|
+
template: wrappedTemplate,
|
|
12985
|
+
agent: data.agent,
|
|
12986
|
+
model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
|
|
12987
|
+
subtask: data.subtask,
|
|
12988
|
+
argumentHint: data["argument-hint"]
|
|
12989
|
+
};
|
|
12990
|
+
commands.push({
|
|
12991
|
+
name: commandName,
|
|
12992
|
+
path: commandPath,
|
|
12993
|
+
definition,
|
|
12994
|
+
scope
|
|
12995
|
+
});
|
|
12996
|
+
} catch {
|
|
12997
|
+
continue;
|
|
12998
|
+
}
|
|
12999
|
+
}
|
|
13000
|
+
return commands;
|
|
13001
|
+
}
|
|
13002
|
+
function commandsToRecord(commands) {
|
|
13003
|
+
const result = {};
|
|
13004
|
+
for (const cmd of commands) {
|
|
13005
|
+
result[cmd.name] = cmd.definition;
|
|
13006
|
+
}
|
|
13007
|
+
return result;
|
|
13008
|
+
}
|
|
13009
|
+
function loadUserCommands() {
|
|
13010
|
+
const userCommandsDir = join40(getClaudeConfigDir(), "commands");
|
|
13011
|
+
const commands = loadCommandsFromDir(userCommandsDir, "user");
|
|
13012
|
+
return commandsToRecord(commands);
|
|
13013
|
+
}
|
|
13014
|
+
function loadProjectCommands() {
|
|
13015
|
+
const projectCommandsDir = join40(process.cwd(), ".claude", "commands");
|
|
13016
|
+
const commands = loadCommandsFromDir(projectCommandsDir, "project");
|
|
13017
|
+
return commandsToRecord(commands);
|
|
13018
|
+
}
|
|
13019
|
+
function loadOpencodeGlobalCommands() {
|
|
13020
|
+
const { homedir: homedir9 } = __require("os");
|
|
13021
|
+
const opencodeCommandsDir = join40(homedir9(), ".config", "opencode", "command");
|
|
13022
|
+
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
13023
|
+
return commandsToRecord(commands);
|
|
13024
|
+
}
|
|
13025
|
+
function loadOpencodeProjectCommands() {
|
|
13026
|
+
const opencodeProjectDir = join40(process.cwd(), ".opencode", "command");
|
|
13027
|
+
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
13028
|
+
return commandsToRecord(commands);
|
|
13029
|
+
}
|
|
13030
|
+
// src/features/builtin-commands/templates/init-deep.ts
|
|
13031
|
+
var INIT_DEEP_TEMPLATE = `# Initialize Deep Knowledge Base
|
|
13032
|
+
|
|
13033
|
+
Generate comprehensive AGENTS.md files across project hierarchy. Combines root-level project knowledge (gen-knowledge) with complexity-based subdirectory documentation (gen-knowledge-deep).
|
|
13034
|
+
|
|
13035
|
+
## Usage
|
|
13036
|
+
|
|
13037
|
+
\`\`\`
|
|
13038
|
+
/init-deep # Analyze and generate hierarchical AGENTS.md
|
|
13039
|
+
/init-deep --create-new # Force create from scratch (ignore existing)
|
|
13040
|
+
/init-deep --max-depth=2 # Limit to N directory levels (default: 3)
|
|
13041
|
+
\`\`\`
|
|
13042
|
+
|
|
13043
|
+
---
|
|
13044
|
+
|
|
13045
|
+
## Core Principles
|
|
13046
|
+
|
|
13047
|
+
- **Telegraphic Style**: Sacrifice grammar for concision ("Project uses React" \u2192 "React 18")
|
|
13048
|
+
- **Predict-then-Compare**: Predict standard \u2192 find actual \u2192 document ONLY deviations
|
|
13049
|
+
- **Hierarchy Aware**: Parent covers general, children cover specific
|
|
13050
|
+
- **No Redundancy**: Child AGENTS.md NEVER repeats parent content
|
|
13051
|
+
- **LSP-First**: Use LSP tools for accurate code intelligence when available (semantic > text search)
|
|
13052
|
+
|
|
13053
|
+
---
|
|
13054
|
+
|
|
13055
|
+
## Process
|
|
13056
|
+
|
|
13057
|
+
<critical>
|
|
13058
|
+
**MANDATORY: TodoWrite for ALL phases. Mark in_progress \u2192 completed in real-time.**
|
|
13059
|
+
</critical>
|
|
13060
|
+
|
|
13061
|
+
### Phase 0: Initialize
|
|
13062
|
+
|
|
13063
|
+
\`\`\`
|
|
13064
|
+
TodoWrite([
|
|
13065
|
+
{ id: "p1-analysis", content: "Parallel project structure & complexity analysis", status: "pending", priority: "high" },
|
|
13066
|
+
{ id: "p2-scoring", content: "Score directories, determine AGENTS.md locations", status: "pending", priority: "high" },
|
|
13067
|
+
{ id: "p3-root", content: "Generate root AGENTS.md with Predict-then-Compare", status: "pending", priority: "high" },
|
|
13068
|
+
{ id: "p4-subdirs", content: "Generate subdirectory AGENTS.md files in parallel", status: "pending", priority: "high" },
|
|
13069
|
+
{ id: "p5-review", content: "Review, deduplicate, validate all files", status: "pending", priority: "medium" }
|
|
13070
|
+
])
|
|
13071
|
+
\`\`\`
|
|
13072
|
+
|
|
13073
|
+
---
|
|
13074
|
+
|
|
13075
|
+
## Phase 1: Parallel Project Analysis
|
|
13076
|
+
|
|
13077
|
+
**Mark "p1-analysis" as in_progress.**
|
|
13078
|
+
|
|
13079
|
+
Launch **ALL tasks simultaneously**:
|
|
13080
|
+
|
|
13081
|
+
<parallel-tasks>
|
|
13082
|
+
|
|
13083
|
+
### Structural Analysis (bash - run in parallel)
|
|
13084
|
+
\`\`\`bash
|
|
13085
|
+
# Task A: Directory depth analysis
|
|
13086
|
+
find . -type d -not -path '*/\\.*' -not -path '*/node_modules/*' -not -path '*/venv/*' -not -path '*/__pycache__/*' -not -path '*/dist/*' -not -path '*/build/*' | awk -F/ '{print NF-1}' | sort -n | uniq -c
|
|
13087
|
+
|
|
13088
|
+
# Task B: File count per directory
|
|
13089
|
+
find . -type f -not -path '*/\\.*' -not -path '*/node_modules/*' -not -path '*/venv/*' -not -path '*/__pycache__/*' | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -30
|
|
13090
|
+
|
|
13091
|
+
# Task C: Code concentration
|
|
13092
|
+
find . -type f \\( -name "*.py" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.go" -o -name "*.rs" -o -name "*.java" \\) -not -path '*/node_modules/*' -not -path '*/venv/*' | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -20
|
|
13093
|
+
|
|
13094
|
+
# Task D: Existing knowledge files
|
|
13095
|
+
find . -type f \\( -name "AGENTS.md" -o -name "CLAUDE.md" \\) -not -path '*/node_modules/*' 2>/dev/null
|
|
13096
|
+
\`\`\`
|
|
13097
|
+
|
|
13098
|
+
### Context Gathering (Explore agents - background_task in parallel)
|
|
13099
|
+
|
|
13100
|
+
\`\`\`
|
|
13101
|
+
background_task(agent="explore", prompt="Project structure: PREDICT standard {lang} patterns \u2192 FIND package.json/pyproject.toml/go.mod \u2192 REPORT deviations only")
|
|
13102
|
+
|
|
13103
|
+
background_task(agent="explore", prompt="Entry points: PREDICT typical (main.py, index.ts) \u2192 FIND actual \u2192 REPORT non-standard organization")
|
|
13104
|
+
|
|
13105
|
+
background_task(agent="explore", prompt="Conventions: FIND .cursor/rules, .cursorrules, eslintrc, pyproject.toml \u2192 REPORT project-specific rules DIFFERENT from defaults")
|
|
13106
|
+
|
|
13107
|
+
background_task(agent="explore", prompt="Anti-patterns: FIND comments with 'DO NOT', 'NEVER', 'ALWAYS', 'LEGACY', 'DEPRECATED' \u2192 REPORT forbidden patterns")
|
|
13108
|
+
|
|
13109
|
+
background_task(agent="explore", prompt="Build/CI: FIND .github/workflows, Makefile, justfile \u2192 REPORT non-standard build/deploy patterns")
|
|
13110
|
+
|
|
13111
|
+
background_task(agent="explore", prompt="Test patterns: FIND pytest.ini, jest.config, test structure \u2192 REPORT unique testing conventions")
|
|
13112
|
+
\`\`\`
|
|
13113
|
+
|
|
13114
|
+
### Code Intelligence Analysis (LSP tools - run in parallel)
|
|
13115
|
+
|
|
13116
|
+
LSP provides semantic understanding beyond text search. Use for accurate code mapping.
|
|
13117
|
+
|
|
13118
|
+
\`\`\`
|
|
13119
|
+
# Step 1: Check LSP availability
|
|
13120
|
+
lsp_servers() # Verify language server is available
|
|
13121
|
+
|
|
13122
|
+
# Step 2: Analyze entry point files (run in parallel)
|
|
13123
|
+
# Find entry points first, then analyze each with lsp_document_symbols
|
|
13124
|
+
lsp_document_symbols(filePath="src/index.ts") # Main entry
|
|
13125
|
+
lsp_document_symbols(filePath="src/main.py") # Python entry
|
|
13126
|
+
lsp_document_symbols(filePath="cmd/main.go") # Go entry
|
|
13127
|
+
|
|
13128
|
+
# Step 3: Discover key symbols across workspace (run in parallel)
|
|
13129
|
+
lsp_workspace_symbols(filePath=".", query="class") # All classes
|
|
13130
|
+
lsp_workspace_symbols(filePath=".", query="interface") # All interfaces
|
|
13131
|
+
lsp_workspace_symbols(filePath=".", query="function") # Top-level functions
|
|
13132
|
+
lsp_workspace_symbols(filePath=".", query="type") # Type definitions
|
|
13133
|
+
|
|
13134
|
+
# Step 4: Analyze symbol centrality (for top 5-10 key symbols)
|
|
13135
|
+
# High reference count = central/important concept
|
|
13136
|
+
lsp_find_references(filePath="src/index.ts", line=X, character=Y) # Main export
|
|
13137
|
+
\`\`\`
|
|
13138
|
+
|
|
13139
|
+
#### LSP Analysis Output Format
|
|
13140
|
+
|
|
13141
|
+
\`\`\`
|
|
13142
|
+
CODE_INTELLIGENCE = {
|
|
13143
|
+
entry_points: [
|
|
13144
|
+
{ file: "src/index.ts", exports: ["Plugin", "createHook"], symbol_count: 12 }
|
|
13145
|
+
],
|
|
13146
|
+
key_symbols: [
|
|
13147
|
+
{ name: "Plugin", type: "class", file: "src/index.ts", refs: 45, role: "Central orchestrator" },
|
|
13148
|
+
{ name: "createHook", type: "function", file: "src/utils.ts", refs: 23, role: "Hook factory" }
|
|
13149
|
+
],
|
|
13150
|
+
module_boundaries: [
|
|
13151
|
+
{ dir: "src/hooks", exports: 21, imports_from: ["shared/"] },
|
|
13152
|
+
{ dir: "src/tools", exports: 15, imports_from: ["shared/", "hooks/"] }
|
|
13153
|
+
]
|
|
13154
|
+
}
|
|
13155
|
+
\`\`\`
|
|
13156
|
+
|
|
13157
|
+
<critical>
|
|
13158
|
+
**LSP Fallback**: If LSP unavailable (no server installed), skip this section and rely on explore agents + AST-grep patterns.
|
|
13159
|
+
</critical>
|
|
13160
|
+
|
|
13161
|
+
</parallel-tasks>
|
|
13162
|
+
|
|
13163
|
+
**Collect all results. Mark "p1-analysis" as completed.**
|
|
13164
|
+
|
|
13165
|
+
---
|
|
13166
|
+
|
|
13167
|
+
## Phase 2: Complexity Scoring & Location Decision
|
|
13168
|
+
|
|
13169
|
+
**Mark "p2-scoring" as in_progress.**
|
|
13170
|
+
|
|
13171
|
+
### Scoring Matrix
|
|
13172
|
+
|
|
13173
|
+
| Factor | Weight | Threshold | Source |
|
|
13174
|
+
|--------|--------|-----------|--------|
|
|
13175
|
+
| File count | 3x | >20 files = high | bash |
|
|
13176
|
+
| Subdirectory count | 2x | >5 subdirs = high | bash |
|
|
13177
|
+
| Code file ratio | 2x | >70% code = high | bash |
|
|
13178
|
+
| Unique patterns | 1x | Has own config | explore |
|
|
13179
|
+
| Module boundary | 2x | Has __init__.py/index.ts | bash |
|
|
13180
|
+
| **Symbol density** | 2x | >30 symbols = high | LSP |
|
|
13181
|
+
| **Export count** | 2x | >10 exports = high | LSP |
|
|
13182
|
+
| **Reference centrality** | 3x | Symbols with >20 refs | LSP |
|
|
13183
|
+
|
|
13184
|
+
<lsp-scoring>
|
|
13185
|
+
**LSP-Enhanced Scoring** (if available):
|
|
13186
|
+
|
|
13187
|
+
\`\`\`
|
|
13188
|
+
For each directory in candidates:
|
|
13189
|
+
symbols = lsp_document_symbols(dir/index.ts or dir/__init__.py)
|
|
13190
|
+
|
|
13191
|
+
symbol_score = len(symbols) > 30 ? 6 : len(symbols) > 15 ? 3 : 0
|
|
13192
|
+
export_score = count(exported symbols) > 10 ? 4 : 0
|
|
13193
|
+
|
|
13194
|
+
# Check if this module is central (many things depend on it)
|
|
13195
|
+
for each exported symbol:
|
|
13196
|
+
refs = lsp_find_references(symbol)
|
|
13197
|
+
if refs > 20: centrality_score += 3
|
|
13198
|
+
|
|
13199
|
+
total_score += symbol_score + export_score + centrality_score
|
|
13200
|
+
\`\`\`
|
|
13201
|
+
</lsp-scoring>
|
|
13202
|
+
|
|
13203
|
+
### Decision Rules
|
|
13204
|
+
|
|
13205
|
+
| Score | Action |
|
|
13206
|
+
|-------|--------|
|
|
13207
|
+
| **Root (.)** | ALWAYS create AGENTS.md |
|
|
13208
|
+
| **High (>15)** | Create dedicated AGENTS.md |
|
|
13209
|
+
| **Medium (8-15)** | Create if distinct domain |
|
|
13210
|
+
| **Low (<8)** | Skip, parent sufficient |
|
|
13211
|
+
|
|
13212
|
+
### Output Format
|
|
13213
|
+
|
|
13214
|
+
\`\`\`
|
|
13215
|
+
AGENTS_LOCATIONS = [
|
|
13216
|
+
{ path: ".", type: "root" },
|
|
13217
|
+
{ path: "src/api", score: 18, reason: "high complexity, 45 files" },
|
|
13218
|
+
{ path: "src/hooks", score: 12, reason: "distinct domain, unique patterns" },
|
|
13219
|
+
]
|
|
13220
|
+
\`\`\`
|
|
13221
|
+
|
|
13222
|
+
**Mark "p2-scoring" as completed.**
|
|
13223
|
+
|
|
13224
|
+
---
|
|
13225
|
+
|
|
13226
|
+
## Phase 3: Generate Root AGENTS.md
|
|
13227
|
+
|
|
13228
|
+
**Mark "p3-root" as in_progress.**
|
|
13229
|
+
|
|
13230
|
+
Root AGENTS.md gets **full treatment** with Predict-then-Compare synthesis.
|
|
13231
|
+
|
|
13232
|
+
### Required Sections
|
|
13233
|
+
|
|
13234
|
+
\`\`\`markdown
|
|
13235
|
+
# PROJECT KNOWLEDGE BASE
|
|
13236
|
+
|
|
13237
|
+
**Generated:** {TIMESTAMP}
|
|
13238
|
+
**Commit:** {SHORT_SHA}
|
|
13239
|
+
**Branch:** {BRANCH}
|
|
13240
|
+
|
|
13241
|
+
## OVERVIEW
|
|
13242
|
+
|
|
13243
|
+
{1-2 sentences: what project does, core tech stack}
|
|
13244
|
+
|
|
13245
|
+
## STRUCTURE
|
|
13246
|
+
|
|
13247
|
+
\\\`\\\`\\\`
|
|
13248
|
+
{project-root}/
|
|
13249
|
+
\u251C\u2500\u2500 {dir}/ # {non-obvious purpose only}
|
|
13250
|
+
\u2514\u2500\u2500 {entry} # entry point
|
|
13251
|
+
\\\`\\\`\\\`
|
|
13252
|
+
|
|
13253
|
+
## WHERE TO LOOK
|
|
13254
|
+
|
|
13255
|
+
| Task | Location | Notes |
|
|
13256
|
+
|------|----------|-------|
|
|
13257
|
+
| Add feature X | \\\`src/x/\\\` | {pattern hint} |
|
|
13258
|
+
|
|
13259
|
+
## CODE MAP
|
|
13260
|
+
|
|
13261
|
+
{Generated from LSP analysis - shows key symbols and their relationships}
|
|
13262
|
+
|
|
13263
|
+
| Symbol | Type | Location | Refs | Role |
|
|
13264
|
+
|--------|------|----------|------|------|
|
|
13265
|
+
| {MainClass} | Class | \\\`src/index.ts\\\` | {N} | {Central orchestrator} |
|
|
13266
|
+
| {createX} | Function | \\\`src/utils.ts\\\` | {N} | {Factory pattern} |
|
|
13267
|
+
| {Config} | Interface | \\\`src/types.ts\\\` | {N} | {Configuration contract} |
|
|
13268
|
+
|
|
13269
|
+
### Module Dependencies
|
|
13270
|
+
|
|
13271
|
+
\\\`\\\`\\\`
|
|
13272
|
+
{entry} \u2500\u2500imports\u2500\u2500> {core/}
|
|
13273
|
+
\u2502 \u2502
|
|
13274
|
+
\u2514\u2500\u2500imports\u2500\u2500> {utils/} <\u2500\u2500imports\u2500\u2500 {features/}
|
|
13275
|
+
\\\`\\\`\\\`
|
|
13276
|
+
|
|
13277
|
+
<code-map-note>
|
|
13278
|
+
**Skip CODE MAP if**: LSP unavailable OR project too small (<10 files) OR no clear module boundaries.
|
|
13279
|
+
</code-map-note>
|
|
13280
|
+
|
|
13281
|
+
## CONVENTIONS
|
|
13282
|
+
|
|
13283
|
+
{ONLY deviations from standard - skip generic advice}
|
|
13284
|
+
|
|
13285
|
+
- **{rule}**: {specific detail}
|
|
13286
|
+
|
|
13287
|
+
## ANTI-PATTERNS (THIS PROJECT)
|
|
13288
|
+
|
|
13289
|
+
{Things explicitly forbidden HERE}
|
|
13290
|
+
|
|
13291
|
+
- **{pattern}**: {why} \u2192 {alternative}
|
|
13292
|
+
|
|
13293
|
+
## UNIQUE STYLES
|
|
13294
|
+
|
|
13295
|
+
{Project-specific coding styles}
|
|
13296
|
+
|
|
13297
|
+
- **{style}**: {how different}
|
|
13298
|
+
|
|
13299
|
+
## COMMANDS
|
|
13300
|
+
|
|
13301
|
+
\\\`\\\`\\\`bash
|
|
13302
|
+
{dev-command}
|
|
13303
|
+
{test-command}
|
|
13304
|
+
{build-command}
|
|
13305
|
+
\\\`\\\`\\\`
|
|
13306
|
+
|
|
13307
|
+
## NOTES
|
|
13308
|
+
|
|
13309
|
+
{Gotchas, non-obvious info}
|
|
13310
|
+
\`\`\`
|
|
13311
|
+
|
|
13312
|
+
### Quality Gates
|
|
13313
|
+
|
|
13314
|
+
- [ ] Size: 50-150 lines
|
|
13315
|
+
- [ ] No generic advice ("write clean code")
|
|
13316
|
+
- [ ] No obvious info ("tests/ has tests")
|
|
13317
|
+
- [ ] Every item is project-specific
|
|
13318
|
+
|
|
13319
|
+
**Mark "p3-root" as completed.**
|
|
13320
|
+
|
|
13321
|
+
---
|
|
13322
|
+
|
|
13323
|
+
## Phase 4: Generate Subdirectory AGENTS.md
|
|
13324
|
+
|
|
13325
|
+
**Mark "p4-subdirs" as in_progress.**
|
|
13326
|
+
|
|
13327
|
+
For each location in AGENTS_LOCATIONS (except root), launch **parallel document-writer agents**:
|
|
13328
|
+
|
|
13329
|
+
\`\`\`typescript
|
|
13330
|
+
for (const loc of AGENTS_LOCATIONS.filter(l => l.path !== ".")) {
|
|
13331
|
+
background_task({
|
|
13332
|
+
agent: "document-writer",
|
|
13333
|
+
prompt: \\\`
|
|
13334
|
+
Generate AGENTS.md for: \${loc.path}
|
|
13335
|
+
|
|
13336
|
+
CONTEXT:
|
|
13337
|
+
- Complexity reason: \${loc.reason}
|
|
13338
|
+
- Parent AGENTS.md: ./AGENTS.md (already covers project overview)
|
|
13339
|
+
|
|
13340
|
+
CRITICAL RULES:
|
|
13341
|
+
1. Focus ONLY on this directory's specific context
|
|
13342
|
+
2. NEVER repeat parent AGENTS.md content
|
|
13343
|
+
3. Shorter is better - 30-80 lines max
|
|
13344
|
+
4. Telegraphic style - sacrifice grammar
|
|
13345
|
+
|
|
13346
|
+
REQUIRED SECTIONS:
|
|
13347
|
+
- OVERVIEW (1 line: what this directory does)
|
|
13348
|
+
- STRUCTURE (only if >5 subdirs)
|
|
13349
|
+
- WHERE TO LOOK (directory-specific tasks)
|
|
13350
|
+
- CONVENTIONS (only if DIFFERENT from root)
|
|
13351
|
+
- ANTI-PATTERNS (directory-specific only)
|
|
13352
|
+
|
|
13353
|
+
OUTPUT: Write to \${loc.path}/AGENTS.md
|
|
13354
|
+
\\\`
|
|
13355
|
+
})
|
|
13356
|
+
}
|
|
13357
|
+
\`\`\`
|
|
13358
|
+
|
|
13359
|
+
**Wait for all agents. Mark "p4-subdirs" as completed.**
|
|
13360
|
+
|
|
13361
|
+
---
|
|
13362
|
+
|
|
13363
|
+
## Phase 5: Review & Deduplicate
|
|
13364
|
+
|
|
13365
|
+
**Mark "p5-review" as in_progress.**
|
|
13366
|
+
|
|
13367
|
+
### Validation Checklist
|
|
13368
|
+
|
|
13369
|
+
For EACH generated AGENTS.md:
|
|
13370
|
+
|
|
13371
|
+
| Check | Action if Fail |
|
|
13372
|
+
|-------|----------------|
|
|
13373
|
+
| Contains generic advice | REMOVE the line |
|
|
13374
|
+
| Repeats parent content | REMOVE the line |
|
|
13375
|
+
| Missing required section | ADD it |
|
|
13376
|
+
| Over 150 lines (root) / 80 lines (subdir) | TRIM |
|
|
13377
|
+
| Verbose explanations | REWRITE telegraphic |
|
|
13378
|
+
|
|
13379
|
+
### Cross-Reference Validation
|
|
13380
|
+
|
|
13381
|
+
\`\`\`
|
|
13382
|
+
For each child AGENTS.md:
|
|
13383
|
+
For each line in child:
|
|
13384
|
+
If similar line exists in parent:
|
|
13385
|
+
REMOVE from child (parent already covers)
|
|
13386
|
+
\`\`\`
|
|
13387
|
+
|
|
13388
|
+
**Mark "p5-review" as completed.**
|
|
13389
|
+
|
|
13390
|
+
---
|
|
13391
|
+
|
|
13392
|
+
## Final Report
|
|
13393
|
+
|
|
13394
|
+
\`\`\`
|
|
13395
|
+
=== init-deep Complete ===
|
|
13396
|
+
|
|
13397
|
+
Files Generated:
|
|
13398
|
+
\u2713 ./AGENTS.md (root, {N} lines)
|
|
13399
|
+
\u2713 ./src/hooks/AGENTS.md ({N} lines)
|
|
13400
|
+
\u2713 ./src/tools/AGENTS.md ({N} lines)
|
|
13401
|
+
|
|
13402
|
+
Directories Analyzed: {N}
|
|
13403
|
+
AGENTS.md Created: {N}
|
|
13404
|
+
Total Lines: {N}
|
|
13405
|
+
|
|
13406
|
+
Hierarchy:
|
|
13407
|
+
./AGENTS.md
|
|
13408
|
+
\u251C\u2500\u2500 src/hooks/AGENTS.md
|
|
13409
|
+
\u2514\u2500\u2500 src/tools/AGENTS.md
|
|
13410
|
+
\`\`\`
|
|
13411
|
+
|
|
13412
|
+
---
|
|
13413
|
+
|
|
13414
|
+
## Anti-Patterns for THIS Command
|
|
13415
|
+
|
|
13416
|
+
- **Over-documenting**: Not every directory needs AGENTS.md
|
|
13417
|
+
- **Redundancy**: Child must NOT repeat parent
|
|
13418
|
+
- **Generic content**: Remove anything that applies to ALL projects
|
|
13419
|
+
- **Sequential execution**: MUST use parallel agents
|
|
13420
|
+
- **Deep nesting**: Rarely need AGENTS.md at depth 4+
|
|
13421
|
+
- **Verbose style**: "This directory contains..." \u2192 just list it
|
|
13422
|
+
- **Ignoring LSP**: If LSP available, USE IT - semantic analysis > text grep
|
|
13423
|
+
- **LSP without fallback**: Always have explore agent backup if LSP unavailable
|
|
13424
|
+
- **Over-referencing**: Don't trace refs for EVERY symbol - focus on exports only`;
|
|
13425
|
+
|
|
13426
|
+
// src/features/builtin-commands/commands.ts
|
|
13427
|
+
var BUILTIN_COMMAND_DEFINITIONS = {
|
|
13428
|
+
"init-deep": {
|
|
13429
|
+
description: "(builtin) Initialize hierarchical AGENTS.md knowledge base",
|
|
13430
|
+
template: `<command-instruction>
|
|
13431
|
+
${INIT_DEEP_TEMPLATE}
|
|
12849
13432
|
</command-instruction>
|
|
12850
13433
|
|
|
12851
13434
|
<user-request>
|
|
12852
13435
|
$ARGUMENTS
|
|
12853
|
-
</user-request
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
13436
|
+
</user-request>`,
|
|
13437
|
+
argumentHint: "[--create-new] [--max-depth=N]"
|
|
13438
|
+
}
|
|
13439
|
+
};
|
|
13440
|
+
function loadBuiltinCommands(disabledCommands) {
|
|
13441
|
+
const disabled = new Set(disabledCommands ?? []);
|
|
13442
|
+
const commands = {};
|
|
13443
|
+
for (const [name, definition] of Object.entries(BUILTIN_COMMAND_DEFINITIONS)) {
|
|
13444
|
+
if (!disabled.has(name)) {
|
|
13445
|
+
commands[name] = {
|
|
13446
|
+
name,
|
|
13447
|
+
...definition
|
|
12864
13448
|
};
|
|
12865
|
-
commands.push({
|
|
12866
|
-
name: commandName,
|
|
12867
|
-
path: commandPath,
|
|
12868
|
-
definition,
|
|
12869
|
-
scope
|
|
12870
|
-
});
|
|
12871
|
-
} catch {
|
|
12872
|
-
continue;
|
|
12873
13449
|
}
|
|
12874
13450
|
}
|
|
12875
13451
|
return commands;
|
|
12876
13452
|
}
|
|
12877
|
-
function commandsToRecord(commands) {
|
|
12878
|
-
const result = {};
|
|
12879
|
-
for (const cmd of commands) {
|
|
12880
|
-
result[cmd.name] = cmd.definition;
|
|
12881
|
-
}
|
|
12882
|
-
return result;
|
|
12883
|
-
}
|
|
12884
|
-
function loadUserCommands() {
|
|
12885
|
-
const userCommandsDir = join40(getClaudeConfigDir(), "commands");
|
|
12886
|
-
const commands = loadCommandsFromDir(userCommandsDir, "user");
|
|
12887
|
-
return commandsToRecord(commands);
|
|
12888
|
-
}
|
|
12889
|
-
function loadProjectCommands() {
|
|
12890
|
-
const projectCommandsDir = join40(process.cwd(), ".claude", "commands");
|
|
12891
|
-
const commands = loadCommandsFromDir(projectCommandsDir, "project");
|
|
12892
|
-
return commandsToRecord(commands);
|
|
12893
|
-
}
|
|
12894
|
-
function loadOpencodeGlobalCommands() {
|
|
12895
|
-
const { homedir: homedir10 } = __require("os");
|
|
12896
|
-
const opencodeCommandsDir = join40(homedir10(), ".config", "opencode", "command");
|
|
12897
|
-
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
12898
|
-
return commandsToRecord(commands);
|
|
12899
|
-
}
|
|
12900
|
-
function loadOpencodeProjectCommands() {
|
|
12901
|
-
const opencodeProjectDir = join40(process.cwd(), ".opencode", "command");
|
|
12902
|
-
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
12903
|
-
return commandsToRecord(commands);
|
|
12904
|
-
}
|
|
12905
13453
|
// src/features/claude-code-agent-loader/loader.ts
|
|
12906
13454
|
import { existsSync as existsSync31, readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
|
|
12907
13455
|
import { join as join41, basename as basename2 } from "path";
|
|
@@ -13065,13 +13613,13 @@ async function loadMcpConfigs() {
|
|
|
13065
13613
|
const servers = {};
|
|
13066
13614
|
const loadedServers = [];
|
|
13067
13615
|
const paths = getMcpConfigPaths();
|
|
13068
|
-
for (const { path:
|
|
13069
|
-
const config = await loadMcpConfigFile(
|
|
13616
|
+
for (const { path: path7, scope } of paths) {
|
|
13617
|
+
const config = await loadMcpConfigFile(path7);
|
|
13070
13618
|
if (!config?.mcpServers)
|
|
13071
13619
|
continue;
|
|
13072
13620
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
13073
13621
|
if (serverConfig.disabled) {
|
|
13074
|
-
log(`Skipping disabled MCP server "${name}"`, { path:
|
|
13622
|
+
log(`Skipping disabled MCP server "${name}"`, { path: path7 });
|
|
13075
13623
|
continue;
|
|
13076
13624
|
}
|
|
13077
13625
|
try {
|
|
@@ -13082,7 +13630,7 @@ async function loadMcpConfigs() {
|
|
|
13082
13630
|
loadedServers.splice(existingIndex, 1);
|
|
13083
13631
|
}
|
|
13084
13632
|
loadedServers.push({ name, scope, config: transformed });
|
|
13085
|
-
log(`Loaded MCP server "${name}" from ${scope}`, { path:
|
|
13633
|
+
log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
|
|
13086
13634
|
} catch (error) {
|
|
13087
13635
|
log(`Failed to transform MCP server "${name}"`, error);
|
|
13088
13636
|
}
|
|
@@ -13092,20 +13640,20 @@ async function loadMcpConfigs() {
|
|
|
13092
13640
|
}
|
|
13093
13641
|
// src/features/claude-code-plugin-loader/loader.ts
|
|
13094
13642
|
import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync22 } from "fs";
|
|
13095
|
-
import { homedir as
|
|
13643
|
+
import { homedir as homedir9 } from "os";
|
|
13096
13644
|
import { join as join43, basename as basename3 } from "path";
|
|
13097
13645
|
var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
|
|
13098
13646
|
function getPluginsBaseDir() {
|
|
13099
13647
|
if (process.env.CLAUDE_PLUGINS_HOME) {
|
|
13100
13648
|
return process.env.CLAUDE_PLUGINS_HOME;
|
|
13101
13649
|
}
|
|
13102
|
-
return join43(
|
|
13650
|
+
return join43(homedir9(), ".claude", "plugins");
|
|
13103
13651
|
}
|
|
13104
13652
|
function getInstalledPluginsPath() {
|
|
13105
13653
|
return join43(getPluginsBaseDir(), "installed_plugins.json");
|
|
13106
13654
|
}
|
|
13107
|
-
function resolvePluginPath(
|
|
13108
|
-
return
|
|
13655
|
+
function resolvePluginPath(path7, pluginRoot) {
|
|
13656
|
+
return path7.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
|
|
13109
13657
|
}
|
|
13110
13658
|
function resolvePluginPaths(obj, pluginRoot) {
|
|
13111
13659
|
if (obj === null || obj === undefined)
|
|
@@ -13142,7 +13690,7 @@ function getClaudeSettingsPath() {
|
|
|
13142
13690
|
if (process.env.CLAUDE_SETTINGS_PATH) {
|
|
13143
13691
|
return process.env.CLAUDE_SETTINGS_PATH;
|
|
13144
13692
|
}
|
|
13145
|
-
return join43(
|
|
13693
|
+
return join43(homedir9(), ".claude", "settings.json");
|
|
13146
13694
|
}
|
|
13147
13695
|
function loadClaudeSettings() {
|
|
13148
13696
|
const settingsPath = getClaudeSettingsPath();
|
|
@@ -13251,7 +13799,7 @@ function discoverInstalledPlugins(options) {
|
|
|
13251
13799
|
return { plugins, errors };
|
|
13252
13800
|
}
|
|
13253
13801
|
function loadPluginCommands(plugins) {
|
|
13254
|
-
const
|
|
13802
|
+
const commands2 = {};
|
|
13255
13803
|
for (const plugin2 of plugins) {
|
|
13256
13804
|
if (!plugin2.commandsDir || !existsSync33(plugin2.commandsDir))
|
|
13257
13805
|
continue;
|
|
@@ -13273,7 +13821,7 @@ ${body.trim()}
|
|
|
13273
13821
|
$ARGUMENTS
|
|
13274
13822
|
</user-request>`;
|
|
13275
13823
|
const formattedDescription = `(plugin: ${plugin2.name}) ${data.description || ""}`;
|
|
13276
|
-
|
|
13824
|
+
commands2[namespacedName] = {
|
|
13277
13825
|
name: namespacedName,
|
|
13278
13826
|
description: formattedDescription,
|
|
13279
13827
|
template: wrappedTemplate,
|
|
@@ -13288,7 +13836,7 @@ $ARGUMENTS
|
|
|
13288
13836
|
}
|
|
13289
13837
|
}
|
|
13290
13838
|
}
|
|
13291
|
-
return
|
|
13839
|
+
return commands2;
|
|
13292
13840
|
}
|
|
13293
13841
|
function loadPluginSkillsAsCommands(plugins) {
|
|
13294
13842
|
const skills = {};
|
|
@@ -13436,14 +13984,14 @@ function loadPluginHooksConfigs(plugins) {
|
|
|
13436
13984
|
}
|
|
13437
13985
|
async function loadAllPluginComponents(options) {
|
|
13438
13986
|
const { plugins, errors } = discoverInstalledPlugins(options);
|
|
13439
|
-
const
|
|
13987
|
+
const commands2 = loadPluginCommands(plugins);
|
|
13440
13988
|
const skills = loadPluginSkillsAsCommands(plugins);
|
|
13441
13989
|
const agents = loadPluginAgents(plugins);
|
|
13442
13990
|
const mcpServers = await loadPluginMcpServers(plugins);
|
|
13443
13991
|
const hooksConfigs = loadPluginHooksConfigs(plugins);
|
|
13444
|
-
log(`Loaded ${plugins.length} plugins with ${Object.keys(
|
|
13992
|
+
log(`Loaded ${plugins.length} plugins with ${Object.keys(commands2).length} commands, ${Object.keys(skills).length} skills, ${Object.keys(agents).length} agents, ${Object.keys(mcpServers).length} MCP servers`);
|
|
13445
13993
|
return {
|
|
13446
|
-
commands,
|
|
13994
|
+
commands: commands2,
|
|
13447
13995
|
skills,
|
|
13448
13996
|
agents,
|
|
13449
13997
|
mcpServers,
|
|
@@ -13490,6 +14038,36 @@ var SEVERITY_MAP = {
|
|
|
13490
14038
|
var DEFAULT_MAX_REFERENCES = 200;
|
|
13491
14039
|
var DEFAULT_MAX_SYMBOLS = 200;
|
|
13492
14040
|
var DEFAULT_MAX_DIAGNOSTICS = 200;
|
|
14041
|
+
var LSP_INSTALL_HINTS = {
|
|
14042
|
+
typescript: "npm install -g typescript-language-server typescript",
|
|
14043
|
+
deno: "Install Deno from https://deno.land",
|
|
14044
|
+
vue: "npm install -g @vue/language-server",
|
|
14045
|
+
eslint: "npm install -g vscode-langservers-extracted",
|
|
14046
|
+
oxlint: "npm install -g oxlint",
|
|
14047
|
+
biome: "npm install -g @biomejs/biome",
|
|
14048
|
+
gopls: "go install golang.org/x/tools/gopls@latest",
|
|
14049
|
+
"ruby-lsp": "gem install ruby-lsp",
|
|
14050
|
+
basedpyright: "pip install basedpyright",
|
|
14051
|
+
pyright: "pip install pyright",
|
|
14052
|
+
ty: "pip install ty",
|
|
14053
|
+
ruff: "pip install ruff",
|
|
14054
|
+
"elixir-ls": "See https://github.com/elixir-lsp/elixir-ls",
|
|
14055
|
+
zls: "See https://github.com/zigtools/zls",
|
|
14056
|
+
csharp: "dotnet tool install -g csharp-ls",
|
|
14057
|
+
fsharp: "dotnet tool install -g fsautocomplete",
|
|
14058
|
+
"sourcekit-lsp": "Included with Xcode or Swift toolchain",
|
|
14059
|
+
rust: "rustup component add rust-analyzer",
|
|
14060
|
+
clangd: "See https://clangd.llvm.org/installation",
|
|
14061
|
+
svelte: "npm install -g svelte-language-server",
|
|
14062
|
+
astro: "npm install -g @astrojs/language-server",
|
|
14063
|
+
"bash-ls": "npm install -g bash-language-server",
|
|
14064
|
+
jdtls: "See https://github.com/eclipse-jdtls/eclipse.jdt.ls",
|
|
14065
|
+
"yaml-ls": "npm install -g yaml-language-server",
|
|
14066
|
+
"lua-ls": "See https://github.com/LuaLS/lua-language-server",
|
|
14067
|
+
php: "npm install -g intelephense",
|
|
14068
|
+
dart: "Included with Dart SDK",
|
|
14069
|
+
"terraform-ls": "See https://github.com/hashicorp/terraform-ls"
|
|
14070
|
+
};
|
|
13493
14071
|
var BUILTIN_SERVERS = {
|
|
13494
14072
|
typescript: {
|
|
13495
14073
|
command: ["typescript-language-server", "--stdio"],
|
|
@@ -13749,12 +14327,12 @@ var EXT_TO_LANG = {
|
|
|
13749
14327
|
// src/tools/lsp/config.ts
|
|
13750
14328
|
import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
|
|
13751
14329
|
import { join as join44 } from "path";
|
|
13752
|
-
import { homedir as
|
|
13753
|
-
function loadJsonFile(
|
|
13754
|
-
if (!existsSync34(
|
|
14330
|
+
import { homedir as homedir10 } from "os";
|
|
14331
|
+
function loadJsonFile(path7) {
|
|
14332
|
+
if (!existsSync34(path7))
|
|
13755
14333
|
return null;
|
|
13756
14334
|
try {
|
|
13757
|
-
return JSON.parse(readFileSync23(
|
|
14335
|
+
return JSON.parse(readFileSync23(path7, "utf-8"));
|
|
13758
14336
|
} catch {
|
|
13759
14337
|
return null;
|
|
13760
14338
|
}
|
|
@@ -13763,8 +14341,8 @@ function getConfigPaths2() {
|
|
|
13763
14341
|
const cwd = process.cwd();
|
|
13764
14342
|
return {
|
|
13765
14343
|
project: join44(cwd, ".opencode", "oh-my-opencode.json"),
|
|
13766
|
-
user: join44(
|
|
13767
|
-
opencode: join44(
|
|
14344
|
+
user: join44(homedir10(), ".config", "opencode", "oh-my-opencode.json"),
|
|
14345
|
+
opencode: join44(homedir10(), ".config", "opencode", "opencode.json")
|
|
13768
14346
|
};
|
|
13769
14347
|
}
|
|
13770
14348
|
function loadAllConfigs() {
|
|
@@ -13836,16 +14414,38 @@ function findServerForExtension(ext) {
|
|
|
13836
14414
|
for (const server of servers) {
|
|
13837
14415
|
if (server.extensions.includes(ext) && isServerInstalled(server.command)) {
|
|
13838
14416
|
return {
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
14417
|
+
status: "found",
|
|
14418
|
+
server: {
|
|
14419
|
+
id: server.id,
|
|
14420
|
+
command: server.command,
|
|
14421
|
+
extensions: server.extensions,
|
|
14422
|
+
priority: server.priority,
|
|
14423
|
+
env: server.env,
|
|
14424
|
+
initialization: server.initialization
|
|
14425
|
+
}
|
|
13845
14426
|
};
|
|
13846
14427
|
}
|
|
13847
14428
|
}
|
|
13848
|
-
|
|
14429
|
+
for (const server of servers) {
|
|
14430
|
+
if (server.extensions.includes(ext)) {
|
|
14431
|
+
const installHint = LSP_INSTALL_HINTS[server.id] || `Install '${server.command[0]}' and ensure it's in your PATH`;
|
|
14432
|
+
return {
|
|
14433
|
+
status: "not_installed",
|
|
14434
|
+
server: {
|
|
14435
|
+
id: server.id,
|
|
14436
|
+
command: server.command,
|
|
14437
|
+
extensions: server.extensions
|
|
14438
|
+
},
|
|
14439
|
+
installHint
|
|
14440
|
+
};
|
|
14441
|
+
}
|
|
14442
|
+
}
|
|
14443
|
+
const availableServers = [...new Set(servers.map((s) => s.id))];
|
|
14444
|
+
return {
|
|
14445
|
+
status: "not_configured",
|
|
14446
|
+
extension: ext,
|
|
14447
|
+
availableServers
|
|
14448
|
+
};
|
|
13849
14449
|
}
|
|
13850
14450
|
function getLanguageId(ext) {
|
|
13851
14451
|
return EXT_TO_LANG[ext] || "plaintext";
|
|
@@ -13868,10 +14468,10 @@ function isServerInstalled(command) {
|
|
|
13868
14468
|
const additionalPaths = [
|
|
13869
14469
|
join44(cwd, "node_modules", ".bin", cmd),
|
|
13870
14470
|
join44(cwd, "node_modules", ".bin", cmd + ext),
|
|
13871
|
-
join44(
|
|
13872
|
-
join44(
|
|
13873
|
-
join44(
|
|
13874
|
-
join44(
|
|
14471
|
+
join44(homedir10(), ".config", "opencode", "bin", cmd),
|
|
14472
|
+
join44(homedir10(), ".config", "opencode", "bin", cmd + ext),
|
|
14473
|
+
join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd),
|
|
14474
|
+
join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
|
|
13875
14475
|
];
|
|
13876
14476
|
for (const p of additionalPaths) {
|
|
13877
14477
|
if (existsSync34(p)) {
|
|
@@ -14486,13 +15086,49 @@ function findWorkspaceRoot(filePath) {
|
|
|
14486
15086
|
}
|
|
14487
15087
|
return __require("path").dirname(resolve6(filePath));
|
|
14488
15088
|
}
|
|
15089
|
+
function formatServerLookupError(result) {
|
|
15090
|
+
if (result.status === "not_installed") {
|
|
15091
|
+
const { server, installHint } = result;
|
|
15092
|
+
return [
|
|
15093
|
+
`LSP server '${server.id}' is configured but NOT INSTALLED.`,
|
|
15094
|
+
``,
|
|
15095
|
+
`Command not found: ${server.command[0]}`,
|
|
15096
|
+
``,
|
|
15097
|
+
`To install:`,
|
|
15098
|
+
` ${installHint}`,
|
|
15099
|
+
``,
|
|
15100
|
+
`Supported extensions: ${server.extensions.join(", ")}`,
|
|
15101
|
+
``,
|
|
15102
|
+
`After installation, the server will be available automatically.`,
|
|
15103
|
+
`Run 'lsp_servers' tool to verify installation status.`
|
|
15104
|
+
].join(`
|
|
15105
|
+
`);
|
|
15106
|
+
}
|
|
15107
|
+
return [
|
|
15108
|
+
`No LSP server configured for extension: ${result.extension}`,
|
|
15109
|
+
``,
|
|
15110
|
+
`Available servers: ${result.availableServers.slice(0, 10).join(", ")}${result.availableServers.length > 10 ? "..." : ""}`,
|
|
15111
|
+
``,
|
|
15112
|
+
`To add a custom server, configure 'lsp' in oh-my-opencode.json:`,
|
|
15113
|
+
` {`,
|
|
15114
|
+
` "lsp": {`,
|
|
15115
|
+
` "my-server": {`,
|
|
15116
|
+
` "command": ["my-lsp", "--stdio"],`,
|
|
15117
|
+
` "extensions": ["${result.extension}"]`,
|
|
15118
|
+
` }`,
|
|
15119
|
+
` }`,
|
|
15120
|
+
` }`
|
|
15121
|
+
].join(`
|
|
15122
|
+
`);
|
|
15123
|
+
}
|
|
14489
15124
|
async function withLspClient(filePath, fn) {
|
|
14490
15125
|
const absPath = resolve6(filePath);
|
|
14491
15126
|
const ext = extname2(absPath);
|
|
14492
|
-
const
|
|
14493
|
-
if (
|
|
14494
|
-
throw new Error(
|
|
15127
|
+
const result = findServerForExtension(ext);
|
|
15128
|
+
if (result.status !== "found") {
|
|
15129
|
+
throw new Error(formatServerLookupError(result));
|
|
14495
15130
|
}
|
|
15131
|
+
const server = result.server;
|
|
14496
15132
|
const root = findWorkspaceRoot(absPath);
|
|
14497
15133
|
const client = await lspManager.getClient(root, server);
|
|
14498
15134
|
try {
|
|
@@ -15483,10 +16119,10 @@ function mergeDefs(...defs) {
|
|
|
15483
16119
|
function cloneDef(schema) {
|
|
15484
16120
|
return mergeDefs(schema._zod.def);
|
|
15485
16121
|
}
|
|
15486
|
-
function getElementAtPath(obj,
|
|
15487
|
-
if (!
|
|
16122
|
+
function getElementAtPath(obj, path7) {
|
|
16123
|
+
if (!path7)
|
|
15488
16124
|
return obj;
|
|
15489
|
-
return
|
|
16125
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
15490
16126
|
}
|
|
15491
16127
|
function promiseAllObject(promisesObj) {
|
|
15492
16128
|
const keys = Object.keys(promisesObj);
|
|
@@ -15845,11 +16481,11 @@ function aborted(x, startIndex = 0) {
|
|
|
15845
16481
|
}
|
|
15846
16482
|
return false;
|
|
15847
16483
|
}
|
|
15848
|
-
function prefixIssues(
|
|
16484
|
+
function prefixIssues(path7, issues) {
|
|
15849
16485
|
return issues.map((iss) => {
|
|
15850
16486
|
var _a;
|
|
15851
16487
|
(_a = iss).path ?? (_a.path = []);
|
|
15852
|
-
iss.path.unshift(
|
|
16488
|
+
iss.path.unshift(path7);
|
|
15853
16489
|
return iss;
|
|
15854
16490
|
});
|
|
15855
16491
|
}
|
|
@@ -16017,7 +16653,7 @@ function treeifyError(error, _mapper) {
|
|
|
16017
16653
|
return issue2.message;
|
|
16018
16654
|
};
|
|
16019
16655
|
const result = { errors: [] };
|
|
16020
|
-
const processError = (error2,
|
|
16656
|
+
const processError = (error2, path7 = []) => {
|
|
16021
16657
|
var _a, _b;
|
|
16022
16658
|
for (const issue2 of error2.issues) {
|
|
16023
16659
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -16027,7 +16663,7 @@ function treeifyError(error, _mapper) {
|
|
|
16027
16663
|
} else if (issue2.code === "invalid_element") {
|
|
16028
16664
|
processError({ issues: issue2.issues }, issue2.path);
|
|
16029
16665
|
} else {
|
|
16030
|
-
const fullpath = [...
|
|
16666
|
+
const fullpath = [...path7, ...issue2.path];
|
|
16031
16667
|
if (fullpath.length === 0) {
|
|
16032
16668
|
result.errors.push(mapper(issue2));
|
|
16033
16669
|
continue;
|
|
@@ -16059,8 +16695,8 @@ function treeifyError(error, _mapper) {
|
|
|
16059
16695
|
}
|
|
16060
16696
|
function toDotPath(_path) {
|
|
16061
16697
|
const segs = [];
|
|
16062
|
-
const
|
|
16063
|
-
for (const seg of
|
|
16698
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16699
|
+
for (const seg of path7) {
|
|
16064
16700
|
if (typeof seg === "number")
|
|
16065
16701
|
segs.push(`[${seg}]`);
|
|
16066
16702
|
else if (typeof seg === "symbol")
|
|
@@ -24838,10 +25474,10 @@ function _property(property, schema, params) {
|
|
|
24838
25474
|
...normalizeParams(params)
|
|
24839
25475
|
});
|
|
24840
25476
|
}
|
|
24841
|
-
function _mime(
|
|
25477
|
+
function _mime(types11, params) {
|
|
24842
25478
|
return new $ZodCheckMimeType({
|
|
24843
25479
|
check: "mime_type",
|
|
24844
|
-
mime:
|
|
25480
|
+
mime: types11,
|
|
24845
25481
|
...normalizeParams(params)
|
|
24846
25482
|
});
|
|
24847
25483
|
}
|
|
@@ -26751,7 +27387,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
26751
27387
|
ZodType.init(inst, def);
|
|
26752
27388
|
inst.min = (size, params) => inst.check(_minSize(size, params));
|
|
26753
27389
|
inst.max = (size, params) => inst.check(_maxSize(size, params));
|
|
26754
|
-
inst.mime = (
|
|
27390
|
+
inst.mime = (types11, params) => inst.check(_mime(Array.isArray(types11) ? types11 : [types11], params));
|
|
26755
27391
|
});
|
|
26756
27392
|
function file(params) {
|
|
26757
27393
|
return _file(ZodFile, params);
|
|
@@ -27410,7 +28046,7 @@ import { existsSync as existsSync37, statSync as statSync4 } from "fs";
|
|
|
27410
28046
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
27411
28047
|
import { existsSync as existsSync36, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
|
|
27412
28048
|
import { join as join45 } from "path";
|
|
27413
|
-
import { homedir as
|
|
28049
|
+
import { homedir as homedir11 } from "os";
|
|
27414
28050
|
import { createRequire as createRequire3 } from "module";
|
|
27415
28051
|
var REPO2 = "ast-grep/ast-grep";
|
|
27416
28052
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -27435,11 +28071,11 @@ var PLATFORM_MAP2 = {
|
|
|
27435
28071
|
function getCacheDir3() {
|
|
27436
28072
|
if (process.platform === "win32") {
|
|
27437
28073
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
27438
|
-
const base2 = localAppData || join45(
|
|
28074
|
+
const base2 = localAppData || join45(homedir11(), "AppData", "Local");
|
|
27439
28075
|
return join45(base2, "oh-my-opencode", "bin");
|
|
27440
28076
|
}
|
|
27441
|
-
const
|
|
27442
|
-
const base =
|
|
28077
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
28078
|
+
const base = xdgCache || join45(homedir11(), ".cache");
|
|
27443
28079
|
return join45(base, "oh-my-opencode", "bin");
|
|
27444
28080
|
}
|
|
27445
28081
|
function getBinaryName3() {
|
|
@@ -27477,8 +28113,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
27477
28113
|
if (existsSync36(binaryPath)) {
|
|
27478
28114
|
return binaryPath;
|
|
27479
28115
|
}
|
|
27480
|
-
const { arch, os:
|
|
27481
|
-
const assetName = `app-${arch}-${
|
|
28116
|
+
const { arch, os: os5 } = platformInfo;
|
|
28117
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
27482
28118
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
27483
28119
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
27484
28120
|
try {
|
|
@@ -27567,9 +28203,9 @@ function findSgCliPathSync() {
|
|
|
27567
28203
|
}
|
|
27568
28204
|
if (process.platform === "darwin") {
|
|
27569
28205
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
27570
|
-
for (const
|
|
27571
|
-
if (existsSync37(
|
|
27572
|
-
return
|
|
28206
|
+
for (const path7 of homebrewPaths) {
|
|
28207
|
+
if (existsSync37(path7) && isValidBinary(path7)) {
|
|
28208
|
+
return path7;
|
|
27573
28209
|
}
|
|
27574
28210
|
}
|
|
27575
28211
|
}
|
|
@@ -27587,10 +28223,9 @@ function getSgCliPath() {
|
|
|
27587
28223
|
}
|
|
27588
28224
|
return "sg";
|
|
27589
28225
|
}
|
|
27590
|
-
function setSgCliPath(
|
|
27591
|
-
resolvedCliPath2 =
|
|
28226
|
+
function setSgCliPath(path7) {
|
|
28227
|
+
resolvedCliPath2 = path7;
|
|
27592
28228
|
}
|
|
27593
|
-
var SG_CLI_PATH = getSgCliPath();
|
|
27594
28229
|
var CLI_LANGUAGES = [
|
|
27595
28230
|
"bash",
|
|
27596
28231
|
"c",
|
|
@@ -28390,7 +29025,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28390
29025
|
return [];
|
|
28391
29026
|
}
|
|
28392
29027
|
const entries = readdirSync15(commandsDir, { withFileTypes: true });
|
|
28393
|
-
const
|
|
29028
|
+
const commands2 = [];
|
|
28394
29029
|
for (const entry of entries) {
|
|
28395
29030
|
if (!isMarkdownFile(entry))
|
|
28396
29031
|
continue;
|
|
@@ -28408,7 +29043,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28408
29043
|
agent: data.agent,
|
|
28409
29044
|
subtask: Boolean(data.subtask)
|
|
28410
29045
|
};
|
|
28411
|
-
|
|
29046
|
+
commands2.push({
|
|
28412
29047
|
name: commandName,
|
|
28413
29048
|
path: commandPath,
|
|
28414
29049
|
metadata,
|
|
@@ -28419,13 +29054,13 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28419
29054
|
continue;
|
|
28420
29055
|
}
|
|
28421
29056
|
}
|
|
28422
|
-
return
|
|
29057
|
+
return commands2;
|
|
28423
29058
|
}
|
|
28424
29059
|
function discoverCommandsSync() {
|
|
28425
|
-
const { homedir:
|
|
29060
|
+
const { homedir: homedir12 } = __require("os");
|
|
28426
29061
|
const userCommandsDir = join49(getClaudeConfigDir(), "commands");
|
|
28427
29062
|
const projectCommandsDir = join49(process.cwd(), ".claude", "commands");
|
|
28428
|
-
const opencodeGlobalDir = join49(
|
|
29063
|
+
const opencodeGlobalDir = join49(homedir12(), ".config", "opencode", "command");
|
|
28429
29064
|
const opencodeProjectDir = join49(process.cwd(), ".opencode", "command");
|
|
28430
29065
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
28431
29066
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
@@ -28476,18 +29111,18 @@ async function formatLoadedCommand(cmd) {
|
|
|
28476
29111
|
return sections.join(`
|
|
28477
29112
|
`);
|
|
28478
29113
|
}
|
|
28479
|
-
function formatCommandList(
|
|
28480
|
-
if (
|
|
29114
|
+
function formatCommandList(commands2) {
|
|
29115
|
+
if (commands2.length === 0) {
|
|
28481
29116
|
return "No commands found.";
|
|
28482
29117
|
}
|
|
28483
29118
|
const lines = [`# Available Commands
|
|
28484
29119
|
`];
|
|
28485
|
-
for (const cmd of
|
|
29120
|
+
for (const cmd of commands2) {
|
|
28486
29121
|
const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
|
|
28487
29122
|
lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
|
|
28488
29123
|
}
|
|
28489
29124
|
lines.push(`
|
|
28490
|
-
**Total**: ${
|
|
29125
|
+
**Total**: ${commands2.length} commands`);
|
|
28491
29126
|
return lines.join(`
|
|
28492
29127
|
`);
|
|
28493
29128
|
}
|
|
@@ -28524,27 +29159,27 @@ ${commandListForDescription}`,
|
|
|
28524
29159
|
command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
|
|
28525
29160
|
},
|
|
28526
29161
|
async execute(args) {
|
|
28527
|
-
const
|
|
29162
|
+
const commands2 = discoverCommandsSync();
|
|
28528
29163
|
if (!args.command) {
|
|
28529
|
-
return formatCommandList(
|
|
29164
|
+
return formatCommandList(commands2) + `
|
|
28530
29165
|
|
|
28531
29166
|
Provide a command name to execute.`;
|
|
28532
29167
|
}
|
|
28533
29168
|
const cmdName = args.command.replace(/^\//, "");
|
|
28534
|
-
const exactMatch =
|
|
29169
|
+
const exactMatch = commands2.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
|
|
28535
29170
|
if (exactMatch) {
|
|
28536
29171
|
return await formatLoadedCommand(exactMatch);
|
|
28537
29172
|
}
|
|
28538
|
-
const partialMatches =
|
|
29173
|
+
const partialMatches = commands2.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
|
|
28539
29174
|
if (partialMatches.length > 0) {
|
|
28540
29175
|
const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
|
|
28541
29176
|
return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
|
|
28542
29177
|
|
|
28543
|
-
` + formatCommandList(
|
|
29178
|
+
` + formatCommandList(commands2);
|
|
28544
29179
|
}
|
|
28545
29180
|
return `Command "/${cmdName}" not found.
|
|
28546
29181
|
|
|
28547
|
-
` + formatCommandList(
|
|
29182
|
+
` + formatCommandList(commands2) + `
|
|
28548
29183
|
|
|
28549
29184
|
Try a different command name.`;
|
|
28550
29185
|
}
|
|
@@ -28552,7 +29187,7 @@ Try a different command name.`;
|
|
|
28552
29187
|
// src/tools/session-manager/constants.ts
|
|
28553
29188
|
import { join as join50 } from "path";
|
|
28554
29189
|
var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
|
|
28555
|
-
var
|
|
29190
|
+
var MESSAGE_STORAGE4 = join50(OPENCODE_STORAGE9, "message");
|
|
28556
29191
|
var PART_STORAGE4 = join50(OPENCODE_STORAGE9, "part");
|
|
28557
29192
|
var TODO_DIR2 = join50(getClaudeConfigDir(), "todos");
|
|
28558
29193
|
var TRANSCRIPT_DIR2 = join50(getClaudeConfigDir(), "transcripts");
|
|
@@ -28628,22 +29263,24 @@ Has Todos: Yes (12 items, 8 completed)
|
|
|
28628
29263
|
Has Transcript: Yes (234 entries)`;
|
|
28629
29264
|
|
|
28630
29265
|
// src/tools/session-manager/storage.ts
|
|
28631
|
-
import { existsSync as existsSync42, readdirSync as readdirSync16
|
|
29266
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16 } from "fs";
|
|
29267
|
+
import { readdir, readFile } from "fs/promises";
|
|
28632
29268
|
import { join as join51 } from "path";
|
|
28633
|
-
function getAllSessions() {
|
|
28634
|
-
if (!existsSync42(
|
|
29269
|
+
async function getAllSessions() {
|
|
29270
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28635
29271
|
return [];
|
|
28636
29272
|
const sessions = [];
|
|
28637
|
-
function scanDirectory(dir) {
|
|
29273
|
+
async function scanDirectory(dir) {
|
|
28638
29274
|
try {
|
|
28639
|
-
|
|
29275
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
29276
|
+
for (const entry of entries) {
|
|
28640
29277
|
if (entry.isDirectory()) {
|
|
28641
29278
|
const sessionPath = join51(dir, entry.name);
|
|
28642
|
-
const files =
|
|
29279
|
+
const files = await readdir(sessionPath);
|
|
28643
29280
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
28644
29281
|
sessions.push(entry.name);
|
|
28645
29282
|
} else {
|
|
28646
|
-
scanDirectory(sessionPath);
|
|
29283
|
+
await scanDirectory(sessionPath);
|
|
28647
29284
|
}
|
|
28648
29285
|
}
|
|
28649
29286
|
}
|
|
@@ -28651,49 +29288,58 @@ function getAllSessions() {
|
|
|
28651
29288
|
return;
|
|
28652
29289
|
}
|
|
28653
29290
|
}
|
|
28654
|
-
scanDirectory(
|
|
29291
|
+
await scanDirectory(MESSAGE_STORAGE4);
|
|
28655
29292
|
return [...new Set(sessions)];
|
|
28656
29293
|
}
|
|
28657
29294
|
function getMessageDir9(sessionID) {
|
|
28658
|
-
if (!existsSync42(
|
|
29295
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28659
29296
|
return "";
|
|
28660
|
-
const directPath = join51(
|
|
29297
|
+
const directPath = join51(MESSAGE_STORAGE4, sessionID);
|
|
28661
29298
|
if (existsSync42(directPath)) {
|
|
28662
29299
|
return directPath;
|
|
28663
29300
|
}
|
|
28664
|
-
|
|
28665
|
-
const
|
|
28666
|
-
|
|
28667
|
-
|
|
29301
|
+
try {
|
|
29302
|
+
for (const dir of readdirSync16(MESSAGE_STORAGE4)) {
|
|
29303
|
+
const sessionPath = join51(MESSAGE_STORAGE4, dir, sessionID);
|
|
29304
|
+
if (existsSync42(sessionPath)) {
|
|
29305
|
+
return sessionPath;
|
|
29306
|
+
}
|
|
28668
29307
|
}
|
|
29308
|
+
} catch {
|
|
29309
|
+
return "";
|
|
28669
29310
|
}
|
|
28670
29311
|
return "";
|
|
28671
29312
|
}
|
|
28672
29313
|
function sessionExists(sessionID) {
|
|
28673
29314
|
return getMessageDir9(sessionID) !== "";
|
|
28674
29315
|
}
|
|
28675
|
-
function readSessionMessages(sessionID) {
|
|
29316
|
+
async function readSessionMessages(sessionID) {
|
|
28676
29317
|
const messageDir = getMessageDir9(sessionID);
|
|
28677
29318
|
if (!messageDir || !existsSync42(messageDir))
|
|
28678
29319
|
return [];
|
|
28679
29320
|
const messages = [];
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
28695
|
-
|
|
29321
|
+
try {
|
|
29322
|
+
const files = await readdir(messageDir);
|
|
29323
|
+
for (const file2 of files) {
|
|
29324
|
+
if (!file2.endsWith(".json"))
|
|
29325
|
+
continue;
|
|
29326
|
+
try {
|
|
29327
|
+
const content = await readFile(join51(messageDir, file2), "utf-8");
|
|
29328
|
+
const meta = JSON.parse(content);
|
|
29329
|
+
const parts = await readParts2(meta.id);
|
|
29330
|
+
messages.push({
|
|
29331
|
+
id: meta.id,
|
|
29332
|
+
role: meta.role,
|
|
29333
|
+
agent: meta.agent,
|
|
29334
|
+
time: meta.time,
|
|
29335
|
+
parts
|
|
29336
|
+
});
|
|
29337
|
+
} catch {
|
|
29338
|
+
continue;
|
|
29339
|
+
}
|
|
28696
29340
|
}
|
|
29341
|
+
} catch {
|
|
29342
|
+
return [];
|
|
28697
29343
|
}
|
|
28698
29344
|
return messages.sort((a, b) => {
|
|
28699
29345
|
const aTime = a.time?.created ?? 0;
|
|
@@ -28703,61 +29349,71 @@ function readSessionMessages(sessionID) {
|
|
|
28703
29349
|
return a.id.localeCompare(b.id);
|
|
28704
29350
|
});
|
|
28705
29351
|
}
|
|
28706
|
-
function readParts2(messageID) {
|
|
29352
|
+
async function readParts2(messageID) {
|
|
28707
29353
|
const partDir = join51(PART_STORAGE4, messageID);
|
|
28708
29354
|
if (!existsSync42(partDir))
|
|
28709
29355
|
return [];
|
|
28710
29356
|
const parts = [];
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
|
|
28718
|
-
|
|
29357
|
+
try {
|
|
29358
|
+
const files = await readdir(partDir);
|
|
29359
|
+
for (const file2 of files) {
|
|
29360
|
+
if (!file2.endsWith(".json"))
|
|
29361
|
+
continue;
|
|
29362
|
+
try {
|
|
29363
|
+
const content = await readFile(join51(partDir, file2), "utf-8");
|
|
29364
|
+
parts.push(JSON.parse(content));
|
|
29365
|
+
} catch {
|
|
29366
|
+
continue;
|
|
29367
|
+
}
|
|
28719
29368
|
}
|
|
29369
|
+
} catch {
|
|
29370
|
+
return [];
|
|
28720
29371
|
}
|
|
28721
29372
|
return parts.sort((a, b) => a.id.localeCompare(b.id));
|
|
28722
29373
|
}
|
|
28723
|
-
function readSessionTodos(sessionID) {
|
|
29374
|
+
async function readSessionTodos(sessionID) {
|
|
28724
29375
|
if (!existsSync42(TODO_DIR2))
|
|
28725
29376
|
return [];
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
28730
|
-
|
|
28731
|
-
|
|
28732
|
-
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
|
|
28736
|
-
|
|
28737
|
-
|
|
29377
|
+
try {
|
|
29378
|
+
const allFiles = await readdir(TODO_DIR2);
|
|
29379
|
+
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
29380
|
+
for (const file2 of todoFiles) {
|
|
29381
|
+
try {
|
|
29382
|
+
const content = await readFile(join51(TODO_DIR2, file2), "utf-8");
|
|
29383
|
+
const data = JSON.parse(content);
|
|
29384
|
+
if (Array.isArray(data)) {
|
|
29385
|
+
return data.map((item) => ({
|
|
29386
|
+
id: item.id || "",
|
|
29387
|
+
content: item.content || "",
|
|
29388
|
+
status: item.status || "pending",
|
|
29389
|
+
priority: item.priority
|
|
29390
|
+
}));
|
|
29391
|
+
}
|
|
29392
|
+
} catch {
|
|
29393
|
+
continue;
|
|
28738
29394
|
}
|
|
28739
|
-
} catch {
|
|
28740
|
-
continue;
|
|
28741
29395
|
}
|
|
29396
|
+
} catch {
|
|
29397
|
+
return [];
|
|
28742
29398
|
}
|
|
28743
29399
|
return [];
|
|
28744
29400
|
}
|
|
28745
|
-
function readSessionTranscript(sessionID) {
|
|
29401
|
+
async function readSessionTranscript(sessionID) {
|
|
28746
29402
|
if (!existsSync42(TRANSCRIPT_DIR2))
|
|
28747
29403
|
return 0;
|
|
28748
29404
|
const transcriptFile = join51(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
28749
29405
|
if (!existsSync42(transcriptFile))
|
|
28750
29406
|
return 0;
|
|
28751
29407
|
try {
|
|
28752
|
-
const content =
|
|
29408
|
+
const content = await readFile(transcriptFile, "utf-8");
|
|
28753
29409
|
return content.trim().split(`
|
|
28754
29410
|
`).filter(Boolean).length;
|
|
28755
29411
|
} catch {
|
|
28756
29412
|
return 0;
|
|
28757
29413
|
}
|
|
28758
29414
|
}
|
|
28759
|
-
function getSessionInfo(sessionID) {
|
|
28760
|
-
const messages = readSessionMessages(sessionID);
|
|
29415
|
+
async function getSessionInfo(sessionID) {
|
|
29416
|
+
const messages = await readSessionMessages(sessionID);
|
|
28761
29417
|
if (messages.length === 0)
|
|
28762
29418
|
return null;
|
|
28763
29419
|
const agentsUsed = new Set;
|
|
@@ -28774,8 +29430,8 @@ function getSessionInfo(sessionID) {
|
|
|
28774
29430
|
lastMessage = date5;
|
|
28775
29431
|
}
|
|
28776
29432
|
}
|
|
28777
|
-
const todos = readSessionTodos(sessionID);
|
|
28778
|
-
const transcriptEntries = readSessionTranscript(sessionID);
|
|
29433
|
+
const todos = await readSessionTodos(sessionID);
|
|
29434
|
+
const transcriptEntries = await readSessionTranscript(sessionID);
|
|
28779
29435
|
return {
|
|
28780
29436
|
id: sessionID,
|
|
28781
29437
|
message_count: messages.length,
|
|
@@ -28790,11 +29446,11 @@ function getSessionInfo(sessionID) {
|
|
|
28790
29446
|
}
|
|
28791
29447
|
|
|
28792
29448
|
// src/tools/session-manager/utils.ts
|
|
28793
|
-
function formatSessionList(sessionIDs) {
|
|
29449
|
+
async function formatSessionList(sessionIDs) {
|
|
28794
29450
|
if (sessionIDs.length === 0) {
|
|
28795
29451
|
return "No sessions found.";
|
|
28796
29452
|
}
|
|
28797
|
-
const infos = sessionIDs.map((id) => getSessionInfo(id)).filter((info) => info !== null);
|
|
29453
|
+
const infos = (await Promise.all(sessionIDs.map((id) => getSessionInfo(id)))).filter((info) => info !== null);
|
|
28798
29454
|
if (infos.length === 0) {
|
|
28799
29455
|
return "No valid sessions found.";
|
|
28800
29456
|
}
|
|
@@ -28886,29 +29542,33 @@ function formatSearchResults(results) {
|
|
|
28886
29542
|
return lines.join(`
|
|
28887
29543
|
`);
|
|
28888
29544
|
}
|
|
28889
|
-
function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
29545
|
+
async function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
28890
29546
|
if (!fromDate && !toDate)
|
|
28891
29547
|
return sessionIDs;
|
|
28892
29548
|
const from = fromDate ? new Date(fromDate) : null;
|
|
28893
29549
|
const to = toDate ? new Date(toDate) : null;
|
|
28894
|
-
|
|
28895
|
-
|
|
29550
|
+
const results = [];
|
|
29551
|
+
for (const id of sessionIDs) {
|
|
29552
|
+
const info = await getSessionInfo(id);
|
|
28896
29553
|
if (!info || !info.last_message)
|
|
28897
|
-
|
|
29554
|
+
continue;
|
|
28898
29555
|
if (from && info.last_message < from)
|
|
28899
|
-
|
|
29556
|
+
continue;
|
|
28900
29557
|
if (to && info.last_message > to)
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
}
|
|
29558
|
+
continue;
|
|
29559
|
+
results.push(id);
|
|
29560
|
+
}
|
|
29561
|
+
return results;
|
|
28904
29562
|
}
|
|
28905
|
-
function searchInSession(sessionID, query, caseSensitive = false) {
|
|
28906
|
-
const messages = readSessionMessages(sessionID);
|
|
29563
|
+
async function searchInSession(sessionID, query, caseSensitive = false, maxResults) {
|
|
29564
|
+
const messages = await readSessionMessages(sessionID);
|
|
28907
29565
|
const results = [];
|
|
28908
29566
|
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
28909
29567
|
for (const msg of messages) {
|
|
29568
|
+
if (maxResults && results.length >= maxResults)
|
|
29569
|
+
break;
|
|
28910
29570
|
let matchCount = 0;
|
|
28911
|
-
|
|
29571
|
+
const excerpts = [];
|
|
28912
29572
|
for (const part of msg.parts) {
|
|
28913
29573
|
if (part.type === "text" && part.text) {
|
|
28914
29574
|
const text = caseSensitive ? part.text : part.text.toLowerCase();
|
|
@@ -28944,6 +29604,14 @@ function searchInSession(sessionID, query, caseSensitive = false) {
|
|
|
28944
29604
|
}
|
|
28945
29605
|
|
|
28946
29606
|
// src/tools/session-manager/tools.ts
|
|
29607
|
+
var SEARCH_TIMEOUT_MS = 60000;
|
|
29608
|
+
var MAX_SESSIONS_TO_SCAN = 50;
|
|
29609
|
+
function withTimeout(promise2, ms, operation) {
|
|
29610
|
+
return Promise.race([
|
|
29611
|
+
promise2,
|
|
29612
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms))
|
|
29613
|
+
]);
|
|
29614
|
+
}
|
|
28947
29615
|
var session_list = tool({
|
|
28948
29616
|
description: SESSION_LIST_DESCRIPTION,
|
|
28949
29617
|
args: {
|
|
@@ -28953,14 +29621,14 @@ var session_list = tool({
|
|
|
28953
29621
|
},
|
|
28954
29622
|
execute: async (args, _context) => {
|
|
28955
29623
|
try {
|
|
28956
|
-
let sessions = getAllSessions();
|
|
29624
|
+
let sessions = await getAllSessions();
|
|
28957
29625
|
if (args.from_date || args.to_date) {
|
|
28958
|
-
sessions = filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
29626
|
+
sessions = await filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
28959
29627
|
}
|
|
28960
29628
|
if (args.limit && args.limit > 0) {
|
|
28961
29629
|
sessions = sessions.slice(0, args.limit);
|
|
28962
29630
|
}
|
|
28963
|
-
return formatSessionList(sessions);
|
|
29631
|
+
return await formatSessionList(sessions);
|
|
28964
29632
|
} catch (e) {
|
|
28965
29633
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
28966
29634
|
}
|
|
@@ -28979,11 +29647,11 @@ var session_read = tool({
|
|
|
28979
29647
|
if (!sessionExists(args.session_id)) {
|
|
28980
29648
|
return `Session not found: ${args.session_id}`;
|
|
28981
29649
|
}
|
|
28982
|
-
let messages = readSessionMessages(args.session_id);
|
|
29650
|
+
let messages = await readSessionMessages(args.session_id);
|
|
28983
29651
|
if (args.limit && args.limit > 0) {
|
|
28984
29652
|
messages = messages.slice(0, args.limit);
|
|
28985
29653
|
}
|
|
28986
|
-
const todos = args.include_todos ? readSessionTodos(args.session_id) : undefined;
|
|
29654
|
+
const todos = args.include_todos ? await readSessionTodos(args.session_id) : undefined;
|
|
28987
29655
|
return formatSessionMessages(messages, args.include_todos, todos);
|
|
28988
29656
|
} catch (e) {
|
|
28989
29657
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -29000,10 +29668,25 @@ var session_search = tool({
|
|
|
29000
29668
|
},
|
|
29001
29669
|
execute: async (args, _context) => {
|
|
29002
29670
|
try {
|
|
29003
|
-
const
|
|
29004
|
-
const
|
|
29005
|
-
|
|
29006
|
-
|
|
29671
|
+
const resultLimit = args.limit && args.limit > 0 ? args.limit : 20;
|
|
29672
|
+
const searchOperation = async () => {
|
|
29673
|
+
if (args.session_id) {
|
|
29674
|
+
return searchInSession(args.session_id, args.query, args.case_sensitive, resultLimit);
|
|
29675
|
+
}
|
|
29676
|
+
const allSessions = await getAllSessions();
|
|
29677
|
+
const sessionsToScan = allSessions.slice(0, MAX_SESSIONS_TO_SCAN);
|
|
29678
|
+
const allResults = [];
|
|
29679
|
+
for (const sid of sessionsToScan) {
|
|
29680
|
+
if (allResults.length >= resultLimit)
|
|
29681
|
+
break;
|
|
29682
|
+
const remaining = resultLimit - allResults.length;
|
|
29683
|
+
const sessionResults = await searchInSession(sid, args.query, args.case_sensitive, remaining);
|
|
29684
|
+
allResults.push(...sessionResults);
|
|
29685
|
+
}
|
|
29686
|
+
return allResults.slice(0, resultLimit);
|
|
29687
|
+
};
|
|
29688
|
+
const results = await withTimeout(searchOperation(), SEARCH_TIMEOUT_MS, "Search");
|
|
29689
|
+
return formatSearchResults(results);
|
|
29007
29690
|
} catch (e) {
|
|
29008
29691
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
29009
29692
|
}
|
|
@@ -29016,7 +29699,7 @@ var session_info = tool({
|
|
|
29016
29699
|
},
|
|
29017
29700
|
execute: async (args, _context) => {
|
|
29018
29701
|
try {
|
|
29019
|
-
const info = getSessionInfo(args.session_id);
|
|
29702
|
+
const info = await getSessionInfo(args.session_id);
|
|
29020
29703
|
if (!info) {
|
|
29021
29704
|
return `Session not found: ${args.session_id}`;
|
|
29022
29705
|
}
|
|
@@ -29059,12 +29742,12 @@ async function findTmuxPath() {
|
|
|
29059
29742
|
return null;
|
|
29060
29743
|
}
|
|
29061
29744
|
const stdout = await new Response(proc.stdout).text();
|
|
29062
|
-
const
|
|
29745
|
+
const path7 = stdout.trim().split(`
|
|
29063
29746
|
`)[0];
|
|
29064
|
-
if (!
|
|
29747
|
+
if (!path7) {
|
|
29065
29748
|
return null;
|
|
29066
29749
|
}
|
|
29067
|
-
const verifyProc = spawn10([
|
|
29750
|
+
const verifyProc = spawn10([path7, "-V"], {
|
|
29068
29751
|
stdout: "pipe",
|
|
29069
29752
|
stderr: "pipe"
|
|
29070
29753
|
});
|
|
@@ -29072,7 +29755,7 @@ async function findTmuxPath() {
|
|
|
29072
29755
|
if (verifyExitCode !== 0) {
|
|
29073
29756
|
return null;
|
|
29074
29757
|
}
|
|
29075
|
-
return
|
|
29758
|
+
return path7;
|
|
29076
29759
|
} catch {
|
|
29077
29760
|
return null;
|
|
29078
29761
|
}
|
|
@@ -29085,9 +29768,9 @@ async function getTmuxPath() {
|
|
|
29085
29768
|
return initPromise3;
|
|
29086
29769
|
}
|
|
29087
29770
|
initPromise3 = (async () => {
|
|
29088
|
-
const
|
|
29089
|
-
tmuxPath =
|
|
29090
|
-
return
|
|
29771
|
+
const path7 = await findTmuxPath();
|
|
29772
|
+
tmuxPath = path7;
|
|
29773
|
+
return path7;
|
|
29091
29774
|
})();
|
|
29092
29775
|
return initPromise3;
|
|
29093
29776
|
}
|
|
@@ -29224,21 +29907,26 @@ function createBackgroundTask(manager) {
|
|
|
29224
29907
|
agent: tool.schema.string().describe("Agent type to use (any registered agent)")
|
|
29225
29908
|
},
|
|
29226
29909
|
async execute(args, toolContext) {
|
|
29910
|
+
const ctx = toolContext;
|
|
29227
29911
|
if (!args.agent || args.agent.trim() === "") {
|
|
29228
29912
|
return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
|
|
29229
29913
|
}
|
|
29230
29914
|
try {
|
|
29231
|
-
const messageDir = getMessageDir10(
|
|
29915
|
+
const messageDir = getMessageDir10(ctx.sessionID);
|
|
29232
29916
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
29233
29917
|
const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
|
|
29234
29918
|
const task = await manager.launch({
|
|
29235
29919
|
description: args.description,
|
|
29236
29920
|
prompt: args.prompt,
|
|
29237
29921
|
agent: args.agent.trim(),
|
|
29238
|
-
parentSessionID:
|
|
29239
|
-
parentMessageID:
|
|
29922
|
+
parentSessionID: ctx.sessionID,
|
|
29923
|
+
parentMessageID: ctx.messageID,
|
|
29240
29924
|
parentModel
|
|
29241
29925
|
});
|
|
29926
|
+
ctx.metadata?.({
|
|
29927
|
+
title: args.description,
|
|
29928
|
+
metadata: { sessionId: task.sessionID }
|
|
29929
|
+
});
|
|
29242
29930
|
return `Background task launched successfully.
|
|
29243
29931
|
|
|
29244
29932
|
Task ID: ${task.id}
|
|
@@ -29494,6 +30182,7 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29494
30182
|
session_id: tool.schema.string().describe("Existing Task session to continue").optional()
|
|
29495
30183
|
},
|
|
29496
30184
|
async execute(args, toolContext) {
|
|
30185
|
+
const toolCtx = toolContext;
|
|
29497
30186
|
log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`);
|
|
29498
30187
|
if (!ALLOWED_AGENTS.includes(args.subagent_type)) {
|
|
29499
30188
|
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`;
|
|
@@ -29502,9 +30191,9 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29502
30191
|
if (args.session_id) {
|
|
29503
30192
|
return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`;
|
|
29504
30193
|
}
|
|
29505
|
-
return await executeBackground(args,
|
|
30194
|
+
return await executeBackground(args, toolCtx, backgroundManager);
|
|
29506
30195
|
}
|
|
29507
|
-
return await executeSync(args,
|
|
30196
|
+
return await executeSync(args, toolCtx, ctx);
|
|
29508
30197
|
}
|
|
29509
30198
|
});
|
|
29510
30199
|
}
|
|
@@ -29517,6 +30206,10 @@ async function executeBackground(args, toolContext, manager) {
|
|
|
29517
30206
|
parentSessionID: toolContext.sessionID,
|
|
29518
30207
|
parentMessageID: toolContext.messageID
|
|
29519
30208
|
});
|
|
30209
|
+
toolContext.metadata?.({
|
|
30210
|
+
title: args.description,
|
|
30211
|
+
metadata: { sessionId: task.sessionID }
|
|
30212
|
+
});
|
|
29520
30213
|
return `Background agent task launched successfully.
|
|
29521
30214
|
|
|
29522
30215
|
Task ID: ${task.id}
|
|
@@ -29561,6 +30254,10 @@ async function executeSync(args, toolContext, ctx) {
|
|
|
29561
30254
|
sessionID = createResult.data.id;
|
|
29562
30255
|
log(`[call_omo_agent] Created session: ${sessionID}`);
|
|
29563
30256
|
}
|
|
30257
|
+
toolContext.metadata?.({
|
|
30258
|
+
title: args.description,
|
|
30259
|
+
metadata: { sessionId: sessionID }
|
|
30260
|
+
});
|
|
29564
30261
|
log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
|
|
29565
30262
|
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
|
|
29566
30263
|
try {
|
|
@@ -30006,6 +30703,7 @@ class BackgroundManager {
|
|
|
30006
30703
|
}
|
|
30007
30704
|
const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration3}. Use background_output with task_id="${task.id}" to get results.`;
|
|
30008
30705
|
log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
|
|
30706
|
+
const taskId = task.id;
|
|
30009
30707
|
setTimeout(async () => {
|
|
30010
30708
|
try {
|
|
30011
30709
|
const messageDir = getMessageDir11(task.parentSessionID);
|
|
@@ -30021,10 +30719,13 @@ class BackgroundManager {
|
|
|
30021
30719
|
},
|
|
30022
30720
|
query: { directory: this.directory }
|
|
30023
30721
|
});
|
|
30024
|
-
this.clearNotificationsForTask(
|
|
30722
|
+
this.clearNotificationsForTask(taskId);
|
|
30025
30723
|
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
30026
30724
|
} catch (error45) {
|
|
30027
30725
|
log("[background-agent] prompt failed:", String(error45));
|
|
30726
|
+
} finally {
|
|
30727
|
+
this.tasks.delete(taskId);
|
|
30728
|
+
log("[background-agent] Removed completed task from memory:", taskId);
|
|
30028
30729
|
}
|
|
30029
30730
|
}, 200);
|
|
30030
30731
|
}
|
|
@@ -30212,6 +30913,9 @@ var HookNameSchema = exports_external.enum([
|
|
|
30212
30913
|
"empty-message-sanitizer",
|
|
30213
30914
|
"thinking-block-validator"
|
|
30214
30915
|
]);
|
|
30916
|
+
var BuiltinCommandNameSchema = exports_external.enum([
|
|
30917
|
+
"init-deep"
|
|
30918
|
+
]);
|
|
30215
30919
|
var AgentOverrideConfigSchema = exports_external.object({
|
|
30216
30920
|
model: exports_external.string().optional(),
|
|
30217
30921
|
temperature: exports_external.number().min(0).max(2).optional(),
|
|
@@ -30253,6 +30957,9 @@ var SisyphusAgentConfigSchema = exports_external.object({
|
|
|
30253
30957
|
planner_enabled: exports_external.boolean().optional(),
|
|
30254
30958
|
replace_plan: exports_external.boolean().optional()
|
|
30255
30959
|
});
|
|
30960
|
+
var CommentCheckerConfigSchema = exports_external.object({
|
|
30961
|
+
custom_prompt: exports_external.string().optional()
|
|
30962
|
+
});
|
|
30256
30963
|
var DynamicContextPruningConfigSchema = exports_external.object({
|
|
30257
30964
|
enabled: exports_external.boolean().default(false),
|
|
30258
30965
|
notification: exports_external.enum(["off", "minimal", "detailed"]).default("detailed"),
|
|
@@ -30291,17 +30998,19 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
30291
30998
|
preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
|
|
30292
30999
|
truncate_all_tool_outputs: exports_external.boolean().default(true),
|
|
30293
31000
|
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
|
|
30294
|
-
|
|
31001
|
+
dcp_for_compaction: exports_external.boolean().optional()
|
|
30295
31002
|
});
|
|
30296
31003
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
30297
31004
|
$schema: exports_external.string().optional(),
|
|
30298
31005
|
disabled_mcps: exports_external.array(McpNameSchema).optional(),
|
|
30299
31006
|
disabled_agents: exports_external.array(BuiltinAgentNameSchema).optional(),
|
|
30300
31007
|
disabled_hooks: exports_external.array(HookNameSchema).optional(),
|
|
31008
|
+
disabled_commands: exports_external.array(BuiltinCommandNameSchema).optional(),
|
|
30301
31009
|
agents: AgentOverridesSchema.optional(),
|
|
30302
31010
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
|
30303
31011
|
google_auth: exports_external.boolean().optional(),
|
|
30304
31012
|
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
31013
|
+
comment_checker: CommentCheckerConfigSchema.optional(),
|
|
30305
31014
|
experimental: ExperimentalConfigSchema.optional(),
|
|
30306
31015
|
auto_update: exports_external.boolean().optional()
|
|
30307
31016
|
});
|
|
@@ -30378,7 +31087,7 @@ var PLAN_PERMISSION = {
|
|
|
30378
31087
|
|
|
30379
31088
|
// src/index.ts
|
|
30380
31089
|
import * as fs7 from "fs";
|
|
30381
|
-
import * as
|
|
31090
|
+
import * as path7 from "path";
|
|
30382
31091
|
var AGENT_NAME_MAP = {
|
|
30383
31092
|
omo: "Sisyphus",
|
|
30384
31093
|
OmO: "Sisyphus",
|
|
@@ -30477,14 +31186,20 @@ function mergeConfigs(base, override) {
|
|
|
30477
31186
|
...override.disabled_hooks ?? []
|
|
30478
31187
|
])
|
|
30479
31188
|
],
|
|
31189
|
+
disabled_commands: [
|
|
31190
|
+
...new Set([
|
|
31191
|
+
...base.disabled_commands ?? [],
|
|
31192
|
+
...override.disabled_commands ?? []
|
|
31193
|
+
])
|
|
31194
|
+
],
|
|
30480
31195
|
claude_code: deepMerge(base.claude_code, override.claude_code)
|
|
30481
31196
|
};
|
|
30482
31197
|
}
|
|
30483
31198
|
function loadPluginConfig(directory, ctx) {
|
|
30484
|
-
const userBasePath =
|
|
31199
|
+
const userBasePath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode");
|
|
30485
31200
|
const userDetected = detectConfigFile(userBasePath);
|
|
30486
31201
|
const userConfigPath = userDetected.format !== "none" ? userDetected.path : userBasePath + ".json";
|
|
30487
|
-
const projectBasePath =
|
|
31202
|
+
const projectBasePath = path7.join(directory, ".opencode", "oh-my-opencode");
|
|
30488
31203
|
const projectDetected = detectConfigFile(projectBasePath);
|
|
30489
31204
|
const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : projectBasePath + ".json";
|
|
30490
31205
|
let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
|
|
@@ -30520,7 +31235,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30520
31235
|
const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
|
|
30521
31236
|
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30522
31237
|
const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
|
|
30523
|
-
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
|
|
31238
|
+
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks(pluginConfig.comment_checker) : null;
|
|
30524
31239
|
const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30525
31240
|
const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
|
|
30526
31241
|
const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
|
|
@@ -30700,12 +31415,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30700
31415
|
...mcpResult.servers,
|
|
30701
31416
|
...pluginComponents.mcpServers
|
|
30702
31417
|
};
|
|
31418
|
+
const builtinCommands = loadBuiltinCommands(pluginConfig.disabled_commands);
|
|
30703
31419
|
const userCommands = pluginConfig.claude_code?.commands ?? true ? loadUserCommands() : {};
|
|
30704
31420
|
const opencodeGlobalCommands = loadOpencodeGlobalCommands();
|
|
30705
31421
|
const systemCommands = config3.command ?? {};
|
|
30706
31422
|
const projectCommands = pluginConfig.claude_code?.commands ?? true ? loadProjectCommands() : {};
|
|
30707
31423
|
const opencodeProjectCommands = loadOpencodeProjectCommands();
|
|
30708
31424
|
config3.command = {
|
|
31425
|
+
...builtinCommands,
|
|
30709
31426
|
...userCommands,
|
|
30710
31427
|
...opencodeGlobalCommands,
|
|
30711
31428
|
...systemCommands,
|