chainlesschain 0.162.37 → 0.162.39
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 +3 -2
- package/src/assets/web-panel/assets/{AIOps-_oxz4VHy.js → AIOps-DCjoAX_u.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-uaeqFuDj.js → ActionButton-XHoOmsbP.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BPVV0OUf.js → Analytics--xaFkDnL.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-ppCYKm3I.js → AppLayout-CSa3FBn8.js} +4 -4
- package/src/assets/web-panel/assets/{Audit-DFAY6umk.js → Audit-ONWXiAwG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-pAPBFDyP.js → Backup-CKOPNdgy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BbBl0uT2.js → BaseInput-PNj4uVqg.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Ct22JUnT.js → Chat-CZCulyXV.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DPlsLl22.js → ChatBubbleRenderer-CjuJpfpV.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DEkCollc.js → Checkbox-jvy668lD.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Tor-de39.js → Codegen-DhUebOQD.js} +1 -1
- package/src/assets/web-panel/assets/{Col-ojNrLQU7.js → Col-BiBvHfdT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOGhqMF.js → Community-CmEdEti-.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CYKNlSZ4.js → Compact-CtxpF4R5.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-C5E6ABuA.js → Compliance-CvPTrTAJ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CHeEsZ3W.js → Cowork-BMafGHjy.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B4e1n2e7.js → Cron-mdg_4TR1.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DbNV8P9R.js → Crosschain--dGxsUvn.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C5_Tk3nC.js → DID-C9oKaCml.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BhdV_c4N.js → Dashboard-CoGxKMvy.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CEi5AMtM.js → Dropdown-CDDu3ZZ3.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DOhPiYng.js → EmailListRenderer-Dy7_r9Ag.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-fu4NRP3X.js → FamilyGuardDashboard-CNg6vImJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B7BtIWKL.js → Federation-CT61bf3u.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BmPWZVLP.js → FormItemContext-CSLRnXhg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-hsOPNJq8.js → GenericCardRenderer-CZ4NE5N3.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bi_EFBUH.js → Git-DBuOma3L.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-emf2ubDK.js → Governance-BTU_SEef.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B7KjKzkI.js → Inference-47SAmLC_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-uAaBK0F3.js → KnowledgeGraph-DCrK5vP4.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-utK7hNpj.js → Logs-BqiDxdav.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CzQe6n3z.js → Marketplace-CReUjsDt.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CuAaJr51.js → McpTools-agZBV3p8.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-CRuZZJ75.js → Memory-C_YvUtyS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Cp06wunh.js → MobileBridge-41fP1Tui.js} +3 -3
- package/src/assets/web-panel/assets/{MobileProjects-DJEdUwhr.js → MobileProjects-BkqLvGfL.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-8YY4dR7g.js → Mtc-JFJCXUnk.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BmPJYHar.js → MtcAudit-BHNpPZC9.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-d-ydyVdq.js → Multisig-DuCRumiz.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DA_ikw_n.js → NLProgramming-DK-g0fKY.js} +1 -1
- package/src/assets/web-panel/assets/Notes-BSMcjsPf.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-CzPZXEtK.js → NotificationSettings-9ouC118H.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-LG2nUO5y.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DdDZ_Ap6.js → Organization-DSV7oRnR.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BnMBkttv.js → Overflow-DVkkORc3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Es1050f-.js → P2P-BXXjkkQD.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CKkRmyn9.js → PdhVaultBrowser-O5hNnLTP.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-zU9n9cAD.js → Permissions-D_s0H5Av.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BZi5Xwas.js → PersonalDataHub-CzMDrwUi.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CRfeGiFc.js → Pipeline-i9krLVTL.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CQA_IgLA.js → Privacy-cMQcj9I8.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C9hmEvoT.js → ProjectInit-Ca_l7avo.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-yXA72ws4.js → ProjectSettings-BkaIhd6b.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BpWS-qam.js → Projects-Dy9yNmDg.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cxe55dRD.js → Providers-D0nzYiqz.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Do0aUTQr.js → QuickAsk-Bzzr9d0f.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend--ysZHjyA.js → Recommend-C-UFbQnX.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BOBU8JrH.js → Reputation-BKMIKO5F.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C6X7bRKE.js → Row-Bs7htK1T.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D8AwqlkQ.js → RssFeed-v6MdULUh.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Bi3rCZD4.js → Search-DlRWYzvz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DxUDVrtY.js → Security-DXWO37xX.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BXXN7yC1.js → Services-C2tWA-O0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B3BR34tZ.js → Skeleton-Q8pIYY4a.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BjYu8OQ1.js → Skills-D7XBlErj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDkCtD8w.js → Sla-CiyMVPJ1.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CGhYzP7V.js → SpeechSettings-CadCeeiR.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYNKVAHA.js → SyncSettings-DzNAUhQq.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-BjdHjZeb.js +1 -0
- package/src/assets/web-panel/assets/{Templates-CQuYFf2C.js → Templates-DfgEpUa4.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DdzZh8vE.js → Tenant-C8ajkuYi.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-B9rHwQQx.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-DKOARnc_.js → TimelineRenderer-D1ZVNezX.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D7QRNG8y.js → Tokens-CAkED4mx.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCsqLZl4.js → Trigger-CJSrm6X0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BarGUa6p.js → Trust-B-TeorSk.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-pHrg5a8E.js → UkeySign-Di7Ymofy.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Dug3m1py.js → VideoEditing-DM1eYNZe.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BfK3Z_Ez.js → Wallet-DvRWkbmR.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CYRdl9td.js → WebAuthn-CeZ3Y622.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DTW5AcqM.js → WorkflowEditor-Cq8c4h5j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CCXz4j38.js → chat-7-WfML6Q.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BJBOhAqa.js → colors-D6FgCmB-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-E9M6BQcM.js → compact-item-ClYV25qi.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cg9CAws4.js → createContext-CDhtjdkV.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-O0FVFeZg.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DhVtqv5L.js → hasIn-DZSH5LQd.js} +1 -1
- package/src/assets/web-panel/assets/{index-_PNqQ5mE.js → index---azBCXl.js} +1 -1
- package/src/assets/web-panel/assets/index--ANIKvhL.js +1 -0
- package/src/assets/web-panel/assets/{index-kz1oXl1a.js → index-B13QnrnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-2dUIli.js → index-B4PMzmOx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca8BYV1g.js → index-B6VWGnwq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFZPRd0T.js → index-B78X5S22.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwvewrul.js → index-BHeK8I5A.js} +1 -1
- package/src/assets/web-panel/assets/{index-MdXEhfdJ.js → index-BJ7mrOaB.js} +1 -1
- package/src/assets/web-panel/assets/{index--7o5YdL6.js → index-BUOPjAUM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFp-wdrQ.js → index-B_mMFQ4S.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdhnGez0.js → index-Bj8hZiyL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUp_c8Le.js → index-BlBF_l8m.js} +1 -1
- package/src/assets/web-panel/assets/{index-6-04M2Nx.js → index-BpzOUiSb.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByazO4Q9.js → index-BqOIoEo6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ComyTKz-.js → index-C7pQa2is.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm1m7BJh.js → index-C7sC56w8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4NBF4Sa.js → index-CDX4QU3k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSQazU6J.js → index-CKgS8E_X.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAB0nGP7.js → index-CSjoWPxB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8bjEHrQ.js → index-CWOkL-8O.js} +1 -1
- package/src/assets/web-panel/assets/{index-CznfPnOx.js → index-CmU631Je.js} +3 -3
- package/src/assets/web-panel/assets/{index-D7DXdf7x.js → index-CrTmxbL8.js} +1 -1
- package/src/assets/web-panel/assets/{index-wkt-o5q5.js → index-CxwfFZ1u.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxSzyly9.js → index-D0YzTJJO.js} +1 -1
- package/src/assets/web-panel/assets/{index-4N5lNXGP.js → index-DGJK8D0l.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaFe1aqY.js → index-DGj1orXm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di5LBXcE.js → index-DL6GFJAd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_SMPD4L.js → index-DLizxxId.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSiyjCYi.js → index-DPEYvNvq.js} +1 -1
- package/src/assets/web-panel/assets/index-DUfp4rnQ.js +1 -0
- package/src/assets/web-panel/assets/{index-D5yC2Ps8.js → index-DWRoh3_3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDcJO27F.js → index-DZ4zuoCP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJ8nNT8h.js → index-DgaF1F0W.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChsSljaN.js → index-Di9pFrHV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFarAlXj.js → index-DjG82V0v.js} +1 -1
- package/src/assets/web-panel/assets/{index-B111fZ21.js → index-Or_McYjX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVR_s-pT.js → index-rCs9VJJp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkTeBHI9.js → index-tU6pZ1TP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeRlLp3F.js → index-z-R0KaJS.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-iyBaePF-.js → initDefaultProps-CSdsIGy3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-RWtj4rgu.js → motion-Do-AcZV4.js} +1 -1
- package/src/assets/web-panel/assets/{move-CqPRVzpH.js → move-BmgOoMsi.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DsvJze25.js → omit-D4Tm7-s9.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4tfZBhc.js → pickAttrs-CuWA8-lj.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-KvHUwXMA.js → placementArrow-BSbEF5op.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DGdJ-b7W.js → responsiveObserve-GIMJwB_9.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cd6ebRmw.js → slide-DlZxpIBe.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bg9GcIAn.js → statusUtils-BZ26LPlh.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-MQjKsG84.js → styleChecker-Yn_3FZ0l.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C241WujP.js → useFlexGapSupport-O_LOE1AB.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CMpy7RS4.js → useFs-VFMyQqtl.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BLHtapKb.js → usePersonalDataHub-B_hyrGB-.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DmcTV67c.js → vnode-D4LttGy7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DHL8_0Y8.js → zoom-KnTK1fjj.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +31 -0
- package/src/commands/mcp.js +236 -6
- package/src/harness/mcp-client.js +70 -1
- package/src/lib/ide-context.js +271 -0
- package/src/lib/repl-completer.js +139 -0
- package/src/lib/settings-hooks.cjs +1 -0
- package/src/repl/agent-repl.js +88 -20
- package/src/repl/mcp-prompt.js +122 -0
- package/src/runtime/agent-core.js +156 -17
- package/src/runtime/headless-runner.js +53 -9
- package/src/runtime/headless-stream.js +19 -9
- package/src/runtime/mcp-config.js +118 -9
- package/src/assets/web-panel/assets/Notes-DIyF-fRe.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-BiLtg-LY.js +0 -1
- package/src/assets/web-panel/assets/Tasks-DavmlJpd.js +0 -1
- package/src/assets/web-panel/assets/Terminal-D75WeG9d.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BrsbTJUv.js +0 -1
- package/src/assets/web-panel/assets/index-DSTQDO-Y.js +0 -1
- package/src/assets/web-panel/assets/index-c2U6LV3Q.js +0 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE live prompt context (Claude-Code parity) — when an IDE bridge is
|
|
3
|
+
* connected (lib/ide-bridge.js → mcp-config.js `loadIdeMcp`, server `ide`),
|
|
4
|
+
* automatically share the editor's state at the moment a prompt is submitted:
|
|
5
|
+
* the active file, the open editor tabs, and the current selection. The agent
|
|
6
|
+
* no longer has to *choose* to call mcp__ide__getSelection — the context rides
|
|
7
|
+
* along with every user turn, exactly like Claude Code's at-submit selection
|
|
8
|
+
* sharing.
|
|
9
|
+
*
|
|
10
|
+
* The context is EPHEMERAL by design: entry points append it to the in-flight
|
|
11
|
+
* user content only, after session persistence, so a resumed session replays
|
|
12
|
+
* the user's words, not a stale editor snapshot.
|
|
13
|
+
*
|
|
14
|
+
* Everything here is best-effort and bounded: a missing/slow IDE server can
|
|
15
|
+
* never block or fail a turn (short timeout, all errors → null), and the
|
|
16
|
+
* injected block is capped. `CC_IDE_CONTEXT=0` disables the feature without
|
|
17
|
+
* disconnecting the IDE tools themselves.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Hard cap on the selected text we inline into the prompt. */
|
|
21
|
+
const SELECTION_TEXT_CAP = 2000;
|
|
22
|
+
/** At most this many open-editor entries are listed. */
|
|
23
|
+
const OPEN_EDITORS_CAP = 10;
|
|
24
|
+
/** Per-tool-call budget; the IDE answers from memory, so this is generous. */
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
26
|
+
|
|
27
|
+
/** Env kill-switch: CC_IDE_CONTEXT=0|false|off disables injection. */
|
|
28
|
+
export function ideContextEnabled(env = process.env) {
|
|
29
|
+
const v = String(env?.CC_IDE_CONTEXT ?? "").toLowerCase();
|
|
30
|
+
return !(v === "0" || v === "false" || v === "off");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Does this resolved MCP bundle expose the IDE bridge's selection tool?
|
|
35
|
+
* (`resolveAgentMcp` connects the bridge as server `ide`, so its tools land in
|
|
36
|
+
* `externalToolExecutors` as mcp__ide__*.)
|
|
37
|
+
*/
|
|
38
|
+
export function hasIdeContextTools(mcp) {
|
|
39
|
+
return !!(
|
|
40
|
+
mcp?.mcpClient?.callTool &&
|
|
41
|
+
mcp.externalToolExecutors?.mcp__ide__getSelection?.kind === "mcp"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read an MCP tools/call result's first text block as JSON. The IDE bridge
|
|
47
|
+
* servers always wrap handler data as
|
|
48
|
+
* `{content:[{type:"text",text:JSON.stringify(data)}]}`. Returns null for
|
|
49
|
+
* isError results, non-text content, or unparsable text.
|
|
50
|
+
*/
|
|
51
|
+
export function parseToolResultJson(result) {
|
|
52
|
+
if (!result || result.isError) return null;
|
|
53
|
+
const block = Array.isArray(result.content)
|
|
54
|
+
? result.content.find((b) => b && b.type === "text")
|
|
55
|
+
: null;
|
|
56
|
+
if (!block || typeof block.text !== "string") return null;
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(block.text);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Resolve to the promise's value, or null after `ms` / on rejection. */
|
|
65
|
+
function withTimeout(promise, ms) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
const timer = setTimeout(() => resolve(null), ms);
|
|
68
|
+
promise.then(
|
|
69
|
+
(v) => {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
resolve(v);
|
|
72
|
+
},
|
|
73
|
+
() => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve(null);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Query the connected IDE for its live state. Returns
|
|
83
|
+
* `{ selection, openEditors }` (either field may be null) or null when the
|
|
84
|
+
* feature is disabled, no IDE tools are connected, or nothing useful came
|
|
85
|
+
* back. Never throws.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} mcp resolved bundle from resolveAgentMcp
|
|
88
|
+
* @param {object} opts { env?, timeoutMs? }
|
|
89
|
+
*/
|
|
90
|
+
export async function collectIdeContext(mcp, opts = {}) {
|
|
91
|
+
if (!ideContextEnabled(opts.env || process.env)) return null;
|
|
92
|
+
if (!hasIdeContextTools(mcp)) return null;
|
|
93
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
94
|
+
const executors = mcp.externalToolExecutors;
|
|
95
|
+
const call = (name) => {
|
|
96
|
+
const exec = executors[name];
|
|
97
|
+
if (!exec || exec.kind !== "mcp") return Promise.resolve(null);
|
|
98
|
+
let p;
|
|
99
|
+
try {
|
|
100
|
+
p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, {});
|
|
101
|
+
} catch {
|
|
102
|
+
return Promise.resolve(null);
|
|
103
|
+
}
|
|
104
|
+
return withTimeout(p.then(parseToolResultJson), timeoutMs);
|
|
105
|
+
};
|
|
106
|
+
const [selection, editors] = await Promise.all([
|
|
107
|
+
call("mcp__ide__getSelection"),
|
|
108
|
+
call("mcp__ide__getOpenEditors"),
|
|
109
|
+
]);
|
|
110
|
+
const openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
|
|
111
|
+
if (!selection && !(openEditors && openEditors.length > 0)) return null;
|
|
112
|
+
return { selection: selection || null, openEditors };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Render collected IDE state as a compact tagged block for the user turn.
|
|
117
|
+
* Returns null when there is nothing worth saying.
|
|
118
|
+
*/
|
|
119
|
+
export function formatIdeContext(ctx) {
|
|
120
|
+
if (!ctx) return null;
|
|
121
|
+
const lines = [];
|
|
122
|
+
const editors = Array.isArray(ctx.openEditors) ? ctx.openEditors : [];
|
|
123
|
+
const active = editors.find((e) => e && e.active);
|
|
124
|
+
if (active?.file) lines.push(`Active file: ${active.file}`);
|
|
125
|
+
if (editors.length > 0) {
|
|
126
|
+
const names = editors
|
|
127
|
+
.filter((e) => e && e.file)
|
|
128
|
+
.slice(0, OPEN_EDITORS_CAP)
|
|
129
|
+
.map((e) => (e.active ? `${e.file} (active)` : e.file));
|
|
130
|
+
const more = editors.length - names.length;
|
|
131
|
+
lines.push(
|
|
132
|
+
`Open editors: ${names.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const sel = ctx.selection;
|
|
136
|
+
if (sel && typeof sel.text === "string" && sel.text.length > 0) {
|
|
137
|
+
const start = sel.selection?.start?.line;
|
|
138
|
+
const end = sel.selection?.end?.line;
|
|
139
|
+
const range =
|
|
140
|
+
Number.isInteger(start) && Number.isInteger(end)
|
|
141
|
+
? `:${start + 1}-${end + 1}` // editor lines are 0-based
|
|
142
|
+
: "";
|
|
143
|
+
const text =
|
|
144
|
+
sel.text.length > SELECTION_TEXT_CAP
|
|
145
|
+
? sel.text.slice(0, SELECTION_TEXT_CAP) + "\n...(selection truncated)"
|
|
146
|
+
: sel.text;
|
|
147
|
+
lines.push(`Selected text in ${sel.file || "the active editor"}${range}:`);
|
|
148
|
+
lines.push(text);
|
|
149
|
+
} else if (sel?.file && !active) {
|
|
150
|
+
lines.push(`Active file: ${sel.file}`);
|
|
151
|
+
}
|
|
152
|
+
if (lines.length === 0) return null;
|
|
153
|
+
return (
|
|
154
|
+
"<ide-context>\n" +
|
|
155
|
+
"Live editor state, shared automatically at prompt time (an IDE is " +
|
|
156
|
+
"connected). This reflects what the user is looking at NOW:\n" +
|
|
157
|
+
lines.join("\n") +
|
|
158
|
+
"\n</ide-context>"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Append extra text to user-turn content, preserving multimodal arrays
|
|
164
|
+
* (OpenAI-style content parts from --image runs).
|
|
165
|
+
*/
|
|
166
|
+
export function appendTextToContent(content, extra) {
|
|
167
|
+
if (!extra) return content;
|
|
168
|
+
if (typeof content === "string") {
|
|
169
|
+
return content.length > 0 ? `${content}\n\n${extra}` : extra;
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(content)) {
|
|
172
|
+
return [...content, { type: "text", text: extra }];
|
|
173
|
+
}
|
|
174
|
+
return content;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* One-call convenience for entry points: collect + format. Returns the tagged
|
|
179
|
+
* block string or null. Never throws.
|
|
180
|
+
*/
|
|
181
|
+
export async function buildIdePromptContext(mcp, opts = {}) {
|
|
182
|
+
try {
|
|
183
|
+
const ctx = await collectIdeContext(mcp, opts);
|
|
184
|
+
return ctx ? formatIdeContext(ctx) : null;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Post-edit diagnostics feedback (Claude-Code parity) ────────────────────
|
|
191
|
+
//
|
|
192
|
+
// After the agent mutates a file, the connected IDE's language servers see the
|
|
193
|
+
// change and update their diagnostics. Pulling them right back into the tool
|
|
194
|
+
// result lets the model fix what it just broke in the SAME loop instead of
|
|
195
|
+
// discovering it turns later. The IDE needs a beat to re-lint, hence the
|
|
196
|
+
// settle delay (CC_IDE_DIAG_SETTLE_MS overrides; 0 skips the wait).
|
|
197
|
+
|
|
198
|
+
/** Give language servers this long to notice the disk change before asking. */
|
|
199
|
+
const DIAG_SETTLE_MS = 600;
|
|
200
|
+
/** At most this many diagnostics are surfaced per edit. */
|
|
201
|
+
const DIAG_CAP = 10;
|
|
202
|
+
/** Only these severities are worth interrupting the model for. */
|
|
203
|
+
const DIAG_SEVERITIES = new Set(["error", "warning"]);
|
|
204
|
+
|
|
205
|
+
/** Does this MCP surface expose the IDE bridge's diagnostics tool? */
|
|
206
|
+
export function hasIdeDiagnosticsTool(mcp) {
|
|
207
|
+
return !!(
|
|
208
|
+
mcp?.mcpClient?.callTool &&
|
|
209
|
+
mcp.externalToolExecutors?.mcp__ide__getDiagnostics?.kind === "mcp"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Pull the IDE's current error/warning diagnostics for one file. `mcp` accepts
|
|
215
|
+
* either a resolveAgentMcp bundle or agent-core's tool context (both carry
|
|
216
|
+
* `mcpClient` + `externalToolExecutors`). Returns a non-empty array or null.
|
|
217
|
+
* Never throws.
|
|
218
|
+
*
|
|
219
|
+
* @param {object} mcp { mcpClient, externalToolExecutors }
|
|
220
|
+
* @param {string} filePath absolute path of the just-edited file
|
|
221
|
+
* @param {object} opts { env?, settleMs?, timeoutMs? }
|
|
222
|
+
*/
|
|
223
|
+
export async function collectIdeDiagnostics(mcp, filePath, opts = {}) {
|
|
224
|
+
const env = opts.env || process.env;
|
|
225
|
+
if (!ideContextEnabled(env)) return null;
|
|
226
|
+
if (!filePath || !hasIdeDiagnosticsTool(mcp)) return null;
|
|
227
|
+
const settle =
|
|
228
|
+
opts.settleMs ??
|
|
229
|
+
(Number.isFinite(Number(env.CC_IDE_DIAG_SETTLE_MS))
|
|
230
|
+
? Number(env.CC_IDE_DIAG_SETTLE_MS)
|
|
231
|
+
: DIAG_SETTLE_MS);
|
|
232
|
+
if (settle > 0) await new Promise((r) => setTimeout(r, settle));
|
|
233
|
+
const exec = mcp.externalToolExecutors.mcp__ide__getDiagnostics;
|
|
234
|
+
let p;
|
|
235
|
+
try {
|
|
236
|
+
p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, {
|
|
237
|
+
path: filePath,
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const data = await withTimeout(
|
|
243
|
+
p.then(parseToolResultJson),
|
|
244
|
+
opts.timeoutMs || DEFAULT_TIMEOUT_MS,
|
|
245
|
+
);
|
|
246
|
+
const all = Array.isArray(data?.diagnostics) ? data.diagnostics : null;
|
|
247
|
+
if (!all) return null;
|
|
248
|
+
const relevant = all.filter(
|
|
249
|
+
(d) => d && DIAG_SEVERITIES.has(String(d.severity).toLowerCase()),
|
|
250
|
+
);
|
|
251
|
+
return relevant.length > 0 ? relevant : null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Render pulled diagnostics as a compact feedback string for the tool result.
|
|
256
|
+
* Returns null when there is nothing to report.
|
|
257
|
+
*/
|
|
258
|
+
export function formatIdeDiagnostics(diags, { cap = DIAG_CAP } = {}) {
|
|
259
|
+
if (!Array.isArray(diags) || diags.length === 0) return null;
|
|
260
|
+
const shown = diags.slice(0, cap).map((d) => {
|
|
261
|
+
const loc = Number.isInteger(d.line) ? `:${d.line + 1}` : "";
|
|
262
|
+
const src = d.source ? ` (${d.source})` : "";
|
|
263
|
+
return ` [${d.severity}] ${d.file || ""}${loc} ${d.message || ""}${src}`.trimEnd();
|
|
264
|
+
});
|
|
265
|
+
const more = diags.length - shown.length;
|
|
266
|
+
return (
|
|
267
|
+
`IDE diagnostics after this edit (${diags.length} problem${diags.length === 1 ? "" : "s"}):\n` +
|
|
268
|
+
shown.join("\n") +
|
|
269
|
+
(more > 0 ? `\n (+${more} more)` : "")
|
|
270
|
+
);
|
|
271
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL `@` file-reference tab-completion (Claude-Code @-mention parity).
|
|
3
|
+
*
|
|
4
|
+
* Typing `@src/fo<TAB>` in the agent REPL completes file/dir paths the same
|
|
5
|
+
* way the @-token will later be expanded by file-ref-expander. Two candidate
|
|
6
|
+
* sources, merged:
|
|
7
|
+
* 1. the filesystem under cwd (dirs get a trailing `/` so TAB can descend)
|
|
8
|
+
* 2. the connected IDE's OPEN EDITOR TABS (when the IDE bridge is up) —
|
|
9
|
+
* the files you are working on rank first, before any fs listing.
|
|
10
|
+
*
|
|
11
|
+
* IDE tabs come from an injected async `getIdeOpenFiles()`; the completer
|
|
12
|
+
* caches the last list (TTL) and refreshes in the background, so completion
|
|
13
|
+
* stays synchronous-fast and a slow/dead IDE costs nothing (first TAB simply
|
|
14
|
+
* has no IDE entries yet).
|
|
15
|
+
*/
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
|
|
19
|
+
/** Refresh the IDE open-editors list at most this often. */
|
|
20
|
+
const IDE_CACHE_TTL_MS = 5000;
|
|
21
|
+
/** Hard cap on candidates shown per TAB. */
|
|
22
|
+
const MAX_CANDIDATES = 30;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Find a trailing `@token` being typed at the end of the line. Returns
|
|
26
|
+
* `{ prefix }` (token without `@`, may be "") or null when the cursor is not
|
|
27
|
+
* on an @-token. An @ must start the line or follow whitespace, mirroring
|
|
28
|
+
* file-ref-expander's token rules.
|
|
29
|
+
*/
|
|
30
|
+
export function extractAtPrefix(line) {
|
|
31
|
+
const m = /(^|\s)@([^\s@]*)$/.exec(line || "");
|
|
32
|
+
return m ? { prefix: m[2] } : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Normalize to forward slashes (what @refs use on every platform). */
|
|
36
|
+
function fwd(p) {
|
|
37
|
+
return String(p).replace(/\\/g, "/");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Filesystem candidates for an @-prefix, relative to cwd. Directories get a
|
|
42
|
+
* trailing `/`. Missing dirs / unreadable entries → empty (best-effort).
|
|
43
|
+
*/
|
|
44
|
+
export function fileCandidates(prefix, { cwd = process.cwd(), deps } = {}) {
|
|
45
|
+
const readdir = deps?.readdir || ((d) => fs.readdirSync(d));
|
|
46
|
+
const isDir =
|
|
47
|
+
deps?.isDir ||
|
|
48
|
+
((p) => {
|
|
49
|
+
try {
|
|
50
|
+
return fs.statSync(p).isDirectory();
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const norm = fwd(prefix);
|
|
56
|
+
const slash = norm.lastIndexOf("/");
|
|
57
|
+
const dirPart = slash >= 0 ? norm.slice(0, slash + 1) : "";
|
|
58
|
+
const basePart = slash >= 0 ? norm.slice(slash + 1) : norm;
|
|
59
|
+
const absDir = path.resolve(cwd, dirPart || ".");
|
|
60
|
+
let names;
|
|
61
|
+
try {
|
|
62
|
+
names = readdir(absDir);
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const name of names) {
|
|
68
|
+
if (basePart && !name.toLowerCase().startsWith(basePart.toLowerCase())) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (name === "node_modules" || name === ".git") continue;
|
|
72
|
+
const rel = dirPart + name;
|
|
73
|
+
out.push(isDir(path.join(absDir, name)) ? `${rel}/` : rel);
|
|
74
|
+
if (out.length >= MAX_CANDIDATES) break;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build a readline completer. Returns `completer(line)` → `[hits, replaced]`
|
|
81
|
+
* per the readline contract; non-@ lines complete to nothing.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} opts { cwd?, getIdeOpenFiles?: () => Promise<string[]>, deps? }
|
|
84
|
+
*/
|
|
85
|
+
export function makeAtCompleter(opts = {}) {
|
|
86
|
+
const cwd = opts.cwd || process.cwd();
|
|
87
|
+
const getIde = opts.getIdeOpenFiles || null;
|
|
88
|
+
const now = opts.deps?.now || Date.now;
|
|
89
|
+
let ideFiles = [];
|
|
90
|
+
let ideFetchedAt = -Infinity; // "never" — the first @ must always fetch
|
|
91
|
+
let ideInFlight = false;
|
|
92
|
+
|
|
93
|
+
const refreshIde = () => {
|
|
94
|
+
if (!getIde || ideInFlight || now() - ideFetchedAt < IDE_CACHE_TTL_MS) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
ideInFlight = true;
|
|
98
|
+
Promise.resolve()
|
|
99
|
+
.then(() => getIde())
|
|
100
|
+
.then((files) => {
|
|
101
|
+
ideFiles = Array.isArray(files)
|
|
102
|
+
? files
|
|
103
|
+
.filter((f) => typeof f === "string" && f.length > 0)
|
|
104
|
+
.map((f) => {
|
|
105
|
+
const rel = path.relative(cwd, f);
|
|
106
|
+
// Keep workspace files relative (the natural @ref form);
|
|
107
|
+
// out-of-workspace files keep their absolute path.
|
|
108
|
+
return rel && !rel.startsWith("..") ? fwd(rel) : fwd(f);
|
|
109
|
+
})
|
|
110
|
+
: [];
|
|
111
|
+
ideFetchedAt = now();
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {
|
|
114
|
+
ideFetchedAt = now(); // don't hammer a dead IDE
|
|
115
|
+
})
|
|
116
|
+
.finally(() => {
|
|
117
|
+
ideInFlight = false;
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const completer = (line) => {
|
|
122
|
+
const at = extractAtPrefix(line);
|
|
123
|
+
if (!at) return [[], line];
|
|
124
|
+
refreshIde(); // async top-up for the NEXT tab; this one uses the cache
|
|
125
|
+
const norm = fwd(at.prefix).toLowerCase();
|
|
126
|
+
const fromIde = ideFiles.filter((f) => f.toLowerCase().startsWith(norm));
|
|
127
|
+
const fromFs = fileCandidates(at.prefix, { cwd, deps: opts.deps });
|
|
128
|
+
const merged = [...new Set([...fromIde, ...fromFs])].slice(
|
|
129
|
+
0,
|
|
130
|
+
MAX_CANDIDATES,
|
|
131
|
+
);
|
|
132
|
+
// readline replaces `replaced` with the chosen hit — keep the `@`.
|
|
133
|
+
return [merged.map((m) => `@${m}`), `@${at.prefix}`];
|
|
134
|
+
};
|
|
135
|
+
// test seam: expose the cache refresher state
|
|
136
|
+
completer._refreshIde = refreshIde;
|
|
137
|
+
completer._ideState = () => ({ ideFiles, ideFetchedAt });
|
|
138
|
+
return completer;
|
|
139
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -74,6 +74,7 @@ import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
|
74
74
|
import { composeSystemPrompt } from "../runtime/system-prompt.js";
|
|
75
75
|
import { makeFallbackChatFn } from "../runtime/fallback-model.js";
|
|
76
76
|
import { resolveSlashMacro } from "./slash-macro.js";
|
|
77
|
+
import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* Reference to the runtime DB for hook execution (set during startAgentRepl)
|
|
@@ -129,7 +130,8 @@ async function _persistAlwaysAllow(tool, args) {
|
|
|
129
130
|
scope: "local",
|
|
130
131
|
});
|
|
131
132
|
if (!_permissionRules) _permissionRules = { allow: [], ask: [], deny: [] };
|
|
132
|
-
if (!_permissionRules.allow.includes(rule))
|
|
133
|
+
if (!_permissionRules.allow.includes(rule))
|
|
134
|
+
_permissionRules.allow.push(rule);
|
|
133
135
|
return { rule, file };
|
|
134
136
|
} catch (err) {
|
|
135
137
|
process.stderr.write(` always-allow persist failed: ${err.message}\n`);
|
|
@@ -504,9 +506,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
504
506
|
// settings.json SessionStart hooks → inject session context (observe-only).
|
|
505
507
|
if (_settingsHooks) {
|
|
506
508
|
try {
|
|
507
|
-
const { runSessionStartHooks } =
|
|
508
|
-
"../lib/settings-hook-events.cjs"
|
|
509
|
-
);
|
|
509
|
+
const { runSessionStartHooks } =
|
|
510
|
+
await import("../lib/settings-hook-events.cjs");
|
|
510
511
|
const ctx = runSessionStartHooks(_settingsHooks, {
|
|
511
512
|
source: "startup",
|
|
512
513
|
cwd: process.cwd(),
|
|
@@ -731,11 +732,35 @@ export async function startAgentRepl(options = {}) {
|
|
|
731
732
|
return chalk.green("> ");
|
|
732
733
|
};
|
|
733
734
|
|
|
735
|
+
// `@` tab-completion (Claude-Code @-mention parity): filesystem paths +
|
|
736
|
+
// (when the IDE bridge is connected) the editor's open tabs ranked first.
|
|
737
|
+
const { makeAtCompleter } = await import("../lib/repl-completer.js");
|
|
738
|
+
const atCompleter = makeAtCompleter({
|
|
739
|
+
cwd: process.cwd(),
|
|
740
|
+
getIdeOpenFiles: async () => {
|
|
741
|
+
const exec = _adhocMcp?.externalToolExecutors?.mcp__ide__getOpenEditors;
|
|
742
|
+
if (!exec || exec.kind !== "mcp" || !_adhocMcp?.mcpClient?.callTool) {
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
const { parseToolResultJson } = await import("../lib/ide-context.js");
|
|
746
|
+
const res = await _adhocMcp.mcpClient.callTool(
|
|
747
|
+
exec.serverName,
|
|
748
|
+
exec.toolName,
|
|
749
|
+
{},
|
|
750
|
+
);
|
|
751
|
+
const data = parseToolResultJson(res);
|
|
752
|
+
return Array.isArray(data?.editors)
|
|
753
|
+
? data.editors.map((e) => e?.file).filter(Boolean)
|
|
754
|
+
: [];
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
|
|
734
758
|
const rl = readline.createInterface({
|
|
735
759
|
input: process.stdin,
|
|
736
760
|
output: process.stdout,
|
|
737
761
|
prompt: getPrompt(),
|
|
738
762
|
terminal: true,
|
|
763
|
+
completer: atCompleter,
|
|
739
764
|
});
|
|
740
765
|
|
|
741
766
|
logger.log(chalk.bold("\nChainlessChain Agent"));
|
|
@@ -805,7 +830,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
805
830
|
if (_statusLineEnabled && _renderStatus) {
|
|
806
831
|
const line = _renderStatus();
|
|
807
832
|
// Built-in line is dimmed; a custom command may carry its own ANSI.
|
|
808
|
-
if (line)
|
|
833
|
+
if (line)
|
|
834
|
+
process.stdout.write((_customStatus ? line : chalk.dim(line)) + "\n");
|
|
809
835
|
}
|
|
810
836
|
rl.setPrompt(getPrompt());
|
|
811
837
|
rl.prompt();
|
|
@@ -1024,15 +1050,16 @@ export async function startAgentRepl(options = {}) {
|
|
|
1024
1050
|
logger.info("Status line: on");
|
|
1025
1051
|
} else {
|
|
1026
1052
|
// bare / "show" → report state + a one-off render
|
|
1027
|
-
const line =
|
|
1053
|
+
const line =
|
|
1054
|
+
_statusLineEnabled && _renderStatus ? _renderStatus() : null;
|
|
1028
1055
|
if (line) {
|
|
1029
|
-
logger.info(
|
|
1030
|
-
`Status line: ${_customStatus ? line : chalk.dim(line)}`,
|
|
1031
|
-
);
|
|
1056
|
+
logger.info(`Status line: ${_customStatus ? line : chalk.dim(line)}`);
|
|
1032
1057
|
} else {
|
|
1033
1058
|
logger.info(
|
|
1034
1059
|
`Status line: ${_statusLineEnabled ? "on (no content yet)" : "off"}` +
|
|
1035
|
-
(_statusLineEnabled
|
|
1060
|
+
(_statusLineEnabled
|
|
1061
|
+
? ""
|
|
1062
|
+
: ` — enable with ${chalk.cyan("/statusline on")}`),
|
|
1036
1063
|
);
|
|
1037
1064
|
}
|
|
1038
1065
|
if (_customStatus) {
|
|
@@ -1046,9 +1073,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
1046
1073
|
if (trimmed === "/output-style" || trimmed.startsWith("/output-style ")) {
|
|
1047
1074
|
const arg = trimmed.slice("/output-style".length).trim();
|
|
1048
1075
|
try {
|
|
1049
|
-
const { discoverOutputStyles, getOutputStyle } =
|
|
1050
|
-
"../lib/output-styles.js"
|
|
1051
|
-
);
|
|
1076
|
+
const { discoverOutputStyles, getOutputStyle } =
|
|
1077
|
+
await import("../lib/output-styles.js");
|
|
1052
1078
|
if (!arg) {
|
|
1053
1079
|
logger.log(chalk.bold("Output styles:"));
|
|
1054
1080
|
for (const s of discoverOutputStyles(process.cwd())) {
|
|
@@ -1801,15 +1827,47 @@ export async function startAgentRepl(options = {}) {
|
|
|
1801
1827
|
return;
|
|
1802
1828
|
}
|
|
1803
1829
|
|
|
1830
|
+
// `/mcp` — overview of connected MCP servers' resources + prompts.
|
|
1831
|
+
if (trimmed === "/mcp" || trimmed === "/mcp ") {
|
|
1832
|
+
const mcpClient = _adhocMcp?.mcpClient || _bundleMcpClient;
|
|
1833
|
+
logger.log(renderMcpSurface(mcpClient));
|
|
1834
|
+
prompt();
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1804
1838
|
// User-defined slash-command macros (.claude/commands/*.md), Claude-Code
|
|
1805
1839
|
// parity. resolveSlashMacro maps a leading /name to a command macro and
|
|
1806
1840
|
// expands its template; a non-match returns the line unchanged so a literal
|
|
1807
1841
|
// prompt like "/etc/hosts" still reaches the LLM. Wire is unit-tested.
|
|
1808
1842
|
let promptText = trimmed;
|
|
1843
|
+
|
|
1844
|
+
// MCP server-provided prompts (Claude-Code parity): `/mcp__<server>__<name>
|
|
1845
|
+
// [json-args]` fetches a rendered prompt template from the connected MCP
|
|
1846
|
+
// server and uses its text as this turn's input. Falls through unchanged
|
|
1847
|
+
// when the line isn't an MCP prompt command.
|
|
1848
|
+
if (promptText.startsWith("/mcp__")) {
|
|
1849
|
+
try {
|
|
1850
|
+
const expanded = await expandMcpPrompt(
|
|
1851
|
+
promptText,
|
|
1852
|
+
_adhocMcp?.mcpClient || _bundleMcpClient,
|
|
1853
|
+
);
|
|
1854
|
+
if (expanded != null) {
|
|
1855
|
+
promptText = expanded;
|
|
1856
|
+
logger.log(chalk.gray(`[mcp] prompt expanded`));
|
|
1857
|
+
}
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
logger.info(
|
|
1860
|
+
chalk.yellow(`[mcp] prompt expansion failed: ${err.message}`),
|
|
1861
|
+
);
|
|
1862
|
+
prompt();
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1809
1866
|
try {
|
|
1810
1867
|
const macro = await resolveSlashMacro(trimmed, { cwd: process.cwd() });
|
|
1811
1868
|
if (macro.matched) {
|
|
1812
|
-
for (const w of macro.warnings)
|
|
1869
|
+
for (const w of macro.warnings)
|
|
1870
|
+
logger.info(chalk.yellow(`[@ref] ${w}`));
|
|
1813
1871
|
promptText = macro.promptText;
|
|
1814
1872
|
logger.log(
|
|
1815
1873
|
chalk.gray(`[/${macro.name}] macro expanded (${macro.scope})`),
|
|
@@ -1864,9 +1922,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
1864
1922
|
// is observe-only). block → abort the turn; context → inject before the turn.
|
|
1865
1923
|
if (_settingsHooks) {
|
|
1866
1924
|
try {
|
|
1867
|
-
const { runUserPromptSubmitHooks } =
|
|
1868
|
-
"../lib/settings-hook-events.cjs"
|
|
1869
|
-
);
|
|
1925
|
+
const { runUserPromptSubmitHooks } =
|
|
1926
|
+
await import("../lib/settings-hook-events.cjs");
|
|
1870
1927
|
const ups = runUserPromptSubmitHooks(_settingsHooks, {
|
|
1871
1928
|
prompt: userContent,
|
|
1872
1929
|
cwd: process.cwd(),
|
|
@@ -1889,6 +1946,18 @@ export async function startAgentRepl(options = {}) {
|
|
|
1889
1946
|
}
|
|
1890
1947
|
}
|
|
1891
1948
|
|
|
1949
|
+
// IDE live context (Claude-Code parity): re-shared on every prompt while
|
|
1950
|
+
// an IDE bridge is connected — the user's selection moves between turns.
|
|
1951
|
+
// Ephemeral: persistence stores effectivePrompt, not this snapshot.
|
|
1952
|
+
// Best-effort; CC_IDE_CONTEXT=0 disables.
|
|
1953
|
+
try {
|
|
1954
|
+
const { buildIdePromptContext } = await import("../lib/ide-context.js");
|
|
1955
|
+
const ideCtx = await buildIdePromptContext(_adhocMcp);
|
|
1956
|
+
if (ideCtx) userContent += `\n\n${ideCtx}`;
|
|
1957
|
+
} catch (_err) {
|
|
1958
|
+
// optional polish — never fail the turn over it
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1892
1961
|
// Add user message
|
|
1893
1962
|
messages.push({ role: "user", content: userContent });
|
|
1894
1963
|
|
|
@@ -2136,9 +2205,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
2136
2205
|
// settings.json SessionEnd hooks (observe-only) when the REPL exits.
|
|
2137
2206
|
if (_settingsHooks) {
|
|
2138
2207
|
try {
|
|
2139
|
-
const { runObserveHooks } =
|
|
2140
|
-
"../lib/settings-hook-events.cjs"
|
|
2141
|
-
);
|
|
2208
|
+
const { runObserveHooks } =
|
|
2209
|
+
await import("../lib/settings-hook-events.cjs");
|
|
2142
2210
|
runObserveHooks(
|
|
2143
2211
|
_settingsHooks,
|
|
2144
2212
|
"SessionEnd",
|