chainlesschain 0.166.13 → 0.166.14
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-BN25cNOD.js → AIOps-C8SyFhOo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Da9IP-m5.js → ActionButton-Dup5gFK0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D7hkEvQm.js → Analytics-8FyOWA7V.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-CCV4Xnbc.js → AppLayout-DBzdU9dg.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CVSnVJeA.js → Artifacts-C_0Z8gNy.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CuYdw5Ag.js → Audit-Cf2a-58m.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DZCnK-Tf.js → BackgroundAgents-M7w-2zgB.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-cV9FdpTy.js → Backup-CCTHQXoL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BRnv6YSp.js → BaseInput-DjXpeFwJ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Bw5w87P4.js → Chat-cAvchsJU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-C0GjtUAw.js → ChatBubbleRenderer-DvEvxlZv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DaoTqcdt.js → Checkbox-DKs6qvOn.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B4dYcEIi.js → Codegen-DlkA7b9X.js} +1 -1
- package/src/assets/web-panel/assets/{Col-mAhuOeuD.js → Col-CkfdKhyJ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BK6KEdBk.js → Community-iNXMHzj6.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CCglmNu5.js → Compact-DQxPEd09.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Cm8S4of7.js → Compliance-CSKvMCjs.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Dhs1QLMe.js → Cowork-CkWqazpD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CLlhmXWk.js → Cron-k6W8IzhB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BnHVbaNs.js → Crosschain-C8MS1OuY.js} +1 -1
- package/src/assets/web-panel/assets/{DID-UzQP9-0O.js → DID-DJuCcgiQ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CBdlPClz.js → Dashboard-LeThKzp3.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-axIRPdq1.js → Dropdown-BvxwgpE4.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DUalW5rx.js → EmailListRenderer-n3e_a1M3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bl4W9luY.js → FamilyGuardDashboard-509W6W6v.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C1yP-uEu.js → Federation-xgZReQm0.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Xh6Ig5q8.js → FormItemContext-jZOhKcQx.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C2cz3bt2.js → GenericCardRenderer-Jo0ULzHv.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_z63ixg.js → Git-BsRcyWQP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BYqB33_3.js → Governance-JlpMYTAk.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CEULuArm.js → Inference-CUEz872-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fgMsu-0H.js → KnowledgeGraph-DgrvvEDU.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DO9Hdcec.js → Logs-Df-KY4U3.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BEPDcSwz.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B8zyLiFe.js → Marketplace-kPZDTOeI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BLAN2mWW.js → McpTools-CRRzPqNX.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CrQcFM7u.js → Memory-D6pNSkg9.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DVnyiiU-.js → MobileBridge-g-pvtwRC.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-qjCME5OD.js → MobileProjects-Bzayfrfe.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-4JE3C3It.js → Mtc-B7S3W-XL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CjBuak9I.js → MtcAudit-B8OMEWVb.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-BtPcYQcC.js → Multisig-CowRp7GH.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Cx653_Ba.js → NLProgramming-BkQNyi4c.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CkuD3YzS.js → Notes-Cm7gtkKz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BgNgU7HH.js → NotificationSettings-CbPTVh9m.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BTC8W0a-.js → OrderTableRenderer-Dswi26mJ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Dmd4Hjxz.js → Organization-BkjQ9yy7.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-C_5DA-G0.js → Overflow-DmRFXP7Y.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-uwslV4XC.js → P2P-DQE1toAu.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CuNgwVnX.js → PdhVaultBrowser-BujyoDRG.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-CJayVX9H.js → Permissions-xSjfOaiU.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Cb05H5go.js → PersonalDataHub-B9ICLqVP.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BZlGKapZ.js → Pipeline-CUNwyN9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BIzZWYzd.js → Privacy-SOoCricX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-vEYYyQXQ.js → ProjectInit-CSaFul0F.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-MZqTy5CC.js → ProjectSettings-CueiB_oe.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CRARUOl8.js → Projects-BOeXvU7y.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-80VAK8PX.js → Providers-CTqf_31h.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzRSoIwK.js → QrScannerModal-C8ZP31E9.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BpwQ2n4C.js → QuickAsk-CPgn4713.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BC03TZ79.js → Recommend-8_e0zw0i.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DE59-jaa.js → RemoteSession-iT7lbGqw.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DOHmeWyA.js → Reputation-B4q9SUOz.js} +1 -1
- package/src/assets/web-panel/assets/{Row-hc5RZl1D.js → Row-CA_nASyL.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cv5uMP7E.js → RssFeed-Cwb0E3yv.js} +3 -3
- package/src/assets/web-panel/assets/{Search-qohu7Ug0.js → Search-D9iVsOxJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-drCPFWV5.js → Security-DPLbYchG.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BAEgUeRy.js → Services-DGMVL_LF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CkJkKBlo.js → Skeleton-B_6kym0D.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-bMuKEpez.js → Skills-Ds6r0dXB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DxWQVHRC.js → Sla-BLHAbBhb.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BpS8idmV.js → SpeechSettings-CZuddd14.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BPBLlW5v.js → SyncSettings-CdBq7oSl.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C4SLEeUh.js → Tasks-DfT60Nyl.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-r-wfU39U.js → Templates-RLfsKy2V.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C7vjRmLN.js → Tenant-Cw8xCNLE.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DdzeFCV2.js → Terminal-BOUkNu4f.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CYyWN2KD.js → TimelineRenderer-CgGZWaRm.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D8A9osUg.js → Tokens-CpwjVyir.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DBvrNlyS.js → Trigger-DL21H-eb.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DBVAGc7o.js → Trust-B3ffFclW.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C1vQUCRJ.js → UkeySign-CkBeb5aI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BKs4dah4.js → VideoEditing-U2iVaE3r.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ByIuj3lD.js → Wallet-CQk2wzYd.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DYlAFCm9.js → WebAuthn-DYIuIYzT.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Dwyadd0c.js → WorkflowEditor-tg1c2mZQ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Bm2f3s08.js → chat-CtL61spq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C8ORSh7V.js → colors-DKpbeo8s.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-1iVlnsM-.js → compact-item-gPZsrswt.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CmqNyUSL.js → createContext-CfHlwzVr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DdqnMhzi.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-rUMaX3Ul.js → hasIn-MV47g_6X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCEyz58d.js → index-6G0ETFTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVtnizyC.js → index-BBw52sEl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIvM-uAX.js → index-BN-wxSQ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvedhJeM.js → index-BOyGUuBE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCOhTILd.js → index-BQp5M0s1.js} +1 -1
- package/src/assets/web-panel/assets/index-BcK8o4ke.js +1 -0
- package/src/assets/web-panel/assets/{index-DVJ1uKf4.js → index-BjlPD7GO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQL9r73Y.js → index-C4lF-f_z.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5RfRciU.js → index-C6nC_DyY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgUE51Pz.js → index-C9RF4ieu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkFvuAex.js → index-C9gtY4nG.js} +3 -3
- package/src/assets/web-panel/assets/{index-C-J5-pGH.js → index-CB1HnbNK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf1PlIHp.js → index-CRv_uMLV.js} +1 -1
- package/src/assets/web-panel/assets/{index-tqTx5wXo.js → index-C_B9LiKs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bgw4Y2rU.js → index-Cjv0kKCU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJxtPFOh.js → index-CmM9jqwe.js} +1 -1
- package/src/assets/web-panel/assets/{index-pHWA7bmM.js → index-CmPJpLoD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsve5Gkv.js → index-CzfxTSPW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B59OWAs7.js → index-D1XB5-t5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK-RY0-7.js → index-DGtEBodr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC32_0xT.js → index-DIeJmlAA.js} +1 -1
- package/src/assets/web-panel/assets/{index-OcbzdRpT.js → index-DMMvlLp9.js} +1 -1
- package/src/assets/web-panel/assets/{index-geQinnns.js → index-DU4rZpiK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D98FXb0w.js → index-D_Pl1GnW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC8Wb6cy.js → index-Dd19vRwy.js} +1 -1
- package/src/assets/web-panel/assets/{index-fPK6GFEg.js → index-Ddcpj1El.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfiHyVyl.js → index-Df9sWKpl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-ZIHi42.js → index-DfLyHCWi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDHk911Q.js → index-DnZ_zS1M.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFniLMn9.js → index-DsH-i_3d.js} +1 -1
- package/src/assets/web-panel/assets/index-DvbAjyYI.js +1 -0
- package/src/assets/web-panel/assets/{index-Dcck8QfW.js → index-ExAGPAnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwMdWKiF.js → index-Rb7CRMw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-reUHhuz8.js → index-Zvkt9xqi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgwLNxmr.js → index-bSFrAszw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3B1S-mW.js → index-fYcS7NS0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTfEmKK0.js → index-srBc9U_s.js} +1 -1
- package/src/assets/web-panel/assets/{index-B53c3diB.js → index-xUlgG0Xe.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8BrB0Ls.js → index-yxxXlqBA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CuA-cpkL.js → initDefaultProps-BeOZoSZ4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CimmE_9b.js → motion-BXE8VMKx.js} +1 -1
- package/src/assets/web-panel/assets/{move-DlGlrltz.js → move-I9sdvPGX.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CK6BJQ07.js → mtc-parser-d25zrJd0.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BKjBfZfB.js → omit-D71V_T4e.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B2chVYw9.js → pickAttrs-BNZKpIAR.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BW8aLJJ3.js → placementArrow-jOcgxTTz.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dXlPl66S.js → responsiveObserve-pta_O8AU.js} +1 -1
- package/src/assets/web-panel/assets/{slide-hSa89S4G.js → slide-Dlq6om7z.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DM6KLt-s.js → statusUtils-BnkO_yRv.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C9MQAK15.js → styleChecker-p8NfWmcG.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BpQDpP0z.js → useFlexGapSupport-KcjZZ5U7.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C7rtRViA.js → useFs-gqonDMWB.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Dm15xafq.js → usePersonalDataHub-jIIfE4go.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-mEiEytw4.js → vnode-Dyo7fBrM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DzPxflhY.js → zoom-6l8BMg9W.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +49 -27
- package/src/commands/record-replay.js +772 -0
- package/src/commands/skill.js +3 -0
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-sandbox.js +4 -8
- package/src/lib/agent-team/team-message-tools.js +23 -5
- package/src/lib/process-execution-broker/credential-transport.js +31 -0
- package/src/lib/process-execution-broker/index.js +74 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +7 -3
- package/src/lib/record-replay/browser-target-policy.js +406 -0
- package/src/lib/record-replay/index.js +4 -0
- package/src/lib/record-replay/playwright-ui-driver.js +83 -20
- package/src/lib/record-replay/playwright-ui-recorder.js +611 -0
- package/src/lib/record-replay/recorded-skill-package.js +486 -0
- package/src/lib/record-replay/recorded-skill-store.js +1166 -0
- package/src/lib/record-replay/skill-recorder.js +634 -64
- package/src/runtime/headless-runner.js +72 -51
- package/src/assets/web-panel/assets/MarkdownRenderer-BkrK04xW.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B69RCDDH.js +0 -1
- package/src/assets/web-panel/assets/index-9biMQ-tw.js +0 -1
- package/src/assets/web-panel/assets/index-COie87kl.js +0 -1
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
openSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { dirname, resolve } from "node:path";
|
|
10
|
+
import { createInterface } from "node:readline/promises";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import { logger } from "../lib/logger.js";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_RECORDED_SKILL_POLICY,
|
|
15
|
+
RecordedSkillStore,
|
|
16
|
+
assertRecordedSkillBrowserBinding,
|
|
17
|
+
installRecordedSkillPackage,
|
|
18
|
+
launchPlaywrightRecordedSkillDriver,
|
|
19
|
+
launchPlaywrightRecordedSkillRecorder,
|
|
20
|
+
replayRecordedSkill,
|
|
21
|
+
reviewRecordedSkillDraft,
|
|
22
|
+
stageRecordedSkillPackageRevocation,
|
|
23
|
+
} from "../lib/record-replay/index.js";
|
|
24
|
+
|
|
25
|
+
const MAX_INPUT_FILE_BYTES = 2 * 1024 * 1024;
|
|
26
|
+
const MAX_FIXTURE_BYTES = 4 * 1024 * 1024;
|
|
27
|
+
|
|
28
|
+
function collect(value, previous) {
|
|
29
|
+
return [...previous, value];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function commandError(code, message) {
|
|
33
|
+
const error = new Error(message);
|
|
34
|
+
error.name = "RecordReplayCommandError";
|
|
35
|
+
error.code = code;
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readRegularFile(filePath, maxBytes, label) {
|
|
40
|
+
const target = resolve(filePath);
|
|
41
|
+
const entry = lstatSync(target);
|
|
42
|
+
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > maxBytes) {
|
|
43
|
+
throw commandError(
|
|
44
|
+
"CC_RECORD_FILE_UNSAFE",
|
|
45
|
+
`${label} must be a bounded regular file and may not be a symbolic link`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return readFileSync(target, "utf8");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readJsonFile(filePath, label) {
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(readRegularFile(filePath, MAX_INPUT_FILE_BYTES, label));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error?.code) throw error;
|
|
57
|
+
throw commandError("CC_RECORD_JSON_INVALID", `${label} is not valid JSON`);
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeJsonExclusive(filePath, value) {
|
|
63
|
+
const target = resolve(filePath);
|
|
64
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
65
|
+
const handle = openSync(target, "wx", 0o600);
|
|
66
|
+
try {
|
|
67
|
+
writeFileSync(handle, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
68
|
+
} finally {
|
|
69
|
+
closeSync(handle);
|
|
70
|
+
}
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseAssignments(values = [], inputFile) {
|
|
75
|
+
const inputs = {};
|
|
76
|
+
if (inputFile) {
|
|
77
|
+
const fromFile = readJsonFile(inputFile, "input file");
|
|
78
|
+
if (!fromFile || typeof fromFile !== "object" || Array.isArray(fromFile)) {
|
|
79
|
+
throw commandError(
|
|
80
|
+
"CC_RECORD_INPUT_INVALID",
|
|
81
|
+
"input file must contain a JSON object",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
Object.assign(inputs, fromFile);
|
|
85
|
+
}
|
|
86
|
+
for (const assignment of values) {
|
|
87
|
+
const separator = assignment.indexOf("=");
|
|
88
|
+
const name = assignment.slice(0, separator);
|
|
89
|
+
if (
|
|
90
|
+
separator < 1 ||
|
|
91
|
+
!/^[A-Za-z][A-Za-z0-9_]*$/u.test(name) ||
|
|
92
|
+
Object.prototype.hasOwnProperty.call(inputs, name)
|
|
93
|
+
) {
|
|
94
|
+
throw commandError(
|
|
95
|
+
"CC_RECORD_INPUT_INVALID",
|
|
96
|
+
"each --input must be a unique name=value assignment",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
inputs[name] = assignment.slice(separator + 1);
|
|
100
|
+
}
|
|
101
|
+
return inputs;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function reviewProjection(entry) {
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
schema: "chainlesschain.recorded-skill-review-projection/v1",
|
|
107
|
+
name: entry.name,
|
|
108
|
+
revision: entry.revision,
|
|
109
|
+
state: entry.state,
|
|
110
|
+
description: entry.skill.description,
|
|
111
|
+
actions: entry.skill.actions.map((action) => ({ ...action })),
|
|
112
|
+
parameters: entry.skill.parameters.map((parameter) => ({ ...parameter })),
|
|
113
|
+
capabilityManifest: [...entry.skill.capabilityManifest],
|
|
114
|
+
environment: entry.skill.environment,
|
|
115
|
+
failureConditions: [...entry.skill.failureConditions],
|
|
116
|
+
draftDigest: entry.skill.draftDigest,
|
|
117
|
+
approvalDigest: entry.skill.approvalDigest || null,
|
|
118
|
+
lastReplayDigest: entry.lastReplay?.report?.replayDigest || null,
|
|
119
|
+
installation: entry.installation,
|
|
120
|
+
entryDigest: entry.entryDigest,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function printReview(entry) {
|
|
125
|
+
const projection = reviewProjection(entry);
|
|
126
|
+
logger.log(chalk.bold(`\nRecorded Skill: ${projection.name}`));
|
|
127
|
+
logger.log(` state: ${projection.state} revision: ${projection.revision}`);
|
|
128
|
+
logger.log(` draft: ${projection.draftDigest}`);
|
|
129
|
+
logger.log(` capabilities: ${projection.capabilityManifest.join(", ")}`);
|
|
130
|
+
logger.log(` environment: ${projection.environment.digest}`);
|
|
131
|
+
logger.log(chalk.bold("\nActions:"));
|
|
132
|
+
for (const action of projection.actions) {
|
|
133
|
+
const value =
|
|
134
|
+
"value" in action ? ` value=${JSON.stringify(action.value)}` : "";
|
|
135
|
+
logger.log(
|
|
136
|
+
` ${action.id} ${action.kind} target=${JSON.stringify(action.target)}${value}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
logger.log(chalk.bold("\nParameters:"));
|
|
140
|
+
if (projection.parameters.length === 0) logger.log(" (none)");
|
|
141
|
+
for (const parameter of projection.parameters) {
|
|
142
|
+
logger.log(
|
|
143
|
+
` ${parameter.name} required=${parameter.required} sensitive=${parameter.sensitive}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
logger.log(chalk.bold("\nFailure conditions:"));
|
|
147
|
+
if (projection.failureConditions.length === 0)
|
|
148
|
+
logger.log(" (none declared)");
|
|
149
|
+
for (const condition of projection.failureConditions)
|
|
150
|
+
logger.log(` - ${condition}`);
|
|
151
|
+
logger.log("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function output(value, options, message) {
|
|
155
|
+
if (options.json) console.log(JSON.stringify(value, null, 2));
|
|
156
|
+
else if (message) logger.success(message);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function guarded(action, options = {}) {
|
|
160
|
+
try {
|
|
161
|
+
await action();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const code = String(error?.code || "CC_RECORD_COMMAND_FAILED");
|
|
164
|
+
if (options.json) {
|
|
165
|
+
console.log(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
success: false,
|
|
168
|
+
code,
|
|
169
|
+
error: String(error?.message || "Record & Replay command failed"),
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
logger.error(
|
|
174
|
+
`[${code}] ${error?.message || "Record & Replay command failed"}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function waitForRecordingStop() {
|
|
182
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
183
|
+
throw commandError(
|
|
184
|
+
"CC_RECORD_INTERACTIVE_REQUIRED",
|
|
185
|
+
"interactive recording requires a TTY; use --automation for headless capture",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const prompt = createInterface({
|
|
189
|
+
input: process.stdin,
|
|
190
|
+
output: process.stdout,
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
await prompt.question(
|
|
194
|
+
"Interact with the Chromium window, then press Enter here to finish recording. ",
|
|
195
|
+
);
|
|
196
|
+
} finally {
|
|
197
|
+
prompt.close();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseAssertions(options) {
|
|
202
|
+
if (!options.assertions) return [];
|
|
203
|
+
const assertions = readJsonFile(options.assertions, "assertions file");
|
|
204
|
+
if (!Array.isArray(assertions)) {
|
|
205
|
+
throw commandError(
|
|
206
|
+
"CC_RECORD_ASSERTION_INVALID",
|
|
207
|
+
"assertions file must contain a JSON array",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return assertions;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseEnvironment(options) {
|
|
214
|
+
if (!options.environment) return {};
|
|
215
|
+
const environment = readJsonFile(options.environment, "environment file");
|
|
216
|
+
if (
|
|
217
|
+
!environment ||
|
|
218
|
+
typeof environment !== "object" ||
|
|
219
|
+
Array.isArray(environment)
|
|
220
|
+
) {
|
|
221
|
+
throw commandError(
|
|
222
|
+
"CC_RECORD_ENVIRONMENT_INVALID",
|
|
223
|
+
"environment file must contain a JSON object",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return environment;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function parseBrowserTarget(options, { reviewedEnvironment } = {}) {
|
|
230
|
+
const hasFixture =
|
|
231
|
+
typeof options.fixture === "string" && options.fixture.length > 0;
|
|
232
|
+
const hasUrl = typeof options.url === "string" && options.url.length > 0;
|
|
233
|
+
if (hasFixture === hasUrl) {
|
|
234
|
+
throw commandError(
|
|
235
|
+
"CC_RECORD_TARGET_INVALID",
|
|
236
|
+
"provide exactly one --fixture or --url browser target",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const expectedAdapter = reviewedEnvironment?.adapter;
|
|
240
|
+
if (
|
|
241
|
+
(expectedAdapter === "self-contained-html" && !hasFixture) ||
|
|
242
|
+
(expectedAdapter === "url-origin" && !hasUrl)
|
|
243
|
+
) {
|
|
244
|
+
throw commandError(
|
|
245
|
+
"CC_RECORD_TARGET_DRIFT",
|
|
246
|
+
"replay target type does not match the reviewed recording",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const storageState = options.storageState
|
|
250
|
+
? readJsonFile(options.storageState, "browser storage state")
|
|
251
|
+
: undefined;
|
|
252
|
+
return {
|
|
253
|
+
html: hasFixture
|
|
254
|
+
? readRegularFile(options.fixture, MAX_FIXTURE_BYTES, "fixture")
|
|
255
|
+
: undefined,
|
|
256
|
+
url: hasUrl ? options.url : undefined,
|
|
257
|
+
allowedOrigins:
|
|
258
|
+
reviewedEnvironment?.networkPolicy?.allowedOrigins ||
|
|
259
|
+
options.allowedOrigin ||
|
|
260
|
+
[],
|
|
261
|
+
identity: reviewedEnvironment?.identity || options.identity || "anonymous",
|
|
262
|
+
storageState,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function registerRecordReplayCommands(skill) {
|
|
267
|
+
const recording = skill
|
|
268
|
+
.command("recording")
|
|
269
|
+
.alias("record-replay")
|
|
270
|
+
.description(
|
|
271
|
+
"Record, review, replay, enable, and govern deterministic UI skills",
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
recording
|
|
275
|
+
.command("record <name>")
|
|
276
|
+
.description(
|
|
277
|
+
"Capture a real Chromium interaction into a parameterized draft",
|
|
278
|
+
)
|
|
279
|
+
.option("--fixture <path>", "Self-contained HTML fixture")
|
|
280
|
+
.option(
|
|
281
|
+
"--url <url>",
|
|
282
|
+
"HTTPS target URL (loopback HTTP is allowed for testing)",
|
|
283
|
+
)
|
|
284
|
+
.option(
|
|
285
|
+
"--allowed-origin <origin>",
|
|
286
|
+
"Exact additional network origin; repeat as needed",
|
|
287
|
+
collect,
|
|
288
|
+
[],
|
|
289
|
+
)
|
|
290
|
+
.option("--identity <id>", "Non-secret browser identity label", "anonymous")
|
|
291
|
+
.option(
|
|
292
|
+
"--storage-state <path>",
|
|
293
|
+
"Playwright storage-state JSON (never persisted)",
|
|
294
|
+
)
|
|
295
|
+
.option(
|
|
296
|
+
"--sensitive <parameter>",
|
|
297
|
+
"Mark a captured parameter as sensitive; repeat as needed",
|
|
298
|
+
collect,
|
|
299
|
+
[],
|
|
300
|
+
)
|
|
301
|
+
.option("--description <text>", "Recorded Skill description", "")
|
|
302
|
+
.option("--failure <condition>", "Reviewed failure condition", collect, [])
|
|
303
|
+
.option("--observe <selector>", "Append an observe action", collect, [])
|
|
304
|
+
.option("--assertions <path>", "JSON array of explicit assert actions")
|
|
305
|
+
.option("--environment <path>", "Additional environment requirements JSON")
|
|
306
|
+
.option(
|
|
307
|
+
"--automation <path>",
|
|
308
|
+
"Bounded click/type/select JSON used for headless capture",
|
|
309
|
+
)
|
|
310
|
+
.option("--headless", "Launch Chromium headless")
|
|
311
|
+
.option("--timeout-ms <ms>", "Recorder action timeout", "5000")
|
|
312
|
+
.option("--max-actions <count>", "Maximum captured actions", "256")
|
|
313
|
+
.option("--json", "Machine-readable result")
|
|
314
|
+
.action(async (name, options) =>
|
|
315
|
+
guarded(async () => {
|
|
316
|
+
const target = parseBrowserTarget(options);
|
|
317
|
+
const automation = options.automation
|
|
318
|
+
? readJsonFile(options.automation, "automation file")
|
|
319
|
+
: null;
|
|
320
|
+
const recorder = await launchPlaywrightRecordedSkillRecorder({
|
|
321
|
+
...target,
|
|
322
|
+
headless: options.headless === true || Boolean(automation),
|
|
323
|
+
timeoutMs: Number(options.timeoutMs),
|
|
324
|
+
maxActions: Number(options.maxActions),
|
|
325
|
+
});
|
|
326
|
+
try {
|
|
327
|
+
if (automation) await recorder.runAutomation(automation);
|
|
328
|
+
else await waitForRecordingStop();
|
|
329
|
+
const draft = await recorder.finish({
|
|
330
|
+
name,
|
|
331
|
+
description: options.description,
|
|
332
|
+
environment: parseEnvironment(options),
|
|
333
|
+
failureConditions: options.failure,
|
|
334
|
+
observations: options.observe,
|
|
335
|
+
assertions: parseAssertions(options),
|
|
336
|
+
sensitiveParameters: options.sensitive,
|
|
337
|
+
});
|
|
338
|
+
const store = new RecordedSkillStore();
|
|
339
|
+
const entry = store.create({
|
|
340
|
+
draft,
|
|
341
|
+
source: {
|
|
342
|
+
adapter: recorder.adapter,
|
|
343
|
+
targetDigest: recorder.targetDigest,
|
|
344
|
+
browserVersion: recorder.browserVersion,
|
|
345
|
+
},
|
|
346
|
+
actor: "cli-recorder",
|
|
347
|
+
});
|
|
348
|
+
const projection = reviewProjection(entry);
|
|
349
|
+
if (!options.json) printReview(entry);
|
|
350
|
+
output(
|
|
351
|
+
projection,
|
|
352
|
+
options,
|
|
353
|
+
`Recorded draft ${name} at revision ${entry.revision}`,
|
|
354
|
+
);
|
|
355
|
+
} finally {
|
|
356
|
+
await recorder.close();
|
|
357
|
+
}
|
|
358
|
+
}, options),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
recording
|
|
362
|
+
.command("list", { isDefault: true })
|
|
363
|
+
.description("List retained recorded Skills")
|
|
364
|
+
.option("--json", "Machine-readable result")
|
|
365
|
+
.action((options) =>
|
|
366
|
+
guarded(async () => {
|
|
367
|
+
const records = new RecordedSkillStore().list();
|
|
368
|
+
if (options.json) console.log(JSON.stringify({ records }, null, 2));
|
|
369
|
+
else if (records.length === 0) logger.info("No recorded Skills found.");
|
|
370
|
+
else {
|
|
371
|
+
logger.log(chalk.bold(`\nRecorded Skills (${records.length}):\n`));
|
|
372
|
+
for (const record of records) {
|
|
373
|
+
logger.log(
|
|
374
|
+
` ${chalk.cyan(record.name.padEnd(28))} ${record.state.padEnd(10)} rev ${record.revision} ${record.actionCount} actions`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
logger.log("");
|
|
378
|
+
}
|
|
379
|
+
}, options),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
recording
|
|
383
|
+
.command("show <name>")
|
|
384
|
+
.description("Show the exact review projection for one recorded Skill")
|
|
385
|
+
.option("--json", "Machine-readable result")
|
|
386
|
+
.action((name, options) =>
|
|
387
|
+
guarded(async () => {
|
|
388
|
+
const entry = new RecordedSkillStore().get(name);
|
|
389
|
+
if (options.json)
|
|
390
|
+
console.log(JSON.stringify(reviewProjection(entry), null, 2));
|
|
391
|
+
else printReview(entry);
|
|
392
|
+
}, options),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
recording
|
|
396
|
+
.command("review <name>")
|
|
397
|
+
.description("Review and explicitly approve the current draft revision")
|
|
398
|
+
.requiredOption("--reviewer <id>", "Stable reviewer identity")
|
|
399
|
+
.option(
|
|
400
|
+
"--approve",
|
|
401
|
+
"Approve the displayed exact revision and failure conditions",
|
|
402
|
+
)
|
|
403
|
+
.option("--json", "Machine-readable result")
|
|
404
|
+
.action((name, options) =>
|
|
405
|
+
guarded(async () => {
|
|
406
|
+
const store = new RecordedSkillStore();
|
|
407
|
+
const current = store.get(name);
|
|
408
|
+
if (!options.json) printReview(current);
|
|
409
|
+
if (options.approve !== true) {
|
|
410
|
+
throw commandError(
|
|
411
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
412
|
+
"review is read-only without --approve",
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const approved = reviewRecordedSkillDraft(current.skill, {
|
|
416
|
+
reviewerId: options.reviewer,
|
|
417
|
+
approvedCapabilities: current.skill.capabilityManifest,
|
|
418
|
+
acceptedFailureConditions: true,
|
|
419
|
+
});
|
|
420
|
+
const entry = store.approve({
|
|
421
|
+
name,
|
|
422
|
+
expectedRevision: current.revision,
|
|
423
|
+
skill: approved,
|
|
424
|
+
actor: options.reviewer,
|
|
425
|
+
});
|
|
426
|
+
output(
|
|
427
|
+
reviewProjection(entry),
|
|
428
|
+
options,
|
|
429
|
+
`Approved ${name} at revision ${entry.revision}`,
|
|
430
|
+
);
|
|
431
|
+
}, options),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
recording
|
|
435
|
+
.command("replay <name>")
|
|
436
|
+
.description("Replay an approved Skill in ephemeral policy-bound Chromium")
|
|
437
|
+
.option("--fixture <path>", "Original self-contained HTML fixture")
|
|
438
|
+
.option("--url <url>", "Original reviewed HTTPS target URL")
|
|
439
|
+
.option("--storage-state <path>", "Matching Playwright storage-state JSON")
|
|
440
|
+
.option(
|
|
441
|
+
"--input <name=value>",
|
|
442
|
+
"Runtime parameter; repeat as needed",
|
|
443
|
+
collect,
|
|
444
|
+
[],
|
|
445
|
+
)
|
|
446
|
+
.option("--input-file <path>", "JSON object of runtime parameters")
|
|
447
|
+
.option("--timeout-ms <ms>", "Per-action timeout", "5000")
|
|
448
|
+
.option("--settle-ms <ms>", "Post-action settle delay", "25")
|
|
449
|
+
.option("--json", "Machine-readable result")
|
|
450
|
+
.action((name, options) =>
|
|
451
|
+
guarded(async () => {
|
|
452
|
+
const store = new RecordedSkillStore();
|
|
453
|
+
const current = store.get(name);
|
|
454
|
+
if (
|
|
455
|
+
!["approved", "validated", "enabled", "revoked"].includes(
|
|
456
|
+
current.state,
|
|
457
|
+
)
|
|
458
|
+
) {
|
|
459
|
+
throw commandError(
|
|
460
|
+
"CC_RECORD_NOT_APPROVED",
|
|
461
|
+
"recorded Skill must be approved before replay",
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
const reviewedEnvironment = current.skill.environment.requirements;
|
|
465
|
+
const target = parseBrowserTarget(options, { reviewedEnvironment });
|
|
466
|
+
const driver = await launchPlaywrightRecordedSkillDriver({
|
|
467
|
+
...target,
|
|
468
|
+
timeoutMs: Number(options.timeoutMs),
|
|
469
|
+
settleMs: Number(options.settleMs),
|
|
470
|
+
});
|
|
471
|
+
try {
|
|
472
|
+
assertRecordedSkillBrowserBinding(driver, {
|
|
473
|
+
source: current.source,
|
|
474
|
+
environment: reviewedEnvironment,
|
|
475
|
+
});
|
|
476
|
+
const startedAt = Date.now();
|
|
477
|
+
const report = await replayRecordedSkill(current.skill, {
|
|
478
|
+
inputs: parseAssignments(options.input, options.inputFile),
|
|
479
|
+
environment: reviewedEnvironment,
|
|
480
|
+
isolation: {
|
|
481
|
+
sandboxed: true,
|
|
482
|
+
network: driver.networkPolicy.mode,
|
|
483
|
+
allowedOrigins: driver.networkPolicy.allowedOrigins,
|
|
484
|
+
},
|
|
485
|
+
executor: driver.executor,
|
|
486
|
+
});
|
|
487
|
+
const entry = store.recordReplay({
|
|
488
|
+
name,
|
|
489
|
+
expectedRevision: current.revision,
|
|
490
|
+
report,
|
|
491
|
+
targetDigest: driver.targetDigest,
|
|
492
|
+
browserVersion: driver.browserVersion,
|
|
493
|
+
durationMs: Date.now() - startedAt,
|
|
494
|
+
actor: "cli-replay",
|
|
495
|
+
});
|
|
496
|
+
output(
|
|
497
|
+
{
|
|
498
|
+
success: true,
|
|
499
|
+
name,
|
|
500
|
+
state: entry.state,
|
|
501
|
+
revision: entry.revision,
|
|
502
|
+
replayDigest: report.replayDigest,
|
|
503
|
+
actionCount: report.receipts.length,
|
|
504
|
+
driver: driver.summary(),
|
|
505
|
+
},
|
|
506
|
+
options,
|
|
507
|
+
`Replay succeeded for ${name} (${report.receipts.length} actions)`,
|
|
508
|
+
);
|
|
509
|
+
} finally {
|
|
510
|
+
await driver.close();
|
|
511
|
+
}
|
|
512
|
+
}, options),
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
recording
|
|
516
|
+
.command("enable <name>")
|
|
517
|
+
.description(
|
|
518
|
+
"Install a replay-validated workflow into the existing Skill loader",
|
|
519
|
+
)
|
|
520
|
+
.option("--global", "Install in the global managed Skill layer")
|
|
521
|
+
.option("--approve", "Confirm installation of the exact validated revision")
|
|
522
|
+
.option("--json", "Machine-readable result")
|
|
523
|
+
.action((name, options) =>
|
|
524
|
+
guarded(async () => {
|
|
525
|
+
if (options.approve !== true) {
|
|
526
|
+
throw commandError(
|
|
527
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
528
|
+
"enablement requires --approve",
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
const store = new RecordedSkillStore();
|
|
532
|
+
const current = store.get(name);
|
|
533
|
+
const scope = options.global ? "global" : "project";
|
|
534
|
+
const installed = installRecordedSkillPackage(current, { scope });
|
|
535
|
+
let entry;
|
|
536
|
+
try {
|
|
537
|
+
entry = store.enable({
|
|
538
|
+
name,
|
|
539
|
+
expectedRevision: current.revision,
|
|
540
|
+
scope,
|
|
541
|
+
packageDigest: installed.packageDigest,
|
|
542
|
+
actor: "cli-enable",
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (installed.created) {
|
|
546
|
+
const rollback = stageRecordedSkillPackageRevocation({
|
|
547
|
+
name,
|
|
548
|
+
scope,
|
|
549
|
+
expectedPackageDigest: installed.packageDigest,
|
|
550
|
+
});
|
|
551
|
+
rollback.commit();
|
|
552
|
+
}
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
output(
|
|
556
|
+
{
|
|
557
|
+
success: true,
|
|
558
|
+
name,
|
|
559
|
+
state: entry.state,
|
|
560
|
+
revision: entry.revision,
|
|
561
|
+
scope,
|
|
562
|
+
packageDigest: installed.packageDigest,
|
|
563
|
+
},
|
|
564
|
+
options,
|
|
565
|
+
`Enabled recorded Skill ${name} in the ${scope} layer`,
|
|
566
|
+
);
|
|
567
|
+
}, options),
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
recording
|
|
571
|
+
.command("revoke <name>")
|
|
572
|
+
.description("Atomically revoke an enabled generated Skill package")
|
|
573
|
+
.option("--approve", "Confirm revocation of the exact installed package")
|
|
574
|
+
.option("--json", "Machine-readable result")
|
|
575
|
+
.action((name, options) =>
|
|
576
|
+
guarded(async () => {
|
|
577
|
+
if (options.approve !== true) {
|
|
578
|
+
throw commandError(
|
|
579
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
580
|
+
"revocation requires --approve",
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const store = new RecordedSkillStore();
|
|
584
|
+
const current = store.get(name);
|
|
585
|
+
if (current.state !== "enabled" || !current.installation) {
|
|
586
|
+
throw commandError(
|
|
587
|
+
"CC_RECORD_NOT_ENABLED",
|
|
588
|
+
"recorded Skill is not enabled",
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
const staged = stageRecordedSkillPackageRevocation({
|
|
592
|
+
name,
|
|
593
|
+
scope: current.installation.scope,
|
|
594
|
+
expectedPackageDigest: current.installation.packageDigest,
|
|
595
|
+
});
|
|
596
|
+
let entry;
|
|
597
|
+
try {
|
|
598
|
+
entry = store.revoke({
|
|
599
|
+
name,
|
|
600
|
+
expectedRevision: current.revision,
|
|
601
|
+
actor: "cli-revoke",
|
|
602
|
+
});
|
|
603
|
+
staged.commit();
|
|
604
|
+
} catch (error) {
|
|
605
|
+
staged.rollback();
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
output(
|
|
609
|
+
{ success: true, name, state: entry.state, revision: entry.revision },
|
|
610
|
+
options,
|
|
611
|
+
`Revoked recorded Skill ${name}`,
|
|
612
|
+
);
|
|
613
|
+
}, options),
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
recording
|
|
617
|
+
.command("export <name>")
|
|
618
|
+
.description("Export a digest-bound portable record without runtime inputs")
|
|
619
|
+
.requiredOption("--output <path>", "New export JSON path")
|
|
620
|
+
.option("--json", "Machine-readable result")
|
|
621
|
+
.action((name, options) =>
|
|
622
|
+
guarded(async () => {
|
|
623
|
+
const exported = new RecordedSkillStore().export(name, {
|
|
624
|
+
actor: "cli-export",
|
|
625
|
+
});
|
|
626
|
+
const target = writeJsonExclusive(options.output, exported);
|
|
627
|
+
output(
|
|
628
|
+
{
|
|
629
|
+
success: true,
|
|
630
|
+
name,
|
|
631
|
+
output: target,
|
|
632
|
+
exportDigest: exported.exportDigest,
|
|
633
|
+
},
|
|
634
|
+
options,
|
|
635
|
+
`Exported ${name} to ${target}`,
|
|
636
|
+
);
|
|
637
|
+
}, options),
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
recording
|
|
641
|
+
.command("import <path>")
|
|
642
|
+
.description("Import and fully revalidate a portable recorded Skill")
|
|
643
|
+
.option("--json", "Machine-readable result")
|
|
644
|
+
.action((filePath, options) =>
|
|
645
|
+
guarded(async () => {
|
|
646
|
+
const entry = new RecordedSkillStore().import(
|
|
647
|
+
readJsonFile(filePath, "recorded Skill export"),
|
|
648
|
+
{ actor: "cli-import" },
|
|
649
|
+
);
|
|
650
|
+
output(
|
|
651
|
+
reviewProjection(entry),
|
|
652
|
+
options,
|
|
653
|
+
`Imported recorded Skill ${entry.name} at revision ${entry.revision}`,
|
|
654
|
+
);
|
|
655
|
+
}, options),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
recording
|
|
659
|
+
.command("delete <name>")
|
|
660
|
+
.description("Delete a retained, non-enabled recorded Skill")
|
|
661
|
+
.option("--approve", "Confirm deletion of the exact current revision")
|
|
662
|
+
.option("--json", "Machine-readable result")
|
|
663
|
+
.action((name, options) =>
|
|
664
|
+
guarded(async () => {
|
|
665
|
+
if (options.approve !== true) {
|
|
666
|
+
throw commandError(
|
|
667
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
668
|
+
"deletion requires --approve",
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
const store = new RecordedSkillStore();
|
|
672
|
+
const current = store.get(name);
|
|
673
|
+
const removed = store.delete({
|
|
674
|
+
name,
|
|
675
|
+
expectedRevision: current.revision,
|
|
676
|
+
actor: "cli-delete",
|
|
677
|
+
});
|
|
678
|
+
output(
|
|
679
|
+
{ success: true, removed },
|
|
680
|
+
options,
|
|
681
|
+
`Deleted retained recorded Skill ${name}`,
|
|
682
|
+
);
|
|
683
|
+
}, options),
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
recording
|
|
687
|
+
.command("audit")
|
|
688
|
+
.description(
|
|
689
|
+
"Verify and display the content-free Record & Replay audit chain",
|
|
690
|
+
)
|
|
691
|
+
.option("--name <name>", "Filter one recorded Skill")
|
|
692
|
+
.option("--json", "Machine-readable result")
|
|
693
|
+
.action((options) =>
|
|
694
|
+
guarded(async () => {
|
|
695
|
+
const events = new RecordedSkillStore().audit({ name: options.name });
|
|
696
|
+
if (options.json) console.log(JSON.stringify({ events }, null, 2));
|
|
697
|
+
else {
|
|
698
|
+
logger.log(
|
|
699
|
+
chalk.bold(`\nRecord & Replay audit events (${events.length}):\n`),
|
|
700
|
+
);
|
|
701
|
+
for (const event of events) {
|
|
702
|
+
logger.log(
|
|
703
|
+
` #${event.sequence} ${event.action.padEnd(18)} ${event.name.padEnd(24)} rev ${event.revision} ${event.state}`,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
logger.log("");
|
|
707
|
+
}
|
|
708
|
+
}, options),
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
recording
|
|
712
|
+
.command("prune")
|
|
713
|
+
.description(
|
|
714
|
+
"Delete expired non-enabled records under the retention policy",
|
|
715
|
+
)
|
|
716
|
+
.option("--approve", "Confirm retention deletion")
|
|
717
|
+
.option("--json", "Machine-readable result")
|
|
718
|
+
.action((options) =>
|
|
719
|
+
guarded(async () => {
|
|
720
|
+
if (options.approve !== true) {
|
|
721
|
+
throw commandError(
|
|
722
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
723
|
+
"retention pruning requires --approve",
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const removed = new RecordedSkillStore().pruneExpired({
|
|
727
|
+
actor: "cli-prune",
|
|
728
|
+
});
|
|
729
|
+
output(
|
|
730
|
+
{ success: true, removed },
|
|
731
|
+
options,
|
|
732
|
+
`Pruned ${removed.length} expired recorded Skill record(s)`,
|
|
733
|
+
);
|
|
734
|
+
}, options),
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
recording
|
|
738
|
+
.command("policy")
|
|
739
|
+
.description("Show or replace owner policy for retention and capabilities")
|
|
740
|
+
.option("--set <path>", "Validated policy JSON to apply")
|
|
741
|
+
.option("--approve", "Confirm replacement of the current governance policy")
|
|
742
|
+
.option("--json", "Machine-readable result")
|
|
743
|
+
.action((options) =>
|
|
744
|
+
guarded(async () => {
|
|
745
|
+
const store = new RecordedSkillStore();
|
|
746
|
+
if (options.set && options.approve !== true) {
|
|
747
|
+
throw commandError(
|
|
748
|
+
"CC_RECORD_EXPLICIT_APPROVAL_REQUIRED",
|
|
749
|
+
"policy replacement requires --approve",
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const policy = options.set
|
|
753
|
+
? store.setPolicy(readJsonFile(options.set, "policy file"), {
|
|
754
|
+
actor: "cli-policy",
|
|
755
|
+
})
|
|
756
|
+
: store.policy();
|
|
757
|
+
if (options.json) console.log(JSON.stringify(policy, null, 2));
|
|
758
|
+
else {
|
|
759
|
+
logger.log(chalk.bold("\nRecord & Replay policy:\n"));
|
|
760
|
+
logger.log(JSON.stringify(policy, null, 2));
|
|
761
|
+
logger.log("");
|
|
762
|
+
}
|
|
763
|
+
}, options),
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
recording
|
|
767
|
+
.command("policy-template")
|
|
768
|
+
.description("Print the default owner policy JSON")
|
|
769
|
+
.action(() =>
|
|
770
|
+
console.log(JSON.stringify(DEFAULT_RECORDED_SKILL_POLICY, null, 2)),
|
|
771
|
+
);
|
|
772
|
+
}
|