agentquad 0.4.8 → 0.4.10
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/dist-web/assets/{index-DdqC2CwH.css → index-01ijE-nK.css} +1 -1
- package/dist-web/assets/{index-DkI6ZJx_.js → index-BwfZflW8.js} +235 -225
- package/dist-web/index.html +2 -2
- package/package.json +1 -1
- package/src/db.js +12 -7
- package/src/openclaw-hook.js +32 -2
- package/src/routes/ai-terminal.js +15 -0
- package/src/server.js +7 -0
package/dist-web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>AgentQuad</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BwfZflW8.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-01ijE-nK.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/db.js
CHANGED
|
@@ -885,23 +885,28 @@ export function openDb(file = ':memory:') {
|
|
|
885
885
|
if (unboundOnly) where.push('tf.bound_todo_id IS NULL')
|
|
886
886
|
|
|
887
887
|
if (q && ftsAvailable) {
|
|
888
|
-
// trigram tokenizer 要求 ≥3 字才能走 MATCH;<3
|
|
888
|
+
// trigram tokenizer 要求 ≥3 字才能走 MATCH;<3 字没有 FTS 索引可用,必须 LIKE 全表扫。
|
|
889
|
+
// 扫 transcript_fts.content(每轮一行,几万行)在大库上要 5s 级;这里只扫 transcript_files.first_user_prompt
|
|
890
|
+
// (每会话一行,~1500 行,100x 体量缩水) — 短查询场景里用户基本只关心首轮命中,可以接受漏掉 turn 内的匹配。
|
|
889
891
|
if (q.length < 3) {
|
|
890
892
|
const like = `%${q.replace(/[\\%_]/g, s => '\\' + s)}%`
|
|
891
|
-
where.push(`tf.
|
|
893
|
+
where.push(`tf.first_user_prompt LIKE ? ESCAPE '\\'`)
|
|
892
894
|
params.push(like)
|
|
893
895
|
const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : ''
|
|
894
896
|
const total = db.prepare(`SELECT COUNT(*) AS n FROM transcript_files tf ${whereSql}`).get(...params).n
|
|
895
897
|
const rows = db.prepare(`
|
|
896
|
-
SELECT tf.*, (
|
|
897
|
-
SELECT SUBSTR(content, MAX(1, INSTR(content, ?) - 16), 64)
|
|
898
|
-
FROM transcript_fts WHERE file_id = tf.id AND content LIKE ? ESCAPE '\\' LIMIT 1
|
|
899
|
-
) AS snippet
|
|
898
|
+
SELECT tf.*, SUBSTR(tf.first_user_prompt, MAX(1, INSTR(tf.first_user_prompt, ?) - 16), 64) AS snippet
|
|
900
899
|
FROM transcript_files tf
|
|
901
900
|
${whereSql}
|
|
902
901
|
ORDER BY tf.started_at DESC
|
|
903
902
|
LIMIT ? OFFSET ?
|
|
904
|
-
`).all(q,
|
|
903
|
+
`).all(q, ...params, limit, offset)
|
|
904
|
+
// SQLite FTS 的 snippet() 只能走 MATCH,<3 字路径没法用它包裹高亮,这里在 JS 里手工补 <mark>,
|
|
905
|
+
// 让前端 dangerouslySetInnerHTML 渲染出和长查询一致的高亮效果。
|
|
906
|
+
const markRe = new RegExp(q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi')
|
|
907
|
+
for (const row of rows) {
|
|
908
|
+
if (row.snippet) row.snippet = row.snippet.replace(markRe, m => `<mark>${m}</mark>`)
|
|
909
|
+
}
|
|
905
910
|
return { total, items: rows }
|
|
906
911
|
}
|
|
907
912
|
const ftsQuery = q.replace(/"/g, '""')
|
package/src/openclaw-hook.js
CHANGED
|
@@ -336,6 +336,22 @@ export function createOpenClawHookHandler(deps = {}) {
|
|
|
336
336
|
return getSessionPermissionMode(sessionId) !== 'bypass'
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
// detector 路径专用:bypass guard 让位。理由是 detector 三层守卫(anchor +
|
|
340
|
+
// ≥2 数字选项 + Esc/Tab footer)证明 Claude TUI 实际**正在**渲染权限框 ——
|
|
341
|
+
// 不管 AgentQuad 记的是不是 bypass。
|
|
342
|
+
// 真实场景:主人启动 session 时选了 bypass,之后在 TUI 内用 /permission-mode
|
|
343
|
+
// 切到 default。AgentQuad 没法感知 TUI 内部模式改动(hook / jsonl 都不 echo
|
|
344
|
+
// 这条信息),session.permissionMode 永远停在 'bypass'。如果继续按 bypass guard
|
|
345
|
+
// 拒推,前端能看见 "AI 等待授权" 卡片但 IM 永远收不到 —— 跟原始 bug 同症状。
|
|
346
|
+
// handleClaude (Notification hook) 仍走老 isPermissionReminderEligible,因为
|
|
347
|
+
// 那条路径在真 bypass 下 fire 多半是 idle 通知噪声,吞掉是对的。
|
|
348
|
+
function isDetectorPushEligible(sessionId) {
|
|
349
|
+
if (!sessionId) return false
|
|
350
|
+
if (!resolveExplicitInteractiveRoute(sessionId)) return false
|
|
351
|
+
if (suppressPermissionNotifications()) return false
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
354
|
+
|
|
339
355
|
function permissionShortId(sessionId) {
|
|
340
356
|
return String(sessionId || '').slice(-4)
|
|
341
357
|
}
|
|
@@ -621,6 +637,11 @@ export function createOpenClawHookHandler(deps = {}) {
|
|
|
621
637
|
if (!sessionId) return { ok: false, reason: 'no_sessionId' }
|
|
622
638
|
const sess = aiTerminal?.sessions?.get(sessionId)
|
|
623
639
|
if (!sess) return { ok: false, reason: 'session_gone' }
|
|
640
|
+
// bridge in-memory route 缺失但 DB 有 → 先恢复,否则 postCard 一样发不出去。
|
|
641
|
+
// handleClaudeDetector 同源处理,保持两条 detector 路径行为一致。
|
|
642
|
+
if (openclaw?.hasExplicitRoute && !openclaw.hasExplicitRoute(sessionId)) {
|
|
643
|
+
restorePersistedRoute(sessionId, sess.todoId)
|
|
644
|
+
}
|
|
624
645
|
// 把 session.status 翻成 pending_confirm —— 前端 deriveAiState 据此显示"待确认"。
|
|
625
646
|
// 信号源是 codex-prompt-detector(已经过 AI self-quoted 过滤),比旧的 PTY 正则路径准。
|
|
626
647
|
try { aiTerminal?.markPendingConfirm?.(sessionId, { source: 'codex-detector', promptText }) } catch { /* ignore */ }
|
|
@@ -658,6 +679,14 @@ export function createOpenClawHookHandler(deps = {}) {
|
|
|
658
679
|
const sess = aiTerminal?.sessions?.get(sessionId)
|
|
659
680
|
if (!sess) return { ok: false, reason: 'session_gone' }
|
|
660
681
|
|
|
682
|
+
// 0) bridge in-memory route 缺失但 DB 有 → 先恢复。否则 isPermissionReminderEligible
|
|
683
|
+
// 会立即 short-circuit 返回 false,IM 永远收不到权限卡片。
|
|
684
|
+
// handleClaude 在自己开头做了同样的事;detector 路径之前漏了这一步,导致
|
|
685
|
+
// resume / mode-switch(spawnSession skipTelegram=true)后第一条权限弹窗被吞。
|
|
686
|
+
if (openclaw?.hasExplicitRoute && !openclaw.hasExplicitRoute(sessionId)) {
|
|
687
|
+
restorePersistedRoute(sessionId, sess.todoId)
|
|
688
|
+
}
|
|
689
|
+
|
|
661
690
|
// 1) 翻状态。markPendingConfirm 默认只接受 running → pending_confirm,但 PTY-detector
|
|
662
691
|
// 要求 anchor + ≥2 数字选项才 emit,假阳性概率极低;显式 allowIdleFlip=true 让它
|
|
663
692
|
// 在 status=idle 时也能翻(覆盖 auto 模式权限框在 Stop hook 后才浮出的场景)。
|
|
@@ -669,8 +698,9 @@ export function createOpenClawHookHandler(deps = {}) {
|
|
|
669
698
|
})
|
|
670
699
|
} catch { /* ignore */ }
|
|
671
700
|
|
|
672
|
-
// 2) IM 推送资格 & cooldown 跟真 Notification 共享 →
|
|
673
|
-
|
|
701
|
+
// 2) IM 推送资格 & cooldown 跟真 Notification 共享 → 不会双推。
|
|
702
|
+
// detector 路径用 isDetectorPushEligible(不查 bypass mode),原因见函数注释。
|
|
703
|
+
if (!isDetectorPushEligible(sessionId)) {
|
|
674
704
|
return { ok: true, action: 'skipped', reason: 'im_push_not_eligible' }
|
|
675
705
|
}
|
|
676
706
|
const cd = notificationCooldownMs()
|
|
@@ -681,6 +681,20 @@ export function createAiTerminal({ db, pty, logDir, defaultCwd, getDefaultCwd, o
|
|
|
681
681
|
}
|
|
682
682
|
} catch { /* 模板查不到就当无 agent */ }
|
|
683
683
|
}
|
|
684
|
+
// Resume / mode-switch 路径:上一条 aiSession 即将被 mergeTodoAiSessions 按
|
|
685
|
+
// (tool, nativeSessionId) 过滤掉(同一个 Claude/Codex 后端只是换了 AgentQuad
|
|
686
|
+
// sessionId)。它身上挂的 larkRoute / telegramRoute 是 IM 推送的唯一来源,
|
|
687
|
+
// 如果不显式继承,新 sessionId 进 DB 时 route 字段就空了 → 后续 detector /
|
|
688
|
+
// Stop hook 调 isPermissionReminderEligible 返回 false → IM 静默丢推送
|
|
689
|
+
// (前端仍能看见 "AI 等待授权",因为它走 markPendingConfirm,不依赖 route)。
|
|
690
|
+
const preservedRoutes = {}
|
|
691
|
+
if (resumeNativeId) {
|
|
692
|
+
const priorAi = (todo.aiSessions || []).find(
|
|
693
|
+
(item) => item?.tool === tool && item?.nativeSessionId === resumeNativeId,
|
|
694
|
+
)
|
|
695
|
+
if (priorAi?.larkRoute) preservedRoutes.larkRoute = priorAi.larkRoute
|
|
696
|
+
if (priorAi?.telegramRoute) preservedRoutes.telegramRoute = priorAi.telegramRoute
|
|
697
|
+
}
|
|
684
698
|
// 3. 一次性把 nativeSessionId 写进 DB(搬进 try 内:失败时不留脏 DB)。
|
|
685
699
|
db.updateTodo(todoId, {
|
|
686
700
|
status: 'ai_running',
|
|
@@ -694,6 +708,7 @@ export function createAiTerminal({ db, pty, logDir, defaultCwd, getDefaultCwd, o
|
|
|
694
708
|
completedAt: null,
|
|
695
709
|
prompt,
|
|
696
710
|
permissionMode: effectivePermissionMode,
|
|
711
|
+
...preservedRoutes,
|
|
697
712
|
...(agentTemplateId ? { agentTemplateId, agentName } : {}),
|
|
698
713
|
...(label ? { label } : {}),
|
|
699
714
|
}),
|
package/src/server.js
CHANGED
|
@@ -882,10 +882,17 @@ export function createServer(opts = {}) {
|
|
|
882
882
|
}
|
|
883
883
|
});
|
|
884
884
|
|
|
885
|
+
// All entries here share the VSCode-family CLI contract: `--new-window <cwd>` opens
|
|
886
|
+
// the workspace, `--goto file:line:col` jumps. New editors only fit here if they
|
|
887
|
+
// honor those two flags — otherwise extend the spawn logic below per-editor.
|
|
885
888
|
const EDITOR_BINS = {
|
|
886
889
|
"trae-cn": "/Applications/Trae CN.app/Contents/Resources/app/bin/trae-cn",
|
|
887
890
|
"trae": "/Applications/Trae.app/Contents/Resources/app/bin/trae",
|
|
888
891
|
"cursor": "/Applications/Cursor.app/Contents/Resources/app/bin/cursor",
|
|
892
|
+
"vscode": "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
893
|
+
"vscode-insiders":
|
|
894
|
+
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code",
|
|
895
|
+
"windsurf": "/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf",
|
|
889
896
|
};
|
|
890
897
|
|
|
891
898
|
app.post("/api/system/open-trae", (req, res) => {
|