oh-my-opencode 2.7.0 → 2.7.2
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 +78 -2
- package/README.ko.md +81 -2
- package/README.md +17 -1
- package/README.zh-cn.md +82 -2
- package/dist/cli/index.js +24 -22
- package/dist/features/builtin-commands/templates/init-deep.d.ts +1 -1
- package/dist/hooks/todo-continuation-enforcer.test.d.ts +1 -0
- package/dist/index.js +394 -196
- package/dist/tools/glob/cli.d.ts +6 -1
- package/dist/tools/glob/constants.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4382,7 +4382,7 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
4382
4382
|
- Do not stop until all tasks are done`;
|
|
4383
4383
|
var COUNTDOWN_SECONDS = 2;
|
|
4384
4384
|
var TOAST_DURATION_MS = 900;
|
|
4385
|
-
var
|
|
4385
|
+
var ERROR_COOLDOWN_MS = 3000;
|
|
4386
4386
|
function getMessageDir(sessionID) {
|
|
4387
4387
|
if (!existsSync6(MESSAGE_STORAGE))
|
|
4388
4388
|
return null;
|
|
@@ -4396,7 +4396,7 @@ function getMessageDir(sessionID) {
|
|
|
4396
4396
|
}
|
|
4397
4397
|
return null;
|
|
4398
4398
|
}
|
|
4399
|
-
function
|
|
4399
|
+
function isAbortError(error) {
|
|
4400
4400
|
if (!error)
|
|
4401
4401
|
return false;
|
|
4402
4402
|
if (typeof error === "object") {
|
|
@@ -4422,47 +4422,41 @@ function getIncompleteCount(todos) {
|
|
|
4422
4422
|
function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
4423
4423
|
const { backgroundManager } = options;
|
|
4424
4424
|
const sessions = new Map;
|
|
4425
|
-
function
|
|
4425
|
+
function getState(sessionID) {
|
|
4426
4426
|
let state2 = sessions.get(sessionID);
|
|
4427
4427
|
if (!state2) {
|
|
4428
|
-
state2 = {
|
|
4428
|
+
state2 = {};
|
|
4429
4429
|
sessions.set(sessionID, state2);
|
|
4430
4430
|
}
|
|
4431
4431
|
return state2;
|
|
4432
4432
|
}
|
|
4433
|
-
function
|
|
4434
|
-
if (state2.timer) {
|
|
4435
|
-
clearTimeout(state2.timer);
|
|
4436
|
-
state2.timer = undefined;
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
function invalidate(sessionID, reason) {
|
|
4433
|
+
function cancelCountdown(sessionID) {
|
|
4440
4434
|
const state2 = sessions.get(sessionID);
|
|
4441
4435
|
if (!state2)
|
|
4442
4436
|
return;
|
|
4443
|
-
if (state2.
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
if (state2.
|
|
4448
|
-
|
|
4449
|
-
state2.
|
|
4437
|
+
if (state2.countdownTimer) {
|
|
4438
|
+
clearTimeout(state2.countdownTimer);
|
|
4439
|
+
state2.countdownTimer = undefined;
|
|
4440
|
+
}
|
|
4441
|
+
if (state2.countdownInterval) {
|
|
4442
|
+
clearInterval(state2.countdownInterval);
|
|
4443
|
+
state2.countdownInterval = undefined;
|
|
4450
4444
|
}
|
|
4451
4445
|
}
|
|
4452
|
-
function
|
|
4453
|
-
|
|
4454
|
-
|
|
4446
|
+
function cleanup(sessionID) {
|
|
4447
|
+
cancelCountdown(sessionID);
|
|
4448
|
+
sessions.delete(sessionID);
|
|
4455
4449
|
}
|
|
4456
4450
|
const markRecovering = (sessionID) => {
|
|
4457
|
-
const state2 =
|
|
4458
|
-
|
|
4459
|
-
|
|
4451
|
+
const state2 = getState(sessionID);
|
|
4452
|
+
state2.isRecovering = true;
|
|
4453
|
+
cancelCountdown(sessionID);
|
|
4460
4454
|
log(`[${HOOK_NAME}] Session marked as recovering`, { sessionID });
|
|
4461
4455
|
};
|
|
4462
4456
|
const markRecoveryComplete = (sessionID) => {
|
|
4463
4457
|
const state2 = sessions.get(sessionID);
|
|
4464
|
-
if (state2
|
|
4465
|
-
state2.
|
|
4458
|
+
if (state2) {
|
|
4459
|
+
state2.isRecovering = false;
|
|
4466
4460
|
log(`[${HOOK_NAME}] Session recovery complete`, { sessionID });
|
|
4467
4461
|
}
|
|
4468
4462
|
};
|
|
@@ -4476,101 +4470,51 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4476
4470
|
}
|
|
4477
4471
|
}).catch(() => {});
|
|
4478
4472
|
}
|
|
4479
|
-
async function
|
|
4473
|
+
async function injectContinuation(sessionID, incompleteCount, total) {
|
|
4480
4474
|
const state2 = sessions.get(sessionID);
|
|
4481
|
-
if (
|
|
4482
|
-
|
|
4483
|
-
if (state2.version !== capturedVersion) {
|
|
4484
|
-
log(`[${HOOK_NAME}] Injection aborted: version mismatch`, {
|
|
4485
|
-
sessionID,
|
|
4486
|
-
capturedVersion,
|
|
4487
|
-
currentVersion: state2.version
|
|
4488
|
-
});
|
|
4475
|
+
if (state2?.isRecovering) {
|
|
4476
|
+
log(`[${HOOK_NAME}] Skipped injection: in recovery`, { sessionID });
|
|
4489
4477
|
return;
|
|
4490
4478
|
}
|
|
4491
|
-
if (state2.
|
|
4492
|
-
log(`[${HOOK_NAME}]
|
|
4493
|
-
sessionID,
|
|
4494
|
-
mode: state2.mode
|
|
4495
|
-
});
|
|
4479
|
+
if (state2?.lastErrorAt && Date.now() - state2.lastErrorAt < ERROR_COOLDOWN_MS) {
|
|
4480
|
+
log(`[${HOOK_NAME}] Skipped injection: recent error`, { sessionID });
|
|
4496
4481
|
return;
|
|
4497
4482
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
sessionID,
|
|
4503
|
-
elapsedMs: elapsed,
|
|
4504
|
-
minIntervalMs: MIN_INJECTION_INTERVAL_MS
|
|
4505
|
-
});
|
|
4506
|
-
state2.mode = "idle";
|
|
4507
|
-
return;
|
|
4508
|
-
}
|
|
4483
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4484
|
+
if (hasRunningBgTasks) {
|
|
4485
|
+
log(`[${HOOK_NAME}] Skipped injection: background tasks running`, { sessionID });
|
|
4486
|
+
return;
|
|
4509
4487
|
}
|
|
4510
|
-
state2.mode = "injecting";
|
|
4511
4488
|
let todos = [];
|
|
4512
4489
|
try {
|
|
4513
4490
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4514
4491
|
todos = response.data ?? response;
|
|
4515
4492
|
} catch (err) {
|
|
4516
|
-
log(`[${HOOK_NAME}] Failed to fetch todos
|
|
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";
|
|
4493
|
+
log(`[${HOOK_NAME}] Failed to fetch todos`, { sessionID, error: String(err) });
|
|
4523
4494
|
return;
|
|
4524
4495
|
}
|
|
4525
|
-
const
|
|
4526
|
-
if (
|
|
4527
|
-
log(`[${HOOK_NAME}]
|
|
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";
|
|
4496
|
+
const freshIncompleteCount = getIncompleteCount(todos);
|
|
4497
|
+
if (freshIncompleteCount === 0) {
|
|
4498
|
+
log(`[${HOOK_NAME}] Skipped injection: no incomplete todos`, { sessionID });
|
|
4535
4499
|
return;
|
|
4536
4500
|
}
|
|
4537
4501
|
const messageDir = getMessageDir(sessionID);
|
|
4538
4502
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
4539
|
-
const
|
|
4540
|
-
if (!
|
|
4541
|
-
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, {
|
|
4542
|
-
sessionID,
|
|
4543
|
-
agent: prevMessage?.agent,
|
|
4544
|
-
tools: prevMessage?.tools
|
|
4545
|
-
});
|
|
4546
|
-
state2.mode = "idle";
|
|
4503
|
+
const hasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
4504
|
+
if (!hasWritePermission) {
|
|
4505
|
+
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: prevMessage?.agent });
|
|
4547
4506
|
return;
|
|
4548
4507
|
}
|
|
4549
4508
|
const agentName = prevMessage?.agent?.toLowerCase() ?? "";
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
log(`[${HOOK_NAME}] Skipped: plan mode agent detected`, {
|
|
4553
|
-
sessionID,
|
|
4554
|
-
agent: prevMessage?.agent
|
|
4555
|
-
});
|
|
4556
|
-
state2.mode = "idle";
|
|
4509
|
+
if (agentName === "plan" || agentName === "planner-sisyphus") {
|
|
4510
|
+
log(`[${HOOK_NAME}] Skipped: plan mode agent`, { sessionID, agent: prevMessage?.agent });
|
|
4557
4511
|
return;
|
|
4558
4512
|
}
|
|
4559
4513
|
const prompt = `${CONTINUATION_PROMPT}
|
|
4560
4514
|
|
|
4561
|
-
[Status: ${todos.length -
|
|
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();
|
|
4515
|
+
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]`;
|
|
4568
4516
|
try {
|
|
4569
|
-
log(`[${HOOK_NAME}] Injecting continuation
|
|
4570
|
-
sessionID,
|
|
4571
|
-
agent: prevMessage?.agent,
|
|
4572
|
-
incompleteCount
|
|
4573
|
-
});
|
|
4517
|
+
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: prevMessage?.agent, incompleteCount: freshIncompleteCount });
|
|
4574
4518
|
await ctx.client.session.prompt({
|
|
4575
4519
|
path: { id: sessionID },
|
|
4576
4520
|
body: {
|
|
@@ -4579,41 +4523,27 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4579
4523
|
},
|
|
4580
4524
|
query: { directory: ctx.directory }
|
|
4581
4525
|
});
|
|
4582
|
-
log(`[${HOOK_NAME}]
|
|
4526
|
+
log(`[${HOOK_NAME}] Injection successful`, { sessionID });
|
|
4583
4527
|
} catch (err) {
|
|
4584
|
-
log(`[${HOOK_NAME}]
|
|
4528
|
+
log(`[${HOOK_NAME}] Injection failed`, { sessionID, error: String(err) });
|
|
4585
4529
|
}
|
|
4586
|
-
state2.mode = "idle";
|
|
4587
4530
|
}
|
|
4588
|
-
function startCountdown(sessionID, incompleteCount) {
|
|
4589
|
-
const state2 =
|
|
4590
|
-
|
|
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);
|
|
4531
|
+
function startCountdown(sessionID, incompleteCount, total) {
|
|
4532
|
+
const state2 = getState(sessionID);
|
|
4533
|
+
cancelCountdown(sessionID);
|
|
4601
4534
|
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
clearInterval(toastInterval);
|
|
4605
|
-
return;
|
|
4606
|
-
}
|
|
4535
|
+
showCountdownToast(secondsRemaining, incompleteCount);
|
|
4536
|
+
state2.countdownInterval = setInterval(() => {
|
|
4607
4537
|
secondsRemaining--;
|
|
4608
4538
|
if (secondsRemaining > 0) {
|
|
4609
4539
|
showCountdownToast(secondsRemaining, incompleteCount);
|
|
4610
4540
|
}
|
|
4611
4541
|
}, 1000);
|
|
4612
|
-
state2.
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
executeInjection(sessionID, capturedVersion);
|
|
4542
|
+
state2.countdownTimer = setTimeout(() => {
|
|
4543
|
+
cancelCountdown(sessionID);
|
|
4544
|
+
injectContinuation(sessionID, incompleteCount, total);
|
|
4616
4545
|
}, COUNTDOWN_SECONDS * 1000);
|
|
4546
|
+
log(`[${HOOK_NAME}] Countdown started`, { sessionID, seconds: COUNTDOWN_SECONDS, incompleteCount });
|
|
4617
4547
|
}
|
|
4618
4548
|
const handler = async ({ event }) => {
|
|
4619
4549
|
const props = event.properties;
|
|
@@ -4621,33 +4551,36 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4621
4551
|
const sessionID = props?.sessionID;
|
|
4622
4552
|
if (!sessionID)
|
|
4623
4553
|
return;
|
|
4624
|
-
const
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
log(`[${HOOK_NAME}] session.error received`, { sessionID, isInterrupt, error: props?.error });
|
|
4554
|
+
const state2 = getState(sessionID);
|
|
4555
|
+
state2.lastErrorAt = Date.now();
|
|
4556
|
+
cancelCountdown(sessionID);
|
|
4557
|
+
log(`[${HOOK_NAME}] session.error`, { sessionID, isAbort: isAbortError(props?.error) });
|
|
4629
4558
|
return;
|
|
4630
4559
|
}
|
|
4631
4560
|
if (event.type === "session.idle") {
|
|
4632
4561
|
const sessionID = props?.sessionID;
|
|
4633
4562
|
if (!sessionID)
|
|
4634
4563
|
return;
|
|
4635
|
-
log(`[${HOOK_NAME}] session.idle
|
|
4636
|
-
|
|
4637
|
-
|
|
4564
|
+
log(`[${HOOK_NAME}] session.idle`, { sessionID });
|
|
4565
|
+
const mainSessionID2 = getMainSessionID();
|
|
4566
|
+
const isMainSession = sessionID === mainSessionID2;
|
|
4567
|
+
const isBackgroundTaskSession = subagentSessions.has(sessionID);
|
|
4568
|
+
if (mainSessionID2 && !isMainSession && !isBackgroundTaskSession) {
|
|
4569
|
+
log(`[${HOOK_NAME}] Skipped: not main or background task session`, { sessionID });
|
|
4638
4570
|
return;
|
|
4639
4571
|
}
|
|
4640
|
-
const state2 =
|
|
4641
|
-
if (state2.
|
|
4642
|
-
log(`[${HOOK_NAME}] Skipped:
|
|
4572
|
+
const state2 = getState(sessionID);
|
|
4573
|
+
if (state2.isRecovering) {
|
|
4574
|
+
log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID });
|
|
4643
4575
|
return;
|
|
4644
4576
|
}
|
|
4645
|
-
if (state2.
|
|
4646
|
-
log(`[${HOOK_NAME}] Skipped: error
|
|
4577
|
+
if (state2.lastErrorAt && Date.now() - state2.lastErrorAt < ERROR_COOLDOWN_MS) {
|
|
4578
|
+
log(`[${HOOK_NAME}] Skipped: recent error (cooldown)`, { sessionID });
|
|
4647
4579
|
return;
|
|
4648
4580
|
}
|
|
4649
|
-
|
|
4650
|
-
|
|
4581
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
4582
|
+
if (hasRunningBgTasks) {
|
|
4583
|
+
log(`[${HOOK_NAME}] Skipped: background tasks running`, { sessionID });
|
|
4651
4584
|
return;
|
|
4652
4585
|
}
|
|
4653
4586
|
let todos = [];
|
|
@@ -4655,54 +4588,37 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4655
4588
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
4656
4589
|
todos = response.data ?? response;
|
|
4657
4590
|
} catch (err) {
|
|
4658
|
-
log(`[${HOOK_NAME}] Todo
|
|
4591
|
+
log(`[${HOOK_NAME}] Todo fetch failed`, { sessionID, error: String(err) });
|
|
4659
4592
|
return;
|
|
4660
4593
|
}
|
|
4661
4594
|
if (!todos || todos.length === 0) {
|
|
4662
|
-
log(`[${HOOK_NAME}] No todos
|
|
4595
|
+
log(`[${HOOK_NAME}] No todos`, { sessionID });
|
|
4663
4596
|
return;
|
|
4664
4597
|
}
|
|
4665
4598
|
const incompleteCount = getIncompleteCount(todos);
|
|
4666
4599
|
if (incompleteCount === 0) {
|
|
4667
|
-
log(`[${HOOK_NAME}] All todos
|
|
4668
|
-
return;
|
|
4669
|
-
}
|
|
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 });
|
|
4600
|
+
log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
|
|
4673
4601
|
return;
|
|
4674
4602
|
}
|
|
4675
|
-
|
|
4676
|
-
sessionID,
|
|
4677
|
-
incomplete: incompleteCount,
|
|
4678
|
-
total: todos.length
|
|
4679
|
-
});
|
|
4680
|
-
startCountdown(sessionID, incompleteCount);
|
|
4603
|
+
startCountdown(sessionID, incompleteCount, todos.length);
|
|
4681
4604
|
return;
|
|
4682
4605
|
}
|
|
4683
4606
|
if (event.type === "message.updated") {
|
|
4684
4607
|
const info = props?.info;
|
|
4685
4608
|
const sessionID = info?.sessionID;
|
|
4686
4609
|
const role = info?.role;
|
|
4687
|
-
const finish = info?.finish;
|
|
4688
4610
|
if (!sessionID)
|
|
4689
4611
|
return;
|
|
4690
4612
|
if (role === "user") {
|
|
4691
4613
|
const state2 = sessions.get(sessionID);
|
|
4692
|
-
if (state2
|
|
4693
|
-
state2.
|
|
4694
|
-
log(`[${HOOK_NAME}] User message cleared errorBypass mode`, { sessionID });
|
|
4614
|
+
if (state2) {
|
|
4615
|
+
state2.lastErrorAt = undefined;
|
|
4695
4616
|
}
|
|
4696
|
-
|
|
4697
|
-
|
|
4617
|
+
cancelCountdown(sessionID);
|
|
4618
|
+
log(`[${HOOK_NAME}] User message: cleared error state`, { sessionID });
|
|
4698
4619
|
}
|
|
4699
|
-
if (role === "assistant"
|
|
4700
|
-
|
|
4701
|
-
return;
|
|
4702
|
-
}
|
|
4703
|
-
if (role === "assistant" && finish) {
|
|
4704
|
-
log(`[${HOOK_NAME}] Assistant turn finished`, { sessionID, finish });
|
|
4705
|
-
return;
|
|
4620
|
+
if (role === "assistant") {
|
|
4621
|
+
cancelCountdown(sessionID);
|
|
4706
4622
|
}
|
|
4707
4623
|
return;
|
|
4708
4624
|
}
|
|
@@ -4711,26 +4627,22 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
4711
4627
|
const sessionID = info?.sessionID;
|
|
4712
4628
|
const role = info?.role;
|
|
4713
4629
|
if (sessionID && role === "assistant") {
|
|
4714
|
-
|
|
4630
|
+
cancelCountdown(sessionID);
|
|
4715
4631
|
}
|
|
4716
4632
|
return;
|
|
4717
4633
|
}
|
|
4718
4634
|
if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
|
|
4719
4635
|
const sessionID = props?.sessionID;
|
|
4720
4636
|
if (sessionID) {
|
|
4721
|
-
|
|
4637
|
+
cancelCountdown(sessionID);
|
|
4722
4638
|
}
|
|
4723
4639
|
return;
|
|
4724
4640
|
}
|
|
4725
4641
|
if (event.type === "session.deleted") {
|
|
4726
4642
|
const sessionInfo = props?.info;
|
|
4727
4643
|
if (sessionInfo?.id) {
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
clearTimer(state2);
|
|
4731
|
-
}
|
|
4732
|
-
sessions.delete(sessionInfo.id);
|
|
4733
|
-
log(`[${HOOK_NAME}] Session deleted, state cleaned up`, { sessionID: sessionInfo.id });
|
|
4644
|
+
cleanup(sessionInfo.id);
|
|
4645
|
+
log(`[${HOOK_NAME}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
4734
4646
|
}
|
|
4735
4647
|
return;
|
|
4736
4648
|
}
|
|
@@ -11080,8 +10992,11 @@ function createEmptyMessageSanitizerHook() {
|
|
|
11080
10992
|
return {
|
|
11081
10993
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
11082
10994
|
const { messages } = output;
|
|
11083
|
-
for (
|
|
11084
|
-
|
|
10995
|
+
for (let i = 0;i < messages.length; i++) {
|
|
10996
|
+
const message = messages[i];
|
|
10997
|
+
const isLastMessage = i === messages.length - 1;
|
|
10998
|
+
const isAssistant = message.info.role === "assistant";
|
|
10999
|
+
if (isLastMessage && isAssistant)
|
|
11085
11000
|
continue;
|
|
11086
11001
|
const parts = message.parts;
|
|
11087
11002
|
if (!hasValidContent(parts)) {
|
|
@@ -13135,6 +13050,7 @@ Generate comprehensive AGENTS.md files across project hierarchy. Combines root-l
|
|
|
13135
13050
|
- **Predict-then-Compare**: Predict standard \u2192 find actual \u2192 document ONLY deviations
|
|
13136
13051
|
- **Hierarchy Aware**: Parent covers general, children cover specific
|
|
13137
13052
|
- **No Redundancy**: Child AGENTS.md NEVER repeats parent content
|
|
13053
|
+
- **LSP-First**: Use LSP tools for accurate code intelligence when available (semantic > text search)
|
|
13138
13054
|
|
|
13139
13055
|
---
|
|
13140
13056
|
|
|
@@ -13197,6 +13113,53 @@ background_task(agent="explore", prompt="Build/CI: FIND .github/workflows, Makef
|
|
|
13197
13113
|
background_task(agent="explore", prompt="Test patterns: FIND pytest.ini, jest.config, test structure \u2192 REPORT unique testing conventions")
|
|
13198
13114
|
\`\`\`
|
|
13199
13115
|
|
|
13116
|
+
### Code Intelligence Analysis (LSP tools - run in parallel)
|
|
13117
|
+
|
|
13118
|
+
LSP provides semantic understanding beyond text search. Use for accurate code mapping.
|
|
13119
|
+
|
|
13120
|
+
\`\`\`
|
|
13121
|
+
# Step 1: Check LSP availability
|
|
13122
|
+
lsp_servers() # Verify language server is available
|
|
13123
|
+
|
|
13124
|
+
# Step 2: Analyze entry point files (run in parallel)
|
|
13125
|
+
# Find entry points first, then analyze each with lsp_document_symbols
|
|
13126
|
+
lsp_document_symbols(filePath="src/index.ts") # Main entry
|
|
13127
|
+
lsp_document_symbols(filePath="src/main.py") # Python entry
|
|
13128
|
+
lsp_document_symbols(filePath="cmd/main.go") # Go entry
|
|
13129
|
+
|
|
13130
|
+
# Step 3: Discover key symbols across workspace (run in parallel)
|
|
13131
|
+
lsp_workspace_symbols(filePath=".", query="class") # All classes
|
|
13132
|
+
lsp_workspace_symbols(filePath=".", query="interface") # All interfaces
|
|
13133
|
+
lsp_workspace_symbols(filePath=".", query="function") # Top-level functions
|
|
13134
|
+
lsp_workspace_symbols(filePath=".", query="type") # Type definitions
|
|
13135
|
+
|
|
13136
|
+
# Step 4: Analyze symbol centrality (for top 5-10 key symbols)
|
|
13137
|
+
# High reference count = central/important concept
|
|
13138
|
+
lsp_find_references(filePath="src/index.ts", line=X, character=Y) # Main export
|
|
13139
|
+
\`\`\`
|
|
13140
|
+
|
|
13141
|
+
#### LSP Analysis Output Format
|
|
13142
|
+
|
|
13143
|
+
\`\`\`
|
|
13144
|
+
CODE_INTELLIGENCE = {
|
|
13145
|
+
entry_points: [
|
|
13146
|
+
{ file: "src/index.ts", exports: ["Plugin", "createHook"], symbol_count: 12 }
|
|
13147
|
+
],
|
|
13148
|
+
key_symbols: [
|
|
13149
|
+
{ name: "Plugin", type: "class", file: "src/index.ts", refs: 45, role: "Central orchestrator" },
|
|
13150
|
+
{ name: "createHook", type: "function", file: "src/utils.ts", refs: 23, role: "Hook factory" }
|
|
13151
|
+
],
|
|
13152
|
+
module_boundaries: [
|
|
13153
|
+
{ dir: "src/hooks", exports: 21, imports_from: ["shared/"] },
|
|
13154
|
+
{ dir: "src/tools", exports: 15, imports_from: ["shared/", "hooks/"] }
|
|
13155
|
+
]
|
|
13156
|
+
}
|
|
13157
|
+
\`\`\`
|
|
13158
|
+
|
|
13159
|
+
<critical>
|
|
13160
|
+
**LSP Fallback**: If LSP unavailable (no server installed), skip this section and rely on explore agents + AST-grep patterns.
|
|
13161
|
+
</critical>
|
|
13162
|
+
|
|
13200
13163
|
</parallel-tasks>
|
|
13201
13164
|
|
|
13202
13165
|
**Collect all results. Mark "p1-analysis" as completed.**
|
|
@@ -13209,13 +13172,35 @@ background_task(agent="explore", prompt="Test patterns: FIND pytest.ini, jest.co
|
|
|
13209
13172
|
|
|
13210
13173
|
### Scoring Matrix
|
|
13211
13174
|
|
|
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 |
|
|
13175
|
+
| Factor | Weight | Threshold | Source |
|
|
13176
|
+
|--------|--------|-----------|--------|
|
|
13177
|
+
| File count | 3x | >20 files = high | bash |
|
|
13178
|
+
| Subdirectory count | 2x | >5 subdirs = high | bash |
|
|
13179
|
+
| Code file ratio | 2x | >70% code = high | bash |
|
|
13180
|
+
| Unique patterns | 1x | Has own config | explore |
|
|
13181
|
+
| Module boundary | 2x | Has __init__.py/index.ts | bash |
|
|
13182
|
+
| **Symbol density** | 2x | >30 symbols = high | LSP |
|
|
13183
|
+
| **Export count** | 2x | >10 exports = high | LSP |
|
|
13184
|
+
| **Reference centrality** | 3x | Symbols with >20 refs | LSP |
|
|
13185
|
+
|
|
13186
|
+
<lsp-scoring>
|
|
13187
|
+
**LSP-Enhanced Scoring** (if available):
|
|
13188
|
+
|
|
13189
|
+
\`\`\`
|
|
13190
|
+
For each directory in candidates:
|
|
13191
|
+
symbols = lsp_document_symbols(dir/index.ts or dir/__init__.py)
|
|
13192
|
+
|
|
13193
|
+
symbol_score = len(symbols) > 30 ? 6 : len(symbols) > 15 ? 3 : 0
|
|
13194
|
+
export_score = count(exported symbols) > 10 ? 4 : 0
|
|
13195
|
+
|
|
13196
|
+
# Check if this module is central (many things depend on it)
|
|
13197
|
+
for each exported symbol:
|
|
13198
|
+
refs = lsp_find_references(symbol)
|
|
13199
|
+
if refs > 20: centrality_score += 3
|
|
13200
|
+
|
|
13201
|
+
total_score += symbol_score + export_score + centrality_score
|
|
13202
|
+
\`\`\`
|
|
13203
|
+
</lsp-scoring>
|
|
13219
13204
|
|
|
13220
13205
|
### Decision Rules
|
|
13221
13206
|
|
|
@@ -13273,6 +13258,28 @@ Root AGENTS.md gets **full treatment** with Predict-then-Compare synthesis.
|
|
|
13273
13258
|
|------|----------|-------|
|
|
13274
13259
|
| Add feature X | \\\`src/x/\\\` | {pattern hint} |
|
|
13275
13260
|
|
|
13261
|
+
## CODE MAP
|
|
13262
|
+
|
|
13263
|
+
{Generated from LSP analysis - shows key symbols and their relationships}
|
|
13264
|
+
|
|
13265
|
+
| Symbol | Type | Location | Refs | Role |
|
|
13266
|
+
|--------|------|----------|------|------|
|
|
13267
|
+
| {MainClass} | Class | \\\`src/index.ts\\\` | {N} | {Central orchestrator} |
|
|
13268
|
+
| {createX} | Function | \\\`src/utils.ts\\\` | {N} | {Factory pattern} |
|
|
13269
|
+
| {Config} | Interface | \\\`src/types.ts\\\` | {N} | {Configuration contract} |
|
|
13270
|
+
|
|
13271
|
+
### Module Dependencies
|
|
13272
|
+
|
|
13273
|
+
\\\`\\\`\\\`
|
|
13274
|
+
{entry} \u2500\u2500imports\u2500\u2500> {core/}
|
|
13275
|
+
\u2502 \u2502
|
|
13276
|
+
\u2514\u2500\u2500imports\u2500\u2500> {utils/} <\u2500\u2500imports\u2500\u2500 {features/}
|
|
13277
|
+
\\\`\\\`\\\`
|
|
13278
|
+
|
|
13279
|
+
<code-map-note>
|
|
13280
|
+
**Skip CODE MAP if**: LSP unavailable OR project too small (<10 files) OR no clear module boundaries.
|
|
13281
|
+
</code-map-note>
|
|
13282
|
+
|
|
13276
13283
|
## CONVENTIONS
|
|
13277
13284
|
|
|
13278
13285
|
{ONLY deviations from standard - skip generic advice}
|
|
@@ -13413,7 +13420,10 @@ Hierarchy:
|
|
|
13413
13420
|
- **Generic content**: Remove anything that applies to ALL projects
|
|
13414
13421
|
- **Sequential execution**: MUST use parallel agents
|
|
13415
13422
|
- **Deep nesting**: Rarely need AGENTS.md at depth 4+
|
|
13416
|
-
- **Verbose style**: "This directory contains..." \u2192 just list it
|
|
13423
|
+
- **Verbose style**: "This directory contains..." \u2192 just list it
|
|
13424
|
+
- **Ignoring LSP**: If LSP available, USE IT - semantic analysis > text grep
|
|
13425
|
+
- **LSP without fallback**: Always have explore agent backup if LSP unavailable
|
|
13426
|
+
- **Over-referencing**: Don't trace refs for EVERY symbol - focus on exports only`;
|
|
13417
13427
|
|
|
13418
13428
|
// src/features/builtin-commands/commands.ts
|
|
13419
13429
|
var BUILTIN_COMMAND_DEFINITIONS = {
|
|
@@ -14446,6 +14456,10 @@ function isServerInstalled(command) {
|
|
|
14446
14456
|
if (command.length === 0)
|
|
14447
14457
|
return false;
|
|
14448
14458
|
const cmd = command[0];
|
|
14459
|
+
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
14460
|
+
if (existsSync34(cmd))
|
|
14461
|
+
return true;
|
|
14462
|
+
}
|
|
14449
14463
|
const isWindows2 = process.platform === "win32";
|
|
14450
14464
|
const ext = isWindows2 ? ".exe" : "";
|
|
14451
14465
|
const pathEnv = process.env.PATH || "";
|
|
@@ -14470,6 +14484,9 @@ function isServerInstalled(command) {
|
|
|
14470
14484
|
return true;
|
|
14471
14485
|
}
|
|
14472
14486
|
}
|
|
14487
|
+
if (cmd === "bun" || cmd === "node") {
|
|
14488
|
+
return true;
|
|
14489
|
+
}
|
|
14473
14490
|
return false;
|
|
14474
14491
|
}
|
|
14475
14492
|
function getAllServers() {
|
|
@@ -28558,7 +28575,7 @@ var ast_grep_replace = tool({
|
|
|
28558
28575
|
}
|
|
28559
28576
|
});
|
|
28560
28577
|
// src/tools/grep/cli.ts
|
|
28561
|
-
var {spawn:
|
|
28578
|
+
var {spawn: spawn9 } = globalThis.Bun;
|
|
28562
28579
|
|
|
28563
28580
|
// src/tools/grep/constants.ts
|
|
28564
28581
|
import { existsSync as existsSync40 } from "fs";
|
|
@@ -28568,6 +28585,31 @@ import { spawnSync } from "child_process";
|
|
|
28568
28585
|
// src/tools/grep/downloader.ts
|
|
28569
28586
|
import { existsSync as existsSync39, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync14 } from "fs";
|
|
28570
28587
|
import { join as join47 } from "path";
|
|
28588
|
+
var {spawn: spawn8 } = globalThis.Bun;
|
|
28589
|
+
function findFileRecursive(dir, filename) {
|
|
28590
|
+
try {
|
|
28591
|
+
const entries = readdirSync14(dir, { withFileTypes: true, recursive: true });
|
|
28592
|
+
for (const entry of entries) {
|
|
28593
|
+
if (entry.isFile() && entry.name === filename) {
|
|
28594
|
+
return join47(entry.parentPath ?? dir, entry.name);
|
|
28595
|
+
}
|
|
28596
|
+
}
|
|
28597
|
+
} catch {
|
|
28598
|
+
return null;
|
|
28599
|
+
}
|
|
28600
|
+
return null;
|
|
28601
|
+
}
|
|
28602
|
+
var RG_VERSION = "14.1.1";
|
|
28603
|
+
var PLATFORM_CONFIG = {
|
|
28604
|
+
"arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" },
|
|
28605
|
+
"arm64-linux": { platform: "aarch64-unknown-linux-gnu", extension: "tar.gz" },
|
|
28606
|
+
"x64-darwin": { platform: "x86_64-apple-darwin", extension: "tar.gz" },
|
|
28607
|
+
"x64-linux": { platform: "x86_64-unknown-linux-musl", extension: "tar.gz" },
|
|
28608
|
+
"x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" }
|
|
28609
|
+
};
|
|
28610
|
+
function getPlatformKey() {
|
|
28611
|
+
return `${process.arch}-${process.platform}`;
|
|
28612
|
+
}
|
|
28571
28613
|
function getInstallDir() {
|
|
28572
28614
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
28573
28615
|
return join47(homeDir, ".cache", "oh-my-opencode", "bin");
|
|
@@ -28576,6 +28618,110 @@ function getRgPath() {
|
|
|
28576
28618
|
const isWindows2 = process.platform === "win32";
|
|
28577
28619
|
return join47(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
|
|
28578
28620
|
}
|
|
28621
|
+
async function downloadFile(url2, destPath) {
|
|
28622
|
+
const response2 = await fetch(url2);
|
|
28623
|
+
if (!response2.ok) {
|
|
28624
|
+
throw new Error(`Failed to download: ${response2.status} ${response2.statusText}`);
|
|
28625
|
+
}
|
|
28626
|
+
const buffer = await response2.arrayBuffer();
|
|
28627
|
+
await Bun.write(destPath, buffer);
|
|
28628
|
+
}
|
|
28629
|
+
async function extractTarGz2(archivePath, destDir) {
|
|
28630
|
+
const platformKey = getPlatformKey();
|
|
28631
|
+
const args = ["tar", "-xzf", archivePath, "--strip-components=1"];
|
|
28632
|
+
if (platformKey.endsWith("-darwin")) {
|
|
28633
|
+
args.push("--include=*/rg");
|
|
28634
|
+
} else if (platformKey.endsWith("-linux")) {
|
|
28635
|
+
args.push("--wildcards", "*/rg");
|
|
28636
|
+
}
|
|
28637
|
+
const proc = spawn8(args, {
|
|
28638
|
+
cwd: destDir,
|
|
28639
|
+
stdout: "pipe",
|
|
28640
|
+
stderr: "pipe"
|
|
28641
|
+
});
|
|
28642
|
+
const exitCode = await proc.exited;
|
|
28643
|
+
if (exitCode !== 0) {
|
|
28644
|
+
const stderr = await new Response(proc.stderr).text();
|
|
28645
|
+
throw new Error(`Failed to extract tar.gz: ${stderr}`);
|
|
28646
|
+
}
|
|
28647
|
+
}
|
|
28648
|
+
async function extractZipWindows(archivePath, destDir) {
|
|
28649
|
+
const proc = spawn8(["powershell", "-Command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`], { stdout: "pipe", stderr: "pipe" });
|
|
28650
|
+
const exitCode = await proc.exited;
|
|
28651
|
+
if (exitCode !== 0) {
|
|
28652
|
+
throw new Error("Failed to extract zip with PowerShell");
|
|
28653
|
+
}
|
|
28654
|
+
const foundPath = findFileRecursive(destDir, "rg.exe");
|
|
28655
|
+
if (foundPath) {
|
|
28656
|
+
const destPath = join47(destDir, "rg.exe");
|
|
28657
|
+
if (foundPath !== destPath) {
|
|
28658
|
+
const { renameSync } = await import("fs");
|
|
28659
|
+
renameSync(foundPath, destPath);
|
|
28660
|
+
}
|
|
28661
|
+
}
|
|
28662
|
+
}
|
|
28663
|
+
async function extractZipUnix(archivePath, destDir) {
|
|
28664
|
+
const proc = spawn8(["unzip", "-o", archivePath, "-d", destDir], {
|
|
28665
|
+
stdout: "pipe",
|
|
28666
|
+
stderr: "pipe"
|
|
28667
|
+
});
|
|
28668
|
+
const exitCode = await proc.exited;
|
|
28669
|
+
if (exitCode !== 0) {
|
|
28670
|
+
throw new Error("Failed to extract zip");
|
|
28671
|
+
}
|
|
28672
|
+
const foundPath = findFileRecursive(destDir, "rg");
|
|
28673
|
+
if (foundPath) {
|
|
28674
|
+
const destPath = join47(destDir, "rg");
|
|
28675
|
+
if (foundPath !== destPath) {
|
|
28676
|
+
const { renameSync } = await import("fs");
|
|
28677
|
+
renameSync(foundPath, destPath);
|
|
28678
|
+
}
|
|
28679
|
+
}
|
|
28680
|
+
}
|
|
28681
|
+
async function extractZip3(archivePath, destDir) {
|
|
28682
|
+
if (process.platform === "win32") {
|
|
28683
|
+
await extractZipWindows(archivePath, destDir);
|
|
28684
|
+
} else {
|
|
28685
|
+
await extractZipUnix(archivePath, destDir);
|
|
28686
|
+
}
|
|
28687
|
+
}
|
|
28688
|
+
async function downloadAndInstallRipgrep() {
|
|
28689
|
+
const platformKey = getPlatformKey();
|
|
28690
|
+
const config3 = PLATFORM_CONFIG[platformKey];
|
|
28691
|
+
if (!config3) {
|
|
28692
|
+
throw new Error(`Unsupported platform: ${platformKey}`);
|
|
28693
|
+
}
|
|
28694
|
+
const installDir = getInstallDir();
|
|
28695
|
+
const rgPath = getRgPath();
|
|
28696
|
+
if (existsSync39(rgPath)) {
|
|
28697
|
+
return rgPath;
|
|
28698
|
+
}
|
|
28699
|
+
mkdirSync11(installDir, { recursive: true });
|
|
28700
|
+
const filename = `ripgrep-${RG_VERSION}-${config3.platform}.${config3.extension}`;
|
|
28701
|
+
const url2 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
|
|
28702
|
+
const archivePath = join47(installDir, filename);
|
|
28703
|
+
try {
|
|
28704
|
+
await downloadFile(url2, archivePath);
|
|
28705
|
+
if (config3.extension === "tar.gz") {
|
|
28706
|
+
await extractTarGz2(archivePath, installDir);
|
|
28707
|
+
} else {
|
|
28708
|
+
await extractZip3(archivePath, installDir);
|
|
28709
|
+
}
|
|
28710
|
+
if (process.platform !== "win32") {
|
|
28711
|
+
chmodSync3(rgPath, 493);
|
|
28712
|
+
}
|
|
28713
|
+
if (!existsSync39(rgPath)) {
|
|
28714
|
+
throw new Error("ripgrep binary not found after extraction");
|
|
28715
|
+
}
|
|
28716
|
+
return rgPath;
|
|
28717
|
+
} finally {
|
|
28718
|
+
if (existsSync39(archivePath)) {
|
|
28719
|
+
try {
|
|
28720
|
+
unlinkSync10(archivePath);
|
|
28721
|
+
} catch {}
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
}
|
|
28579
28725
|
function getInstalledRipgrepPath() {
|
|
28580
28726
|
const rgPath = getRgPath();
|
|
28581
28727
|
return existsSync39(rgPath) ? rgPath : null;
|
|
@@ -28583,6 +28729,7 @@ function getInstalledRipgrepPath() {
|
|
|
28583
28729
|
|
|
28584
28730
|
// src/tools/grep/constants.ts
|
|
28585
28731
|
var cachedCli = null;
|
|
28732
|
+
var autoInstallAttempted = false;
|
|
28586
28733
|
function findExecutable(name) {
|
|
28587
28734
|
const isWindows2 = process.platform === "win32";
|
|
28588
28735
|
const cmd = isWindows2 ? "where" : "which";
|
|
@@ -28601,6 +28748,7 @@ function getOpenCodeBundledRg() {
|
|
|
28601
28748
|
const isWindows2 = process.platform === "win32";
|
|
28602
28749
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
28603
28750
|
const candidates = [
|
|
28751
|
+
join48(getDataDir(), "opencode", "bin", rgName),
|
|
28604
28752
|
join48(execDir, rgName),
|
|
28605
28753
|
join48(execDir, "bin", rgName),
|
|
28606
28754
|
join48(execDir, "..", "bin", rgName),
|
|
@@ -28639,6 +28787,23 @@ function resolveGrepCli() {
|
|
|
28639
28787
|
cachedCli = { path: "rg", backend: "rg" };
|
|
28640
28788
|
return cachedCli;
|
|
28641
28789
|
}
|
|
28790
|
+
async function resolveGrepCliWithAutoInstall() {
|
|
28791
|
+
const current = resolveGrepCli();
|
|
28792
|
+
if (current.backend === "rg") {
|
|
28793
|
+
return current;
|
|
28794
|
+
}
|
|
28795
|
+
if (autoInstallAttempted) {
|
|
28796
|
+
return current;
|
|
28797
|
+
}
|
|
28798
|
+
autoInstallAttempted = true;
|
|
28799
|
+
try {
|
|
28800
|
+
const rgPath = await downloadAndInstallRipgrep();
|
|
28801
|
+
cachedCli = { path: rgPath, backend: "rg" };
|
|
28802
|
+
return cachedCli;
|
|
28803
|
+
} catch {
|
|
28804
|
+
return current;
|
|
28805
|
+
}
|
|
28806
|
+
}
|
|
28642
28807
|
var DEFAULT_MAX_DEPTH = 20;
|
|
28643
28808
|
var DEFAULT_MAX_FILESIZE = "10M";
|
|
28644
28809
|
var DEFAULT_MAX_COUNT = 500;
|
|
@@ -28753,7 +28918,7 @@ async function runRg(options) {
|
|
|
28753
28918
|
}
|
|
28754
28919
|
const paths = options.paths?.length ? options.paths : ["."];
|
|
28755
28920
|
args.push(...paths);
|
|
28756
|
-
const proc =
|
|
28921
|
+
const proc = spawn9([cli.path, ...args], {
|
|
28757
28922
|
stdout: "pipe",
|
|
28758
28923
|
stderr: "pipe"
|
|
28759
28924
|
});
|
|
@@ -28855,7 +29020,7 @@ var grep = tool({
|
|
|
28855
29020
|
});
|
|
28856
29021
|
|
|
28857
29022
|
// src/tools/glob/cli.ts
|
|
28858
|
-
var {spawn:
|
|
29023
|
+
var {spawn: spawn10 } = globalThis.Bun;
|
|
28859
29024
|
|
|
28860
29025
|
// src/tools/glob/constants.ts
|
|
28861
29026
|
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
@@ -28893,6 +29058,19 @@ function buildFindArgs(options) {
|
|
|
28893
29058
|
}
|
|
28894
29059
|
return args;
|
|
28895
29060
|
}
|
|
29061
|
+
function buildPowerShellCommand(options) {
|
|
29062
|
+
const maxDepth = Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH2, DEFAULT_MAX_DEPTH2);
|
|
29063
|
+
const paths = options.paths?.length ? options.paths : ["."];
|
|
29064
|
+
const searchPath = paths[0] || ".";
|
|
29065
|
+
const escapedPath = searchPath.replace(/'/g, "''");
|
|
29066
|
+
const escapedPattern = options.pattern.replace(/'/g, "''");
|
|
29067
|
+
let psCommand = `Get-ChildItem -Path '${escapedPath}' -File -Recurse -Depth ${maxDepth - 1} -Filter '${escapedPattern}'`;
|
|
29068
|
+
if (options.hidden) {
|
|
29069
|
+
psCommand += " -Force";
|
|
29070
|
+
}
|
|
29071
|
+
psCommand += " -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName";
|
|
29072
|
+
return ["powershell", "-NoProfile", "-Command", psCommand];
|
|
29073
|
+
}
|
|
28896
29074
|
async function getFileMtime(filePath) {
|
|
28897
29075
|
try {
|
|
28898
29076
|
const stats = await stat(filePath);
|
|
@@ -28901,21 +29079,33 @@ async function getFileMtime(filePath) {
|
|
|
28901
29079
|
return 0;
|
|
28902
29080
|
}
|
|
28903
29081
|
}
|
|
28904
|
-
async function runRgFiles(options) {
|
|
28905
|
-
const cli = resolveGrepCli();
|
|
29082
|
+
async function runRgFiles(options, resolvedCli) {
|
|
29083
|
+
const cli = resolvedCli ?? resolveGrepCli();
|
|
28906
29084
|
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
|
|
28907
29085
|
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
28908
29086
|
const isRg = cli.backend === "rg";
|
|
28909
|
-
const
|
|
28910
|
-
|
|
29087
|
+
const isWindows2 = process.platform === "win32";
|
|
29088
|
+
let command;
|
|
29089
|
+
let cwd;
|
|
28911
29090
|
if (isRg) {
|
|
29091
|
+
const args = buildRgArgs2(options);
|
|
29092
|
+
const paths = options.paths?.length ? options.paths : ["."];
|
|
28912
29093
|
args.push(...paths);
|
|
29094
|
+
command = [cli.path, ...args];
|
|
29095
|
+
cwd = undefined;
|
|
29096
|
+
} else if (isWindows2) {
|
|
29097
|
+
command = buildPowerShellCommand(options);
|
|
29098
|
+
cwd = undefined;
|
|
29099
|
+
} else {
|
|
29100
|
+
const args = buildFindArgs(options);
|
|
29101
|
+
const paths = options.paths?.length ? options.paths : ["."];
|
|
29102
|
+
cwd = paths[0] || ".";
|
|
29103
|
+
command = [cli.path, ...args];
|
|
28913
29104
|
}
|
|
28914
|
-
const
|
|
28915
|
-
const proc = spawn9([cli.path, ...args], {
|
|
29105
|
+
const proc = spawn10(command, {
|
|
28916
29106
|
stdout: "pipe",
|
|
28917
29107
|
stderr: "pipe",
|
|
28918
|
-
cwd
|
|
29108
|
+
cwd
|
|
28919
29109
|
});
|
|
28920
29110
|
const timeoutPromise = new Promise((_, reject) => {
|
|
28921
29111
|
const id = setTimeout(() => {
|
|
@@ -28947,7 +29137,14 @@ async function runRgFiles(options) {
|
|
|
28947
29137
|
truncated = true;
|
|
28948
29138
|
break;
|
|
28949
29139
|
}
|
|
28950
|
-
|
|
29140
|
+
let filePath;
|
|
29141
|
+
if (isRg) {
|
|
29142
|
+
filePath = line;
|
|
29143
|
+
} else if (isWindows2) {
|
|
29144
|
+
filePath = line.trim();
|
|
29145
|
+
} else {
|
|
29146
|
+
filePath = `${cwd}/${line}`;
|
|
29147
|
+
}
|
|
28951
29148
|
const mtime = await getFileMtime(filePath);
|
|
28952
29149
|
files.push({ path: filePath, mtime });
|
|
28953
29150
|
}
|
|
@@ -28998,11 +29195,12 @@ var glob = tool({
|
|
|
28998
29195
|
},
|
|
28999
29196
|
execute: async (args) => {
|
|
29000
29197
|
try {
|
|
29198
|
+
const cli = await resolveGrepCliWithAutoInstall();
|
|
29001
29199
|
const paths = args.path ? [args.path] : undefined;
|
|
29002
29200
|
const result = await runRgFiles({
|
|
29003
29201
|
pattern: args.pattern,
|
|
29004
29202
|
paths
|
|
29005
|
-
});
|
|
29203
|
+
}, cli);
|
|
29006
29204
|
return formatGlobResult(result);
|
|
29007
29205
|
} catch (e) {
|
|
29008
29206
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -29718,14 +29916,14 @@ var INTERACTIVE_BASH_DESCRIPTION = `Execute tmux commands. Use "omo-{name}" sess
|
|
|
29718
29916
|
Blocked (use bash instead): capture-pane, save-buffer, show-buffer, pipe-pane.`;
|
|
29719
29917
|
|
|
29720
29918
|
// src/tools/interactive-bash/utils.ts
|
|
29721
|
-
var {spawn:
|
|
29919
|
+
var {spawn: spawn11 } = globalThis.Bun;
|
|
29722
29920
|
var tmuxPath = null;
|
|
29723
29921
|
var initPromise3 = null;
|
|
29724
29922
|
async function findTmuxPath() {
|
|
29725
29923
|
const isWindows2 = process.platform === "win32";
|
|
29726
29924
|
const cmd = isWindows2 ? "where" : "which";
|
|
29727
29925
|
try {
|
|
29728
|
-
const proc =
|
|
29926
|
+
const proc = spawn11([cmd, "tmux"], {
|
|
29729
29927
|
stdout: "pipe",
|
|
29730
29928
|
stderr: "pipe"
|
|
29731
29929
|
});
|
|
@@ -29739,7 +29937,7 @@ async function findTmuxPath() {
|
|
|
29739
29937
|
if (!path7) {
|
|
29740
29938
|
return null;
|
|
29741
29939
|
}
|
|
29742
|
-
const verifyProc =
|
|
29940
|
+
const verifyProc = spawn11([path7, "-V"], {
|
|
29743
29941
|
stdout: "pipe",
|
|
29744
29942
|
stderr: "pipe"
|
|
29745
29943
|
});
|