oh-my-opencode 2.6.2 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +3 -3
- package/README.zh-cn.md +3 -3
- 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/index.d.ts +1 -1
- package/dist/index.js +1318 -609
- 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 MIN_INJECTION_INTERVAL_MS = 1e4;
|
|
4403
4386
|
function getMessageDir(sessionID) {
|
|
4404
4387
|
if (!existsSync6(MESSAGE_STORAGE))
|
|
4405
4388
|
return null;
|
|
@@ -4433,39 +4416,216 @@ 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 getOrCreateState(sessionID) {
|
|
4426
|
+
let state2 = sessions.get(sessionID);
|
|
4427
|
+
if (!state2) {
|
|
4428
|
+
state2 = { version: 0, mode: "idle" };
|
|
4429
|
+
sessions.set(sessionID, state2);
|
|
4430
|
+
}
|
|
4431
|
+
return state2;
|
|
4432
|
+
}
|
|
4433
|
+
function clearTimer(state2) {
|
|
4434
|
+
if (state2.timer) {
|
|
4435
|
+
clearTimeout(state2.timer);
|
|
4436
|
+
state2.timer = undefined;
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
function invalidate(sessionID, reason) {
|
|
4440
|
+
const state2 = sessions.get(sessionID);
|
|
4441
|
+
if (!state2)
|
|
4442
|
+
return;
|
|
4443
|
+
if (state2.mode === "recovering")
|
|
4444
|
+
return;
|
|
4445
|
+
state2.version++;
|
|
4446
|
+
clearTimer(state2);
|
|
4447
|
+
if (state2.mode !== "idle" && state2.mode !== "errorBypass") {
|
|
4448
|
+
log(`[${HOOK_NAME}] Invalidated`, { sessionID, reason, prevMode: state2.mode, newVersion: state2.version });
|
|
4449
|
+
state2.mode = "idle";
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
function isMainSession(sessionID) {
|
|
4453
|
+
const mainSessionID2 = getMainSessionID();
|
|
4454
|
+
return !mainSessionID2 || sessionID === mainSessionID2;
|
|
4455
|
+
}
|
|
4446
4456
|
const markRecovering = (sessionID) => {
|
|
4447
|
-
|
|
4457
|
+
const state2 = getOrCreateState(sessionID);
|
|
4458
|
+
invalidate(sessionID, "entering recovery mode");
|
|
4459
|
+
state2.mode = "recovering";
|
|
4460
|
+
log(`[${HOOK_NAME}] Session marked as recovering`, { sessionID });
|
|
4448
4461
|
};
|
|
4449
4462
|
const markRecoveryComplete = (sessionID) => {
|
|
4450
|
-
|
|
4463
|
+
const state2 = sessions.get(sessionID);
|
|
4464
|
+
if (state2 && state2.mode === "recovering") {
|
|
4465
|
+
state2.mode = "idle";
|
|
4466
|
+
log(`[${HOOK_NAME}] Session recovery complete`, { sessionID });
|
|
4467
|
+
}
|
|
4451
4468
|
};
|
|
4469
|
+
async function showCountdownToast(seconds, incompleteCount) {
|
|
4470
|
+
await ctx.client.tui.showToast({
|
|
4471
|
+
body: {
|
|
4472
|
+
title: "Todo Continuation",
|
|
4473
|
+
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
4474
|
+
variant: "warning",
|
|
4475
|
+
duration: TOAST_DURATION_MS
|
|
4476
|
+
}
|
|
4477
|
+
}).catch(() => {});
|
|
4478
|
+
}
|
|
4479
|
+
async function executeInjection(sessionID, capturedVersion) {
|
|
4480
|
+
const state2 = sessions.get(sessionID);
|
|
4481
|
+
if (!state2)
|
|
4482
|
+
return;
|
|
4483
|
+
if (state2.version !== capturedVersion) {
|
|
4484
|
+
log(`[${HOOK_NAME}] Injection aborted: version mismatch`, {
|
|
4485
|
+
sessionID,
|
|
4486
|
+
capturedVersion,
|
|
4487
|
+
currentVersion: state2.version
|
|
4488
|
+
});
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
if (state2.mode !== "countingDown") {
|
|
4492
|
+
log(`[${HOOK_NAME}] Injection aborted: mode changed`, {
|
|
4493
|
+
sessionID,
|
|
4494
|
+
mode: state2.mode
|
|
4495
|
+
});
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
if (state2.lastAttemptedAt) {
|
|
4499
|
+
const elapsed = Date.now() - state2.lastAttemptedAt;
|
|
4500
|
+
if (elapsed < MIN_INJECTION_INTERVAL_MS) {
|
|
4501
|
+
log(`[${HOOK_NAME}] Injection throttled: too soon since last injection`, {
|
|
4502
|
+
sessionID,
|
|
4503
|
+
elapsedMs: elapsed,
|
|
4504
|
+
minIntervalMs: MIN_INJECTION_INTERVAL_MS
|
|
4505
|
+
});
|
|
4506
|
+
state2.mode = "idle";
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
state2.mode = "injecting";
|
|
4511
|
+
let todos = [];
|
|
4512
|
+
try {
|
|
4513
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4514
|
+
todos = response.data ?? response;
|
|
4515
|
+
} catch (err) {
|
|
4516
|
+
log(`[${HOOK_NAME}] Failed to fetch todos for injection`, { sessionID, error: String(err) });
|
|
4517
|
+
state2.mode = "idle";
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
if (state2.version !== capturedVersion) {
|
|
4521
|
+
log(`[${HOOK_NAME}] Injection aborted after todo fetch: version mismatch`, { sessionID });
|
|
4522
|
+
state2.mode = "idle";
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
const incompleteCount = getIncompleteCount(todos);
|
|
4526
|
+
if (incompleteCount === 0) {
|
|
4527
|
+
log(`[${HOOK_NAME}] No incomplete todos at injection time`, { sessionID, total: todos.length });
|
|
4528
|
+
state2.mode = "idle";
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4531
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4532
|
+
if (hasRunningBgTasks) {
|
|
4533
|
+
log(`[${HOOK_NAME}] Skipped: background tasks still running`, { sessionID });
|
|
4534
|
+
state2.mode = "idle";
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
const messageDir = getMessageDir(sessionID);
|
|
4538
|
+
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
4539
|
+
const agentHasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
4540
|
+
if (!agentHasWritePermission) {
|
|
4541
|
+
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, {
|
|
4542
|
+
sessionID,
|
|
4543
|
+
agent: prevMessage?.agent,
|
|
4544
|
+
tools: prevMessage?.tools
|
|
4545
|
+
});
|
|
4546
|
+
state2.mode = "idle";
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
const agentName = prevMessage?.agent?.toLowerCase() ?? "";
|
|
4550
|
+
const isPlanModeAgent = agentName === "plan" || agentName === "planner-sisyphus";
|
|
4551
|
+
if (isPlanModeAgent) {
|
|
4552
|
+
log(`[${HOOK_NAME}] Skipped: plan mode agent detected`, {
|
|
4553
|
+
sessionID,
|
|
4554
|
+
agent: prevMessage?.agent
|
|
4555
|
+
});
|
|
4556
|
+
state2.mode = "idle";
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4559
|
+
const prompt = `${CONTINUATION_PROMPT}
|
|
4560
|
+
|
|
4561
|
+
[Status: ${todos.length - incompleteCount}/${todos.length} completed, ${incompleteCount} remaining]`;
|
|
4562
|
+
if (state2.version !== capturedVersion) {
|
|
4563
|
+
log(`[${HOOK_NAME}] Injection aborted: version changed before API call`, { sessionID });
|
|
4564
|
+
state2.mode = "idle";
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
state2.lastAttemptedAt = Date.now();
|
|
4568
|
+
try {
|
|
4569
|
+
log(`[${HOOK_NAME}] Injecting continuation prompt`, {
|
|
4570
|
+
sessionID,
|
|
4571
|
+
agent: prevMessage?.agent,
|
|
4572
|
+
incompleteCount
|
|
4573
|
+
});
|
|
4574
|
+
await ctx.client.session.prompt({
|
|
4575
|
+
path: { id: sessionID },
|
|
4576
|
+
body: {
|
|
4577
|
+
agent: prevMessage?.agent,
|
|
4578
|
+
parts: [{ type: "text", text: prompt }]
|
|
4579
|
+
},
|
|
4580
|
+
query: { directory: ctx.directory }
|
|
4581
|
+
});
|
|
4582
|
+
log(`[${HOOK_NAME}] Continuation prompt injected successfully`, { sessionID });
|
|
4583
|
+
} catch (err) {
|
|
4584
|
+
log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
|
|
4585
|
+
}
|
|
4586
|
+
state2.mode = "idle";
|
|
4587
|
+
}
|
|
4588
|
+
function startCountdown(sessionID, incompleteCount) {
|
|
4589
|
+
const state2 = getOrCreateState(sessionID);
|
|
4590
|
+
invalidate(sessionID, "starting new countdown");
|
|
4591
|
+
state2.version++;
|
|
4592
|
+
state2.mode = "countingDown";
|
|
4593
|
+
const capturedVersion = state2.version;
|
|
4594
|
+
log(`[${HOOK_NAME}] Starting countdown`, {
|
|
4595
|
+
sessionID,
|
|
4596
|
+
seconds: COUNTDOWN_SECONDS,
|
|
4597
|
+
version: capturedVersion,
|
|
4598
|
+
incompleteCount
|
|
4599
|
+
});
|
|
4600
|
+
showCountdownToast(COUNTDOWN_SECONDS, incompleteCount);
|
|
4601
|
+
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
4602
|
+
const toastInterval = setInterval(() => {
|
|
4603
|
+
if (state2.version !== capturedVersion) {
|
|
4604
|
+
clearInterval(toastInterval);
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4607
|
+
secondsRemaining--;
|
|
4608
|
+
if (secondsRemaining > 0) {
|
|
4609
|
+
showCountdownToast(secondsRemaining, incompleteCount);
|
|
4610
|
+
}
|
|
4611
|
+
}, 1000);
|
|
4612
|
+
state2.timer = setTimeout(() => {
|
|
4613
|
+
clearInterval(toastInterval);
|
|
4614
|
+
clearTimer(state2);
|
|
4615
|
+
executeInjection(sessionID, capturedVersion);
|
|
4616
|
+
}, COUNTDOWN_SECONDS * 1000);
|
|
4617
|
+
}
|
|
4452
4618
|
const handler = async ({ event }) => {
|
|
4453
4619
|
const props = event.properties;
|
|
4454
4620
|
if (event.type === "session.error") {
|
|
4455
4621
|
const sessionID = props?.sessionID;
|
|
4456
|
-
if (sessionID)
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
const countdown = pendingCountdowns.get(sessionID);
|
|
4464
|
-
if (countdown) {
|
|
4465
|
-
clearInterval(countdown.intervalId);
|
|
4466
|
-
pendingCountdowns.delete(sessionID);
|
|
4467
|
-
}
|
|
4468
|
-
}
|
|
4622
|
+
if (!sessionID)
|
|
4623
|
+
return;
|
|
4624
|
+
const isInterrupt = detectInterrupt(props?.error);
|
|
4625
|
+
const state2 = getOrCreateState(sessionID);
|
|
4626
|
+
invalidate(sessionID, isInterrupt ? "user interrupt" : "session error");
|
|
4627
|
+
state2.mode = "errorBypass";
|
|
4628
|
+
log(`[${HOOK_NAME}] session.error received`, { sessionID, isInterrupt, error: props?.error });
|
|
4469
4629
|
return;
|
|
4470
4630
|
}
|
|
4471
4631
|
if (event.type === "session.idle") {
|
|
@@ -4473,40 +4633,27 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4473
4633
|
if (!sessionID)
|
|
4474
4634
|
return;
|
|
4475
4635
|
log(`[${HOOK_NAME}] session.idle received`, { sessionID });
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
log(`[${HOOK_NAME}] Skipped: not main session`, { sessionID, mainSessionID: mainSessionID2 });
|
|
4636
|
+
if (!isMainSession(sessionID)) {
|
|
4637
|
+
log(`[${HOOK_NAME}] Skipped: not main session`, { sessionID });
|
|
4479
4638
|
return;
|
|
4480
4639
|
}
|
|
4481
|
-
const
|
|
4482
|
-
if (
|
|
4483
|
-
clearInterval(existingCountdown.intervalId);
|
|
4484
|
-
pendingCountdowns.delete(sessionID);
|
|
4485
|
-
log(`[${HOOK_NAME}] Cancelled existing countdown`, { sessionID });
|
|
4486
|
-
}
|
|
4487
|
-
if (recoveringSessions.has(sessionID)) {
|
|
4640
|
+
const state2 = getOrCreateState(sessionID);
|
|
4641
|
+
if (state2.mode === "recovering") {
|
|
4488
4642
|
log(`[${HOOK_NAME}] Skipped: session in recovery mode`, { sessionID });
|
|
4489
4643
|
return;
|
|
4490
4644
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
interruptedSessions.delete(sessionID);
|
|
4494
|
-
errorSessions.delete(sessionID);
|
|
4495
|
-
log(`[${HOOK_NAME}] Skipped: error/interrupt bypass`, { sessionID });
|
|
4645
|
+
if (state2.mode === "errorBypass") {
|
|
4646
|
+
log(`[${HOOK_NAME}] Skipped: error bypass (awaiting user message to resume)`, { sessionID });
|
|
4496
4647
|
return;
|
|
4497
4648
|
}
|
|
4498
|
-
if (
|
|
4499
|
-
log(`[${HOOK_NAME}] Skipped: already
|
|
4649
|
+
if (state2.mode === "countingDown" || state2.mode === "injecting") {
|
|
4650
|
+
log(`[${HOOK_NAME}] Skipped: already ${state2.mode}`, { sessionID });
|
|
4500
4651
|
return;
|
|
4501
4652
|
}
|
|
4502
4653
|
let todos = [];
|
|
4503
4654
|
try {
|
|
4504
|
-
|
|
4505
|
-
const response = await ctx.client.session.todo({
|
|
4506
|
-
path: { id: sessionID }
|
|
4507
|
-
});
|
|
4655
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4508
4656
|
todos = response.data ?? response;
|
|
4509
|
-
log(`[${HOOK_NAME}] Todo API response`, { sessionID, todosCount: todos?.length ?? 0 });
|
|
4510
4657
|
} catch (err) {
|
|
4511
4658
|
log(`[${HOOK_NAME}] Todo API error`, { sessionID, error: String(err) });
|
|
4512
4659
|
return;
|
|
@@ -4515,188 +4662,77 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4515
4662
|
log(`[${HOOK_NAME}] No todos found`, { sessionID });
|
|
4516
4663
|
return;
|
|
4517
4664
|
}
|
|
4518
|
-
const
|
|
4519
|
-
if (
|
|
4665
|
+
const incompleteCount = getIncompleteCount(todos);
|
|
4666
|
+
if (incompleteCount === 0) {
|
|
4520
4667
|
log(`[${HOOK_NAME}] All todos completed`, { sessionID, total: todos.length });
|
|
4521
4668
|
return;
|
|
4522
4669
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
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 });
|
|
4670
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4671
|
+
if (hasRunningBgTasks) {
|
|
4672
|
+
log(`[${HOOK_NAME}] Skipped: background tasks still running`, { sessionID });
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
log(`[${HOOK_NAME}] Found incomplete todos`, {
|
|
4676
|
+
sessionID,
|
|
4677
|
+
incomplete: incompleteCount,
|
|
4678
|
+
total: todos.length
|
|
4679
|
+
});
|
|
4680
|
+
startCountdown(sessionID, incompleteCount);
|
|
4681
|
+
return;
|
|
4616
4682
|
}
|
|
4617
4683
|
if (event.type === "message.updated") {
|
|
4618
4684
|
const info = props?.info;
|
|
4619
4685
|
const sessionID = info?.sessionID;
|
|
4620
4686
|
const role = info?.role;
|
|
4621
4687
|
const finish = info?.finish;
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
log(`[${HOOK_NAME}]
|
|
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
|
-
}
|
|
4688
|
+
if (!sessionID)
|
|
4689
|
+
return;
|
|
4690
|
+
if (role === "user") {
|
|
4691
|
+
const state2 = sessions.get(sessionID);
|
|
4692
|
+
if (state2?.mode === "errorBypass") {
|
|
4693
|
+
state2.mode = "idle";
|
|
4694
|
+
log(`[${HOOK_NAME}] User message cleared errorBypass mode`, { sessionID });
|
|
4683
4695
|
}
|
|
4696
|
+
invalidate(sessionID, "user message received");
|
|
4697
|
+
return;
|
|
4684
4698
|
}
|
|
4699
|
+
if (role === "assistant" && !finish) {
|
|
4700
|
+
invalidate(sessionID, "assistant is working (streaming)");
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
if (role === "assistant" && finish) {
|
|
4704
|
+
log(`[${HOOK_NAME}] Assistant turn finished`, { sessionID, finish });
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
if (event.type === "message.part.updated") {
|
|
4710
|
+
const info = props?.info;
|
|
4711
|
+
const sessionID = info?.sessionID;
|
|
4712
|
+
const role = info?.role;
|
|
4713
|
+
if (sessionID && role === "assistant") {
|
|
4714
|
+
invalidate(sessionID, "assistant streaming");
|
|
4715
|
+
}
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
|
|
4719
|
+
const sessionID = props?.sessionID;
|
|
4720
|
+
if (sessionID) {
|
|
4721
|
+
invalidate(sessionID, `tool execution (${event.type})`);
|
|
4722
|
+
}
|
|
4723
|
+
return;
|
|
4685
4724
|
}
|
|
4686
4725
|
if (event.type === "session.deleted") {
|
|
4687
4726
|
const sessionInfo = props?.info;
|
|
4688
4727
|
if (sessionInfo?.id) {
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
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);
|
|
4728
|
+
const state2 = sessions.get(sessionInfo.id);
|
|
4729
|
+
if (state2) {
|
|
4730
|
+
clearTimer(state2);
|
|
4698
4731
|
}
|
|
4732
|
+
sessions.delete(sessionInfo.id);
|
|
4733
|
+
log(`[${HOOK_NAME}] Session deleted, state cleaned up`, { sessionID: sessionInfo.id });
|
|
4699
4734
|
}
|
|
4735
|
+
return;
|
|
4700
4736
|
}
|
|
4701
4737
|
};
|
|
4702
4738
|
return {
|
|
@@ -5154,28 +5190,7 @@ import { join as join10 } from "path";
|
|
|
5154
5190
|
|
|
5155
5191
|
// src/hooks/session-recovery/constants.ts
|
|
5156
5192
|
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");
|
|
5193
|
+
var OPENCODE_STORAGE2 = getOpenCodeStorageDir();
|
|
5179
5194
|
var MESSAGE_STORAGE2 = join9(OPENCODE_STORAGE2, "message");
|
|
5180
5195
|
var PART_STORAGE2 = join9(OPENCODE_STORAGE2, "part");
|
|
5181
5196
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
@@ -5297,7 +5312,16 @@ function findEmptyMessages(sessionID) {
|
|
|
5297
5312
|
}
|
|
5298
5313
|
function findEmptyMessageByIndex(sessionID, targetIndex) {
|
|
5299
5314
|
const messages = readMessages(sessionID);
|
|
5300
|
-
const indicesToTry = [
|
|
5315
|
+
const indicesToTry = [
|
|
5316
|
+
targetIndex,
|
|
5317
|
+
targetIndex - 1,
|
|
5318
|
+
targetIndex + 1,
|
|
5319
|
+
targetIndex - 2,
|
|
5320
|
+
targetIndex + 2,
|
|
5321
|
+
targetIndex - 3,
|
|
5322
|
+
targetIndex - 4,
|
|
5323
|
+
targetIndex - 5
|
|
5324
|
+
];
|
|
5301
5325
|
for (const idx of indicesToTry) {
|
|
5302
5326
|
if (idx < 0 || idx >= messages.length)
|
|
5303
5327
|
continue;
|
|
@@ -5723,8 +5747,8 @@ var PLATFORM_MAP = {
|
|
|
5723
5747
|
"win32-x64": { os: "windows", arch: "amd64", ext: "zip" }
|
|
5724
5748
|
};
|
|
5725
5749
|
function getCacheDir() {
|
|
5726
|
-
const
|
|
5727
|
-
const base =
|
|
5750
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
5751
|
+
const base = xdgCache || join11(homedir5(), ".cache");
|
|
5728
5752
|
return join11(base, "oh-my-opencode", "bin");
|
|
5729
5753
|
}
|
|
5730
5754
|
function getBinaryName() {
|
|
@@ -5785,8 +5809,8 @@ async function downloadCommentChecker() {
|
|
|
5785
5809
|
return binaryPath;
|
|
5786
5810
|
}
|
|
5787
5811
|
const version = getPackageVersion();
|
|
5788
|
-
const { os:
|
|
5789
|
-
const assetName = `comment-checker_v${version}_${
|
|
5812
|
+
const { os: os4, arch, ext } = platformInfo;
|
|
5813
|
+
const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
|
|
5790
5814
|
const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
|
|
5791
5815
|
debugLog(`Downloading from: ${downloadUrl}`);
|
|
5792
5816
|
console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
|
|
@@ -5898,15 +5922,15 @@ async function getCommentCheckerPath() {
|
|
|
5898
5922
|
function startBackgroundInit() {
|
|
5899
5923
|
if (!initPromise) {
|
|
5900
5924
|
initPromise = getCommentCheckerPath();
|
|
5901
|
-
initPromise.then((
|
|
5902
|
-
debugLog2("background init complete:",
|
|
5925
|
+
initPromise.then((path4) => {
|
|
5926
|
+
debugLog2("background init complete:", path4 || "no binary");
|
|
5903
5927
|
}).catch((err) => {
|
|
5904
5928
|
debugLog2("background init error:", err);
|
|
5905
5929
|
});
|
|
5906
5930
|
}
|
|
5907
5931
|
}
|
|
5908
5932
|
var COMMENT_CHECKER_CLI_PATH = findCommentCheckerPathSync();
|
|
5909
|
-
async function runCommentChecker(input, cliPath) {
|
|
5933
|
+
async function runCommentChecker(input, cliPath, customPrompt) {
|
|
5910
5934
|
const binaryPath = cliPath ?? resolvedCliPath ?? COMMENT_CHECKER_CLI_PATH;
|
|
5911
5935
|
if (!binaryPath) {
|
|
5912
5936
|
debugLog2("comment-checker binary not found");
|
|
@@ -5919,7 +5943,11 @@ async function runCommentChecker(input, cliPath) {
|
|
|
5919
5943
|
const jsonInput = JSON.stringify(input);
|
|
5920
5944
|
debugLog2("running comment-checker with input:", jsonInput.substring(0, 200));
|
|
5921
5945
|
try {
|
|
5922
|
-
const
|
|
5946
|
+
const args = [binaryPath];
|
|
5947
|
+
if (customPrompt) {
|
|
5948
|
+
args.push("--prompt", customPrompt);
|
|
5949
|
+
}
|
|
5950
|
+
const proc = spawn4(args, {
|
|
5923
5951
|
stdin: "pipe",
|
|
5924
5952
|
stdout: "pipe",
|
|
5925
5953
|
stderr: "pipe"
|
|
@@ -5961,6 +5989,7 @@ function debugLog3(...args) {
|
|
|
5961
5989
|
var pendingCalls = new Map;
|
|
5962
5990
|
var PENDING_CALL_TTL = 60000;
|
|
5963
5991
|
var cliPathPromise = null;
|
|
5992
|
+
var cleanupIntervalStarted = false;
|
|
5964
5993
|
function cleanupOldPendingCalls() {
|
|
5965
5994
|
const now = Date.now();
|
|
5966
5995
|
for (const [callID, call] of pendingCalls) {
|
|
@@ -5969,13 +5998,16 @@ function cleanupOldPendingCalls() {
|
|
|
5969
5998
|
}
|
|
5970
5999
|
}
|
|
5971
6000
|
}
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
6001
|
+
function createCommentCheckerHooks(config) {
|
|
6002
|
+
debugLog3("createCommentCheckerHooks called", { config });
|
|
6003
|
+
if (!cleanupIntervalStarted) {
|
|
6004
|
+
cleanupIntervalStarted = true;
|
|
6005
|
+
setInterval(cleanupOldPendingCalls, 1e4);
|
|
6006
|
+
}
|
|
5975
6007
|
startBackgroundInit();
|
|
5976
6008
|
cliPathPromise = getCommentCheckerPath();
|
|
5977
|
-
cliPathPromise.then((
|
|
5978
|
-
debugLog3("CLI path resolved:",
|
|
6009
|
+
cliPathPromise.then((path4) => {
|
|
6010
|
+
debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
|
|
5979
6011
|
}).catch((err) => {
|
|
5980
6012
|
debugLog3("CLI path resolution error:", err);
|
|
5981
6013
|
});
|
|
@@ -6031,14 +6063,14 @@ function createCommentCheckerHooks() {
|
|
|
6031
6063
|
return;
|
|
6032
6064
|
}
|
|
6033
6065
|
debugLog3("using CLI:", cliPath);
|
|
6034
|
-
await processWithCli(input, pendingCall, output, cliPath);
|
|
6066
|
+
await processWithCli(input, pendingCall, output, cliPath, config?.custom_prompt);
|
|
6035
6067
|
} catch (err) {
|
|
6036
6068
|
debugLog3("tool.execute.after failed:", err);
|
|
6037
6069
|
}
|
|
6038
6070
|
}
|
|
6039
6071
|
};
|
|
6040
6072
|
}
|
|
6041
|
-
async function processWithCli(input, pendingCall, output, cliPath) {
|
|
6073
|
+
async function processWithCli(input, pendingCall, output, cliPath, customPrompt) {
|
|
6042
6074
|
debugLog3("using CLI mode with path:", cliPath);
|
|
6043
6075
|
const hookInput = {
|
|
6044
6076
|
session_id: pendingCall.sessionID,
|
|
@@ -6054,7 +6086,7 @@ async function processWithCli(input, pendingCall, output, cliPath) {
|
|
|
6054
6086
|
edits: pendingCall.edits
|
|
6055
6087
|
}
|
|
6056
6088
|
};
|
|
6057
|
-
const result = await runCommentChecker(hookInput, cliPath);
|
|
6089
|
+
const result = await runCommentChecker(hookInput, cliPath, customPrompt);
|
|
6058
6090
|
if (result.hasComments && result.message) {
|
|
6059
6091
|
debugLog3("CLI detected comments, appending message");
|
|
6060
6092
|
output.output += `
|
|
@@ -6113,7 +6145,7 @@ import { join as join15 } from "path";
|
|
|
6113
6145
|
|
|
6114
6146
|
// src/hooks/directory-agents-injector/constants.ts
|
|
6115
6147
|
import { join as join14 } from "path";
|
|
6116
|
-
var OPENCODE_STORAGE3 =
|
|
6148
|
+
var OPENCODE_STORAGE3 = getOpenCodeStorageDir();
|
|
6117
6149
|
var AGENTS_INJECTOR_STORAGE = join14(OPENCODE_STORAGE3, "directory-agents");
|
|
6118
6150
|
var AGENTS_FILENAME = "AGENTS.md";
|
|
6119
6151
|
|
|
@@ -6162,12 +6194,12 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
6162
6194
|
}
|
|
6163
6195
|
return sessionCaches.get(sessionID);
|
|
6164
6196
|
}
|
|
6165
|
-
function resolveFilePath2(
|
|
6166
|
-
if (!
|
|
6197
|
+
function resolveFilePath2(path4) {
|
|
6198
|
+
if (!path4)
|
|
6167
6199
|
return null;
|
|
6168
|
-
if (
|
|
6169
|
-
return
|
|
6170
|
-
return resolve2(ctx.directory,
|
|
6200
|
+
if (path4.startsWith("/"))
|
|
6201
|
+
return path4;
|
|
6202
|
+
return resolve2(ctx.directory, path4);
|
|
6171
6203
|
}
|
|
6172
6204
|
function findAgentsMdUp(startDir) {
|
|
6173
6205
|
const found = [];
|
|
@@ -6285,7 +6317,7 @@ import { join as join18 } from "path";
|
|
|
6285
6317
|
|
|
6286
6318
|
// src/hooks/directory-readme-injector/constants.ts
|
|
6287
6319
|
import { join as join17 } from "path";
|
|
6288
|
-
var OPENCODE_STORAGE4 =
|
|
6320
|
+
var OPENCODE_STORAGE4 = getOpenCodeStorageDir();
|
|
6289
6321
|
var README_INJECTOR_STORAGE = join17(OPENCODE_STORAGE4, "directory-readme");
|
|
6290
6322
|
var README_FILENAME = "README.md";
|
|
6291
6323
|
|
|
@@ -6334,12 +6366,12 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
6334
6366
|
}
|
|
6335
6367
|
return sessionCaches.get(sessionID);
|
|
6336
6368
|
}
|
|
6337
|
-
function resolveFilePath2(
|
|
6338
|
-
if (!
|
|
6369
|
+
function resolveFilePath2(path4) {
|
|
6370
|
+
if (!path4)
|
|
6339
6371
|
return null;
|
|
6340
|
-
if (
|
|
6341
|
-
return
|
|
6342
|
-
return resolve3(ctx.directory,
|
|
6372
|
+
if (path4.startsWith("/"))
|
|
6373
|
+
return path4;
|
|
6374
|
+
return resolve3(ctx.directory, path4);
|
|
6343
6375
|
}
|
|
6344
6376
|
function findReadmeMdUp(startDir) {
|
|
6345
6377
|
const found = [];
|
|
@@ -6478,7 +6510,8 @@ var TOKEN_LIMIT_KEYWORDS = [
|
|
|
6478
6510
|
"token limit",
|
|
6479
6511
|
"context length",
|
|
6480
6512
|
"too many tokens",
|
|
6481
|
-
"non-empty content"
|
|
6513
|
+
"non-empty content",
|
|
6514
|
+
"invalid_request_error"
|
|
6482
6515
|
];
|
|
6483
6516
|
var MESSAGE_INDEX_PATTERN = /messages\.(\d+)/;
|
|
6484
6517
|
function extractTokensFromMessage(message) {
|
|
@@ -6566,9 +6599,9 @@ function parseAnthropicTokenLimitError(err) {
|
|
|
6566
6599
|
if (typeof responseBody === "string") {
|
|
6567
6600
|
try {
|
|
6568
6601
|
const jsonPatterns = [
|
|
6569
|
-
/data:\s*(\{[\s\S]
|
|
6570
|
-
/(\{"type"\s*:\s*"error"[\s\S]
|
|
6571
|
-
/(\{[\s\S]
|
|
6602
|
+
/data:\s*(\{[\s\S]*\})\s*$/m,
|
|
6603
|
+
/(\{"type"\s*:\s*"error"[\s\S]*\})/,
|
|
6604
|
+
/(\{[\s\S]*"error"[\s\S]*\})/
|
|
6572
6605
|
];
|
|
6573
6606
|
for (const pattern of jsonPatterns) {
|
|
6574
6607
|
const dataMatch = responseBody.match(pattern);
|
|
@@ -6655,7 +6688,6 @@ function estimateTokens2(text) {
|
|
|
6655
6688
|
}
|
|
6656
6689
|
|
|
6657
6690
|
// src/hooks/anthropic-auto-compact/pruning-deduplication.ts
|
|
6658
|
-
var MESSAGE_STORAGE3 = join20(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6659
6691
|
function createToolSignature(toolName, input) {
|
|
6660
6692
|
const sortedInput = sortObject(input);
|
|
6661
6693
|
return `${toolName}::${JSON.stringify(sortedInput)}`;
|
|
@@ -6675,13 +6707,13 @@ function sortObject(obj) {
|
|
|
6675
6707
|
return sorted;
|
|
6676
6708
|
}
|
|
6677
6709
|
function getMessageDir3(sessionID) {
|
|
6678
|
-
if (!existsSync15(
|
|
6710
|
+
if (!existsSync15(MESSAGE_STORAGE))
|
|
6679
6711
|
return null;
|
|
6680
|
-
const directPath = join20(
|
|
6712
|
+
const directPath = join20(MESSAGE_STORAGE, sessionID);
|
|
6681
6713
|
if (existsSync15(directPath))
|
|
6682
6714
|
return directPath;
|
|
6683
|
-
for (const dir of readdirSync4(
|
|
6684
|
-
const sessionPath = join20(
|
|
6715
|
+
for (const dir of readdirSync4(MESSAGE_STORAGE)) {
|
|
6716
|
+
const sessionPath = join20(MESSAGE_STORAGE, dir, sessionID);
|
|
6685
6717
|
if (existsSync15(sessionPath))
|
|
6686
6718
|
return sessionPath;
|
|
6687
6719
|
}
|
|
@@ -6793,15 +6825,14 @@ function findToolOutput(messages, callID) {
|
|
|
6793
6825
|
// src/hooks/anthropic-auto-compact/pruning-supersede.ts
|
|
6794
6826
|
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
6795
6827
|
import { join as join21 } from "path";
|
|
6796
|
-
var MESSAGE_STORAGE4 = join21(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6797
6828
|
function getMessageDir4(sessionID) {
|
|
6798
|
-
if (!existsSync16(
|
|
6829
|
+
if (!existsSync16(MESSAGE_STORAGE))
|
|
6799
6830
|
return null;
|
|
6800
|
-
const directPath = join21(
|
|
6831
|
+
const directPath = join21(MESSAGE_STORAGE, sessionID);
|
|
6801
6832
|
if (existsSync16(directPath))
|
|
6802
6833
|
return directPath;
|
|
6803
|
-
for (const dir of readdirSync5(
|
|
6804
|
-
const sessionPath = join21(
|
|
6834
|
+
for (const dir of readdirSync5(MESSAGE_STORAGE)) {
|
|
6835
|
+
const sessionPath = join21(MESSAGE_STORAGE, dir, sessionID);
|
|
6805
6836
|
if (existsSync16(sessionPath))
|
|
6806
6837
|
return sessionPath;
|
|
6807
6838
|
}
|
|
@@ -6956,15 +6987,14 @@ function findToolInput(messages, callID) {
|
|
|
6956
6987
|
// src/hooks/anthropic-auto-compact/pruning-purge-errors.ts
|
|
6957
6988
|
import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
6958
6989
|
import { join as join22 } from "path";
|
|
6959
|
-
var MESSAGE_STORAGE5 = join22(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
6960
6990
|
function getMessageDir5(sessionID) {
|
|
6961
|
-
if (!existsSync17(
|
|
6991
|
+
if (!existsSync17(MESSAGE_STORAGE))
|
|
6962
6992
|
return null;
|
|
6963
|
-
const directPath = join22(
|
|
6993
|
+
const directPath = join22(MESSAGE_STORAGE, sessionID);
|
|
6964
6994
|
if (existsSync17(directPath))
|
|
6965
6995
|
return directPath;
|
|
6966
|
-
for (const dir of readdirSync6(
|
|
6967
|
-
const sessionPath = join22(
|
|
6996
|
+
for (const dir of readdirSync6(MESSAGE_STORAGE)) {
|
|
6997
|
+
const sessionPath = join22(MESSAGE_STORAGE, dir, sessionID);
|
|
6968
6998
|
if (existsSync17(sessionPath))
|
|
6969
6999
|
return sessionPath;
|
|
6970
7000
|
}
|
|
@@ -7062,15 +7092,14 @@ function executePurgeErrors(sessionID, state2, config, protectedTools) {
|
|
|
7062
7092
|
// src/hooks/anthropic-auto-compact/pruning-storage.ts
|
|
7063
7093
|
import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
7064
7094
|
import { join as join23 } from "path";
|
|
7065
|
-
var MESSAGE_STORAGE6 = join23(process.env.HOME || process.env.USERPROFILE || "", ".config", "opencode", "sessions");
|
|
7066
7095
|
function getMessageDir6(sessionID) {
|
|
7067
|
-
if (!existsSync18(
|
|
7096
|
+
if (!existsSync18(MESSAGE_STORAGE))
|
|
7068
7097
|
return null;
|
|
7069
|
-
const directPath = join23(
|
|
7098
|
+
const directPath = join23(MESSAGE_STORAGE, sessionID);
|
|
7070
7099
|
if (existsSync18(directPath))
|
|
7071
7100
|
return directPath;
|
|
7072
|
-
for (const dir of readdirSync7(
|
|
7073
|
-
const sessionPath = join23(
|
|
7101
|
+
for (const dir of readdirSync7(MESSAGE_STORAGE)) {
|
|
7102
|
+
const sessionPath = join23(MESSAGE_STORAGE, dir, sessionID);
|
|
7074
7103
|
if (existsSync18(sessionPath))
|
|
7075
7104
|
return sessionPath;
|
|
7076
7105
|
}
|
|
@@ -7212,27 +7241,20 @@ async function executeDynamicContextPruning(sessionID, config, client) {
|
|
|
7212
7241
|
|
|
7213
7242
|
// src/hooks/anthropic-auto-compact/storage.ts
|
|
7214
7243
|
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7215
|
-
import { homedir as homedir6 } from "os";
|
|
7216
7244
|
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");
|
|
7245
|
+
var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
|
|
7246
|
+
var MESSAGE_STORAGE3 = join24(OPENCODE_STORAGE5, "message");
|
|
7225
7247
|
var PART_STORAGE3 = join24(OPENCODE_STORAGE5, "part");
|
|
7226
7248
|
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
7249
|
function getMessageDir7(sessionID) {
|
|
7228
|
-
if (!existsSync19(
|
|
7250
|
+
if (!existsSync19(MESSAGE_STORAGE3))
|
|
7229
7251
|
return "";
|
|
7230
|
-
const directPath = join24(
|
|
7252
|
+
const directPath = join24(MESSAGE_STORAGE3, sessionID);
|
|
7231
7253
|
if (existsSync19(directPath)) {
|
|
7232
7254
|
return directPath;
|
|
7233
7255
|
}
|
|
7234
|
-
for (const dir of readdirSync8(
|
|
7235
|
-
const sessionPath = join24(
|
|
7256
|
+
for (const dir of readdirSync8(MESSAGE_STORAGE3)) {
|
|
7257
|
+
const sessionPath = join24(MESSAGE_STORAGE3, dir, sessionID);
|
|
7236
7258
|
if (existsSync19(sessionPath)) {
|
|
7237
7259
|
return sessionPath;
|
|
7238
7260
|
}
|
|
@@ -7360,6 +7382,7 @@ function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRa
|
|
|
7360
7382
|
}
|
|
7361
7383
|
|
|
7362
7384
|
// src/hooks/anthropic-auto-compact/executor.ts
|
|
7385
|
+
var PLACEHOLDER_TEXT = "[user interrupted]";
|
|
7363
7386
|
function getOrCreateRetryState(autoCompactState, sessionID) {
|
|
7364
7387
|
let state2 = autoCompactState.retryStateBySession.get(sessionID);
|
|
7365
7388
|
if (!state2) {
|
|
@@ -7392,6 +7415,32 @@ function getOrCreateDcpState(autoCompactState, sessionID) {
|
|
|
7392
7415
|
}
|
|
7393
7416
|
return state2;
|
|
7394
7417
|
}
|
|
7418
|
+
function sanitizeEmptyMessagesBeforeSummarize(sessionID) {
|
|
7419
|
+
const emptyMessageIds = findEmptyMessages(sessionID);
|
|
7420
|
+
if (emptyMessageIds.length === 0) {
|
|
7421
|
+
return 0;
|
|
7422
|
+
}
|
|
7423
|
+
let fixedCount = 0;
|
|
7424
|
+
for (const messageID of emptyMessageIds) {
|
|
7425
|
+
const replaced = replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT);
|
|
7426
|
+
if (replaced) {
|
|
7427
|
+
fixedCount++;
|
|
7428
|
+
} else {
|
|
7429
|
+
const injected = injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT);
|
|
7430
|
+
if (injected) {
|
|
7431
|
+
fixedCount++;
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
if (fixedCount > 0) {
|
|
7436
|
+
log("[auto-compact] pre-summarize sanitization fixed empty messages", {
|
|
7437
|
+
sessionID,
|
|
7438
|
+
fixedCount,
|
|
7439
|
+
totalEmpty: emptyMessageIds.length
|
|
7440
|
+
});
|
|
7441
|
+
}
|
|
7442
|
+
return fixedCount;
|
|
7443
|
+
}
|
|
7395
7444
|
async function getLastMessagePair(sessionID, client, directory) {
|
|
7396
7445
|
try {
|
|
7397
7446
|
const resp = await client.session.messages({
|
|
@@ -7547,6 +7596,77 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7547
7596
|
try {
|
|
7548
7597
|
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
7549
7598
|
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
7599
|
+
const dcpState = getOrCreateDcpState(autoCompactState, sessionID);
|
|
7600
|
+
if (experimental?.dcp_for_compaction && !dcpState.attempted && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
7601
|
+
dcpState.attempted = true;
|
|
7602
|
+
log("[auto-compact] DCP triggered FIRST on token limit error", {
|
|
7603
|
+
sessionID,
|
|
7604
|
+
currentTokens: errorData.currentTokens,
|
|
7605
|
+
maxTokens: errorData.maxTokens
|
|
7606
|
+
});
|
|
7607
|
+
const dcpConfig = experimental.dynamic_context_pruning ?? {
|
|
7608
|
+
enabled: true,
|
|
7609
|
+
notification: "detailed",
|
|
7610
|
+
protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"]
|
|
7611
|
+
};
|
|
7612
|
+
try {
|
|
7613
|
+
const pruningResult = await executeDynamicContextPruning(sessionID, dcpConfig, client);
|
|
7614
|
+
if (pruningResult.itemsPruned > 0) {
|
|
7615
|
+
dcpState.itemsPruned = pruningResult.itemsPruned;
|
|
7616
|
+
log("[auto-compact] DCP successful, proceeding to compaction", {
|
|
7617
|
+
itemsPruned: pruningResult.itemsPruned,
|
|
7618
|
+
tokensSaved: pruningResult.totalTokensSaved
|
|
7619
|
+
});
|
|
7620
|
+
await client.tui.showToast({
|
|
7621
|
+
body: {
|
|
7622
|
+
title: "Dynamic Context Pruning",
|
|
7623
|
+
message: `Pruned ${pruningResult.itemsPruned} items (~${Math.round(pruningResult.totalTokensSaved / 1000)}k tokens). Running compaction...`,
|
|
7624
|
+
variant: "success",
|
|
7625
|
+
duration: 3000
|
|
7626
|
+
}
|
|
7627
|
+
}).catch(() => {});
|
|
7628
|
+
const providerID = msg.providerID;
|
|
7629
|
+
const modelID = msg.modelID;
|
|
7630
|
+
if (providerID && modelID) {
|
|
7631
|
+
try {
|
|
7632
|
+
sanitizeEmptyMessagesBeforeSummarize(sessionID);
|
|
7633
|
+
await client.tui.showToast({
|
|
7634
|
+
body: {
|
|
7635
|
+
title: "Auto Compact",
|
|
7636
|
+
message: "Summarizing session after DCP...",
|
|
7637
|
+
variant: "warning",
|
|
7638
|
+
duration: 3000
|
|
7639
|
+
}
|
|
7640
|
+
}).catch(() => {});
|
|
7641
|
+
await client.session.summarize({
|
|
7642
|
+
path: { id: sessionID },
|
|
7643
|
+
body: { providerID, modelID },
|
|
7644
|
+
query: { directory }
|
|
7645
|
+
});
|
|
7646
|
+
clearSessionState(autoCompactState, sessionID);
|
|
7647
|
+
setTimeout(async () => {
|
|
7648
|
+
try {
|
|
7649
|
+
await client.session.prompt_async({
|
|
7650
|
+
path: { sessionID },
|
|
7651
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
7652
|
+
query: { directory }
|
|
7653
|
+
});
|
|
7654
|
+
} catch {}
|
|
7655
|
+
}, 500);
|
|
7656
|
+
return;
|
|
7657
|
+
} catch (summarizeError) {
|
|
7658
|
+
log("[auto-compact] summarize after DCP failed, continuing recovery", {
|
|
7659
|
+
error: String(summarizeError)
|
|
7660
|
+
});
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
} else {
|
|
7664
|
+
log("[auto-compact] DCP did not prune any items", { sessionID });
|
|
7665
|
+
}
|
|
7666
|
+
} catch (error) {
|
|
7667
|
+
log("[auto-compact] DCP failed", { error: String(error) });
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7550
7670
|
if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
7551
7671
|
log("[auto-compact] aggressive truncation triggered (experimental)", {
|
|
7552
7672
|
currentTokens: errorData.currentTokens,
|
|
@@ -7673,6 +7793,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7673
7793
|
const modelID = msg.modelID;
|
|
7674
7794
|
if (providerID && modelID) {
|
|
7675
7795
|
try {
|
|
7796
|
+
sanitizeEmptyMessagesBeforeSummarize(sessionID);
|
|
7676
7797
|
await client.tui.showToast({
|
|
7677
7798
|
body: {
|
|
7678
7799
|
title: "Auto Compact",
|
|
@@ -7715,45 +7836,6 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7715
7836
|
}).catch(() => {});
|
|
7716
7837
|
}
|
|
7717
7838
|
}
|
|
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
7839
|
const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
|
|
7758
7840
|
if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
|
|
7759
7841
|
const pair = await getLastMessagePair(sessionID, client, directory);
|
|
@@ -8506,21 +8588,21 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
8506
8588
|
|
|
8507
8589
|
// src/hooks/claude-code-hooks/config-loader.ts
|
|
8508
8590
|
import { existsSync as existsSync22 } from "fs";
|
|
8509
|
-
import { homedir as
|
|
8591
|
+
import { homedir as homedir6 } from "os";
|
|
8510
8592
|
import { join as join27 } from "path";
|
|
8511
|
-
var USER_CONFIG_PATH = join27(
|
|
8593
|
+
var USER_CONFIG_PATH = join27(homedir6(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
8512
8594
|
function getProjectConfigPath() {
|
|
8513
8595
|
return join27(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
8514
8596
|
}
|
|
8515
|
-
async function loadConfigFromPath(
|
|
8516
|
-
if (!existsSync22(
|
|
8597
|
+
async function loadConfigFromPath(path4) {
|
|
8598
|
+
if (!existsSync22(path4)) {
|
|
8517
8599
|
return null;
|
|
8518
8600
|
}
|
|
8519
8601
|
try {
|
|
8520
|
-
const content = await Bun.file(
|
|
8602
|
+
const content = await Bun.file(path4).text();
|
|
8521
8603
|
return JSON.parse(content);
|
|
8522
8604
|
} catch (error) {
|
|
8523
|
-
log("Failed to load config", { path:
|
|
8605
|
+
log("Failed to load config", { path: path4, error });
|
|
8524
8606
|
return null;
|
|
8525
8607
|
}
|
|
8526
8608
|
}
|
|
@@ -8708,10 +8790,10 @@ function ensureTranscriptDir() {
|
|
|
8708
8790
|
}
|
|
8709
8791
|
function appendTranscriptEntry(sessionId, entry) {
|
|
8710
8792
|
ensureTranscriptDir();
|
|
8711
|
-
const
|
|
8793
|
+
const path4 = getTranscriptPath(sessionId);
|
|
8712
8794
|
const line = JSON.stringify(entry) + `
|
|
8713
8795
|
`;
|
|
8714
|
-
appendFileSync5(
|
|
8796
|
+
appendFileSync5(path4, line);
|
|
8715
8797
|
}
|
|
8716
8798
|
function recordToolUse(sessionId, toolName, toolInput) {
|
|
8717
8799
|
appendTranscriptEntry(sessionId, {
|
|
@@ -8818,11 +8900,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
8818
8900
|
}
|
|
8819
8901
|
}
|
|
8820
8902
|
}
|
|
8821
|
-
function deleteTempTranscript(
|
|
8822
|
-
if (!
|
|
8903
|
+
function deleteTempTranscript(path4) {
|
|
8904
|
+
if (!path4)
|
|
8823
8905
|
return;
|
|
8824
8906
|
try {
|
|
8825
|
-
unlinkSync5(
|
|
8907
|
+
unlinkSync5(path4);
|
|
8826
8908
|
} catch {}
|
|
8827
8909
|
}
|
|
8828
8910
|
|
|
@@ -9449,7 +9531,7 @@ ${result.message}`;
|
|
|
9449
9531
|
}
|
|
9450
9532
|
// src/hooks/rules-injector/index.ts
|
|
9451
9533
|
import { readFileSync as readFileSync15 } from "fs";
|
|
9452
|
-
import { homedir as
|
|
9534
|
+
import { homedir as homedir7 } from "os";
|
|
9453
9535
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
9454
9536
|
|
|
9455
9537
|
// src/hooks/rules-injector/finder.ts
|
|
@@ -9463,7 +9545,7 @@ import { dirname as dirname4, join as join31, relative } from "path";
|
|
|
9463
9545
|
|
|
9464
9546
|
// src/hooks/rules-injector/constants.ts
|
|
9465
9547
|
import { join as join30 } from "path";
|
|
9466
|
-
var OPENCODE_STORAGE6 =
|
|
9548
|
+
var OPENCODE_STORAGE6 = getOpenCodeStorageDir();
|
|
9467
9549
|
var RULES_INJECTOR_STORAGE = join30(OPENCODE_STORAGE6, "rules-injector");
|
|
9468
9550
|
var PROJECT_MARKERS = [
|
|
9469
9551
|
".git",
|
|
@@ -9804,12 +9886,12 @@ function createRulesInjectorHook(ctx) {
|
|
|
9804
9886
|
}
|
|
9805
9887
|
return sessionCaches.get(sessionID);
|
|
9806
9888
|
}
|
|
9807
|
-
function resolveFilePath2(
|
|
9808
|
-
if (!
|
|
9889
|
+
function resolveFilePath2(path4) {
|
|
9890
|
+
if (!path4)
|
|
9809
9891
|
return null;
|
|
9810
|
-
if (
|
|
9811
|
-
return
|
|
9812
|
-
return resolve4(ctx.directory,
|
|
9892
|
+
if (path4.startsWith("/"))
|
|
9893
|
+
return path4;
|
|
9894
|
+
return resolve4(ctx.directory, path4);
|
|
9813
9895
|
}
|
|
9814
9896
|
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
9815
9897
|
const resolved = resolveFilePath2(filePath);
|
|
@@ -9817,7 +9899,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
9817
9899
|
return;
|
|
9818
9900
|
const projectRoot = findProjectRoot(resolved);
|
|
9819
9901
|
const cache2 = getSessionCache(sessionID);
|
|
9820
|
-
const home =
|
|
9902
|
+
const home = homedir7();
|
|
9821
9903
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved);
|
|
9822
9904
|
const toInject = [];
|
|
9823
9905
|
for (const candidate of ruleFileCandidates) {
|
|
@@ -9932,33 +10014,33 @@ function createBackgroundNotificationHook(manager) {
|
|
|
9932
10014
|
}
|
|
9933
10015
|
// src/hooks/auto-update-checker/checker.ts
|
|
9934
10016
|
import * as fs5 from "fs";
|
|
9935
|
-
import * as
|
|
10017
|
+
import * as path5 from "path";
|
|
9936
10018
|
import { fileURLToPath } from "url";
|
|
9937
10019
|
|
|
9938
10020
|
// src/hooks/auto-update-checker/constants.ts
|
|
9939
|
-
import * as
|
|
9940
|
-
import * as
|
|
10021
|
+
import * as path4 from "path";
|
|
10022
|
+
import * as os4 from "os";
|
|
9941
10023
|
var PACKAGE_NAME = "oh-my-opencode";
|
|
9942
10024
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
9943
10025
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
9944
10026
|
function getCacheDir2() {
|
|
9945
10027
|
if (process.platform === "win32") {
|
|
9946
|
-
return
|
|
10028
|
+
return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
|
|
9947
10029
|
}
|
|
9948
|
-
return
|
|
10030
|
+
return path4.join(os4.homedir(), ".cache", "opencode");
|
|
9949
10031
|
}
|
|
9950
10032
|
var CACHE_DIR = getCacheDir2();
|
|
9951
|
-
var VERSION_FILE =
|
|
9952
|
-
var INSTALLED_PACKAGE_JSON =
|
|
10033
|
+
var VERSION_FILE = path4.join(CACHE_DIR, "version");
|
|
10034
|
+
var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
9953
10035
|
function getUserConfigDir2() {
|
|
9954
10036
|
if (process.platform === "win32") {
|
|
9955
|
-
return process.env.APPDATA ??
|
|
10037
|
+
return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
|
|
9956
10038
|
}
|
|
9957
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
10039
|
+
return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
|
|
9958
10040
|
}
|
|
9959
10041
|
var USER_CONFIG_DIR = getUserConfigDir2();
|
|
9960
|
-
var USER_OPENCODE_CONFIG =
|
|
9961
|
-
var USER_OPENCODE_CONFIG_JSONC =
|
|
10042
|
+
var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
|
|
10043
|
+
var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
|
|
9962
10044
|
|
|
9963
10045
|
// src/hooks/auto-update-checker/checker.ts
|
|
9964
10046
|
function stripJsonComments(json) {
|
|
@@ -9966,8 +10048,8 @@ function stripJsonComments(json) {
|
|
|
9966
10048
|
}
|
|
9967
10049
|
function getConfigPaths(directory) {
|
|
9968
10050
|
return [
|
|
9969
|
-
|
|
9970
|
-
|
|
10051
|
+
path5.join(directory, ".opencode", "opencode.json"),
|
|
10052
|
+
path5.join(directory, ".opencode", "opencode.jsonc"),
|
|
9971
10053
|
USER_OPENCODE_CONFIG,
|
|
9972
10054
|
USER_OPENCODE_CONFIG_JSONC
|
|
9973
10055
|
];
|
|
@@ -9998,9 +10080,9 @@ function getLocalDevPath(directory) {
|
|
|
9998
10080
|
function findPackageJsonUp(startPath) {
|
|
9999
10081
|
try {
|
|
10000
10082
|
const stat = fs5.statSync(startPath);
|
|
10001
|
-
let dir = stat.isDirectory() ? startPath :
|
|
10083
|
+
let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
|
|
10002
10084
|
for (let i = 0;i < 10; i++) {
|
|
10003
|
-
const pkgPath =
|
|
10085
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
10004
10086
|
if (fs5.existsSync(pkgPath)) {
|
|
10005
10087
|
try {
|
|
10006
10088
|
const content = fs5.readFileSync(pkgPath, "utf-8");
|
|
@@ -10009,7 +10091,7 @@ function findPackageJsonUp(startPath) {
|
|
|
10009
10091
|
return pkgPath;
|
|
10010
10092
|
} catch {}
|
|
10011
10093
|
}
|
|
10012
|
-
const parent =
|
|
10094
|
+
const parent = path5.dirname(dir);
|
|
10013
10095
|
if (parent === dir)
|
|
10014
10096
|
break;
|
|
10015
10097
|
dir = parent;
|
|
@@ -10066,7 +10148,7 @@ function getCachedVersion() {
|
|
|
10066
10148
|
}
|
|
10067
10149
|
} catch {}
|
|
10068
10150
|
try {
|
|
10069
|
-
const currentDir =
|
|
10151
|
+
const currentDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
10070
10152
|
const pkgPath = findPackageJsonUp(currentDir);
|
|
10071
10153
|
if (pkgPath) {
|
|
10072
10154
|
const content = fs5.readFileSync(pkgPath, "utf-8");
|
|
@@ -10142,12 +10224,12 @@ async function getLatestVersion() {
|
|
|
10142
10224
|
|
|
10143
10225
|
// src/hooks/auto-update-checker/cache.ts
|
|
10144
10226
|
import * as fs6 from "fs";
|
|
10145
|
-
import * as
|
|
10227
|
+
import * as path6 from "path";
|
|
10146
10228
|
function stripTrailingCommas(json) {
|
|
10147
10229
|
return json.replace(/,(\s*[}\]])/g, "$1");
|
|
10148
10230
|
}
|
|
10149
10231
|
function removeFromBunLock(packageName) {
|
|
10150
|
-
const lockPath =
|
|
10232
|
+
const lockPath = path6.join(CACHE_DIR, "bun.lock");
|
|
10151
10233
|
if (!fs6.existsSync(lockPath))
|
|
10152
10234
|
return false;
|
|
10153
10235
|
try {
|
|
@@ -10173,8 +10255,8 @@ function removeFromBunLock(packageName) {
|
|
|
10173
10255
|
}
|
|
10174
10256
|
function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
10175
10257
|
try {
|
|
10176
|
-
const pkgDir =
|
|
10177
|
-
const pkgJsonPath =
|
|
10258
|
+
const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
|
|
10259
|
+
const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
|
|
10178
10260
|
let packageRemoved = false;
|
|
10179
10261
|
let dependencyRemoved = false;
|
|
10180
10262
|
let lockRemoved = false;
|
|
@@ -10372,7 +10454,7 @@ import { join as join37 } from "path";
|
|
|
10372
10454
|
|
|
10373
10455
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
10374
10456
|
import { join as join36 } from "path";
|
|
10375
|
-
var OPENCODE_STORAGE7 =
|
|
10457
|
+
var OPENCODE_STORAGE7 = getOpenCodeStorageDir();
|
|
10376
10458
|
var AGENT_USAGE_REMINDER_STORAGE = join36(OPENCODE_STORAGE7, "agent-usage-reminder");
|
|
10377
10459
|
var TARGET_TOOLS = new Set([
|
|
10378
10460
|
"grep",
|
|
@@ -10756,7 +10838,7 @@ import { join as join39 } from "path";
|
|
|
10756
10838
|
|
|
10757
10839
|
// src/hooks/interactive-bash-session/constants.ts
|
|
10758
10840
|
import { join as join38 } from "path";
|
|
10759
|
-
var OPENCODE_STORAGE8 =
|
|
10841
|
+
var OPENCODE_STORAGE8 = getOpenCodeStorageDir();
|
|
10760
10842
|
var INTERACTIVE_BASH_SESSION_STORAGE = join38(OPENCODE_STORAGE8, "interactive-bash-session");
|
|
10761
10843
|
var OMO_SESSION_PREFIX = "omo-";
|
|
10762
10844
|
function buildSessionReminderMessage(sessions) {
|
|
@@ -10979,7 +11061,7 @@ function createInteractiveBashSessionHook(_ctx) {
|
|
|
10979
11061
|
};
|
|
10980
11062
|
}
|
|
10981
11063
|
// src/hooks/empty-message-sanitizer/index.ts
|
|
10982
|
-
var
|
|
11064
|
+
var PLACEHOLDER_TEXT2 = "[user interrupted]";
|
|
10983
11065
|
function hasTextContent(part) {
|
|
10984
11066
|
if (part.type === "text") {
|
|
10985
11067
|
const text = part.text;
|
|
@@ -11008,7 +11090,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11008
11090
|
if (part.type === "text") {
|
|
11009
11091
|
const textPart = part;
|
|
11010
11092
|
if (!textPart.text || !textPart.text.trim()) {
|
|
11011
|
-
textPart.text =
|
|
11093
|
+
textPart.text = PLACEHOLDER_TEXT2;
|
|
11012
11094
|
textPart.synthetic = true;
|
|
11013
11095
|
injected = true;
|
|
11014
11096
|
break;
|
|
@@ -11022,7 +11104,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11022
11104
|
messageID: message.info.id,
|
|
11023
11105
|
sessionID: message.info.sessionID ?? "",
|
|
11024
11106
|
type: "text",
|
|
11025
|
-
text:
|
|
11107
|
+
text: PLACEHOLDER_TEXT2,
|
|
11026
11108
|
synthetic: true
|
|
11027
11109
|
};
|
|
11028
11110
|
if (insertIndex === -1) {
|
|
@@ -11036,7 +11118,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11036
11118
|
if (part.type === "text") {
|
|
11037
11119
|
const textPart = part;
|
|
11038
11120
|
if (textPart.text !== undefined && textPart.text.trim() === "") {
|
|
11039
|
-
textPart.text =
|
|
11121
|
+
textPart.text = PLACEHOLDER_TEXT2;
|
|
11040
11122
|
textPart.synthetic = true;
|
|
11041
11123
|
}
|
|
11042
11124
|
}
|
|
@@ -11055,12 +11137,12 @@ function isExtendedThinkingModel(modelID) {
|
|
|
11055
11137
|
}
|
|
11056
11138
|
return lower.includes("claude-sonnet-4") || lower.includes("claude-opus-4") || lower.includes("claude-3");
|
|
11057
11139
|
}
|
|
11058
|
-
function
|
|
11140
|
+
function hasContentParts(parts) {
|
|
11059
11141
|
if (!parts || parts.length === 0)
|
|
11060
11142
|
return false;
|
|
11061
11143
|
return parts.some((part) => {
|
|
11062
11144
|
const type = part.type;
|
|
11063
|
-
return type === "tool" || type === "tool_use";
|
|
11145
|
+
return type === "tool" || type === "tool_use" || type === "text";
|
|
11064
11146
|
});
|
|
11065
11147
|
}
|
|
11066
11148
|
function startsWithThinkingBlock(parts) {
|
|
@@ -11119,7 +11201,7 @@ function createThinkingBlockValidatorHook() {
|
|
|
11119
11201
|
const msg = messages[i];
|
|
11120
11202
|
if (msg.info.role !== "assistant")
|
|
11121
11203
|
continue;
|
|
11122
|
-
if (
|
|
11204
|
+
if (hasContentParts(msg.parts) && !startsWithThinkingBlock(msg.parts)) {
|
|
11123
11205
|
const previousThinking = findPreviousThinkingContent(messages, i);
|
|
11124
11206
|
const thinkingContent = previousThinking || "[Continuing from previous reasoning]";
|
|
11125
11207
|
prependThinkingBlock(msg, thinkingContent);
|
|
@@ -11392,10 +11474,66 @@ function startCallbackServer(timeoutMs = 5 * 60 * 1000) {
|
|
|
11392
11474
|
};
|
|
11393
11475
|
}
|
|
11394
11476
|
// src/auth/antigravity/token.ts
|
|
11477
|
+
class AntigravityTokenRefreshError extends Error {
|
|
11478
|
+
code;
|
|
11479
|
+
description;
|
|
11480
|
+
status;
|
|
11481
|
+
statusText;
|
|
11482
|
+
responseBody;
|
|
11483
|
+
constructor(options) {
|
|
11484
|
+
super(options.message);
|
|
11485
|
+
this.name = "AntigravityTokenRefreshError";
|
|
11486
|
+
this.code = options.code;
|
|
11487
|
+
this.description = options.description;
|
|
11488
|
+
this.status = options.status;
|
|
11489
|
+
this.statusText = options.statusText;
|
|
11490
|
+
this.responseBody = options.responseBody;
|
|
11491
|
+
}
|
|
11492
|
+
get isInvalidGrant() {
|
|
11493
|
+
return this.code === "invalid_grant";
|
|
11494
|
+
}
|
|
11495
|
+
get isNetworkError() {
|
|
11496
|
+
return this.status === 0;
|
|
11497
|
+
}
|
|
11498
|
+
}
|
|
11499
|
+
function parseOAuthErrorPayload(text) {
|
|
11500
|
+
if (!text) {
|
|
11501
|
+
return {};
|
|
11502
|
+
}
|
|
11503
|
+
try {
|
|
11504
|
+
const payload = JSON.parse(text);
|
|
11505
|
+
let code;
|
|
11506
|
+
if (typeof payload.error === "string") {
|
|
11507
|
+
code = payload.error;
|
|
11508
|
+
} else if (payload.error && typeof payload.error === "object") {
|
|
11509
|
+
code = payload.error.status ?? payload.error.code;
|
|
11510
|
+
}
|
|
11511
|
+
return {
|
|
11512
|
+
code,
|
|
11513
|
+
description: payload.error_description
|
|
11514
|
+
};
|
|
11515
|
+
} catch {
|
|
11516
|
+
return { description: text };
|
|
11517
|
+
}
|
|
11518
|
+
}
|
|
11395
11519
|
function isTokenExpired(tokens) {
|
|
11396
11520
|
const expirationTime = tokens.timestamp + tokens.expires_in * 1000;
|
|
11397
11521
|
return Date.now() >= expirationTime - ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS;
|
|
11398
11522
|
}
|
|
11523
|
+
var MAX_REFRESH_RETRIES = 3;
|
|
11524
|
+
var INITIAL_RETRY_DELAY_MS = 1000;
|
|
11525
|
+
function calculateRetryDelay(attempt) {
|
|
11526
|
+
return Math.min(INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt), 1e4);
|
|
11527
|
+
}
|
|
11528
|
+
function isRetryableError(status) {
|
|
11529
|
+
if (status === 0)
|
|
11530
|
+
return true;
|
|
11531
|
+
if (status === 429)
|
|
11532
|
+
return true;
|
|
11533
|
+
if (status >= 500 && status < 600)
|
|
11534
|
+
return true;
|
|
11535
|
+
return false;
|
|
11536
|
+
}
|
|
11399
11537
|
async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID, clientSecret = ANTIGRAVITY_CLIENT_SECRET) {
|
|
11400
11538
|
const params = new URLSearchParams({
|
|
11401
11539
|
grant_type: "refresh_token",
|
|
@@ -11403,24 +11541,67 @@ async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID
|
|
|
11403
11541
|
client_id: clientId,
|
|
11404
11542
|
client_secret: clientSecret
|
|
11405
11543
|
});
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11544
|
+
let lastError;
|
|
11545
|
+
for (let attempt = 0;attempt <= MAX_REFRESH_RETRIES; attempt++) {
|
|
11546
|
+
try {
|
|
11547
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
11548
|
+
method: "POST",
|
|
11549
|
+
headers: {
|
|
11550
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
11551
|
+
},
|
|
11552
|
+
body: params
|
|
11553
|
+
});
|
|
11554
|
+
if (response.ok) {
|
|
11555
|
+
const data = await response.json();
|
|
11556
|
+
return {
|
|
11557
|
+
access_token: data.access_token,
|
|
11558
|
+
refresh_token: data.refresh_token || refreshToken,
|
|
11559
|
+
expires_in: data.expires_in,
|
|
11560
|
+
token_type: data.token_type
|
|
11561
|
+
};
|
|
11562
|
+
}
|
|
11563
|
+
const responseBody = await response.text().catch(() => {
|
|
11564
|
+
return;
|
|
11565
|
+
});
|
|
11566
|
+
const parsed = parseOAuthErrorPayload(responseBody);
|
|
11567
|
+
lastError = new AntigravityTokenRefreshError({
|
|
11568
|
+
message: parsed.description || `Token refresh failed: ${response.status} ${response.statusText}`,
|
|
11569
|
+
code: parsed.code,
|
|
11570
|
+
description: parsed.description,
|
|
11571
|
+
status: response.status,
|
|
11572
|
+
statusText: response.statusText,
|
|
11573
|
+
responseBody
|
|
11574
|
+
});
|
|
11575
|
+
if (parsed.code === "invalid_grant") {
|
|
11576
|
+
throw lastError;
|
|
11577
|
+
}
|
|
11578
|
+
if (!isRetryableError(response.status)) {
|
|
11579
|
+
throw lastError;
|
|
11580
|
+
}
|
|
11581
|
+
if (attempt < MAX_REFRESH_RETRIES) {
|
|
11582
|
+
const delay = calculateRetryDelay(attempt);
|
|
11583
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
11584
|
+
}
|
|
11585
|
+
} catch (error) {
|
|
11586
|
+
if (error instanceof AntigravityTokenRefreshError) {
|
|
11587
|
+
throw error;
|
|
11588
|
+
}
|
|
11589
|
+
lastError = new AntigravityTokenRefreshError({
|
|
11590
|
+
message: error instanceof Error ? error.message : "Network error during token refresh",
|
|
11591
|
+
status: 0,
|
|
11592
|
+
statusText: "Network Error"
|
|
11593
|
+
});
|
|
11594
|
+
if (attempt < MAX_REFRESH_RETRIES) {
|
|
11595
|
+
const delay = calculateRetryDelay(attempt);
|
|
11596
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
11597
|
+
}
|
|
11598
|
+
}
|
|
11416
11599
|
}
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
token_type: data.token_type
|
|
11423
|
-
};
|
|
11600
|
+
throw lastError || new AntigravityTokenRefreshError({
|
|
11601
|
+
message: "Token refresh failed after all retries",
|
|
11602
|
+
status: 0,
|
|
11603
|
+
statusText: "Max Retries Exceeded"
|
|
11604
|
+
});
|
|
11424
11605
|
}
|
|
11425
11606
|
function parseStoredToken(stored) {
|
|
11426
11607
|
const parts = stored.split("|");
|
|
@@ -11645,6 +11826,10 @@ function clearProjectContextCache(accessToken) {
|
|
|
11645
11826
|
projectContextCache.clear();
|
|
11646
11827
|
}
|
|
11647
11828
|
}
|
|
11829
|
+
function invalidateProjectContextByRefreshToken(_refreshToken) {
|
|
11830
|
+
projectContextCache.clear();
|
|
11831
|
+
debugLog4(`[invalidateProjectContextByRefreshToken] Cleared all project context cache due to refresh token invalidation`);
|
|
11832
|
+
}
|
|
11648
11833
|
// src/auth/antigravity/request.ts
|
|
11649
11834
|
function buildRequestHeaders(accessToken) {
|
|
11650
11835
|
return {
|
|
@@ -12359,7 +12544,7 @@ function debugLog7(message) {
|
|
|
12359
12544
|
console.log(`[antigravity-fetch] ${message}`);
|
|
12360
12545
|
}
|
|
12361
12546
|
}
|
|
12362
|
-
function
|
|
12547
|
+
function isRetryableError2(status) {
|
|
12363
12548
|
if (status === 0)
|
|
12364
12549
|
return true;
|
|
12365
12550
|
if (status === 429)
|
|
@@ -12377,11 +12562,11 @@ var GCP_PERMISSION_ERROR_PATTERNS = [
|
|
|
12377
12562
|
function isGcpPermissionError(text) {
|
|
12378
12563
|
return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
|
|
12379
12564
|
}
|
|
12380
|
-
function
|
|
12565
|
+
function calculateRetryDelay2(attempt) {
|
|
12381
12566
|
return Math.min(200 * Math.pow(2, attempt), 2000);
|
|
12382
12567
|
}
|
|
12383
12568
|
async function isRetryableResponse(response) {
|
|
12384
|
-
if (
|
|
12569
|
+
if (isRetryableError2(response.status))
|
|
12385
12570
|
return true;
|
|
12386
12571
|
if (response.status === 403) {
|
|
12387
12572
|
try {
|
|
@@ -12460,7 +12645,7 @@ async function attemptFetch(options) {
|
|
|
12460
12645
|
const text = await response.clone().text();
|
|
12461
12646
|
if (isGcpPermissionError(text)) {
|
|
12462
12647
|
if (attempt < maxPermissionRetries) {
|
|
12463
|
-
const delay =
|
|
12648
|
+
const delay = calculateRetryDelay2(attempt);
|
|
12464
12649
|
debugLog7(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
|
|
12465
12650
|
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
12466
12651
|
continue;
|
|
@@ -12581,6 +12766,14 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
|
|
|
12581
12766
|
});
|
|
12582
12767
|
debugLog7("Token refreshed successfully");
|
|
12583
12768
|
} catch (error) {
|
|
12769
|
+
if (error instanceof AntigravityTokenRefreshError) {
|
|
12770
|
+
if (error.isInvalidGrant) {
|
|
12771
|
+
debugLog7(`[REFRESH] Token revoked (invalid_grant), clearing caches`);
|
|
12772
|
+
invalidateProjectContextByRefreshToken(refreshParts.refreshToken);
|
|
12773
|
+
clearProjectContextCache();
|
|
12774
|
+
}
|
|
12775
|
+
throw new Error(`Antigravity: Token refresh failed: ${error.description || error.message}${error.code ? ` (${error.code})` : ""}`);
|
|
12776
|
+
}
|
|
12584
12777
|
throw new Error(`Antigravity: Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
12585
12778
|
}
|
|
12586
12779
|
}
|
|
@@ -12662,10 +12855,29 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
|
|
|
12662
12855
|
debugLog7("[401] Token refreshed, retrying request...");
|
|
12663
12856
|
return executeWithEndpoints();
|
|
12664
12857
|
} catch (refreshError) {
|
|
12858
|
+
if (refreshError instanceof AntigravityTokenRefreshError) {
|
|
12859
|
+
if (refreshError.isInvalidGrant) {
|
|
12860
|
+
debugLog7(`[401] Token revoked (invalid_grant), clearing caches`);
|
|
12861
|
+
invalidateProjectContextByRefreshToken(refreshParts.refreshToken);
|
|
12862
|
+
clearProjectContextCache();
|
|
12863
|
+
}
|
|
12864
|
+
debugLog7(`[401] Token refresh failed: ${refreshError.description || refreshError.message}`);
|
|
12865
|
+
return new Response(JSON.stringify({
|
|
12866
|
+
error: {
|
|
12867
|
+
message: refreshError.description || refreshError.message,
|
|
12868
|
+
type: refreshError.isInvalidGrant ? "token_revoked" : "unauthorized",
|
|
12869
|
+
code: refreshError.code || "token_refresh_failed"
|
|
12870
|
+
}
|
|
12871
|
+
}), {
|
|
12872
|
+
status: 401,
|
|
12873
|
+
statusText: "Unauthorized",
|
|
12874
|
+
headers: { "Content-Type": "application/json" }
|
|
12875
|
+
});
|
|
12876
|
+
}
|
|
12665
12877
|
debugLog7(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
|
|
12666
12878
|
return new Response(JSON.stringify({
|
|
12667
12879
|
error: {
|
|
12668
|
-
message:
|
|
12880
|
+
message: refreshError instanceof Error ? refreshError.message : "Unknown error",
|
|
12669
12881
|
type: "unauthorized",
|
|
12670
12882
|
code: "token_refresh_failed"
|
|
12671
12883
|
}
|
|
@@ -12892,8 +13104,8 @@ function loadProjectCommands() {
|
|
|
12892
13104
|
return commandsToRecord(commands);
|
|
12893
13105
|
}
|
|
12894
13106
|
function loadOpencodeGlobalCommands() {
|
|
12895
|
-
const { homedir:
|
|
12896
|
-
const opencodeCommandsDir = join40(
|
|
13107
|
+
const { homedir: homedir9 } = __require("os");
|
|
13108
|
+
const opencodeCommandsDir = join40(homedir9(), ".config", "opencode", "command");
|
|
12897
13109
|
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
12898
13110
|
return commandsToRecord(commands);
|
|
12899
13111
|
}
|
|
@@ -12902,6 +13114,334 @@ function loadOpencodeProjectCommands() {
|
|
|
12902
13114
|
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
12903
13115
|
return commandsToRecord(commands);
|
|
12904
13116
|
}
|
|
13117
|
+
// src/features/builtin-commands/templates/init-deep.ts
|
|
13118
|
+
var INIT_DEEP_TEMPLATE = `# Initialize Deep Knowledge Base
|
|
13119
|
+
|
|
13120
|
+
Generate comprehensive AGENTS.md files across project hierarchy. Combines root-level project knowledge (gen-knowledge) with complexity-based subdirectory documentation (gen-knowledge-deep).
|
|
13121
|
+
|
|
13122
|
+
## Usage
|
|
13123
|
+
|
|
13124
|
+
\`\`\`
|
|
13125
|
+
/init-deep # Analyze and generate hierarchical AGENTS.md
|
|
13126
|
+
/init-deep --create-new # Force create from scratch (ignore existing)
|
|
13127
|
+
/init-deep --max-depth=2 # Limit to N directory levels (default: 3)
|
|
13128
|
+
\`\`\`
|
|
13129
|
+
|
|
13130
|
+
---
|
|
13131
|
+
|
|
13132
|
+
## Core Principles
|
|
13133
|
+
|
|
13134
|
+
- **Telegraphic Style**: Sacrifice grammar for concision ("Project uses React" \u2192 "React 18")
|
|
13135
|
+
- **Predict-then-Compare**: Predict standard \u2192 find actual \u2192 document ONLY deviations
|
|
13136
|
+
- **Hierarchy Aware**: Parent covers general, children cover specific
|
|
13137
|
+
- **No Redundancy**: Child AGENTS.md NEVER repeats parent content
|
|
13138
|
+
|
|
13139
|
+
---
|
|
13140
|
+
|
|
13141
|
+
## Process
|
|
13142
|
+
|
|
13143
|
+
<critical>
|
|
13144
|
+
**MANDATORY: TodoWrite for ALL phases. Mark in_progress \u2192 completed in real-time.**
|
|
13145
|
+
</critical>
|
|
13146
|
+
|
|
13147
|
+
### Phase 0: Initialize
|
|
13148
|
+
|
|
13149
|
+
\`\`\`
|
|
13150
|
+
TodoWrite([
|
|
13151
|
+
{ id: "p1-analysis", content: "Parallel project structure & complexity analysis", status: "pending", priority: "high" },
|
|
13152
|
+
{ id: "p2-scoring", content: "Score directories, determine AGENTS.md locations", status: "pending", priority: "high" },
|
|
13153
|
+
{ id: "p3-root", content: "Generate root AGENTS.md with Predict-then-Compare", status: "pending", priority: "high" },
|
|
13154
|
+
{ id: "p4-subdirs", content: "Generate subdirectory AGENTS.md files in parallel", status: "pending", priority: "high" },
|
|
13155
|
+
{ id: "p5-review", content: "Review, deduplicate, validate all files", status: "pending", priority: "medium" }
|
|
13156
|
+
])
|
|
13157
|
+
\`\`\`
|
|
13158
|
+
|
|
13159
|
+
---
|
|
13160
|
+
|
|
13161
|
+
## Phase 1: Parallel Project Analysis
|
|
13162
|
+
|
|
13163
|
+
**Mark "p1-analysis" as in_progress.**
|
|
13164
|
+
|
|
13165
|
+
Launch **ALL tasks simultaneously**:
|
|
13166
|
+
|
|
13167
|
+
<parallel-tasks>
|
|
13168
|
+
|
|
13169
|
+
### Structural Analysis (bash - run in parallel)
|
|
13170
|
+
\`\`\`bash
|
|
13171
|
+
# Task A: Directory depth analysis
|
|
13172
|
+
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
|
|
13173
|
+
|
|
13174
|
+
# Task B: File count per directory
|
|
13175
|
+
find . -type f -not -path '*/\\.*' -not -path '*/node_modules/*' -not -path '*/venv/*' -not -path '*/__pycache__/*' | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -30
|
|
13176
|
+
|
|
13177
|
+
# Task C: Code concentration
|
|
13178
|
+
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
|
|
13179
|
+
|
|
13180
|
+
# Task D: Existing knowledge files
|
|
13181
|
+
find . -type f \\( -name "AGENTS.md" -o -name "CLAUDE.md" \\) -not -path '*/node_modules/*' 2>/dev/null
|
|
13182
|
+
\`\`\`
|
|
13183
|
+
|
|
13184
|
+
### Context Gathering (Explore agents - background_task in parallel)
|
|
13185
|
+
|
|
13186
|
+
\`\`\`
|
|
13187
|
+
background_task(agent="explore", prompt="Project structure: PREDICT standard {lang} patterns \u2192 FIND package.json/pyproject.toml/go.mod \u2192 REPORT deviations only")
|
|
13188
|
+
|
|
13189
|
+
background_task(agent="explore", prompt="Entry points: PREDICT typical (main.py, index.ts) \u2192 FIND actual \u2192 REPORT non-standard organization")
|
|
13190
|
+
|
|
13191
|
+
background_task(agent="explore", prompt="Conventions: FIND .cursor/rules, .cursorrules, eslintrc, pyproject.toml \u2192 REPORT project-specific rules DIFFERENT from defaults")
|
|
13192
|
+
|
|
13193
|
+
background_task(agent="explore", prompt="Anti-patterns: FIND comments with 'DO NOT', 'NEVER', 'ALWAYS', 'LEGACY', 'DEPRECATED' \u2192 REPORT forbidden patterns")
|
|
13194
|
+
|
|
13195
|
+
background_task(agent="explore", prompt="Build/CI: FIND .github/workflows, Makefile, justfile \u2192 REPORT non-standard build/deploy patterns")
|
|
13196
|
+
|
|
13197
|
+
background_task(agent="explore", prompt="Test patterns: FIND pytest.ini, jest.config, test structure \u2192 REPORT unique testing conventions")
|
|
13198
|
+
\`\`\`
|
|
13199
|
+
|
|
13200
|
+
</parallel-tasks>
|
|
13201
|
+
|
|
13202
|
+
**Collect all results. Mark "p1-analysis" as completed.**
|
|
13203
|
+
|
|
13204
|
+
---
|
|
13205
|
+
|
|
13206
|
+
## Phase 2: Complexity Scoring & Location Decision
|
|
13207
|
+
|
|
13208
|
+
**Mark "p2-scoring" as in_progress.**
|
|
13209
|
+
|
|
13210
|
+
### Scoring Matrix
|
|
13211
|
+
|
|
13212
|
+
| Factor | Weight | Threshold |
|
|
13213
|
+
|--------|--------|-----------|
|
|
13214
|
+
| File count | 3x | >20 files = high |
|
|
13215
|
+
| Subdirectory count | 2x | >5 subdirs = high |
|
|
13216
|
+
| Code file ratio | 2x | >70% code = high |
|
|
13217
|
+
| Unique patterns | 1x | Has own config |
|
|
13218
|
+
| Module boundary | 2x | Has __init__.py/index.ts |
|
|
13219
|
+
|
|
13220
|
+
### Decision Rules
|
|
13221
|
+
|
|
13222
|
+
| Score | Action |
|
|
13223
|
+
|-------|--------|
|
|
13224
|
+
| **Root (.)** | ALWAYS create AGENTS.md |
|
|
13225
|
+
| **High (>15)** | Create dedicated AGENTS.md |
|
|
13226
|
+
| **Medium (8-15)** | Create if distinct domain |
|
|
13227
|
+
| **Low (<8)** | Skip, parent sufficient |
|
|
13228
|
+
|
|
13229
|
+
### Output Format
|
|
13230
|
+
|
|
13231
|
+
\`\`\`
|
|
13232
|
+
AGENTS_LOCATIONS = [
|
|
13233
|
+
{ path: ".", type: "root" },
|
|
13234
|
+
{ path: "src/api", score: 18, reason: "high complexity, 45 files" },
|
|
13235
|
+
{ path: "src/hooks", score: 12, reason: "distinct domain, unique patterns" },
|
|
13236
|
+
]
|
|
13237
|
+
\`\`\`
|
|
13238
|
+
|
|
13239
|
+
**Mark "p2-scoring" as completed.**
|
|
13240
|
+
|
|
13241
|
+
---
|
|
13242
|
+
|
|
13243
|
+
## Phase 3: Generate Root AGENTS.md
|
|
13244
|
+
|
|
13245
|
+
**Mark "p3-root" as in_progress.**
|
|
13246
|
+
|
|
13247
|
+
Root AGENTS.md gets **full treatment** with Predict-then-Compare synthesis.
|
|
13248
|
+
|
|
13249
|
+
### Required Sections
|
|
13250
|
+
|
|
13251
|
+
\`\`\`markdown
|
|
13252
|
+
# PROJECT KNOWLEDGE BASE
|
|
13253
|
+
|
|
13254
|
+
**Generated:** {TIMESTAMP}
|
|
13255
|
+
**Commit:** {SHORT_SHA}
|
|
13256
|
+
**Branch:** {BRANCH}
|
|
13257
|
+
|
|
13258
|
+
## OVERVIEW
|
|
13259
|
+
|
|
13260
|
+
{1-2 sentences: what project does, core tech stack}
|
|
13261
|
+
|
|
13262
|
+
## STRUCTURE
|
|
13263
|
+
|
|
13264
|
+
\\\`\\\`\\\`
|
|
13265
|
+
{project-root}/
|
|
13266
|
+
\u251C\u2500\u2500 {dir}/ # {non-obvious purpose only}
|
|
13267
|
+
\u2514\u2500\u2500 {entry} # entry point
|
|
13268
|
+
\\\`\\\`\\\`
|
|
13269
|
+
|
|
13270
|
+
## WHERE TO LOOK
|
|
13271
|
+
|
|
13272
|
+
| Task | Location | Notes |
|
|
13273
|
+
|------|----------|-------|
|
|
13274
|
+
| Add feature X | \\\`src/x/\\\` | {pattern hint} |
|
|
13275
|
+
|
|
13276
|
+
## CONVENTIONS
|
|
13277
|
+
|
|
13278
|
+
{ONLY deviations from standard - skip generic advice}
|
|
13279
|
+
|
|
13280
|
+
- **{rule}**: {specific detail}
|
|
13281
|
+
|
|
13282
|
+
## ANTI-PATTERNS (THIS PROJECT)
|
|
13283
|
+
|
|
13284
|
+
{Things explicitly forbidden HERE}
|
|
13285
|
+
|
|
13286
|
+
- **{pattern}**: {why} \u2192 {alternative}
|
|
13287
|
+
|
|
13288
|
+
## UNIQUE STYLES
|
|
13289
|
+
|
|
13290
|
+
{Project-specific coding styles}
|
|
13291
|
+
|
|
13292
|
+
- **{style}**: {how different}
|
|
13293
|
+
|
|
13294
|
+
## COMMANDS
|
|
13295
|
+
|
|
13296
|
+
\\\`\\\`\\\`bash
|
|
13297
|
+
{dev-command}
|
|
13298
|
+
{test-command}
|
|
13299
|
+
{build-command}
|
|
13300
|
+
\\\`\\\`\\\`
|
|
13301
|
+
|
|
13302
|
+
## NOTES
|
|
13303
|
+
|
|
13304
|
+
{Gotchas, non-obvious info}
|
|
13305
|
+
\`\`\`
|
|
13306
|
+
|
|
13307
|
+
### Quality Gates
|
|
13308
|
+
|
|
13309
|
+
- [ ] Size: 50-150 lines
|
|
13310
|
+
- [ ] No generic advice ("write clean code")
|
|
13311
|
+
- [ ] No obvious info ("tests/ has tests")
|
|
13312
|
+
- [ ] Every item is project-specific
|
|
13313
|
+
|
|
13314
|
+
**Mark "p3-root" as completed.**
|
|
13315
|
+
|
|
13316
|
+
---
|
|
13317
|
+
|
|
13318
|
+
## Phase 4: Generate Subdirectory AGENTS.md
|
|
13319
|
+
|
|
13320
|
+
**Mark "p4-subdirs" as in_progress.**
|
|
13321
|
+
|
|
13322
|
+
For each location in AGENTS_LOCATIONS (except root), launch **parallel document-writer agents**:
|
|
13323
|
+
|
|
13324
|
+
\`\`\`typescript
|
|
13325
|
+
for (const loc of AGENTS_LOCATIONS.filter(l => l.path !== ".")) {
|
|
13326
|
+
background_task({
|
|
13327
|
+
agent: "document-writer",
|
|
13328
|
+
prompt: \\\`
|
|
13329
|
+
Generate AGENTS.md for: \${loc.path}
|
|
13330
|
+
|
|
13331
|
+
CONTEXT:
|
|
13332
|
+
- Complexity reason: \${loc.reason}
|
|
13333
|
+
- Parent AGENTS.md: ./AGENTS.md (already covers project overview)
|
|
13334
|
+
|
|
13335
|
+
CRITICAL RULES:
|
|
13336
|
+
1. Focus ONLY on this directory's specific context
|
|
13337
|
+
2. NEVER repeat parent AGENTS.md content
|
|
13338
|
+
3. Shorter is better - 30-80 lines max
|
|
13339
|
+
4. Telegraphic style - sacrifice grammar
|
|
13340
|
+
|
|
13341
|
+
REQUIRED SECTIONS:
|
|
13342
|
+
- OVERVIEW (1 line: what this directory does)
|
|
13343
|
+
- STRUCTURE (only if >5 subdirs)
|
|
13344
|
+
- WHERE TO LOOK (directory-specific tasks)
|
|
13345
|
+
- CONVENTIONS (only if DIFFERENT from root)
|
|
13346
|
+
- ANTI-PATTERNS (directory-specific only)
|
|
13347
|
+
|
|
13348
|
+
OUTPUT: Write to \${loc.path}/AGENTS.md
|
|
13349
|
+
\\\`
|
|
13350
|
+
})
|
|
13351
|
+
}
|
|
13352
|
+
\`\`\`
|
|
13353
|
+
|
|
13354
|
+
**Wait for all agents. Mark "p4-subdirs" as completed.**
|
|
13355
|
+
|
|
13356
|
+
---
|
|
13357
|
+
|
|
13358
|
+
## Phase 5: Review & Deduplicate
|
|
13359
|
+
|
|
13360
|
+
**Mark "p5-review" as in_progress.**
|
|
13361
|
+
|
|
13362
|
+
### Validation Checklist
|
|
13363
|
+
|
|
13364
|
+
For EACH generated AGENTS.md:
|
|
13365
|
+
|
|
13366
|
+
| Check | Action if Fail |
|
|
13367
|
+
|-------|----------------|
|
|
13368
|
+
| Contains generic advice | REMOVE the line |
|
|
13369
|
+
| Repeats parent content | REMOVE the line |
|
|
13370
|
+
| Missing required section | ADD it |
|
|
13371
|
+
| Over 150 lines (root) / 80 lines (subdir) | TRIM |
|
|
13372
|
+
| Verbose explanations | REWRITE telegraphic |
|
|
13373
|
+
|
|
13374
|
+
### Cross-Reference Validation
|
|
13375
|
+
|
|
13376
|
+
\`\`\`
|
|
13377
|
+
For each child AGENTS.md:
|
|
13378
|
+
For each line in child:
|
|
13379
|
+
If similar line exists in parent:
|
|
13380
|
+
REMOVE from child (parent already covers)
|
|
13381
|
+
\`\`\`
|
|
13382
|
+
|
|
13383
|
+
**Mark "p5-review" as completed.**
|
|
13384
|
+
|
|
13385
|
+
---
|
|
13386
|
+
|
|
13387
|
+
## Final Report
|
|
13388
|
+
|
|
13389
|
+
\`\`\`
|
|
13390
|
+
=== init-deep Complete ===
|
|
13391
|
+
|
|
13392
|
+
Files Generated:
|
|
13393
|
+
\u2713 ./AGENTS.md (root, {N} lines)
|
|
13394
|
+
\u2713 ./src/hooks/AGENTS.md ({N} lines)
|
|
13395
|
+
\u2713 ./src/tools/AGENTS.md ({N} lines)
|
|
13396
|
+
|
|
13397
|
+
Directories Analyzed: {N}
|
|
13398
|
+
AGENTS.md Created: {N}
|
|
13399
|
+
Total Lines: {N}
|
|
13400
|
+
|
|
13401
|
+
Hierarchy:
|
|
13402
|
+
./AGENTS.md
|
|
13403
|
+
\u251C\u2500\u2500 src/hooks/AGENTS.md
|
|
13404
|
+
\u2514\u2500\u2500 src/tools/AGENTS.md
|
|
13405
|
+
\`\`\`
|
|
13406
|
+
|
|
13407
|
+
---
|
|
13408
|
+
|
|
13409
|
+
## Anti-Patterns for THIS Command
|
|
13410
|
+
|
|
13411
|
+
- **Over-documenting**: Not every directory needs AGENTS.md
|
|
13412
|
+
- **Redundancy**: Child must NOT repeat parent
|
|
13413
|
+
- **Generic content**: Remove anything that applies to ALL projects
|
|
13414
|
+
- **Sequential execution**: MUST use parallel agents
|
|
13415
|
+
- **Deep nesting**: Rarely need AGENTS.md at depth 4+
|
|
13416
|
+
- **Verbose style**: "This directory contains..." \u2192 just list it`;
|
|
13417
|
+
|
|
13418
|
+
// src/features/builtin-commands/commands.ts
|
|
13419
|
+
var BUILTIN_COMMAND_DEFINITIONS = {
|
|
13420
|
+
"init-deep": {
|
|
13421
|
+
description: "(builtin) Initialize hierarchical AGENTS.md knowledge base",
|
|
13422
|
+
template: `<command-instruction>
|
|
13423
|
+
${INIT_DEEP_TEMPLATE}
|
|
13424
|
+
</command-instruction>
|
|
13425
|
+
|
|
13426
|
+
<user-request>
|
|
13427
|
+
$ARGUMENTS
|
|
13428
|
+
</user-request>`,
|
|
13429
|
+
argumentHint: "[--create-new] [--max-depth=N]"
|
|
13430
|
+
}
|
|
13431
|
+
};
|
|
13432
|
+
function loadBuiltinCommands(disabledCommands) {
|
|
13433
|
+
const disabled = new Set(disabledCommands ?? []);
|
|
13434
|
+
const commands = {};
|
|
13435
|
+
for (const [name, definition] of Object.entries(BUILTIN_COMMAND_DEFINITIONS)) {
|
|
13436
|
+
if (!disabled.has(name)) {
|
|
13437
|
+
commands[name] = {
|
|
13438
|
+
name,
|
|
13439
|
+
...definition
|
|
13440
|
+
};
|
|
13441
|
+
}
|
|
13442
|
+
}
|
|
13443
|
+
return commands;
|
|
13444
|
+
}
|
|
12905
13445
|
// src/features/claude-code-agent-loader/loader.ts
|
|
12906
13446
|
import { existsSync as existsSync31, readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
|
|
12907
13447
|
import { join as join41, basename as basename2 } from "path";
|
|
@@ -13065,13 +13605,13 @@ async function loadMcpConfigs() {
|
|
|
13065
13605
|
const servers = {};
|
|
13066
13606
|
const loadedServers = [];
|
|
13067
13607
|
const paths = getMcpConfigPaths();
|
|
13068
|
-
for (const { path:
|
|
13069
|
-
const config = await loadMcpConfigFile(
|
|
13608
|
+
for (const { path: path7, scope } of paths) {
|
|
13609
|
+
const config = await loadMcpConfigFile(path7);
|
|
13070
13610
|
if (!config?.mcpServers)
|
|
13071
13611
|
continue;
|
|
13072
13612
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
13073
13613
|
if (serverConfig.disabled) {
|
|
13074
|
-
log(`Skipping disabled MCP server "${name}"`, { path:
|
|
13614
|
+
log(`Skipping disabled MCP server "${name}"`, { path: path7 });
|
|
13075
13615
|
continue;
|
|
13076
13616
|
}
|
|
13077
13617
|
try {
|
|
@@ -13082,7 +13622,7 @@ async function loadMcpConfigs() {
|
|
|
13082
13622
|
loadedServers.splice(existingIndex, 1);
|
|
13083
13623
|
}
|
|
13084
13624
|
loadedServers.push({ name, scope, config: transformed });
|
|
13085
|
-
log(`Loaded MCP server "${name}" from ${scope}`, { path:
|
|
13625
|
+
log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
|
|
13086
13626
|
} catch (error) {
|
|
13087
13627
|
log(`Failed to transform MCP server "${name}"`, error);
|
|
13088
13628
|
}
|
|
@@ -13092,20 +13632,20 @@ async function loadMcpConfigs() {
|
|
|
13092
13632
|
}
|
|
13093
13633
|
// src/features/claude-code-plugin-loader/loader.ts
|
|
13094
13634
|
import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync22 } from "fs";
|
|
13095
|
-
import { homedir as
|
|
13635
|
+
import { homedir as homedir9 } from "os";
|
|
13096
13636
|
import { join as join43, basename as basename3 } from "path";
|
|
13097
13637
|
var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
|
|
13098
13638
|
function getPluginsBaseDir() {
|
|
13099
13639
|
if (process.env.CLAUDE_PLUGINS_HOME) {
|
|
13100
13640
|
return process.env.CLAUDE_PLUGINS_HOME;
|
|
13101
13641
|
}
|
|
13102
|
-
return join43(
|
|
13642
|
+
return join43(homedir9(), ".claude", "plugins");
|
|
13103
13643
|
}
|
|
13104
13644
|
function getInstalledPluginsPath() {
|
|
13105
13645
|
return join43(getPluginsBaseDir(), "installed_plugins.json");
|
|
13106
13646
|
}
|
|
13107
|
-
function resolvePluginPath(
|
|
13108
|
-
return
|
|
13647
|
+
function resolvePluginPath(path7, pluginRoot) {
|
|
13648
|
+
return path7.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
|
|
13109
13649
|
}
|
|
13110
13650
|
function resolvePluginPaths(obj, pluginRoot) {
|
|
13111
13651
|
if (obj === null || obj === undefined)
|
|
@@ -13142,7 +13682,7 @@ function getClaudeSettingsPath() {
|
|
|
13142
13682
|
if (process.env.CLAUDE_SETTINGS_PATH) {
|
|
13143
13683
|
return process.env.CLAUDE_SETTINGS_PATH;
|
|
13144
13684
|
}
|
|
13145
|
-
return join43(
|
|
13685
|
+
return join43(homedir9(), ".claude", "settings.json");
|
|
13146
13686
|
}
|
|
13147
13687
|
function loadClaudeSettings() {
|
|
13148
13688
|
const settingsPath = getClaudeSettingsPath();
|
|
@@ -13251,7 +13791,7 @@ function discoverInstalledPlugins(options) {
|
|
|
13251
13791
|
return { plugins, errors };
|
|
13252
13792
|
}
|
|
13253
13793
|
function loadPluginCommands(plugins) {
|
|
13254
|
-
const
|
|
13794
|
+
const commands2 = {};
|
|
13255
13795
|
for (const plugin2 of plugins) {
|
|
13256
13796
|
if (!plugin2.commandsDir || !existsSync33(plugin2.commandsDir))
|
|
13257
13797
|
continue;
|
|
@@ -13273,7 +13813,7 @@ ${body.trim()}
|
|
|
13273
13813
|
$ARGUMENTS
|
|
13274
13814
|
</user-request>`;
|
|
13275
13815
|
const formattedDescription = `(plugin: ${plugin2.name}) ${data.description || ""}`;
|
|
13276
|
-
|
|
13816
|
+
commands2[namespacedName] = {
|
|
13277
13817
|
name: namespacedName,
|
|
13278
13818
|
description: formattedDescription,
|
|
13279
13819
|
template: wrappedTemplate,
|
|
@@ -13288,7 +13828,7 @@ $ARGUMENTS
|
|
|
13288
13828
|
}
|
|
13289
13829
|
}
|
|
13290
13830
|
}
|
|
13291
|
-
return
|
|
13831
|
+
return commands2;
|
|
13292
13832
|
}
|
|
13293
13833
|
function loadPluginSkillsAsCommands(plugins) {
|
|
13294
13834
|
const skills = {};
|
|
@@ -13436,14 +13976,14 @@ function loadPluginHooksConfigs(plugins) {
|
|
|
13436
13976
|
}
|
|
13437
13977
|
async function loadAllPluginComponents(options) {
|
|
13438
13978
|
const { plugins, errors } = discoverInstalledPlugins(options);
|
|
13439
|
-
const
|
|
13979
|
+
const commands2 = loadPluginCommands(plugins);
|
|
13440
13980
|
const skills = loadPluginSkillsAsCommands(plugins);
|
|
13441
13981
|
const agents = loadPluginAgents(plugins);
|
|
13442
13982
|
const mcpServers = await loadPluginMcpServers(plugins);
|
|
13443
13983
|
const hooksConfigs = loadPluginHooksConfigs(plugins);
|
|
13444
|
-
log(`Loaded ${plugins.length} plugins with ${Object.keys(
|
|
13984
|
+
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
13985
|
return {
|
|
13446
|
-
commands,
|
|
13986
|
+
commands: commands2,
|
|
13447
13987
|
skills,
|
|
13448
13988
|
agents,
|
|
13449
13989
|
mcpServers,
|
|
@@ -13490,6 +14030,36 @@ var SEVERITY_MAP = {
|
|
|
13490
14030
|
var DEFAULT_MAX_REFERENCES = 200;
|
|
13491
14031
|
var DEFAULT_MAX_SYMBOLS = 200;
|
|
13492
14032
|
var DEFAULT_MAX_DIAGNOSTICS = 200;
|
|
14033
|
+
var LSP_INSTALL_HINTS = {
|
|
14034
|
+
typescript: "npm install -g typescript-language-server typescript",
|
|
14035
|
+
deno: "Install Deno from https://deno.land",
|
|
14036
|
+
vue: "npm install -g @vue/language-server",
|
|
14037
|
+
eslint: "npm install -g vscode-langservers-extracted",
|
|
14038
|
+
oxlint: "npm install -g oxlint",
|
|
14039
|
+
biome: "npm install -g @biomejs/biome",
|
|
14040
|
+
gopls: "go install golang.org/x/tools/gopls@latest",
|
|
14041
|
+
"ruby-lsp": "gem install ruby-lsp",
|
|
14042
|
+
basedpyright: "pip install basedpyright",
|
|
14043
|
+
pyright: "pip install pyright",
|
|
14044
|
+
ty: "pip install ty",
|
|
14045
|
+
ruff: "pip install ruff",
|
|
14046
|
+
"elixir-ls": "See https://github.com/elixir-lsp/elixir-ls",
|
|
14047
|
+
zls: "See https://github.com/zigtools/zls",
|
|
14048
|
+
csharp: "dotnet tool install -g csharp-ls",
|
|
14049
|
+
fsharp: "dotnet tool install -g fsautocomplete",
|
|
14050
|
+
"sourcekit-lsp": "Included with Xcode or Swift toolchain",
|
|
14051
|
+
rust: "rustup component add rust-analyzer",
|
|
14052
|
+
clangd: "See https://clangd.llvm.org/installation",
|
|
14053
|
+
svelte: "npm install -g svelte-language-server",
|
|
14054
|
+
astro: "npm install -g @astrojs/language-server",
|
|
14055
|
+
"bash-ls": "npm install -g bash-language-server",
|
|
14056
|
+
jdtls: "See https://github.com/eclipse-jdtls/eclipse.jdt.ls",
|
|
14057
|
+
"yaml-ls": "npm install -g yaml-language-server",
|
|
14058
|
+
"lua-ls": "See https://github.com/LuaLS/lua-language-server",
|
|
14059
|
+
php: "npm install -g intelephense",
|
|
14060
|
+
dart: "Included with Dart SDK",
|
|
14061
|
+
"terraform-ls": "See https://github.com/hashicorp/terraform-ls"
|
|
14062
|
+
};
|
|
13493
14063
|
var BUILTIN_SERVERS = {
|
|
13494
14064
|
typescript: {
|
|
13495
14065
|
command: ["typescript-language-server", "--stdio"],
|
|
@@ -13749,12 +14319,12 @@ var EXT_TO_LANG = {
|
|
|
13749
14319
|
// src/tools/lsp/config.ts
|
|
13750
14320
|
import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
|
|
13751
14321
|
import { join as join44 } from "path";
|
|
13752
|
-
import { homedir as
|
|
13753
|
-
function loadJsonFile(
|
|
13754
|
-
if (!existsSync34(
|
|
14322
|
+
import { homedir as homedir10 } from "os";
|
|
14323
|
+
function loadJsonFile(path7) {
|
|
14324
|
+
if (!existsSync34(path7))
|
|
13755
14325
|
return null;
|
|
13756
14326
|
try {
|
|
13757
|
-
return JSON.parse(readFileSync23(
|
|
14327
|
+
return JSON.parse(readFileSync23(path7, "utf-8"));
|
|
13758
14328
|
} catch {
|
|
13759
14329
|
return null;
|
|
13760
14330
|
}
|
|
@@ -13763,8 +14333,8 @@ function getConfigPaths2() {
|
|
|
13763
14333
|
const cwd = process.cwd();
|
|
13764
14334
|
return {
|
|
13765
14335
|
project: join44(cwd, ".opencode", "oh-my-opencode.json"),
|
|
13766
|
-
user: join44(
|
|
13767
|
-
opencode: join44(
|
|
14336
|
+
user: join44(homedir10(), ".config", "opencode", "oh-my-opencode.json"),
|
|
14337
|
+
opencode: join44(homedir10(), ".config", "opencode", "opencode.json")
|
|
13768
14338
|
};
|
|
13769
14339
|
}
|
|
13770
14340
|
function loadAllConfigs() {
|
|
@@ -13836,16 +14406,38 @@ function findServerForExtension(ext) {
|
|
|
13836
14406
|
for (const server of servers) {
|
|
13837
14407
|
if (server.extensions.includes(ext) && isServerInstalled(server.command)) {
|
|
13838
14408
|
return {
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
14409
|
+
status: "found",
|
|
14410
|
+
server: {
|
|
14411
|
+
id: server.id,
|
|
14412
|
+
command: server.command,
|
|
14413
|
+
extensions: server.extensions,
|
|
14414
|
+
priority: server.priority,
|
|
14415
|
+
env: server.env,
|
|
14416
|
+
initialization: server.initialization
|
|
14417
|
+
}
|
|
13845
14418
|
};
|
|
13846
14419
|
}
|
|
13847
14420
|
}
|
|
13848
|
-
|
|
14421
|
+
for (const server of servers) {
|
|
14422
|
+
if (server.extensions.includes(ext)) {
|
|
14423
|
+
const installHint = LSP_INSTALL_HINTS[server.id] || `Install '${server.command[0]}' and ensure it's in your PATH`;
|
|
14424
|
+
return {
|
|
14425
|
+
status: "not_installed",
|
|
14426
|
+
server: {
|
|
14427
|
+
id: server.id,
|
|
14428
|
+
command: server.command,
|
|
14429
|
+
extensions: server.extensions
|
|
14430
|
+
},
|
|
14431
|
+
installHint
|
|
14432
|
+
};
|
|
14433
|
+
}
|
|
14434
|
+
}
|
|
14435
|
+
const availableServers = [...new Set(servers.map((s) => s.id))];
|
|
14436
|
+
return {
|
|
14437
|
+
status: "not_configured",
|
|
14438
|
+
extension: ext,
|
|
14439
|
+
availableServers
|
|
14440
|
+
};
|
|
13849
14441
|
}
|
|
13850
14442
|
function getLanguageId(ext) {
|
|
13851
14443
|
return EXT_TO_LANG[ext] || "plaintext";
|
|
@@ -13868,10 +14460,10 @@ function isServerInstalled(command) {
|
|
|
13868
14460
|
const additionalPaths = [
|
|
13869
14461
|
join44(cwd, "node_modules", ".bin", cmd),
|
|
13870
14462
|
join44(cwd, "node_modules", ".bin", cmd + ext),
|
|
13871
|
-
join44(
|
|
13872
|
-
join44(
|
|
13873
|
-
join44(
|
|
13874
|
-
join44(
|
|
14463
|
+
join44(homedir10(), ".config", "opencode", "bin", cmd),
|
|
14464
|
+
join44(homedir10(), ".config", "opencode", "bin", cmd + ext),
|
|
14465
|
+
join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd),
|
|
14466
|
+
join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
|
|
13875
14467
|
];
|
|
13876
14468
|
for (const p of additionalPaths) {
|
|
13877
14469
|
if (existsSync34(p)) {
|
|
@@ -14486,13 +15078,49 @@ function findWorkspaceRoot(filePath) {
|
|
|
14486
15078
|
}
|
|
14487
15079
|
return __require("path").dirname(resolve6(filePath));
|
|
14488
15080
|
}
|
|
15081
|
+
function formatServerLookupError(result) {
|
|
15082
|
+
if (result.status === "not_installed") {
|
|
15083
|
+
const { server, installHint } = result;
|
|
15084
|
+
return [
|
|
15085
|
+
`LSP server '${server.id}' is configured but NOT INSTALLED.`,
|
|
15086
|
+
``,
|
|
15087
|
+
`Command not found: ${server.command[0]}`,
|
|
15088
|
+
``,
|
|
15089
|
+
`To install:`,
|
|
15090
|
+
` ${installHint}`,
|
|
15091
|
+
``,
|
|
15092
|
+
`Supported extensions: ${server.extensions.join(", ")}`,
|
|
15093
|
+
``,
|
|
15094
|
+
`After installation, the server will be available automatically.`,
|
|
15095
|
+
`Run 'lsp_servers' tool to verify installation status.`
|
|
15096
|
+
].join(`
|
|
15097
|
+
`);
|
|
15098
|
+
}
|
|
15099
|
+
return [
|
|
15100
|
+
`No LSP server configured for extension: ${result.extension}`,
|
|
15101
|
+
``,
|
|
15102
|
+
`Available servers: ${result.availableServers.slice(0, 10).join(", ")}${result.availableServers.length > 10 ? "..." : ""}`,
|
|
15103
|
+
``,
|
|
15104
|
+
`To add a custom server, configure 'lsp' in oh-my-opencode.json:`,
|
|
15105
|
+
` {`,
|
|
15106
|
+
` "lsp": {`,
|
|
15107
|
+
` "my-server": {`,
|
|
15108
|
+
` "command": ["my-lsp", "--stdio"],`,
|
|
15109
|
+
` "extensions": ["${result.extension}"]`,
|
|
15110
|
+
` }`,
|
|
15111
|
+
` }`,
|
|
15112
|
+
` }`
|
|
15113
|
+
].join(`
|
|
15114
|
+
`);
|
|
15115
|
+
}
|
|
14489
15116
|
async function withLspClient(filePath, fn) {
|
|
14490
15117
|
const absPath = resolve6(filePath);
|
|
14491
15118
|
const ext = extname2(absPath);
|
|
14492
|
-
const
|
|
14493
|
-
if (
|
|
14494
|
-
throw new Error(
|
|
15119
|
+
const result = findServerForExtension(ext);
|
|
15120
|
+
if (result.status !== "found") {
|
|
15121
|
+
throw new Error(formatServerLookupError(result));
|
|
14495
15122
|
}
|
|
15123
|
+
const server = result.server;
|
|
14496
15124
|
const root = findWorkspaceRoot(absPath);
|
|
14497
15125
|
const client = await lspManager.getClient(root, server);
|
|
14498
15126
|
try {
|
|
@@ -15483,10 +16111,10 @@ function mergeDefs(...defs) {
|
|
|
15483
16111
|
function cloneDef(schema) {
|
|
15484
16112
|
return mergeDefs(schema._zod.def);
|
|
15485
16113
|
}
|
|
15486
|
-
function getElementAtPath(obj,
|
|
15487
|
-
if (!
|
|
16114
|
+
function getElementAtPath(obj, path7) {
|
|
16115
|
+
if (!path7)
|
|
15488
16116
|
return obj;
|
|
15489
|
-
return
|
|
16117
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
15490
16118
|
}
|
|
15491
16119
|
function promiseAllObject(promisesObj) {
|
|
15492
16120
|
const keys = Object.keys(promisesObj);
|
|
@@ -15845,11 +16473,11 @@ function aborted(x, startIndex = 0) {
|
|
|
15845
16473
|
}
|
|
15846
16474
|
return false;
|
|
15847
16475
|
}
|
|
15848
|
-
function prefixIssues(
|
|
16476
|
+
function prefixIssues(path7, issues) {
|
|
15849
16477
|
return issues.map((iss) => {
|
|
15850
16478
|
var _a;
|
|
15851
16479
|
(_a = iss).path ?? (_a.path = []);
|
|
15852
|
-
iss.path.unshift(
|
|
16480
|
+
iss.path.unshift(path7);
|
|
15853
16481
|
return iss;
|
|
15854
16482
|
});
|
|
15855
16483
|
}
|
|
@@ -16017,7 +16645,7 @@ function treeifyError(error, _mapper) {
|
|
|
16017
16645
|
return issue2.message;
|
|
16018
16646
|
};
|
|
16019
16647
|
const result = { errors: [] };
|
|
16020
|
-
const processError = (error2,
|
|
16648
|
+
const processError = (error2, path7 = []) => {
|
|
16021
16649
|
var _a, _b;
|
|
16022
16650
|
for (const issue2 of error2.issues) {
|
|
16023
16651
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -16027,7 +16655,7 @@ function treeifyError(error, _mapper) {
|
|
|
16027
16655
|
} else if (issue2.code === "invalid_element") {
|
|
16028
16656
|
processError({ issues: issue2.issues }, issue2.path);
|
|
16029
16657
|
} else {
|
|
16030
|
-
const fullpath = [...
|
|
16658
|
+
const fullpath = [...path7, ...issue2.path];
|
|
16031
16659
|
if (fullpath.length === 0) {
|
|
16032
16660
|
result.errors.push(mapper(issue2));
|
|
16033
16661
|
continue;
|
|
@@ -16059,8 +16687,8 @@ function treeifyError(error, _mapper) {
|
|
|
16059
16687
|
}
|
|
16060
16688
|
function toDotPath(_path) {
|
|
16061
16689
|
const segs = [];
|
|
16062
|
-
const
|
|
16063
|
-
for (const seg of
|
|
16690
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16691
|
+
for (const seg of path7) {
|
|
16064
16692
|
if (typeof seg === "number")
|
|
16065
16693
|
segs.push(`[${seg}]`);
|
|
16066
16694
|
else if (typeof seg === "symbol")
|
|
@@ -24838,10 +25466,10 @@ function _property(property, schema, params) {
|
|
|
24838
25466
|
...normalizeParams(params)
|
|
24839
25467
|
});
|
|
24840
25468
|
}
|
|
24841
|
-
function _mime(
|
|
25469
|
+
function _mime(types11, params) {
|
|
24842
25470
|
return new $ZodCheckMimeType({
|
|
24843
25471
|
check: "mime_type",
|
|
24844
|
-
mime:
|
|
25472
|
+
mime: types11,
|
|
24845
25473
|
...normalizeParams(params)
|
|
24846
25474
|
});
|
|
24847
25475
|
}
|
|
@@ -26751,7 +27379,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
26751
27379
|
ZodType.init(inst, def);
|
|
26752
27380
|
inst.min = (size, params) => inst.check(_minSize(size, params));
|
|
26753
27381
|
inst.max = (size, params) => inst.check(_maxSize(size, params));
|
|
26754
|
-
inst.mime = (
|
|
27382
|
+
inst.mime = (types11, params) => inst.check(_mime(Array.isArray(types11) ? types11 : [types11], params));
|
|
26755
27383
|
});
|
|
26756
27384
|
function file(params) {
|
|
26757
27385
|
return _file(ZodFile, params);
|
|
@@ -27410,7 +28038,7 @@ import { existsSync as existsSync37, statSync as statSync4 } from "fs";
|
|
|
27410
28038
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
27411
28039
|
import { existsSync as existsSync36, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
|
|
27412
28040
|
import { join as join45 } from "path";
|
|
27413
|
-
import { homedir as
|
|
28041
|
+
import { homedir as homedir11 } from "os";
|
|
27414
28042
|
import { createRequire as createRequire3 } from "module";
|
|
27415
28043
|
var REPO2 = "ast-grep/ast-grep";
|
|
27416
28044
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -27435,11 +28063,11 @@ var PLATFORM_MAP2 = {
|
|
|
27435
28063
|
function getCacheDir3() {
|
|
27436
28064
|
if (process.platform === "win32") {
|
|
27437
28065
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
27438
|
-
const base2 = localAppData || join45(
|
|
28066
|
+
const base2 = localAppData || join45(homedir11(), "AppData", "Local");
|
|
27439
28067
|
return join45(base2, "oh-my-opencode", "bin");
|
|
27440
28068
|
}
|
|
27441
|
-
const
|
|
27442
|
-
const base =
|
|
28069
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
28070
|
+
const base = xdgCache || join45(homedir11(), ".cache");
|
|
27443
28071
|
return join45(base, "oh-my-opencode", "bin");
|
|
27444
28072
|
}
|
|
27445
28073
|
function getBinaryName3() {
|
|
@@ -27477,8 +28105,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
27477
28105
|
if (existsSync36(binaryPath)) {
|
|
27478
28106
|
return binaryPath;
|
|
27479
28107
|
}
|
|
27480
|
-
const { arch, os:
|
|
27481
|
-
const assetName = `app-${arch}-${
|
|
28108
|
+
const { arch, os: os5 } = platformInfo;
|
|
28109
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
27482
28110
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
27483
28111
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
27484
28112
|
try {
|
|
@@ -27567,9 +28195,9 @@ function findSgCliPathSync() {
|
|
|
27567
28195
|
}
|
|
27568
28196
|
if (process.platform === "darwin") {
|
|
27569
28197
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
27570
|
-
for (const
|
|
27571
|
-
if (existsSync37(
|
|
27572
|
-
return
|
|
28198
|
+
for (const path7 of homebrewPaths) {
|
|
28199
|
+
if (existsSync37(path7) && isValidBinary(path7)) {
|
|
28200
|
+
return path7;
|
|
27573
28201
|
}
|
|
27574
28202
|
}
|
|
27575
28203
|
}
|
|
@@ -27587,10 +28215,9 @@ function getSgCliPath() {
|
|
|
27587
28215
|
}
|
|
27588
28216
|
return "sg";
|
|
27589
28217
|
}
|
|
27590
|
-
function setSgCliPath(
|
|
27591
|
-
resolvedCliPath2 =
|
|
28218
|
+
function setSgCliPath(path7) {
|
|
28219
|
+
resolvedCliPath2 = path7;
|
|
27592
28220
|
}
|
|
27593
|
-
var SG_CLI_PATH = getSgCliPath();
|
|
27594
28221
|
var CLI_LANGUAGES = [
|
|
27595
28222
|
"bash",
|
|
27596
28223
|
"c",
|
|
@@ -28390,7 +29017,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28390
29017
|
return [];
|
|
28391
29018
|
}
|
|
28392
29019
|
const entries = readdirSync15(commandsDir, { withFileTypes: true });
|
|
28393
|
-
const
|
|
29020
|
+
const commands2 = [];
|
|
28394
29021
|
for (const entry of entries) {
|
|
28395
29022
|
if (!isMarkdownFile(entry))
|
|
28396
29023
|
continue;
|
|
@@ -28408,7 +29035,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28408
29035
|
agent: data.agent,
|
|
28409
29036
|
subtask: Boolean(data.subtask)
|
|
28410
29037
|
};
|
|
28411
|
-
|
|
29038
|
+
commands2.push({
|
|
28412
29039
|
name: commandName,
|
|
28413
29040
|
path: commandPath,
|
|
28414
29041
|
metadata,
|
|
@@ -28419,13 +29046,13 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28419
29046
|
continue;
|
|
28420
29047
|
}
|
|
28421
29048
|
}
|
|
28422
|
-
return
|
|
29049
|
+
return commands2;
|
|
28423
29050
|
}
|
|
28424
29051
|
function discoverCommandsSync() {
|
|
28425
|
-
const { homedir:
|
|
29052
|
+
const { homedir: homedir12 } = __require("os");
|
|
28426
29053
|
const userCommandsDir = join49(getClaudeConfigDir(), "commands");
|
|
28427
29054
|
const projectCommandsDir = join49(process.cwd(), ".claude", "commands");
|
|
28428
|
-
const opencodeGlobalDir = join49(
|
|
29055
|
+
const opencodeGlobalDir = join49(homedir12(), ".config", "opencode", "command");
|
|
28429
29056
|
const opencodeProjectDir = join49(process.cwd(), ".opencode", "command");
|
|
28430
29057
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
28431
29058
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
@@ -28476,18 +29103,18 @@ async function formatLoadedCommand(cmd) {
|
|
|
28476
29103
|
return sections.join(`
|
|
28477
29104
|
`);
|
|
28478
29105
|
}
|
|
28479
|
-
function formatCommandList(
|
|
28480
|
-
if (
|
|
29106
|
+
function formatCommandList(commands2) {
|
|
29107
|
+
if (commands2.length === 0) {
|
|
28481
29108
|
return "No commands found.";
|
|
28482
29109
|
}
|
|
28483
29110
|
const lines = [`# Available Commands
|
|
28484
29111
|
`];
|
|
28485
|
-
for (const cmd of
|
|
29112
|
+
for (const cmd of commands2) {
|
|
28486
29113
|
const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
|
|
28487
29114
|
lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
|
|
28488
29115
|
}
|
|
28489
29116
|
lines.push(`
|
|
28490
|
-
**Total**: ${
|
|
29117
|
+
**Total**: ${commands2.length} commands`);
|
|
28491
29118
|
return lines.join(`
|
|
28492
29119
|
`);
|
|
28493
29120
|
}
|
|
@@ -28524,27 +29151,27 @@ ${commandListForDescription}`,
|
|
|
28524
29151
|
command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
|
|
28525
29152
|
},
|
|
28526
29153
|
async execute(args) {
|
|
28527
|
-
const
|
|
29154
|
+
const commands2 = discoverCommandsSync();
|
|
28528
29155
|
if (!args.command) {
|
|
28529
|
-
return formatCommandList(
|
|
29156
|
+
return formatCommandList(commands2) + `
|
|
28530
29157
|
|
|
28531
29158
|
Provide a command name to execute.`;
|
|
28532
29159
|
}
|
|
28533
29160
|
const cmdName = args.command.replace(/^\//, "");
|
|
28534
|
-
const exactMatch =
|
|
29161
|
+
const exactMatch = commands2.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
|
|
28535
29162
|
if (exactMatch) {
|
|
28536
29163
|
return await formatLoadedCommand(exactMatch);
|
|
28537
29164
|
}
|
|
28538
|
-
const partialMatches =
|
|
29165
|
+
const partialMatches = commands2.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
|
|
28539
29166
|
if (partialMatches.length > 0) {
|
|
28540
29167
|
const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
|
|
28541
29168
|
return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
|
|
28542
29169
|
|
|
28543
|
-
` + formatCommandList(
|
|
29170
|
+
` + formatCommandList(commands2);
|
|
28544
29171
|
}
|
|
28545
29172
|
return `Command "/${cmdName}" not found.
|
|
28546
29173
|
|
|
28547
|
-
` + formatCommandList(
|
|
29174
|
+
` + formatCommandList(commands2) + `
|
|
28548
29175
|
|
|
28549
29176
|
Try a different command name.`;
|
|
28550
29177
|
}
|
|
@@ -28552,7 +29179,7 @@ Try a different command name.`;
|
|
|
28552
29179
|
// src/tools/session-manager/constants.ts
|
|
28553
29180
|
import { join as join50 } from "path";
|
|
28554
29181
|
var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
|
|
28555
|
-
var
|
|
29182
|
+
var MESSAGE_STORAGE4 = join50(OPENCODE_STORAGE9, "message");
|
|
28556
29183
|
var PART_STORAGE4 = join50(OPENCODE_STORAGE9, "part");
|
|
28557
29184
|
var TODO_DIR2 = join50(getClaudeConfigDir(), "todos");
|
|
28558
29185
|
var TRANSCRIPT_DIR2 = join50(getClaudeConfigDir(), "transcripts");
|
|
@@ -28628,22 +29255,24 @@ Has Todos: Yes (12 items, 8 completed)
|
|
|
28628
29255
|
Has Transcript: Yes (234 entries)`;
|
|
28629
29256
|
|
|
28630
29257
|
// src/tools/session-manager/storage.ts
|
|
28631
|
-
import { existsSync as existsSync42, readdirSync as readdirSync16
|
|
29258
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16 } from "fs";
|
|
29259
|
+
import { readdir, readFile } from "fs/promises";
|
|
28632
29260
|
import { join as join51 } from "path";
|
|
28633
|
-
function getAllSessions() {
|
|
28634
|
-
if (!existsSync42(
|
|
29261
|
+
async function getAllSessions() {
|
|
29262
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28635
29263
|
return [];
|
|
28636
29264
|
const sessions = [];
|
|
28637
|
-
function scanDirectory(dir) {
|
|
29265
|
+
async function scanDirectory(dir) {
|
|
28638
29266
|
try {
|
|
28639
|
-
|
|
29267
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
29268
|
+
for (const entry of entries) {
|
|
28640
29269
|
if (entry.isDirectory()) {
|
|
28641
29270
|
const sessionPath = join51(dir, entry.name);
|
|
28642
|
-
const files =
|
|
29271
|
+
const files = await readdir(sessionPath);
|
|
28643
29272
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
28644
29273
|
sessions.push(entry.name);
|
|
28645
29274
|
} else {
|
|
28646
|
-
scanDirectory(sessionPath);
|
|
29275
|
+
await scanDirectory(sessionPath);
|
|
28647
29276
|
}
|
|
28648
29277
|
}
|
|
28649
29278
|
}
|
|
@@ -28651,49 +29280,58 @@ function getAllSessions() {
|
|
|
28651
29280
|
return;
|
|
28652
29281
|
}
|
|
28653
29282
|
}
|
|
28654
|
-
scanDirectory(
|
|
29283
|
+
await scanDirectory(MESSAGE_STORAGE4);
|
|
28655
29284
|
return [...new Set(sessions)];
|
|
28656
29285
|
}
|
|
28657
29286
|
function getMessageDir9(sessionID) {
|
|
28658
|
-
if (!existsSync42(
|
|
29287
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28659
29288
|
return "";
|
|
28660
|
-
const directPath = join51(
|
|
29289
|
+
const directPath = join51(MESSAGE_STORAGE4, sessionID);
|
|
28661
29290
|
if (existsSync42(directPath)) {
|
|
28662
29291
|
return directPath;
|
|
28663
29292
|
}
|
|
28664
|
-
|
|
28665
|
-
const
|
|
28666
|
-
|
|
28667
|
-
|
|
29293
|
+
try {
|
|
29294
|
+
for (const dir of readdirSync16(MESSAGE_STORAGE4)) {
|
|
29295
|
+
const sessionPath = join51(MESSAGE_STORAGE4, dir, sessionID);
|
|
29296
|
+
if (existsSync42(sessionPath)) {
|
|
29297
|
+
return sessionPath;
|
|
29298
|
+
}
|
|
28668
29299
|
}
|
|
29300
|
+
} catch {
|
|
29301
|
+
return "";
|
|
28669
29302
|
}
|
|
28670
29303
|
return "";
|
|
28671
29304
|
}
|
|
28672
29305
|
function sessionExists(sessionID) {
|
|
28673
29306
|
return getMessageDir9(sessionID) !== "";
|
|
28674
29307
|
}
|
|
28675
|
-
function readSessionMessages(sessionID) {
|
|
29308
|
+
async function readSessionMessages(sessionID) {
|
|
28676
29309
|
const messageDir = getMessageDir9(sessionID);
|
|
28677
29310
|
if (!messageDir || !existsSync42(messageDir))
|
|
28678
29311
|
return [];
|
|
28679
29312
|
const messages = [];
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
28695
|
-
|
|
29313
|
+
try {
|
|
29314
|
+
const files = await readdir(messageDir);
|
|
29315
|
+
for (const file2 of files) {
|
|
29316
|
+
if (!file2.endsWith(".json"))
|
|
29317
|
+
continue;
|
|
29318
|
+
try {
|
|
29319
|
+
const content = await readFile(join51(messageDir, file2), "utf-8");
|
|
29320
|
+
const meta = JSON.parse(content);
|
|
29321
|
+
const parts = await readParts2(meta.id);
|
|
29322
|
+
messages.push({
|
|
29323
|
+
id: meta.id,
|
|
29324
|
+
role: meta.role,
|
|
29325
|
+
agent: meta.agent,
|
|
29326
|
+
time: meta.time,
|
|
29327
|
+
parts
|
|
29328
|
+
});
|
|
29329
|
+
} catch {
|
|
29330
|
+
continue;
|
|
29331
|
+
}
|
|
28696
29332
|
}
|
|
29333
|
+
} catch {
|
|
29334
|
+
return [];
|
|
28697
29335
|
}
|
|
28698
29336
|
return messages.sort((a, b) => {
|
|
28699
29337
|
const aTime = a.time?.created ?? 0;
|
|
@@ -28703,61 +29341,71 @@ function readSessionMessages(sessionID) {
|
|
|
28703
29341
|
return a.id.localeCompare(b.id);
|
|
28704
29342
|
});
|
|
28705
29343
|
}
|
|
28706
|
-
function readParts2(messageID) {
|
|
29344
|
+
async function readParts2(messageID) {
|
|
28707
29345
|
const partDir = join51(PART_STORAGE4, messageID);
|
|
28708
29346
|
if (!existsSync42(partDir))
|
|
28709
29347
|
return [];
|
|
28710
29348
|
const parts = [];
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
|
|
28718
|
-
|
|
29349
|
+
try {
|
|
29350
|
+
const files = await readdir(partDir);
|
|
29351
|
+
for (const file2 of files) {
|
|
29352
|
+
if (!file2.endsWith(".json"))
|
|
29353
|
+
continue;
|
|
29354
|
+
try {
|
|
29355
|
+
const content = await readFile(join51(partDir, file2), "utf-8");
|
|
29356
|
+
parts.push(JSON.parse(content));
|
|
29357
|
+
} catch {
|
|
29358
|
+
continue;
|
|
29359
|
+
}
|
|
28719
29360
|
}
|
|
29361
|
+
} catch {
|
|
29362
|
+
return [];
|
|
28720
29363
|
}
|
|
28721
29364
|
return parts.sort((a, b) => a.id.localeCompare(b.id));
|
|
28722
29365
|
}
|
|
28723
|
-
function readSessionTodos(sessionID) {
|
|
29366
|
+
async function readSessionTodos(sessionID) {
|
|
28724
29367
|
if (!existsSync42(TODO_DIR2))
|
|
28725
29368
|
return [];
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
28730
|
-
|
|
28731
|
-
|
|
28732
|
-
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
|
|
28736
|
-
|
|
28737
|
-
|
|
29369
|
+
try {
|
|
29370
|
+
const allFiles = await readdir(TODO_DIR2);
|
|
29371
|
+
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
29372
|
+
for (const file2 of todoFiles) {
|
|
29373
|
+
try {
|
|
29374
|
+
const content = await readFile(join51(TODO_DIR2, file2), "utf-8");
|
|
29375
|
+
const data = JSON.parse(content);
|
|
29376
|
+
if (Array.isArray(data)) {
|
|
29377
|
+
return data.map((item) => ({
|
|
29378
|
+
id: item.id || "",
|
|
29379
|
+
content: item.content || "",
|
|
29380
|
+
status: item.status || "pending",
|
|
29381
|
+
priority: item.priority
|
|
29382
|
+
}));
|
|
29383
|
+
}
|
|
29384
|
+
} catch {
|
|
29385
|
+
continue;
|
|
28738
29386
|
}
|
|
28739
|
-
} catch {
|
|
28740
|
-
continue;
|
|
28741
29387
|
}
|
|
29388
|
+
} catch {
|
|
29389
|
+
return [];
|
|
28742
29390
|
}
|
|
28743
29391
|
return [];
|
|
28744
29392
|
}
|
|
28745
|
-
function readSessionTranscript(sessionID) {
|
|
29393
|
+
async function readSessionTranscript(sessionID) {
|
|
28746
29394
|
if (!existsSync42(TRANSCRIPT_DIR2))
|
|
28747
29395
|
return 0;
|
|
28748
29396
|
const transcriptFile = join51(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
28749
29397
|
if (!existsSync42(transcriptFile))
|
|
28750
29398
|
return 0;
|
|
28751
29399
|
try {
|
|
28752
|
-
const content =
|
|
29400
|
+
const content = await readFile(transcriptFile, "utf-8");
|
|
28753
29401
|
return content.trim().split(`
|
|
28754
29402
|
`).filter(Boolean).length;
|
|
28755
29403
|
} catch {
|
|
28756
29404
|
return 0;
|
|
28757
29405
|
}
|
|
28758
29406
|
}
|
|
28759
|
-
function getSessionInfo(sessionID) {
|
|
28760
|
-
const messages = readSessionMessages(sessionID);
|
|
29407
|
+
async function getSessionInfo(sessionID) {
|
|
29408
|
+
const messages = await readSessionMessages(sessionID);
|
|
28761
29409
|
if (messages.length === 0)
|
|
28762
29410
|
return null;
|
|
28763
29411
|
const agentsUsed = new Set;
|
|
@@ -28774,8 +29422,8 @@ function getSessionInfo(sessionID) {
|
|
|
28774
29422
|
lastMessage = date5;
|
|
28775
29423
|
}
|
|
28776
29424
|
}
|
|
28777
|
-
const todos = readSessionTodos(sessionID);
|
|
28778
|
-
const transcriptEntries = readSessionTranscript(sessionID);
|
|
29425
|
+
const todos = await readSessionTodos(sessionID);
|
|
29426
|
+
const transcriptEntries = await readSessionTranscript(sessionID);
|
|
28779
29427
|
return {
|
|
28780
29428
|
id: sessionID,
|
|
28781
29429
|
message_count: messages.length,
|
|
@@ -28790,11 +29438,11 @@ function getSessionInfo(sessionID) {
|
|
|
28790
29438
|
}
|
|
28791
29439
|
|
|
28792
29440
|
// src/tools/session-manager/utils.ts
|
|
28793
|
-
function formatSessionList(sessionIDs) {
|
|
29441
|
+
async function formatSessionList(sessionIDs) {
|
|
28794
29442
|
if (sessionIDs.length === 0) {
|
|
28795
29443
|
return "No sessions found.";
|
|
28796
29444
|
}
|
|
28797
|
-
const infos = sessionIDs.map((id) => getSessionInfo(id)).filter((info) => info !== null);
|
|
29445
|
+
const infos = (await Promise.all(sessionIDs.map((id) => getSessionInfo(id)))).filter((info) => info !== null);
|
|
28798
29446
|
if (infos.length === 0) {
|
|
28799
29447
|
return "No valid sessions found.";
|
|
28800
29448
|
}
|
|
@@ -28886,29 +29534,33 @@ function formatSearchResults(results) {
|
|
|
28886
29534
|
return lines.join(`
|
|
28887
29535
|
`);
|
|
28888
29536
|
}
|
|
28889
|
-
function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
29537
|
+
async function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
28890
29538
|
if (!fromDate && !toDate)
|
|
28891
29539
|
return sessionIDs;
|
|
28892
29540
|
const from = fromDate ? new Date(fromDate) : null;
|
|
28893
29541
|
const to = toDate ? new Date(toDate) : null;
|
|
28894
|
-
|
|
28895
|
-
|
|
29542
|
+
const results = [];
|
|
29543
|
+
for (const id of sessionIDs) {
|
|
29544
|
+
const info = await getSessionInfo(id);
|
|
28896
29545
|
if (!info || !info.last_message)
|
|
28897
|
-
|
|
29546
|
+
continue;
|
|
28898
29547
|
if (from && info.last_message < from)
|
|
28899
|
-
|
|
29548
|
+
continue;
|
|
28900
29549
|
if (to && info.last_message > to)
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
}
|
|
29550
|
+
continue;
|
|
29551
|
+
results.push(id);
|
|
29552
|
+
}
|
|
29553
|
+
return results;
|
|
28904
29554
|
}
|
|
28905
|
-
function searchInSession(sessionID, query, caseSensitive = false) {
|
|
28906
|
-
const messages = readSessionMessages(sessionID);
|
|
29555
|
+
async function searchInSession(sessionID, query, caseSensitive = false, maxResults) {
|
|
29556
|
+
const messages = await readSessionMessages(sessionID);
|
|
28907
29557
|
const results = [];
|
|
28908
29558
|
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
28909
29559
|
for (const msg of messages) {
|
|
29560
|
+
if (maxResults && results.length >= maxResults)
|
|
29561
|
+
break;
|
|
28910
29562
|
let matchCount = 0;
|
|
28911
|
-
|
|
29563
|
+
const excerpts = [];
|
|
28912
29564
|
for (const part of msg.parts) {
|
|
28913
29565
|
if (part.type === "text" && part.text) {
|
|
28914
29566
|
const text = caseSensitive ? part.text : part.text.toLowerCase();
|
|
@@ -28944,6 +29596,14 @@ function searchInSession(sessionID, query, caseSensitive = false) {
|
|
|
28944
29596
|
}
|
|
28945
29597
|
|
|
28946
29598
|
// src/tools/session-manager/tools.ts
|
|
29599
|
+
var SEARCH_TIMEOUT_MS = 60000;
|
|
29600
|
+
var MAX_SESSIONS_TO_SCAN = 50;
|
|
29601
|
+
function withTimeout(promise2, ms, operation) {
|
|
29602
|
+
return Promise.race([
|
|
29603
|
+
promise2,
|
|
29604
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms))
|
|
29605
|
+
]);
|
|
29606
|
+
}
|
|
28947
29607
|
var session_list = tool({
|
|
28948
29608
|
description: SESSION_LIST_DESCRIPTION,
|
|
28949
29609
|
args: {
|
|
@@ -28953,14 +29613,14 @@ var session_list = tool({
|
|
|
28953
29613
|
},
|
|
28954
29614
|
execute: async (args, _context) => {
|
|
28955
29615
|
try {
|
|
28956
|
-
let sessions = getAllSessions();
|
|
29616
|
+
let sessions = await getAllSessions();
|
|
28957
29617
|
if (args.from_date || args.to_date) {
|
|
28958
|
-
sessions = filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
29618
|
+
sessions = await filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
28959
29619
|
}
|
|
28960
29620
|
if (args.limit && args.limit > 0) {
|
|
28961
29621
|
sessions = sessions.slice(0, args.limit);
|
|
28962
29622
|
}
|
|
28963
|
-
return formatSessionList(sessions);
|
|
29623
|
+
return await formatSessionList(sessions);
|
|
28964
29624
|
} catch (e) {
|
|
28965
29625
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
28966
29626
|
}
|
|
@@ -28979,11 +29639,11 @@ var session_read = tool({
|
|
|
28979
29639
|
if (!sessionExists(args.session_id)) {
|
|
28980
29640
|
return `Session not found: ${args.session_id}`;
|
|
28981
29641
|
}
|
|
28982
|
-
let messages = readSessionMessages(args.session_id);
|
|
29642
|
+
let messages = await readSessionMessages(args.session_id);
|
|
28983
29643
|
if (args.limit && args.limit > 0) {
|
|
28984
29644
|
messages = messages.slice(0, args.limit);
|
|
28985
29645
|
}
|
|
28986
|
-
const todos = args.include_todos ? readSessionTodos(args.session_id) : undefined;
|
|
29646
|
+
const todos = args.include_todos ? await readSessionTodos(args.session_id) : undefined;
|
|
28987
29647
|
return formatSessionMessages(messages, args.include_todos, todos);
|
|
28988
29648
|
} catch (e) {
|
|
28989
29649
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -29000,10 +29660,25 @@ var session_search = tool({
|
|
|
29000
29660
|
},
|
|
29001
29661
|
execute: async (args, _context) => {
|
|
29002
29662
|
try {
|
|
29003
|
-
const
|
|
29004
|
-
const
|
|
29005
|
-
|
|
29006
|
-
|
|
29663
|
+
const resultLimit = args.limit && args.limit > 0 ? args.limit : 20;
|
|
29664
|
+
const searchOperation = async () => {
|
|
29665
|
+
if (args.session_id) {
|
|
29666
|
+
return searchInSession(args.session_id, args.query, args.case_sensitive, resultLimit);
|
|
29667
|
+
}
|
|
29668
|
+
const allSessions = await getAllSessions();
|
|
29669
|
+
const sessionsToScan = allSessions.slice(0, MAX_SESSIONS_TO_SCAN);
|
|
29670
|
+
const allResults = [];
|
|
29671
|
+
for (const sid of sessionsToScan) {
|
|
29672
|
+
if (allResults.length >= resultLimit)
|
|
29673
|
+
break;
|
|
29674
|
+
const remaining = resultLimit - allResults.length;
|
|
29675
|
+
const sessionResults = await searchInSession(sid, args.query, args.case_sensitive, remaining);
|
|
29676
|
+
allResults.push(...sessionResults);
|
|
29677
|
+
}
|
|
29678
|
+
return allResults.slice(0, resultLimit);
|
|
29679
|
+
};
|
|
29680
|
+
const results = await withTimeout(searchOperation(), SEARCH_TIMEOUT_MS, "Search");
|
|
29681
|
+
return formatSearchResults(results);
|
|
29007
29682
|
} catch (e) {
|
|
29008
29683
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
29009
29684
|
}
|
|
@@ -29016,7 +29691,7 @@ var session_info = tool({
|
|
|
29016
29691
|
},
|
|
29017
29692
|
execute: async (args, _context) => {
|
|
29018
29693
|
try {
|
|
29019
|
-
const info = getSessionInfo(args.session_id);
|
|
29694
|
+
const info = await getSessionInfo(args.session_id);
|
|
29020
29695
|
if (!info) {
|
|
29021
29696
|
return `Session not found: ${args.session_id}`;
|
|
29022
29697
|
}
|
|
@@ -29059,12 +29734,12 @@ async function findTmuxPath() {
|
|
|
29059
29734
|
return null;
|
|
29060
29735
|
}
|
|
29061
29736
|
const stdout = await new Response(proc.stdout).text();
|
|
29062
|
-
const
|
|
29737
|
+
const path7 = stdout.trim().split(`
|
|
29063
29738
|
`)[0];
|
|
29064
|
-
if (!
|
|
29739
|
+
if (!path7) {
|
|
29065
29740
|
return null;
|
|
29066
29741
|
}
|
|
29067
|
-
const verifyProc = spawn10([
|
|
29742
|
+
const verifyProc = spawn10([path7, "-V"], {
|
|
29068
29743
|
stdout: "pipe",
|
|
29069
29744
|
stderr: "pipe"
|
|
29070
29745
|
});
|
|
@@ -29072,7 +29747,7 @@ async function findTmuxPath() {
|
|
|
29072
29747
|
if (verifyExitCode !== 0) {
|
|
29073
29748
|
return null;
|
|
29074
29749
|
}
|
|
29075
|
-
return
|
|
29750
|
+
return path7;
|
|
29076
29751
|
} catch {
|
|
29077
29752
|
return null;
|
|
29078
29753
|
}
|
|
@@ -29085,9 +29760,9 @@ async function getTmuxPath() {
|
|
|
29085
29760
|
return initPromise3;
|
|
29086
29761
|
}
|
|
29087
29762
|
initPromise3 = (async () => {
|
|
29088
|
-
const
|
|
29089
|
-
tmuxPath =
|
|
29090
|
-
return
|
|
29763
|
+
const path7 = await findTmuxPath();
|
|
29764
|
+
tmuxPath = path7;
|
|
29765
|
+
return path7;
|
|
29091
29766
|
})();
|
|
29092
29767
|
return initPromise3;
|
|
29093
29768
|
}
|
|
@@ -29224,21 +29899,26 @@ function createBackgroundTask(manager) {
|
|
|
29224
29899
|
agent: tool.schema.string().describe("Agent type to use (any registered agent)")
|
|
29225
29900
|
},
|
|
29226
29901
|
async execute(args, toolContext) {
|
|
29902
|
+
const ctx = toolContext;
|
|
29227
29903
|
if (!args.agent || args.agent.trim() === "") {
|
|
29228
29904
|
return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
|
|
29229
29905
|
}
|
|
29230
29906
|
try {
|
|
29231
|
-
const messageDir = getMessageDir10(
|
|
29907
|
+
const messageDir = getMessageDir10(ctx.sessionID);
|
|
29232
29908
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
29233
29909
|
const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
|
|
29234
29910
|
const task = await manager.launch({
|
|
29235
29911
|
description: args.description,
|
|
29236
29912
|
prompt: args.prompt,
|
|
29237
29913
|
agent: args.agent.trim(),
|
|
29238
|
-
parentSessionID:
|
|
29239
|
-
parentMessageID:
|
|
29914
|
+
parentSessionID: ctx.sessionID,
|
|
29915
|
+
parentMessageID: ctx.messageID,
|
|
29240
29916
|
parentModel
|
|
29241
29917
|
});
|
|
29918
|
+
ctx.metadata?.({
|
|
29919
|
+
title: args.description,
|
|
29920
|
+
metadata: { sessionId: task.sessionID }
|
|
29921
|
+
});
|
|
29242
29922
|
return `Background task launched successfully.
|
|
29243
29923
|
|
|
29244
29924
|
Task ID: ${task.id}
|
|
@@ -29494,6 +30174,7 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29494
30174
|
session_id: tool.schema.string().describe("Existing Task session to continue").optional()
|
|
29495
30175
|
},
|
|
29496
30176
|
async execute(args, toolContext) {
|
|
30177
|
+
const toolCtx = toolContext;
|
|
29497
30178
|
log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`);
|
|
29498
30179
|
if (!ALLOWED_AGENTS.includes(args.subagent_type)) {
|
|
29499
30180
|
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`;
|
|
@@ -29502,9 +30183,9 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29502
30183
|
if (args.session_id) {
|
|
29503
30184
|
return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`;
|
|
29504
30185
|
}
|
|
29505
|
-
return await executeBackground(args,
|
|
30186
|
+
return await executeBackground(args, toolCtx, backgroundManager);
|
|
29506
30187
|
}
|
|
29507
|
-
return await executeSync(args,
|
|
30188
|
+
return await executeSync(args, toolCtx, ctx);
|
|
29508
30189
|
}
|
|
29509
30190
|
});
|
|
29510
30191
|
}
|
|
@@ -29517,6 +30198,10 @@ async function executeBackground(args, toolContext, manager) {
|
|
|
29517
30198
|
parentSessionID: toolContext.sessionID,
|
|
29518
30199
|
parentMessageID: toolContext.messageID
|
|
29519
30200
|
});
|
|
30201
|
+
toolContext.metadata?.({
|
|
30202
|
+
title: args.description,
|
|
30203
|
+
metadata: { sessionId: task.sessionID }
|
|
30204
|
+
});
|
|
29520
30205
|
return `Background agent task launched successfully.
|
|
29521
30206
|
|
|
29522
30207
|
Task ID: ${task.id}
|
|
@@ -29561,6 +30246,10 @@ async function executeSync(args, toolContext, ctx) {
|
|
|
29561
30246
|
sessionID = createResult.data.id;
|
|
29562
30247
|
log(`[call_omo_agent] Created session: ${sessionID}`);
|
|
29563
30248
|
}
|
|
30249
|
+
toolContext.metadata?.({
|
|
30250
|
+
title: args.description,
|
|
30251
|
+
metadata: { sessionId: sessionID }
|
|
30252
|
+
});
|
|
29564
30253
|
log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
|
|
29565
30254
|
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
|
|
29566
30255
|
try {
|
|
@@ -30006,6 +30695,7 @@ class BackgroundManager {
|
|
|
30006
30695
|
}
|
|
30007
30696
|
const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration3}. Use background_output with task_id="${task.id}" to get results.`;
|
|
30008
30697
|
log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
|
|
30698
|
+
const taskId = task.id;
|
|
30009
30699
|
setTimeout(async () => {
|
|
30010
30700
|
try {
|
|
30011
30701
|
const messageDir = getMessageDir11(task.parentSessionID);
|
|
@@ -30021,10 +30711,13 @@ class BackgroundManager {
|
|
|
30021
30711
|
},
|
|
30022
30712
|
query: { directory: this.directory }
|
|
30023
30713
|
});
|
|
30024
|
-
this.clearNotificationsForTask(
|
|
30714
|
+
this.clearNotificationsForTask(taskId);
|
|
30025
30715
|
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
30026
30716
|
} catch (error45) {
|
|
30027
30717
|
log("[background-agent] prompt failed:", String(error45));
|
|
30718
|
+
} finally {
|
|
30719
|
+
this.tasks.delete(taskId);
|
|
30720
|
+
log("[background-agent] Removed completed task from memory:", taskId);
|
|
30028
30721
|
}
|
|
30029
30722
|
}, 200);
|
|
30030
30723
|
}
|
|
@@ -30212,6 +30905,9 @@ var HookNameSchema = exports_external.enum([
|
|
|
30212
30905
|
"empty-message-sanitizer",
|
|
30213
30906
|
"thinking-block-validator"
|
|
30214
30907
|
]);
|
|
30908
|
+
var BuiltinCommandNameSchema = exports_external.enum([
|
|
30909
|
+
"init-deep"
|
|
30910
|
+
]);
|
|
30215
30911
|
var AgentOverrideConfigSchema = exports_external.object({
|
|
30216
30912
|
model: exports_external.string().optional(),
|
|
30217
30913
|
temperature: exports_external.number().min(0).max(2).optional(),
|
|
@@ -30253,6 +30949,9 @@ var SisyphusAgentConfigSchema = exports_external.object({
|
|
|
30253
30949
|
planner_enabled: exports_external.boolean().optional(),
|
|
30254
30950
|
replace_plan: exports_external.boolean().optional()
|
|
30255
30951
|
});
|
|
30952
|
+
var CommentCheckerConfigSchema = exports_external.object({
|
|
30953
|
+
custom_prompt: exports_external.string().optional()
|
|
30954
|
+
});
|
|
30256
30955
|
var DynamicContextPruningConfigSchema = exports_external.object({
|
|
30257
30956
|
enabled: exports_external.boolean().default(false),
|
|
30258
30957
|
notification: exports_external.enum(["off", "minimal", "detailed"]).default("detailed"),
|
|
@@ -30291,17 +30990,19 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
30291
30990
|
preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
|
|
30292
30991
|
truncate_all_tool_outputs: exports_external.boolean().default(true),
|
|
30293
30992
|
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
|
|
30294
|
-
|
|
30993
|
+
dcp_for_compaction: exports_external.boolean().optional()
|
|
30295
30994
|
});
|
|
30296
30995
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
30297
30996
|
$schema: exports_external.string().optional(),
|
|
30298
30997
|
disabled_mcps: exports_external.array(McpNameSchema).optional(),
|
|
30299
30998
|
disabled_agents: exports_external.array(BuiltinAgentNameSchema).optional(),
|
|
30300
30999
|
disabled_hooks: exports_external.array(HookNameSchema).optional(),
|
|
31000
|
+
disabled_commands: exports_external.array(BuiltinCommandNameSchema).optional(),
|
|
30301
31001
|
agents: AgentOverridesSchema.optional(),
|
|
30302
31002
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
|
30303
31003
|
google_auth: exports_external.boolean().optional(),
|
|
30304
31004
|
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
31005
|
+
comment_checker: CommentCheckerConfigSchema.optional(),
|
|
30305
31006
|
experimental: ExperimentalConfigSchema.optional(),
|
|
30306
31007
|
auto_update: exports_external.boolean().optional()
|
|
30307
31008
|
});
|
|
@@ -30378,7 +31079,7 @@ var PLAN_PERMISSION = {
|
|
|
30378
31079
|
|
|
30379
31080
|
// src/index.ts
|
|
30380
31081
|
import * as fs7 from "fs";
|
|
30381
|
-
import * as
|
|
31082
|
+
import * as path7 from "path";
|
|
30382
31083
|
var AGENT_NAME_MAP = {
|
|
30383
31084
|
omo: "Sisyphus",
|
|
30384
31085
|
OmO: "Sisyphus",
|
|
@@ -30477,14 +31178,20 @@ function mergeConfigs(base, override) {
|
|
|
30477
31178
|
...override.disabled_hooks ?? []
|
|
30478
31179
|
])
|
|
30479
31180
|
],
|
|
31181
|
+
disabled_commands: [
|
|
31182
|
+
...new Set([
|
|
31183
|
+
...base.disabled_commands ?? [],
|
|
31184
|
+
...override.disabled_commands ?? []
|
|
31185
|
+
])
|
|
31186
|
+
],
|
|
30480
31187
|
claude_code: deepMerge(base.claude_code, override.claude_code)
|
|
30481
31188
|
};
|
|
30482
31189
|
}
|
|
30483
31190
|
function loadPluginConfig(directory, ctx) {
|
|
30484
|
-
const userBasePath =
|
|
31191
|
+
const userBasePath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode");
|
|
30485
31192
|
const userDetected = detectConfigFile(userBasePath);
|
|
30486
31193
|
const userConfigPath = userDetected.format !== "none" ? userDetected.path : userBasePath + ".json";
|
|
30487
|
-
const projectBasePath =
|
|
31194
|
+
const projectBasePath = path7.join(directory, ".opencode", "oh-my-opencode");
|
|
30488
31195
|
const projectDetected = detectConfigFile(projectBasePath);
|
|
30489
31196
|
const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : projectBasePath + ".json";
|
|
30490
31197
|
let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
|
|
@@ -30520,7 +31227,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30520
31227
|
const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
|
|
30521
31228
|
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30522
31229
|
const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
|
|
30523
|
-
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
|
|
31230
|
+
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks(pluginConfig.comment_checker) : null;
|
|
30524
31231
|
const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30525
31232
|
const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
|
|
30526
31233
|
const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
|
|
@@ -30700,12 +31407,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30700
31407
|
...mcpResult.servers,
|
|
30701
31408
|
...pluginComponents.mcpServers
|
|
30702
31409
|
};
|
|
31410
|
+
const builtinCommands = loadBuiltinCommands(pluginConfig.disabled_commands);
|
|
30703
31411
|
const userCommands = pluginConfig.claude_code?.commands ?? true ? loadUserCommands() : {};
|
|
30704
31412
|
const opencodeGlobalCommands = loadOpencodeGlobalCommands();
|
|
30705
31413
|
const systemCommands = config3.command ?? {};
|
|
30706
31414
|
const projectCommands = pluginConfig.claude_code?.commands ?? true ? loadProjectCommands() : {};
|
|
30707
31415
|
const opencodeProjectCommands = loadOpencodeProjectCommands();
|
|
30708
31416
|
config3.command = {
|
|
31417
|
+
...builtinCommands,
|
|
30709
31418
|
...userCommands,
|
|
30710
31419
|
...opencodeGlobalCommands,
|
|
30711
31420
|
...systemCommands,
|