@desplega.ai/agent-swarm 2.4.0 → 2.4.2
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-dv7hmx4r.js} +9 -9
- package/dist/{actions-vget6r1k.js → actions-51nc1gny.js} +21 -21
- package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-abp5tdx8.js} +4 -4
- package/dist/{app-mqap8t8m.js → app-9h14qvr6.js} +3 -3
- package/dist/{assistant-h0gv97mv.js → assistant-wv1gz3mt.js} +22 -22
- package/dist/{boot-reembed-qr8a894z.js → boot-reembed-6yn53ms9.js} +1 -1
- package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-7fcka4z5.js} +1 -1
- package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-pm1580ax.js} +4 -4
- package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-g5hfc1x8.js} +4 -4
- package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gf3kxhf0.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-s2k1qd8y.js} +13 -13
- package/dist/{cli-tj4j2ts4.js → cli-02qm7src.js} +8 -8
- package/dist/{cli-1cevz46c.js → cli-0qvtffe9.js} +6 -6
- package/dist/{cli-avmqhatn.js → cli-10hhg0km.js} +8 -8
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-kz116kvw.js → cli-239yjk27.js} +7 -7
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-x719j3vs.js → cli-5rk36sjf.js} +442 -205
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-5s5hx57y.js → cli-7awf1fp8.js} +1 -1
- package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
- package/dist/{cli-ywyrffff.js → cli-8sxqsbbq.js} +1 -1
- package/dist/{cli-rsd7ga7v.js → cli-8zwe9mpk.js} +11 -11
- 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-mvg2hf8p.js → cli-ahtpy9kf.js} +14 -7
- package/dist/{cli-x1p5dhq2.js → cli-bcj753sq.js} +3 -3
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
- package/dist/{cli-rg7ntypm.js → cli-d3qqdy4c.js} +456 -94
- package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
- package/dist/{cli-02vhhssk.js → cli-efaqv1c7.js} +5 -5
- package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
- package/dist/{cli-kk2r80n5.js → cli-faypxz7q.js} +4 -4
- package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
- package/dist/{cli-wrv09v7q.js → cli-ftyypbg5.js} +1 -1
- package/dist/{cli-zvze9rbm.js → cli-fyzjmseb.js} +3 -3
- package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
- package/dist/{cli-nrrg0fvy.js → cli-h38rjv87.js} +133 -12
- package/dist/{cli-tg9ksbn8.js → cli-h93c9vb3.js} +2 -2
- package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-bhwa8rzc.js → cli-hyfw2j1e.js} +13 -12
- package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-6ftrrwm1.js → cli-n85dqg6d.js} +5 -5
- package/dist/{cli-8cxkw1f5.js → cli-nmqdc2t3.js} +2 -2
- package/dist/{cli-4fhyq711.js → cli-nn47d7e8.js} +13 -13
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
- package/dist/{cli-xfe63zx3.js → cli-pkht5hap.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-tgvpmnwz.js → cli-r99kc9yq.js} +5 -5
- package/dist/{cli-bfc3k1x5.js → cli-rdyw3mct.js} +9 -9
- package/dist/{cli-b80a1100.js → cli-s6vr7r2d.js} +2 -2
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-ttaz6c7m.js → cli-saw9a6f6.js} +6 -5
- package/dist/{cli-mz1ezzzh.js → cli-shvfedvm.js} +3 -3
- 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-t30yj95q.js} +2 -2
- 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-nme7wery.js → cli-y2x52gs2.js} +46 -13
- package/dist/{cli-ffbrkh1r.js → cli-y8n5k515.js} +46 -38
- package/dist/{cli-ecshf504.js → cli-z4wd2ftv.js} +7 -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 +20 -20
- package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-ttygw382.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-60b58vv3.js} +7 -7
- package/dist/{commands-psqhkttb.js → commands-edmdwa4x.js} +2 -2
- package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-5fym8sw1.js} +23 -23
- package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-nc1rd869.js → db-2y0mgstc.js} +3 -3
- package/dist/{db-2xbyenjr.js → db-nn0hcmx5.js} +6 -2
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-ewceb8ts.js → dispatcher-0a9wwjcx.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-zhvt95n3.js} +2 -2
- package/dist/{grok-adapter-vj88xr23.js → grok-adapter-qnr1hp2y.js} +10 -10
- package/dist/handlers-dkcarkd0.js +39 -0
- package/dist/{heartbeat-t2m9r3wa.js → heartbeat-gz97r7ac.js} +23 -23
- package/dist/{hook-xa677f8r.js → hook-1mmdgjqs.js} +16 -9
- package/dist/{hook-8femx1bg.js → hook-x8rthca4.js} +6 -6
- package/dist/{http-xpmmq028.js → http-r8f85xr9.js} +274 -160
- package/dist/index-5jecgp3n.js +41 -0
- package/dist/index-aex0eba9.js +40 -0
- package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
- package/dist/{index-cdzyfrsp.js → index-smxyk04s.js} +1 -1
- package/dist/{index-dg2tmj99.js → index-vzvfqzfj.js} +20 -20
- package/dist/{index-s7ckdajq.js → index-y7rbcrc1.js} +10 -10
- package/dist/{keepalive-3nr4st3r.js → keepalive-64c0f34c.js} +4 -4
- package/dist/lead-b1da0f97.js +78 -0
- package/dist/{maintenance-verwpe2c.js → maintenance-mnxpar12.js} +1 -1
- package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-36hs2v4h.js} +3 -3
- package/dist/{onboard-aytqedmq.js → onboard-xes8de61.js} +3 -3
- package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-hckw4wt1.js → otel-impl-fzc57kcq.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-1nek2w7b.js} +3 -3
- package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-sqefb5hw.js} +2 -2
- package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-ttyfaep2.js} +1 -1
- package/dist/{render-v2-7nqgvk2q.js → render-v2-vb527jwn.js} +3 -3
- package/dist/{resume-b6djcj3c.js → resume-jcdzg1bn.js} +10 -8
- package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/{session-access-fc3f9y12.js → session-access-zsp4wsp0.js} +4 -4
- package/dist/{setup-n39szm6b.js → setup-1nb2zqd3.js} +1 -1
- package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-jem2fre0.js → work-objects-61w08fwr.js} +23 -23
- package/dist/worker-c1e68hpa.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +491 -52
- package/package.json +3 -3
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/catalog.generated.json +3 -2
- package/src/apps/definition.ts +152 -4
- package/src/apps/row-store.ts +23 -9
- package/src/apps/schema-migrate.ts +14 -6
- package/src/apps/script-types.ts +2 -1
- package/src/apps/sync.ts +20 -19
- package/src/be/db/tasks/read.ts +112 -0
- package/src/be/db.ts +16 -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 +25 -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 +99 -183
- 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/status.ts +49 -43
- package/src/http/tasks.ts +49 -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/acp-targets.ts +1 -1
- 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 +29 -6
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +385 -0
- package/src/tests/apps-json-column.test.ts +611 -0
- package/src/tests/apps-script-types.test.ts +2 -0
- package/src/tests/apps-sync-engine.test.ts +42 -0
- package/src/tests/check-pr-body.test.ts +0 -23
- package/src/tests/credential-check.test.ts +38 -0
- package/src/tests/db-task-read-contract.test.ts +4 -0
- package/src/tests/db-task-write-contract.test.ts +2 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- 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 +62 -1
- package/src/tests/status.test.ts +158 -9
- package/src/tests/swarm-tool-result-gate.test.ts +2 -4
- package/src/tests/task-lineage.test.ts +180 -0
- package/src/tests/telemetry-context.test.ts +128 -2
- package/src/tests/telemetry-init.test.ts +62 -5
- 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/user-token-rest-auth.test.ts +38 -0
- 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 +239 -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 +44 -4
- package/templates/skills/apps/content.md +44 -4
- 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,16 +1,17 @@
|
|
|
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,
|
|
7
8
|
type AppQueryDef,
|
|
8
9
|
type AppValidationIssue,
|
|
9
10
|
applyAppDefinitionPatch,
|
|
10
|
-
ColumnKindSchema,
|
|
11
11
|
isIso8601Date,
|
|
12
12
|
type ModelDef,
|
|
13
13
|
parseAppDefinition,
|
|
14
|
+
ScalarColumnKindSchema,
|
|
14
15
|
SORTABLE_SYSTEM_DATE_COLUMNS,
|
|
15
16
|
SYSTEM_COLUMN_KINDS,
|
|
16
17
|
scriptCompatWarnings,
|
|
@@ -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+)?$/;
|
|
@@ -220,7 +216,7 @@ const UserConfigValuesSchema = z.record(
|
|
|
220
216
|
* on the wire anyway — the key can only ever be absent.
|
|
221
217
|
*/
|
|
222
218
|
const AppUserConfigFieldSchema = z.object({
|
|
223
|
-
kind:
|
|
219
|
+
kind: ScalarColumnKindSchema,
|
|
224
220
|
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
225
221
|
enum: z.array(z.string()).optional(),
|
|
226
222
|
label: z.string().optional(),
|
|
@@ -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
|
}
|
|
@@ -800,6 +785,7 @@ async function resolveModel(
|
|
|
800
785
|
}
|
|
801
786
|
|
|
802
787
|
function parseFilterValue(raw: string, column: ModelDef["columns"][string]): unknown {
|
|
788
|
+
if (column.kind === "json") throw new Error("json columns cannot be filtered");
|
|
803
789
|
if (column.kind === "number") {
|
|
804
790
|
if (!DECIMAL_NUMBER_PATTERN.test(raw)) throw new Error("must be a decimal number");
|
|
805
791
|
const value = Number(raw);
|
|
@@ -991,6 +977,10 @@ function rowValue(row: AppRow, column: string): unknown {
|
|
|
991
977
|
return Object.hasOwn(row, column) ? row[column] : undefined;
|
|
992
978
|
}
|
|
993
979
|
|
|
980
|
+
export function rowMatchesFilters(row: AppRow, filters: RowFilter[]): boolean {
|
|
981
|
+
return filters.every((filter) => rowValue(row, filter.column) === filter.value);
|
|
982
|
+
}
|
|
983
|
+
|
|
994
984
|
export function applyQuery(
|
|
995
985
|
rows: AppRow[],
|
|
996
986
|
query: AppQueryDef,
|
|
@@ -1019,6 +1009,71 @@ export function applyQuery(
|
|
|
1019
1009
|
return selected.slice(0, query.limit ?? 200);
|
|
1020
1010
|
}
|
|
1021
1011
|
|
|
1012
|
+
export async function selectAppRows(
|
|
1013
|
+
appId: string,
|
|
1014
|
+
modelName: string,
|
|
1015
|
+
model: ModelDef,
|
|
1016
|
+
filters: RowFilter[],
|
|
1017
|
+
sortRaw?: string,
|
|
1018
|
+
limit = 200,
|
|
1019
|
+
systemColumns = false,
|
|
1020
|
+
) {
|
|
1021
|
+
// Backstop for every caller: the REST route (`filter.<col>`) and the MCP
|
|
1022
|
+
// tools (`equals.<col>`) reject json filters with their own paths first.
|
|
1023
|
+
const jsonFilterIssues = filters.flatMap((filter) =>
|
|
1024
|
+
Object.hasOwn(model.columns, filter.column) && model.columns[filter.column]!.kind === "json"
|
|
1025
|
+
? [{ path: `filter.${filter.column}`, message: "json columns cannot be filtered" }]
|
|
1026
|
+
: [],
|
|
1027
|
+
);
|
|
1028
|
+
if (jsonFilterIssues.length > 0) throw new AppRowValidationError(jsonFilterIssues);
|
|
1029
|
+
|
|
1030
|
+
// SQL narrows the scan; the `===` re-check keeps the exact filter semantics.
|
|
1031
|
+
const rows = (
|
|
1032
|
+
await listAppRows(
|
|
1033
|
+
appId,
|
|
1034
|
+
modelName,
|
|
1035
|
+
filters.flatMap((filter) =>
|
|
1036
|
+
typeof filter.value === "string" ||
|
|
1037
|
+
typeof filter.value === "number" ||
|
|
1038
|
+
typeof filter.value === "boolean"
|
|
1039
|
+
? [{ column: filter.column, value: filter.value }]
|
|
1040
|
+
: [],
|
|
1041
|
+
),
|
|
1042
|
+
)
|
|
1043
|
+
).filter((row) => rowMatchesFilters(row, filters));
|
|
1044
|
+
|
|
1045
|
+
if (sortRaw) {
|
|
1046
|
+
const [column, dir, extra] = sortRaw.split(":");
|
|
1047
|
+
if (
|
|
1048
|
+
extra !== undefined ||
|
|
1049
|
+
!column ||
|
|
1050
|
+
(dir !== "asc" && dir !== "desc") ||
|
|
1051
|
+
(!SORTABLE_SYSTEM_DATE_COLUMNS.has(column) &&
|
|
1052
|
+
!(systemColumns && Object.hasOwn(SYSTEM_COLUMN_KINDS, column)) &&
|
|
1053
|
+
(!Object.hasOwn(model.columns, column) || model.columns[column]!.hidden === true))
|
|
1054
|
+
) {
|
|
1055
|
+
throw new AppRowValidationError([
|
|
1056
|
+
{ path: "sort", message: "must be <column>:<asc|desc> for a known column" },
|
|
1057
|
+
]);
|
|
1058
|
+
}
|
|
1059
|
+
if (model.columns[column]?.kind === "json") {
|
|
1060
|
+
throw new AppRowValidationError([{ path: "sort", message: "json columns cannot be sorted" }]);
|
|
1061
|
+
}
|
|
1062
|
+
rows.sort((a, b) => {
|
|
1063
|
+
const result = compareValues(
|
|
1064
|
+
rowValue(a, column),
|
|
1065
|
+
rowValue(b, column),
|
|
1066
|
+
dir,
|
|
1067
|
+
SORTABLE_SYSTEM_DATE_COLUMNS.has(column) ||
|
|
1068
|
+
(Object.hasOwn(model.columns, column) && model.columns[column]!.kind === "date"),
|
|
1069
|
+
);
|
|
1070
|
+
return result;
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const total = rows.length;
|
|
1074
|
+
return { rows: rows.slice(0, limit), total };
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1022
1077
|
export async function handleApps(
|
|
1023
1078
|
req: IncomingMessage,
|
|
1024
1079
|
res: ServerResponse,
|
|
@@ -1189,55 +1244,23 @@ export async function handleApps(
|
|
|
1189
1244
|
json(res, { error: "invalid row query", issues }, 400);
|
|
1190
1245
|
return true;
|
|
1191
1246
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1247
|
+
try {
|
|
1248
|
+
listRowsRoute.respond(
|
|
1249
|
+
res,
|
|
1250
|
+
200,
|
|
1251
|
+
await selectAppRows(
|
|
1252
|
+
parsed.params.id,
|
|
1253
|
+
parsed.params.model,
|
|
1254
|
+
resolved.model,
|
|
1255
|
+
filters,
|
|
1256
|
+
parsed.query.sort,
|
|
1257
|
+
parsed.query.limit,
|
|
1203
1258
|
),
|
|
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
|
-
});
|
|
1259
|
+
);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (!(error instanceof AppRowValidationError)) throw error;
|
|
1262
|
+
json(res, { error: "invalid sort", issues: error.issues }, 400);
|
|
1238
1263
|
}
|
|
1239
|
-
const total = rows.length;
|
|
1240
|
-
listRowsRoute.respond(res, 200, { rows: rows.slice(0, parsed.query.limit ?? 200), total });
|
|
1241
1264
|
return true;
|
|
1242
1265
|
}
|
|
1243
1266
|
|
|
@@ -1359,122 +1382,15 @@ export async function handleApps(
|
|
|
1359
1382
|
return true;
|
|
1360
1383
|
}
|
|
1361
1384
|
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),
|
|
1385
|
+
const output = await runAppAction({
|
|
1386
|
+
app,
|
|
1387
|
+
name: parsed.params.name,
|
|
1388
|
+
input: parsed.body.input,
|
|
1389
|
+
caller,
|
|
1390
|
+
actor,
|
|
1462
1391
|
});
|
|
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 });
|
|
1392
|
+
if (output.status === 200) runActionRoute.respond(res, 200, output.body);
|
|
1393
|
+
else json(res, output.body, output.status);
|
|
1478
1394
|
return true;
|
|
1479
1395
|
}
|
|
1480
1396
|
|
|
@@ -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
|
+
}
|