oh-my-opencode 2.6.1 → 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/features/claude-code-plugin-loader/types.d.ts +17 -4
- 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 +1326 -612
- 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;
|
|
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;
|
|
4684
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();
|
|
@@ -13186,6 +13726,12 @@ function isPluginEnabled(pluginKey, settingsEnabledPlugins, overrideEnabledPlugi
|
|
|
13186
13726
|
}
|
|
13187
13727
|
return true;
|
|
13188
13728
|
}
|
|
13729
|
+
function extractPluginEntries(db) {
|
|
13730
|
+
if (db.version === 1) {
|
|
13731
|
+
return Object.entries(db.plugins).map(([key, installation]) => [key, installation]);
|
|
13732
|
+
}
|
|
13733
|
+
return Object.entries(db.plugins).map(([key, installations]) => [key, installations[0]]);
|
|
13734
|
+
}
|
|
13189
13735
|
function discoverInstalledPlugins(options) {
|
|
13190
13736
|
const db = loadInstalledPlugins();
|
|
13191
13737
|
const settings = loadClaudeSettings();
|
|
@@ -13196,14 +13742,13 @@ function discoverInstalledPlugins(options) {
|
|
|
13196
13742
|
}
|
|
13197
13743
|
const settingsEnabledPlugins = settings?.enabledPlugins;
|
|
13198
13744
|
const overrideEnabledPlugins = options?.enabledPluginsOverride;
|
|
13199
|
-
for (const [pluginKey,
|
|
13200
|
-
if (!
|
|
13745
|
+
for (const [pluginKey, installation] of extractPluginEntries(db)) {
|
|
13746
|
+
if (!installation)
|
|
13201
13747
|
continue;
|
|
13202
13748
|
if (!isPluginEnabled(pluginKey, settingsEnabledPlugins, overrideEnabledPlugins)) {
|
|
13203
13749
|
log(`Plugin disabled: ${pluginKey}`);
|
|
13204
13750
|
continue;
|
|
13205
13751
|
}
|
|
13206
|
-
const installation = installations[0];
|
|
13207
13752
|
const { installPath, scope, version } = installation;
|
|
13208
13753
|
if (!existsSync33(installPath)) {
|
|
13209
13754
|
errors.push({
|
|
@@ -13246,7 +13791,7 @@ function discoverInstalledPlugins(options) {
|
|
|
13246
13791
|
return { plugins, errors };
|
|
13247
13792
|
}
|
|
13248
13793
|
function loadPluginCommands(plugins) {
|
|
13249
|
-
const
|
|
13794
|
+
const commands2 = {};
|
|
13250
13795
|
for (const plugin2 of plugins) {
|
|
13251
13796
|
if (!plugin2.commandsDir || !existsSync33(plugin2.commandsDir))
|
|
13252
13797
|
continue;
|
|
@@ -13268,7 +13813,7 @@ ${body.trim()}
|
|
|
13268
13813
|
$ARGUMENTS
|
|
13269
13814
|
</user-request>`;
|
|
13270
13815
|
const formattedDescription = `(plugin: ${plugin2.name}) ${data.description || ""}`;
|
|
13271
|
-
|
|
13816
|
+
commands2[namespacedName] = {
|
|
13272
13817
|
name: namespacedName,
|
|
13273
13818
|
description: formattedDescription,
|
|
13274
13819
|
template: wrappedTemplate,
|
|
@@ -13283,7 +13828,7 @@ $ARGUMENTS
|
|
|
13283
13828
|
}
|
|
13284
13829
|
}
|
|
13285
13830
|
}
|
|
13286
|
-
return
|
|
13831
|
+
return commands2;
|
|
13287
13832
|
}
|
|
13288
13833
|
function loadPluginSkillsAsCommands(plugins) {
|
|
13289
13834
|
const skills = {};
|
|
@@ -13431,14 +13976,14 @@ function loadPluginHooksConfigs(plugins) {
|
|
|
13431
13976
|
}
|
|
13432
13977
|
async function loadAllPluginComponents(options) {
|
|
13433
13978
|
const { plugins, errors } = discoverInstalledPlugins(options);
|
|
13434
|
-
const
|
|
13979
|
+
const commands2 = loadPluginCommands(plugins);
|
|
13435
13980
|
const skills = loadPluginSkillsAsCommands(plugins);
|
|
13436
13981
|
const agents = loadPluginAgents(plugins);
|
|
13437
13982
|
const mcpServers = await loadPluginMcpServers(plugins);
|
|
13438
13983
|
const hooksConfigs = loadPluginHooksConfigs(plugins);
|
|
13439
|
-
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`);
|
|
13440
13985
|
return {
|
|
13441
|
-
commands,
|
|
13986
|
+
commands: commands2,
|
|
13442
13987
|
skills,
|
|
13443
13988
|
agents,
|
|
13444
13989
|
mcpServers,
|
|
@@ -13485,6 +14030,36 @@ var SEVERITY_MAP = {
|
|
|
13485
14030
|
var DEFAULT_MAX_REFERENCES = 200;
|
|
13486
14031
|
var DEFAULT_MAX_SYMBOLS = 200;
|
|
13487
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
|
+
};
|
|
13488
14063
|
var BUILTIN_SERVERS = {
|
|
13489
14064
|
typescript: {
|
|
13490
14065
|
command: ["typescript-language-server", "--stdio"],
|
|
@@ -13744,12 +14319,12 @@ var EXT_TO_LANG = {
|
|
|
13744
14319
|
// src/tools/lsp/config.ts
|
|
13745
14320
|
import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
|
|
13746
14321
|
import { join as join44 } from "path";
|
|
13747
|
-
import { homedir as
|
|
13748
|
-
function loadJsonFile(
|
|
13749
|
-
if (!existsSync34(
|
|
14322
|
+
import { homedir as homedir10 } from "os";
|
|
14323
|
+
function loadJsonFile(path7) {
|
|
14324
|
+
if (!existsSync34(path7))
|
|
13750
14325
|
return null;
|
|
13751
14326
|
try {
|
|
13752
|
-
return JSON.parse(readFileSync23(
|
|
14327
|
+
return JSON.parse(readFileSync23(path7, "utf-8"));
|
|
13753
14328
|
} catch {
|
|
13754
14329
|
return null;
|
|
13755
14330
|
}
|
|
@@ -13758,8 +14333,8 @@ function getConfigPaths2() {
|
|
|
13758
14333
|
const cwd = process.cwd();
|
|
13759
14334
|
return {
|
|
13760
14335
|
project: join44(cwd, ".opencode", "oh-my-opencode.json"),
|
|
13761
|
-
user: join44(
|
|
13762
|
-
opencode: join44(
|
|
14336
|
+
user: join44(homedir10(), ".config", "opencode", "oh-my-opencode.json"),
|
|
14337
|
+
opencode: join44(homedir10(), ".config", "opencode", "opencode.json")
|
|
13763
14338
|
};
|
|
13764
14339
|
}
|
|
13765
14340
|
function loadAllConfigs() {
|
|
@@ -13831,16 +14406,38 @@ function findServerForExtension(ext) {
|
|
|
13831
14406
|
for (const server of servers) {
|
|
13832
14407
|
if (server.extensions.includes(ext) && isServerInstalled(server.command)) {
|
|
13833
14408
|
return {
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
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
|
+
}
|
|
13840
14418
|
};
|
|
13841
14419
|
}
|
|
13842
14420
|
}
|
|
13843
|
-
|
|
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
|
+
};
|
|
13844
14441
|
}
|
|
13845
14442
|
function getLanguageId(ext) {
|
|
13846
14443
|
return EXT_TO_LANG[ext] || "plaintext";
|
|
@@ -13863,10 +14460,10 @@ function isServerInstalled(command) {
|
|
|
13863
14460
|
const additionalPaths = [
|
|
13864
14461
|
join44(cwd, "node_modules", ".bin", cmd),
|
|
13865
14462
|
join44(cwd, "node_modules", ".bin", cmd + ext),
|
|
13866
|
-
join44(
|
|
13867
|
-
join44(
|
|
13868
|
-
join44(
|
|
13869
|
-
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)
|
|
13870
14467
|
];
|
|
13871
14468
|
for (const p of additionalPaths) {
|
|
13872
14469
|
if (existsSync34(p)) {
|
|
@@ -14481,13 +15078,49 @@ function findWorkspaceRoot(filePath) {
|
|
|
14481
15078
|
}
|
|
14482
15079
|
return __require("path").dirname(resolve6(filePath));
|
|
14483
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
|
+
}
|
|
14484
15116
|
async function withLspClient(filePath, fn) {
|
|
14485
15117
|
const absPath = resolve6(filePath);
|
|
14486
15118
|
const ext = extname2(absPath);
|
|
14487
|
-
const
|
|
14488
|
-
if (
|
|
14489
|
-
throw new Error(
|
|
15119
|
+
const result = findServerForExtension(ext);
|
|
15120
|
+
if (result.status !== "found") {
|
|
15121
|
+
throw new Error(formatServerLookupError(result));
|
|
14490
15122
|
}
|
|
15123
|
+
const server = result.server;
|
|
14491
15124
|
const root = findWorkspaceRoot(absPath);
|
|
14492
15125
|
const client = await lspManager.getClient(root, server);
|
|
14493
15126
|
try {
|
|
@@ -15478,10 +16111,10 @@ function mergeDefs(...defs) {
|
|
|
15478
16111
|
function cloneDef(schema) {
|
|
15479
16112
|
return mergeDefs(schema._zod.def);
|
|
15480
16113
|
}
|
|
15481
|
-
function getElementAtPath(obj,
|
|
15482
|
-
if (!
|
|
16114
|
+
function getElementAtPath(obj, path7) {
|
|
16115
|
+
if (!path7)
|
|
15483
16116
|
return obj;
|
|
15484
|
-
return
|
|
16117
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
15485
16118
|
}
|
|
15486
16119
|
function promiseAllObject(promisesObj) {
|
|
15487
16120
|
const keys = Object.keys(promisesObj);
|
|
@@ -15840,11 +16473,11 @@ function aborted(x, startIndex = 0) {
|
|
|
15840
16473
|
}
|
|
15841
16474
|
return false;
|
|
15842
16475
|
}
|
|
15843
|
-
function prefixIssues(
|
|
16476
|
+
function prefixIssues(path7, issues) {
|
|
15844
16477
|
return issues.map((iss) => {
|
|
15845
16478
|
var _a;
|
|
15846
16479
|
(_a = iss).path ?? (_a.path = []);
|
|
15847
|
-
iss.path.unshift(
|
|
16480
|
+
iss.path.unshift(path7);
|
|
15848
16481
|
return iss;
|
|
15849
16482
|
});
|
|
15850
16483
|
}
|
|
@@ -16012,7 +16645,7 @@ function treeifyError(error, _mapper) {
|
|
|
16012
16645
|
return issue2.message;
|
|
16013
16646
|
};
|
|
16014
16647
|
const result = { errors: [] };
|
|
16015
|
-
const processError = (error2,
|
|
16648
|
+
const processError = (error2, path7 = []) => {
|
|
16016
16649
|
var _a, _b;
|
|
16017
16650
|
for (const issue2 of error2.issues) {
|
|
16018
16651
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -16022,7 +16655,7 @@ function treeifyError(error, _mapper) {
|
|
|
16022
16655
|
} else if (issue2.code === "invalid_element") {
|
|
16023
16656
|
processError({ issues: issue2.issues }, issue2.path);
|
|
16024
16657
|
} else {
|
|
16025
|
-
const fullpath = [...
|
|
16658
|
+
const fullpath = [...path7, ...issue2.path];
|
|
16026
16659
|
if (fullpath.length === 0) {
|
|
16027
16660
|
result.errors.push(mapper(issue2));
|
|
16028
16661
|
continue;
|
|
@@ -16054,8 +16687,8 @@ function treeifyError(error, _mapper) {
|
|
|
16054
16687
|
}
|
|
16055
16688
|
function toDotPath(_path) {
|
|
16056
16689
|
const segs = [];
|
|
16057
|
-
const
|
|
16058
|
-
for (const seg of
|
|
16690
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16691
|
+
for (const seg of path7) {
|
|
16059
16692
|
if (typeof seg === "number")
|
|
16060
16693
|
segs.push(`[${seg}]`);
|
|
16061
16694
|
else if (typeof seg === "symbol")
|
|
@@ -24833,10 +25466,10 @@ function _property(property, schema, params) {
|
|
|
24833
25466
|
...normalizeParams(params)
|
|
24834
25467
|
});
|
|
24835
25468
|
}
|
|
24836
|
-
function _mime(
|
|
25469
|
+
function _mime(types11, params) {
|
|
24837
25470
|
return new $ZodCheckMimeType({
|
|
24838
25471
|
check: "mime_type",
|
|
24839
|
-
mime:
|
|
25472
|
+
mime: types11,
|
|
24840
25473
|
...normalizeParams(params)
|
|
24841
25474
|
});
|
|
24842
25475
|
}
|
|
@@ -26746,7 +27379,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
26746
27379
|
ZodType.init(inst, def);
|
|
26747
27380
|
inst.min = (size, params) => inst.check(_minSize(size, params));
|
|
26748
27381
|
inst.max = (size, params) => inst.check(_maxSize(size, params));
|
|
26749
|
-
inst.mime = (
|
|
27382
|
+
inst.mime = (types11, params) => inst.check(_mime(Array.isArray(types11) ? types11 : [types11], params));
|
|
26750
27383
|
});
|
|
26751
27384
|
function file(params) {
|
|
26752
27385
|
return _file(ZodFile, params);
|
|
@@ -27405,7 +28038,7 @@ import { existsSync as existsSync37, statSync as statSync4 } from "fs";
|
|
|
27405
28038
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
27406
28039
|
import { existsSync as existsSync36, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
|
|
27407
28040
|
import { join as join45 } from "path";
|
|
27408
|
-
import { homedir as
|
|
28041
|
+
import { homedir as homedir11 } from "os";
|
|
27409
28042
|
import { createRequire as createRequire3 } from "module";
|
|
27410
28043
|
var REPO2 = "ast-grep/ast-grep";
|
|
27411
28044
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -27430,11 +28063,11 @@ var PLATFORM_MAP2 = {
|
|
|
27430
28063
|
function getCacheDir3() {
|
|
27431
28064
|
if (process.platform === "win32") {
|
|
27432
28065
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
27433
|
-
const base2 = localAppData || join45(
|
|
28066
|
+
const base2 = localAppData || join45(homedir11(), "AppData", "Local");
|
|
27434
28067
|
return join45(base2, "oh-my-opencode", "bin");
|
|
27435
28068
|
}
|
|
27436
|
-
const
|
|
27437
|
-
const base =
|
|
28069
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
28070
|
+
const base = xdgCache || join45(homedir11(), ".cache");
|
|
27438
28071
|
return join45(base, "oh-my-opencode", "bin");
|
|
27439
28072
|
}
|
|
27440
28073
|
function getBinaryName3() {
|
|
@@ -27472,8 +28105,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
27472
28105
|
if (existsSync36(binaryPath)) {
|
|
27473
28106
|
return binaryPath;
|
|
27474
28107
|
}
|
|
27475
|
-
const { arch, os:
|
|
27476
|
-
const assetName = `app-${arch}-${
|
|
28108
|
+
const { arch, os: os5 } = platformInfo;
|
|
28109
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
27477
28110
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
27478
28111
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
27479
28112
|
try {
|
|
@@ -27562,9 +28195,9 @@ function findSgCliPathSync() {
|
|
|
27562
28195
|
}
|
|
27563
28196
|
if (process.platform === "darwin") {
|
|
27564
28197
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
27565
|
-
for (const
|
|
27566
|
-
if (existsSync37(
|
|
27567
|
-
return
|
|
28198
|
+
for (const path7 of homebrewPaths) {
|
|
28199
|
+
if (existsSync37(path7) && isValidBinary(path7)) {
|
|
28200
|
+
return path7;
|
|
27568
28201
|
}
|
|
27569
28202
|
}
|
|
27570
28203
|
}
|
|
@@ -27582,10 +28215,9 @@ function getSgCliPath() {
|
|
|
27582
28215
|
}
|
|
27583
28216
|
return "sg";
|
|
27584
28217
|
}
|
|
27585
|
-
function setSgCliPath(
|
|
27586
|
-
resolvedCliPath2 =
|
|
28218
|
+
function setSgCliPath(path7) {
|
|
28219
|
+
resolvedCliPath2 = path7;
|
|
27587
28220
|
}
|
|
27588
|
-
var SG_CLI_PATH = getSgCliPath();
|
|
27589
28221
|
var CLI_LANGUAGES = [
|
|
27590
28222
|
"bash",
|
|
27591
28223
|
"c",
|
|
@@ -28385,7 +29017,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28385
29017
|
return [];
|
|
28386
29018
|
}
|
|
28387
29019
|
const entries = readdirSync15(commandsDir, { withFileTypes: true });
|
|
28388
|
-
const
|
|
29020
|
+
const commands2 = [];
|
|
28389
29021
|
for (const entry of entries) {
|
|
28390
29022
|
if (!isMarkdownFile(entry))
|
|
28391
29023
|
continue;
|
|
@@ -28403,7 +29035,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28403
29035
|
agent: data.agent,
|
|
28404
29036
|
subtask: Boolean(data.subtask)
|
|
28405
29037
|
};
|
|
28406
|
-
|
|
29038
|
+
commands2.push({
|
|
28407
29039
|
name: commandName,
|
|
28408
29040
|
path: commandPath,
|
|
28409
29041
|
metadata,
|
|
@@ -28414,13 +29046,13 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
28414
29046
|
continue;
|
|
28415
29047
|
}
|
|
28416
29048
|
}
|
|
28417
|
-
return
|
|
29049
|
+
return commands2;
|
|
28418
29050
|
}
|
|
28419
29051
|
function discoverCommandsSync() {
|
|
28420
|
-
const { homedir:
|
|
29052
|
+
const { homedir: homedir12 } = __require("os");
|
|
28421
29053
|
const userCommandsDir = join49(getClaudeConfigDir(), "commands");
|
|
28422
29054
|
const projectCommandsDir = join49(process.cwd(), ".claude", "commands");
|
|
28423
|
-
const opencodeGlobalDir = join49(
|
|
29055
|
+
const opencodeGlobalDir = join49(homedir12(), ".config", "opencode", "command");
|
|
28424
29056
|
const opencodeProjectDir = join49(process.cwd(), ".opencode", "command");
|
|
28425
29057
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
28426
29058
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
@@ -28471,18 +29103,18 @@ async function formatLoadedCommand(cmd) {
|
|
|
28471
29103
|
return sections.join(`
|
|
28472
29104
|
`);
|
|
28473
29105
|
}
|
|
28474
|
-
function formatCommandList(
|
|
28475
|
-
if (
|
|
29106
|
+
function formatCommandList(commands2) {
|
|
29107
|
+
if (commands2.length === 0) {
|
|
28476
29108
|
return "No commands found.";
|
|
28477
29109
|
}
|
|
28478
29110
|
const lines = [`# Available Commands
|
|
28479
29111
|
`];
|
|
28480
|
-
for (const cmd of
|
|
29112
|
+
for (const cmd of commands2) {
|
|
28481
29113
|
const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
|
|
28482
29114
|
lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
|
|
28483
29115
|
}
|
|
28484
29116
|
lines.push(`
|
|
28485
|
-
**Total**: ${
|
|
29117
|
+
**Total**: ${commands2.length} commands`);
|
|
28486
29118
|
return lines.join(`
|
|
28487
29119
|
`);
|
|
28488
29120
|
}
|
|
@@ -28519,27 +29151,27 @@ ${commandListForDescription}`,
|
|
|
28519
29151
|
command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
|
|
28520
29152
|
},
|
|
28521
29153
|
async execute(args) {
|
|
28522
|
-
const
|
|
29154
|
+
const commands2 = discoverCommandsSync();
|
|
28523
29155
|
if (!args.command) {
|
|
28524
|
-
return formatCommandList(
|
|
29156
|
+
return formatCommandList(commands2) + `
|
|
28525
29157
|
|
|
28526
29158
|
Provide a command name to execute.`;
|
|
28527
29159
|
}
|
|
28528
29160
|
const cmdName = args.command.replace(/^\//, "");
|
|
28529
|
-
const exactMatch =
|
|
29161
|
+
const exactMatch = commands2.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
|
|
28530
29162
|
if (exactMatch) {
|
|
28531
29163
|
return await formatLoadedCommand(exactMatch);
|
|
28532
29164
|
}
|
|
28533
|
-
const partialMatches =
|
|
29165
|
+
const partialMatches = commands2.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
|
|
28534
29166
|
if (partialMatches.length > 0) {
|
|
28535
29167
|
const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
|
|
28536
29168
|
return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
|
|
28537
29169
|
|
|
28538
|
-
` + formatCommandList(
|
|
29170
|
+
` + formatCommandList(commands2);
|
|
28539
29171
|
}
|
|
28540
29172
|
return `Command "/${cmdName}" not found.
|
|
28541
29173
|
|
|
28542
|
-
` + formatCommandList(
|
|
29174
|
+
` + formatCommandList(commands2) + `
|
|
28543
29175
|
|
|
28544
29176
|
Try a different command name.`;
|
|
28545
29177
|
}
|
|
@@ -28547,7 +29179,7 @@ Try a different command name.`;
|
|
|
28547
29179
|
// src/tools/session-manager/constants.ts
|
|
28548
29180
|
import { join as join50 } from "path";
|
|
28549
29181
|
var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
|
|
28550
|
-
var
|
|
29182
|
+
var MESSAGE_STORAGE4 = join50(OPENCODE_STORAGE9, "message");
|
|
28551
29183
|
var PART_STORAGE4 = join50(OPENCODE_STORAGE9, "part");
|
|
28552
29184
|
var TODO_DIR2 = join50(getClaudeConfigDir(), "todos");
|
|
28553
29185
|
var TRANSCRIPT_DIR2 = join50(getClaudeConfigDir(), "transcripts");
|
|
@@ -28623,22 +29255,24 @@ Has Todos: Yes (12 items, 8 completed)
|
|
|
28623
29255
|
Has Transcript: Yes (234 entries)`;
|
|
28624
29256
|
|
|
28625
29257
|
// src/tools/session-manager/storage.ts
|
|
28626
|
-
import { existsSync as existsSync42, readdirSync as readdirSync16
|
|
29258
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16 } from "fs";
|
|
29259
|
+
import { readdir, readFile } from "fs/promises";
|
|
28627
29260
|
import { join as join51 } from "path";
|
|
28628
|
-
function getAllSessions() {
|
|
28629
|
-
if (!existsSync42(
|
|
29261
|
+
async function getAllSessions() {
|
|
29262
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28630
29263
|
return [];
|
|
28631
29264
|
const sessions = [];
|
|
28632
|
-
function scanDirectory(dir) {
|
|
29265
|
+
async function scanDirectory(dir) {
|
|
28633
29266
|
try {
|
|
28634
|
-
|
|
29267
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
29268
|
+
for (const entry of entries) {
|
|
28635
29269
|
if (entry.isDirectory()) {
|
|
28636
29270
|
const sessionPath = join51(dir, entry.name);
|
|
28637
|
-
const files =
|
|
29271
|
+
const files = await readdir(sessionPath);
|
|
28638
29272
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
28639
29273
|
sessions.push(entry.name);
|
|
28640
29274
|
} else {
|
|
28641
|
-
scanDirectory(sessionPath);
|
|
29275
|
+
await scanDirectory(sessionPath);
|
|
28642
29276
|
}
|
|
28643
29277
|
}
|
|
28644
29278
|
}
|
|
@@ -28646,49 +29280,58 @@ function getAllSessions() {
|
|
|
28646
29280
|
return;
|
|
28647
29281
|
}
|
|
28648
29282
|
}
|
|
28649
|
-
scanDirectory(
|
|
29283
|
+
await scanDirectory(MESSAGE_STORAGE4);
|
|
28650
29284
|
return [...new Set(sessions)];
|
|
28651
29285
|
}
|
|
28652
29286
|
function getMessageDir9(sessionID) {
|
|
28653
|
-
if (!existsSync42(
|
|
29287
|
+
if (!existsSync42(MESSAGE_STORAGE4))
|
|
28654
29288
|
return "";
|
|
28655
|
-
const directPath = join51(
|
|
29289
|
+
const directPath = join51(MESSAGE_STORAGE4, sessionID);
|
|
28656
29290
|
if (existsSync42(directPath)) {
|
|
28657
29291
|
return directPath;
|
|
28658
29292
|
}
|
|
28659
|
-
|
|
28660
|
-
const
|
|
28661
|
-
|
|
28662
|
-
|
|
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
|
+
}
|
|
28663
29299
|
}
|
|
29300
|
+
} catch {
|
|
29301
|
+
return "";
|
|
28664
29302
|
}
|
|
28665
29303
|
return "";
|
|
28666
29304
|
}
|
|
28667
29305
|
function sessionExists(sessionID) {
|
|
28668
29306
|
return getMessageDir9(sessionID) !== "";
|
|
28669
29307
|
}
|
|
28670
|
-
function readSessionMessages(sessionID) {
|
|
29308
|
+
async function readSessionMessages(sessionID) {
|
|
28671
29309
|
const messageDir = getMessageDir9(sessionID);
|
|
28672
29310
|
if (!messageDir || !existsSync42(messageDir))
|
|
28673
29311
|
return [];
|
|
28674
29312
|
const messages = [];
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
|
|
28678
|
-
|
|
28679
|
-
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
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
|
+
}
|
|
28691
29332
|
}
|
|
29333
|
+
} catch {
|
|
29334
|
+
return [];
|
|
28692
29335
|
}
|
|
28693
29336
|
return messages.sort((a, b) => {
|
|
28694
29337
|
const aTime = a.time?.created ?? 0;
|
|
@@ -28698,61 +29341,71 @@ function readSessionMessages(sessionID) {
|
|
|
28698
29341
|
return a.id.localeCompare(b.id);
|
|
28699
29342
|
});
|
|
28700
29343
|
}
|
|
28701
|
-
function readParts2(messageID) {
|
|
29344
|
+
async function readParts2(messageID) {
|
|
28702
29345
|
const partDir = join51(PART_STORAGE4, messageID);
|
|
28703
29346
|
if (!existsSync42(partDir))
|
|
28704
29347
|
return [];
|
|
28705
29348
|
const parts = [];
|
|
28706
|
-
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
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
|
+
}
|
|
28714
29360
|
}
|
|
29361
|
+
} catch {
|
|
29362
|
+
return [];
|
|
28715
29363
|
}
|
|
28716
29364
|
return parts.sort((a, b) => a.id.localeCompare(b.id));
|
|
28717
29365
|
}
|
|
28718
|
-
function readSessionTodos(sessionID) {
|
|
29366
|
+
async function readSessionTodos(sessionID) {
|
|
28719
29367
|
if (!existsSync42(TODO_DIR2))
|
|
28720
29368
|
return [];
|
|
28721
|
-
|
|
28722
|
-
|
|
28723
|
-
|
|
28724
|
-
|
|
28725
|
-
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
28730
|
-
|
|
28731
|
-
|
|
28732
|
-
|
|
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;
|
|
28733
29386
|
}
|
|
28734
|
-
} catch {
|
|
28735
|
-
continue;
|
|
28736
29387
|
}
|
|
29388
|
+
} catch {
|
|
29389
|
+
return [];
|
|
28737
29390
|
}
|
|
28738
29391
|
return [];
|
|
28739
29392
|
}
|
|
28740
|
-
function readSessionTranscript(sessionID) {
|
|
29393
|
+
async function readSessionTranscript(sessionID) {
|
|
28741
29394
|
if (!existsSync42(TRANSCRIPT_DIR2))
|
|
28742
29395
|
return 0;
|
|
28743
29396
|
const transcriptFile = join51(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
28744
29397
|
if (!existsSync42(transcriptFile))
|
|
28745
29398
|
return 0;
|
|
28746
29399
|
try {
|
|
28747
|
-
const content =
|
|
29400
|
+
const content = await readFile(transcriptFile, "utf-8");
|
|
28748
29401
|
return content.trim().split(`
|
|
28749
29402
|
`).filter(Boolean).length;
|
|
28750
29403
|
} catch {
|
|
28751
29404
|
return 0;
|
|
28752
29405
|
}
|
|
28753
29406
|
}
|
|
28754
|
-
function getSessionInfo(sessionID) {
|
|
28755
|
-
const messages = readSessionMessages(sessionID);
|
|
29407
|
+
async function getSessionInfo(sessionID) {
|
|
29408
|
+
const messages = await readSessionMessages(sessionID);
|
|
28756
29409
|
if (messages.length === 0)
|
|
28757
29410
|
return null;
|
|
28758
29411
|
const agentsUsed = new Set;
|
|
@@ -28769,8 +29422,8 @@ function getSessionInfo(sessionID) {
|
|
|
28769
29422
|
lastMessage = date5;
|
|
28770
29423
|
}
|
|
28771
29424
|
}
|
|
28772
|
-
const todos = readSessionTodos(sessionID);
|
|
28773
|
-
const transcriptEntries = readSessionTranscript(sessionID);
|
|
29425
|
+
const todos = await readSessionTodos(sessionID);
|
|
29426
|
+
const transcriptEntries = await readSessionTranscript(sessionID);
|
|
28774
29427
|
return {
|
|
28775
29428
|
id: sessionID,
|
|
28776
29429
|
message_count: messages.length,
|
|
@@ -28785,11 +29438,11 @@ function getSessionInfo(sessionID) {
|
|
|
28785
29438
|
}
|
|
28786
29439
|
|
|
28787
29440
|
// src/tools/session-manager/utils.ts
|
|
28788
|
-
function formatSessionList(sessionIDs) {
|
|
29441
|
+
async function formatSessionList(sessionIDs) {
|
|
28789
29442
|
if (sessionIDs.length === 0) {
|
|
28790
29443
|
return "No sessions found.";
|
|
28791
29444
|
}
|
|
28792
|
-
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);
|
|
28793
29446
|
if (infos.length === 0) {
|
|
28794
29447
|
return "No valid sessions found.";
|
|
28795
29448
|
}
|
|
@@ -28881,29 +29534,33 @@ function formatSearchResults(results) {
|
|
|
28881
29534
|
return lines.join(`
|
|
28882
29535
|
`);
|
|
28883
29536
|
}
|
|
28884
|
-
function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
29537
|
+
async function filterSessionsByDate(sessionIDs, fromDate, toDate) {
|
|
28885
29538
|
if (!fromDate && !toDate)
|
|
28886
29539
|
return sessionIDs;
|
|
28887
29540
|
const from = fromDate ? new Date(fromDate) : null;
|
|
28888
29541
|
const to = toDate ? new Date(toDate) : null;
|
|
28889
|
-
|
|
28890
|
-
|
|
29542
|
+
const results = [];
|
|
29543
|
+
for (const id of sessionIDs) {
|
|
29544
|
+
const info = await getSessionInfo(id);
|
|
28891
29545
|
if (!info || !info.last_message)
|
|
28892
|
-
|
|
29546
|
+
continue;
|
|
28893
29547
|
if (from && info.last_message < from)
|
|
28894
|
-
|
|
29548
|
+
continue;
|
|
28895
29549
|
if (to && info.last_message > to)
|
|
28896
|
-
|
|
28897
|
-
|
|
28898
|
-
}
|
|
29550
|
+
continue;
|
|
29551
|
+
results.push(id);
|
|
29552
|
+
}
|
|
29553
|
+
return results;
|
|
28899
29554
|
}
|
|
28900
|
-
function searchInSession(sessionID, query, caseSensitive = false) {
|
|
28901
|
-
const messages = readSessionMessages(sessionID);
|
|
29555
|
+
async function searchInSession(sessionID, query, caseSensitive = false, maxResults) {
|
|
29556
|
+
const messages = await readSessionMessages(sessionID);
|
|
28902
29557
|
const results = [];
|
|
28903
29558
|
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
28904
29559
|
for (const msg of messages) {
|
|
29560
|
+
if (maxResults && results.length >= maxResults)
|
|
29561
|
+
break;
|
|
28905
29562
|
let matchCount = 0;
|
|
28906
|
-
|
|
29563
|
+
const excerpts = [];
|
|
28907
29564
|
for (const part of msg.parts) {
|
|
28908
29565
|
if (part.type === "text" && part.text) {
|
|
28909
29566
|
const text = caseSensitive ? part.text : part.text.toLowerCase();
|
|
@@ -28939,6 +29596,14 @@ function searchInSession(sessionID, query, caseSensitive = false) {
|
|
|
28939
29596
|
}
|
|
28940
29597
|
|
|
28941
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
|
+
}
|
|
28942
29607
|
var session_list = tool({
|
|
28943
29608
|
description: SESSION_LIST_DESCRIPTION,
|
|
28944
29609
|
args: {
|
|
@@ -28948,14 +29613,14 @@ var session_list = tool({
|
|
|
28948
29613
|
},
|
|
28949
29614
|
execute: async (args, _context) => {
|
|
28950
29615
|
try {
|
|
28951
|
-
let sessions = getAllSessions();
|
|
29616
|
+
let sessions = await getAllSessions();
|
|
28952
29617
|
if (args.from_date || args.to_date) {
|
|
28953
|
-
sessions = filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
29618
|
+
sessions = await filterSessionsByDate(sessions, args.from_date, args.to_date);
|
|
28954
29619
|
}
|
|
28955
29620
|
if (args.limit && args.limit > 0) {
|
|
28956
29621
|
sessions = sessions.slice(0, args.limit);
|
|
28957
29622
|
}
|
|
28958
|
-
return formatSessionList(sessions);
|
|
29623
|
+
return await formatSessionList(sessions);
|
|
28959
29624
|
} catch (e) {
|
|
28960
29625
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
28961
29626
|
}
|
|
@@ -28974,11 +29639,11 @@ var session_read = tool({
|
|
|
28974
29639
|
if (!sessionExists(args.session_id)) {
|
|
28975
29640
|
return `Session not found: ${args.session_id}`;
|
|
28976
29641
|
}
|
|
28977
|
-
let messages = readSessionMessages(args.session_id);
|
|
29642
|
+
let messages = await readSessionMessages(args.session_id);
|
|
28978
29643
|
if (args.limit && args.limit > 0) {
|
|
28979
29644
|
messages = messages.slice(0, args.limit);
|
|
28980
29645
|
}
|
|
28981
|
-
const todos = args.include_todos ? readSessionTodos(args.session_id) : undefined;
|
|
29646
|
+
const todos = args.include_todos ? await readSessionTodos(args.session_id) : undefined;
|
|
28982
29647
|
return formatSessionMessages(messages, args.include_todos, todos);
|
|
28983
29648
|
} catch (e) {
|
|
28984
29649
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -28995,10 +29660,25 @@ var session_search = tool({
|
|
|
28995
29660
|
},
|
|
28996
29661
|
execute: async (args, _context) => {
|
|
28997
29662
|
try {
|
|
28998
|
-
const
|
|
28999
|
-
const
|
|
29000
|
-
|
|
29001
|
-
|
|
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);
|
|
29002
29682
|
} catch (e) {
|
|
29003
29683
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
29004
29684
|
}
|
|
@@ -29011,7 +29691,7 @@ var session_info = tool({
|
|
|
29011
29691
|
},
|
|
29012
29692
|
execute: async (args, _context) => {
|
|
29013
29693
|
try {
|
|
29014
|
-
const info = getSessionInfo(args.session_id);
|
|
29694
|
+
const info = await getSessionInfo(args.session_id);
|
|
29015
29695
|
if (!info) {
|
|
29016
29696
|
return `Session not found: ${args.session_id}`;
|
|
29017
29697
|
}
|
|
@@ -29054,12 +29734,12 @@ async function findTmuxPath() {
|
|
|
29054
29734
|
return null;
|
|
29055
29735
|
}
|
|
29056
29736
|
const stdout = await new Response(proc.stdout).text();
|
|
29057
|
-
const
|
|
29737
|
+
const path7 = stdout.trim().split(`
|
|
29058
29738
|
`)[0];
|
|
29059
|
-
if (!
|
|
29739
|
+
if (!path7) {
|
|
29060
29740
|
return null;
|
|
29061
29741
|
}
|
|
29062
|
-
const verifyProc = spawn10([
|
|
29742
|
+
const verifyProc = spawn10([path7, "-V"], {
|
|
29063
29743
|
stdout: "pipe",
|
|
29064
29744
|
stderr: "pipe"
|
|
29065
29745
|
});
|
|
@@ -29067,7 +29747,7 @@ async function findTmuxPath() {
|
|
|
29067
29747
|
if (verifyExitCode !== 0) {
|
|
29068
29748
|
return null;
|
|
29069
29749
|
}
|
|
29070
|
-
return
|
|
29750
|
+
return path7;
|
|
29071
29751
|
} catch {
|
|
29072
29752
|
return null;
|
|
29073
29753
|
}
|
|
@@ -29080,9 +29760,9 @@ async function getTmuxPath() {
|
|
|
29080
29760
|
return initPromise3;
|
|
29081
29761
|
}
|
|
29082
29762
|
initPromise3 = (async () => {
|
|
29083
|
-
const
|
|
29084
|
-
tmuxPath =
|
|
29085
|
-
return
|
|
29763
|
+
const path7 = await findTmuxPath();
|
|
29764
|
+
tmuxPath = path7;
|
|
29765
|
+
return path7;
|
|
29086
29766
|
})();
|
|
29087
29767
|
return initPromise3;
|
|
29088
29768
|
}
|
|
@@ -29219,21 +29899,26 @@ function createBackgroundTask(manager) {
|
|
|
29219
29899
|
agent: tool.schema.string().describe("Agent type to use (any registered agent)")
|
|
29220
29900
|
},
|
|
29221
29901
|
async execute(args, toolContext) {
|
|
29902
|
+
const ctx = toolContext;
|
|
29222
29903
|
if (!args.agent || args.agent.trim() === "") {
|
|
29223
29904
|
return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
|
|
29224
29905
|
}
|
|
29225
29906
|
try {
|
|
29226
|
-
const messageDir = getMessageDir10(
|
|
29907
|
+
const messageDir = getMessageDir10(ctx.sessionID);
|
|
29227
29908
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
29228
29909
|
const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
|
|
29229
29910
|
const task = await manager.launch({
|
|
29230
29911
|
description: args.description,
|
|
29231
29912
|
prompt: args.prompt,
|
|
29232
29913
|
agent: args.agent.trim(),
|
|
29233
|
-
parentSessionID:
|
|
29234
|
-
parentMessageID:
|
|
29914
|
+
parentSessionID: ctx.sessionID,
|
|
29915
|
+
parentMessageID: ctx.messageID,
|
|
29235
29916
|
parentModel
|
|
29236
29917
|
});
|
|
29918
|
+
ctx.metadata?.({
|
|
29919
|
+
title: args.description,
|
|
29920
|
+
metadata: { sessionId: task.sessionID }
|
|
29921
|
+
});
|
|
29237
29922
|
return `Background task launched successfully.
|
|
29238
29923
|
|
|
29239
29924
|
Task ID: ${task.id}
|
|
@@ -29489,6 +30174,7 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29489
30174
|
session_id: tool.schema.string().describe("Existing Task session to continue").optional()
|
|
29490
30175
|
},
|
|
29491
30176
|
async execute(args, toolContext) {
|
|
30177
|
+
const toolCtx = toolContext;
|
|
29492
30178
|
log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`);
|
|
29493
30179
|
if (!ALLOWED_AGENTS.includes(args.subagent_type)) {
|
|
29494
30180
|
return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.`;
|
|
@@ -29497,9 +30183,9 @@ function createCallOmoAgent(ctx, backgroundManager) {
|
|
|
29497
30183
|
if (args.session_id) {
|
|
29498
30184
|
return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`;
|
|
29499
30185
|
}
|
|
29500
|
-
return await executeBackground(args,
|
|
30186
|
+
return await executeBackground(args, toolCtx, backgroundManager);
|
|
29501
30187
|
}
|
|
29502
|
-
return await executeSync(args,
|
|
30188
|
+
return await executeSync(args, toolCtx, ctx);
|
|
29503
30189
|
}
|
|
29504
30190
|
});
|
|
29505
30191
|
}
|
|
@@ -29512,6 +30198,10 @@ async function executeBackground(args, toolContext, manager) {
|
|
|
29512
30198
|
parentSessionID: toolContext.sessionID,
|
|
29513
30199
|
parentMessageID: toolContext.messageID
|
|
29514
30200
|
});
|
|
30201
|
+
toolContext.metadata?.({
|
|
30202
|
+
title: args.description,
|
|
30203
|
+
metadata: { sessionId: task.sessionID }
|
|
30204
|
+
});
|
|
29515
30205
|
return `Background agent task launched successfully.
|
|
29516
30206
|
|
|
29517
30207
|
Task ID: ${task.id}
|
|
@@ -29556,6 +30246,10 @@ async function executeSync(args, toolContext, ctx) {
|
|
|
29556
30246
|
sessionID = createResult.data.id;
|
|
29557
30247
|
log(`[call_omo_agent] Created session: ${sessionID}`);
|
|
29558
30248
|
}
|
|
30249
|
+
toolContext.metadata?.({
|
|
30250
|
+
title: args.description,
|
|
30251
|
+
metadata: { sessionId: sessionID }
|
|
30252
|
+
});
|
|
29559
30253
|
log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
|
|
29560
30254
|
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
|
|
29561
30255
|
try {
|
|
@@ -30001,6 +30695,7 @@ class BackgroundManager {
|
|
|
30001
30695
|
}
|
|
30002
30696
|
const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration3}. Use background_output with task_id="${task.id}" to get results.`;
|
|
30003
30697
|
log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
|
|
30698
|
+
const taskId = task.id;
|
|
30004
30699
|
setTimeout(async () => {
|
|
30005
30700
|
try {
|
|
30006
30701
|
const messageDir = getMessageDir11(task.parentSessionID);
|
|
@@ -30016,10 +30711,13 @@ class BackgroundManager {
|
|
|
30016
30711
|
},
|
|
30017
30712
|
query: { directory: this.directory }
|
|
30018
30713
|
});
|
|
30019
|
-
this.clearNotificationsForTask(
|
|
30714
|
+
this.clearNotificationsForTask(taskId);
|
|
30020
30715
|
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
30021
30716
|
} catch (error45) {
|
|
30022
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);
|
|
30023
30721
|
}
|
|
30024
30722
|
}, 200);
|
|
30025
30723
|
}
|
|
@@ -30207,6 +30905,9 @@ var HookNameSchema = exports_external.enum([
|
|
|
30207
30905
|
"empty-message-sanitizer",
|
|
30208
30906
|
"thinking-block-validator"
|
|
30209
30907
|
]);
|
|
30908
|
+
var BuiltinCommandNameSchema = exports_external.enum([
|
|
30909
|
+
"init-deep"
|
|
30910
|
+
]);
|
|
30210
30911
|
var AgentOverrideConfigSchema = exports_external.object({
|
|
30211
30912
|
model: exports_external.string().optional(),
|
|
30212
30913
|
temperature: exports_external.number().min(0).max(2).optional(),
|
|
@@ -30248,6 +30949,9 @@ var SisyphusAgentConfigSchema = exports_external.object({
|
|
|
30248
30949
|
planner_enabled: exports_external.boolean().optional(),
|
|
30249
30950
|
replace_plan: exports_external.boolean().optional()
|
|
30250
30951
|
});
|
|
30952
|
+
var CommentCheckerConfigSchema = exports_external.object({
|
|
30953
|
+
custom_prompt: exports_external.string().optional()
|
|
30954
|
+
});
|
|
30251
30955
|
var DynamicContextPruningConfigSchema = exports_external.object({
|
|
30252
30956
|
enabled: exports_external.boolean().default(false),
|
|
30253
30957
|
notification: exports_external.enum(["off", "minimal", "detailed"]).default("detailed"),
|
|
@@ -30286,17 +30990,19 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
30286
30990
|
preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
|
|
30287
30991
|
truncate_all_tool_outputs: exports_external.boolean().default(true),
|
|
30288
30992
|
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
|
|
30289
|
-
|
|
30993
|
+
dcp_for_compaction: exports_external.boolean().optional()
|
|
30290
30994
|
});
|
|
30291
30995
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
30292
30996
|
$schema: exports_external.string().optional(),
|
|
30293
30997
|
disabled_mcps: exports_external.array(McpNameSchema).optional(),
|
|
30294
30998
|
disabled_agents: exports_external.array(BuiltinAgentNameSchema).optional(),
|
|
30295
30999
|
disabled_hooks: exports_external.array(HookNameSchema).optional(),
|
|
31000
|
+
disabled_commands: exports_external.array(BuiltinCommandNameSchema).optional(),
|
|
30296
31001
|
agents: AgentOverridesSchema.optional(),
|
|
30297
31002
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
|
30298
31003
|
google_auth: exports_external.boolean().optional(),
|
|
30299
31004
|
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
31005
|
+
comment_checker: CommentCheckerConfigSchema.optional(),
|
|
30300
31006
|
experimental: ExperimentalConfigSchema.optional(),
|
|
30301
31007
|
auto_update: exports_external.boolean().optional()
|
|
30302
31008
|
});
|
|
@@ -30373,7 +31079,7 @@ var PLAN_PERMISSION = {
|
|
|
30373
31079
|
|
|
30374
31080
|
// src/index.ts
|
|
30375
31081
|
import * as fs7 from "fs";
|
|
30376
|
-
import * as
|
|
31082
|
+
import * as path7 from "path";
|
|
30377
31083
|
var AGENT_NAME_MAP = {
|
|
30378
31084
|
omo: "Sisyphus",
|
|
30379
31085
|
OmO: "Sisyphus",
|
|
@@ -30472,14 +31178,20 @@ function mergeConfigs(base, override) {
|
|
|
30472
31178
|
...override.disabled_hooks ?? []
|
|
30473
31179
|
])
|
|
30474
31180
|
],
|
|
31181
|
+
disabled_commands: [
|
|
31182
|
+
...new Set([
|
|
31183
|
+
...base.disabled_commands ?? [],
|
|
31184
|
+
...override.disabled_commands ?? []
|
|
31185
|
+
])
|
|
31186
|
+
],
|
|
30475
31187
|
claude_code: deepMerge(base.claude_code, override.claude_code)
|
|
30476
31188
|
};
|
|
30477
31189
|
}
|
|
30478
31190
|
function loadPluginConfig(directory, ctx) {
|
|
30479
|
-
const userBasePath =
|
|
31191
|
+
const userBasePath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode");
|
|
30480
31192
|
const userDetected = detectConfigFile(userBasePath);
|
|
30481
31193
|
const userConfigPath = userDetected.format !== "none" ? userDetected.path : userBasePath + ".json";
|
|
30482
|
-
const projectBasePath =
|
|
31194
|
+
const projectBasePath = path7.join(directory, ".opencode", "oh-my-opencode");
|
|
30483
31195
|
const projectDetected = detectConfigFile(projectBasePath);
|
|
30484
31196
|
const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : projectBasePath + ".json";
|
|
30485
31197
|
let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
|
|
@@ -30515,7 +31227,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30515
31227
|
const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
|
|
30516
31228
|
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30517
31229
|
const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
|
|
30518
|
-
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
|
|
31230
|
+
const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks(pluginConfig.comment_checker) : null;
|
|
30519
31231
|
const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
30520
31232
|
const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
|
|
30521
31233
|
const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
|
|
@@ -30695,12 +31407,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
30695
31407
|
...mcpResult.servers,
|
|
30696
31408
|
...pluginComponents.mcpServers
|
|
30697
31409
|
};
|
|
31410
|
+
const builtinCommands = loadBuiltinCommands(pluginConfig.disabled_commands);
|
|
30698
31411
|
const userCommands = pluginConfig.claude_code?.commands ?? true ? loadUserCommands() : {};
|
|
30699
31412
|
const opencodeGlobalCommands = loadOpencodeGlobalCommands();
|
|
30700
31413
|
const systemCommands = config3.command ?? {};
|
|
30701
31414
|
const projectCommands = pluginConfig.claude_code?.commands ?? true ? loadProjectCommands() : {};
|
|
30702
31415
|
const opencodeProjectCommands = loadOpencodeProjectCommands();
|
|
30703
31416
|
config3.command = {
|
|
31417
|
+
...builtinCommands,
|
|
30704
31418
|
...userCommands,
|
|
30705
31419
|
...opencodeGlobalCommands,
|
|
30706
31420
|
...systemCommands,
|