chainlesschain 0.162.61 → 0.162.66
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/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-BiB8WfIz.js → AIOps-BeJlvHR1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-NLBhC6jG.js → ActionButton-B93fwcal.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-k62j-xiL.js → Analytics-B-Lc0FRK.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-spr0Sm6J.js → AppLayout-nv2C8TdH.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-C3NHJos3.js → Audit-B4pwb1Oe.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C2V9tGqF.js → Backup-B4HFWkJA.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DeKm11mH.js → BaseInput-BQk1ONWO.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CHZ2CU7x.js → Chat-B2IobFfI.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DEXSa7tC.js → ChatBubbleRenderer-jJL-hGlG.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-q6E9VeLr.js → Checkbox-BHCU5kit.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen--4w4QpUI.js → Codegen-Bqgq9-0q.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DLOkwTsj.js → Col-BqDf398Z.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B1LxJGfE.js → Community-Cd58ltip.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C_769oQZ.js → Compact-CWa3CY1X.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-zsI0s7vB.js → Compliance-BNJWsGi0.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A1WA6whF.js → Cowork-ChOCC2KD.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-C3JDTyyd.js → Cron-BpWtgfDE.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-D8O6uB86.js → Crosschain-Csy7U94a.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BpOebm5d.js → DID-CIh7lr9T.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Defso6kA.js → Dashboard-DYJUc9Jy.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cv9BrwT_.js → Dropdown-GnUptPAU.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BWKHbh4C.js → EmailListRenderer-sE9mvxjT.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard--m_Ru7Ci.js → FamilyGuardDashboard-Dg7-GHSu.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Bs6ZcAP0.js → Federation-BiH_O7jy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CcAs3Acx.js → FormItemContext-C1AZ_qE-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DI1oL4pK.js → GenericCardRenderer-WpsC5meD.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C27t3-fW.js → Git-L4XGb5Qj.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dr_syXc_.js → Governance-CJ9Gn_A9.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CWM8dIbA.js → Inference-Dlc9Ey87.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph--cFDUZv3.js → KnowledgeGraph-CfndHiBW.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Cnn2_Onf.js → Logs-CSOLZERs.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-4T9ok3Gz.js → Marketplace-Cjiz9wPY.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BQvZwqcN.js → McpTools-C8UNhnTj.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BE9rPkM_.js → Memory-BhOoGXRL.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DJ3j5lXC.js → MobileBridge-rKKcGEvg.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-qasLvYdb.js → MobileProjects-KvGFVl79.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D3CSPTD9.js → Mtc-jyA3mXYt.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DaYVGCN5.js → MtcAudit-BYi6sulR.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Dz1c5r5w.js → Multisig-CWZGD6_3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BIKV_K-a.js → NLProgramming-bOIPDOh5.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-f6t-rmOa.js → Notes-_I6Hs_bJ.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DHLQh8Fy.js → NotificationSettings-Bj9Bcy2A.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CDMZ3o6i.js → OrderTableRenderer-C5zZeOhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DIsL758p.js → Organization-B-SfOynT.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BMM7apnZ.js → Overflow-CG7JBYts.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CTGMmTvi.js → P2P-vpebJYvr.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DWwmm0k1.js → PdhVaultBrowser-CPeKixo2.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Bed5JxMx.js → Permissions-CpflO2Ac.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CIiZhSM5.js → PersonalDataHub-D6CjgWDH.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CtirPodz.js → Pipeline-CgC59gHt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DuXhXhE7.js → Privacy-CoZn6LrI.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DZrnguBl.js → ProjectInit-BmNYdFPv.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-HIltqsJ1.js → ProjectSettings-C13HHOUG.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BWFkePg4.js → Projects-BSHYaYKU.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DEP0Jdvl.js → Providers-Dh6ys5NR.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-T2THoHNx.js → QuickAsk-Cljz9ZIS.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CvbxaSwm.js → Recommend-CEAVAYGZ.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-6Afy6tfp.js → Reputation-C7AxH6cu.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DY8OPWaO.js → Row-D5Jgzbof.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-wDGWb9pZ.js → RssFeed-V6vBnNBE.js} +3 -3
- package/src/assets/web-panel/assets/{Search-D_zHAwZY.js → Search-dfDC6aHa.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Czq7AlGG.js → Security-BOSRXul6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Ac1g0ZcG.js → Services-dsNT3Tra.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DXQ3eeSW.js → Skeleton-_rXFZqCe.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CWRioX4u.js → Skills-WWvbl-N6.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BlHthzfs.js → Sla-BZT_pwjV.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Ct240JmL.js → SpeechSettings-BSVqkQ0F.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BgDIt8Q-.js → SyncSettings-TLHuQW_s.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-8-jiv3Dt.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Dp9QhyIw.js → Templates-D7qs_H3G.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CHTYMxzY.js → Tenant-B4hRLxlc.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-X-NGwLpv.js → Terminal-lrlEETgH.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Bh8jA18j.js → TimelineRenderer-Do8UQaNj.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DWkTd5dv.js → Tokens-wyuwl9gS.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CRgVg6sd.js → Trigger-Ck4j8Emr.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BgWOXd0W.js → Trust-Z07lGZvX.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BlTUB9Y-.js → UkeySign-5rI48ojV.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DI64XgNb.js → VideoEditing-Dm8PV-Ss.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CJ3TNGiG.js → Wallet-BwWYuV0j.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2-rWWoV.js → WebAuthn-CavU3f2i.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-VI9otbaH.js → WorkflowEditor-jj1aB37x.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CgYfiaVh.js → chat-BpRqPqbA.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B9EhRTky.js → colors-BBJU99fO.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cb7bjraa.js → compact-item-t2Elz5Kg.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DlXPeXuj.js → createContext-B_D6Nida.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CmJstpP_.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BbgRfrdf.js → hasIn-D5k1KNpe.js} +1 -1
- package/src/assets/web-panel/assets/{index-ojRAd7Nq.js → index-1lcpLp-e.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoEFFKn3.js → index-2KvtrQkP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BU8hEUyq.js → index-33FQbw3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt-lPYpq.js → index-3TymUGUQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8kB0k3E.js → index-6iwQSswx.js} +1 -1
- package/src/assets/web-panel/assets/{index-c7-Jd6WB.js → index-B2cxsdFe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPaffcT8.js → index-BCpJT0on.js} +1 -1
- package/src/assets/web-panel/assets/{index-p03wNqiP.js → index-BH_HjIO6.js} +1 -1
- package/src/assets/web-panel/assets/{index-1iUK_kAw.js → index-BVEb-kUY.js} +1 -1
- package/src/assets/web-panel/assets/{index-AR-QpAkP.js → index-BaS1rfcr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfSS-U5o.js → index-BfKFGtsC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDzNdZcX.js → index-Bo72gKe8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGx8aO_Y.js → index-C-271Q6Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-2ts5iOIB.js → index-C3alXfss.js} +1 -1
- package/src/assets/web-panel/assets/{index-C95qWAh4.js → index-CB1AFQiL.js} +1 -1
- package/src/assets/web-panel/assets/{index-De59Xat-.js → index-CHyyhgQ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmr31VCO.js → index-CP2Nz1mx.js} +3 -3
- package/src/assets/web-panel/assets/{index-fG-1gXy0.js → index-CT_-tscA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6t-Shqr.js → index-CWwjhqgi.js} +1 -1
- package/src/assets/web-panel/assets/{index-C73WgOc2.js → index-CYdBeNTv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMyzmvtJ.js → index-CgCeon6Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUU9DY4J.js → index-Ctt8xM0M.js} +1 -1
- package/src/assets/web-panel/assets/{index-BD2W-qsS.js → index-CwAKqpJd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ira0HLPr.js → index-CwEJnamf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh6qWb1v.js → index-D8LnFSKZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-Zz9PvD.js → index-DGtbeySv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bk7r1a9x.js → index-DHHTczBZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbLJShJB.js → index-DIgtJB5J.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf9zwbk-.js → index-DLfAmDGs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Caiu2avX.js → index-DRh9m8hf.js} +1 -1
- package/src/assets/web-panel/assets/index-Dric_1LC.js +1 -0
- package/src/assets/web-panel/assets/{index-CHR47Q5B.js → index-DzIq7BlR.js} +1 -1
- package/src/assets/web-panel/assets/{index-OVrh8wTN.js → index-E4x-hzLB.js} +1 -1
- package/src/assets/web-panel/assets/{index-5CrFMQjt.js → index-GSUbdu-w.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dx4sm6dm.js → index-GSpv5udU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkQIyK-V.js → index-NMj4bTp1.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8DB27uJ.js → index-OmJk1MzD.js} +1 -1
- package/src/assets/web-panel/assets/index-eFol7ymc.js +1 -0
- package/src/assets/web-panel/assets/{index-DXxa7PR8.js → index-eVBDpynR.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-JT674ACa.js → initDefaultProps-CuryY55W.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CokflrA9.js → motion-IVsWxV-s.js} +1 -1
- package/src/assets/web-panel/assets/{move-CfMhRpyC.js → move-DNKsQLrH.js} +1 -1
- package/src/assets/web-panel/assets/{omit-ClYc5II5.js → omit-gXu4NluL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CTwEb_8h.js → pickAttrs-CVb4Ykex.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Cb3StU_t.js → placementArrow-BarpAJK0.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-uIxkx5M1.js → responsiveObserve-eaDhQlY1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B9HZBQ7i.js → slide-C8oU8Wlo.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C72bwYl0.js → statusUtils-DrBdj2xb.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BFTtaQb8.js → styleChecker-_Rycq1-Q.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DKl5j41_.js → useFlexGapSupport-BagU5XLY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BUHS6bo3.js → useFs-DZYMOaAQ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D8KrYSq4.js → usePersonalDataHub-CDMZ5QvU.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-JYP-aZDj.js → vnode-Bg7MuEf5.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BFusdxdH.js → zoom-oJIwomWy.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +39 -0
- package/src/commands/insights.js +137 -0
- package/src/commands/review.js +807 -0
- package/src/index.js +4 -0
- package/src/lib/cost-budget.js +109 -0
- package/src/lib/ide-context.js +50 -0
- package/src/lib/personal-data-hub-wiring.js +36 -0
- package/src/lib/session-insights.js +145 -0
- package/src/lib/skill-loader.js +18 -3
- package/src/repl/agent-repl.js +34 -0
- package/src/repl/tasks-status.js +82 -0
- package/src/runtime/agent-core.js +38 -3
- package/src/runtime/headless-runner.js +51 -3
- package/src/runtime/headless-stream.js +62 -4
- package/src/runtime/mcp-config.js +6 -0
- package/src/skills-bundled/run/SKILL.md +49 -0
- package/src/skills-bundled/verify/SKILL.md +49 -0
- package/src/assets/web-panel/assets/Tasks-3PTmatJP.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D-Hp8s_8.js +0 -1
- package/src/assets/web-panel/assets/index-BvnHuxVM.js +0 -1
- package/src/assets/web-panel/assets/index-Dkm5IGwX.js +0 -1
package/src/index.js
CHANGED
|
@@ -64,6 +64,8 @@ import { registerGoalCommand } from "./commands/goal.js";
|
|
|
64
64
|
import { registerCommandCommand } from "./commands/command.js";
|
|
65
65
|
import { registerCompactCommand } from "./commands/compact.js";
|
|
66
66
|
import { registerLoopCommand } from "./commands/loop.js";
|
|
67
|
+
import { registerReviewCommand } from "./commands/review.js";
|
|
68
|
+
import { registerInsightsCommand } from "./commands/insights.js";
|
|
67
69
|
import { registerPermissionsCommand } from "./commands/permissions.js";
|
|
68
70
|
import { registerOutputStyleCommand } from "./commands/output-style.js";
|
|
69
71
|
import { registerStatuslineCommand } from "./commands/statusline.js";
|
|
@@ -477,6 +479,8 @@ export function createProgram(opts = {}) {
|
|
|
477
479
|
registerCommandCommand(program);
|
|
478
480
|
registerCompactCommand(program);
|
|
479
481
|
registerLoopCommand(program);
|
|
482
|
+
registerReviewCommand(program);
|
|
483
|
+
registerInsightsCommand(program);
|
|
480
484
|
registerPermissionsCommand(program);
|
|
481
485
|
registerOutputStyleCommand(program);
|
|
482
486
|
registerStatuslineCommand(program);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cost-budget — a hard USD spend cap for unattended agent runs
|
|
3
|
+
* (Claude-Code `--max-budget-usd` parity).
|
|
4
|
+
*
|
|
5
|
+
* Where IterationBudget caps the number of agent-loop turns, CostBudget caps the
|
|
6
|
+
* estimated dollar cost: it accumulates the per-call cost (via llm-pricing) as
|
|
7
|
+
* token-usage events arrive and reports when the cap is reached, so the runner
|
|
8
|
+
* can stop BEFORE making another paid LLM call. Because a call's cost is only
|
|
9
|
+
* known after it returns, a run may overshoot by at most one call — it never
|
|
10
|
+
* starts a new turn once over budget.
|
|
11
|
+
*
|
|
12
|
+
* Local/free providers (ollama, …) and unpriced models cost $0 here, so a cap
|
|
13
|
+
* can never trigger for them; `shouldWarnInactive()` lets the caller surface a
|
|
14
|
+
* one-time "cap inactive" notice instead of silently doing nothing.
|
|
15
|
+
*
|
|
16
|
+
* Pure + dependency-light (only llm-pricing) so it is unit-testable without a
|
|
17
|
+
* real agent loop.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { estimateCost } from "./llm-pricing.js";
|
|
21
|
+
|
|
22
|
+
const round = (n, dp = 6) => {
|
|
23
|
+
const f = Math.pow(10, dp);
|
|
24
|
+
return Math.round((Number(n) + Number.EPSILON) * f) / f;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Parse a `--max-budget-usd` value into a positive number, or null when unset. */
|
|
28
|
+
export function parseBudgetUsd(value) {
|
|
29
|
+
if (value == null || value === "") return null;
|
|
30
|
+
const n = Number(value);
|
|
31
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Invalid --max-budget-usd "${value}". Expected a positive number of US dollars.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class CostBudget {
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} opts
|
|
42
|
+
* @param {number|null} [opts.limitUsd] cap in USD; null/≤0 → disabled
|
|
43
|
+
* @param {object} [opts.table] merged price table (mergePricing output)
|
|
44
|
+
*/
|
|
45
|
+
constructor({ limitUsd = null, table = undefined } = {}) {
|
|
46
|
+
const lim = Number(limitUsd);
|
|
47
|
+
this.limitUsd = Number.isFinite(lim) && lim > 0 ? lim : null;
|
|
48
|
+
this.table = table;
|
|
49
|
+
this.spentUsd = 0;
|
|
50
|
+
this.priced = false; // priced ≥1 non-free usage record
|
|
51
|
+
this.sawUnpriced = false; // saw tokens we couldn't price
|
|
52
|
+
this.sawFree = false; // saw a free/local provider
|
|
53
|
+
this._warned = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enabled() {
|
|
57
|
+
return this.limitUsd != null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fold one token-usage record into the running spend.
|
|
62
|
+
* @returns {object} the estimateCost() result for this record
|
|
63
|
+
*/
|
|
64
|
+
add({ provider, model, usage } = {}) {
|
|
65
|
+
const est = estimateCost({
|
|
66
|
+
provider,
|
|
67
|
+
model,
|
|
68
|
+
inputTokens: usage?.input_tokens || 0,
|
|
69
|
+
outputTokens: usage?.output_tokens || 0,
|
|
70
|
+
table: this.table,
|
|
71
|
+
});
|
|
72
|
+
const tokens = (usage?.input_tokens || 0) + (usage?.output_tokens || 0);
|
|
73
|
+
if (est.free) {
|
|
74
|
+
this.sawFree = true;
|
|
75
|
+
} else if (est.matched) {
|
|
76
|
+
this.spentUsd = round(this.spentUsd + est.totalCost);
|
|
77
|
+
this.priced = true;
|
|
78
|
+
} else if (tokens > 0) {
|
|
79
|
+
this.sawUnpriced = true;
|
|
80
|
+
}
|
|
81
|
+
return est;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True once the running spend has reached/passed the cap. */
|
|
85
|
+
exceeded() {
|
|
86
|
+
return this.limitUsd != null && this.spentUsd >= this.limitUsd;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** USD left under the cap (Infinity when disabled). */
|
|
90
|
+
remaining() {
|
|
91
|
+
return this.limitUsd == null
|
|
92
|
+
? Infinity
|
|
93
|
+
: Math.max(0, round(this.limitUsd - this.spentUsd));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* True the FIRST time we can tell the cap can't bite — a cap was set but every
|
|
98
|
+
* usage so far has been free/local or unpriced, so spend stays $0. Lets the
|
|
99
|
+
* caller print a one-time "cap inactive" warning instead of a silent no-op.
|
|
100
|
+
*/
|
|
101
|
+
shouldWarnInactive() {
|
|
102
|
+
if (!this.enabled() || this._warned || this.priced) return false;
|
|
103
|
+
if (this.sawUnpriced || this.sawFree) {
|
|
104
|
+
this._warned = true;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/lib/ide-context.js
CHANGED
|
@@ -278,6 +278,13 @@ export function hasIdeOpenDiff(mcp) {
|
|
|
278
278
|
* Run one blocking openDiff review in the connected IDE. Returns
|
|
279
279
|
* { outcome:"accepted", finalText|null } — the IDE wrote the file itself
|
|
280
280
|
* { outcome:"rejected" } — nothing was written
|
|
281
|
+
* { outcome:"changes-requested", comments, reviewedText }
|
|
282
|
+
* — the user annotated the diff with
|
|
283
|
+
* revision notes instead of
|
|
284
|
+
* accepting/rejecting; nothing was
|
|
285
|
+
* written and the caller should
|
|
286
|
+
* feed `comments` back to the agent
|
|
287
|
+
* so it revises and re-proposes.
|
|
281
288
|
* null — IDE unavailable / transport
|
|
282
289
|
* error / malformed reply → the
|
|
283
290
|
* caller falls back to its normal
|
|
@@ -309,10 +316,53 @@ export async function requestIdeDiffApproval(mcp, req = {}) {
|
|
|
309
316
|
finalText: typeof data.finalText === "string" ? data.finalText : null,
|
|
310
317
|
};
|
|
311
318
|
}
|
|
319
|
+
if (data?.outcome === "changes-requested") {
|
|
320
|
+
return {
|
|
321
|
+
outcome: "changes-requested",
|
|
322
|
+
comments: Array.isArray(data.comments) ? data.comments : [],
|
|
323
|
+
reviewedText:
|
|
324
|
+
typeof data.reviewedText === "string" ? data.reviewedText : null,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
312
327
|
if (data?.outcome === "rejected") return { outcome: "rejected" };
|
|
313
328
|
return null; // anything else is not a verdict — fail safe to fallback
|
|
314
329
|
}
|
|
315
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Render line-anchored review comments (from an openDiff "changes-requested"
|
|
333
|
+
* verdict) into a compact feedback block the agent can act on. Each comment is
|
|
334
|
+
* `{ line?, endLine?, lineText?, note }` with 0-based editor lines. Returns
|
|
335
|
+
* null when there is no actionable note. Pure — safe to unit-test.
|
|
336
|
+
*/
|
|
337
|
+
export function formatReviewComments(comments, { path: filePath } = {}) {
|
|
338
|
+
if (!Array.isArray(comments) || comments.length === 0) return null;
|
|
339
|
+
const lines = comments
|
|
340
|
+
.map((c) => {
|
|
341
|
+
if (!c || typeof c.note !== "string" || c.note.trim().length === 0) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const start = Number.isInteger(c.line) ? c.line + 1 : null; // 0→1-based
|
|
345
|
+
const end = Number.isInteger(c.endLine) ? c.endLine + 1 : start;
|
|
346
|
+
const where =
|
|
347
|
+
start != null
|
|
348
|
+
? end != null && end !== start
|
|
349
|
+
? `lines ${start}-${end}`
|
|
350
|
+
: `line ${start}`
|
|
351
|
+
: "(general)";
|
|
352
|
+
const anchor =
|
|
353
|
+
typeof c.lineText === "string" && c.lineText.trim().length > 0
|
|
354
|
+
? ` ⟪${c.lineText.trim().slice(0, 120)}⟫`
|
|
355
|
+
: "";
|
|
356
|
+
return ` • ${where}: ${c.note.trim()}${anchor}`;
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
if (lines.length === 0) return null;
|
|
360
|
+
const header = filePath
|
|
361
|
+
? `Review comments on ${filePath}:`
|
|
362
|
+
: "Review comments:";
|
|
363
|
+
return `${header}\n${lines.join("\n")}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
316
366
|
// ─── Explicit @selection / @diagnostics at-mentions (Claude-Code parity) ────
|
|
317
367
|
//
|
|
318
368
|
// The ambient `<ide-context>` block above shares the selection on every turn.
|
|
@@ -57,6 +57,8 @@ const {
|
|
|
57
57
|
ZhihuAdapter,
|
|
58
58
|
BossZhipinAdapter,
|
|
59
59
|
CsdnAdapter,
|
|
60
|
+
DongchediAdapter,
|
|
61
|
+
TianyanchaAdapter,
|
|
60
62
|
DouyinAdapter,
|
|
61
63
|
XiaohongshuAdapter,
|
|
62
64
|
ToutiaoAdapter,
|
|
@@ -72,14 +74,30 @@ const {
|
|
|
72
74
|
AppleHealthAdapter,
|
|
73
75
|
NeteaseMusicAdapter,
|
|
74
76
|
KugouMusicAdapter,
|
|
77
|
+
QQMusicAdapter,
|
|
78
|
+
FanqieReadingAdapter,
|
|
79
|
+
QimaoReadingAdapter,
|
|
80
|
+
JoyrunAdapter,
|
|
75
81
|
IqiyiVideoAdapter,
|
|
76
82
|
TencentVideoAdapter,
|
|
83
|
+
XiguaVideoAdapter,
|
|
77
84
|
WeReadAdapter,
|
|
78
85
|
WpsDocAdapter,
|
|
79
86
|
TencentDocsAdapter,
|
|
80
87
|
BaiduNetdiskAdapter,
|
|
88
|
+
CamScannerDocAdapter,
|
|
89
|
+
IXiamenAdapter,
|
|
90
|
+
MeiyouAdapter,
|
|
91
|
+
TaxAdapter,
|
|
92
|
+
CmbcBankAdapter,
|
|
93
|
+
BocBankAdapter,
|
|
94
|
+
BankcommBankAdapter,
|
|
95
|
+
IcbcBankAdapter,
|
|
96
|
+
DcepAdapter,
|
|
97
|
+
Tmri12123Adapter,
|
|
81
98
|
DingTalkPcAdapter,
|
|
82
99
|
FeishuPcAdapter,
|
|
100
|
+
WeWorkPcAdapter,
|
|
83
101
|
BaiduMapAdapter,
|
|
84
102
|
TencentMapAdapter,
|
|
85
103
|
JdAdapter,
|
|
@@ -529,6 +547,8 @@ async function initHub() {
|
|
|
529
547
|
ZhihuAdapter,
|
|
530
548
|
BossZhipinAdapter,
|
|
531
549
|
CsdnAdapter,
|
|
550
|
+
DongchediAdapter,
|
|
551
|
+
TianyanchaAdapter,
|
|
532
552
|
DouyinAdapter,
|
|
533
553
|
XiaohongshuAdapter,
|
|
534
554
|
ToutiaoAdapter,
|
|
@@ -544,14 +564,30 @@ async function initHub() {
|
|
|
544
564
|
AppleHealthAdapter,
|
|
545
565
|
NeteaseMusicAdapter,
|
|
546
566
|
KugouMusicAdapter,
|
|
567
|
+
QQMusicAdapter,
|
|
568
|
+
FanqieReadingAdapter,
|
|
569
|
+
QimaoReadingAdapter,
|
|
570
|
+
JoyrunAdapter,
|
|
547
571
|
IqiyiVideoAdapter,
|
|
548
572
|
TencentVideoAdapter,
|
|
573
|
+
XiguaVideoAdapter,
|
|
549
574
|
WeReadAdapter,
|
|
550
575
|
WpsDocAdapter,
|
|
551
576
|
TencentDocsAdapter,
|
|
552
577
|
BaiduNetdiskAdapter,
|
|
578
|
+
CamScannerDocAdapter,
|
|
579
|
+
IXiamenAdapter,
|
|
580
|
+
MeiyouAdapter,
|
|
581
|
+
TaxAdapter,
|
|
582
|
+
CmbcBankAdapter,
|
|
583
|
+
BocBankAdapter,
|
|
584
|
+
BankcommBankAdapter,
|
|
585
|
+
IcbcBankAdapter,
|
|
586
|
+
DcepAdapter,
|
|
587
|
+
Tmri12123Adapter,
|
|
553
588
|
DingTalkPcAdapter,
|
|
554
589
|
FeishuPcAdapter,
|
|
590
|
+
WeWorkPcAdapter,
|
|
555
591
|
BaiduMapAdapter,
|
|
556
592
|
TencentMapAdapter,
|
|
557
593
|
JdAdapter,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-insights — turn a JSONL session's events into an analysis report
|
|
3
|
+
* (Claude-Code `/insights` parity). Pure: takes the already-read event array
|
|
4
|
+
* and returns a structured summary. No new data is collected — this reads the
|
|
5
|
+
* same `token_usage` / `tool_call` / message events the session already wrote,
|
|
6
|
+
* and layers on top of `aggregateUsage` (session-usage.js) for tokens.
|
|
7
|
+
*
|
|
8
|
+
* Note: headless runs persist user/assistant/token_usage but not necessarily
|
|
9
|
+
* tool_call/tool_result, so the tools section degrades gracefully to zero when
|
|
10
|
+
* those events aren't present (the REPL records them).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { aggregateUsage } from "./session-usage.js";
|
|
14
|
+
|
|
15
|
+
/** Human-readable duration from milliseconds. */
|
|
16
|
+
export function formatDuration(ms) {
|
|
17
|
+
const n = Number(ms);
|
|
18
|
+
if (!Number.isFinite(n) || n <= 0) return "0s";
|
|
19
|
+
const s = Math.round(n / 1000);
|
|
20
|
+
if (s < 60) return `${s}s`;
|
|
21
|
+
const m = Math.floor(s / 60);
|
|
22
|
+
const rs = s % 60;
|
|
23
|
+
if (m < 60) return rs ? `${m}m ${rs}s` : `${m}m`;
|
|
24
|
+
const h = Math.floor(m / 60);
|
|
25
|
+
const rm = m % 60;
|
|
26
|
+
return rm ? `${h}h ${rm}m` : `${h}h`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Detect whether a tool_result event represents an error (lenient). */
|
|
30
|
+
function isToolError(event) {
|
|
31
|
+
const r = event?.data?.result;
|
|
32
|
+
return Boolean(
|
|
33
|
+
event?.data?.error ||
|
|
34
|
+
(r && typeof r === "object" && (r.error || r.is_error || r.isError)),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Analyze a session's events into a structured insights object. Pure.
|
|
40
|
+
*
|
|
41
|
+
* @param {object[]} events JSONL events ({type, timestamp, data})
|
|
42
|
+
* @param {string} [sessionId]
|
|
43
|
+
* @returns {object}
|
|
44
|
+
*/
|
|
45
|
+
export function analyzeSession(events, sessionId) {
|
|
46
|
+
const evs = Array.isArray(events) ? events : [];
|
|
47
|
+
const start = evs.find((e) => e && e.type === "session_start");
|
|
48
|
+
|
|
49
|
+
const stamps = evs
|
|
50
|
+
.map((e) => Number(e?.timestamp))
|
|
51
|
+
.filter((t) => Number.isFinite(t) && t > 0);
|
|
52
|
+
const startedAt = stamps.length ? Math.min(...stamps) : null;
|
|
53
|
+
const endedAt = stamps.length ? Math.max(...stamps) : null;
|
|
54
|
+
const durationMs =
|
|
55
|
+
startedAt != null && endedAt != null ? endedAt - startedAt : 0;
|
|
56
|
+
|
|
57
|
+
let userMsgs = 0;
|
|
58
|
+
let assistantMsgs = 0;
|
|
59
|
+
let compactions = 0;
|
|
60
|
+
let toolCalls = 0;
|
|
61
|
+
let toolErrors = 0;
|
|
62
|
+
const toolByName = new Map();
|
|
63
|
+
|
|
64
|
+
const bump = (name) => {
|
|
65
|
+
const t = toolByName.get(name) || { tool: name, count: 0, errors: 0 };
|
|
66
|
+
toolByName.set(name, t);
|
|
67
|
+
return t;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for (const e of evs) {
|
|
71
|
+
if (!e || typeof e !== "object") continue;
|
|
72
|
+
switch (e.type) {
|
|
73
|
+
case "user_message":
|
|
74
|
+
userMsgs++;
|
|
75
|
+
break;
|
|
76
|
+
case "assistant_message":
|
|
77
|
+
assistantMsgs++;
|
|
78
|
+
break;
|
|
79
|
+
case "compact":
|
|
80
|
+
compactions++;
|
|
81
|
+
break;
|
|
82
|
+
case "tool_call": {
|
|
83
|
+
toolCalls++;
|
|
84
|
+
bump(e.data?.tool || "?").count++;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "tool_result": {
|
|
88
|
+
if (isToolError(e)) {
|
|
89
|
+
toolErrors++;
|
|
90
|
+
bump(e.data?.tool || "?").errors++;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
default:
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Headless persistence writes token_usage with only {input_tokens,
|
|
100
|
+
// output_tokens} — no model/provider — so the raw aggregate can't be priced.
|
|
101
|
+
// Backfill the session's recorded model/provider (from session_start) onto
|
|
102
|
+
// those events so cost estimation works (cc cost lacks this, so insights cost
|
|
103
|
+
// is strictly better for headless sessions).
|
|
104
|
+
const sessModel = start?.data?.model || null;
|
|
105
|
+
const sessProvider = start?.data?.provider || null;
|
|
106
|
+
const usageEvents =
|
|
107
|
+
sessModel || sessProvider
|
|
108
|
+
? evs.map((e) => {
|
|
109
|
+
if (e?.type !== "token_usage") return e;
|
|
110
|
+
const d = e.data || {};
|
|
111
|
+
if (d.provider || d.model) return e;
|
|
112
|
+
return {
|
|
113
|
+
...e,
|
|
114
|
+
data: { ...d, provider: sessProvider, model: sessModel },
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
: evs;
|
|
118
|
+
const usage = aggregateUsage(usageEvents);
|
|
119
|
+
const iso = (t) => (t != null ? new Date(t).toISOString() : null);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
sessionId: sessionId || null,
|
|
123
|
+
meta: {
|
|
124
|
+
title: start?.data?.title || null,
|
|
125
|
+
model: start?.data?.model || usage.byModel[0]?.model || null,
|
|
126
|
+
provider: start?.data?.provider || usage.byModel[0]?.provider || null,
|
|
127
|
+
startedAt: iso(startedAt),
|
|
128
|
+
endedAt: iso(endedAt),
|
|
129
|
+
durationMs,
|
|
130
|
+
},
|
|
131
|
+
events: evs.length,
|
|
132
|
+
messages: {
|
|
133
|
+
user: userMsgs,
|
|
134
|
+
assistant: assistantMsgs,
|
|
135
|
+
total: userMsgs + assistantMsgs,
|
|
136
|
+
},
|
|
137
|
+
tools: {
|
|
138
|
+
calls: toolCalls,
|
|
139
|
+
errors: toolErrors,
|
|
140
|
+
byTool: Array.from(toolByName.values()).sort((a, b) => b.count - a.count),
|
|
141
|
+
},
|
|
142
|
+
compactions,
|
|
143
|
+
usage,
|
|
144
|
+
};
|
|
145
|
+
}
|
package/src/lib/skill-loader.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multi-layer skill loader for CLI
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 0 (lowest) bundled — desktop-app-vue/.../skills/builtin/
|
|
4
|
+
* 7-layer priority system (highest wins on name collision):
|
|
5
|
+
* 0 (lowest) bundled — desktop-app-vue/.../skills/builtin/ (monorepo only)
|
|
6
|
+
* 0b cli-bundled — packages/cli/src/skills-bundled/ (ships with cc)
|
|
6
7
|
* 1 marketplace — <userData>/marketplace/skills/
|
|
7
8
|
* 2 managed — <userData>/skills/
|
|
8
9
|
* 3 claude-user — ~/.claude/skills/ (Claude-Code 可移植)
|
|
@@ -69,6 +70,7 @@ export function bundledSkillsDisabled(opts = {}) {
|
|
|
69
70
|
/** Layer names in priority order (lowest → highest) */
|
|
70
71
|
export const LAYER_NAMES = [
|
|
71
72
|
"bundled",
|
|
73
|
+
"cli-bundled",
|
|
72
74
|
"marketplace",
|
|
73
75
|
"managed",
|
|
74
76
|
"claude-user",
|
|
@@ -269,6 +271,18 @@ export class CLISkillLoader {
|
|
|
269
271
|
exists: bundledPath !== null,
|
|
270
272
|
});
|
|
271
273
|
|
|
274
|
+
// Layer 0b: cli-bundled — skills shipped INSIDE the cc package
|
|
275
|
+
// (packages/cli/src/skills-bundled). Unlike the desktop `bundled` layer
|
|
276
|
+
// above (absent in a published cc install), this is always present, so
|
|
277
|
+
// CLI-owned global skills (run / verify) work everywhere cc runs without
|
|
278
|
+
// touching the desktop app's version-counted skill set.
|
|
279
|
+
const cliBundledPath = path.resolve(__dirname, "../skills-bundled");
|
|
280
|
+
layers.push({
|
|
281
|
+
layer: "cli-bundled",
|
|
282
|
+
path: cliBundledPath,
|
|
283
|
+
exists: fs.existsSync(cliBundledPath),
|
|
284
|
+
});
|
|
285
|
+
|
|
272
286
|
// Layer 1: marketplace — <userData>/marketplace/skills/
|
|
273
287
|
const userData = getElectronUserDataDir();
|
|
274
288
|
const marketplacePath = path.join(userData, "marketplace", "skills");
|
|
@@ -399,7 +413,8 @@ export class CLISkillLoader {
|
|
|
399
413
|
// Process in priority order (lowest first, so higher layers overwrite)
|
|
400
414
|
for (const { layer, path: layerPath, exists } of layers) {
|
|
401
415
|
if (!exists) continue;
|
|
402
|
-
if (dropBundled && layer === "bundled")
|
|
416
|
+
if (dropBundled && (layer === "bundled" || layer === "cli-bundled"))
|
|
417
|
+
continue;
|
|
403
418
|
const skills = this._loadFromDir(layerPath, layer);
|
|
404
419
|
for (const skill of skills) {
|
|
405
420
|
skillMap.set(skill.id, skill);
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -75,7 +75,10 @@ import {
|
|
|
75
75
|
agentLoop as coreAgentLoop,
|
|
76
76
|
formatToolArgs,
|
|
77
77
|
killAllBackgroundShellTasks,
|
|
78
|
+
killBackgroundShellTask,
|
|
79
|
+
listBackgroundShellTasks,
|
|
78
80
|
} from "../runtime/agent-core.js";
|
|
81
|
+
import { formatBackgroundTasks } from "./tasks-status.js";
|
|
79
82
|
import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
80
83
|
import { composeSystemPrompt } from "../runtime/system-prompt.js";
|
|
81
84
|
import {
|
|
@@ -833,6 +836,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
833
836
|
"/statusline",
|
|
834
837
|
"/sub-agents",
|
|
835
838
|
"/task",
|
|
839
|
+
"/tasks",
|
|
836
840
|
"/terminal-setup",
|
|
837
841
|
"/vim",
|
|
838
842
|
],
|
|
@@ -1253,6 +1257,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1253
1257
|
logger.log(
|
|
1254
1258
|
` ${chalk.cyan("/sub-agents")} Show active/completed sub-agents`,
|
|
1255
1259
|
);
|
|
1260
|
+
logger.log(
|
|
1261
|
+
` ${chalk.cyan("/tasks")} Show background shell tasks (kill <id> · kill-all)`,
|
|
1262
|
+
);
|
|
1256
1263
|
logger.log(
|
|
1257
1264
|
` ${chalk.cyan("/ide")} IDE bridge status (connected editor, tools, or why not)`,
|
|
1258
1265
|
);
|
|
@@ -1292,6 +1299,33 @@ export async function startAgentRepl(options = {}) {
|
|
|
1292
1299
|
return;
|
|
1293
1300
|
}
|
|
1294
1301
|
|
|
1302
|
+
// `/tasks` — user-facing view of the agent's background shell tasks
|
|
1303
|
+
// (run_shell run_in_background). Must precede the `/task` handler below,
|
|
1304
|
+
// which matches with startsWith("/task") and would otherwise swallow it.
|
|
1305
|
+
if (trimmed === "/tasks" || trimmed.startsWith("/tasks ")) {
|
|
1306
|
+
const rest = trimmed.slice("/tasks".length).trim();
|
|
1307
|
+
if (rest === "kill-all") {
|
|
1308
|
+
const n = killAllBackgroundShellTasks();
|
|
1309
|
+
logger.log(chalk.dim(`Killed ${n} background shell task(s).`));
|
|
1310
|
+
} else if (rest.startsWith("kill ")) {
|
|
1311
|
+
const id = rest.slice("kill ".length).trim();
|
|
1312
|
+
const ok = id ? killBackgroundShellTask(id) : false;
|
|
1313
|
+
logger.log(
|
|
1314
|
+
ok
|
|
1315
|
+
? chalk.dim(`Killed background shell task ${id}.`)
|
|
1316
|
+
: chalk.dim(`No running background shell task with id "${id}".`),
|
|
1317
|
+
);
|
|
1318
|
+
} else if (rest === "kill") {
|
|
1319
|
+
logger.log(chalk.dim("Usage: /tasks kill <id> · /tasks kill-all"));
|
|
1320
|
+
} else {
|
|
1321
|
+
logger.log(
|
|
1322
|
+
"\n" + formatBackgroundTasks(listBackgroundShellTasks()) + "\n",
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
prompt();
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1295
1329
|
if (trimmed === "/sub-agents" || trimmed === "/subagents") {
|
|
1296
1330
|
try {
|
|
1297
1331
|
const { SubAgentRegistry } =
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure renderer for the `/tasks` REPL command — a user-facing view of the
|
|
3
|
+
* agent's background shell tasks (the ones it starts with
|
|
4
|
+
* run_shell { run_in_background: true }). The data comes from agent-core's
|
|
5
|
+
* listBackgroundShellTasks(); this module only formats it, so it stays
|
|
6
|
+
* deterministic and unit-testable (inject `now` for stable elapsed time).
|
|
7
|
+
*
|
|
8
|
+
* Background shells are otherwise only visible to the agent (via its
|
|
9
|
+
* check_shell tool) — this surfaces them to the human, the way Claude Code's
|
|
10
|
+
* background-tasks view does. Sub-agents live under a separate registry and
|
|
11
|
+
* are shown by `/sub-agents`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Coerce an ISO-string or epoch-ms timestamp to epoch ms, or null. */
|
|
15
|
+
function toMs(v) {
|
|
16
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
17
|
+
if (typeof v === "string") {
|
|
18
|
+
const t = Date.parse(v);
|
|
19
|
+
return Number.isFinite(t) ? t : null;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Human-friendly elapsed duration. */
|
|
25
|
+
function fmtElapsed(ms) {
|
|
26
|
+
if (!Number.isFinite(ms) || ms < 0) return "?";
|
|
27
|
+
const s = Math.floor(ms / 1000);
|
|
28
|
+
if (s < 60) return `${s}s`;
|
|
29
|
+
const m = Math.floor(s / 60);
|
|
30
|
+
if (m < 60) return `${m}m${s % 60}s`;
|
|
31
|
+
const h = Math.floor(m / 60);
|
|
32
|
+
return `${h}h${m % 60}m`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A short status badge for one task. */
|
|
36
|
+
export function taskStatusLabel(t) {
|
|
37
|
+
switch (t?.status) {
|
|
38
|
+
case "running":
|
|
39
|
+
return "● running";
|
|
40
|
+
case "exited":
|
|
41
|
+
return `✓ exited${t.exitCode != null ? ` (${t.exitCode})` : ""}`;
|
|
42
|
+
case "failed":
|
|
43
|
+
return `✗ failed${t.exitCode != null ? ` (${t.exitCode})` : ""}`;
|
|
44
|
+
case "error":
|
|
45
|
+
return "✗ error";
|
|
46
|
+
default:
|
|
47
|
+
return t?.status || "?";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Render the background-shell task list as a plain-text block.
|
|
53
|
+
* @param {Array} tasks from listBackgroundShellTasks()
|
|
54
|
+
* @param {object} opts { now?: epoch-ms } — defaults to Date.now()
|
|
55
|
+
*/
|
|
56
|
+
export function formatBackgroundTasks(tasks, { now = Date.now() } = {}) {
|
|
57
|
+
const list = Array.isArray(tasks) ? tasks : [];
|
|
58
|
+
if (list.length === 0) {
|
|
59
|
+
return (
|
|
60
|
+
"No background shell tasks.\n" +
|
|
61
|
+
" (The agent starts these with run_shell run_in_background:true; " +
|
|
62
|
+
"sub-agents are under /sub-agents.)"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const running = list.filter((t) => t?.status === "running").length;
|
|
66
|
+
const lines = [
|
|
67
|
+
`Background shell tasks (${list.length}, ${running} running):`,
|
|
68
|
+
];
|
|
69
|
+
for (const t of list) {
|
|
70
|
+
const started = toMs(t.startedAt);
|
|
71
|
+
const ended = toMs(t.endedAt);
|
|
72
|
+
const elapsed =
|
|
73
|
+
started != null ? fmtElapsed((ended ?? now) - started) : "?";
|
|
74
|
+
const cmd = String(t.command || "")
|
|
75
|
+
.replace(/\s+/g, " ")
|
|
76
|
+
.trim();
|
|
77
|
+
const cmdShort = cmd.length > 70 ? cmd.slice(0, 70) + "…" : cmd;
|
|
78
|
+
lines.push(` ${taskStatusLabel(t)} ${t.id} ${elapsed} ${cmdShort}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push("Manage: /tasks kill <id> · /tasks kill-all");
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
@@ -175,6 +175,16 @@ function _killTask(task) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Kill one background shell task by id (for the user-facing `/tasks kill <id>`).
|
|
180
|
+
* @returns {boolean} true if a running task with that id was signalled
|
|
181
|
+
*/
|
|
182
|
+
export function killBackgroundShellTask(id) {
|
|
183
|
+
const task = _backgroundShellTasks.get(id);
|
|
184
|
+
if (!task) return false;
|
|
185
|
+
return _killTask(task);
|
|
186
|
+
}
|
|
187
|
+
|
|
178
188
|
/**
|
|
179
189
|
* Kill every still-running background shell task. Callers (REPL exit, headless
|
|
180
190
|
* shutdown) invoke this so a backgrounded `npm run dev` doesn't outlive the
|
|
@@ -241,7 +251,9 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
241
251
|
if (ide?.outcome === "accepted") {
|
|
242
252
|
return { blocked: false, ideApplied: ide.result };
|
|
243
253
|
}
|
|
244
|
-
|
|
254
|
+
// Both rejected and changes-requested mean "not applied + feed the
|
|
255
|
+
// verdict's message back" — same control flow, different message body.
|
|
256
|
+
if (ide?.outcome === "rejected" || ide?.outcome === "changes-requested") {
|
|
245
257
|
return {
|
|
246
258
|
blocked: true,
|
|
247
259
|
reason: ide.result.error,
|
|
@@ -714,8 +726,12 @@ async function tryIdeDiffApprovalForEdit(
|
|
|
714
726
|
if (typeof context.permissionConfirm !== "function") return null; // interactive only
|
|
715
727
|
if (!context.mcpClient || !context.externalToolExecutors) return null;
|
|
716
728
|
try {
|
|
717
|
-
const {
|
|
718
|
-
|
|
729
|
+
const {
|
|
730
|
+
ideDiffApprovalEnabled,
|
|
731
|
+
hasIdeOpenDiff,
|
|
732
|
+
requestIdeDiffApproval,
|
|
733
|
+
formatReviewComments,
|
|
734
|
+
} = await import("../lib/ide-context.js");
|
|
719
735
|
const mcpLike = {
|
|
720
736
|
mcpClient: context.mcpClient,
|
|
721
737
|
externalToolExecutors: context.externalToolExecutors,
|
|
@@ -753,6 +769,25 @@ async function tryIdeDiffApprovalForEdit(
|
|
|
753
769
|
},
|
|
754
770
|
};
|
|
755
771
|
}
|
|
772
|
+
if (verdict?.outcome === "changes-requested") {
|
|
773
|
+
// The reviewer annotated the diff instead of accepting/rejecting: the
|
|
774
|
+
// file is untouched and the notes flow back as the tool result, so the
|
|
775
|
+
// agent revises and re-proposes (Claude-Code inline-review parity).
|
|
776
|
+
const feedback =
|
|
777
|
+
formatReviewComments(verdict.comments, { path: proposal.filePath }) ||
|
|
778
|
+
"The user requested changes in the IDE diff review (no specific notes).";
|
|
779
|
+
return {
|
|
780
|
+
outcome: "changes-requested",
|
|
781
|
+
result: {
|
|
782
|
+
error:
|
|
783
|
+
`[IDE review] "${name}" was NOT applied — the user requested changes:\n` +
|
|
784
|
+
`${feedback}\n` +
|
|
785
|
+
"Revise the edit to address this feedback, then propose it again.",
|
|
786
|
+
policy: { decision: "deny", rule, via: "ide-diff-review" },
|
|
787
|
+
reviewComments: verdict.comments,
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
}
|
|
756
791
|
} catch (_err) {
|
|
757
792
|
// diff-approval routing is best-effort — fall back to the normal confirm
|
|
758
793
|
}
|