@xopcai/xopc 0.0.161 → 0.0.162
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-o7GRljfD.js → activity-store-hpcNv9MZ.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-9R3szLMc.js → agent-avatar-dicebear-Zf9UQEmQ.js} +1 -1
- package/dist/gateway/static/root/assets/agents-DfVkZlHc.js +2 -0
- package/dist/gateway/static/root/assets/{api-jSTqFMq9.js → api-Dnatri09.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-D81cyE76.js → app-management-settings-panel-BBTR9wJP.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Ct6bZQCV.js → appearance-settings-C2zpLCHD.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-BMtAa4Ra.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-CfD7AZKn.js → archive-plugin-9eR_ODY1.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CP_Gaxt3.js → attachment-load-DPuWYUeX.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-BGKM_NDZ.js → automation-suggestion-card-Dw49OJRl.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-BiSAMYTr.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-IPr2EVnS.js → binary-plugins-BdkJpDFm.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-B6aHEGA6.js → block-editor-CAi7SXYU.js} +47 -47
- package/dist/gateway/static/root/assets/browser-settings-page-DHmaEJS2.js +8 -0
- package/dist/gateway/static/root/assets/browser-workflow-inputs-Dx6wkGz1.js +1 -0
- package/dist/gateway/static/root/assets/browser-workflows-page-Skertc-A.js +3 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-B5wSgf4f.js +2 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-CCX9-LjU.js → capability-presets-api-CNFl7dM3.js} +1 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DyvFZwBj.js +2 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-MmLMLDWf.js → channel-recipient-api-DYQyGDw4.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BBKEqDZn.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-Drk110ve.js +1 -0
- package/dist/gateway/static/root/assets/{connector-logo-BbjzIs87.js → connector-logo-Bng6kxYF.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-HpgaUDQ1.js +2 -0
- package/dist/gateway/static/root/assets/desktop-pet-Bpf6oegd.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DQLqGLc0.js → desktop-pet-settings-BVUyefSd.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-ClsiXhmz.js → directory-picker-path-field-DjSZAPz_.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-CjMPPdzg.js → dreaming-settings-CAZk2AM4.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-57eizjhB.js → extension-debug-page-C2F6vTTd.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CsbSb3u1.js → extension-page-AnIt4bn4.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DKA9-lyP.js → extension-settings-page-CLSc6682.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-DIPJuzBZ.js → gateway-config-api-DLFyZv33.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-CDLqAQY7.js → gateway-settings-DnQAA-qq.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DrfSJtvT.js → gateway-startup-retry-DbzLyJ3R.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-DrnKFC6M.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-Cw1hpJkZ.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api-lGC10UkU.js → goals-config-api-REmr3QCr.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BDGkTVma.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-BWq0pdrA.js → goals-settings-Dz0F3uRO.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-D4o8BJVv.js → heartbeat-settings-CQFY5gYs.js} +1 -1
- package/dist/gateway/static/root/assets/index-C8JkTS6d.js +199 -0
- package/dist/gateway/static/root/assets/index-hmrG55qg.css +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-6T2QYehS.js → keyboard-shortcuts-settings-BnOQXU5k.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-Bdef1b7F.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-DCjg2K9f.js → local-apps-page-KsOtWOyM.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-Dar-xqQr.js → locale-store-0lpYdNDr.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-CyYCBqym.js → logs-page-CwLPgrWh.js} +3 -3
- package/dist/gateway/static/root/assets/{markdown-split-rt1XO43g.js → markdown-split-D5GRg10r.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-Bwo8CJuh.js → media-plugins-C1ryULGs.js} +1 -1
- package/dist/gateway/static/root/assets/messages-BwQ3WssU.js +5 -0
- package/dist/gateway/static/root/assets/{notes-page-Dp-pkX7d.js → notes-page-CIeZ1_DT.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-Cf06q19x.js +4 -0
- package/dist/gateway/static/root/assets/{page-tabs-CzZ4iKOb.js → page-tabs-Caj1-E_X.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-3E_Qa-58.js → preview-open-alternatives-DKwEce-9.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-B2T48Sbt.js → product-open-page-B90ZskkN.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-SHTurM4t.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-BcC2qUAw.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-6vGefBHv.js +1 -0
- package/dist/gateway/static/root/assets/save-bar-controls-BDYt53uQ.js +1 -0
- package/dist/gateway/static/root/assets/{schedule-field-TkqUL3wm.js → schedule-field-Bcdni4Pj.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-DC-iU5IG.js → schema-form-D7oCARF7.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-C5014a53.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CDY3ZI1K.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-Dpqth3oA.js → settings-collapsible-section-6eNF8Jey.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-CNqF6IwY.js → settings-form-section-CARTHCxH.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-DkvwitK_.js → settings-loading-skeleton-DEwYqESe.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-B6vVdHeQ.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-DWvNgISx.js → settings-page-layout-D3srt5Bi.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-z-rF0Hdg.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DGpSPW-n.js → share-preview-page-Ck9SxiXw.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-CHZFXE_d.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-B7p2QNXt.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-ncw3tX9P.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-8TxxSbUw.js → system-settings-panel-DLBUQSk2.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-B5FzMWOx.js → theme-store-C1oaqeD2.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-DEGtSA5h.js → typed-models-lib-Divbg_C4.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-BDU6FneG.js → use-channel-catalog-CP9a_rAf.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-iYfeSLKI.js +1 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-CrwkicvT.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-page-BQWxyPg8.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-50ZzGay5.js +1 -0
- package/dist/gateway/static/root/assets/work-page-DPqEahx7.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Cwi8Pz7n.js → workflow-run-setup-panel-DbDPYQwB.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-C3eIC5HZ.js +11 -0
- package/dist/gateway/static/root/index.html +7 -7
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +1 -0
- package/dist/src/agent/agent-manager.js +1 -0
- package/dist/src/agent/client-error-format.js +3 -3
- package/dist/src/agent/outcomes/task-evaluation.d.ts +20 -0
- package/dist/src/agent/outcomes/task-evaluation.js +48 -0
- package/dist/src/agent/outcomes/task-verifier.d.ts +29 -0
- package/dist/src/agent/outcomes/task-verifier.js +71 -0
- package/dist/src/agent/prompt/memory/index.js +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +32 -1
- package/dist/src/agent/service.js +1 -0
- package/dist/src/agent/service.types.d.ts +1 -0
- package/dist/src/agent/tools/browser/tool/browser-use-tool.d.ts +1 -7
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js +2 -73
- package/dist/src/agent/tools/browser/tool/schemas.d.ts +1 -13
- package/dist/src/agent/tools/browser/tool/schemas.js +1 -9
- package/dist/src/agent/tools/browser-recipe-tool.d.ts +12 -0
- package/dist/src/agent/tools/browser-recipe-tool.js +113 -0
- package/dist/src/agent/tools/factory.d.ts +2 -0
- package/dist/src/agent/tools/factory.js +2 -0
- package/dist/src/agent/tools/index.d.ts +1 -0
- package/dist/src/agent/tools/index.js +2 -1
- package/dist/src/automations/domain/types.d.ts +13 -0
- package/dist/src/automations/domain/validation.d.ts +44 -24
- package/dist/src/automations/domain/validation.js +27 -18
- package/dist/src/automations/draft/automation-draft-validator.d.ts +6 -1
- package/dist/src/automations/draft/automation-draft-validator.js +1 -0
- package/dist/src/automations/service/action-executor.d.ts +1 -0
- package/dist/src/automations/service/action-executor.js +22 -0
- package/dist/src/browser/index.d.ts +0 -1
- package/dist/src/browser/index.js +1 -2
- package/dist/src/browser/pipeline/runner.d.ts +9 -2
- package/dist/src/browser/pipeline/runner.js +151 -105
- package/dist/src/browser/pipeline/schema.d.ts +21 -15
- package/dist/src/browser/pipeline/schema.js +93 -99
- package/dist/src/browser/recipes/index.d.ts +3 -0
- package/dist/src/browser/recipes/index.js +3 -0
- package/dist/src/browser/recipes/repository.d.ts +13 -0
- package/dist/src/browser/recipes/repository.js +81 -0
- package/dist/src/browser/recipes/runtime-service.d.ts +6 -0
- package/dist/src/browser/recipes/runtime-service.js +30 -0
- package/dist/src/browser/recipes/service.d.ts +36 -0
- package/dist/src/browser/recipes/service.js +186 -0
- package/dist/src/browser/recipes/types.d.ts +36 -0
- package/dist/src/browser/recipes/types.js +1 -0
- package/dist/src/cli/commands/browser-cli-helpers.js +6 -12
- package/dist/src/cli/commands/browser.js +4 -3
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/schema.d.ts +3 -3
- package/dist/src/config/schema.js +21 -21
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
- package/dist/src/gateway/hono/routes/browser-workflows.d.ts +3 -0
- package/dist/src/gateway/hono/routes/browser-workflows.js +78 -0
- package/dist/src/gateway/hono/routes/home.js +1 -0
- package/dist/src/gateway/hono/routes/index.js +4 -0
- package/dist/src/gateway/hono/routes/interaction-state.d.ts +3 -0
- package/dist/src/gateway/hono/routes/interaction-state.js +45 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/memory.js +33 -4
- package/dist/src/gateway/hono/routes/task-outcomes.d.ts +3 -0
- package/dist/src/gateway/hono/routes/task-outcomes.js +109 -0
- package/dist/src/gateway/hono/routes/you.d.ts +10 -2
- package/dist/src/gateway/hono/routes/you.js +184 -28
- package/dist/src/gateway/service/run-gateway-agent.js +84 -0
- package/dist/src/gateway/service.d.ts +3 -0
- package/dist/src/gateway/service.js +14 -0
- package/dist/src/proactive/insight-repository.d.ts +2 -0
- package/dist/src/proactive/insight-repository.js +5 -2
- package/dist/src/proactive/run-processor.js +22 -1
- package/dist/src/proactive/types.d.ts +2 -0
- package/dist/src/proactive/value-score.d.ts +13 -0
- package/dist/src/proactive/value-score.js +30 -0
- package/dist/src/share/share-types.js +5 -5
- package/dist/src/share/site-share-types.d.ts +1 -1
- package/dist/src/share/site-share-types.js +5 -5
- package/dist/src/storage/sqlite/index.d.ts +3 -0
- package/dist/src/storage/sqlite/index.js +4 -1
- package/dist/src/storage/sqlite/interaction-state-repository.d.ts +17 -0
- package/dist/src/storage/sqlite/interaction-state-repository.js +60 -0
- package/dist/src/storage/sqlite/memory-records-repository.js +1 -1
- package/dist/src/storage/sqlite/migrations/053_browser_recipes.sql +45 -0
- package/dist/src/storage/sqlite/migrations/054_task_outcomes.sql +24 -0
- package/dist/src/storage/sqlite/migrations/055_relationship_settings.sql +14 -0
- package/dist/src/storage/sqlite/migrations/056_task_verification.sql +6 -0
- package/dist/src/storage/sqlite/migrations/057_interaction_states.sql +11 -0
- package/dist/src/storage/sqlite/migrations/058_proactive_value.sql +3 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/relationship-settings-repository.d.ts +17 -0
- package/dist/src/storage/sqlite/relationship-settings-repository.js +64 -0
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/schema.sql +46 -0
- package/dist/src/storage/sqlite/task-outcome-repository.d.ts +90 -0
- package/dist/src/storage/sqlite/task-outcome-repository.js +138 -0
- package/dist/src/user-context/interaction-state.d.ts +13 -0
- package/dist/src/user-context/interaction-state.js +69 -0
- package/dist/src/user-context/memory-use-audit.d.ts +10 -0
- package/dist/src/user-context/memory-use-audit.js +25 -0
- package/dist/src/user-context/personal-playbook.d.ts +11 -0
- package/dist/src/user-context/personal-playbook.js +41 -0
- package/dist/src/user-context/projection.d.ts +4 -0
- package/dist/src/user-context/projection.js +5 -1
- package/dist/src/user-context/relationship-continuity.d.ts +14 -0
- package/dist/src/user-context/relationship-continuity.js +51 -0
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/action-boundary-settings-panel-BIVBUiNT.js +0 -1
- package/dist/gateway/static/root/assets/agents-_qbUaYbs.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-0sPqD0FK.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-BxWBuBsE.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-C499b9AH.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-Dva9SiPK.js +0 -2
- package/dist/gateway/static/root/assets/channels-settings-DNAU7u9o.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-CIOH7tgn.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-TwiBp8vY.js +0 -2
- package/dist/gateway/static/root/assets/desktop-pet-CYH5ZSXy.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-Q3B0E-0s.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-C5VAK04G.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-BJb-5IYw.js +0 -2
- package/dist/gateway/static/root/assets/index-CkUHH170.css +0 -1
- package/dist/gateway/static/root/assets/index-vq_cJZJC.js +0 -199
- package/dist/gateway/static/root/assets/local-app-workbench-page-euH17Kvb.js +0 -2
- package/dist/gateway/static/root/assets/messages-CxySDn9d.js +0 -5
- package/dist/gateway/static/root/assets/models-hub-panel-DObxxjc9.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-CCb4RXcZ.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-BvEcriRC.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-B_e79MRJ.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DGcqnYfR.js +0 -1
- package/dist/gateway/static/root/assets/save-bar-controls-CkeKasGm.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DQZhrDJ8.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BkdeN99Y.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-qPG7I2zd.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-lwRAh9aw.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-BGj9IVUr.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-B2UBAs5U.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-DdjzFZ8I.js +0 -2
- package/dist/gateway/static/root/assets/user-context-page-BTTZpAGV.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DuDQd-WU.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-page-BUVkVCsW.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DeEPPu4n.js +0 -1
- package/dist/gateway/static/root/assets/work-page-DQNUyTUq.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-BIy4wrzO.js +0 -11
- package/dist/src/browser/pipeline/source.d.ts +0 -9
- package/dist/src/browser/pipeline/source.js +0 -77
|
@@ -2,7 +2,6 @@ import { createLogger } from "../../utils/logger/index.js";
|
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { isTruthyValue, resolveTemplateDeep } from "./template.js";
|
|
4
4
|
import { parseBrowserPipeline } from "./schema.js";
|
|
5
|
-
import { loadBrowserPipelineSource, resolvePipelineIncludeLocation } from "./source.js";
|
|
6
5
|
import { addStepTrace, createPipelineOutput, formatPipelineResult } from "./output.js";
|
|
7
6
|
import { setTimeout } from "node:timers/promises";
|
|
8
7
|
//#region src/browser/pipeline/runner.ts
|
|
@@ -17,6 +16,29 @@ const CONTROL_ACTIONS = new Set([
|
|
|
17
16
|
"sleep",
|
|
18
17
|
"set_var"
|
|
19
18
|
]);
|
|
19
|
+
const RECIPE_ACTIONS = new Set([
|
|
20
|
+
...CONTROL_ACTIONS,
|
|
21
|
+
"navigate",
|
|
22
|
+
"snapshot",
|
|
23
|
+
"click",
|
|
24
|
+
"type",
|
|
25
|
+
"scroll",
|
|
26
|
+
"screenshot",
|
|
27
|
+
"press",
|
|
28
|
+
"evaluate",
|
|
29
|
+
"wait",
|
|
30
|
+
"wait_for_navigation",
|
|
31
|
+
"wait_for_network_idle",
|
|
32
|
+
"element_text",
|
|
33
|
+
"element_attribute",
|
|
34
|
+
"output",
|
|
35
|
+
"assert",
|
|
36
|
+
"select",
|
|
37
|
+
"map",
|
|
38
|
+
"filter",
|
|
39
|
+
"sort",
|
|
40
|
+
"limit"
|
|
41
|
+
]);
|
|
20
42
|
function validateBrowserPipeline(yamlSource, registry) {
|
|
21
43
|
const parseResult = parseBrowserPipeline(yamlSource);
|
|
22
44
|
if (!parseResult.ok || !parseResult.document) return {
|
|
@@ -36,85 +58,56 @@ function validateBrowserPipeline(yamlSource, registry) {
|
|
|
36
58
|
errors: []
|
|
37
59
|
};
|
|
38
60
|
}
|
|
39
|
-
async function validateBrowserPipelineSource(yamlSource, registry, sourceLocation) {
|
|
40
|
-
try {
|
|
41
|
-
const doc = await expandPipelineDocument(yamlSource, sourceLocation);
|
|
42
|
-
const errors = [];
|
|
43
|
-
validateSteps(doc.pipeline, "pipeline", registry, errors);
|
|
44
|
-
validateSteps(doc.onError ?? [], "on_error", registry, errors);
|
|
45
|
-
return errors.length > 0 ? {
|
|
46
|
-
ok: false,
|
|
47
|
-
document: doc,
|
|
48
|
-
errors
|
|
49
|
-
} : {
|
|
50
|
-
ok: true,
|
|
51
|
-
document: doc,
|
|
52
|
-
errors: []
|
|
53
|
-
};
|
|
54
|
-
} catch (e) {
|
|
55
|
-
return {
|
|
56
|
-
ok: false,
|
|
57
|
-
errors: [{
|
|
58
|
-
path: "",
|
|
59
|
-
message: e instanceof Error ? e.message : String(e)
|
|
60
|
-
}]
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
61
|
function validateSteps(steps, path, registry, errors) {
|
|
65
62
|
if (!registry) return;
|
|
66
63
|
for (let i = 0; i < steps.length; i++) {
|
|
67
64
|
const step = steps[i];
|
|
68
|
-
if (!
|
|
65
|
+
if (!RECIPE_ACTIONS.has(step.action)) errors.push({
|
|
66
|
+
path: `${path}[${i}]`,
|
|
67
|
+
message: `Action is not available in Browser Recipe v1: "${step.action}"`
|
|
68
|
+
});
|
|
69
|
+
else if (!CONTROL_ACTIONS.has(step.action) && !registry.has(step.action)) errors.push({
|
|
69
70
|
path: `${path}[${i}]`,
|
|
70
71
|
message: `Unknown action: "${step.action}"`
|
|
71
72
|
});
|
|
73
|
+
const requiredKey = {
|
|
74
|
+
navigate: "url",
|
|
75
|
+
type: "text",
|
|
76
|
+
press: "key",
|
|
77
|
+
evaluate: "javascript",
|
|
78
|
+
element_text: "selector",
|
|
79
|
+
element_attribute: "selector",
|
|
80
|
+
set_var: "name"
|
|
81
|
+
}[step.action];
|
|
82
|
+
if (requiredKey && !(requiredKey in step.args)) errors.push({
|
|
83
|
+
path: `${path}[${i}].${step.action}.${requiredKey}`,
|
|
84
|
+
message: `Required field is missing: ${requiredKey}`
|
|
85
|
+
});
|
|
86
|
+
if (step.action === "click" && ![
|
|
87
|
+
"selector",
|
|
88
|
+
"text",
|
|
89
|
+
"role"
|
|
90
|
+
].some((key) => key in step.args)) errors.push({
|
|
91
|
+
path: `${path}[${i}].click`,
|
|
92
|
+
message: "One of selector, text, or role is required"
|
|
93
|
+
});
|
|
94
|
+
if (step.action === "type" && !["selector", "label"].some((key) => key in step.args)) errors.push({
|
|
95
|
+
path: `${path}[${i}].type`,
|
|
96
|
+
message: "One of selector or label is required"
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
if (step.action === "if") {
|
|
100
|
+
validateSteps(normalizeNestedPipeline(step.args.then, `${path}[${i}].if.then`), `${path}[${i}].if.then`, registry, errors);
|
|
101
|
+
validateSteps(normalizeNestedPipeline(step.args.else, `${path}[${i}].if.else`), `${path}[${i}].if.else`, registry, errors);
|
|
102
|
+
} else if (step.action === "retry") validateSteps(normalizeNestedPipeline(step.args.pipeline, `${path}[${i}].retry.pipeline`), `${path}[${i}].retry.pipeline`, registry, errors);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
errors.push({
|
|
105
|
+
path: `${path}[${i}].${step.action}`,
|
|
106
|
+
message: error instanceof Error ? error.message : String(error)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
72
109
|
}
|
|
73
110
|
}
|
|
74
|
-
async function expandPipelineDocument(yamlSource, sourceLocation, visiting = /* @__PURE__ */ new Set()) {
|
|
75
|
-
const parsed = parseBrowserPipeline(yamlSource);
|
|
76
|
-
if (!parsed.ok || !parsed.document) {
|
|
77
|
-
const message = parsed.errors.map((e) => `${e.path}: ${e.message}`).join("\n");
|
|
78
|
-
throw new Error(message);
|
|
79
|
-
}
|
|
80
|
-
const doc = parsed.document;
|
|
81
|
-
const key = sourceLocation ?? `inline:${doc.name}`;
|
|
82
|
-
if (visiting.has(key)) throw new Error(`Circular pipeline include detected: ${key}`);
|
|
83
|
-
visiting.add(key);
|
|
84
|
-
let merged = {
|
|
85
|
-
name: doc.name,
|
|
86
|
-
description: doc.description,
|
|
87
|
-
provider: doc.provider,
|
|
88
|
-
include: [],
|
|
89
|
-
timeoutSeconds: doc.timeoutSeconds,
|
|
90
|
-
args: {},
|
|
91
|
-
pipeline: [],
|
|
92
|
-
onError: []
|
|
93
|
-
};
|
|
94
|
-
for (const includePath of doc.include ?? []) {
|
|
95
|
-
const includeSource = await loadBrowserPipelineSource(resolvePipelineIncludeLocation(includePath, sourceLocation));
|
|
96
|
-
const includeDoc = await expandPipelineDocument(includeSource.source, includeSource.location, visiting);
|
|
97
|
-
merged = mergePipelineDocuments(merged, includeDoc);
|
|
98
|
-
}
|
|
99
|
-
merged = mergePipelineDocuments(merged, doc);
|
|
100
|
-
visiting.delete(key);
|
|
101
|
-
return merged;
|
|
102
|
-
}
|
|
103
|
-
function mergePipelineDocuments(target, source) {
|
|
104
|
-
return {
|
|
105
|
-
name: source.name || target.name,
|
|
106
|
-
description: source.description ?? target.description,
|
|
107
|
-
provider: source.provider ?? target.provider,
|
|
108
|
-
include: [...target.include ?? [], ...source.include ?? []],
|
|
109
|
-
timeoutSeconds: source.timeoutSeconds ?? target.timeoutSeconds,
|
|
110
|
-
args: {
|
|
111
|
-
...target.args,
|
|
112
|
-
...source.args
|
|
113
|
-
},
|
|
114
|
-
pipeline: [...target.pipeline, ...source.pipeline],
|
|
115
|
-
onError: [...target.onError ?? [], ...source.onError ?? []]
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
111
|
function resolveArgs(doc, overrides) {
|
|
119
112
|
const resolved = {};
|
|
120
113
|
for (const key of Object.keys(overrides)) if (!(key in doc.args)) throw new Error(`Unknown pipeline arg: ${key}`);
|
|
@@ -125,6 +118,10 @@ function resolveArgs(doc, overrides) {
|
|
|
125
118
|
continue;
|
|
126
119
|
}
|
|
127
120
|
if (def.choices && !def.choices.some((choice) => Object.is(choice, value))) throw new Error(`Invalid value for arg ${key}: expected one of ${def.choices.map(String).join(", ")}`);
|
|
121
|
+
if (def.type === "string" && typeof value !== "string") throw new Error(`Arg ${key} must be a string`);
|
|
122
|
+
if (def.type === "number" && (typeof value !== "number" || !Number.isFinite(value))) throw new Error(`Arg ${key} must be a number`);
|
|
123
|
+
if (def.type === "integer" && (typeof value !== "number" || !Number.isInteger(value))) throw new Error(`Arg ${key} must be an integer`);
|
|
124
|
+
if (def.type === "boolean" && typeof value !== "boolean") throw new Error(`Arg ${key} must be a boolean`);
|
|
128
125
|
resolved[key] = value;
|
|
129
126
|
}
|
|
130
127
|
return resolved;
|
|
@@ -139,24 +136,25 @@ function templateContext(runtime) {
|
|
|
139
136
|
};
|
|
140
137
|
}
|
|
141
138
|
function actionArgs(step, runtime) {
|
|
142
|
-
if (typeof step.args === "string") {
|
|
143
|
-
const resolved = resolveTemplateDeep(step.args, templateContext(runtime));
|
|
144
|
-
if (step.action === "evaluate" || step.action === "eval" || step.action === "console") return { javascript: String(resolved ?? "") };
|
|
145
|
-
return { value: resolved };
|
|
146
|
-
}
|
|
147
139
|
return resolveTemplateDeep(step.args, templateContext(runtime));
|
|
148
140
|
}
|
|
149
|
-
async function executePipeline(steps, scope, runtime, actionCtx, registry, output) {
|
|
141
|
+
async function executePipeline(steps, scope, runtime, actionCtx, registry, output, options) {
|
|
150
142
|
for (let i = 0; i < steps.length; i++) {
|
|
151
143
|
if (actionCtx.signal?.aborted) throw actionError("ABORTED", "Pipeline aborted");
|
|
152
|
-
await executeStep(steps[i], i, scope, runtime, actionCtx, registry, output);
|
|
144
|
+
await executeStep(steps[i], i, scope, runtime, actionCtx, registry, output, options);
|
|
153
145
|
}
|
|
154
146
|
}
|
|
155
|
-
async function executeStep(step, index, scope, runtime, actionCtx, registry, output) {
|
|
147
|
+
async function executeStep(step, index, scope, runtime, actionCtx, registry, output, options) {
|
|
156
148
|
const started = Date.now();
|
|
149
|
+
options.onStep?.({
|
|
150
|
+
index,
|
|
151
|
+
scope,
|
|
152
|
+
action: step.action,
|
|
153
|
+
status: "started"
|
|
154
|
+
});
|
|
157
155
|
let result;
|
|
158
156
|
try {
|
|
159
|
-
result = await executeStepOnce(step, index, scope, runtime, actionCtx, registry, output);
|
|
157
|
+
result = await executeStepOnce(step, index, scope, runtime, actionCtx, registry, output, options);
|
|
160
158
|
} catch (e) {
|
|
161
159
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
162
160
|
result = {
|
|
@@ -178,16 +176,24 @@ async function executeStep(step, index, scope, runtime, actionCtx, registry, out
|
|
|
178
176
|
data: result.data,
|
|
179
177
|
error: result.error
|
|
180
178
|
}, result);
|
|
179
|
+
options.onStep?.({
|
|
180
|
+
index,
|
|
181
|
+
scope,
|
|
182
|
+
action: step.action,
|
|
183
|
+
status: result.ok ? "completed" : "failed",
|
|
184
|
+
elapsedMs: Date.now() - started,
|
|
185
|
+
error: result.error?.message
|
|
186
|
+
});
|
|
181
187
|
if (!result.ok) {
|
|
182
188
|
runtime.error = result.error;
|
|
183
189
|
throw actionError(result.error?.code ?? "STEP_FAILED", result.error?.message ?? "Step failed");
|
|
184
190
|
}
|
|
185
191
|
}
|
|
186
|
-
async function executeStepOnce(step, index, scope, runtime, actionCtx, registry, output) {
|
|
192
|
+
async function executeStepOnce(step, index, scope, runtime, actionCtx, registry, output, options) {
|
|
187
193
|
switch (step.action) {
|
|
188
194
|
case "sleep": {
|
|
189
195
|
const args = actionArgs(step, runtime);
|
|
190
|
-
const ms = Number(args.ms ??
|
|
196
|
+
const ms = Number(args.ms ?? 1e3);
|
|
191
197
|
await setTimeout(Number.isFinite(ms) ? ms : 1e3, void 0, { signal: actionCtx.signal });
|
|
192
198
|
runtime.last = null;
|
|
193
199
|
return {
|
|
@@ -219,7 +225,7 @@ async function executeStepOnce(step, index, scope, runtime, actionCtx, registry,
|
|
|
219
225
|
}
|
|
220
226
|
case "if": {
|
|
221
227
|
const args = actionArgs(step, runtime);
|
|
222
|
-
await executePipeline(normalizeNestedPipeline(isTruthyValue(args.condition) ? args.then : args.else, `${scope}.${index}.if`), `${scope}.${index}.if`, runtime, actionCtx, registry, output);
|
|
228
|
+
await executePipeline(normalizeNestedPipeline(isTruthyValue(args.condition) ? args.then : args.else, `${scope}.${index}.if`), `${scope}.${index}.if`, runtime, actionCtx, registry, output, options);
|
|
223
229
|
return {
|
|
224
230
|
ok: true,
|
|
225
231
|
action: "if",
|
|
@@ -230,11 +236,11 @@ async function executeStepOnce(step, index, scope, runtime, actionCtx, registry,
|
|
|
230
236
|
case "retry": {
|
|
231
237
|
const args = actionArgs(step, runtime);
|
|
232
238
|
const times = Math.max(1, Number(args.times ?? 3));
|
|
233
|
-
const delayMs = Math.max(0, Number(args.delay_ms ??
|
|
234
|
-
const retrySteps = normalizeNestedPipeline(args.pipeline
|
|
239
|
+
const delayMs = Math.max(0, Number(args.delay_ms ?? 250));
|
|
240
|
+
const retrySteps = normalizeNestedPipeline(args.pipeline, `${scope}.${index}.retry`);
|
|
235
241
|
let lastError;
|
|
236
242
|
for (let attempt = 1; attempt <= times; attempt++) try {
|
|
237
|
-
await executePipeline(retrySteps, `${scope}.${index}.retry.${attempt}`, runtime, actionCtx, registry, output);
|
|
243
|
+
await executePipeline(retrySteps, `${scope}.${index}.retry.${attempt}`, runtime, actionCtx, registry, output, options);
|
|
238
244
|
output.ok = true;
|
|
239
245
|
output.error = void 0;
|
|
240
246
|
return {
|
|
@@ -263,6 +269,29 @@ async function executeStepOnce(step, index, scope, runtime, actionCtx, registry,
|
|
|
263
269
|
}
|
|
264
270
|
async function executeBrowserAction(step, runtime, actionCtx, registry) {
|
|
265
271
|
const args = actionArgs(step, runtime);
|
|
272
|
+
if (step.action === "navigate") {
|
|
273
|
+
let hostname;
|
|
274
|
+
try {
|
|
275
|
+
hostname = new URL(String(args.url)).hostname.toLowerCase();
|
|
276
|
+
} catch {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
action: step.action,
|
|
280
|
+
error: {
|
|
281
|
+
code: "INVALID_URL",
|
|
282
|
+
message: "navigate.url must be an absolute URL"
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (!runtime.doc.domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) return {
|
|
287
|
+
ok: false,
|
|
288
|
+
action: step.action,
|
|
289
|
+
error: {
|
|
290
|
+
code: "DOMAIN_BLOCKED",
|
|
291
|
+
message: `Domain is not declared by the recipe: ${hostname}`
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
266
295
|
actionCtx.pipeline = {
|
|
267
296
|
args: runtime.args,
|
|
268
297
|
last: runtime.last,
|
|
@@ -271,6 +300,20 @@ async function executeBrowserAction(step, runtime, actionCtx, registry) {
|
|
|
271
300
|
error: runtime.error
|
|
272
301
|
};
|
|
273
302
|
const result = await registry.execute(step.action, actionCtx, args);
|
|
303
|
+
if (result.ok && step.action === "navigate") {
|
|
304
|
+
const finalUrl = result.data?.url;
|
|
305
|
+
if (typeof finalUrl === "string") {
|
|
306
|
+
const hostname = new URL(finalUrl).hostname.toLowerCase();
|
|
307
|
+
if (!runtime.doc.domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))) return {
|
|
308
|
+
ok: false,
|
|
309
|
+
action: step.action,
|
|
310
|
+
error: {
|
|
311
|
+
code: "DOMAIN_BLOCKED",
|
|
312
|
+
message: `Redirected to an undeclared domain: ${hostname}`
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
274
317
|
if (result.ok) {
|
|
275
318
|
if (step.action === "output") runtime.outputs.push(result.data);
|
|
276
319
|
if (result.data !== void 0) runtime.last = result.data;
|
|
@@ -285,22 +328,11 @@ function normalizeNestedPipeline(value, path) {
|
|
|
285
328
|
if (keys.length !== 1) throw new Error(`${path}[${index}] must contain exactly one action`);
|
|
286
329
|
const action = keys[0];
|
|
287
330
|
const args = obj[action];
|
|
288
|
-
if (typeof args === "string") return {
|
|
289
|
-
action,
|
|
290
|
-
args
|
|
291
|
-
};
|
|
292
331
|
if (args && typeof args === "object" && !Array.isArray(args)) return {
|
|
293
332
|
action,
|
|
294
333
|
args
|
|
295
334
|
};
|
|
296
|
-
|
|
297
|
-
action,
|
|
298
|
-
args: {}
|
|
299
|
-
};
|
|
300
|
-
return {
|
|
301
|
-
action,
|
|
302
|
-
args: { value: args }
|
|
303
|
-
};
|
|
335
|
+
throw new Error(`${path}[${index}].${action} must be an object`);
|
|
304
336
|
});
|
|
305
337
|
}
|
|
306
338
|
function actionError(code, message) {
|
|
@@ -311,7 +343,9 @@ function actionError(code, message) {
|
|
|
311
343
|
async function runBrowserPipeline(yamlSource, argOverrides, ctx, registry, options = {}) {
|
|
312
344
|
let doc;
|
|
313
345
|
try {
|
|
314
|
-
|
|
346
|
+
const parsed = parseBrowserPipeline(yamlSource);
|
|
347
|
+
if (!parsed.ok || !parsed.document) throw new Error(parsed.errors.map((error) => `${error.path}: ${error.message}`).join("\n"));
|
|
348
|
+
doc = parsed.document;
|
|
315
349
|
} catch (e) {
|
|
316
350
|
const msg = e instanceof Error ? e.message : String(e);
|
|
317
351
|
return {
|
|
@@ -377,18 +411,30 @@ async function runBrowserPipeline(yamlSource, argOverrides, ctx, registry, optio
|
|
|
377
411
|
steps: doc.pipeline.length
|
|
378
412
|
}, `Running pipeline: ${doc.name}`);
|
|
379
413
|
try {
|
|
380
|
-
const
|
|
414
|
+
const timeoutController = new AbortController();
|
|
415
|
+
const runContext = {
|
|
416
|
+
...ctx,
|
|
417
|
+
signal: ctx.signal ? AbortSignal.any([ctx.signal, timeoutController.signal]) : timeoutController.signal
|
|
418
|
+
};
|
|
419
|
+
const run = executePipeline(doc.pipeline, "pipeline", runtime, runContext, registry, output, options);
|
|
381
420
|
if (doc.timeoutSeconds && doc.timeoutSeconds > 0) await Promise.race([run, setTimeout(doc.timeoutSeconds * 1e3, void 0, { signal: ctx.signal }).then(() => {
|
|
421
|
+
timeoutController.abort();
|
|
382
422
|
throw actionError("PIPELINE_TIMEOUT", `Pipeline timed out after ${doc.timeoutSeconds} seconds`);
|
|
383
423
|
})]);
|
|
384
424
|
else await run;
|
|
385
|
-
} catch {
|
|
425
|
+
} catch (error) {
|
|
426
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
427
|
+
const code = failure.code ?? "PIPELINE_FAILED";
|
|
428
|
+
runtime.error ??= {
|
|
429
|
+
code,
|
|
430
|
+
message: failure.message
|
|
431
|
+
};
|
|
386
432
|
output.ok = false;
|
|
387
433
|
output.error ??= {
|
|
388
434
|
step: output.trace.length,
|
|
389
435
|
action: "pipeline",
|
|
390
|
-
code
|
|
391
|
-
message:
|
|
436
|
+
code,
|
|
437
|
+
message: failure.message
|
|
392
438
|
};
|
|
393
439
|
}
|
|
394
440
|
output.last = runtime.last;
|
|
@@ -396,7 +442,7 @@ async function runBrowserPipeline(yamlSource, argOverrides, ctx, registry, optio
|
|
|
396
442
|
if (!output.ok && doc.onError && doc.onError.length > 0) {
|
|
397
443
|
log.info({ pipeline: doc.name }, "Running on_error diagnostics");
|
|
398
444
|
try {
|
|
399
|
-
await executePipeline(doc.onError, "on_error", runtime, ctx, registry, output);
|
|
445
|
+
await executePipeline(doc.onError, "on_error", runtime, ctx, registry, output, options);
|
|
400
446
|
} catch {}
|
|
401
447
|
output.last = runtime.last;
|
|
402
448
|
output.outputs = runtime.outputs;
|
|
@@ -404,4 +450,4 @@ async function runBrowserPipeline(yamlSource, argOverrides, ctx, registry, optio
|
|
|
404
450
|
return formatPipelineResult(output);
|
|
405
451
|
}
|
|
406
452
|
//#endregion
|
|
407
|
-
export { runBrowserPipeline, validateBrowserPipeline
|
|
453
|
+
export { runBrowserPipeline, validateBrowserPipeline };
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const BROWSER_RECIPE_API_VERSION: "xopc.ai/browser-recipe/v1";
|
|
3
|
+
declare const PipelineArgSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<{
|
|
5
|
+
string: "string";
|
|
6
|
+
number: "number";
|
|
7
|
+
boolean: "boolean";
|
|
8
|
+
integer: "integer";
|
|
9
|
+
}>;
|
|
10
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
default: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
12
|
+
description: z.ZodOptional<z.ZodString>;
|
|
13
|
+
choices: z.ZodOptional<z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
export type PipelineArgDef = z.infer<typeof PipelineArgSchema>;
|
|
11
16
|
export interface PipelineStep {
|
|
12
|
-
/** The single action name (key of the step object). */
|
|
13
17
|
action: string;
|
|
14
|
-
|
|
15
|
-
args: Record<string, unknown> | string;
|
|
18
|
+
args: Record<string, unknown>;
|
|
16
19
|
}
|
|
17
20
|
export interface PipelineDocument {
|
|
21
|
+
apiVersion: typeof BROWSER_RECIPE_API_VERSION;
|
|
22
|
+
id: string;
|
|
18
23
|
name: string;
|
|
19
24
|
description?: string;
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
risk: 'read_only' | 'account_write' | 'sensitive';
|
|
26
|
+
domains: string[];
|
|
22
27
|
timeoutSeconds?: number;
|
|
23
28
|
args: Record<string, PipelineArgDef>;
|
|
24
29
|
pipeline: PipelineStep[];
|
|
@@ -34,3 +39,4 @@ export interface PipelineParseResult {
|
|
|
34
39
|
errors: PipelineValidationError[];
|
|
35
40
|
}
|
|
36
41
|
export declare function parseBrowserPipeline(yamlSource: string): PipelineParseResult;
|
|
42
|
+
export {};
|
|
@@ -1,122 +1,116 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import yaml from "js-yaml";
|
|
2
3
|
//#region src/browser/pipeline/schema.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const BROWSER_RECIPE_API_VERSION = "xopc.ai/browser-recipe/v1";
|
|
5
|
+
const IdSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100);
|
|
6
|
+
const JsonValueSchema = z.lazy(() => z.union([
|
|
7
|
+
z.string(),
|
|
8
|
+
z.number(),
|
|
9
|
+
z.boolean(),
|
|
10
|
+
z.null(),
|
|
11
|
+
z.array(JsonValueSchema),
|
|
12
|
+
z.record(z.string(), JsonValueSchema)
|
|
13
|
+
]));
|
|
14
|
+
const PipelineArgSchema = z.object({
|
|
15
|
+
type: z.enum([
|
|
16
|
+
"string",
|
|
17
|
+
"number",
|
|
18
|
+
"integer",
|
|
19
|
+
"boolean"
|
|
20
|
+
]),
|
|
21
|
+
required: z.boolean().optional(),
|
|
22
|
+
default: JsonValueSchema.optional(),
|
|
23
|
+
description: z.string().max(500).optional(),
|
|
24
|
+
choices: z.array(JsonValueSchema).optional()
|
|
25
|
+
}).strict().superRefine((definition, ctx) => {
|
|
26
|
+
const matchesType = (value) => {
|
|
27
|
+
if (definition.type === "string") return typeof value === "string";
|
|
28
|
+
if (definition.type === "boolean") return typeof value === "boolean";
|
|
29
|
+
if (definition.type === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
30
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
31
|
+
};
|
|
32
|
+
if (definition.default !== void 0 && !matchesType(definition.default)) ctx.addIssue({
|
|
33
|
+
code: "custom",
|
|
34
|
+
path: ["default"],
|
|
35
|
+
message: `Default must match arg type ${definition.type}.`
|
|
36
|
+
});
|
|
37
|
+
definition.choices?.forEach((choice, index) => {
|
|
38
|
+
if (!matchesType(choice)) ctx.addIssue({
|
|
39
|
+
code: "custom",
|
|
40
|
+
path: ["choices", index],
|
|
41
|
+
message: `Choice must match arg type ${definition.type}.`
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
const DomainSchema = z.string().trim().toLowerCase().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/, "Domain must be a hostname without scheme, path, port, or wildcard.");
|
|
46
|
+
const StepSchema = z.record(z.string(), z.record(z.string(), z.unknown())).superRefine((step, ctx) => {
|
|
47
|
+
if (Object.keys(step).length !== 1) ctx.addIssue({
|
|
48
|
+
code: "custom",
|
|
49
|
+
message: "Each step must contain exactly one action."
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
const RecipeSchema = z.object({
|
|
53
|
+
apiVersion: z.literal(BROWSER_RECIPE_API_VERSION),
|
|
54
|
+
id: IdSchema,
|
|
55
|
+
name: z.string().trim().min(1).max(200),
|
|
56
|
+
description: z.string().max(2e3).optional(),
|
|
57
|
+
risk: z.enum([
|
|
58
|
+
"read_only",
|
|
59
|
+
"account_write",
|
|
60
|
+
"sensitive"
|
|
61
|
+
]),
|
|
62
|
+
domains: z.array(DomainSchema).min(1),
|
|
63
|
+
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
64
|
+
args: z.record(z.string(), PipelineArgSchema).default({}),
|
|
65
|
+
pipeline: z.array(StepSchema).min(1),
|
|
66
|
+
on_error: z.array(StepSchema).optional()
|
|
67
|
+
}).strict();
|
|
68
|
+
function stepsFromRaw(steps) {
|
|
69
|
+
return steps.map((step) => {
|
|
70
|
+
const [action, args] = Object.entries(step)[0];
|
|
71
|
+
return {
|
|
72
|
+
action,
|
|
73
|
+
args
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
6
77
|
function parseBrowserPipeline(yamlSource) {
|
|
7
|
-
const errors = [];
|
|
8
78
|
let raw;
|
|
9
79
|
try {
|
|
10
80
|
raw = yaml.load(yamlSource);
|
|
11
|
-
} catch (
|
|
81
|
+
} catch (error) {
|
|
12
82
|
return {
|
|
13
83
|
ok: false,
|
|
14
84
|
errors: [{
|
|
15
85
|
path: "",
|
|
16
|
-
message: `YAML parse error: ${
|
|
86
|
+
message: `YAML parse error: ${error instanceof Error ? error.message : String(error)}`
|
|
17
87
|
}]
|
|
18
88
|
};
|
|
19
89
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
errors: [{
|
|
23
|
-
path: "",
|
|
24
|
-
message: "Pipeline must be a YAML mapping (object)."
|
|
25
|
-
}]
|
|
26
|
-
};
|
|
27
|
-
const doc = raw;
|
|
28
|
-
const name = typeof doc.name === "string" ? doc.name.trim() : "";
|
|
29
|
-
if (!name) errors.push({
|
|
30
|
-
path: "name",
|
|
31
|
-
message: "`name` is required."
|
|
32
|
-
});
|
|
33
|
-
const description = typeof doc.description === "string" ? doc.description : void 0;
|
|
34
|
-
const provider = typeof doc.provider === "string" ? doc.provider : void 0;
|
|
35
|
-
const include = Array.isArray(doc.include) ? doc.include.filter((x) => typeof x === "string") : void 0;
|
|
36
|
-
const timeoutSeconds = typeof doc.timeoutSeconds === "number" ? doc.timeoutSeconds : void 0;
|
|
37
|
-
const argsRaw = doc.args && typeof doc.args === "object" && !Array.isArray(doc.args) ? doc.args : {};
|
|
38
|
-
const args = {};
|
|
39
|
-
for (const [k, v] of Object.entries(argsRaw)) if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
40
|
-
const def = v;
|
|
41
|
-
args[k] = {
|
|
42
|
-
type: typeof def.type === "string" ? def.type : "string",
|
|
43
|
-
required: def.required === true,
|
|
44
|
-
default: def.default,
|
|
45
|
-
description: typeof def.description === "string" ? def.description : void 0,
|
|
46
|
-
choices: Array.isArray(def.choices) ? def.choices : void 0
|
|
47
|
-
};
|
|
48
|
-
} else args[k] = {
|
|
49
|
-
type: "string",
|
|
50
|
-
default: v
|
|
51
|
-
};
|
|
52
|
-
function parseSteps(rawSteps, path, required) {
|
|
53
|
-
if (!Array.isArray(rawSteps)) {
|
|
54
|
-
if (required) errors.push({
|
|
55
|
-
path,
|
|
56
|
-
message: `\`${path}\` must be an array of steps.`
|
|
57
|
-
});
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
const pipeline = [];
|
|
61
|
-
for (let i = 0; i < rawSteps.length; i++) {
|
|
62
|
-
const step = rawSteps[i];
|
|
63
|
-
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
64
|
-
errors.push({
|
|
65
|
-
path: `${path}[${i}]`,
|
|
66
|
-
message: "Each step must be a YAML mapping."
|
|
67
|
-
});
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const stepObj = step;
|
|
71
|
-
const actionKeys = Object.keys(stepObj);
|
|
72
|
-
if (actionKeys.length !== 1) {
|
|
73
|
-
errors.push({
|
|
74
|
-
path: `${path}[${i}]`,
|
|
75
|
-
message: `Each step must have exactly one action key, found ${actionKeys.length}: [${actionKeys.join(", ")}].`
|
|
76
|
-
});
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const action = actionKeys[0];
|
|
80
|
-
const actionArgs = stepObj[action];
|
|
81
|
-
if (typeof actionArgs === "string") pipeline.push({
|
|
82
|
-
action,
|
|
83
|
-
args: actionArgs
|
|
84
|
-
});
|
|
85
|
-
else if (actionArgs && typeof actionArgs === "object" && !Array.isArray(actionArgs)) pipeline.push({
|
|
86
|
-
action,
|
|
87
|
-
args: actionArgs
|
|
88
|
-
});
|
|
89
|
-
else if (actionArgs === null || actionArgs === void 0) pipeline.push({
|
|
90
|
-
action,
|
|
91
|
-
args: {}
|
|
92
|
-
});
|
|
93
|
-
else pipeline.push({
|
|
94
|
-
action,
|
|
95
|
-
args: { value: actionArgs }
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return pipeline;
|
|
99
|
-
}
|
|
100
|
-
const pipeline = parseSteps(doc.pipeline, "pipeline", true);
|
|
101
|
-
const onError = doc.on_error === void 0 ? void 0 : parseSteps(doc.on_error, "on_error", false);
|
|
102
|
-
if (errors.length > 0) return {
|
|
90
|
+
const parsed = RecipeSchema.safeParse(raw);
|
|
91
|
+
if (!parsed.success) return {
|
|
103
92
|
ok: false,
|
|
104
|
-
errors
|
|
93
|
+
errors: parsed.error.issues.map((issue) => ({
|
|
94
|
+
path: issue.path.join("."),
|
|
95
|
+
message: issue.message
|
|
96
|
+
}))
|
|
105
97
|
};
|
|
106
98
|
return {
|
|
107
99
|
ok: true,
|
|
108
100
|
document: {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
apiVersion: parsed.data.apiVersion,
|
|
102
|
+
id: parsed.data.id,
|
|
103
|
+
name: parsed.data.name,
|
|
104
|
+
description: parsed.data.description,
|
|
105
|
+
risk: parsed.data.risk,
|
|
106
|
+
domains: parsed.data.domains,
|
|
107
|
+
timeoutSeconds: parsed.data.timeoutSeconds,
|
|
108
|
+
args: parsed.data.args,
|
|
109
|
+
pipeline: stepsFromRaw(parsed.data.pipeline),
|
|
110
|
+
onError: parsed.data.on_error ? stepsFromRaw(parsed.data.on_error) : void 0
|
|
117
111
|
},
|
|
118
112
|
errors: []
|
|
119
113
|
};
|
|
120
114
|
}
|
|
121
115
|
//#endregion
|
|
122
|
-
export { parseBrowserPipeline };
|
|
116
|
+
export { BROWSER_RECIPE_API_VERSION, parseBrowserPipeline };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BrowserRecipe, BrowserRecipeRun, BrowserRecipeRunEvent } from './types.js';
|
|
2
|
+
export declare function listBrowserRecipes(): BrowserRecipe[];
|
|
3
|
+
export declare function getBrowserRecipe(id: string): BrowserRecipe | null;
|
|
4
|
+
export declare function saveBrowserRecipe(recipe: BrowserRecipe): void;
|
|
5
|
+
export declare function deleteBrowserRecipe(id: string): boolean;
|
|
6
|
+
export declare function saveBrowserRecipeRun(run: BrowserRecipeRun): void;
|
|
7
|
+
export declare function getBrowserRecipeRun(id: string): BrowserRecipeRun | null;
|
|
8
|
+
export declare function listBrowserRecipeRuns(recipeId?: string): BrowserRecipeRun[];
|
|
9
|
+
export declare function hasActiveBrowserRecipeRuns(recipeId: string): boolean;
|
|
10
|
+
export declare function listActiveBrowserRecipeRuns(): BrowserRecipeRun[];
|
|
11
|
+
export declare function nextBrowserRecipeRunEventSeq(runId: string): number;
|
|
12
|
+
export declare function appendBrowserRecipeRunEvent(event: BrowserRecipeRunEvent): void;
|
|
13
|
+
export declare function listBrowserRecipeRunEvents(runId: string): BrowserRecipeRunEvent[];
|