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,611 @@
|
|
|
1
|
+
import { createRecordedSkillDraft } from "./skill-recorder.js";
|
|
2
|
+
import { executionBroker } from "../process-execution-broker/index.js";
|
|
3
|
+
import {
|
|
4
|
+
navigationAllowedByRecordedSkillPolicy,
|
|
5
|
+
prepareRecordedSkillBrowserTarget,
|
|
6
|
+
recordedSkillBrowserEnvironment,
|
|
7
|
+
requestAllowedByRecordedSkillPolicy,
|
|
8
|
+
} from "./browser-target-policy.js";
|
|
9
|
+
|
|
10
|
+
/* global document, Element */
|
|
11
|
+
|
|
12
|
+
const RECORDER_CALLBACK = "__ccRecordReplayCapture";
|
|
13
|
+
const MAX_ACTIONS = 256;
|
|
14
|
+
const AUTOMATION_KINDS = new Set(["click", "type", "select"]);
|
|
15
|
+
|
|
16
|
+
function recorderUiError(code, message, details = {}) {
|
|
17
|
+
const error = new Error(message);
|
|
18
|
+
error.name = "RecordedSkillUiRecorderError";
|
|
19
|
+
error.code = code;
|
|
20
|
+
Object.assign(error, details);
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function boundedString(value, label, maxLength) {
|
|
25
|
+
if (typeof value !== "string" || !value || value.length > maxLength) {
|
|
26
|
+
throw recorderUiError(
|
|
27
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
28
|
+
`${label} must be a non-empty string no longer than ${maxLength} characters`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function boundedViewport(viewport) {
|
|
35
|
+
const width = Number(viewport?.width);
|
|
36
|
+
const height = Number(viewport?.height);
|
|
37
|
+
if (
|
|
38
|
+
!Number.isInteger(width) ||
|
|
39
|
+
width < 320 ||
|
|
40
|
+
width > 3_840 ||
|
|
41
|
+
!Number.isInteger(height) ||
|
|
42
|
+
height < 240 ||
|
|
43
|
+
height > 2_160
|
|
44
|
+
) {
|
|
45
|
+
throw recorderUiError(
|
|
46
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
47
|
+
"viewport must use bounded integer width and height",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({ width, height });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function safeParameterBase(selector, index) {
|
|
54
|
+
const id = /#([A-Za-z][A-Za-z0-9_-]*)/u.exec(selector)?.[1];
|
|
55
|
+
const named =
|
|
56
|
+
/\[(?:name|data-testid|data-test|data-cc-record)=["']([^"']+)["']\]/u.exec(
|
|
57
|
+
selector,
|
|
58
|
+
)?.[1];
|
|
59
|
+
let value = String(id || named || `field${index + 1}`)
|
|
60
|
+
.replace(/[^A-Za-z0-9_]/gu, "_")
|
|
61
|
+
.replace(/_+/gu, "_")
|
|
62
|
+
.slice(0, 64);
|
|
63
|
+
if (!/^[A-Za-z]/u.test(value)) value = `field_${value}`;
|
|
64
|
+
return value || `field${index + 1}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parameterizeCapturedActions(actions, sensitiveParameters = []) {
|
|
68
|
+
if (
|
|
69
|
+
!Array.isArray(sensitiveParameters) ||
|
|
70
|
+
sensitiveParameters.some(
|
|
71
|
+
(name) =>
|
|
72
|
+
typeof name !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/u.test(name),
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
75
|
+
throw recorderUiError(
|
|
76
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
77
|
+
"sensitive parameter names must be valid identifiers",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const manuallySensitive = new Set(sensitiveParameters);
|
|
81
|
+
const bindings = [];
|
|
82
|
+
const byCapturedValue = new Map();
|
|
83
|
+
const usedNames = new Set();
|
|
84
|
+
for (const [index, action] of actions.entries()) {
|
|
85
|
+
if (!["type", "select"].includes(action.kind) || !action.value) continue;
|
|
86
|
+
const capturedValue = action.value;
|
|
87
|
+
const existing = byCapturedValue.get(capturedValue);
|
|
88
|
+
if (existing) {
|
|
89
|
+
if (action.sensitive === true) existing.sensitive = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const base = safeParameterBase(action.target, index);
|
|
93
|
+
let name = base;
|
|
94
|
+
let suffix = 2;
|
|
95
|
+
while (usedNames.has(name)) name = `${base}_${suffix++}`;
|
|
96
|
+
usedNames.add(name);
|
|
97
|
+
const binding = {
|
|
98
|
+
name,
|
|
99
|
+
value: capturedValue,
|
|
100
|
+
sensitive: action.sensitive === true || manuallySensitive.has(name),
|
|
101
|
+
required: true,
|
|
102
|
+
};
|
|
103
|
+
bindings.push(binding);
|
|
104
|
+
byCapturedValue.set(capturedValue, binding);
|
|
105
|
+
}
|
|
106
|
+
const unknownSensitive = [...manuallySensitive].filter(
|
|
107
|
+
(name) => !bindings.some((binding) => binding.name === name),
|
|
108
|
+
);
|
|
109
|
+
if (unknownSensitive.length > 0) {
|
|
110
|
+
throw recorderUiError(
|
|
111
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
112
|
+
`sensitive parameter was not captured: ${unknownSensitive[0]}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
actions: actions.map((action) => {
|
|
117
|
+
const persisted = { ...action };
|
|
118
|
+
delete persisted.sensitive;
|
|
119
|
+
return persisted;
|
|
120
|
+
}),
|
|
121
|
+
parameterBindings: bindings,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateCapturedAction(value) {
|
|
126
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
127
|
+
throw recorderUiError(
|
|
128
|
+
"CC_RECORD_UI_EVENT_INVALID",
|
|
129
|
+
"the browser recorder emitted an invalid action",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const kind = String(value.kind || "");
|
|
133
|
+
if (!["click", "type", "select"].includes(kind)) {
|
|
134
|
+
throw recorderUiError(
|
|
135
|
+
"CC_RECORD_UI_EVENT_INVALID",
|
|
136
|
+
"the browser recorder emitted an unsupported action",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const action = {
|
|
140
|
+
kind,
|
|
141
|
+
target: boundedString(value.target, "captured target", 1_024),
|
|
142
|
+
};
|
|
143
|
+
if (["type", "select"].includes(kind)) {
|
|
144
|
+
if (typeof value.value !== "string") {
|
|
145
|
+
throw recorderUiError(
|
|
146
|
+
"CC_RECORD_UI_EVENT_INVALID",
|
|
147
|
+
"the browser recorder emitted an invalid field value",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const maxLength = kind === "type" ? 8_192 : 1_024;
|
|
151
|
+
if (value.value.length > maxLength) {
|
|
152
|
+
throw recorderUiError(
|
|
153
|
+
"CC_RECORD_UI_EVENT_INVALID",
|
|
154
|
+
"the browser recorder emitted an oversized field value",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
action.value = value.value;
|
|
158
|
+
if (value.sensitive === true) action.sensitive = true;
|
|
159
|
+
}
|
|
160
|
+
return Object.freeze(action);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function validateAutomation(actions) {
|
|
164
|
+
if (!Array.isArray(actions) || actions.length > MAX_ACTIONS) {
|
|
165
|
+
throw recorderUiError(
|
|
166
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
167
|
+
`automation must contain at most ${MAX_ACTIONS} actions`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return actions.map((action) => {
|
|
171
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) {
|
|
172
|
+
throw recorderUiError(
|
|
173
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
174
|
+
"automation actions must be objects",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const kind = String(action.kind || "");
|
|
178
|
+
if (!AUTOMATION_KINDS.has(kind)) {
|
|
179
|
+
throw recorderUiError(
|
|
180
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
181
|
+
"automation only supports click, type, and select",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const normalized = {
|
|
185
|
+
kind,
|
|
186
|
+
target: boundedString(action.target, "automation target", 1_024),
|
|
187
|
+
};
|
|
188
|
+
if (["type", "select"].includes(kind)) {
|
|
189
|
+
normalized.value = boundedString(
|
|
190
|
+
action.value,
|
|
191
|
+
"automation value",
|
|
192
|
+
kind === "type" ? 8_192 : 1_024,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return normalized;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function installCaptureScript(page) {
|
|
200
|
+
await page.evaluate((callbackName) => {
|
|
201
|
+
const cssEscape = (value) => {
|
|
202
|
+
if (globalThis.CSS?.escape) return globalThis.CSS.escape(value);
|
|
203
|
+
return String(value).replace(
|
|
204
|
+
/[^A-Za-z0-9_-]/g,
|
|
205
|
+
(character) => `\\${character.codePointAt(0).toString(16)} `,
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
const quoted = (value) =>
|
|
209
|
+
String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
210
|
+
const unique = (selector) => {
|
|
211
|
+
try {
|
|
212
|
+
return document.querySelectorAll(selector).length === 1;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const selectorFor = (element) => {
|
|
218
|
+
if (!(element instanceof Element)) return null;
|
|
219
|
+
for (const attribute of ["data-testid", "data-test", "data-cc-record"]) {
|
|
220
|
+
const value = element.getAttribute(attribute);
|
|
221
|
+
if (value) {
|
|
222
|
+
const selector = `[${attribute}="${quoted(value)}"]`;
|
|
223
|
+
if (unique(selector)) return selector;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (element.id) {
|
|
227
|
+
const selector = `#${cssEscape(element.id)}`;
|
|
228
|
+
if (unique(selector)) return selector;
|
|
229
|
+
}
|
|
230
|
+
const tag = element.tagName.toLowerCase();
|
|
231
|
+
const name = element.getAttribute("name");
|
|
232
|
+
if (name) {
|
|
233
|
+
const selector = `${tag}[name="${quoted(name)}"]`;
|
|
234
|
+
if (unique(selector)) return selector;
|
|
235
|
+
}
|
|
236
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
237
|
+
if (ariaLabel) {
|
|
238
|
+
const selector = `${tag}[aria-label="${quoted(ariaLabel)}"]`;
|
|
239
|
+
if (unique(selector)) return selector;
|
|
240
|
+
}
|
|
241
|
+
const segments = [];
|
|
242
|
+
let current = element;
|
|
243
|
+
while (
|
|
244
|
+
current &&
|
|
245
|
+
current !== document.documentElement &&
|
|
246
|
+
segments.length < 8
|
|
247
|
+
) {
|
|
248
|
+
const currentTag = current.tagName.toLowerCase();
|
|
249
|
+
const siblings = current.parentElement
|
|
250
|
+
? [...current.parentElement.children].filter(
|
|
251
|
+
(candidate) => candidate.tagName === current.tagName,
|
|
252
|
+
)
|
|
253
|
+
: [];
|
|
254
|
+
const position = siblings.indexOf(current) + 1;
|
|
255
|
+
segments.unshift(
|
|
256
|
+
siblings.length > 1
|
|
257
|
+
? `${currentTag}:nth-of-type(${position})`
|
|
258
|
+
: currentTag,
|
|
259
|
+
);
|
|
260
|
+
const selector = segments.join(" > ");
|
|
261
|
+
if (unique(selector)) return selector;
|
|
262
|
+
current = current.parentElement;
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
};
|
|
266
|
+
const emit = (action) => {
|
|
267
|
+
const callback = globalThis[callbackName];
|
|
268
|
+
if (typeof callback === "function") void callback(action);
|
|
269
|
+
};
|
|
270
|
+
const click = (event) => {
|
|
271
|
+
const target = event.target?.closest?.(
|
|
272
|
+
"button,a,[role='button'],input,select,textarea,[data-testid],[data-test],[data-cc-record]",
|
|
273
|
+
);
|
|
274
|
+
if (!target) return;
|
|
275
|
+
const tag = target.tagName.toLowerCase();
|
|
276
|
+
if (["input", "select", "textarea"].includes(tag)) return;
|
|
277
|
+
const selector = selectorFor(target);
|
|
278
|
+
if (selector) emit({ kind: "click", target: selector });
|
|
279
|
+
};
|
|
280
|
+
const change = (event) => {
|
|
281
|
+
const target = event.target;
|
|
282
|
+
if (!(target instanceof Element)) return;
|
|
283
|
+
const selector = selectorFor(target);
|
|
284
|
+
if (!selector) return;
|
|
285
|
+
const tag = target.tagName.toLowerCase();
|
|
286
|
+
if (tag === "select") {
|
|
287
|
+
emit({
|
|
288
|
+
kind: "select",
|
|
289
|
+
target: selector,
|
|
290
|
+
value: String(target.value || ""),
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (tag === "input" || tag === "textarea") {
|
|
295
|
+
const type = String(
|
|
296
|
+
target.getAttribute("type") || "text",
|
|
297
|
+
).toLowerCase();
|
|
298
|
+
if (
|
|
299
|
+
["checkbox", "radio", "file", "submit", "button", "reset"].includes(
|
|
300
|
+
type,
|
|
301
|
+
)
|
|
302
|
+
) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
emit({
|
|
306
|
+
kind: "type",
|
|
307
|
+
target: selector,
|
|
308
|
+
value: String(target.value || ""),
|
|
309
|
+
sensitive: type === "password",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
document.addEventListener("click", click, true);
|
|
314
|
+
document.addEventListener("change", change, true);
|
|
315
|
+
globalThis.__ccRecordReplayStop = () => {
|
|
316
|
+
document.removeEventListener("click", click, true);
|
|
317
|
+
document.removeEventListener("change", change, true);
|
|
318
|
+
delete globalThis.__ccRecordReplayStop;
|
|
319
|
+
};
|
|
320
|
+
}, RECORDER_CALLBACK);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Launch a real Chromium recorder for a self-contained HTML target. The
|
|
325
|
+
* browser emits only the bounded click/type/select vocabulary and selectors
|
|
326
|
+
* built from stable attributes or structural CSS; text content is never used
|
|
327
|
+
* to form selectors. Captured values remain in memory until finish() replaces
|
|
328
|
+
* them with parameter placeholders.
|
|
329
|
+
*/
|
|
330
|
+
export async function launchPlaywrightRecordedSkillRecorder({
|
|
331
|
+
html,
|
|
332
|
+
url,
|
|
333
|
+
allowedOrigins = [],
|
|
334
|
+
storageState,
|
|
335
|
+
identity = "anonymous",
|
|
336
|
+
playwright,
|
|
337
|
+
headless = false,
|
|
338
|
+
timeoutMs = 5_000,
|
|
339
|
+
viewport = { width: 960, height: 640 },
|
|
340
|
+
maxActions = MAX_ACTIONS,
|
|
341
|
+
} = {}) {
|
|
342
|
+
const target = prepareRecordedSkillBrowserTarget({
|
|
343
|
+
html,
|
|
344
|
+
url,
|
|
345
|
+
allowedOrigins,
|
|
346
|
+
storageState,
|
|
347
|
+
identity,
|
|
348
|
+
});
|
|
349
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 30_000) {
|
|
350
|
+
throw recorderUiError(
|
|
351
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
352
|
+
"timeoutMs must be an integer between 100 and 30000",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (
|
|
356
|
+
!Number.isInteger(maxActions) ||
|
|
357
|
+
maxActions < 1 ||
|
|
358
|
+
maxActions > MAX_ACTIONS
|
|
359
|
+
) {
|
|
360
|
+
throw recorderUiError(
|
|
361
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
362
|
+
`maxActions must be an integer between 1 and ${MAX_ACTIONS}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const safeViewport = boundedViewport(viewport);
|
|
366
|
+
const runtime = playwright || (await import("playwright"));
|
|
367
|
+
if (typeof runtime?.chromium?.launch !== "function") {
|
|
368
|
+
throw recorderUiError(
|
|
369
|
+
"CC_RECORD_UI_RECORDER_UNAVAILABLE",
|
|
370
|
+
"Playwright Chromium is required for UI recording",
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const executablePath = runtime.chromium.executablePath?.();
|
|
374
|
+
const browser = executablePath
|
|
375
|
+
? await executionBroker.runWithProcessContext(
|
|
376
|
+
{
|
|
377
|
+
origin: "record-replay:chromium-recorder",
|
|
378
|
+
policy: "allow",
|
|
379
|
+
allowedCommands: [executablePath],
|
|
380
|
+
auditContext: { actor: "record-replay-recorder" },
|
|
381
|
+
},
|
|
382
|
+
() =>
|
|
383
|
+
runtime.chromium.launch({
|
|
384
|
+
headless: headless === true,
|
|
385
|
+
executablePath,
|
|
386
|
+
}),
|
|
387
|
+
)
|
|
388
|
+
: await runtime.chromium.launch({ headless: headless === true });
|
|
389
|
+
const context = await browser.newContext({
|
|
390
|
+
viewport: safeViewport,
|
|
391
|
+
acceptDownloads: false,
|
|
392
|
+
serviceWorkers: "block",
|
|
393
|
+
storageState: target.storageState || undefined,
|
|
394
|
+
});
|
|
395
|
+
const capturedActions = [];
|
|
396
|
+
let deniedRequestCount = 0;
|
|
397
|
+
let overflowed = false;
|
|
398
|
+
let closed = false;
|
|
399
|
+
let finished = false;
|
|
400
|
+
let page;
|
|
401
|
+
try {
|
|
402
|
+
await context.route("**/*", async (route) => {
|
|
403
|
+
if (!requestAllowedByRecordedSkillPolicy(route.request().url(), target)) {
|
|
404
|
+
deniedRequestCount += 1;
|
|
405
|
+
await route.abort("blockedbyclient");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
await route.continue();
|
|
409
|
+
});
|
|
410
|
+
if (target.networkPolicy.mode === "deny") await context.setOffline(true);
|
|
411
|
+
page = await context.newPage();
|
|
412
|
+
await page.exposeFunction(RECORDER_CALLBACK, (rawAction) => {
|
|
413
|
+
if (finished || closed) return;
|
|
414
|
+
if (capturedActions.length >= maxActions) {
|
|
415
|
+
overflowed = true;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
capturedActions.push(validateCapturedAction(rawAction));
|
|
419
|
+
});
|
|
420
|
+
if (target.adapter === "self-contained-html") {
|
|
421
|
+
await page.setContent(target.html, {
|
|
422
|
+
waitUntil: "domcontentloaded",
|
|
423
|
+
timeout: timeoutMs,
|
|
424
|
+
});
|
|
425
|
+
} else {
|
|
426
|
+
await page.goto(target.url, {
|
|
427
|
+
waitUntil: "domcontentloaded",
|
|
428
|
+
timeout: timeoutMs,
|
|
429
|
+
});
|
|
430
|
+
if (!navigationAllowedByRecordedSkillPolicy(page.url(), target)) {
|
|
431
|
+
throw recorderUiError(
|
|
432
|
+
"CC_RECORD_UI_NETWORK_ATTEMPT",
|
|
433
|
+
"recording target redirected outside its reviewed origin policy",
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
page.on("domcontentloaded", () => {
|
|
437
|
+
void installCaptureScript(page).catch(() => {});
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
await installCaptureScript(page);
|
|
441
|
+
} catch (cause) {
|
|
442
|
+
await browser.close();
|
|
443
|
+
throw recorderUiError(
|
|
444
|
+
"CC_RECORD_UI_SETUP_FAILED",
|
|
445
|
+
"isolated UI recorder setup failed",
|
|
446
|
+
{ cause },
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
if (deniedRequestCount > 0) {
|
|
450
|
+
await browser.close();
|
|
451
|
+
throw recorderUiError(
|
|
452
|
+
"CC_RECORD_UI_FIXTURE_NETWORKED",
|
|
453
|
+
"recording target attempted access outside its reviewed network policy while loading",
|
|
454
|
+
{ deniedRequestCount },
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return Object.freeze({
|
|
459
|
+
browserVersion: String(browser.version?.() || "unknown").slice(0, 128),
|
|
460
|
+
adapter: target.adapter,
|
|
461
|
+
targetDigest: target.targetDigest,
|
|
462
|
+
fixtureDigest:
|
|
463
|
+
target.adapter === "self-contained-html" ? target.targetDigest : null,
|
|
464
|
+
storageStateDigest: target.storageStateDigest,
|
|
465
|
+
identity: target.identity,
|
|
466
|
+
networkPolicy: target.networkPolicy,
|
|
467
|
+
async runAutomation(actions) {
|
|
468
|
+
if (finished || closed) {
|
|
469
|
+
throw recorderUiError(
|
|
470
|
+
"CC_RECORD_UI_STATE_INVALID",
|
|
471
|
+
"the UI recorder is no longer accepting interactions",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
for (const action of validateAutomation(actions)) {
|
|
475
|
+
const locator = page.locator(action.target);
|
|
476
|
+
if ((await locator.count()) !== 1) {
|
|
477
|
+
throw recorderUiError(
|
|
478
|
+
"CC_RECORD_UI_TARGET_AMBIGUOUS",
|
|
479
|
+
"automation target must resolve to exactly one element",
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (action.kind === "click") {
|
|
483
|
+
await locator.click({ timeout: timeoutMs });
|
|
484
|
+
} else if (action.kind === "type") {
|
|
485
|
+
await locator.fill(action.value, { timeout: timeoutMs });
|
|
486
|
+
await locator.dispatchEvent("change");
|
|
487
|
+
} else {
|
|
488
|
+
await locator.selectOption(action.value, { timeout: timeoutMs });
|
|
489
|
+
}
|
|
490
|
+
await page.waitForTimeout(0);
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
async finish({
|
|
494
|
+
name,
|
|
495
|
+
description = "",
|
|
496
|
+
environment = {},
|
|
497
|
+
failureConditions = [],
|
|
498
|
+
observations = [],
|
|
499
|
+
assertions = [],
|
|
500
|
+
sensitiveParameters = [],
|
|
501
|
+
} = {}) {
|
|
502
|
+
if (finished || closed) {
|
|
503
|
+
throw recorderUiError(
|
|
504
|
+
"CC_RECORD_UI_STATE_INVALID",
|
|
505
|
+
"the UI recorder has already finished",
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
finished = true;
|
|
509
|
+
await page.evaluate(() => globalThis.__ccRecordReplayStop?.());
|
|
510
|
+
if (overflowed) {
|
|
511
|
+
throw recorderUiError(
|
|
512
|
+
"CC_RECORD_UI_ACTION_LIMIT",
|
|
513
|
+
`recording exceeded the ${maxActions} action limit`,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
if (deniedRequestCount > 0) {
|
|
517
|
+
throw recorderUiError(
|
|
518
|
+
"CC_RECORD_UI_NETWORK_ATTEMPT",
|
|
519
|
+
"recorded interaction attempted filesystem or network access",
|
|
520
|
+
{ deniedRequestCount },
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (!Array.isArray(observations) || !Array.isArray(assertions)) {
|
|
524
|
+
throw recorderUiError(
|
|
525
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
526
|
+
"observations and assertions must be arrays",
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const normalizedObservations = observations.map((observation) => ({
|
|
530
|
+
kind: "observe",
|
|
531
|
+
target: boundedString(
|
|
532
|
+
typeof observation === "string" ? observation : observation?.target,
|
|
533
|
+
"observation target",
|
|
534
|
+
1_024,
|
|
535
|
+
),
|
|
536
|
+
}));
|
|
537
|
+
const normalizedAssertions = assertions.map((assertion) => {
|
|
538
|
+
if (
|
|
539
|
+
!assertion ||
|
|
540
|
+
typeof assertion !== "object" ||
|
|
541
|
+
Array.isArray(assertion)
|
|
542
|
+
) {
|
|
543
|
+
throw recorderUiError(
|
|
544
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
545
|
+
"assertions must be objects",
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const normalized = {
|
|
549
|
+
kind: "assert",
|
|
550
|
+
target: boundedString(assertion.target, "assertion target", 1_024),
|
|
551
|
+
};
|
|
552
|
+
if ("value" in assertion) {
|
|
553
|
+
if (
|
|
554
|
+
typeof assertion.value !== "string" ||
|
|
555
|
+
assertion.value.length > 8_192
|
|
556
|
+
) {
|
|
557
|
+
throw recorderUiError(
|
|
558
|
+
"CC_RECORD_UI_ARGUMENT_INVALID",
|
|
559
|
+
"assertion value must be a string no longer than 8192 characters",
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
normalized.value = assertion.value;
|
|
563
|
+
}
|
|
564
|
+
return normalized;
|
|
565
|
+
});
|
|
566
|
+
const parameterized = parameterizeCapturedActions(
|
|
567
|
+
[
|
|
568
|
+
...capturedActions,
|
|
569
|
+
...normalizedObservations,
|
|
570
|
+
...normalizedAssertions,
|
|
571
|
+
],
|
|
572
|
+
sensitiveParameters,
|
|
573
|
+
);
|
|
574
|
+
capturedActions.splice(0, capturedActions.length);
|
|
575
|
+
return createRecordedSkillDraft({
|
|
576
|
+
name,
|
|
577
|
+
description,
|
|
578
|
+
actions: parameterized.actions,
|
|
579
|
+
parameterBindings: parameterized.parameterBindings,
|
|
580
|
+
environment: {
|
|
581
|
+
...environment,
|
|
582
|
+
...recordedSkillBrowserEnvironment(target),
|
|
583
|
+
},
|
|
584
|
+
failureConditions,
|
|
585
|
+
});
|
|
586
|
+
},
|
|
587
|
+
summary() {
|
|
588
|
+
const counts = { click: 0, type: 0, select: 0 };
|
|
589
|
+
for (const action of capturedActions) counts[action.kind] += 1;
|
|
590
|
+
return Object.freeze({
|
|
591
|
+
schema: "chainlesschain.recorded-skill-ui-recording-summary/v1",
|
|
592
|
+
closed,
|
|
593
|
+
finished,
|
|
594
|
+
actionCount: capturedActions.length,
|
|
595
|
+
actionCounts: Object.freeze(counts),
|
|
596
|
+
deniedRequestCount,
|
|
597
|
+
overflowed,
|
|
598
|
+
});
|
|
599
|
+
},
|
|
600
|
+
async close() {
|
|
601
|
+
if (closed) return;
|
|
602
|
+
closed = true;
|
|
603
|
+
capturedActions.splice(0, capturedActions.length);
|
|
604
|
+
try {
|
|
605
|
+
await context.close();
|
|
606
|
+
} finally {
|
|
607
|
+
await browser.close();
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
}
|