chainlesschain 0.162.159 → 0.162.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-ByIiF5bT.js → AIOps-CkRHjW8e.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-NlgSFZq5.js → ActionButton-BP3MeNi2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D17cOUcb.js → Analytics-CjbjQsOr.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B4mEZcbl.js → AppLayout-i0adH1M8.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CASsmyQw.js → Artifacts-CCCG2GOU.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DYAia2e2.js → Audit-DrsgvwHC.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C1lvYUSu.js → BackgroundAgents-DkXNkbZM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B6agK6bA.js → Backup-DarTWCex.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DM43R2vy.js → BaseInput-DaDzhcto.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Dtdp3eXp.js → Chat-C4EzGw6V.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BL5c_OQ1.js → ChatBubbleRenderer-lkavIWMS.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-D2xf7uZt.js → Checkbox-gddNgaQi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D3j45jJL.js → Codegen-BKldFWf9.js} +1 -1
- package/src/assets/web-panel/assets/{Col-voPJ9fUZ.js → Col-ChR-brDs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DEsyTESF.js → Community-DlHtZX86.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-OHV_QXLD.js → Compact-BAhfcBpC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-eDTJWC-J.js → Compliance-gIrKkm6m.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BBvy-6d1.js → Cowork-BYnzLBO3.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Dw2eoJxu.js → Cron-ByUDIljI.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DQHms_Bh.js → Crosschain-DOdNbHFp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cyu7Id1c.js → DID-BJHaNWD2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-sIDzkG92.js → Dashboard-Drm_tClz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DUW3T2ey.js → Dropdown-CA1WLzGN.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CECELXW8.js → EmailListRenderer-Di1SKjNO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BIZR37ov.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BEZIQX84.js → Federation-CubPwv_N.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BcdUMznL.js → FormItemContext-CsUFe5bU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DsN_fr5H.js → GenericCardRenderer-D7xRjPjZ.js} +1 -1
- package/src/assets/web-panel/assets/{Git-63LhjBx2.js → Git-D-Qx9dhO.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYtQtIcH.js → Governance-_YMYaU4n.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BwR6UUox.js → Inference-J7Yzh-K_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-D9lVNTUA.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C4q5h7Ut.js → Logs-oMTSYfGY.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DexLlXfN.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Plksliuw.js → Marketplace-DKUqYDaF.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DZUFfeuG.js → McpTools-BEv3Gx61.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-NoDYt29V.js → Memory-CtA8XD2y.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CEXGTkdh.js → MobileBridge-ug7c1mMj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DTAa0cwy.js → Mtc-WJaMs4i2.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CVV7LnNS.js → MtcAudit-DXnK_-ru.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BqqGrjt_.js → Multisig-C4Ev7726.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C4WP8DRp.js → NLProgramming-BMFb9_zk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Zo4DJUVI.js → Notes-Bl93nvBX.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DBcjRaz2.js → NotificationSettings-BN2W5cl6.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dxc9oN4o.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BT9WPBOX.js → Organization-CAs48637.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D6b4bZ3q.js → Overflow-Bhq27Zoc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cw3OsYx-.js → P2P-D61-2542.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C0nyiK2b.js → PdhVaultBrowser-C1j4iUxz.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-zVBZEB_C.js → Permissions-rJ2-fScM.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BOspsFew.js → PersonalDataHub-DFIlTok6.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CQWfZ4XH.js → Pipeline-BjIgEWoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C2oGJ_WL.js → Privacy-BQoH5wcX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BZMR4qY1.js → ProjectInit-CYR_-_Ub.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DAujoP63.js → ProjectSettings-nkatCq-9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
- package/src/assets/web-panel/assets/{Providers-D_RGHM-v.js → Providers-BnKM9fsF.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Mf6KfL2J.js → QrScannerModal-Cfe82dbz.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-5D8URoI_.js → QuickAsk-r1RbPufV.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Df4kC8Gy.js → Recommend-CjCbM2WE.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DGi3f3vO.js → RemoteSession-BuMGPH6V.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-jmkGFNWb.js → Reputation-Bmj3NY1F.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DP6Pcu3m.js → Row-BIsNcE_1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D60hSuPO.js → RssFeed-BObmojD2.js} +3 -3
- package/src/assets/web-panel/assets/{Search-0mf1BQUK.js → Search-BRmbkQNS.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CfZtLFYV.js → Security-_rF3DRpY.js} +4 -4
- package/src/assets/web-panel/assets/{Services-D9zpZLWz.js → Services-D0If9ZV4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D1_44eMD.js → Skeleton-o7zIksUg.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-cVFa8zkz.js → Skills-oUR7zi9u.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cinpzc3L.js → Sla-fqKyGi03.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-bqdiJE_P.js → SpeechSettings-B1wZ_gxx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgl_SLgj.js → SyncSettings-B3qJWYXq.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DawWAEhy.js → Tasks-BH5_dUfr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Ca0nY651.js → Templates-CCIVpjRP.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CgaGEhLm.js → Tenant-2GZtTGnB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D-9EnpRx.js → Terminal-BlMkfs_u.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CSOxDHZ6.js → TimelineRenderer-U6hi5OMc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpwLZpL3.js → Tokens-C-_BerGJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DgowwBK5.js → Trigger-DaKzBgN3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BZutapjE.js → Trust-CLvcsco3.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-QZPt8_Sp.js → UkeySign-BFn_Y1zZ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DlQx5UDJ.js → VideoEditing-B0V18dLT.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ZfdTgtWA.js → Wallet-BbmP7Imu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CAp0y_Cr.js → WebAuthn-Dewj_0kt.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BVITZEmr.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CC2tPaNg.js → chat-B1OIpZnh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-31brc9CA.js → colors-CSGNVdri.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DLMkC9C-.js → compact-item-8S0vcg0u.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BMtveI6F.js → createContext-vZyc27TN.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DbD6WHW2.js → hasIn-DWw3-UhA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm0byjm1.js → index-B4pP2ycr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXa-4zd.js → index-BDHyovnK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_eUKGYP.js → index-BKEmQQvR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOM-VFlv.js → index-BQssf0EQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-bdaJwTXJ.js → index-BZnauCqN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cwbqh0KZ.js → index-BdyokEau.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJvxFyIz.js → index-BeBv896m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DndNnYc0.js → index-BjC-TrOb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWgfipcC.js → index-BkWd6YL7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7cV0tu0.js → index-BoIPHP4R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcVszn2b.js → index-BuB5P0TR.js} +1 -1
- package/src/assets/web-panel/assets/{index-COHHQ9cU.js → index-BwKrTVV0.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1PfeI88.js → index-C6r27A-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cod97Urs.js → index-CB2MOS_8.js} +3 -3
- package/src/assets/web-panel/assets/{index-CEwjBA9q.js → index-CFQWZvak.js} +1 -1
- package/src/assets/web-panel/assets/{index-D67kIagl.js → index-CI3slBKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-jG6h22Qg.js → index-CJgKP2cm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZl-UXB6.js → index-CLPr2vC_.js} +1 -1
- package/src/assets/web-panel/assets/{index-ntVQqSML.js → index-CbZqw7pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-cGlCDW.js → index-CfEvacz7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFRLmCR3.js → index-CwXBblm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-AaIKG60c.js → index-D1i_8oop.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPpS-lGb.js → index-D8qXVfoV.js} +1 -1
- package/src/assets/web-panel/assets/{index-WQUO2vvK.js → index-DKwl1AkQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUmLMFsy.js → index-DLAvUtOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbc2JeQG.js → index-DLgkAhEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKAB6-6V.js → index-DPgY5GLy.js} +1 -1
- package/src/assets/web-panel/assets/{index-ALvoNTRF.js → index-Dc40pX3F.js} +1 -1
- package/src/assets/web-panel/assets/{index-ep6JchNG.js → index-DcfLXAW7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C21pdBqs.js → index-DeCS12Mh.js} +1 -1
- package/src/assets/web-panel/assets/{index-3oBsXZmF.js → index-Dg0Izipr.js} +1 -1
- package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
- package/src/assets/web-panel/assets/{index-BnFLzejT.js → index-DtHWHJnj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7LHNw_N.js → index-Q6oyLnKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoO4WEJB.js → index-dxocDVD_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiRQUoyE.js → index-t-q3rkVm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFXKbOek.js → index-tr1x96W9.js} +1 -1
- package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
- package/src/assets/web-panel/assets/{index-CJhREOlG.js → index-xx5M5Rbj.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D5UoBRhN.js → initDefaultProps-Di5YFId0.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DltOw0CR.js → motion-BVSN0aeL.js} +1 -1
- package/src/assets/web-panel/assets/{move-_oPkk5zj.js → move-DHNiaP2l.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-EES8ROCS.js → mtc-parser-xJPf3gQD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B-_39x_M.js → omit-C7qzUdkN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-l6D0yJHz.js → pickAttrs-pSpjg3Le.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DIeUK31L.js → placementArrow-cs_jL_UC.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dG4EzUQo.js → responsiveObserve-DkS_Zs4r.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWFzYGdH.js → slide-B5O1Kpxo.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CECdTA5H.js → statusUtils-BX02GnZp.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D6kP-mMK.js → styleChecker-EmBYRftd.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BWfna_rd.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bb-j6rzo.js → useFs-CfP7M1m7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D5OIsYM4.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNpwzFTM.js → vnode-7xh0If_O.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-vNLpFQBW.js → zoom-DmitvTR2.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +65 -6
- package/src/commands/background-session.js +30 -0
- package/src/commands/mcp.js +46 -0
- package/src/commands/session.js +70 -0
- package/src/data/changelog.json +7 -2
- package/src/harness/jsonl-session-store.js +70 -1
- package/src/harness/mcp-client.js +50 -0
- package/src/lib/agent-sandbox.js +68 -0
- package/src/lib/agents.js +12 -4
- package/src/lib/api-key-helper.js +62 -0
- package/src/lib/background-agent-supervisor.js +32 -0
- package/src/lib/dependency-install-policy.js +130 -0
- package/src/lib/exit-codes.cjs +77 -0
- package/src/lib/headless-manifest.js +175 -0
- package/src/lib/hook-runner.cjs +70 -4
- package/src/lib/ide-context-redaction.js +130 -0
- package/src/lib/ide-context.js +89 -13
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/llm-config-defaults.js +24 -8
- package/src/lib/project-mcp-trust.js +87 -0
- package/src/lib/sensitive-file-guard.js +51 -0
- package/src/lib/sub-agent-context.js +7 -3
- package/src/runtime/agent-core.js +102 -6
- package/src/runtime/coding-agent-contract-shared.cjs +19 -2
- package/src/runtime/headless-runner.js +66 -7
- package/src/runtime/headless-stream.js +29 -1
- package/src/runtime/mcp-config.js +32 -1
- package/src/assets/web-panel/assets/MobileProjects-DnbJkbZo.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +0 -1
- package/src/assets/web-panel/assets/Projects-DvH_P2Uq.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +0 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +0 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +0 -1
package/src/commands/agent.js
CHANGED
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
} from "../runtime/fallback-model.js";
|
|
17
17
|
import { resolveImages, resolveVisionLlm } from "../lib/image-input.js";
|
|
18
18
|
import { loadConfig } from "../lib/config-manager.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
normalizeAgentSandbox,
|
|
21
|
+
assertSandboxAvailable,
|
|
22
|
+
} from "../lib/agent-sandbox.js";
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Resolve + validate `--add-dir` values into absolute, existing, de-duped
|
|
@@ -172,6 +175,14 @@ export function registerAgentCommand(program) {
|
|
|
172
175
|
"--bare",
|
|
173
176
|
"Everything --safe-mode disables PLUS skills, plugins and MCP/IDE auto-connect — minimal fast surface for scripted calls (explicit --mcp-config still loads; permission rules STAY active)",
|
|
174
177
|
)
|
|
178
|
+
.option(
|
|
179
|
+
"--ephemeral",
|
|
180
|
+
"No session persistence: nothing is written to the session store (headless; a --resume id still replays history read-only)",
|
|
181
|
+
)
|
|
182
|
+
.option(
|
|
183
|
+
"--capabilities",
|
|
184
|
+
"Print a machine-readable capability manifest (JSON: protocol version, tools, permission modes, exit codes) and exit",
|
|
185
|
+
)
|
|
175
186
|
.option(
|
|
176
187
|
"--disable-slash-commands",
|
|
177
188
|
"Interactive REPL: don't dispatch '/' input as slash commands — it reaches the model verbatim (/exit and /quit stay live)",
|
|
@@ -367,12 +378,31 @@ export function registerAgentCommand(program) {
|
|
|
367
378
|
false,
|
|
368
379
|
)
|
|
369
380
|
.action(async (task, options, command) => {
|
|
381
|
+
// --capabilities: print the machine-readable manifest and exit — no
|
|
382
|
+
// config, no bootstrap, no network (gap-analysis 2026-07-11 快速收益 #6).
|
|
383
|
+
if (options.capabilities) {
|
|
384
|
+
const { buildAgentCapabilities } =
|
|
385
|
+
await import("../lib/headless-manifest.js");
|
|
386
|
+
console.log(JSON.stringify(buildAgentCapabilities(), null, 2));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
370
389
|
// CC_API_KEY env fallback for --api-key: lets callers (IDE plugins,
|
|
371
390
|
// scripts) pass the key via the environment instead of argv, where it
|
|
372
391
|
// is visible to every local process (/proc/<pid>/cmdline, Win32_Process).
|
|
373
392
|
// An explicit --api-key still wins.
|
|
374
393
|
if (!options.apiKey && process.env.CC_API_KEY) {
|
|
375
394
|
options.apiKey = process.env.CC_API_KEY;
|
|
395
|
+
} else if (
|
|
396
|
+
options.apiKey &&
|
|
397
|
+
process.argv.includes("--api-key") &&
|
|
398
|
+
!process.env.VITEST &&
|
|
399
|
+
!process.env.VITEST_WORKER_ID
|
|
400
|
+
) {
|
|
401
|
+
// gap-analysis 2026-07-11: argv keys leak into shell history, process
|
|
402
|
+
// lists and logs — steer callers to the env/helper paths.
|
|
403
|
+
process.stderr.write(
|
|
404
|
+
"⚠️ --api-key on the command line is visible to every local process (shell history, ps). Prefer CC_API_KEY env or config llm.apiKeyHelper.\n",
|
|
405
|
+
);
|
|
376
406
|
}
|
|
377
407
|
const bypassPermissions = !!(
|
|
378
408
|
options.allowDangerousBypass ||
|
|
@@ -830,6 +860,7 @@ export function registerAgentCommand(program) {
|
|
|
830
860
|
outputStyle: options.outputStyle || null,
|
|
831
861
|
strictMcpConfig: options.strictMcpConfig === true,
|
|
832
862
|
replayUserMessages: options.replayUserMessages === true,
|
|
863
|
+
ephemeral: options.ephemeral === true,
|
|
833
864
|
maxCostUsd,
|
|
834
865
|
priceTable,
|
|
835
866
|
chatFn: fallbackChatFn,
|
|
@@ -937,6 +968,15 @@ export function registerAgentCommand(program) {
|
|
|
937
968
|
network: options.sandboxNetwork === true,
|
|
938
969
|
settings: settingsSandbox,
|
|
939
970
|
});
|
|
971
|
+
// Strict sandbox mode: failIfUnavailable refuses to START when the
|
|
972
|
+
// configured engine can't run — no silent per-command degradation.
|
|
973
|
+
try {
|
|
974
|
+
assertSandboxAvailable(agentSandbox);
|
|
975
|
+
} catch (err) {
|
|
976
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
977
|
+
await _finishWorktree();
|
|
978
|
+
process.exit(6); // config error (see lib/exit-codes.cjs)
|
|
979
|
+
}
|
|
940
980
|
// Resume requested onto a session with no headless (JSONL) transcript?
|
|
941
981
|
// Warn instead of silently starting empty 鈥?headless resume rebuilds
|
|
942
982
|
// from the JSONL store only (DB-only sessions are not replayable here).
|
|
@@ -1025,6 +1065,8 @@ export function registerAgentCommand(program) {
|
|
|
1025
1065
|
// --settings: extra .claude/settings.json permission rules
|
|
1026
1066
|
settingsFile: options.settings || null,
|
|
1027
1067
|
outputStyle: options.outputStyle || null,
|
|
1068
|
+
// --ephemeral: no session persistence (resume replay stays read-only)
|
|
1069
|
+
ephemeral: options.ephemeral === true,
|
|
1028
1070
|
// --max-budget-usd: hard spend cap (+ price table from config llm.pricing)
|
|
1029
1071
|
maxCostUsd,
|
|
1030
1072
|
priceTable,
|
|
@@ -1074,6 +1116,13 @@ export function registerAgentCommand(program) {
|
|
|
1074
1116
|
return;
|
|
1075
1117
|
}
|
|
1076
1118
|
|
|
1119
|
+
// --ephemeral is a headless contract (the interactive REPL owns its own
|
|
1120
|
+
// session lifecycle) — warn instead of silently dropping the flag.
|
|
1121
|
+
if (options.ephemeral) {
|
|
1122
|
+
process.stderr.write(
|
|
1123
|
+
"--ephemeral is only used in headless mode (-p / a task / piped stdin); ignoring for the interactive session.\n",
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1077
1126
|
// Reached only for an interactive session, where --image has no turn to
|
|
1078
1127
|
// attach to 鈥?warn instead of silently dropping the attachment.
|
|
1079
1128
|
if (images.length) {
|
|
@@ -1082,6 +1131,20 @@ export function registerAgentCommand(program) {
|
|
|
1082
1131
|
);
|
|
1083
1132
|
}
|
|
1084
1133
|
|
|
1134
|
+
// Strict sandbox mode also guards the interactive session: refuse to
|
|
1135
|
+
// start when failIfUnavailable is set and the engine can't run.
|
|
1136
|
+
const interactiveSandbox = normalizeAgentSandbox(options.sandbox, {
|
|
1137
|
+
cwd: process.cwd(),
|
|
1138
|
+
network: options.sandboxNetwork === true,
|
|
1139
|
+
settings: settingsSandbox,
|
|
1140
|
+
});
|
|
1141
|
+
try {
|
|
1142
|
+
assertSandboxAvailable(interactiveSandbox);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
1145
|
+
process.exit(6); // config error (see lib/exit-codes.cjs)
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1085
1148
|
const runtime = createAgentRuntimeFactory().createAgentRuntime({
|
|
1086
1149
|
model: options.model,
|
|
1087
1150
|
thinking,
|
|
@@ -1103,11 +1166,7 @@ export function registerAgentCommand(program) {
|
|
|
1103
1166
|
parkOnExit: options.parkOnExit, // false when --no-park-on-exit
|
|
1104
1167
|
bundlePath: options.bundle || null,
|
|
1105
1168
|
additionalDirectories,
|
|
1106
|
-
sandbox:
|
|
1107
|
-
cwd: process.cwd(),
|
|
1108
|
-
network: options.sandboxNetwork === true,
|
|
1109
|
-
settings: settingsSandbox,
|
|
1110
|
-
}),
|
|
1169
|
+
sandbox: interactiveSandbox,
|
|
1111
1170
|
autoCheckpoint,
|
|
1112
1171
|
// --vim: start the REPL in vim-mode editing (also CC_VIM=1 or /vim).
|
|
1113
1172
|
vimMode: options.vim === true,
|
|
@@ -597,6 +597,36 @@ export function registerBackgroundSessionCommands(program) {
|
|
|
597
597
|
}
|
|
598
598
|
});
|
|
599
599
|
|
|
600
|
+
// daemon rm — record cleanup (gap-analysis 2026-07-11 P0 后台 Supervisor).
|
|
601
|
+
daemon
|
|
602
|
+
.command("rm <id>")
|
|
603
|
+
.description(
|
|
604
|
+
"Remove a finished background agent's record + log (running agents need --force; the conversation session itself is untouched)",
|
|
605
|
+
)
|
|
606
|
+
.option("--force", "Stop a still-running agent first, then remove")
|
|
607
|
+
.option("--keep-log", "Keep the log file, remove only the state record")
|
|
608
|
+
.option("--json", "Output as JSON")
|
|
609
|
+
.action(async (id, options) => {
|
|
610
|
+
try {
|
|
611
|
+
const { removeBackgroundAgent } = await loadSupervisor();
|
|
612
|
+
const result = removeBackgroundAgent(id, {
|
|
613
|
+
force: options.force === true,
|
|
614
|
+
keepLog: options.keepLog === true,
|
|
615
|
+
});
|
|
616
|
+
if (options.json) {
|
|
617
|
+
console.log(JSON.stringify(result, null, 2));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
logger.log(
|
|
621
|
+
chalk.green(`Removed background agent ${result.id}`) +
|
|
622
|
+
chalk.gray(` (was ${result.status})`),
|
|
623
|
+
);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
logger.error(chalk.red(error.message));
|
|
626
|
+
process.exitCode = 1;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
600
630
|
daemon
|
|
601
631
|
.command("stop [id]")
|
|
602
632
|
.description("Stop one background agent, or all running agents with --any")
|
package/src/commands/mcp.js
CHANGED
|
@@ -999,6 +999,52 @@ export function registerMcpCommand(program) {
|
|
|
999
999
|
});
|
|
1000
1000
|
|
|
1001
1001
|
// mcp scaffold — generate a boilerplate MCP server project
|
|
1002
|
+
mcp
|
|
1003
|
+
.command("trust-project")
|
|
1004
|
+
.description(
|
|
1005
|
+
"Re-trust the project .mcp.json after reviewing a content change (records its fingerprint for --project-mcp runs)",
|
|
1006
|
+
)
|
|
1007
|
+
.option("--json", "Output as JSON")
|
|
1008
|
+
.action(async (options) => {
|
|
1009
|
+
try {
|
|
1010
|
+
const fsMod = await import("node:fs");
|
|
1011
|
+
const { projectRootBase } = await import("../lib/project-root.cjs");
|
|
1012
|
+
const { recordProjectMcpTrust, projectMcpFingerprint } =
|
|
1013
|
+
await import("../lib/project-mcp-trust.js");
|
|
1014
|
+
const cwd = process.cwd();
|
|
1015
|
+
const candidates = [path.join(cwd, ".mcp.json")];
|
|
1016
|
+
const root = projectRootBase(cwd, { fs: fsMod.default, path });
|
|
1017
|
+
if (root) candidates.push(path.join(root, ".mcp.json"));
|
|
1018
|
+
const results = [];
|
|
1019
|
+
for (const file of [...new Set(candidates)]) {
|
|
1020
|
+
if (!fsMod.existsSync(file)) continue;
|
|
1021
|
+
const content = fsMod.readFileSync(file, "utf-8");
|
|
1022
|
+
const ok = recordProjectMcpTrust(file, content);
|
|
1023
|
+
results.push({
|
|
1024
|
+
file,
|
|
1025
|
+
trusted: ok,
|
|
1026
|
+
fingerprint: projectMcpFingerprint(content).slice(0, 16),
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
if (options.json) {
|
|
1030
|
+
console.log(JSON.stringify(results, null, 2));
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (results.length === 0) {
|
|
1034
|
+
console.log("No .mcp.json found in this project.");
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
for (const r of results) {
|
|
1038
|
+
console.log(
|
|
1039
|
+
`${r.trusted ? "Trusted" : "FAILED to record"} ${r.file} (${r.fingerprint})`,
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
console.error(`Failed: ${err.message}`);
|
|
1044
|
+
process.exitCode = 1;
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1002
1048
|
mcp
|
|
1003
1049
|
.command("scaffold <name>")
|
|
1004
1050
|
.description("Scaffold a new MCP server project (stdio or http+sse)")
|
package/src/commands/session.js
CHANGED
|
@@ -534,6 +534,76 @@ export function registerSessionCommand(program) {
|
|
|
534
534
|
}
|
|
535
535
|
});
|
|
536
536
|
|
|
537
|
+
// session rename — named sessions (gap-analysis 2026-07-11 快速收益 #5).
|
|
538
|
+
// Appends a session_rename event (hash chain stays intact); last one wins.
|
|
539
|
+
session
|
|
540
|
+
.command("rename")
|
|
541
|
+
.description("Rename a headless (JSONL) session — shows up in session list")
|
|
542
|
+
.argument("<id>", "Session ID")
|
|
543
|
+
.argument("<title...>", "New title")
|
|
544
|
+
.option("--json", "Output as JSON")
|
|
545
|
+
.action(async (id, title, options) => {
|
|
546
|
+
try {
|
|
547
|
+
const { renameSession } =
|
|
548
|
+
await import("../harness/jsonl-session-store.js");
|
|
549
|
+
const result = renameSession(
|
|
550
|
+
id,
|
|
551
|
+
Array.isArray(title) ? title.join(" ") : title,
|
|
552
|
+
);
|
|
553
|
+
if (options.json) {
|
|
554
|
+
console.log(JSON.stringify(result, null, 2));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
logger.success(`Renamed session ${result.id}`);
|
|
558
|
+
logger.info(` ${result.title}`);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
logger.error(`Failed: ${err.message}`);
|
|
561
|
+
process.exitCode = 1;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// session prune — retention sweep (gap-analysis 2026-07-11 P1 "保留期限").
|
|
566
|
+
session
|
|
567
|
+
.command("prune")
|
|
568
|
+
.description(
|
|
569
|
+
"Delete JSONL sessions whose last activity is older than a cutoff (newest N always kept)",
|
|
570
|
+
)
|
|
571
|
+
.requiredOption(
|
|
572
|
+
"--older-than <days>",
|
|
573
|
+
"Delete sessions idle for more than this many days",
|
|
574
|
+
)
|
|
575
|
+
.option("--keep <n>", "Always keep the newest N sessions (default 10)")
|
|
576
|
+
.option("--dry-run", "List what would be deleted without deleting")
|
|
577
|
+
.option("--json", "Output as JSON")
|
|
578
|
+
.action(async (options) => {
|
|
579
|
+
try {
|
|
580
|
+
const { pruneJsonlSessions } =
|
|
581
|
+
await import("../harness/jsonl-session-store.js");
|
|
582
|
+
const result = pruneJsonlSessions({
|
|
583
|
+
olderThanDays: Number(options.olderThan),
|
|
584
|
+
keep: options.keep != null ? Number(options.keep) : undefined,
|
|
585
|
+
dryRun: options.dryRun === true,
|
|
586
|
+
});
|
|
587
|
+
if (options.json) {
|
|
588
|
+
console.log(JSON.stringify(result, null, 2));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const verb = result.dryRun ? "Would delete" : "Deleted";
|
|
592
|
+
logger.info(
|
|
593
|
+
`${verb} ${result.deleted.length} of ${result.scanned} session(s).`,
|
|
594
|
+
);
|
|
595
|
+
for (const id of result.deleted.slice(0, 50)) {
|
|
596
|
+
logger.info(` - ${id}`);
|
|
597
|
+
}
|
|
598
|
+
if (result.deleted.length > 50) {
|
|
599
|
+
logger.info(` … and ${result.deleted.length - 50} more`);
|
|
600
|
+
}
|
|
601
|
+
} catch (err) {
|
|
602
|
+
logger.error(`Failed: ${err.message}`);
|
|
603
|
+
process.exitCode = 1;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
537
607
|
session
|
|
538
608
|
.command("migrate")
|
|
539
609
|
.description("Migrate legacy JSON session files to JSONL")
|
package/src/data/changelog.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"releases": [
|
|
4
4
|
{
|
|
5
5
|
"productVersion": "Unreleased",
|
|
6
|
-
"cliVersion": "0.162.
|
|
6
|
+
"cliVersion": "0.162.160",
|
|
7
7
|
"cliVersions": [
|
|
8
8
|
"0.4.46",
|
|
9
9
|
"0.4.51",
|
|
@@ -17,11 +17,16 @@
|
|
|
17
17
|
"0.162.156",
|
|
18
18
|
"0.162.157",
|
|
19
19
|
"0.162.158",
|
|
20
|
-
"0.162.159"
|
|
20
|
+
"0.162.159",
|
|
21
|
+
"0.162.160"
|
|
21
22
|
],
|
|
22
23
|
"date": null,
|
|
23
24
|
"title": "",
|
|
24
25
|
"sections": [
|
|
26
|
+
{
|
|
27
|
+
"heading": "Added — cc CLI 0.162.160:运行时安全与确定性 8 批(沙箱严格模式 + 依赖/凭据安全 + 确定性 Headless + Subagent 契约 + MCP 生命周期 + Hooks 硬化)(CLI-only npm 发版)",
|
|
28
|
+
"body": "> `chainlesschain` 0.162.159 → **0.162.160** 发 npm `latest`(经 `npm-publish.yml`,`--provenance --access public`)。纯 `packages/cli/src` 增量;未触 `pdh/lib` → 无 Android cc bundle rollover / 无 USR_VERSION 改动。命令面**只加子命令**(`cc session rename/prune`、`cc daemon rm`、`cc mcp trust-project`),**顶层命令数 175 不变**(manifest 无需重生)。对照 `docs/CLAUDE_CODE_CLI_GAP_ANALYSIS.md`(docs 根)审计确认仍缺的 8 项全落地(commit 链 `c4533ad848..4c2e297754`)。\n>\n> ⚠️ **行为变更(升级前须知)**:① `run_code` 的 pip 依赖**默认不再自动安装**(改 opt-in,见下);② agent 脚本默认写 OS 临时目录、不再落项目根(`persist:true` 才进项目);③ headless/agent **退出码细化**——最大轮次→3、预算超限→4、模型错误→5、配置错误→6(成功仍 0、通用失败仍 1、hook block 仍 2、SIGINT/SIGTERM 仍 130/143)。依赖固定 exit≠0 判失败的脚本不受影响;依赖\"失败必为 1\"的脚本需放宽。\n\n- **确定性 Headless(A)**:新增 `--ephemeral`(resume 只读回放、零落盘);init 事件补 `protocol_version`/`session_persistence`/`loaded_sources`/`policy_digest`/`tools_hash`/`isolation_level`(消费者可判定回放确定性与工具/策略指纹);退出码分类(`lib/exit-codes.cjs`);`cc agent --capabilities` 打印机读能力清单(协议版本、agent 工具集、权限模式、退出码、特性开关)后退出。\n- **依赖安装安全(B)**:`run_code` 的 pip 自动安装**默认关闭**——经 settings `runCode.autoInstall` 或 `CC_RUN_CODE_AUTO_INSTALL` 显式开启,且受 `runCode.installAllowlist`(PEP 503 归一化包名)约束,每次安装写审计 JSONL(`~/.chainlesschain/audit/`,`CC_AUDIT_DIR` 可覆盖);agent 脚本默认写 OS 临时目录(`persist:true` 才进项目根)。系统提示词/工具契约文案已同步。\n- **凭据卫生(C)**:config `llm.apiKeyHelper`——从外部命令(→ OS 凭据库)解析 API Key,5 分钟缓存,helper 失败 **fail-closed 并告警一次**(返回 null,绝不静默替换成别的凭据);argv `--api-key` 明文传参现给出告警(进程表/shell 历史泄露面)。\n- **沙箱严格模式(D)**:`failIfUnavailable` 此前只是声明、从不执行——现在启动期探测沙箱引擎(docker `version` / bwrap `--version`),不可用即拒绝启动(退出码 6),headless 与交互双路均生效;`isolation_level`(os-sandbox / container / policy-only)透出到 init 事件与 `sandboxSummary`,不再静默降级为\"只有工具规则\"。\n- **Subagent 契约(E)**:`spawn_sub_agent` 与 `.claude/agents/*.md` frontmatter 双入口新增 `disallowedTools`(从 allowedTools 减去)、`maxTurns`、`isolation:worktree`。**顺手修出一个真 bug**:`SubAgentContext._runCore` 早已算出工具白名单却从未传给 agentLoop——子代理在 LLM 面一直看到全量工具,白名单形同虚设(修=`enabledToolNames` 透传)。\n- **会话运营(F)**:`cc session rename <id> <title>`(append `session_rename` 事件,哈希链不破)+ `cc session prune --older-than <days> [--keep N] [--dry-run]`(按最后活动保留期清理,默认保最新 10)+ `cc daemon rm <id> [--force] [--keep-log]`(终态直接移除、运行中需 `--force`)。\n- **MCP 生命周期(G)**:mcp-client 处理 `notifications/tools/list_changed` + `notifications/resources/list_changed`——合并式重拉服务器工具/资源清单(刻意**不动 in-flight LLM 工具数组**,保 tool-search 缓存稳定性,追加式更新);项目 `.mcp.json` 首用记 sha256 指纹,文件变更后 **fail-closed 拒绝加载**,需 `CC_PROJECT_MCP_TRUST=1` 或 `cc mcp trust-project` 显式重信任(防被换过的项目配置静默拉起新 MCP server)。\n- **Hooks 硬化(H)**:每个 hook 的 stdin payload 打 `schema_version`(=1,additive 变更保号、破坏性 reshape 才 bump);连续失败熔断——同一 hook 命令连续 3 次非阻塞失败(spawn error / 超时 / 非 2 非零退出)开闸,60s 冷却后半开试跑一次,成功或真 block/ask 决策关闸(`CC_HOOK_BREAKER_THRESHOLD=0` 关闭、`CC_HOOK_BREAKER_COOLDOWN_MS` 可调);坏 hook 不再每轮拖慢 agent。"
|
|
29
|
+
},
|
|
25
30
|
{
|
|
26
31
|
"heading": "Added — cc CLI 0.162.159:Claude-Code 平价 gap-analysis 14 项收口(cloud handoff + /tui + /voice + /fast + plugin 治理 + session SQLite 索引)(CLI-only npm 发版)",
|
|
27
32
|
"body": "> `chainlesschain` 0.162.158 → **0.162.159** 发 npm `latest`(经 `npm-publish.yml`,`--provenance --access public`)。纯 `packages/cli/src` 增量;未触 `pdh/lib` → 无 Android cc bundle rollover / 无 USR_VERSION 改动。顶层命令数 **173 → 175**(新增 `cc cloud` + `cc routine`;`cc session index` 是 `session` 子命令、`/tui` `/voice` `/fast` 为 REPL-only 均不计数)。对照 `docs/internal/cli-claude-code-gap-analysis-2026-07-11.md` 14 项全落地。\n\n- **`cc cloud`(P1#7)自托管 cloud handoff**:`cc cloud run/status/attach/list`——`git bundle`(base64)当前分支上传到配置的私有 runner(`cloud.baseUrl`/`cloud.token` 或 `CC_CLOUD_URL`/`CC_CLOUD_TOKEN`),runner 完成后 `cc cloud attach` 轮询到终态并 `git apply --3way` 回流 patch、落 plan/artifacts、显示 PR——无 Anthropic 云依赖。本地 job 台账 `~/.chainlesschain/cloud-jobs/`(穿越守卫),不落地的 patch 存旁不失败。\n- **`/tui`(P1#10)全屏无闪烁视图**:`/tui fullscreen` 进备用屏缓冲(`?1049h`,保滚动历史)+ 尾锚定视口窗口化 + diff 重绘(仅改动行光标寻址重写,无整屏清屏→无闪烁);`/tui default` 退回流式滚动;`CC_NO_FLICKER=1` 启动即全屏;屏幕阅读器模式强制 default。持久化 `cli.tuiMode`。\n- **`/voice`(P2#11)语音听写**:`/voice hold|tap|off`(hold=按住说话,tap=切换);严格 local-first STT 优先级(本地 Whisper > 系统 STT > 云,云需 `voice.allowCloud` 显式开);SSH/无 TTY/CI/`CC_NO_VOICE` 环境清晰降级不假装监听;真实采集是主机绑定 DI seam。\n- **`/fast`(P2#12)延迟档**:`/fast on|off|status`——下一轮最小化推理(thinking off)+ 除非用户 `/model` 钉过模型否则换到 provider 的低延迟模型(anthropic→haiku-4-5、openai→4o-mini、deepseek/dashscope/mistral/gemini),始终显示成本/质量权衡。持久化 `cli.fastMode`。\n- **插件治理(P2#13)三项**:manifest `dependencies`(插件名→semver 区间 + 保留 `host`/`cc` 约束 CLI 版本)经 `cc plugin validate` 校验缺失/越界依赖;LSP 注册表改**每 languageId 有序候选表**——一个插件 LSP server 二进制缺失不再遮蔽 builtin(回退到下一候选/内建,扩展名不再\"变黑\");`isPluginInUse` 把 LSP/MCP/hooks/monitors 被动组件视为 in-use,不再被\"未调用即弃用\"误判。\n- **`cc session index`(P2#14)外部 session 存储**:JSONL 仍是权威源,新增 SQLite 索引(`~/.chainlesschain/session-index.db`)——增量按 mtime+size 同步 + 内容/标题搜索(LIKE 转义通配符)+ stats + `--rebuild`,取代逐文件解析(1072 真 session 验过);可插拔 off-box mirror(`fs`/`http` 驱动,S3/WebDAV/Redis 同接口待配),`session.mirror.{kind,dir|baseUrl,token}` 配置,加密/访问控制由驱动独立负责。\n- **P0/P1 早前提交同随本版发出**:`cc bg view` 交互式 agent 面板(P0#1)+ background supervisor 稳定性加固(P0#2)+ `/doctor` 可修复 checkup + `/checkup` 别名(P0#3)+ transcript hash-chain 防篡改 `cc session verify`(P0#4)+ Inbound Channels(webhook/telegram allowlist,P0#5)+ computer-use MCP 显式开启(P1#6)+ Routines(cron/once/webhook/github,P1#8)+ PR/session linking(P1#9)。"
|
|
@@ -271,10 +271,17 @@ export function listJsonlSessions(options = {}) {
|
|
|
271
271
|
const messageCount = events.filter(
|
|
272
272
|
(e) => e.type === "user_message" || e.type === "assistant_message",
|
|
273
273
|
).length;
|
|
274
|
+
// cc session rename appends session_rename events; the LAST one wins.
|
|
275
|
+
let renamedTitle = null;
|
|
276
|
+
for (const e of events) {
|
|
277
|
+
if (e.type === "session_rename" && e.data?.title) {
|
|
278
|
+
renamedTitle = e.data.title;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
274
281
|
|
|
275
282
|
return {
|
|
276
283
|
id,
|
|
277
|
-
title: startEvent?.data?.title || "Untitled",
|
|
284
|
+
title: renamedTitle || startEvent?.data?.title || "Untitled",
|
|
278
285
|
provider: startEvent?.data?.provider || "",
|
|
279
286
|
model: startEvent?.data?.model || "",
|
|
280
287
|
message_count: messageCount,
|
|
@@ -296,6 +303,68 @@ export function listJsonlSessions(options = {}) {
|
|
|
296
303
|
return files;
|
|
297
304
|
}
|
|
298
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Rename a session (gap-analysis 2026-07-11 P1 "命名会话"): appends a
|
|
308
|
+
* `session_rename` event so the hash chain stays intact (no rewrite). The
|
|
309
|
+
* LAST rename wins when listing/showing.
|
|
310
|
+
*/
|
|
311
|
+
export function renameSession(sessionId, title) {
|
|
312
|
+
if (!sessionExists(sessionId)) {
|
|
313
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
314
|
+
}
|
|
315
|
+
const normalized = String(title || "").trim();
|
|
316
|
+
if (!normalized) throw new Error("A non-empty title is required");
|
|
317
|
+
appendEvent(sessionId, "session_rename", { title: normalized.slice(0, 200) });
|
|
318
|
+
return { id: sessionId, title: normalized.slice(0, 200) };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Prune old sessions (gap-analysis 2026-07-11 P1 "保留期限"): delete session
|
|
323
|
+
* transcripts whose LAST activity is older than `olderThanDays`, always
|
|
324
|
+
* keeping the newest `keep` (default 10) regardless of age. Dry-run returns
|
|
325
|
+
* the same shape without deleting.
|
|
326
|
+
*/
|
|
327
|
+
export function pruneJsonlSessions(options = {}) {
|
|
328
|
+
const olderThanDays = Number(options.olderThanDays);
|
|
329
|
+
if (!Number.isFinite(olderThanDays) || olderThanDays < 0) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
"prune requires --older-than <days> (a non-negative number)",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const keep = Number.isFinite(Number(options.keep))
|
|
335
|
+
? Math.max(0, Math.floor(Number(options.keep)))
|
|
336
|
+
: 10;
|
|
337
|
+
const now = typeof options.now === "number" ? options.now : Date.now();
|
|
338
|
+
const cutoff = now - olderThanDays * 24 * 60 * 60 * 1000;
|
|
339
|
+
// listJsonlSessions sorts by last activity DESC — the first `keep` entries
|
|
340
|
+
// survive unconditionally.
|
|
341
|
+
const all = listJsonlSessions({ limit: 100000 });
|
|
342
|
+
const candidates = all.slice(keep).filter((s) => {
|
|
343
|
+
const last = Date.parse(s.updated_at || s.created_at || "");
|
|
344
|
+
return Number.isFinite(last) && last < cutoff;
|
|
345
|
+
});
|
|
346
|
+
const deleted = [];
|
|
347
|
+
for (const s of candidates) {
|
|
348
|
+
if (options.dryRun === true) {
|
|
349
|
+
deleted.push(s.id);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
rmSync(sessionPath(s.id), { force: true });
|
|
354
|
+
_chainTailCache.delete(s.id);
|
|
355
|
+
deleted.push(s.id);
|
|
356
|
+
} catch {
|
|
357
|
+
/* per-file failures never abort the sweep */
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
scanned: all.length,
|
|
362
|
+
kept: all.length - deleted.length,
|
|
363
|
+
deleted,
|
|
364
|
+
dryRun: options.dryRun === true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
299
368
|
export function forkSession(sourceId) {
|
|
300
369
|
const events = readEvents(sourceId);
|
|
301
370
|
if (events.length === 0) return null;
|
|
@@ -1051,6 +1051,17 @@ export class MCPClient extends EventEmitter {
|
|
|
1051
1051
|
|
|
1052
1052
|
// Server notification
|
|
1053
1053
|
if (msg.method) {
|
|
1054
|
+
// tools/resources list_changed (gap 2026-07-11 MCP 生命周期): refetch
|
|
1055
|
+
// the changed list so entry.tools/entry.resources stay live —
|
|
1056
|
+
// `listTools()`, `/mcp` status and callTool routing all see the update.
|
|
1057
|
+
// (The LLM tool array of an in-flight turn is deliberately NOT mutated:
|
|
1058
|
+
// tool-search's prompt-cache stability depends on an append-only,
|
|
1059
|
+
// stable-prefix tool list.)
|
|
1060
|
+
if (msg.method === "notifications/tools/list_changed") {
|
|
1061
|
+
this._refreshServerList(serverName, "tools");
|
|
1062
|
+
} else if (msg.method === "notifications/resources/list_changed") {
|
|
1063
|
+
this._refreshServerList(serverName, "resources");
|
|
1064
|
+
}
|
|
1054
1065
|
this.emit("notification", {
|
|
1055
1066
|
server: serverName,
|
|
1056
1067
|
method: msg.method,
|
|
@@ -1059,6 +1070,45 @@ export class MCPClient extends EventEmitter {
|
|
|
1059
1070
|
}
|
|
1060
1071
|
}
|
|
1061
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* Re-fetch a server's tools / resources list after a `*_list_changed`
|
|
1075
|
+
* notification. Coalesced per server+kind: a burst of notifications folds
|
|
1076
|
+
* into the in-flight refetch plus at most one trailing pass. Best-effort —
|
|
1077
|
+
* a failed refetch keeps the previous list and waits for the next
|
|
1078
|
+
* notification. Emits "tools-changed" / "resources-changed" on update.
|
|
1079
|
+
*/
|
|
1080
|
+
async _refreshServerList(serverName, kind) {
|
|
1081
|
+
const entry = this.servers.get(serverName);
|
|
1082
|
+
if (!entry) return;
|
|
1083
|
+
const flags = (entry._listRefresh = entry._listRefresh || {});
|
|
1084
|
+
if (flags[`${kind}Running`]) {
|
|
1085
|
+
flags[`${kind}Dirty`] = true;
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
flags[`${kind}Running`] = true;
|
|
1089
|
+
try {
|
|
1090
|
+
do {
|
|
1091
|
+
flags[`${kind}Dirty`] = false;
|
|
1092
|
+
try {
|
|
1093
|
+
const method = kind === "tools" ? "tools/list" : "resources/list";
|
|
1094
|
+
const result = await this._sendRequest(serverName, method, {});
|
|
1095
|
+
const list =
|
|
1096
|
+
(kind === "tools" ? result?.tools : result?.resources) || [];
|
|
1097
|
+
entry[kind] = list;
|
|
1098
|
+
if (kind === "tools") entry.toolsError = null;
|
|
1099
|
+
this.emit(`${kind}-changed`, {
|
|
1100
|
+
server: serverName,
|
|
1101
|
+
count: list.length,
|
|
1102
|
+
});
|
|
1103
|
+
} catch {
|
|
1104
|
+
break; // keep the previous list; the next notification retries
|
|
1105
|
+
}
|
|
1106
|
+
} while (flags[`${kind}Dirty`]);
|
|
1107
|
+
} finally {
|
|
1108
|
+
flags[`${kind}Running`] = false;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1062
1112
|
/**
|
|
1063
1113
|
* Answer a server-initiated JSON-RPC request. `roots/list` returns the
|
|
1064
1114
|
* session's workspace roots (we advertise the roots capability); `ping`
|
package/src/lib/agent-sandbox.js
CHANGED
|
@@ -215,11 +215,79 @@ function executeBubblewrapShell(command, sandbox, options, hostCwd, policy) {
|
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* The run's TRUE isolation level (gap-analysis 2026-07-11 P0 "OS 级沙箱"):
|
|
220
|
+
* - "os-sandbox" : bubblewrap — kernel namespaces confine the child
|
|
221
|
+
* - "container" : docker — container boundary confines the child
|
|
222
|
+
* - "policy-only" : no sandbox — permission rules/shell policy are advisory
|
|
223
|
+
* for already-spawned subprocesses
|
|
224
|
+
* Surfaced in the headless init event and sandboxSummary so a caller can see
|
|
225
|
+
* what actually confines tool subprocesses instead of assuming.
|
|
226
|
+
*/
|
|
227
|
+
export function isolationLevel(sandbox) {
|
|
228
|
+
if (!sandbox) return "policy-only";
|
|
229
|
+
if (sandbox.engine === "bubblewrap") return "os-sandbox";
|
|
230
|
+
if (sandbox.engine === "docker") return "container";
|
|
231
|
+
return "policy-only";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Probe whether the configured sandbox ENGINE is actually runnable on this
|
|
236
|
+
* host (binary present + responds to a version query). Cheap: one spawnSync
|
|
237
|
+
* with a short timeout; no container is started.
|
|
238
|
+
* @returns {{available:boolean, reason:string|null}}
|
|
239
|
+
*/
|
|
240
|
+
export function probeSandboxAvailability(sandbox, deps = _deps) {
|
|
241
|
+
if (!sandbox) return { available: true, reason: null };
|
|
242
|
+
const probeArgs =
|
|
243
|
+
sandbox.engine === "bubblewrap"
|
|
244
|
+
? ["bwrap", ["--version"]]
|
|
245
|
+
: ["docker", ["version", "--format", "{{.Server.Version}}"]];
|
|
246
|
+
const result = deps.spawnSync(probeArgs[0], probeArgs[1], {
|
|
247
|
+
encoding: "utf8",
|
|
248
|
+
timeout: 10000,
|
|
249
|
+
windowsHide: true,
|
|
250
|
+
});
|
|
251
|
+
if (result.error) {
|
|
252
|
+
return {
|
|
253
|
+
available: false,
|
|
254
|
+
reason:
|
|
255
|
+
result.error.code === "ENOENT"
|
|
256
|
+
? `${probeArgs[0]} is not installed`
|
|
257
|
+
: result.error.message,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
261
|
+
return {
|
|
262
|
+
available: false,
|
|
263
|
+
reason: `${probeArgs[0]} probe exited ${result.status}: ${(result.stderr || "").trim().slice(0, 200)}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return { available: true, reason: null };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Strict mode (`sandbox.failIfUnavailable: true` in settings): refuse to
|
|
271
|
+
* START the agent when the configured sandbox engine is unavailable, instead
|
|
272
|
+
* of silently degrading per command. Throws with an actionable message; a
|
|
273
|
+
* no-op when the flag is unset or the sandbox is fine.
|
|
274
|
+
*/
|
|
275
|
+
export function assertSandboxAvailable(sandbox, deps = _deps) {
|
|
276
|
+
if (!sandbox || sandbox.policy?.failIfUnavailable !== true) return;
|
|
277
|
+
const probe = probeSandboxAvailability(sandbox, deps);
|
|
278
|
+
if (!probe.available) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`sandbox.failIfUnavailable: ${sandbox.engine} sandbox is unavailable (${probe.reason}) — refusing to start. Install/start ${sandbox.engine === "bubblewrap" ? "bubblewrap" : "Docker"}, or unset failIfUnavailable to allow per-command degradation.`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
218
285
|
export function sandboxSummary(sandbox) {
|
|
219
286
|
if (!sandbox) return null;
|
|
220
287
|
return {
|
|
221
288
|
engine: sandbox.engine,
|
|
222
289
|
image: sandbox.image,
|
|
290
|
+
isolationLevel: isolationLevel(sandbox),
|
|
223
291
|
network: sandbox.network ? "enabled" : "disabled",
|
|
224
292
|
workspace: "read-write",
|
|
225
293
|
policy: {
|
package/src/lib/agents.js
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
* `review/security.md` is the agent `review:security`.
|
|
9
9
|
*
|
|
10
10
|
* Frontmatter (all optional):
|
|
11
|
-
* name
|
|
12
|
-
* description
|
|
13
|
-
* tools
|
|
14
|
-
*
|
|
11
|
+
* name override the filename-derived name
|
|
12
|
+
* description one-line summary (when to use this agent)
|
|
13
|
+
* tools allow-list — comma string or YAML array; omit = inherit all
|
|
14
|
+
* disallowedTools deny-list — removed from whatever the allow-list resolves to
|
|
15
|
+
* model model override for runs of this agent
|
|
16
|
+
* maxTurns per-run iteration cap for this agent (positive integer)
|
|
17
|
+
* isolation "worktree" → run in a fresh git worktree (gap 2026-07-11)
|
|
15
18
|
*
|
|
16
19
|
* Project scope shadows personal on a name clash. Discovery + parse are pure
|
|
17
20
|
* (inject fs/path/home) so the whole thing is unit-testable.
|
|
@@ -132,13 +135,18 @@ export function parseAgentFile(file, scope, opts = {}) {
|
|
|
132
135
|
return null;
|
|
133
136
|
}
|
|
134
137
|
const { data, body } = parseFrontmatter(content);
|
|
138
|
+
const maxTurns = Number(data.maxTurns);
|
|
135
139
|
return {
|
|
136
140
|
file,
|
|
137
141
|
scope,
|
|
138
142
|
name: data.name || null, // resolved against the path in discoverAgents
|
|
139
143
|
description: data.description || "",
|
|
140
144
|
tools: normalizeTools(data.tools),
|
|
145
|
+
disallowedTools: normalizeTools(data.disallowedTools),
|
|
141
146
|
model: data.model || null,
|
|
147
|
+
maxTurns:
|
|
148
|
+
Number.isFinite(maxTurns) && maxTurns > 0 ? Math.floor(maxTurns) : null,
|
|
149
|
+
isolation: data.isolation === "worktree" ? "worktree" : null,
|
|
142
150
|
systemPrompt: body || "",
|
|
143
151
|
};
|
|
144
152
|
}
|