@xopcai/xopc 0.0.197 → 0.0.199
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-BwmdJ5pv.js → activity-store-DYVN1hRp.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-C5st7lt5.js → agent-avatar-dicebear-Bt70bZjv.js} +1 -1
- package/dist/gateway/static/root/assets/agents-D19uNmJf.js +68 -0
- package/dist/gateway/static/root/assets/{api-BM1hNzjw.js → api-B8mzbAle.js} +1 -1
- package/dist/gateway/static/root/assets/app-management-settings-panel-B4x7JfL8.js +1 -0
- package/dist/gateway/static/root/assets/appearance-settings-Dwdagf-Q.js +1 -0
- package/dist/gateway/static/root/assets/apps-page-DkTiesxe.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-qL07mwnY.js → archive-plugin-Cv0VXKLm.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CqSobpdi.js → attachment-load-CgidKrXn.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-BR6qMOVr.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DBRPyLYE.js → binary-plugins-DN3CQgIq.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-6wG1LU7t.js → block-editor-BwkSl1F_.js} +5 -5
- package/dist/gateway/static/root/assets/browser-settings-page-CDFQWAxI.js +8 -0
- package/dist/gateway/static/root/assets/browser-workflow-inputs-_ndaecsf.js +1 -0
- package/dist/gateway/static/root/assets/{browser-workflows-page-BEu3E0et.js → browser-workflows-page-C_I31wLm.js} +2 -2
- package/dist/gateway/static/root/assets/button-CSEbl4zT.js +1 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-D41mssOq.js +7 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-BoOzFotX.js → capability-presets-api-Dy1jZJKs.js} +1 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-BXgCyuu2.js +3 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-_8IiTxRB.js → channel-recipient-api-D-9qiuio.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-DV5BhAeu.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-B4Rbh3bM.js +1 -0
- package/dist/gateway/static/root/assets/connectors-api-5FEBYguL.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-C_qkEN0T.js +2 -0
- package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +1 -0
- package/dist/gateway/static/root/assets/cron-expression-SEFx5rnr.js +2 -0
- package/dist/gateway/static/root/assets/date-picker-D4zCUQJL.js +1 -0
- package/dist/gateway/static/root/assets/dependency-picker-DpQAxS2k.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-CsLR4pWB.js → desktop-pet-B_5G3lQl.js} +1 -1
- package/dist/gateway/static/root/assets/desktop-pet-settings-ClEyoUJe.js +1 -0
- package/dist/gateway/static/root/assets/directory-picker-path-field-AsFWmOjG.js +1 -0
- package/dist/gateway/static/root/assets/dist-BPoKqzeH.js +1 -0
- package/dist/gateway/static/root/assets/dist-DY4Af0fY.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-CD73euAM.js → extension-debug-page-C22Y2o5l.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DAKjpNwd.js → extension-page-C61Ikawk.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-WlWWcDFr.js +1 -0
- package/dist/gateway/static/root/assets/external-link-DjqjBmc3.js +1 -0
- package/dist/gateway/static/root/assets/form-field-width-DJw_jULe.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-api-ByIOMQdc.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-swr-jB0hAhWO.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-CzzS9A17.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry--2njcDpC.js → gateway-startup-retry-C-fRlHCL.js} +1 -1
- package/dist/gateway/static/root/assets/globe-DSSGFWFn.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-B4A_CXUL.js +1 -0
- package/dist/gateway/static/root/assets/home-page-FVGYYPWo.js +3 -0
- package/dist/gateway/static/root/assets/index-CGbKTCST.js +152 -0
- package/dist/gateway/static/root/assets/index-CR233S0Q.css +1 -0
- package/dist/gateway/static/root/assets/interaction-BmQogeqz.js +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-HMhxgOuD.js → keyboard-shortcuts-settings-BD_TFz0_.js} +1 -1
- package/dist/gateway/static/root/assets/loader-circle-CifKLtgG.js +1 -0
- package/dist/gateway/static/root/assets/local-app-workbench-page-BNpkigNb.js +2 -0
- package/dist/gateway/static/root/assets/local-apps-page-BwbUeLfm.js +1 -0
- package/dist/gateway/static/root/assets/{locale-store-Bjvw7jtg.js → locale-store-xULEcFVs.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-DXP9OQQt.js +2 -0
- package/dist/gateway/static/root/assets/management-settings-CW2VpqtT.js +1 -0
- package/dist/gateway/static/root/assets/{markdown-split-DvVBewS4.js → markdown-split-B1M9KO16.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-YVj7NVzD.js → media-plugins-B_QlNdqZ.js} +1 -1
- package/dist/gateway/static/root/assets/messages-3qRsLZuX.js +3 -0
- package/dist/gateway/static/root/assets/{notes-page-BDzuL2I9.js → notes-page-BTbD4GmH.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-JRiq7QDW.js +4 -0
- package/dist/gateway/static/root/assets/page-tabs-BaEQMYm_.js +1 -0
- package/dist/gateway/static/root/assets/popover-select-BzdnTy3h.js +1 -0
- package/dist/gateway/static/root/assets/preview-open-alternatives-CeqtArlZ.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-Bwx_CQht.js → product-open-page-DnalBZZs.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CjWA13aE.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-jsCkScbG.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CGPJTEkM.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Fe05Nus6.js → runtime-tools-settings-panel-cGkBEfg4.js} +4 -4
- package/dist/gateway/static/root/assets/schema-form-CmsrLlxu.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-BBHzziMd.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-2cGg8-9h.js +1 -0
- package/dist/gateway/static/root/assets/settings-form-draft-Dy10ldo8.js +1 -0
- package/dist/gateway/static/root/assets/settings-form-section-DUWCbaMJ.js +1 -0
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-BRE_-7Da.js → settings-loading-skeleton-Bvy5rdMY.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-C1zxCpqB.js → settings-page-cZSAwsRR.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-layout-BRQNel6r.js +1 -0
- package/dist/gateway/static/root/assets/settings-shell-dialog-layer-BU3UuiJr.js +1 -0
- package/dist/gateway/static/root/assets/setup-status-panel-Cf06b66-.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-BzA2NCAH.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-xlNKydYY.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C5Xu93c_.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-Bvde7_i5.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page.utils-CYwFWTso.js → skills-page.utils-vXSqHR7g.js} +1 -1
- package/dist/gateway/static/root/assets/system-settings-panel-9cQGOGCK.js +1 -0
- package/dist/gateway/static/root/assets/task-detail-page-Bkno4tTe.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-DtxpMC5S.js → theme-store-CHQRiuCF.js} +1 -1
- package/dist/gateway/static/root/assets/tool-activity-R8V9l6nk.js +1 -0
- package/dist/gateway/static/root/assets/typed-models-lib-q5-Vfqd8.js +1 -0
- package/dist/gateway/static/root/assets/use-autosave-7XfKULPI.js +1 -0
- package/dist/gateway/static/root/assets/user-context-page-CZ9m9aC_.js +1 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-DXr21v-3.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-overlay-CeG7g_DZ.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-page-CQWZD03N.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DqIb1oBB.js → workflow-run-setup-panel-DSQQZP9T.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-Cs-MXFJ1.js +5 -0
- package/dist/gateway/static/root/assets/working-directory-picker-modal-D-IgcHPp.js +1 -0
- package/dist/gateway/static/root/index.html +23 -8
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/tasks.js +54 -2
- package/dist/src/agent/agent-manager.d.ts +1 -0
- package/dist/src/agent/agent-manager.js +1 -0
- package/dist/src/agent/service.js +1 -0
- package/dist/src/agent/service.types.d.ts +1 -0
- package/dist/src/agent/tools/factory.d.ts +2 -0
- package/dist/src/agent/tools/factory.js +2 -1
- package/dist/src/agent/tools/xopc-use-tool.d.ts +1 -0
- package/dist/src/agent/tools/xopc-use-tool.js +56 -6
- package/dist/src/automations/domain/validation.d.ts +40 -0
- package/dist/src/automations/draft/automation-draft-validator.d.ts +4 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/rules.js +2 -2
- package/dist/src/gateway/hono/app.d.ts +2 -0
- package/dist/src/gateway/hono/app.js +10 -3
- package/dist/src/gateway/hono/middleware/strict-rate-limit.d.ts +4 -0
- package/dist/src/gateway/hono/middleware/strict-rate-limit.js +8 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +2 -1
- package/dist/src/gateway/hono/routes/config.js +9 -0
- package/dist/src/gateway/hono/routes/deps.d.ts +1 -0
- package/dist/src/gateway/hono/routes/projects.js +55 -0
- package/dist/src/gateway/hono/routes/tasks.js +112 -19
- package/dist/src/gateway/host.d.ts +9 -1
- package/dist/src/gateway/host.js +22 -5
- package/dist/src/gateway/rate-limit/buckets.d.ts +3 -0
- package/dist/src/gateway/rate-limit/buckets.js +12 -0
- package/dist/src/gateway/runtime-config.js +6 -5
- package/dist/src/gateway/security/audit.js +0 -7
- package/dist/src/gateway/server.js +4 -1
- package/dist/src/gateway/service.d.ts +1 -0
- package/dist/src/gateway/service.js +5 -1
- package/dist/src/proactive/actions/service.d.ts +2 -0
- package/dist/src/proactive/actions/service.js +80 -0
- package/dist/src/proactive/execution/insight.js +20 -0
- package/dist/src/proactive/execution/repository.js +8 -2
- package/dist/src/proactive/execution/types.d.ts +14 -0
- package/dist/src/proactive/inbox/repository.js +59 -9
- package/dist/src/proactive/inbox/service.d.ts +2 -2
- package/dist/src/proactive/inbox/service.js +7 -2
- package/dist/src/proactive/inbox/types.d.ts +14 -0
- package/dist/src/proactive/inbox/worker.js +2 -0
- package/dist/src/proactive/scenarios/prompt-composer.js +2 -2
- package/dist/src/storage/sqlite/migrations/118_task_board_rank.sql +6 -0
- package/dist/src/storage/sqlite/migrations/119_proactive_disposition.sql +13 -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/schema.js +1 -1
- package/dist/src/tasks/home-query-service.d.ts +5 -0
- package/dist/src/tasks/home-query-service.js +7 -2
- package/dist/src/tasks/index.d.ts +1 -0
- package/dist/src/tasks/index.js +4 -3
- package/dist/src/tasks/project-monitoring-service.d.ts +9 -0
- package/dist/src/tasks/project-monitoring-service.js +41 -3
- package/dist/src/tasks/project-operating-view-service.d.ts +1 -2
- package/dist/src/tasks/project-operating-view-service.js +26 -23
- package/dist/src/tasks/task-application-service.js +99 -6
- package/dist/src/tasks/task-change-events.d.ts +13 -0
- package/dist/src/tasks/task-change-events.js +29 -0
- package/dist/src/tasks/task-read-model-projector.js +2 -1
- package/dist/src/tasks/task-repository.d.ts +7 -0
- package/dist/src/tasks/task-repository.js +29 -6
- package/dist/src/tunnel/enable-lan-pairing.js +15 -26
- package/dist/src/tunnel/pair-context.js +1 -1
- package/dist/src/tunnel/pair-url.js +1 -1
- package/dist/src/tunnel/tunnel-qr.d.ts +0 -7
- package/dist/src/tunnel/tunnel-qr.js +2 -19
- package/package.json +3 -1
- package/dist/gateway/static/root/assets/agents-Baxfj44y.js +0 -68
- package/dist/gateway/static/root/assets/app-management-settings-panel-S8LhoD6G.js +0 -1
- package/dist/gateway/static/root/assets/appearance-settings-D8Cd5vDP.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-0uCxKyE5.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-XyDat125.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CrYNMR4w.js +0 -8
- package/dist/gateway/static/root/assets/browser-workflow-inputs-Abil4puY.js +0 -1
- package/dist/gateway/static/root/assets/capabilities-settings-panel-CvMcDhGf.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-CHJDY-Ih.js +0 -3
- package/dist/gateway/static/root/assets/channels-settings-Ds9BJbcY.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-DPaJSYX_.js +0 -1
- package/dist/gateway/static/root/assets/connectors-api-CgWjgPSI.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-NUs-Zf7F.js +0 -2
- package/dist/gateway/static/root/assets/cron-expression-ZdDBAoqA.js +0 -2
- package/dist/gateway/static/root/assets/dependency-picker-BGeB1jpn.js +0 -1
- package/dist/gateway/static/root/assets/desktop-pet-settings-CCjT1bdo.js +0 -1
- package/dist/gateway/static/root/assets/directory-picker-path-field-C-Te1QN3.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-BLSNdlgd.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-api-CQrf0k3D.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CvgH8IJI.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-SPpnqbjl.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BFoD6047.js +0 -3
- package/dist/gateway/static/root/assets/index-84ys9u-K.js +0 -152
- package/dist/gateway/static/root/assets/index-D-srnucP.css +0 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-D5uL2_Fd.js +0 -2
- package/dist/gateway/static/root/assets/local-apps-page-DIWkFa3g.js +0 -1
- package/dist/gateway/static/root/assets/logs-page--6v8qq6y.js +0 -2
- package/dist/gateway/static/root/assets/management-settings-DtEuS-kR.js +0 -1
- package/dist/gateway/static/root/assets/messages-DQ5X6cXp.js +0 -3
- package/dist/gateway/static/root/assets/notes-workbench-IdjuNXvH.js +0 -4
- package/dist/gateway/static/root/assets/page-tabs-7bSEWfSX.js +0 -1
- package/dist/gateway/static/root/assets/preview-open-alternatives-tgLbHgcV.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-DnJXSuM4.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DckZKlBI.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CfbUHBUH.js +0 -1
- package/dist/gateway/static/root/assets/schema-form-CFfyC4CB.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BCfCh4My.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-Cbi4FvzO.js +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-gEnEYT_3.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-layout-CRpXF_05.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BcHdpmFf.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-DU8L68A5.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-kjTqH9yY.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-QAGpwzIM.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-lE1-YRdK.js +0 -2
- package/dist/gateway/static/root/assets/system-settings-panel-CVDsiPKn.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-DsM13-cy.js +0 -1
- package/dist/gateway/static/root/assets/typed-models-lib-DelyYgoc.js +0 -1
- package/dist/gateway/static/root/assets/use-autosave-C50Mq5yQ.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-D-VEOt2A.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DYfgTe3Y.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-overlay-DPbujsp3.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-page-D77opady.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-B9-PLmlX.js +0 -5
- package/dist/src/gateway/ensure-network-cors.d.ts +0 -3
- package/dist/src/gateway/ensure-network-cors.js +0 -23
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
|
|
1
2
|
import { TaskRepository } from "../../../tasks/task-repository.js";
|
|
2
3
|
import { TaskRunRepository } from "../../../tasks/task-run-repository.js";
|
|
3
4
|
import { TaskReadModelProjector } from "../../../tasks/task-read-model-projector.js";
|
|
4
5
|
import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
|
|
5
|
-
import { TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
|
|
6
|
+
import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
|
|
6
7
|
import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
|
|
7
8
|
import { TaskContextRepository } from "../../../tasks/task-context-repository.js";
|
|
9
|
+
import { enqueueTaskChangedEvent } from "../../../tasks/task-change-events.js";
|
|
8
10
|
import { TaskDependencyError, TaskDependencyService } from "../../../tasks/task-dependency-service.js";
|
|
9
11
|
import { TaskApplicationService } from "../../../tasks/task-application-service.js";
|
|
10
12
|
import { ProjectMonitoringService } from "../../../tasks/project-monitoring-service.js";
|
|
@@ -13,6 +15,7 @@ import { TaskValueMetricsService } from "../../../tasks/task-value-metrics-servi
|
|
|
13
15
|
import { TaskSignalService } from "../../../tasks/task-signal-service.js";
|
|
14
16
|
//#region src/gateway/hono/routes/tasks.ts
|
|
15
17
|
function registerTaskRoutes(authenticated, deps) {
|
|
18
|
+
const taskRateLimit = deps.taskRateLimitMiddleware ?? deps.strictRateLimitMiddleware;
|
|
16
19
|
const application = new TaskApplicationService();
|
|
17
20
|
const operatingViews = new ProjectOperatingViewService(deps.service.projects);
|
|
18
21
|
const monitoring = new ProjectMonitoringService();
|
|
@@ -42,7 +45,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
42
45
|
})
|
|
43
46
|
});
|
|
44
47
|
});
|
|
45
|
-
authenticated.post("/api/tasks",
|
|
48
|
+
authenticated.post("/api/tasks", taskRateLimit, async (c) => {
|
|
46
49
|
const body = await c.req.json().catch(() => ({}));
|
|
47
50
|
const parsed = TaskCreateRequestSchema.safeParse(body);
|
|
48
51
|
if (!parsed.success) return c.json({
|
|
@@ -57,6 +60,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
57
60
|
error: created.reason
|
|
58
61
|
}, 409);
|
|
59
62
|
if (created.runId) deps.service.dispatchTaskRuns();
|
|
63
|
+
else deps.service.dispatchTaskEvents();
|
|
60
64
|
return c.json({
|
|
61
65
|
ok: true,
|
|
62
66
|
task: created.model.task,
|
|
@@ -97,33 +101,57 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
97
101
|
allowedCommands: model.allowedCommands
|
|
98
102
|
});
|
|
99
103
|
});
|
|
100
|
-
authenticated.patch("/api/tasks/:id",
|
|
104
|
+
authenticated.patch("/api/tasks/:id", taskRateLimit, async (c) => {
|
|
101
105
|
const parsed = TaskPatchRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
102
106
|
if (!parsed.success) return c.json({
|
|
103
107
|
ok: false,
|
|
104
108
|
error: "Invalid task patch"
|
|
105
109
|
}, 400);
|
|
106
|
-
const updated =
|
|
110
|
+
const updated = runSqliteWriteTransaction((db) => {
|
|
111
|
+
const task = tasks.update(c.req.param("id"), parsed.data);
|
|
112
|
+
if (!task) return void 0;
|
|
113
|
+
const changedFields = Object.keys(parsed.data).filter((field) => field !== "expectedVersion");
|
|
114
|
+
enqueueTaskChangedEvent(db, {
|
|
115
|
+
taskId: task.id,
|
|
116
|
+
projectId: task.projectId,
|
|
117
|
+
version: task.version,
|
|
118
|
+
changedFields,
|
|
119
|
+
actor: { kind: "user" }
|
|
120
|
+
});
|
|
121
|
+
return task;
|
|
122
|
+
});
|
|
107
123
|
if (!updated) return c.json({
|
|
108
124
|
ok: false,
|
|
109
125
|
error: "Task changed or was not found"
|
|
110
126
|
}, 409);
|
|
127
|
+
deps.service.dispatchTaskEvents();
|
|
111
128
|
return c.json({
|
|
112
129
|
ok: true,
|
|
113
130
|
task: updated
|
|
114
131
|
});
|
|
115
132
|
});
|
|
116
|
-
authenticated.put("/api/tasks/:id/dependencies",
|
|
133
|
+
authenticated.put("/api/tasks/:id/dependencies", taskRateLimit, async (c) => {
|
|
117
134
|
const parsed = TaskDependencyUpdateRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
118
135
|
if (!parsed.success) return c.json({
|
|
119
136
|
ok: false,
|
|
120
137
|
error: "Invalid task dependencies"
|
|
121
138
|
}, 400);
|
|
122
139
|
try {
|
|
123
|
-
const task =
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
const task = runSqliteWriteTransaction((db) => {
|
|
141
|
+
const changed = dependencies.replace({
|
|
142
|
+
taskId: c.req.param("id"),
|
|
143
|
+
...parsed.data
|
|
144
|
+
});
|
|
145
|
+
enqueueTaskChangedEvent(db, {
|
|
146
|
+
taskId: changed.id,
|
|
147
|
+
projectId: changed.projectId,
|
|
148
|
+
version: changed.version,
|
|
149
|
+
changedFields: ["dependencies"],
|
|
150
|
+
actor: { kind: "user" }
|
|
151
|
+
});
|
|
152
|
+
return changed;
|
|
126
153
|
});
|
|
154
|
+
deps.service.dispatchTaskEvents();
|
|
127
155
|
return c.json({
|
|
128
156
|
ok: true,
|
|
129
157
|
task,
|
|
@@ -140,7 +168,45 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
140
168
|
}, status);
|
|
141
169
|
}
|
|
142
170
|
});
|
|
143
|
-
authenticated.
|
|
171
|
+
authenticated.put("/api/tasks/:id/board-position", taskRateLimit, async (c) => {
|
|
172
|
+
const parsed = TaskBoardPositionRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
173
|
+
if (!parsed.success) return c.json({
|
|
174
|
+
ok: false,
|
|
175
|
+
error: "Invalid task board position"
|
|
176
|
+
}, 400);
|
|
177
|
+
try {
|
|
178
|
+
const task = runSqliteWriteTransaction((db) => {
|
|
179
|
+
const reordered = tasks.reorder({
|
|
180
|
+
taskId: c.req.param("id"),
|
|
181
|
+
...parsed.data
|
|
182
|
+
});
|
|
183
|
+
if (!reordered) return void 0;
|
|
184
|
+
enqueueTaskChangedEvent(db, {
|
|
185
|
+
taskId: reordered.id,
|
|
186
|
+
projectId: reordered.projectId,
|
|
187
|
+
version: reordered.version,
|
|
188
|
+
changedFields: ["boardRank"],
|
|
189
|
+
actor: { kind: "user" }
|
|
190
|
+
});
|
|
191
|
+
return reordered;
|
|
192
|
+
});
|
|
193
|
+
if (!task) return c.json({
|
|
194
|
+
ok: false,
|
|
195
|
+
error: "Task changed or was not found"
|
|
196
|
+
}, 409);
|
|
197
|
+
deps.service.dispatchTaskEvents();
|
|
198
|
+
return c.json({
|
|
199
|
+
ok: true,
|
|
200
|
+
task
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return c.json({
|
|
204
|
+
ok: false,
|
|
205
|
+
error: error instanceof Error ? error.message : String(error)
|
|
206
|
+
}, 400);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
authenticated.post("/api/tasks/:id/commands", taskRateLimit, async (c) => {
|
|
144
210
|
const parsed = TaskCommandRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
145
211
|
if (!parsed.success) return c.json({
|
|
146
212
|
ok: false,
|
|
@@ -165,6 +231,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
165
231
|
}, 409);
|
|
166
232
|
}
|
|
167
233
|
if (result.runId || parsed.data.command?.type === "resolve_wait") deps.service.dispatchTaskRuns();
|
|
234
|
+
else deps.service.dispatchTaskEvents();
|
|
168
235
|
if (parsed.data.command?.type === "close" && parsed.data.command.resolution === "done") signals.dependencyClosed(c.req.param("id"));
|
|
169
236
|
return c.json({
|
|
170
237
|
ok: true,
|
|
@@ -172,7 +239,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
172
239
|
...result.runId ? { run: runs.get(result.runId) } : {}
|
|
173
240
|
});
|
|
174
241
|
});
|
|
175
|
-
authenticated.post("/api/tasks/:id/context",
|
|
242
|
+
authenticated.post("/api/tasks/:id/context", taskRateLimit, async (c) => {
|
|
176
243
|
const task = tasks.get(c.req.param("id"));
|
|
177
244
|
if (!task) return c.json({
|
|
178
245
|
ok: false,
|
|
@@ -183,17 +250,42 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
183
250
|
ok: false,
|
|
184
251
|
error: "Invalid task context edge"
|
|
185
252
|
}, 400);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
edge: context.add({
|
|
253
|
+
const edge = runSqliteWriteTransaction((db) => {
|
|
254
|
+
const created = context.add({
|
|
189
255
|
taskId: task.id,
|
|
190
256
|
...parsed.data,
|
|
191
257
|
createdBy: { kind: "user" }
|
|
192
|
-
})
|
|
258
|
+
});
|
|
259
|
+
enqueueTaskChangedEvent(db, {
|
|
260
|
+
taskId: task.id,
|
|
261
|
+
projectId: task.projectId,
|
|
262
|
+
version: task.version,
|
|
263
|
+
changedFields: ["context"],
|
|
264
|
+
actor: { kind: "user" }
|
|
265
|
+
});
|
|
266
|
+
return created;
|
|
267
|
+
});
|
|
268
|
+
deps.service.dispatchTaskEvents();
|
|
269
|
+
return c.json({
|
|
270
|
+
ok: true,
|
|
271
|
+
edge
|
|
193
272
|
}, 201);
|
|
194
273
|
});
|
|
195
|
-
authenticated.delete("/api/tasks/:id/context/:edgeId",
|
|
196
|
-
|
|
274
|
+
authenticated.delete("/api/tasks/:id/context/:edgeId", taskRateLimit, (c) => {
|
|
275
|
+
const task = tasks.get(c.req.param("id"));
|
|
276
|
+
const removed = task ? runSqliteWriteTransaction((db) => {
|
|
277
|
+
const didRemove = context.remove(task.id, c.req.param("edgeId"));
|
|
278
|
+
if (didRemove) enqueueTaskChangedEvent(db, {
|
|
279
|
+
taskId: task.id,
|
|
280
|
+
projectId: task.projectId,
|
|
281
|
+
version: task.version,
|
|
282
|
+
changedFields: ["context"],
|
|
283
|
+
actor: { kind: "user" }
|
|
284
|
+
});
|
|
285
|
+
return didRemove;
|
|
286
|
+
}) : false;
|
|
287
|
+
if (removed) deps.service.dispatchTaskEvents();
|
|
288
|
+
return removed ? c.json({ ok: true }) : c.json({
|
|
197
289
|
ok: false,
|
|
198
290
|
error: "Task context edge not found"
|
|
199
291
|
}, 404);
|
|
@@ -219,7 +311,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
219
311
|
error: "TaskRun not found"
|
|
220
312
|
}, 404);
|
|
221
313
|
});
|
|
222
|
-
authenticated.post("/api/task-runs/:runId/cancel",
|
|
314
|
+
authenticated.post("/api/task-runs/:runId/cancel", taskRateLimit, async (c) => {
|
|
223
315
|
const body = await c.req.json().catch(() => ({}));
|
|
224
316
|
const run = runs.get(c.req.param("runId"));
|
|
225
317
|
if (!run) return c.json({
|
|
@@ -256,13 +348,14 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
256
348
|
ok: false,
|
|
257
349
|
error: result.reason
|
|
258
350
|
}, 409);
|
|
351
|
+
deps.service.dispatchTaskEvents();
|
|
259
352
|
return c.json({
|
|
260
353
|
ok: true,
|
|
261
354
|
run: runs.get(run.id),
|
|
262
355
|
receipt: runs.getReceipt(run.id)
|
|
263
356
|
});
|
|
264
357
|
});
|
|
265
|
-
authenticated.post("/api/task-runs/:runId/feedback",
|
|
358
|
+
authenticated.post("/api/task-runs/:runId/feedback", taskRateLimit, async (c) => {
|
|
266
359
|
const body = await c.req.json().catch(() => ({}));
|
|
267
360
|
if (body.rating !== "helpful" && body.rating !== "not_helpful") return c.json({
|
|
268
361
|
ok: false,
|
|
@@ -306,7 +399,7 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
306
399
|
policy: monitoring.get(projectId)
|
|
307
400
|
});
|
|
308
401
|
});
|
|
309
|
-
authenticated.patch("/api/projects/:projectId/monitoring",
|
|
402
|
+
authenticated.patch("/api/projects/:projectId/monitoring", taskRateLimit, async (c) => {
|
|
310
403
|
const projectId = c.req.param("projectId");
|
|
311
404
|
if (!deps.service.projects.get(projectId)) return c.json({
|
|
312
405
|
ok: false,
|
|
@@ -6,6 +6,13 @@ export declare function isAllInterfacesHost(host: string | undefined): boolean;
|
|
|
6
6
|
export declare const GATEWAY_DEV_CONSOLE_ORIGINS: readonly ["http://localhost:3000", "http://127.0.0.1:3000"];
|
|
7
7
|
/** Expo / React Native web dev server (Metro defaults to port 8081). */
|
|
8
8
|
export declare const GATEWAY_EXPO_DEV_ORIGINS: readonly ["http://localhost:8081", "http://127.0.0.1:8081"];
|
|
9
|
+
export type LanGatewayCandidate = {
|
|
10
|
+
url: string;
|
|
11
|
+
address: string;
|
|
12
|
+
interfaceName: string;
|
|
13
|
+
};
|
|
14
|
+
/** Non-internal IPv4 addresses that can serve this gateway on the local network. */
|
|
15
|
+
export declare function enumerateLanGatewayCandidates(port: number): LanGatewayCandidate[];
|
|
9
16
|
/** Effective HTTP listen port: CLI `--port` override wins over config (default 18790). */
|
|
10
17
|
export declare function resolveEffectiveGatewayPort(config: {
|
|
11
18
|
gateway?: {
|
|
@@ -27,7 +34,8 @@ export declare function buildDefaultCorsOrigins(params: {
|
|
|
27
34
|
}): string[];
|
|
28
35
|
/**
|
|
29
36
|
* Effective browser origins for CORS and CSRF checks.
|
|
30
|
-
*
|
|
37
|
+
* Configured origins extend the gateway's own browser origins instead of replacing them.
|
|
38
|
+
* This keeps the bundled console reachable while allowing explicit external frontends.
|
|
31
39
|
*/
|
|
32
40
|
export declare function resolveGatewayCorsOrigins(params: {
|
|
33
41
|
configuredOrigins?: string[];
|
package/dist/src/gateway/host.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../daemon/constants.js";
|
|
2
|
+
import { networkInterfaces } from "node:os";
|
|
2
3
|
//#region src/gateway/host.ts
|
|
3
4
|
/** True when the bind address is local-only (127.x, localhost, ::1). */
|
|
4
5
|
function isLoopbackHost(host) {
|
|
@@ -17,6 +18,21 @@ const GATEWAY_DEV_CONSOLE_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:
|
|
|
17
18
|
/** Expo / React Native web dev server (Metro defaults to port 8081). */
|
|
18
19
|
const GATEWAY_EXPO_DEV_ORIGINS = ["http://localhost:8081", "http://127.0.0.1:8081"];
|
|
19
20
|
const GATEWAY_LOOPBACK_DEV_ORIGINS = [...GATEWAY_DEV_CONSOLE_ORIGINS, ...GATEWAY_EXPO_DEV_ORIGINS];
|
|
21
|
+
/** Non-internal IPv4 addresses that can serve this gateway on the local network. */
|
|
22
|
+
function enumerateLanGatewayCandidates(port) {
|
|
23
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24
|
+
const candidates = [];
|
|
25
|
+
for (const [interfaceName, addresses] of Object.entries(networkInterfaces())) for (const address of addresses ?? []) {
|
|
26
|
+
if (address.family !== "IPv4" || address.internal || seen.has(address.address)) continue;
|
|
27
|
+
seen.add(address.address);
|
|
28
|
+
candidates.push({
|
|
29
|
+
address: address.address,
|
|
30
|
+
interfaceName,
|
|
31
|
+
url: `http://${address.address}:${port}`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return candidates;
|
|
35
|
+
}
|
|
20
36
|
/** Effective HTTP listen port: CLI `--port` override wins over config (default 18790). */
|
|
21
37
|
function resolveEffectiveGatewayPort(config, listenPortOverride) {
|
|
22
38
|
if (typeof listenPortOverride === "number" && Number.isFinite(listenPortOverride)) return listenPortOverride;
|
|
@@ -35,19 +51,20 @@ function buildDefaultCorsOrigins(params) {
|
|
|
35
51
|
]);
|
|
36
52
|
const bindHost = params.bindHost?.trim();
|
|
37
53
|
if (bindHost && !isLoopbackHost(bindHost) && !isAllInterfacesHost(bindHost)) origins.add(`http://${bindHost}:${params.port}`);
|
|
54
|
+
if (isAllInterfacesHost(bindHost)) for (const candidate of enumerateLanGatewayCandidates(params.port)) origins.add(candidate.url);
|
|
38
55
|
return [...origins];
|
|
39
56
|
}
|
|
40
57
|
/**
|
|
41
58
|
* Effective browser origins for CORS and CSRF checks.
|
|
42
|
-
*
|
|
59
|
+
* Configured origins extend the gateway's own browser origins instead of replacing them.
|
|
60
|
+
* This keeps the bundled console reachable while allowing explicit external frontends.
|
|
43
61
|
*/
|
|
44
62
|
function resolveGatewayCorsOrigins(params) {
|
|
45
63
|
const configured = (params.configuredOrigins ?? []).map((origin) => origin.trim()).filter(Boolean);
|
|
46
|
-
|
|
64
|
+
return [...new Set([...configured, ...buildDefaultCorsOrigins({
|
|
47
65
|
port: params.port,
|
|
48
66
|
bindHost: params.bindHost
|
|
49
|
-
});
|
|
50
|
-
return [...new Set([...configured, ...GATEWAY_LOOPBACK_DEV_ORIGINS])];
|
|
67
|
+
})])];
|
|
51
68
|
}
|
|
52
69
|
/** Browser origin (`https://host`) from a gateway public/tunnel root URL. */
|
|
53
70
|
function originFromGatewayPublicUrl(publicUrl) {
|
|
@@ -73,4 +90,4 @@ function resolveAllowedBrowserOrigins(params) {
|
|
|
73
90
|
return origins;
|
|
74
91
|
}
|
|
75
92
|
//#endregion
|
|
76
|
-
export { GATEWAY_DEV_CONSOLE_ORIGINS, GATEWAY_EXPO_DEV_ORIGINS, buildDefaultCorsOrigins, isAllInterfacesHost, isLoopbackHost, originFromGatewayPublicUrl, resolveAllowedBrowserOrigins, resolveEffectiveGatewayPort, resolveGatewayCorsOrigins, resolveGatewayServiceListenPort };
|
|
93
|
+
export { GATEWAY_DEV_CONSOLE_ORIGINS, GATEWAY_EXPO_DEV_ORIGINS, buildDefaultCorsOrigins, enumerateLanGatewayCandidates, isAllInterfacesHost, isLoopbackHost, originFromGatewayPublicUrl, resolveAllowedBrowserOrigins, resolveEffectiveGatewayPort, resolveGatewayCorsOrigins, resolveGatewayServiceListenPort };
|
|
@@ -36,6 +36,7 @@ declare class BucketRegistry {
|
|
|
36
36
|
private authFailureSignature?;
|
|
37
37
|
private strictApiLimiter?;
|
|
38
38
|
private chatApiLimiter?;
|
|
39
|
+
private taskApiLimiter?;
|
|
39
40
|
private xopcCloudPollLimiter?;
|
|
40
41
|
private channelApiLimiter?;
|
|
41
42
|
private tunnelMutateLimiter?;
|
|
@@ -52,6 +53,8 @@ declare class BucketRegistry {
|
|
|
52
53
|
strictApi(): RateLimiter;
|
|
53
54
|
/** Interactive chat actions — 180 req / 60 s per client IP. */
|
|
54
55
|
chatApi(): RateLimiter;
|
|
56
|
+
/** Interactive task and board mutations — 300 req / 60 s per client IP. */
|
|
57
|
+
taskApi(): RateLimiter;
|
|
55
58
|
/** XOPC Cloud authorization polling — 60 req / 60 s per client IP. */
|
|
56
59
|
xopcCloudPoll(): RateLimiter;
|
|
57
60
|
/** Channel mutation/action endpoints — 20 req / 60 s per client IP. */
|
|
@@ -48,6 +48,7 @@ var BucketRegistry = class {
|
|
|
48
48
|
authFailureSignature;
|
|
49
49
|
strictApiLimiter;
|
|
50
50
|
chatApiLimiter;
|
|
51
|
+
taskApiLimiter;
|
|
51
52
|
xopcCloudPollLimiter;
|
|
52
53
|
channelApiLimiter;
|
|
53
54
|
tunnelMutateLimiter;
|
|
@@ -89,6 +90,14 @@ var BucketRegistry = class {
|
|
|
89
90
|
});
|
|
90
91
|
return this.chatApiLimiter;
|
|
91
92
|
}
|
|
93
|
+
/** Interactive task and board mutations — 300 req / 60 s per client IP. */
|
|
94
|
+
taskApi() {
|
|
95
|
+
if (!this.taskApiLimiter) this.taskApiLimiter = new RateLimiter({
|
|
96
|
+
maxRequests: 300,
|
|
97
|
+
windowMs: 6e4
|
|
98
|
+
});
|
|
99
|
+
return this.taskApiLimiter;
|
|
100
|
+
}
|
|
92
101
|
/** XOPC Cloud authorization polling — 60 req / 60 s per client IP. */
|
|
93
102
|
xopcCloudPoll() {
|
|
94
103
|
if (!this.xopcCloudPollLimiter) this.xopcCloudPollLimiter = new RateLimiter({
|
|
@@ -142,6 +151,7 @@ var BucketRegistry = class {
|
|
|
142
151
|
this.authFailureLimiter?.destroy();
|
|
143
152
|
this.strictApiLimiter?.destroy();
|
|
144
153
|
this.chatApiLimiter?.destroy();
|
|
154
|
+
this.taskApiLimiter?.destroy();
|
|
145
155
|
this.xopcCloudPollLimiter?.destroy();
|
|
146
156
|
this.channelApiLimiter?.destroy();
|
|
147
157
|
this.tunnelMutateLimiter?.destroy();
|
|
@@ -151,6 +161,7 @@ var BucketRegistry = class {
|
|
|
151
161
|
this.authFailureLimiter = void 0;
|
|
152
162
|
this.strictApiLimiter = void 0;
|
|
153
163
|
this.chatApiLimiter = void 0;
|
|
164
|
+
this.taskApiLimiter = void 0;
|
|
154
165
|
this.xopcCloudPollLimiter = void 0;
|
|
155
166
|
this.channelApiLimiter = void 0;
|
|
156
167
|
this.tunnelMutateLimiter = void 0;
|
|
@@ -164,6 +175,7 @@ var BucketRegistry = class {
|
|
|
164
175
|
this.authFailureLimiter?.resetForTests();
|
|
165
176
|
this.strictApiLimiter?.resetForTests();
|
|
166
177
|
this.chatApiLimiter?.resetForTests();
|
|
178
|
+
this.taskApiLimiter?.resetForTests();
|
|
167
179
|
this.xopcCloudPollLimiter?.resetForTests();
|
|
168
180
|
this.channelApiLimiter?.resetForTests();
|
|
169
181
|
this.tunnelMutateLimiter?.resetForTests();
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { isAllInterfacesHost, isLoopbackHost } from "./host.js";
|
|
1
|
+
import { isAllInterfacesHost, isLoopbackHost, resolveGatewayCorsOrigins } from "./host.js";
|
|
2
2
|
import { isAuthRateLimitGloballyDisabled, isGatewayStrictSecurityEnabled } from "./rate-limit/env-flags.js";
|
|
3
3
|
import "./rate-limit/index.js";
|
|
4
4
|
import { resolveGatewayListenPlan } from "./listen.js";
|
|
5
5
|
import { assertTailscaleExposureCompatible } from "./tailscale-lifecycle.js";
|
|
6
6
|
//#region src/gateway/runtime-config.ts
|
|
7
|
-
function normalizeCorsOrigins(cfg) {
|
|
8
|
-
return (cfg.gateway?.corsOrigins ?? []).map((value) => value.trim()).filter(Boolean);
|
|
9
|
-
}
|
|
10
7
|
function hasSharedSecret(auth) {
|
|
11
8
|
if (auth.mode === "trusted-proxy") return true;
|
|
12
9
|
if (auth.mode === "token") return typeof auth.token === "string" && auth.token.trim().length > 0;
|
|
@@ -23,7 +20,11 @@ function assertGatewayRuntimeConfig(params) {
|
|
|
23
20
|
bindOverride: params.bindOverride
|
|
24
21
|
});
|
|
25
22
|
const loopback = isLoopbackHost(bindHost);
|
|
26
|
-
const corsOrigins =
|
|
23
|
+
const corsOrigins = resolveGatewayCorsOrigins({
|
|
24
|
+
configuredOrigins: params.cfg.gateway?.corsOrigins,
|
|
25
|
+
port: params.port,
|
|
26
|
+
bindHost
|
|
27
|
+
});
|
|
27
28
|
const dangerouslyAllowHostHeaderOriginFallback = params.cfg.gateway?.dangerouslyAllowHostHeaderOriginFallback === true;
|
|
28
29
|
if (bindMode === "loopback" && !loopback) throw new Error(`gateway bind=loopback resolved to non-loopback host ${bindHost}; refusing fallback to a network bind`);
|
|
29
30
|
if (bindMode === "custom") {
|
|
@@ -110,13 +110,6 @@ function collectGatewayConfigFindings(params) {
|
|
|
110
110
|
title: "Large number of CORS origins configured",
|
|
111
111
|
detail: `${params.corsOrigins.length} CORS origins configured. Review whether all are necessary.`
|
|
112
112
|
});
|
|
113
|
-
if (!loopback && (!params.corsOrigins || params.corsOrigins.length === 0) && params.dangerouslyAllowHostHeaderOriginFallback !== true) findings.push({
|
|
114
|
-
checkId: "gateway.cors.no_explicit_origins",
|
|
115
|
-
severity: "info",
|
|
116
|
-
title: "No custom CORS origins — loopback browsers only",
|
|
117
|
-
detail: "Gateway listens on a network address with empty gateway.corsOrigins. localhost and 127.0.0.1 on the gateway port are allowed by default; LAN or remote browser URLs must be added explicitly.",
|
|
118
|
-
remediation: "Add gateway.corsOrigins entries (e.g. http://192.168.x.x:<port>) for non-loopback browser access, or enable gateway.dangerouslyAllowHostHeaderOriginFallback only if you understand the CSRF risk."
|
|
119
|
-
});
|
|
120
113
|
if (!loopback && params.auth.mode !== "none" && params.auth.mode !== "trusted-proxy" && params.rateLimitEnabled === false) findings.push({
|
|
121
114
|
checkId: "gateway.auth.no_rate_limit",
|
|
122
115
|
severity: "warn",
|
|
@@ -51,7 +51,10 @@ var GatewayServer = class {
|
|
|
51
51
|
});
|
|
52
52
|
const { configureTunnelFromGatewayConfig } = await import("../tunnel/gateway-lifecycle.js");
|
|
53
53
|
await configureTunnelFromGatewayConfig(this.service.currentConfig, { deferWellKnownFetch: true });
|
|
54
|
-
const app = createHonoApp({
|
|
54
|
+
const app = createHonoApp({
|
|
55
|
+
service: this.service,
|
|
56
|
+
listenHost: bindHost
|
|
57
|
+
});
|
|
55
58
|
const primaryHost = listenHosts[0] ?? bindHost;
|
|
56
59
|
const attachUpgrade = (server) => {
|
|
57
60
|
server.on("upgrade", (req, socket, head) => {
|
|
@@ -120,6 +120,7 @@ export declare class GatewayService {
|
|
|
120
120
|
private createTaskRunDispatcher;
|
|
121
121
|
private createMobileNotificationService;
|
|
122
122
|
dispatchTaskRuns(): void;
|
|
123
|
+
dispatchTaskEvents(): void;
|
|
123
124
|
runAgent(...args: Parameters<GatewayAgentRunner['runAgent']>): ReturnType<GatewayAgentRunner['runAgent']>;
|
|
124
125
|
submitSessionInput(...args: Parameters<GatewayAgentRunner['submitSessionInput']>): Promise<{
|
|
125
126
|
ok: true;
|
|
@@ -460,6 +460,7 @@ var GatewayService = class {
|
|
|
460
460
|
getNotesService: () => this.notesService,
|
|
461
461
|
getProjectService: () => this.projects,
|
|
462
462
|
getLocalAppService: () => this.localApps,
|
|
463
|
+
dispatchTaskEvents: () => this.dispatchTaskEvents(),
|
|
463
464
|
dispatchTaskRuns: () => this.dispatchTaskRuns(),
|
|
464
465
|
getWorkflowRunService: () => this.createWorkflowRunService(),
|
|
465
466
|
sourceContextResolver: async (binding) => {
|
|
@@ -593,10 +594,13 @@ var GatewayService = class {
|
|
|
593
594
|
}
|
|
594
595
|
dispatchTaskRuns() {
|
|
595
596
|
new TaskSignalService().tick();
|
|
596
|
-
|
|
597
|
+
this.dispatchTaskEvents();
|
|
597
598
|
this.createTaskRunDispatcher().dispatch();
|
|
598
599
|
this.createWorkflowRunService().dispatchTaskRuns();
|
|
599
600
|
}
|
|
601
|
+
dispatchTaskEvents() {
|
|
602
|
+
new TaskOutboxDispatcher(publishAutomationProductEvent).drain();
|
|
603
|
+
}
|
|
600
604
|
runAgent(...args) {
|
|
601
605
|
return this.agentRunner.runAgent(...args);
|
|
602
606
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { TaskApplicationService } from "../../tasks/task-application-service.js";
|
|
3
|
+
//#region src/proactive/actions/service.ts
|
|
4
|
+
function projectIdFromAggregationKey(value) {
|
|
5
|
+
if (!value.startsWith("project:") || value.length === 8) throw new Error("Proactive action has no project scope");
|
|
6
|
+
return value.slice(8);
|
|
7
|
+
}
|
|
8
|
+
function executeClaimedAction(row, now = /* @__PURE__ */ new Date()) {
|
|
9
|
+
const action = JSON.parse(row.proposed_action_json);
|
|
10
|
+
try {
|
|
11
|
+
if (action.id !== "create_project_task") throw new Error(`Unsupported proactive action: ${action.id}`);
|
|
12
|
+
const result = new TaskApplicationService().create({
|
|
13
|
+
idempotencyKey: `proactive:${row.insight_id}:${action.id}`,
|
|
14
|
+
title: action.input.title,
|
|
15
|
+
projectId: projectIdFromAggregationKey(row.aggregation_key),
|
|
16
|
+
priority: "normal",
|
|
17
|
+
contract: {
|
|
18
|
+
objective: action.input.objective,
|
|
19
|
+
expectedOutputs: [],
|
|
20
|
+
acceptanceCriteria: [],
|
|
21
|
+
constraints: [],
|
|
22
|
+
approvalRequired: [],
|
|
23
|
+
assumptions: [],
|
|
24
|
+
risks: [],
|
|
25
|
+
acceptancePolicy: "manual",
|
|
26
|
+
outputDestinations: []
|
|
27
|
+
},
|
|
28
|
+
dependencies: [],
|
|
29
|
+
context: [],
|
|
30
|
+
authorityGrants: [],
|
|
31
|
+
activation: {
|
|
32
|
+
mode: "capture",
|
|
33
|
+
phase: "backlog"
|
|
34
|
+
}
|
|
35
|
+
}, {
|
|
36
|
+
kind: "system",
|
|
37
|
+
id: "proactive"
|
|
38
|
+
});
|
|
39
|
+
if (!result.ok) throw new Error(`Task creation failed: ${"reason" in result ? result.reason : "unknown"}`);
|
|
40
|
+
getSqliteDatabase().prepare(`UPDATE proactive_insights
|
|
41
|
+
SET action_status = 'completed', action_result_json = ?, action_error = NULL, action_updated_at = ?
|
|
42
|
+
WHERE insight_id = ? AND action_status = 'executing'`).run(JSON.stringify({ taskId: result.model.task.id }), now.toISOString(), row.insight_id);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
getSqliteDatabase().prepare(`UPDATE proactive_insights
|
|
45
|
+
SET action_status = 'failed', action_error = ?, action_updated_at = ?
|
|
46
|
+
WHERE insight_id = ? AND action_status = 'executing'`).run(String(error instanceof Error ? error.message : error).slice(0, 2e3), now.toISOString(), row.insight_id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function executePendingProactiveActions(now = /* @__PURE__ */ new Date()) {
|
|
50
|
+
const expired = (/* @__PURE__ */ new Date(now.getTime() - 5 * 6e4)).toISOString();
|
|
51
|
+
getSqliteDatabase().prepare(`UPDATE proactive_insights SET action_status = 'pending', action_updated_at = ?
|
|
52
|
+
WHERE action_status = 'executing' AND action_updated_at <= ?`).run(now.toISOString(), expired);
|
|
53
|
+
let executed = 0;
|
|
54
|
+
while (true) {
|
|
55
|
+
const row = runSqliteWriteTransaction((db) => {
|
|
56
|
+
const candidate = db.prepare(`SELECT x.insight_id, b.aggregation_key, x.proposed_action_json
|
|
57
|
+
FROM proactive_insights x
|
|
58
|
+
JOIN proactive_runs r ON r.run_id = x.run_id
|
|
59
|
+
JOIN proactive_signal_batches b ON b.batch_id = r.batch_id
|
|
60
|
+
WHERE x.action_status = 'pending' ORDER BY x.created_at LIMIT 1`).get();
|
|
61
|
+
if (!candidate) return void 0;
|
|
62
|
+
return db.prepare(`UPDATE proactive_insights SET action_status = 'executing', action_updated_at = ?
|
|
63
|
+
WHERE insight_id = ? AND action_status = 'pending'`).run(now.toISOString(), candidate.insight_id).changes === 1 ? candidate : void 0;
|
|
64
|
+
});
|
|
65
|
+
if (!row) return executed;
|
|
66
|
+
executeClaimedAction(row, now);
|
|
67
|
+
executed += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function resolveProactiveActionDecision(inboxItemId, choice, now = /* @__PURE__ */ new Date()) {
|
|
71
|
+
const row = getSqliteDatabase().prepare(`SELECT x.insight_id, x.action_status
|
|
72
|
+
FROM proactive_inbox_items i JOIN proactive_insights x ON x.insight_id = i.insight_id
|
|
73
|
+
WHERE i.inbox_item_id = ?`).get(inboxItemId);
|
|
74
|
+
if (!row || row.action_status !== "approval_required") return;
|
|
75
|
+
getSqliteDatabase().prepare(`UPDATE proactive_insights SET action_status = ?, action_updated_at = ?
|
|
76
|
+
WHERE insight_id = ? AND action_status = 'approval_required'`).run(choice === "approve" ? "pending" : "rejected", now.toISOString(), row.insight_id);
|
|
77
|
+
if (choice === "approve") executePendingProactiveActions(now);
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { executePendingProactiveActions, resolveProactiveActionDecision };
|
|
@@ -30,6 +30,23 @@ function parseDecision(value) {
|
|
|
30
30
|
options
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
function parseProposedAction(value) {
|
|
34
|
+
if (value == null) return void 0;
|
|
35
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Insight proposedAction is invalid");
|
|
36
|
+
const row = value;
|
|
37
|
+
if (row.id !== "create_project_task" || row.risk !== "low") throw new Error("Insight proposedAction is not supported");
|
|
38
|
+
if (!row.input || typeof row.input !== "object" || Array.isArray(row.input)) throw new Error("Insight proposedAction input is invalid");
|
|
39
|
+
const input = row.input;
|
|
40
|
+
return {
|
|
41
|
+
id: "create_project_task",
|
|
42
|
+
risk: "low",
|
|
43
|
+
rationale: boundedText(row.rationale, "proposedAction rationale", 600),
|
|
44
|
+
input: {
|
|
45
|
+
title: boundedText(input.title, "proposedAction title", 160),
|
|
46
|
+
objective: boundedText(input.objective, "proposedAction objective", 1200)
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
33
50
|
function parseInsightCandidate(raw, allowedEvidenceIds) {
|
|
34
51
|
let value;
|
|
35
52
|
try {
|
|
@@ -45,6 +62,8 @@ function parseInsightCandidate(raw, allowedEvidenceIds) {
|
|
|
45
62
|
const evidenceIds = [...new Set(row.evidenceIds.map(String))];
|
|
46
63
|
if (evidenceIds.some((id) => !allowedEvidenceIds.has(id))) throw new Error("Insight cites unknown evidence");
|
|
47
64
|
const decision = parseDecision(row.decision);
|
|
65
|
+
const proposedAction = parseProposedAction(row.proposedAction);
|
|
66
|
+
if (proposedAction && (!decision || decision.options.length !== 2 || decision.options[0]?.id !== "approve" || decision.options[1]?.id !== "reject")) throw new Error("Insight proposedAction requires approve and reject decision options");
|
|
48
67
|
return {
|
|
49
68
|
title: boundedText(row.title, "title", 160),
|
|
50
69
|
summary: boundedText(row.summary, "summary", 1200),
|
|
@@ -53,6 +72,7 @@ function parseInsightCandidate(raw, allowedEvidenceIds) {
|
|
|
53
72
|
recommendation: boundedText(row.recommendation, "recommendation", 800),
|
|
54
73
|
workDone: boundedText(row.workDone, "workDone", 800),
|
|
55
74
|
...decision ? { decision } : {},
|
|
75
|
+
...proposedAction ? { proposedAction } : {},
|
|
56
76
|
urgency: String(row.urgency),
|
|
57
77
|
confidence: row.confidence,
|
|
58
78
|
evidenceIds
|
|
@@ -96,9 +96,9 @@ function finishRun(input, now = /* @__PURE__ */ new Date()) {
|
|
|
96
96
|
createdAt: nowIso
|
|
97
97
|
};
|
|
98
98
|
db.prepare(`INSERT INTO proactive_insights (insight_id, run_id, subscription_id, scenario_key, title, summary,
|
|
99
|
-
why_now, impact, recommendation, work_done, decision_json, urgency, confidence, value_score,
|
|
99
|
+
why_now, impact, recommendation, work_done, decision_json, proposed_action_json, urgency, confidence, value_score,
|
|
100
100
|
evidence_ids_json, content_fingerprint, created_at)
|
|
101
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(insight.id, insight.runId, insight.subscriptionId, insight.scenarioKey, insight.title, insight.summary, insight.whyNow, insight.impact, insight.recommendation, insight.workDone, insight.decision ? JSON.stringify(insight.decision) : null, insight.urgency, insight.confidence, insight.valueScore, JSON.stringify(insight.evidenceIds), fingerprint, insight.createdAt);
|
|
101
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(insight.id, insight.runId, insight.subscriptionId, insight.scenarioKey, insight.title, insight.summary, insight.whyNow, insight.impact, insight.recommendation, insight.workDone, insight.decision ? JSON.stringify(insight.decision) : null, insight.proposedAction ? JSON.stringify(insight.proposedAction) : null, insight.urgency, insight.confidence, insight.valueScore, JSON.stringify(insight.evidenceIds), fingerprint, insight.createdAt);
|
|
102
102
|
return insight;
|
|
103
103
|
});
|
|
104
104
|
}
|
|
@@ -124,9 +124,15 @@ function listInsights(limit = 50) {
|
|
|
124
124
|
recommendation: str(row, "recommendation"),
|
|
125
125
|
workDone: str(row, "work_done"),
|
|
126
126
|
...row.decision_json ? { decision: JSON.parse(str(row, "decision_json")) } : {},
|
|
127
|
+
...row.proposed_action_json ? { proposedAction: JSON.parse(str(row, "proposed_action_json")) } : {},
|
|
127
128
|
urgency: str(row, "urgency"),
|
|
128
129
|
confidence: Number(row.confidence),
|
|
129
130
|
valueScore: Number(row.value_score),
|
|
131
|
+
...row.disposition ? { disposition: str(row, "disposition") } : {},
|
|
132
|
+
...row.disposition_reason ? { dispositionReason: str(row, "disposition_reason") } : {},
|
|
133
|
+
...row.action_status ? { actionStatus: str(row, "action_status") } : {},
|
|
134
|
+
...row.action_result_json ? { actionResult: JSON.parse(str(row, "action_result_json")) } : {},
|
|
135
|
+
...row.action_error ? { actionError: str(row, "action_error") } : {},
|
|
130
136
|
evidenceIds: JSON.parse(str(row, "evidence_ids_json")),
|
|
131
137
|
createdAt: str(row, "created_at")
|
|
132
138
|
}));
|