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
package/src/commands/skill.js
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
removeCliPacks,
|
|
59
59
|
} from "../lib/skill-packs/generator.js";
|
|
60
60
|
import { CLI_PACK_DOMAINS } from "../lib/skill-packs/schema.js";
|
|
61
|
+
import { registerRecordReplayCommands } from "./record-replay.js";
|
|
61
62
|
|
|
62
63
|
const LAYER_LABELS = {
|
|
63
64
|
bundled: chalk.blue("[bundled]"),
|
|
@@ -291,6 +292,8 @@ export function registerSkillCommand(program) {
|
|
|
291
292
|
|
|
292
293
|
const loader = new CLISkillLoader();
|
|
293
294
|
|
|
295
|
+
registerRecordReplayCommands(skill);
|
|
296
|
+
|
|
294
297
|
// skill list
|
|
295
298
|
skill
|
|
296
299
|
.command("list")
|
package/src/data/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "CHANGELOG.md",
|
|
3
3
|
"releases": [
|
|
4
|
+
{
|
|
5
|
+
"productVersion": null,
|
|
6
|
+
"cliVersion": "0.166.14",
|
|
7
|
+
"cliVersions": ["0.166.13", "0.166.14"],
|
|
8
|
+
"date": "2026-08-30",
|
|
9
|
+
"title": "",
|
|
10
|
+
"sections": [
|
|
11
|
+
{
|
|
12
|
+
"heading": "Fixed - cc CLI 0.166.14: restore Docker-optional startup on Windows",
|
|
13
|
+
"body": "> `chainlesschain` **0.166.13 -> 0.166.14** (hotfix candidate, 2026-08-30).\n\n- **Docker remains opt-in**: a normal agent session no longer probes or\n requires Docker unless the CLI flag, settings, or managed policy explicitly\n selects container isolation.\n- **Windows executable lookup**: bare commands resolve through `PATHEXT`\n before launch, so Docker Desktop's POSIX `docker` script cannot shadow\n `docker.exe` and trigger `CreateProcessAsUser` error 193.\n- **Explicit isolation stays fail-closed**: `workspace-write`, `strict`, and\n managed sandbox requirements still reject unavailable engines instead of\n silently degrading."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"productVersion": null,
|
|
6
19
|
"cliVersion": "0.166.13",
|
package/src/lib/agent-sandbox.js
CHANGED
|
@@ -259,14 +259,10 @@ export function normalizeAgentSandbox(value, options = {}) {
|
|
|
259
259
|
*/
|
|
260
260
|
export function normalizeAgentSandboxMode(mode, value, options = {}) {
|
|
261
261
|
if (mode == null || mode === "") {
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
return normalizeAgentSandboxMode("workspace-write", value || true, {
|
|
267
|
-
...options,
|
|
268
|
-
network: false,
|
|
269
|
-
});
|
|
262
|
+
// Keep the container sandbox opt-in unless CLI/settings policy explicitly
|
|
263
|
+
// enables it. The platform process broker still applies its native
|
|
264
|
+
// boundary, but starting a normal agent must not require Docker.
|
|
265
|
+
return normalizeAgentSandbox(value, options);
|
|
270
266
|
}
|
|
271
267
|
if (!AGENT_SANDBOX_MODES.includes(mode)) {
|
|
272
268
|
const error = new Error(
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TEAM_MESSAGE_BRIDGE_MAX_WAIT_MS,
|
|
6
6
|
TEAM_MESSAGE_BRIDGE_PROTOCOL,
|
|
7
7
|
} from "./team-message-bridge.js";
|
|
8
|
+
import { resolveCredentialEnvironmentValue } from "../process-execution-broker/credential-transport.js";
|
|
8
9
|
|
|
9
10
|
export const TEAM_MESSAGE_TOOL_NAMES = Object.freeze([
|
|
10
11
|
"team_send",
|
|
@@ -177,21 +178,38 @@ function descriptor(name) {
|
|
|
177
178
|
});
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
export function resolveTeamMessageToolBundle({
|
|
181
|
+
export async function resolveTeamMessageToolBundle({
|
|
181
182
|
env = process.env,
|
|
182
183
|
call = callTeamMessageBridge,
|
|
184
|
+
resolveCredential = resolveCredentialEnvironmentValue,
|
|
183
185
|
} = {}) {
|
|
184
186
|
const endpoint = env.CC_TEAM_MESSAGE_BRIDGE_ENDPOINT;
|
|
185
|
-
const
|
|
187
|
+
const tokenReference = env.CC_CRED_REF_CC_TEAM_MESSAGE_BRIDGE_TOKEN;
|
|
186
188
|
const protocol = Number(env.CC_TEAM_MESSAGE_BRIDGE_PROTOCOL);
|
|
187
|
-
if (
|
|
189
|
+
if (
|
|
190
|
+
!endpoint &&
|
|
191
|
+
!env.CC_TEAM_MESSAGE_BRIDGE_TOKEN &&
|
|
192
|
+
!tokenReference &&
|
|
193
|
+
!env.CC_TEAM_MESSAGE_BRIDGE_PROTOCOL
|
|
194
|
+
) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
188
197
|
if (
|
|
189
198
|
typeof endpoint !== "string" ||
|
|
190
199
|
!endpoint ||
|
|
200
|
+
protocol !== TEAM_MESSAGE_BRIDGE_PROTOCOL
|
|
201
|
+
) {
|
|
202
|
+
const error = new Error("Team message bridge environment is invalid");
|
|
203
|
+
error.code = "TEAM_MESSAGE_BRIDGE_CONFIG_INVALID";
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
const token = await resolveCredential("CC_TEAM_MESSAGE_BRIDGE_TOKEN", {
|
|
207
|
+
env,
|
|
208
|
+
});
|
|
209
|
+
if (
|
|
191
210
|
typeof token !== "string" ||
|
|
192
211
|
token.length !== 64 ||
|
|
193
|
-
!/^[a-f0-9]{64}$/u.test(token)
|
|
194
|
-
protocol !== TEAM_MESSAGE_BRIDGE_PROTOCOL
|
|
212
|
+
!/^[a-f0-9]{64}$/u.test(token)
|
|
195
213
|
) {
|
|
196
214
|
const error = new Error("Team message bridge environment is invalid");
|
|
197
215
|
error.code = "TEAM_MESSAGE_BRIDGE_CONFIG_INVALID";
|
|
@@ -375,4 +375,35 @@ export async function resolveCredentialRefOverTransport(refId, options = {}) {
|
|
|
375
375
|
});
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Resolve one named environment credential without copying plaintext back into
|
|
380
|
+
* process.env. Direct launches keep accepting the original environment value;
|
|
381
|
+
* brokered launches exchange the short-lived, target-bound reference that the
|
|
382
|
+
* ProcessExecutionBroker placed in CC_CRED_REF_<NAME>.
|
|
383
|
+
*/
|
|
384
|
+
export async function resolveCredentialEnvironmentValue(key, options = {}) {
|
|
385
|
+
if (!/^[A-Z][A-Z0-9_]*$/u.test(String(key || ""))) {
|
|
386
|
+
throw transportError(
|
|
387
|
+
"CC_CREDENTIAL_INVALID_REQUEST",
|
|
388
|
+
"Credential environment key is invalid",
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const env = options.env || process.env;
|
|
392
|
+
const value = env[key];
|
|
393
|
+
const refId = env[`CC_CRED_REF_${key}`];
|
|
394
|
+
const hasValue = typeof value === "string" && value.length > 0;
|
|
395
|
+
const hasReference = typeof refId === "string" && refId.length > 0;
|
|
396
|
+
if (hasValue && hasReference) {
|
|
397
|
+
throw transportError(
|
|
398
|
+
"CC_CREDENTIAL_INVALID_REQUEST",
|
|
399
|
+
"Credential environment contains both plaintext and a broker reference",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (hasValue) return value;
|
|
403
|
+
if (!hasReference) return null;
|
|
404
|
+
const resolveReference =
|
|
405
|
+
options.resolveReference || resolveCredentialRefOverTransport;
|
|
406
|
+
return await resolveReference(refId, { env });
|
|
407
|
+
}
|
|
408
|
+
|
|
378
409
|
export { TRANSPORT_VERSION };
|
|
@@ -29,6 +29,7 @@ import path from "node:path";
|
|
|
29
29
|
import * as fs from "node:fs";
|
|
30
30
|
import * as tty from "node:tty";
|
|
31
31
|
import { createRequire } from "node:module";
|
|
32
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
32
33
|
|
|
33
34
|
const require = createRequire(import.meta.url);
|
|
34
35
|
|
|
@@ -996,6 +997,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
996
997
|
this._ptyAdapter = createNativePtyAdapter();
|
|
997
998
|
this._hooksEventSink = null;
|
|
998
999
|
this._workspaceTransactionManagers = new Map();
|
|
1000
|
+
this._ambientProcessContext = new AsyncLocalStorage();
|
|
999
1001
|
|
|
1000
1002
|
this._ensureLogDir();
|
|
1001
1003
|
this._loadPermissions();
|
|
@@ -1029,6 +1031,76 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1029
1031
|
return "prompt";
|
|
1030
1032
|
}
|
|
1031
1033
|
|
|
1034
|
+
_withAmbientProcessContext(command, options = {}) {
|
|
1035
|
+
const context = this._ambientProcessContext.getStore();
|
|
1036
|
+
if (!context) return options;
|
|
1037
|
+
const commandIdentity = (() => {
|
|
1038
|
+
if (typeof command !== "string" || !command) return "";
|
|
1039
|
+
const normalized = path.isAbsolute(command)
|
|
1040
|
+
? path.resolve(command)
|
|
1041
|
+
: command;
|
|
1042
|
+
return process.platform === "win32"
|
|
1043
|
+
? normalized.toLowerCase()
|
|
1044
|
+
: normalized;
|
|
1045
|
+
})();
|
|
1046
|
+
const allowed = context.allowedCommands.includes(commandIdentity);
|
|
1047
|
+
return {
|
|
1048
|
+
...options,
|
|
1049
|
+
origin: context.origin,
|
|
1050
|
+
scope: context.scope,
|
|
1051
|
+
policy: allowed ? context.policy : "deny",
|
|
1052
|
+
auditContext: context.auditContext,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
runWithProcessContext(
|
|
1057
|
+
{
|
|
1058
|
+
origin,
|
|
1059
|
+
policy = "allow",
|
|
1060
|
+
scope = "default",
|
|
1061
|
+
allowedCommands = [],
|
|
1062
|
+
auditContext,
|
|
1063
|
+
} = {},
|
|
1064
|
+
operation,
|
|
1065
|
+
) {
|
|
1066
|
+
const safeOrigin = String(origin || "");
|
|
1067
|
+
if (
|
|
1068
|
+
!/^[A-Za-z][A-Za-z0-9:._-]{0,127}$/u.test(safeOrigin) ||
|
|
1069
|
+
!["allow", "deny", "prompt"].includes(policy) ||
|
|
1070
|
+
typeof operation !== "function" ||
|
|
1071
|
+
!Array.isArray(allowedCommands) ||
|
|
1072
|
+
allowedCommands.length < 1 ||
|
|
1073
|
+
allowedCommands.length > 16
|
|
1074
|
+
) {
|
|
1075
|
+
const error = new Error("Process execution context is invalid");
|
|
1076
|
+
error.code = "PROCESS_CONTEXT_INVALID";
|
|
1077
|
+
throw error;
|
|
1078
|
+
}
|
|
1079
|
+
const normalizedCommands = allowedCommands.map((command) => {
|
|
1080
|
+
if (typeof command !== "string" || !path.isAbsolute(command)) {
|
|
1081
|
+
const error = new Error(
|
|
1082
|
+
"Process execution context commands must be absolute paths",
|
|
1083
|
+
);
|
|
1084
|
+
error.code = "PROCESS_CONTEXT_INVALID";
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
const normalized = path.resolve(command);
|
|
1088
|
+
return process.platform === "win32"
|
|
1089
|
+
? normalized.toLowerCase()
|
|
1090
|
+
: normalized;
|
|
1091
|
+
});
|
|
1092
|
+
return this._ambientProcessContext.run(
|
|
1093
|
+
Object.freeze({
|
|
1094
|
+
origin: safeOrigin,
|
|
1095
|
+
policy,
|
|
1096
|
+
scope: String(scope || "default").slice(0, 128),
|
|
1097
|
+
allowedCommands: Object.freeze([...new Set(normalizedCommands)]),
|
|
1098
|
+
auditContext,
|
|
1099
|
+
}),
|
|
1100
|
+
operation,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1032
1104
|
_isDangerousCommand(command) {
|
|
1033
1105
|
if (typeof command !== "string") return false;
|
|
1034
1106
|
const lower = command.toLowerCase();
|
|
@@ -4470,6 +4542,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4470
4542
|
}
|
|
4471
4543
|
|
|
4472
4544
|
spawn(command, args, options = {}) {
|
|
4545
|
+
options = this._withAmbientProcessContext(command, options);
|
|
4473
4546
|
const executionId = crypto.randomUUID();
|
|
4474
4547
|
const startTime = Date.now();
|
|
4475
4548
|
const origin = options.origin || "unknown";
|
|
@@ -4857,6 +4930,7 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
4857
4930
|
}
|
|
4858
4931
|
|
|
4859
4932
|
spawnSync(command, args, options = {}) {
|
|
4933
|
+
options = this._withAmbientProcessContext(command, options);
|
|
4860
4934
|
const executionId = crypto.randomUUID();
|
|
4861
4935
|
const startTime = Date.now();
|
|
4862
4936
|
const origin = options.origin || "unknown";
|
|
Binary file
|
|
Binary file
|
|
@@ -1683,8 +1683,12 @@ namespace ChainlessChain.WindowsSandbox
|
|
|
1683
1683
|
string[] configured = pathExt.Split(
|
|
1684
1684
|
new[] { ';' },
|
|
1685
1685
|
StringSplitOptions.RemoveEmptyEntries);
|
|
1686
|
-
|
|
1687
|
-
|
|
1686
|
+
// Match Windows command lookup: a bare command name is
|
|
1687
|
+
// resolved through PATHEXT. Trying an extensionless file first
|
|
1688
|
+
// can select Docker's adjacent POSIX `docker` shell script
|
|
1689
|
+
// instead of docker.exe, which CreateProcessAsUser rejects
|
|
1690
|
+
// with ERROR_BAD_EXE_FORMAT (193).
|
|
1691
|
+
extensions = new string[configured.Length];
|
|
1688
1692
|
for (int index = 0; index < configured.Length; index++)
|
|
1689
1693
|
{
|
|
1690
1694
|
string extension = configured[index].Trim();
|
|
@@ -1697,7 +1701,7 @@ namespace ChainlessChain.WindowsSandbox
|
|
|
1697
1701
|
throw new InvalidDataException(
|
|
1698
1702
|
"Target PATHEXT contains an invalid extension");
|
|
1699
1703
|
}
|
|
1700
|
-
extensions[index
|
|
1704
|
+
extensions[index] = extension;
|
|
1701
1705
|
}
|
|
1702
1706
|
}
|
|
1703
1707
|
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const MAX_HTML_CHARS = 1_000_000;
|
|
4
|
+
const MAX_ALLOWED_ORIGINS = 8;
|
|
5
|
+
const MAX_URL_CHARS = 4_096;
|
|
6
|
+
const MAX_STORAGE_STATE_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
const MAX_STORAGE_STATE_DEPTH = 16;
|
|
8
|
+
const MAX_STORAGE_STATE_NODES = 10_000;
|
|
9
|
+
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/u;
|
|
10
|
+
|
|
11
|
+
function targetError(code, message) {
|
|
12
|
+
const error = new Error(message);
|
|
13
|
+
error.name = "RecordedSkillBrowserTargetError";
|
|
14
|
+
error.code = code;
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function canonicalValue(value) {
|
|
19
|
+
if (Array.isArray(value)) return value.map(canonicalValue);
|
|
20
|
+
if (!value || typeof value !== "object") return value;
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.keys(value)
|
|
23
|
+
.sort()
|
|
24
|
+
.map((key) => [key, canonicalValue(value[key])]),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function deepFreeze(value) {
|
|
29
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value))
|
|
30
|
+
return value;
|
|
31
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
32
|
+
return Object.freeze(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function boundedCanonicalStorageState(value) {
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37
|
+
throw targetError(
|
|
38
|
+
"CC_RECORD_TARGET_INVALID",
|
|
39
|
+
"browser storage state must be a JSON object",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const seen = new WeakSet();
|
|
43
|
+
const pending = [{ value, depth: 0 }];
|
|
44
|
+
let nodes = 0;
|
|
45
|
+
while (pending.length > 0) {
|
|
46
|
+
const current = pending.pop();
|
|
47
|
+
const item = current.value;
|
|
48
|
+
nodes += 1;
|
|
49
|
+
if (
|
|
50
|
+
nodes > MAX_STORAGE_STATE_NODES ||
|
|
51
|
+
current.depth > MAX_STORAGE_STATE_DEPTH
|
|
52
|
+
) {
|
|
53
|
+
throw targetError(
|
|
54
|
+
"CC_RECORD_TARGET_INVALID",
|
|
55
|
+
"browser storage state exceeds its structural limits",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!item || typeof item !== "object") {
|
|
59
|
+
if (
|
|
60
|
+
typeof item === "undefined" ||
|
|
61
|
+
typeof item === "bigint" ||
|
|
62
|
+
typeof item === "function" ||
|
|
63
|
+
typeof item === "symbol" ||
|
|
64
|
+
(typeof item === "number" && !Number.isFinite(item))
|
|
65
|
+
) {
|
|
66
|
+
throw targetError(
|
|
67
|
+
"CC_RECORD_TARGET_INVALID",
|
|
68
|
+
"browser storage state must contain only JSON values",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (seen.has(item)) {
|
|
74
|
+
throw targetError(
|
|
75
|
+
"CC_RECORD_TARGET_INVALID",
|
|
76
|
+
"browser storage state must not contain cycles or shared objects",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
seen.add(item);
|
|
80
|
+
if (
|
|
81
|
+
!Array.isArray(item) &&
|
|
82
|
+
Object.getPrototypeOf(item) !== Object.prototype
|
|
83
|
+
) {
|
|
84
|
+
throw targetError(
|
|
85
|
+
"CC_RECORD_TARGET_INVALID",
|
|
86
|
+
"browser storage state must contain plain JSON objects",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const children = Array.isArray(item) ? item : Object.values(item);
|
|
90
|
+
if (children.length > MAX_STORAGE_STATE_NODES) {
|
|
91
|
+
throw targetError(
|
|
92
|
+
"CC_RECORD_TARGET_INVALID",
|
|
93
|
+
"browser storage state exceeds its structural limits",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
for (const child of children) {
|
|
97
|
+
pending.push({ value: child, depth: current.depth + 1 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const canonical = canonicalValue(value);
|
|
101
|
+
if (
|
|
102
|
+
Buffer.byteLength(JSON.stringify(canonical), "utf8") >
|
|
103
|
+
MAX_STORAGE_STATE_BYTES
|
|
104
|
+
) {
|
|
105
|
+
throw targetError(
|
|
106
|
+
"CC_RECORD_TARGET_INVALID",
|
|
107
|
+
"browser storage state exceeds the 2 MiB limit",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return deepFreeze(canonical);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function digest(value, domain) {
|
|
114
|
+
const bytes = Buffer.isBuffer(value)
|
|
115
|
+
? value
|
|
116
|
+
: Buffer.from(
|
|
117
|
+
typeof value === "string"
|
|
118
|
+
? value
|
|
119
|
+
: JSON.stringify(canonicalValue(value)),
|
|
120
|
+
);
|
|
121
|
+
return `sha256:${createHash("sha256")
|
|
122
|
+
.update(domain)
|
|
123
|
+
.update("\0")
|
|
124
|
+
.update(bytes)
|
|
125
|
+
.digest("hex")}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isLoopback(hostname) {
|
|
129
|
+
const normalized = String(hostname || "").toLowerCase();
|
|
130
|
+
return (
|
|
131
|
+
normalized === "localhost" ||
|
|
132
|
+
normalized === "127.0.0.1" ||
|
|
133
|
+
normalized === "[::1]" ||
|
|
134
|
+
normalized === "::1"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function safeUrl(value, { originOnly = false } = {}) {
|
|
139
|
+
if (
|
|
140
|
+
typeof value !== "string" ||
|
|
141
|
+
value.length < 1 ||
|
|
142
|
+
value.length > MAX_URL_CHARS
|
|
143
|
+
) {
|
|
144
|
+
throw targetError(
|
|
145
|
+
"CC_RECORD_TARGET_INVALID",
|
|
146
|
+
`browser target URL must be no longer than ${MAX_URL_CHARS} characters`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = new URL(String(value));
|
|
152
|
+
} catch {
|
|
153
|
+
throw targetError(
|
|
154
|
+
"CC_RECORD_TARGET_INVALID",
|
|
155
|
+
"browser target must be an absolute URL",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
parsed.username ||
|
|
160
|
+
parsed.password ||
|
|
161
|
+
(parsed.protocol !== "https:" &&
|
|
162
|
+
!(parsed.protocol === "http:" && isLoopback(parsed.hostname)))
|
|
163
|
+
) {
|
|
164
|
+
throw targetError(
|
|
165
|
+
"CC_RECORD_TARGET_INVALID",
|
|
166
|
+
"browser targets require HTTPS; HTTP is allowed only for loopback testing and URL credentials are forbidden",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (originOnly && parsed.href !== `${parsed.origin}/`) {
|
|
170
|
+
throw targetError(
|
|
171
|
+
"CC_RECORD_TARGET_INVALID",
|
|
172
|
+
"allowed origins must not contain a path, query, or fragment",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return parsed;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function createRecordedSkillNetworkPolicy({
|
|
179
|
+
mode = "deny",
|
|
180
|
+
allowedOrigins = [],
|
|
181
|
+
} = {}) {
|
|
182
|
+
if (mode === "deny") {
|
|
183
|
+
if (!Array.isArray(allowedOrigins) || allowedOrigins.length > 0) {
|
|
184
|
+
throw targetError(
|
|
185
|
+
"CC_RECORD_TARGET_INVALID",
|
|
186
|
+
"deny network policy cannot retain allowed origins",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const body = { mode: "deny", allowedOrigins: [] };
|
|
190
|
+
return deepFreeze({
|
|
191
|
+
...body,
|
|
192
|
+
digest: digest(body, "cc.record-replay.network-policy/v1"),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (mode !== "allowlist" || !Array.isArray(allowedOrigins)) {
|
|
196
|
+
throw targetError(
|
|
197
|
+
"CC_RECORD_TARGET_INVALID",
|
|
198
|
+
"browser network policy is invalid",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const origins = [
|
|
202
|
+
...new Set(
|
|
203
|
+
allowedOrigins.map(
|
|
204
|
+
(value) => safeUrl(value, { originOnly: true }).origin,
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
].sort();
|
|
208
|
+
if (origins.length < 1 || origins.length > MAX_ALLOWED_ORIGINS) {
|
|
209
|
+
throw targetError(
|
|
210
|
+
"CC_RECORD_TARGET_INVALID",
|
|
211
|
+
`browser target requires between 1 and ${MAX_ALLOWED_ORIGINS} allowed origins`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const body = { mode: "allowlist", allowedOrigins: origins };
|
|
215
|
+
return deepFreeze({
|
|
216
|
+
...body,
|
|
217
|
+
digest: digest(body, "cc.record-replay.network-policy/v1"),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function digestRecordedSkillStorageState(storageState) {
|
|
222
|
+
if (storageState == null) return null;
|
|
223
|
+
return digest(
|
|
224
|
+
boundedCanonicalStorageState(storageState),
|
|
225
|
+
"cc.record-replay.browser-storage-state/v1",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function prepareRecordedSkillBrowserTarget({
|
|
230
|
+
html,
|
|
231
|
+
url,
|
|
232
|
+
allowedOrigins = [],
|
|
233
|
+
storageState,
|
|
234
|
+
identity = "anonymous",
|
|
235
|
+
} = {}) {
|
|
236
|
+
const hasHtml = typeof html === "string" && html.length > 0;
|
|
237
|
+
const hasUrl = typeof url === "string" && url.length > 0;
|
|
238
|
+
if (hasHtml === hasUrl) {
|
|
239
|
+
throw targetError(
|
|
240
|
+
"CC_RECORD_TARGET_INVALID",
|
|
241
|
+
"provide exactly one self-contained HTML fixture or URL target",
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const safeIdentity = String(identity || "anonymous").trim();
|
|
245
|
+
if (!/^[A-Za-z][A-Za-z0-9._-]{0,127}$/u.test(safeIdentity)) {
|
|
246
|
+
throw targetError(
|
|
247
|
+
"CC_RECORD_TARGET_INVALID",
|
|
248
|
+
"browser identity label is invalid",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (hasHtml) {
|
|
252
|
+
if (
|
|
253
|
+
html.length > MAX_HTML_CHARS ||
|
|
254
|
+
allowedOrigins.length > 0 ||
|
|
255
|
+
storageState != null
|
|
256
|
+
) {
|
|
257
|
+
throw targetError(
|
|
258
|
+
"CC_RECORD_TARGET_INVALID",
|
|
259
|
+
"self-contained HTML recording cannot use network origins or browser credentials",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const networkPolicy = createRecordedSkillNetworkPolicy({ mode: "deny" });
|
|
263
|
+
return deepFreeze({
|
|
264
|
+
adapter: "self-contained-html",
|
|
265
|
+
html,
|
|
266
|
+
url: null,
|
|
267
|
+
targetDigest: digest(html, "cc.record-replay.ui-fixture/v1"),
|
|
268
|
+
networkPolicy,
|
|
269
|
+
storageState: null,
|
|
270
|
+
storageStateDigest: null,
|
|
271
|
+
identity: safeIdentity,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const target = safeUrl(url);
|
|
275
|
+
const networkPolicy = createRecordedSkillNetworkPolicy({
|
|
276
|
+
mode: "allowlist",
|
|
277
|
+
allowedOrigins:
|
|
278
|
+
allowedOrigins.length > 0 ? allowedOrigins : [target.origin],
|
|
279
|
+
});
|
|
280
|
+
if (!networkPolicy.allowedOrigins.includes(target.origin)) {
|
|
281
|
+
throw targetError(
|
|
282
|
+
"CC_RECORD_TARGET_INVALID",
|
|
283
|
+
"target origin must be present in the network allowlist",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const normalizedStorageState =
|
|
287
|
+
storageState == null ? null : boundedCanonicalStorageState(storageState);
|
|
288
|
+
return deepFreeze({
|
|
289
|
+
adapter: "url-origin",
|
|
290
|
+
html: null,
|
|
291
|
+
url: target.href,
|
|
292
|
+
targetDigest: digest(target.href, "cc.record-replay.ui-url/v1"),
|
|
293
|
+
networkPolicy,
|
|
294
|
+
storageState: normalizedStorageState,
|
|
295
|
+
storageStateDigest: digestRecordedSkillStorageState(normalizedStorageState),
|
|
296
|
+
identity: safeIdentity,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function recordedSkillBrowserEnvironment(target) {
|
|
301
|
+
if (
|
|
302
|
+
!target ||
|
|
303
|
+
!["self-contained-html", "url-origin"].includes(target.adapter) ||
|
|
304
|
+
!SHA256_PATTERN.test(String(target.targetDigest || ""))
|
|
305
|
+
) {
|
|
306
|
+
throw targetError(
|
|
307
|
+
"CC_RECORD_TARGET_INVALID",
|
|
308
|
+
"prepared browser target is invalid",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return deepFreeze({
|
|
312
|
+
adapter: target.adapter,
|
|
313
|
+
browser: "chromium",
|
|
314
|
+
targetDigest: target.targetDigest,
|
|
315
|
+
selectorContract: "record-replay-dom-v1",
|
|
316
|
+
networkPolicy: Object.freeze({
|
|
317
|
+
mode: target.networkPolicy.mode,
|
|
318
|
+
allowedOrigins: Object.freeze([...target.networkPolicy.allowedOrigins]),
|
|
319
|
+
digest: target.networkPolicy.digest,
|
|
320
|
+
}),
|
|
321
|
+
identity: target.identity,
|
|
322
|
+
storageStateDigest: target.storageStateDigest,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function assertRecordedSkillBrowserBinding(
|
|
327
|
+
target,
|
|
328
|
+
{ source, environment } = {},
|
|
329
|
+
) {
|
|
330
|
+
let reviewedPolicy;
|
|
331
|
+
try {
|
|
332
|
+
reviewedPolicy = createRecordedSkillNetworkPolicy({
|
|
333
|
+
mode: environment?.networkPolicy?.mode,
|
|
334
|
+
allowedOrigins: environment?.networkPolicy?.allowedOrigins || [],
|
|
335
|
+
});
|
|
336
|
+
} catch {
|
|
337
|
+
reviewedPolicy = null;
|
|
338
|
+
}
|
|
339
|
+
if (
|
|
340
|
+
!source ||
|
|
341
|
+
target?.adapter !== source.adapter ||
|
|
342
|
+
target?.targetDigest !== source.targetDigest ||
|
|
343
|
+
environment?.adapter !== source.adapter ||
|
|
344
|
+
environment?.targetDigest !== source.targetDigest
|
|
345
|
+
) {
|
|
346
|
+
throw targetError(
|
|
347
|
+
"CC_RECORD_TARGET_DRIFT",
|
|
348
|
+
"browser target no longer matches the reviewed recording",
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (
|
|
352
|
+
(environment.storageStateDigest || null) !==
|
|
353
|
+
(target.storageStateDigest || null)
|
|
354
|
+
) {
|
|
355
|
+
throw targetError(
|
|
356
|
+
"CC_RECORD_CREDENTIAL_DRIFT",
|
|
357
|
+
"browser storage state no longer matches the reviewed credential binding",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
if (
|
|
361
|
+
environment.identity !== target.identity ||
|
|
362
|
+
reviewedPolicy?.digest !== environment.networkPolicy?.digest ||
|
|
363
|
+
reviewedPolicy?.digest !== target.networkPolicy?.digest
|
|
364
|
+
) {
|
|
365
|
+
throw targetError(
|
|
366
|
+
"CC_RECORD_ENVIRONMENT_DRIFT",
|
|
367
|
+
"browser identity or network policy no longer matches the reviewed recording",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function requestAllowedByRecordedSkillPolicy(rawUrl, target) {
|
|
374
|
+
let parsed;
|
|
375
|
+
try {
|
|
376
|
+
parsed = new URL(String(rawUrl));
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
if (["about:", "data:"].includes(parsed.protocol)) return true;
|
|
381
|
+
if (parsed.protocol === "blob:") {
|
|
382
|
+
return (
|
|
383
|
+
target.networkPolicy.mode === "allowlist" &&
|
|
384
|
+
target.networkPolicy.allowedOrigins.includes(parsed.origin)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return false;
|
|
388
|
+
return (
|
|
389
|
+
target.networkPolicy.mode === "allowlist" &&
|
|
390
|
+
target.networkPolicy.allowedOrigins.includes(parsed.origin)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function navigationAllowedByRecordedSkillPolicy(rawUrl, target) {
|
|
395
|
+
let parsed;
|
|
396
|
+
try {
|
|
397
|
+
parsed = new URL(String(rawUrl));
|
|
398
|
+
} catch {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return (
|
|
402
|
+
["http:", "https:"].includes(parsed.protocol) &&
|
|
403
|
+
target.networkPolicy.mode === "allowlist" &&
|
|
404
|
+
target.networkPolicy.allowedOrigins.includes(parsed.origin)
|
|
405
|
+
);
|
|
406
|
+
}
|