@desplega.ai/agent-swarm 2.4.0 → 2.4.1
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/README.md +1 -1
- package/dist/{acp-adapter-kedstxx6.js → acp-adapter-zvzr538d.js} +9 -9
- package/dist/{actions-vget6r1k.js → actions-jhtqdw69.js} +21 -21
- package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-bd84rhs2.js} +4 -4
- package/dist/{app-mqap8t8m.js → app-kreq1gwg.js} +3 -3
- package/dist/{assistant-h0gv97mv.js → assistant-3k22sbsq.js} +22 -22
- package/dist/{boot-reembed-qr8a894z.js → boot-reembed-e53x6b9v.js} +1 -1
- package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-f6z923ny.js} +1 -1
- package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-0538vsb7.js} +4 -4
- package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-6wxqkfk2.js} +4 -4
- package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gfts5044.js} +9 -9
- package/dist/{claude-managed-adapter-1mwrsm9d.js → claude-managed-adapter-h2y889ge.js} +2 -2
- package/dist/{claude-managed-setup-gkfgks4j.js → claude-managed-setup-h00hsgtz.js} +2 -2
- package/dist/{claude-sdk-session-apb4ypz3.js → claude-sdk-session-56besmfz.js} +13 -13
- package/dist/{cli-nrrg0fvy.js → cli-0bh477zn.js} +18 -5
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-1cevz46c.js → cli-3kh4kpqy.js} +6 -6
- package/dist/{cli-tg9ksbn8.js → cli-3p8fqcnh.js} +2 -2
- package/dist/{cli-x1p5dhq2.js → cli-3r448dqn.js} +3 -3
- package/dist/{cli-ecshf504.js → cli-3tnpj2bh.js} +7 -7
- package/dist/{cli-ffbrkh1r.js → cli-471qk6s2.js} +46 -38
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-zvze9rbm.js → cli-6ncjvx55.js} +3 -3
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
- package/dist/{cli-y83zk5sb.js → cli-a0kcty51.js} +4 -4
- package/dist/{cli-8b9wvjps.js → cli-a4gvwttf.js} +4 -4
- package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
- package/dist/{cli-mz1ezzzh.js → cli-ash6xxk7.js} +2 -2
- package/dist/{cli-rsd7ga7v.js → cli-axh29tyr.js} +11 -11
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-6ftrrwm1.js → cli-bn5kevnf.js} +5 -5
- package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
- package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
- package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
- package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
- package/dist/{cli-8cxkw1f5.js → cli-fz9kewbh.js} +2 -2
- package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
- package/dist/{cli-kz116kvw.js → cli-gf0kfg81.js} +7 -7
- package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-ywyrffff.js → cli-jh0qcrga.js} +1 -1
- package/dist/{cli-ttaz6c7m.js → cli-k9z4zd4p.js} +6 -5
- package/dist/{cli-nme7wery.js → cli-kmqgmcnb.js} +46 -13
- package/dist/{cli-x719j3vs.js → cli-kr2pmfqx.js} +392 -168
- package/dist/{cli-wrv09v7q.js → cli-m2qgh5sv.js} +1 -1
- package/dist/{cli-xfe63zx3.js → cli-mfe7f7b5.js} +2 -2
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-5s5hx57y.js → cli-nx462e4v.js} +1 -1
- package/dist/{cli-bfc3k1x5.js → cli-p0rakbrk.js} +9 -9
- package/dist/{cli-avmqhatn.js → cli-p6argxh9.js} +8 -8
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
- package/dist/{cli-sppc0cmw.js → cli-pnp2j5zg.js} +2 -2
- package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
- package/dist/{cli-bhwa8rzc.js → cli-rdq7tzwm.js} +13 -12
- package/dist/{cli-rg7ntypm.js → cli-rqqsfz30.js} +379 -87
- package/dist/{cli-kk2r80n5.js → cli-s48yvzb8.js} +4 -4
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-b80a1100.js → cli-sjyetj21.js} +2 -2
- package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
- package/dist/{cli-2rwet3r7.js → cli-sv5gb0yb.js} +1 -1
- package/dist/{cli-5ff2sjyt.js → cli-syhq378v.js} +2 -2
- package/dist/{cli-tj4j2ts4.js → cli-tbhwrxpt.js} +8 -8
- package/dist/{cli-4fhyq711.js → cli-vy0fay6k.js} +13 -13
- package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
- package/dist/{cli-f3nx1h8z.js → cli-wxbn3yfx.js} +2 -2
- package/dist/{cli-wyrr38xm.js → cli-x5mq1fyq.js} +1 -1
- package/dist/{cli-ccn0eybf.js → cli-xbzvet96.js} +6 -6
- package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
- package/dist/{cli-tgvpmnwz.js → cli-xftrvxag.js} +5 -5
- package/dist/{cli-02vhhssk.js → cli-z071fb95.js} +5 -5
- package/dist/{cli-mvg2hf8p.js → cli-z1ew8vbq.js} +14 -7
- package/dist/{cli-dsgj3e7a.js → cli-zfg9enh9.js} +2 -2
- package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-p6rmm7vv.js} +6 -6
- package/dist/{codex-hook-qwzccd64.js → codex-hook-x7nejyrr.js} +7 -4
- package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
- package/dist/{codex-session-runner-0vzn09t8.js → codex-session-runner-8vd5t9t0.js} +7 -7
- package/dist/{commands-psqhkttb.js → commands-7ems2p13.js} +2 -2
- package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-bg33ae53.js} +23 -23
- package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-2xbyenjr.js → db-w505dwa8.js} +2 -2
- package/dist/{db-nc1rd869.js → db-ys2rbdft.js} +3 -3
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-ewceb8ts.js → dispatcher-0s0mckqr.js} +20 -20
- package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
- package/dist/{dsh-adapter-75xwh78t.js → dsh-adapter-3n3net33.js} +3 -3
- package/dist/{e2b-5r6a5jeb.js → e2b-7am6tmq6.js} +2 -2
- package/dist/{e2b-pw1nvtmh.js → e2b-thdcxtjd.js} +2 -2
- package/dist/{easter-egg-3d-xsc47jwf.js → easter-egg-3d-5cgynaja.js} +1 -1
- package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
- package/dist/{execute-gzmw1mnw.js → execute-t4e6m3rw.js} +1 -1
- package/dist/{external-channel-a5dsmxn4.js → external-channel-5b23fv8z.js} +2 -2
- package/dist/{grok-adapter-vj88xr23.js → grok-adapter-q9fghe5q.js} +10 -10
- package/dist/handlers-mqrym347.js +39 -0
- package/dist/{heartbeat-t2m9r3wa.js → heartbeat-tww6yx1w.js} +23 -23
- package/dist/{hook-xa677f8r.js → hook-dx0pkvar.js} +16 -9
- package/dist/{hook-8femx1bg.js → hook-pvmh9b6m.js} +6 -6
- package/dist/{http-xpmmq028.js → http-k2ejbt05.js} +199 -131
- package/dist/{index-s7ckdajq.js → index-2z710v4x.js} +10 -10
- package/dist/{index-dg2tmj99.js → index-dgz61nff.js} +20 -20
- package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
- package/dist/index-h2ek08d6.js +40 -0
- package/dist/index-pqfwh9nn.js +41 -0
- package/dist/{index-cdzyfrsp.js → index-x5aktmp8.js} +1 -1
- package/dist/{keepalive-3nr4st3r.js → keepalive-nhvpw10z.js} +4 -4
- package/dist/lead-jb6sf69z.js +78 -0
- package/dist/{maintenance-verwpe2c.js → maintenance-agakc519.js} +1 -1
- package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-4dcbhtn0.js} +3 -3
- package/dist/{onboard-aytqedmq.js → onboard-4n2kwa4v.js} +3 -3
- package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-hckw4wt1.js → otel-impl-tfa12080.js} +2 -2
- package/dist/{pi-mono-adapter-hxby0ce1.js → pi-mono-adapter-ytp7gz8b.js} +22 -11
- package/dist/{pricing-refresh-5nvme3sm.js → pricing-refresh-3cqx3f3x.js} +3 -3
- package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-9fae5rnv.js} +1 -1
- package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-y6zbfzve.js} +2 -2
- package/dist/{render-v2-7nqgvk2q.js → render-v2-emawjvc4.js} +3 -3
- package/dist/{resume-b6djcj3c.js → resume-zhgts7h7.js} +10 -8
- package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/{session-access-fc3f9y12.js → session-access-cm823e06.js} +4 -4
- package/dist/{setup-n39szm6b.js → setup-tkp99w1e.js} +1 -1
- package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-jem2fre0.js → work-objects-f722h263.js} +23 -23
- package/dist/worker-p7f336rq.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +99 -52
- package/package.json +2 -2
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/row-store.ts +4 -2
- package/src/apps/script-types.ts +1 -1
- package/src/be/db.ts +14 -4
- package/src/be/fleet-benchmark-stats.ts +10 -5
- package/src/be/models-catalog.ts +4 -0
- package/src/be/onboarding.ts +6 -0
- package/src/be/scripts/typecheck.ts +13 -0
- package/src/be/swarm-config-guard.ts +10 -1
- package/src/be/telemetry-enable.ts +1 -1
- package/src/commands/provider-credentials.ts +12 -2
- package/src/commands/runner.ts +3 -0
- package/src/heartbeat/heartbeat.ts +2 -2
- package/src/hooks/codex-hook.ts +6 -2
- package/src/hooks/hook.ts +7 -0
- package/src/hooks/pr-body-guard.ts +38 -11
- package/src/hooks/pr-room-guard.ts +88 -0
- package/src/http/agents.ts +1 -0
- package/src/http/apps.ts +84 -181
- package/src/http/fleet-benchmark.ts +79 -30
- package/src/http/global-config.ts +7 -0
- package/src/http/ranking.ts +24 -40
- package/src/http/tasks.ts +2 -1
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/used-by.ts +55 -19
- package/src/http/users.ts +2 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +15 -0
- package/src/providers/reasoning-effort.ts +2 -1
- package/src/providers/types.ts +2 -0
- package/src/ranking/telemetry-ranking.ts +5 -33
- package/src/scripts-runtime/sdk-allowlist.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +31 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
- package/src/server.ts +4 -0
- package/src/telemetry-context.ts +19 -1
- package/src/telemetry-contract/EVENTS.md +42 -8
- package/src/telemetry-contract/SOURCE.json +6 -5
- package/src/telemetry-contract/catalog.json +225 -5
- package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
- package/src/telemetry-contract/ranking/listing.v1.schema.json +2 -2
- package/src/telemetry-contract/types.gen.ts +225 -27
- package/src/telemetry-identity.ts +47 -9
- package/src/telemetry-snapshot.ts +9 -1
- package/src/telemetry-ui-events.ts +9 -2
- package/src/telemetry.ts +14 -3
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +360 -0
- package/src/tests/check-pr-body.test.ts +0 -23
- package/src/tests/credential-check.test.ts +38 -0
- package/src/tests/fleet-benchmark.test.ts +122 -2
- package/src/tests/http-users.test.ts +62 -1
- package/src/tests/model-catalog-table.test.ts +63 -0
- package/src/tests/model-profiles-api.test.ts +19 -7
- package/src/tests/models-catalog.test.ts +50 -0
- package/src/tests/onboarding.test.ts +21 -0
- package/src/tests/org-website.test.ts +62 -0
- package/src/tests/pi-mono-adapter.test.ts +23 -0
- package/src/tests/pr-body-leaks.test.ts +4 -0
- package/src/tests/pr-room-guard.test.ts +242 -0
- package/src/tests/ranking-listing.test.ts +8 -23
- package/src/tests/ranking-route.test.ts +49 -47
- package/src/tests/reasoning-effort.test.ts +39 -3
- package/src/tests/runner-harness-telemetry-env.test.ts +56 -1
- package/src/tests/swarm-tool-result-gate.test.ts +2 -4
- package/src/tests/telemetry-context.test.ts +128 -2
- package/src/tests/telemetry-ui-route.test.ts +123 -0
- package/src/tests/tool-annotations.test.ts +3 -2
- package/src/tests/used-by-route.test.ts +144 -10
- package/src/tests/workflow-agent-task-retry.test.ts +130 -4
- package/src/tests/workflow-wait-recovery.test.ts +116 -4
- package/src/tools/app-action-run.ts +63 -0
- package/src/tools/app-rows.ts +234 -0
- package/src/tools/model-catalog/index.ts +1 -1
- package/src/tools/tool-config.ts +8 -0
- package/src/tools/utils.ts +1 -1
- package/src/tools/workflows/retry-workflow-run.ts +1 -1
- package/src/types.ts +17 -0
- package/src/utils/company-profile.ts +24 -0
- package/src/utils/org-website.ts +58 -21
- package/src/workflows/recovery.ts +10 -22
- package/src/workflows/resume.ts +16 -0
- package/src/workflows/task-step-routing.ts +47 -7
- package/templates/skills/apps/SKILL.md +41 -2
- package/templates/skills/apps/content.md +41 -2
- package/dist/handlers-zbs90bny.js +0 -39
- package/dist/index-8pwc1jc8.js +0 -40
- package/dist/index-nxa6me0q.js +0 -41
- package/dist/lead-td8h2sfn.js +0 -78
- package/dist/worker-wr6xgfr2.js +0 -78
- package/src/tests/check-ranking-entry.test.ts +0 -146
package/src/http/apps.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import type { AppActionCaller } from "../apps/action-caller";
|
|
4
|
+
import { runAppAction } from "../apps/action-runner";
|
|
4
5
|
import {
|
|
5
6
|
type AppDefinition,
|
|
6
7
|
AppNameSchema,
|
|
@@ -61,14 +62,9 @@ import {
|
|
|
61
62
|
rollbackApp,
|
|
62
63
|
snapshotApp,
|
|
63
64
|
} from "../apps/version";
|
|
64
|
-
import {
|
|
65
|
-
import { getAgentById, getAppVersion, getAppVersions, getLeadAgent } from "../be/db";
|
|
66
|
-
import { getScriptById } from "../be/scripts/db";
|
|
67
|
-
import { getSavedScriptOwnerAgentId, runSavedScriptAsAgent } from "../be/scripts/run-saved";
|
|
68
|
-
import { resolveTemplate } from "../prompts/resolver";
|
|
65
|
+
import { getAgentById, getAppVersion, getAppVersions } from "../be/db";
|
|
69
66
|
import type { RbacPrincipal, RbacResource } from "../rbac";
|
|
70
67
|
import { can } from "../rbac";
|
|
71
|
-
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
72
68
|
import { AgentTaskStatusSchema } from "../types";
|
|
73
69
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
74
70
|
import { scrubObject } from "../utils/secret-scrubber";
|
|
@@ -77,8 +73,8 @@ import { route } from "./route-def";
|
|
|
77
73
|
import { BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError } from "./utils";
|
|
78
74
|
|
|
79
75
|
const MAX_APP_BODY_BYTES = 5 * 1024 * 1024;
|
|
80
|
-
const MAX_APP_ROW_BODY_BYTES = 1 * 1024 * 1024;
|
|
81
|
-
const MAX_APP_BULK_ROWS_BODY_BYTES = 10 * 1024 * 1024;
|
|
76
|
+
export const MAX_APP_ROW_BODY_BYTES = 1 * 1024 * 1024;
|
|
77
|
+
export const MAX_APP_BULK_ROWS_BODY_BYTES = 10 * 1024 * 1024;
|
|
82
78
|
const MAX_USER_CONFIG_BODY_BYTES = 64 * 1024;
|
|
83
79
|
const MAX_USER_CONFIG_VALUES_BYTES = 16 * 1024;
|
|
84
80
|
const DECIMAL_NUMBER_PATTERN = /^[+-]?\d+(?:\.\d+)?$/;
|
|
@@ -756,17 +752,6 @@ function definitionNeedsRepair(
|
|
|
756
752
|
return true;
|
|
757
753
|
}
|
|
758
754
|
|
|
759
|
-
/**
|
|
760
|
-
* One `error` string for a sync result, or undefined when every pass succeeded.
|
|
761
|
-
* Each failed pair is named so a multi-pair sync says which one broke; the pass
|
|
762
|
-
* list in `result.passes` carries the rest.
|
|
763
|
-
*/
|
|
764
|
-
function syncPassError(sync: Awaited<ReturnType<typeof runAppSync>>): string | undefined {
|
|
765
|
-
const failed = sync.passes.filter((pass) => pass.error !== undefined);
|
|
766
|
-
if (failed.length === 0) return undefined;
|
|
767
|
-
return failed.map((pass) => `${pass.model}.${pass.source}: ${pass.error}`).join("; ");
|
|
768
|
-
}
|
|
769
|
-
|
|
770
755
|
function snapshotFailure(res: ServerResponse): void {
|
|
771
756
|
jsonError(res, "failed to snapshot app", 500);
|
|
772
757
|
}
|
|
@@ -991,6 +976,10 @@ function rowValue(row: AppRow, column: string): unknown {
|
|
|
991
976
|
return Object.hasOwn(row, column) ? row[column] : undefined;
|
|
992
977
|
}
|
|
993
978
|
|
|
979
|
+
export function rowMatchesFilters(row: AppRow, filters: RowFilter[]): boolean {
|
|
980
|
+
return filters.every((filter) => rowValue(row, filter.column) === filter.value);
|
|
981
|
+
}
|
|
982
|
+
|
|
994
983
|
export function applyQuery(
|
|
995
984
|
rows: AppRow[],
|
|
996
985
|
query: AppQueryDef,
|
|
@@ -1019,6 +1008,59 @@ export function applyQuery(
|
|
|
1019
1008
|
return selected.slice(0, query.limit ?? 200);
|
|
1020
1009
|
}
|
|
1021
1010
|
|
|
1011
|
+
export async function selectAppRows(
|
|
1012
|
+
appId: string,
|
|
1013
|
+
modelName: string,
|
|
1014
|
+
model: ModelDef,
|
|
1015
|
+
filters: RowFilter[],
|
|
1016
|
+
sortRaw?: string,
|
|
1017
|
+
limit = 200,
|
|
1018
|
+
systemColumns = false,
|
|
1019
|
+
) {
|
|
1020
|
+
// SQL narrows the scan; the `===` re-check keeps the exact filter semantics.
|
|
1021
|
+
const rows = (
|
|
1022
|
+
await listAppRows(
|
|
1023
|
+
appId,
|
|
1024
|
+
modelName,
|
|
1025
|
+
filters.flatMap((filter) =>
|
|
1026
|
+
typeof filter.value === "string" ||
|
|
1027
|
+
typeof filter.value === "number" ||
|
|
1028
|
+
typeof filter.value === "boolean"
|
|
1029
|
+
? [{ column: filter.column, value: filter.value }]
|
|
1030
|
+
: [],
|
|
1031
|
+
),
|
|
1032
|
+
)
|
|
1033
|
+
).filter((row) => rowMatchesFilters(row, filters));
|
|
1034
|
+
|
|
1035
|
+
if (sortRaw) {
|
|
1036
|
+
const [column, dir, extra] = sortRaw.split(":");
|
|
1037
|
+
if (
|
|
1038
|
+
extra !== undefined ||
|
|
1039
|
+
!column ||
|
|
1040
|
+
(dir !== "asc" && dir !== "desc") ||
|
|
1041
|
+
(!SORTABLE_SYSTEM_DATE_COLUMNS.has(column) &&
|
|
1042
|
+
!(systemColumns && Object.hasOwn(SYSTEM_COLUMN_KINDS, column)) &&
|
|
1043
|
+
(!Object.hasOwn(model.columns, column) || model.columns[column]!.hidden === true))
|
|
1044
|
+
) {
|
|
1045
|
+
throw new AppRowValidationError([
|
|
1046
|
+
{ path: "sort", message: "must be <column>:<asc|desc> for a known column" },
|
|
1047
|
+
]);
|
|
1048
|
+
}
|
|
1049
|
+
rows.sort((a, b) => {
|
|
1050
|
+
const result = compareValues(
|
|
1051
|
+
rowValue(a, column),
|
|
1052
|
+
rowValue(b, column),
|
|
1053
|
+
dir,
|
|
1054
|
+
SORTABLE_SYSTEM_DATE_COLUMNS.has(column) ||
|
|
1055
|
+
(Object.hasOwn(model.columns, column) && model.columns[column]!.kind === "date"),
|
|
1056
|
+
);
|
|
1057
|
+
return result;
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
const total = rows.length;
|
|
1061
|
+
return { rows: rows.slice(0, limit), total };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1022
1064
|
export async function handleApps(
|
|
1023
1065
|
req: IncomingMessage,
|
|
1024
1066
|
res: ServerResponse,
|
|
@@ -1189,55 +1231,23 @@ export async function handleApps(
|
|
|
1189
1231
|
json(res, { error: "invalid row query", issues }, 400);
|
|
1190
1232
|
return true;
|
|
1191
1233
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1234
|
+
try {
|
|
1235
|
+
listRowsRoute.respond(
|
|
1236
|
+
res,
|
|
1237
|
+
200,
|
|
1238
|
+
await selectAppRows(
|
|
1239
|
+
parsed.params.id,
|
|
1240
|
+
parsed.params.model,
|
|
1241
|
+
resolved.model,
|
|
1242
|
+
filters,
|
|
1243
|
+
parsed.query.sort,
|
|
1244
|
+
parsed.query.limit,
|
|
1203
1245
|
),
|
|
1204
|
-
)
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const [column, dir, extra] = sortRaw.split(":");
|
|
1209
|
-
if (
|
|
1210
|
-
extra !== undefined ||
|
|
1211
|
-
!column ||
|
|
1212
|
-
(dir !== "asc" && dir !== "desc") ||
|
|
1213
|
-
(!SORTABLE_SYSTEM_DATE_COLUMNS.has(column) &&
|
|
1214
|
-
(!Object.hasOwn(resolved.model.columns, column) ||
|
|
1215
|
-
resolved.model.columns[column]!.hidden === true))
|
|
1216
|
-
) {
|
|
1217
|
-
json(
|
|
1218
|
-
res,
|
|
1219
|
-
{
|
|
1220
|
-
error: "invalid sort",
|
|
1221
|
-
issues: [{ path: "sort", message: "must be <column>:<asc|desc> for a known column" }],
|
|
1222
|
-
},
|
|
1223
|
-
400,
|
|
1224
|
-
);
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
rows.sort((a, b) => {
|
|
1228
|
-
const result = compareValues(
|
|
1229
|
-
rowValue(a, column),
|
|
1230
|
-
rowValue(b, column),
|
|
1231
|
-
dir,
|
|
1232
|
-
SORTABLE_SYSTEM_DATE_COLUMNS.has(column) ||
|
|
1233
|
-
(Object.hasOwn(resolved.model.columns, column) &&
|
|
1234
|
-
resolved.model.columns[column]!.kind === "date"),
|
|
1235
|
-
);
|
|
1236
|
-
return result;
|
|
1237
|
-
});
|
|
1246
|
+
);
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
if (!(error instanceof AppRowValidationError)) throw error;
|
|
1249
|
+
json(res, { error: "invalid sort", issues: error.issues }, 400);
|
|
1238
1250
|
}
|
|
1239
|
-
const total = rows.length;
|
|
1240
|
-
listRowsRoute.respond(res, 200, { rows: rows.slice(0, parsed.query.limit ?? 200), total });
|
|
1241
1251
|
return true;
|
|
1242
1252
|
}
|
|
1243
1253
|
|
|
@@ -1359,122 +1369,15 @@ export async function handleApps(
|
|
|
1359
1369
|
return true;
|
|
1360
1370
|
}
|
|
1361
1371
|
if (definitionNeedsRepair(res, app)) return true;
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
const action = actions[parsed.params.name]!;
|
|
1369
|
-
const input = parsed.body.input ?? {};
|
|
1370
|
-
if (action.kind === "script") {
|
|
1371
|
-
const script = await getScriptById(action.scriptId);
|
|
1372
|
-
if (!script) {
|
|
1373
|
-
json(
|
|
1374
|
-
res,
|
|
1375
|
-
{
|
|
1376
|
-
error: "invalid app action",
|
|
1377
|
-
issues: [
|
|
1378
|
-
{
|
|
1379
|
-
path: `actions.${parsed.params.name}.scriptId`,
|
|
1380
|
-
message: `script "${action.scriptId}" no longer exists`,
|
|
1381
|
-
},
|
|
1382
|
-
],
|
|
1383
|
-
},
|
|
1384
|
-
400,
|
|
1385
|
-
);
|
|
1386
|
-
return true;
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
// Script actions run with the OWNER's bindings by design: wiring a script
|
|
1390
|
-
// into an app is the owner's delegation. parseAppDefinition enforces at
|
|
1391
|
-
// write time that an agent may only wire scripts it owns (or global ones),
|
|
1392
|
-
// so a foreign script id cannot be smuggled in here. Invoker-brokered
|
|
1393
|
-
// credentials remain a possible future tightening.
|
|
1394
|
-
const runAsAgentId = getSavedScriptOwnerAgentId(script);
|
|
1395
|
-
if (!runAsAgentId) {
|
|
1396
|
-
jsonError(res, "agentId is required: this script has no owning agent to run as", 400);
|
|
1397
|
-
return true;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const output = await runSavedScriptAsAgent({
|
|
1401
|
-
script,
|
|
1402
|
-
input: { ...action.args, ...input, app: { id: app.id }, caller },
|
|
1403
|
-
agentId: runAsAgentId,
|
|
1404
|
-
});
|
|
1405
|
-
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
1406
|
-
const error = ok
|
|
1407
|
-
? undefined
|
|
1408
|
-
: output.runtimeError
|
|
1409
|
-
? `${output.runtimeError.name}: ${output.runtimeError.message}`
|
|
1410
|
-
: (output.error ?? `Script exited with code ${output.exitCode}`);
|
|
1411
|
-
runActionRoute.respond(
|
|
1412
|
-
res,
|
|
1413
|
-
200,
|
|
1414
|
-
scrubObject({
|
|
1415
|
-
ok,
|
|
1416
|
-
result: output.result,
|
|
1417
|
-
stdout: output.stdout,
|
|
1418
|
-
...(error === undefined ? {} : { error }),
|
|
1419
|
-
durationMs: output.durationMs,
|
|
1420
|
-
}),
|
|
1421
|
-
);
|
|
1422
|
-
return true;
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
if (action.kind === "sync") {
|
|
1426
|
-
const startedAt = Date.now();
|
|
1427
|
-
const sync = await runAppSync({
|
|
1428
|
-
appId: app.id,
|
|
1429
|
-
...(action.model === undefined ? {} : { model: action.model }),
|
|
1430
|
-
...(action.source === undefined ? {} : { source: action.source }),
|
|
1431
|
-
invokedBy: actor,
|
|
1432
|
-
});
|
|
1433
|
-
const durationMs = Date.now() - startedAt;
|
|
1434
|
-
if (sync.issues && sync.issues.length > 0) {
|
|
1435
|
-
json(res, { error: "invalid sync action", issues: sync.issues }, 400);
|
|
1436
|
-
return true;
|
|
1437
|
-
}
|
|
1438
|
-
// Deliberately the SCRIPT-action response shape, minus `taskId`:
|
|
1439
|
-
// app-surface.tsx branches on `taskId` first, so omitting it gives the
|
|
1440
|
-
// dashboard running -> ok/error + refetchAll() with zero UI changes.
|
|
1441
|
-
const error = syncPassError(sync);
|
|
1442
|
-
runActionRoute.respond(
|
|
1443
|
-
res,
|
|
1444
|
-
200,
|
|
1445
|
-
scrubObject({
|
|
1446
|
-
ok: sync.ok,
|
|
1447
|
-
result: { passes: sync.passes },
|
|
1448
|
-
...(error === undefined ? {} : { error }),
|
|
1449
|
-
durationMs,
|
|
1450
|
-
}),
|
|
1451
|
-
);
|
|
1452
|
-
return true;
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
const lead = action.agentId ? null : await getLeadAgent();
|
|
1456
|
-
const taskPrompt = resolveTemplate("task.app.action", {
|
|
1457
|
-
prompt: action.prompt,
|
|
1458
|
-
app_id: app.id,
|
|
1459
|
-
action_name: parsed.params.name,
|
|
1460
|
-
input_json: JSON.stringify(input),
|
|
1461
|
-
caller_json: JSON.stringify(caller),
|
|
1372
|
+
const output = await runAppAction({
|
|
1373
|
+
app,
|
|
1374
|
+
name: parsed.params.name,
|
|
1375
|
+
input: parsed.body.input,
|
|
1376
|
+
caller,
|
|
1377
|
+
actor,
|
|
1462
1378
|
});
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
{
|
|
1466
|
-
source: "api",
|
|
1467
|
-
agentId: action.agentId ?? lead?.id,
|
|
1468
|
-
routingReason: action.agentId ? "human_pinned" : lead ? "skill" : undefined,
|
|
1469
|
-
routingSource: action.agentId || lead ? "engine_default" : undefined,
|
|
1470
|
-
// App-spawned tasks group under the app's asset namespace so a
|
|
1471
|
-
// swarm-tasks source can pull them back via config.assetKey.
|
|
1472
|
-
key: normalizeAssetKey(`shared/app:${app.id}/action:${parsed.params.name}/`),
|
|
1473
|
-
...(actor.startsWith("user:") ? { requestedByUserId: actor.slice("user:".length) } : {}),
|
|
1474
|
-
},
|
|
1475
|
-
{ origin: "app" },
|
|
1476
|
-
);
|
|
1477
|
-
runActionRoute.respond(res, 200, { ok: true, taskId: task.id, status: task.status });
|
|
1379
|
+
if (output.status === 200) runActionRoute.respond(res, 200, output.body);
|
|
1380
|
+
else json(res, output.body, output.status);
|
|
1478
1381
|
return true;
|
|
1479
1382
|
}
|
|
1480
1383
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { getSwarmConfigs } from "../be/db";
|
|
4
3
|
import { getLocalFleetStats } from "../be/fleet-benchmark-stats";
|
|
5
4
|
import {
|
|
6
5
|
compareWithFleet,
|
|
@@ -12,6 +11,9 @@ import {
|
|
|
12
11
|
localTasksPerWeek,
|
|
13
12
|
} from "../fleet-benchmark";
|
|
14
13
|
import { isTelemetryEnabled } from "../telemetry";
|
|
14
|
+
import { COMPANY_TEAM_SIZES, COMPANY_USE_CASES } from "../utils/company-profile";
|
|
15
|
+
import { normalizeOrgWebsite } from "../utils/org-website";
|
|
16
|
+
import { globalConfig } from "./global-config";
|
|
15
17
|
import { rejectGuest } from "./request-principal";
|
|
16
18
|
import { route } from "./route-def";
|
|
17
19
|
import { telemetryProxyEndpoint } from "./used-by";
|
|
@@ -34,6 +36,14 @@ const FleetStateSchema = z.enum([
|
|
|
34
36
|
|
|
35
37
|
const FleetBenchmarkResponseSchema = z.object({
|
|
36
38
|
state: FleetStateSchema,
|
|
39
|
+
level: z.enum(["basic", "full"]),
|
|
40
|
+
peerGroup: z
|
|
41
|
+
.object({
|
|
42
|
+
teamSize: z.string().nullable(),
|
|
43
|
+
useCase: z.string().nullable(),
|
|
44
|
+
orgs: z.number().int(),
|
|
45
|
+
})
|
|
46
|
+
.nullable(),
|
|
37
47
|
local: z
|
|
38
48
|
.object({
|
|
39
49
|
tasksPerWeek: z.number(),
|
|
@@ -67,7 +77,7 @@ const fleetBenchmark = route({
|
|
|
67
77
|
pattern: ["api", "fleet-benchmark"],
|
|
68
78
|
summary: "Compare this swarm with similar swarms",
|
|
69
79
|
description:
|
|
70
|
-
"
|
|
80
|
+
"Compares local task volume and success rate with fleet percentiles over the last 30 days. Comparisons require telemetry, an organization name, and a known proxy ingest date. A website, team size, and use case enable full comparisons with peer filters. The response includes the proxy's peer group when available. With telemetry off, it returns local DB counts without a proxy request. The fleet cache key includes the peer filters.",
|
|
71
81
|
tags: ["Telemetry"],
|
|
72
82
|
responses: {
|
|
73
83
|
200: {
|
|
@@ -81,8 +91,12 @@ const fleetBenchmark = route({
|
|
|
81
91
|
|
|
82
92
|
// ─── Caches ──────────────────────────────────────────────────────────────────
|
|
83
93
|
|
|
84
|
-
let fleetCache: { value:
|
|
85
|
-
let localCache: {
|
|
94
|
+
let fleetCache: { key: string; value: ProxyFleetBenchmarks | null; until: number } | null = null;
|
|
95
|
+
let localCache: {
|
|
96
|
+
requireIngestAnchor: boolean;
|
|
97
|
+
value: LocalFleetStats | null;
|
|
98
|
+
until: number;
|
|
99
|
+
} | null = null;
|
|
86
100
|
|
|
87
101
|
const ProxyMetricSchema = z.object({
|
|
88
102
|
n: z.number(),
|
|
@@ -112,19 +126,34 @@ const ProxyFleetBenchmarksSchema = z.object({
|
|
|
112
126
|
"2-5": ProxyBlockSchema.optional(),
|
|
113
127
|
"6+": ProxyBlockSchema.optional(),
|
|
114
128
|
}),
|
|
129
|
+
peer_group: z
|
|
130
|
+
.object({
|
|
131
|
+
team_size: z.string().nullable(),
|
|
132
|
+
use_case: z.string().nullable(),
|
|
133
|
+
orgs: z.number().int().nonnegative(),
|
|
134
|
+
})
|
|
135
|
+
.nullable()
|
|
136
|
+
.optional(),
|
|
115
137
|
}) satisfies z.ZodType<FleetBenchmarks>;
|
|
116
138
|
|
|
139
|
+
type ProxyFleetBenchmarks = z.infer<typeof ProxyFleetBenchmarksSchema>;
|
|
140
|
+
|
|
117
141
|
/** The proxy's fleet percentiles. The request carries no install ID. */
|
|
118
|
-
async function fleetBenchmarks(
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
async function fleetBenchmarks(
|
|
143
|
+
now: number,
|
|
144
|
+
peerGroup: { teamSize: string; useCase: string } | null,
|
|
145
|
+
): Promise<ProxyFleetBenchmarks | null> {
|
|
146
|
+
const endpoint = new URL(await telemetryProxyEndpoint("/v1/fleet/benchmarks"));
|
|
147
|
+
endpoint.searchParams.set("window", "30d");
|
|
148
|
+
if (peerGroup) {
|
|
149
|
+
endpoint.searchParams.set("team_size", peerGroup.teamSize);
|
|
150
|
+
endpoint.searchParams.set("use_case", peerGroup.useCase);
|
|
151
|
+
}
|
|
152
|
+
const key = endpoint.toString();
|
|
153
|
+
if (fleetCache?.key === key && fleetCache.until > now) return fleetCache.value;
|
|
154
|
+
let value: ProxyFleetBenchmarks | null = null;
|
|
121
155
|
try {
|
|
122
|
-
const response = await fetch(
|
|
123
|
-
`${await telemetryProxyEndpoint("/v1/fleet/benchmarks")}?window=30d`,
|
|
124
|
-
{
|
|
125
|
-
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
126
|
-
},
|
|
127
|
-
);
|
|
156
|
+
const response = await fetch(key, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
128
157
|
if (response.ok) {
|
|
129
158
|
const parsed = ProxyFleetBenchmarksSchema.safeParse(await response.json());
|
|
130
159
|
if (parsed.success) value = parsed.data;
|
|
@@ -132,38 +161,46 @@ async function fleetBenchmarks(now: number): Promise<FleetBenchmarks | null> {
|
|
|
132
161
|
} catch {
|
|
133
162
|
// Unreachable proxy: the card says the comparison is unavailable.
|
|
134
163
|
}
|
|
135
|
-
fleetCache = { value, until: now + (value ? FLEET_TTL_MS : FLEET_FAILURE_TTL_MS) };
|
|
164
|
+
fleetCache = { key, value, until: now + (value ? FLEET_TTL_MS : FLEET_FAILURE_TTL_MS) };
|
|
136
165
|
return value;
|
|
137
166
|
}
|
|
138
167
|
|
|
139
|
-
async function localStats(now: Date): Promise<LocalFleetStats | null> {
|
|
140
|
-
if (localCache && localCache.until > now.getTime())
|
|
141
|
-
|
|
142
|
-
|
|
168
|
+
async function localStats(now: Date, requireIngestAnchor = true): Promise<LocalFleetStats | null> {
|
|
169
|
+
if (localCache?.requireIngestAnchor === requireIngestAnchor && localCache.until > now.getTime())
|
|
170
|
+
return localCache.value;
|
|
171
|
+
const value = await getLocalFleetStats(now, { requireIngestAnchor });
|
|
172
|
+
localCache = { requireIngestAnchor, value, until: now.getTime() + LOCAL_TTL_MS };
|
|
143
173
|
return value;
|
|
144
174
|
}
|
|
145
175
|
|
|
146
|
-
async function orgName(): Promise<string | undefined> {
|
|
147
|
-
const stored = (
|
|
148
|
-
await getSwarmConfigs({ scope: "global", key: "SWARM_ORG_NAME" })
|
|
149
|
-
)?.[0]?.value?.trim();
|
|
150
|
-
return stored || process.env.SWARM_ORG_NAME?.trim() || undefined;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
176
|
export async function buildFleetBenchmarkResponse(
|
|
154
177
|
now = new Date(),
|
|
155
178
|
): Promise<FleetBenchmarkResponse> {
|
|
179
|
+
const [name, website, teamSize, useCase] = await Promise.all([
|
|
180
|
+
globalConfig("SWARM_ORG_NAME"),
|
|
181
|
+
globalConfig("SWARM_ORG_WEBSITE"),
|
|
182
|
+
globalConfig("SWARM_ORG_TEAM_SIZE"),
|
|
183
|
+
globalConfig("SWARM_ORG_USE_CASE"),
|
|
184
|
+
]);
|
|
185
|
+
const team = z.enum(COMPANY_TEAM_SIZES).safeParse(teamSize);
|
|
186
|
+
const use = z.enum(COMPANY_USE_CASES).safeParse(useCase);
|
|
187
|
+
const peerGroup =
|
|
188
|
+
normalizeOrgWebsite(website) && team.success && use.success
|
|
189
|
+
? { teamSize: team.data, useCase: use.data }
|
|
190
|
+
: null;
|
|
156
191
|
const empty = {
|
|
192
|
+
level: peerGroup ? ("full" as const) : ("basic" as const),
|
|
193
|
+
peerGroup: null,
|
|
157
194
|
local: null,
|
|
158
195
|
minFinishedTasks: FLEET_MIN_FINISHED_TASKS,
|
|
159
196
|
generatedAt: null,
|
|
160
197
|
comparisons: [],
|
|
161
198
|
};
|
|
162
199
|
// Telemetry off means no call to our proxy at all, not even an anonymous one.
|
|
163
|
-
|
|
164
|
-
if (!
|
|
200
|
+
const telemetryEnabled = isTelemetryEnabled();
|
|
201
|
+
if (telemetryEnabled && !name) return { state: "no_org_name", ...empty };
|
|
165
202
|
|
|
166
|
-
const stats = await localStats(now);
|
|
203
|
+
const stats = await localStats(now, telemetryEnabled);
|
|
167
204
|
// Where the proxy started counting this install is unknown: no comparison.
|
|
168
205
|
if (!stats) return { state: "unavailable", ...empty };
|
|
169
206
|
const finishedTasks = stats.tasksCompleted + stats.tasksFailed;
|
|
@@ -174,14 +211,26 @@ export async function buildFleetBenchmarkResponse(
|
|
|
174
211
|
agents: stats.agents,
|
|
175
212
|
bucket: fleetBucketFor(stats.agents),
|
|
176
213
|
};
|
|
214
|
+
if (!telemetryEnabled) return { ...empty, state: "telemetry_off", local };
|
|
177
215
|
if (finishedTasks < FLEET_MIN_FINISHED_TASKS)
|
|
178
216
|
return { ...empty, state: "not_enough_tasks", local };
|
|
179
217
|
|
|
180
|
-
const fleet = await fleetBenchmarks(now.getTime());
|
|
218
|
+
const fleet = await fleetBenchmarks(now.getTime(), peerGroup);
|
|
219
|
+
const responsePeerGroup =
|
|
220
|
+
peerGroup && fleet?.peer_group
|
|
221
|
+
? {
|
|
222
|
+
teamSize: fleet.peer_group.team_size,
|
|
223
|
+
useCase: fleet.peer_group.use_case,
|
|
224
|
+
orgs: fleet.peer_group.orgs,
|
|
225
|
+
}
|
|
226
|
+
: null;
|
|
181
227
|
const comparisons = fleet ? compareWithFleet(stats, fleet, now) : [];
|
|
182
|
-
if (!fleet || comparisons.length === 0)
|
|
228
|
+
if (!fleet || comparisons.length === 0)
|
|
229
|
+
return { ...empty, state: "unavailable", local, peerGroup: responsePeerGroup };
|
|
183
230
|
return {
|
|
231
|
+
...empty,
|
|
184
232
|
state: "ready",
|
|
233
|
+
peerGroup: responsePeerGroup,
|
|
185
234
|
local,
|
|
186
235
|
minFinishedTasks: FLEET_MIN_FINISHED_TASKS,
|
|
187
236
|
generatedAt: fleet.generatedAt,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getSwarmConfigs } from "../be/db";
|
|
2
|
+
|
|
3
|
+
/** Stored values, including empty strings, override env values, as on config reload. */
|
|
4
|
+
export async function globalConfig(key: string): Promise<string | undefined> {
|
|
5
|
+
const stored = (await getSwarmConfigs({ scope: "global", key }))?.[0]?.value;
|
|
6
|
+
return (stored ?? process.env[key])?.trim() || undefined;
|
|
7
|
+
}
|