@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
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { AppNameSchema, type ModelDef, SYSTEM_COLUMN_KINDS } from "../apps/definition";
|
|
4
|
+
import {
|
|
5
|
+
type AppRow,
|
|
6
|
+
AppRowAppNotFoundError,
|
|
7
|
+
AppRowValidationError,
|
|
8
|
+
createAppRow,
|
|
9
|
+
createAppRows,
|
|
10
|
+
deleteAppRow,
|
|
11
|
+
getAppRow,
|
|
12
|
+
patchAppRow,
|
|
13
|
+
validValue,
|
|
14
|
+
} from "../apps/row-store";
|
|
15
|
+
import { appDefinitionNeedsRepair, getApp } from "../apps/store";
|
|
16
|
+
import { getAgentById } from "../be/db";
|
|
17
|
+
import {
|
|
18
|
+
MAX_APP_BULK_ROWS_BODY_BYTES,
|
|
19
|
+
MAX_APP_ROW_BODY_BYTES,
|
|
20
|
+
rowMatchesFilters,
|
|
21
|
+
selectAppRows,
|
|
22
|
+
} from "../http/apps";
|
|
23
|
+
import { can } from "../rbac";
|
|
24
|
+
import {
|
|
25
|
+
createToolRegistrar,
|
|
26
|
+
type SwarmToolResult,
|
|
27
|
+
swarmToolOutputSchema,
|
|
28
|
+
toolErr,
|
|
29
|
+
toolOk,
|
|
30
|
+
} from "./utils";
|
|
31
|
+
|
|
32
|
+
const base = { appId: z.string().min(1), model: AppNameSchema };
|
|
33
|
+
const rowId = z.string().min(1);
|
|
34
|
+
const values = z.record(z.string(), z.unknown());
|
|
35
|
+
const equals = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]));
|
|
36
|
+
const limit = z.number().int().min(1).max(1000);
|
|
37
|
+
const rowOutput = { row: z.looseObject({}).optional() };
|
|
38
|
+
const rowsOutput = { rows: z.array(z.looseObject({})).optional() };
|
|
39
|
+
|
|
40
|
+
function checkSize(value: unknown, max: number): void {
|
|
41
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf8") > max) {
|
|
42
|
+
throw new AppRowValidationError([
|
|
43
|
+
{ path: "values", message: `Row input exceeds ${max} bytes.` },
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rowFilters(model: ModelDef, equals: Record<string, string | number | boolean> = {}) {
|
|
49
|
+
return Object.entries(equals).map(([column, value]) => {
|
|
50
|
+
const declared = Object.hasOwn(model.columns, column) ? model.columns[column] : undefined;
|
|
51
|
+
if ((!declared && !Object.hasOwn(SYSTEM_COLUMN_KINDS, column)) || declared?.hidden) {
|
|
52
|
+
throw new AppRowValidationError([
|
|
53
|
+
{ path: `equals.${column}`, message: "Unknown or hidden column." },
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
if (declared?.kind === "json") {
|
|
57
|
+
throw new AppRowValidationError([
|
|
58
|
+
{ path: `equals.${column}`, message: "json columns cannot be filtered" },
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
if (!validValue(declared ?? { kind: SYSTEM_COLUMN_KINDS[column]! }, value)) {
|
|
62
|
+
throw new AppRowValidationError([
|
|
63
|
+
{ path: `equals.${column}`, message: "Filter value must match the column kind." },
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
return { column, value };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function registerAppRowTools(server: McpServer) {
|
|
71
|
+
function register<Shape extends z.ZodRawShape>(
|
|
72
|
+
name: string,
|
|
73
|
+
description: string,
|
|
74
|
+
shape: Shape,
|
|
75
|
+
output: z.ZodRawShape,
|
|
76
|
+
operation: (
|
|
77
|
+
args: z.infer<z.ZodObject<typeof base & Shape>>,
|
|
78
|
+
model: ModelDef,
|
|
79
|
+
actor: string,
|
|
80
|
+
) => Promise<SwarmToolResult>,
|
|
81
|
+
readOnly = false,
|
|
82
|
+
) {
|
|
83
|
+
createToolRegistrar(server)(
|
|
84
|
+
name,
|
|
85
|
+
{
|
|
86
|
+
title: description,
|
|
87
|
+
description,
|
|
88
|
+
annotations: { readOnlyHint: readOnly },
|
|
89
|
+
rbac: { permission: "app.use" },
|
|
90
|
+
inputSchema: z.object({ ...base, ...shape }),
|
|
91
|
+
outputSchema: swarmToolOutputSchema(output),
|
|
92
|
+
},
|
|
93
|
+
async (args, info) => {
|
|
94
|
+
if (!info.agentId) return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
95
|
+
const { appId, model: modelName } = z.object(base).parse(args);
|
|
96
|
+
const agent = await getAgentById(info.agentId);
|
|
97
|
+
const decision = can({
|
|
98
|
+
principal: { kind: "agent", agentId: info.agentId, isLead: agent?.isLead ?? false },
|
|
99
|
+
verb: "app.use",
|
|
100
|
+
resource: { kind: "app", appId },
|
|
101
|
+
source: "mcp",
|
|
102
|
+
});
|
|
103
|
+
if (!decision.allow) return toolErr(decision.reason);
|
|
104
|
+
const app = await getApp(appId);
|
|
105
|
+
if (!app) return toolErr("App not found.");
|
|
106
|
+
if (appDefinitionNeedsRepair(app))
|
|
107
|
+
return toolErr("App definition needs repair.", { data: { issues: app.definitionError } });
|
|
108
|
+
if (!Object.hasOwn(app.definition.models, modelName)) return toolErr("Model not found.");
|
|
109
|
+
try {
|
|
110
|
+
return await operation(args, app.definition.models[modelName]!, `agent:${info.agentId}`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof AppRowValidationError)
|
|
113
|
+
return toolErr(error.message, { data: { issues: error.issues } });
|
|
114
|
+
if (error instanceof AppRowAppNotFoundError) return toolErr(error.message);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
register(
|
|
122
|
+
"app-rows-list",
|
|
123
|
+
"List app rows with equality filters, sorting, and a limit.",
|
|
124
|
+
{
|
|
125
|
+
equals: equals.optional(),
|
|
126
|
+
sort: z.string().optional(),
|
|
127
|
+
limit: limit.optional(),
|
|
128
|
+
},
|
|
129
|
+
{ ...rowsOutput, total: z.number().optional() },
|
|
130
|
+
async (args, model) => {
|
|
131
|
+
const data = await selectAppRows(
|
|
132
|
+
args.appId,
|
|
133
|
+
args.model,
|
|
134
|
+
model,
|
|
135
|
+
rowFilters(model, args.equals),
|
|
136
|
+
args.sort,
|
|
137
|
+
args.limit,
|
|
138
|
+
true,
|
|
139
|
+
);
|
|
140
|
+
return toolOk(`Found ${data.total} row(s).`, { data });
|
|
141
|
+
},
|
|
142
|
+
true,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
register(
|
|
146
|
+
"app-row-get",
|
|
147
|
+
"Get an app row.",
|
|
148
|
+
{ rowId },
|
|
149
|
+
rowOutput,
|
|
150
|
+
async (args) => {
|
|
151
|
+
const row = await getAppRow(args.appId, args.model, args.rowId);
|
|
152
|
+
return row ? toolOk("Row retrieved.", { data: { row } }) : toolErr("Row not found.");
|
|
153
|
+
},
|
|
154
|
+
true,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
register(
|
|
158
|
+
"app-row-create",
|
|
159
|
+
"Create an app row.",
|
|
160
|
+
{ values },
|
|
161
|
+
rowOutput,
|
|
162
|
+
async (args, model, actor) => {
|
|
163
|
+
checkSize({ values: args.values }, MAX_APP_ROW_BODY_BYTES);
|
|
164
|
+
const row = await createAppRow(args.appId, args.model, model, args.values, { actor });
|
|
165
|
+
return toolOk("Row created.", { data: { row } });
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
register(
|
|
170
|
+
"app-rows-create",
|
|
171
|
+
"Create up to 500 app rows within 10 MiB.",
|
|
172
|
+
{
|
|
173
|
+
rows: z.array(values).min(1).max(500),
|
|
174
|
+
},
|
|
175
|
+
rowsOutput,
|
|
176
|
+
async (args, model, actor) => {
|
|
177
|
+
checkSize({ rows: args.rows }, MAX_APP_BULK_ROWS_BODY_BYTES);
|
|
178
|
+
for (const values of args.rows) checkSize({ values }, MAX_APP_ROW_BODY_BYTES);
|
|
179
|
+
const rows = await createAppRows(args.appId, args.model, model, args.rows, { actor });
|
|
180
|
+
return toolOk(`Created ${rows.length} row(s).`, { data: { rows } });
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
register(
|
|
185
|
+
"app-row-patch",
|
|
186
|
+
"Patch an app row.",
|
|
187
|
+
{ rowId, values },
|
|
188
|
+
rowOutput,
|
|
189
|
+
async (args, model, actor) => {
|
|
190
|
+
checkSize({ values: args.values }, MAX_APP_ROW_BODY_BYTES);
|
|
191
|
+
const row = await patchAppRow(args.appId, args.model, model, args.rowId, args.values, {
|
|
192
|
+
actor,
|
|
193
|
+
});
|
|
194
|
+
return row ? toolOk("Row updated.", { data: { row } }) : toolErr("Row not found.");
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
register(
|
|
199
|
+
"app-row-delete",
|
|
200
|
+
"Delete an app row.",
|
|
201
|
+
{ rowId },
|
|
202
|
+
{ ok: z.boolean().optional() },
|
|
203
|
+
async (args, model) => {
|
|
204
|
+
const deleted = await deleteAppRow(args.appId, args.model, model, args.rowId);
|
|
205
|
+
return deleted ? toolOk("Row deleted.", { data: { ok: true } }) : toolErr("Row not found.");
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
register(
|
|
210
|
+
"app-rows-delete",
|
|
211
|
+
"Delete matching app rows. Supply at least one equality filter.",
|
|
212
|
+
{
|
|
213
|
+
equals: equals.refine(
|
|
214
|
+
(value) => Object.keys(value).length > 0,
|
|
215
|
+
"Supply at least one equality filter.",
|
|
216
|
+
),
|
|
217
|
+
limit: limit.optional(),
|
|
218
|
+
},
|
|
219
|
+
{ deleted: z.number().optional() },
|
|
220
|
+
async (args, model) => {
|
|
221
|
+
const filters = rowFilters(model, args.equals);
|
|
222
|
+
const { rows } = await selectAppRows(
|
|
223
|
+
args.appId,
|
|
224
|
+
args.model,
|
|
225
|
+
model,
|
|
226
|
+
filters,
|
|
227
|
+
undefined,
|
|
228
|
+
args.limit ?? 1000,
|
|
229
|
+
true,
|
|
230
|
+
);
|
|
231
|
+
// Re-check under the row lock: a row patched out of the filter since the select is kept.
|
|
232
|
+
const matches = (row: AppRow) => rowMatchesFilters(row, filters);
|
|
233
|
+
let deleted = 0;
|
|
234
|
+
for (const row of rows)
|
|
235
|
+
if (await deleteAppRow(args.appId, args.model, model, row.id, matches)) deleted++;
|
|
236
|
+
return toolOk(`Deleted ${deleted} row(s).`, { data: { deleted } });
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -87,7 +87,7 @@ export const registerModelCatalogOverlayUpsertTool = (server: McpServer) => {
|
|
|
87
87
|
.string()
|
|
88
88
|
.min(1)
|
|
89
89
|
.describe(
|
|
90
|
-
`models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode, ${openAICompatibleProviderIds().join(", ")}.`,
|
|
90
|
+
`models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode, opencode-go, ${openAICompatibleProviderIds().join(", ")}.`,
|
|
91
91
|
),
|
|
92
92
|
modelId: z
|
|
93
93
|
.string()
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -189,6 +189,14 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
189
189
|
"app-list",
|
|
190
190
|
"app-patch",
|
|
191
191
|
"app-query",
|
|
192
|
+
"app-rows-list",
|
|
193
|
+
"app-row-get",
|
|
194
|
+
"app-row-create",
|
|
195
|
+
"app-rows-create",
|
|
196
|
+
"app-row-patch",
|
|
197
|
+
"app-row-delete",
|
|
198
|
+
"app-rows-delete",
|
|
199
|
+
"app-action-run",
|
|
192
200
|
"app-sync",
|
|
193
201
|
"app-upsert",
|
|
194
202
|
"app-history",
|
package/src/tools/utils.ts
CHANGED
|
@@ -347,7 +347,7 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
|
|
|
347
347
|
const hasQueries = Object.keys(definition?.queries ?? {}).length > 0;
|
|
348
348
|
const hasActions = Object.keys(definition?.actions ?? {}).length > 0;
|
|
349
349
|
return hasQueries || hasActions
|
|
350
|
-
? "
|
|
350
|
+
? "Run `definition.queries` with `app-query` and supply each `$param` value. Invoke `definition.actions` with `app-action-run` through MCP or `POST /api/apps/<id>/actions/<name>` through REST. Sync actions refresh sources."
|
|
351
351
|
: undefined;
|
|
352
352
|
},
|
|
353
353
|
// kv-get is spill-exempt (retrieval path), so oversized values reach the
|
|
@@ -10,7 +10,7 @@ export const registerRetryWorkflowRunTool = (server: McpServer) => {
|
|
|
10
10
|
title: "Retry Workflow Run",
|
|
11
11
|
annotations: { destructiveHint: false },
|
|
12
12
|
description:
|
|
13
|
-
"
|
|
13
|
+
"Resume the same failed workflow run from its failed step(s); completed steps are not re-run. Retrying re-creates the failed node's side effects (e.g. an agent task), so check the target state first if that work was already done manually.",
|
|
14
14
|
inputSchema: z.object({
|
|
15
15
|
runId: z.string().uuid().describe("Workflow run ID to retry"),
|
|
16
16
|
}),
|
package/src/types.ts
CHANGED
|
@@ -1362,6 +1362,14 @@ export const AgentBedrockStatusSchema = z
|
|
|
1362
1362
|
.openapi("AgentBedrockStatus");
|
|
1363
1363
|
export type AgentBedrockStatus = z.infer<typeof AgentBedrockStatusSchema>;
|
|
1364
1364
|
|
|
1365
|
+
export const AgentOpencodeGoStatusSchema = z
|
|
1366
|
+
.object({
|
|
1367
|
+
probedAt: z.number(), // unix ms
|
|
1368
|
+
models: z.array(z.string()).default([]),
|
|
1369
|
+
})
|
|
1370
|
+
.openapi("AgentOpencodeGoStatus");
|
|
1371
|
+
export type AgentOpencodeGoStatus = z.infer<typeof AgentOpencodeGoStatusSchema>;
|
|
1372
|
+
|
|
1365
1373
|
const AcpSessionConfigSelectValueSchema = z.object({
|
|
1366
1374
|
value: z.string(),
|
|
1367
1375
|
name: z.string(),
|
|
@@ -1421,6 +1429,8 @@ export const AgentCredStatusSchema = z
|
|
|
1421
1429
|
reportKind: z.enum(["boot", "post_task"]).default("boot"),
|
|
1422
1430
|
/** Pi-mono Bedrock enumeration block — null when not in Bedrock mode. */
|
|
1423
1431
|
bedrock: AgentBedrockStatusSchema.nullable().default(null),
|
|
1432
|
+
/** Pi-mono only: the `opencode-go` model ids the worker's pi-ai resolves. */
|
|
1433
|
+
opencodeGo: AgentOpencodeGoStatusSchema.nullable().default(null),
|
|
1424
1434
|
/** ACP options advertised by the most recently created session. */
|
|
1425
1435
|
acp: AgentAcpStatusSchema.nullable().default(null),
|
|
1426
1436
|
})
|
|
@@ -2210,6 +2220,13 @@ export const RetryPolicySchema = z
|
|
|
2210
2220
|
.openapi("RetryPolicy");
|
|
2211
2221
|
export type RetryPolicy = z.infer<typeof RetryPolicySchema>;
|
|
2212
2222
|
|
|
2223
|
+
/**
|
|
2224
|
+
* `failureReason` of a workflow-step task cut by a supersede (deploy drain,
|
|
2225
|
+
* heartbeat remediation). The workflow engine re-dispatches it once even
|
|
2226
|
+
* when the node declares no `retry` (see `scheduleTaskStepRetry`).
|
|
2227
|
+
*/
|
|
2228
|
+
export const SUPERSEDED_WORKFLOW_TASK_REASON = "superseded_workflow_task";
|
|
2229
|
+
|
|
2213
2230
|
// --- Executor Metadata ---
|
|
2214
2231
|
|
|
2215
2232
|
export const ExecutorMetaSchema = z
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const COMPANY_TEAM_SIZES = ["1", "2-10", "11-50", "51+"] as const;
|
|
2
|
+
|
|
3
|
+
export const COMPANY_USE_CASES = ["features", "bugs", "review", "ops", "docs", "data"] as const;
|
|
4
|
+
|
|
5
|
+
export const COMPANY_ACQUISITION_SOURCES = [
|
|
6
|
+
"search",
|
|
7
|
+
"github",
|
|
8
|
+
"social",
|
|
9
|
+
"colleague",
|
|
10
|
+
"newsletter",
|
|
11
|
+
"event",
|
|
12
|
+
"ai_assistant",
|
|
13
|
+
"hacker_news",
|
|
14
|
+
"other",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export const USER_FUNCTIONS = [
|
|
18
|
+
"engineer",
|
|
19
|
+
"eng_lead",
|
|
20
|
+
"founder",
|
|
21
|
+
"product",
|
|
22
|
+
"ops",
|
|
23
|
+
"other",
|
|
24
|
+
] as const;
|
package/src/utils/org-website.ts
CHANGED
|
@@ -71,34 +71,71 @@ export function isFilledOrgLogoUrl(
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Free-mail providers with regional domains, matched on the label before the
|
|
76
|
+
* public suffix: hotmail.es, yahoo.co.uk, gmx.net and yandex.com.tr all count.
|
|
77
|
+
*/
|
|
78
|
+
const FREE_MAIL_FAMILIES = new Set([
|
|
79
|
+
"aol",
|
|
80
|
+
"fastmail",
|
|
81
|
+
"gmail",
|
|
82
|
+
"gmx",
|
|
83
|
+
"googlemail",
|
|
84
|
+
"hotmail",
|
|
85
|
+
"icloud",
|
|
86
|
+
"live",
|
|
87
|
+
"msn",
|
|
88
|
+
"outlook",
|
|
89
|
+
"protonmail",
|
|
90
|
+
"rocketmail",
|
|
91
|
+
"yahoo",
|
|
92
|
+
"yandex",
|
|
93
|
+
"ymail",
|
|
94
|
+
"zoho",
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
/** Free-mail providers on one domain only, where the label alone is too generic. */
|
|
74
98
|
const FREE_MAIL_DOMAINS = new Set([
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"me.com",
|
|
99
|
+
"126.com",
|
|
100
|
+
"163.com",
|
|
101
|
+
"comcast.net",
|
|
102
|
+
"daum.net",
|
|
103
|
+
"free.fr",
|
|
104
|
+
"hanmail.net",
|
|
105
|
+
"hey.com",
|
|
106
|
+
"libero.it",
|
|
84
107
|
"mac.com",
|
|
85
|
-
"aol.com",
|
|
86
|
-
"proton.me",
|
|
87
|
-
"protonmail.com",
|
|
88
|
-
"gmx.com",
|
|
89
|
-
"gmx.de",
|
|
90
|
-
"yandex.com",
|
|
91
|
-
"yandex.ru",
|
|
92
108
|
"mail.com",
|
|
93
|
-
"
|
|
109
|
+
"mail.ru",
|
|
110
|
+
"me.com",
|
|
111
|
+
"naver.com",
|
|
112
|
+
"orange.fr",
|
|
113
|
+
"pm.me",
|
|
114
|
+
"proton.me",
|
|
94
115
|
"qq.com",
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
116
|
+
"rediffmail.com",
|
|
117
|
+
"sina.com",
|
|
118
|
+
"t-online.de",
|
|
119
|
+
"tutanota.com",
|
|
120
|
+
"web.de",
|
|
98
121
|
]);
|
|
99
122
|
|
|
123
|
+
/** Second-level labels of a country code suffix (acme.co.uk, acme.com.br). */
|
|
124
|
+
const SECOND_LEVEL_LABELS = new Set(["ac", "co", "com", "edu", "gov", "ne", "net", "or", "org"]);
|
|
125
|
+
|
|
126
|
+
/** Whether `host` (a normalized hostname) belongs to a public free-mail provider. */
|
|
127
|
+
function isFreeMailHost(host: string): boolean {
|
|
128
|
+
const labels = host.split(".");
|
|
129
|
+
const suffixLength = labels.length > 2 && SECOND_LEVEL_LABELS.has(labels.at(-2) ?? "") ? 2 : 1;
|
|
130
|
+
// mail.yahoo.co.uk becomes yahoo.co.uk.
|
|
131
|
+
const registrable = labels.slice(-(suffixLength + 1));
|
|
132
|
+
return (
|
|
133
|
+
FREE_MAIL_FAMILIES.has(registrable[0] ?? "") || FREE_MAIL_DOMAINS.has(registrable.join("."))
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
100
137
|
/** The admin's email domain as a website suggestion, or null for free-mail domains. */
|
|
101
138
|
export function websiteSuggestion(orgDomain: string | null | undefined): string | null {
|
|
102
139
|
const host = normalizeOrgWebsite(orgDomain);
|
|
103
|
-
return host && !
|
|
140
|
+
return host && !isFreeMailHost(host) ? host : null;
|
|
104
141
|
}
|
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
} from "./engine";
|
|
26
26
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
27
27
|
import { getSecretInputKeys } from "./input";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
finalizeOrWait,
|
|
30
|
+
recoverResolvedWaitState,
|
|
31
|
+
resumeFromChildRun,
|
|
32
|
+
resumeWaitState,
|
|
33
|
+
} from "./resume";
|
|
29
34
|
import {
|
|
30
35
|
checkpointPortStepAndResolveSuccessors,
|
|
31
36
|
completeTaskStepAndResolveSuccessors,
|
|
@@ -392,21 +397,12 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
|
|
|
392
397
|
|
|
393
398
|
for (const stuck of stuckRuns) {
|
|
394
399
|
try {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (stuck.waitStatus === "fired") {
|
|
400
|
-
resumeStatus = "fired";
|
|
401
|
-
payload = stuck.firedPayload != null ? safeJsonParse(stuck.firedPayload) : undefined;
|
|
402
|
-
} else if (stuck.waitStatus === "timeout") {
|
|
403
|
-
resumeStatus = "timeout";
|
|
400
|
+
if (stuck.waitStatus === "pending") {
|
|
401
|
+
const status = stuck.waitMode === "time" ? "fired" : "timeout";
|
|
402
|
+
await resumeWaitState(stuck.waitId, status, undefined, registry);
|
|
404
403
|
} else {
|
|
405
|
-
|
|
406
|
-
resumeStatus = stuck.waitMode === "time" ? "fired" : "timeout";
|
|
404
|
+
await recoverResolvedWaitState(stuck.waitId, registry);
|
|
407
405
|
}
|
|
408
|
-
|
|
409
|
-
await resumeWaitState(stuck.waitId, resumeStatus, payload, registry);
|
|
410
406
|
recovered++;
|
|
411
407
|
} catch (err) {
|
|
412
408
|
if (await failRunOnUnreadableReplay(stuck.runId, err)) continue;
|
|
@@ -417,14 +413,6 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
|
|
|
417
413
|
return recovered;
|
|
418
414
|
}
|
|
419
415
|
|
|
420
|
-
function safeJsonParse(s: string): unknown {
|
|
421
|
-
try {
|
|
422
|
-
return JSON.parse(s);
|
|
423
|
-
} catch {
|
|
424
|
-
return s;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
416
|
/**
|
|
429
417
|
* Get run IDs by status. Simple query since there's no dedicated function for this.
|
|
430
418
|
*/
|
package/src/workflows/resume.ts
CHANGED
|
@@ -711,6 +711,22 @@ export async function resumeWaitState(
|
|
|
711
711
|
);
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
/** Finish a wait resolution that committed before its step was checkpointed. */
|
|
715
|
+
export async function recoverResolvedWaitState(
|
|
716
|
+
waitId: string,
|
|
717
|
+
registry: ExecutorRegistry,
|
|
718
|
+
): Promise<void> {
|
|
719
|
+
const waitRow = await getWaitStateById(waitId);
|
|
720
|
+
if (!waitRow) return;
|
|
721
|
+
const status = waitRow.status;
|
|
722
|
+
if (status !== "fired" && status !== "timeout") return;
|
|
723
|
+
|
|
724
|
+
// Replay the durable result; the step checkpoint arbitrates concurrent resumes.
|
|
725
|
+
await failClosedOnUnreadableReplay(waitRow.workflowRunId, waitRow.workflowRunStepId, () =>
|
|
726
|
+
resumeClaimedWait(waitRow, status, waitRow.firedPayload, registry),
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
714
730
|
async function resumeClaimedWait(
|
|
715
731
|
waitRow: WaitStateRow,
|
|
716
732
|
status: "fired" | "timeout",
|
|
@@ -2,12 +2,15 @@ import {
|
|
|
2
2
|
detachTaskFromWorkflowRunStep,
|
|
3
3
|
getDbClient,
|
|
4
4
|
getTaskById,
|
|
5
|
+
getWorkflowRun,
|
|
5
6
|
getWorkflowRunStep,
|
|
6
7
|
updateWorkflowRun,
|
|
7
8
|
updateWorkflowRunStep,
|
|
8
9
|
} from "../be/db";
|
|
9
10
|
import {
|
|
11
|
+
type RetryPolicy,
|
|
10
12
|
RetryPolicySchema,
|
|
13
|
+
SUPERSEDED_WORKFLOW_TASK_REASON,
|
|
11
14
|
type WorkflowDefinition,
|
|
12
15
|
type WorkflowNode,
|
|
13
16
|
type WorkflowRunStep,
|
|
@@ -17,7 +20,7 @@ import { getSuccessors } from "./definition";
|
|
|
17
20
|
import { joinForeach, resolveForeachParent } from "./foreach-join";
|
|
18
21
|
|
|
19
22
|
export interface PortStepRoutingResult {
|
|
20
|
-
/** False when
|
|
23
|
+
/** False when the step was already claimed or its run is terminal. */
|
|
21
24
|
claimed: boolean;
|
|
22
25
|
successors: WorkflowNode[];
|
|
23
26
|
}
|
|
@@ -85,6 +88,10 @@ export async function checkpointPortStepAndResolveSuccessors(
|
|
|
85
88
|
return await getDbClient().transaction(async (): Promise<PortStepRoutingResult> => {
|
|
86
89
|
const current = await getWorkflowRunStep(stepId);
|
|
87
90
|
if (!current || current.status !== "waiting") return { claimed: false, successors: [] };
|
|
91
|
+
const run = await getWorkflowRun(runId);
|
|
92
|
+
if (!run || (run.status !== "waiting" && run.status !== "running")) {
|
|
93
|
+
return { claimed: false, successors: [] };
|
|
94
|
+
}
|
|
88
95
|
await checkpointStep(runId, stepId, nodeId, { output, nextPort }, ctx);
|
|
89
96
|
await updateWorkflowRun(runId, { status: "running" });
|
|
90
97
|
return { claimed: true, successors: getSuccessors(def, nodeId, nextPort) };
|
|
@@ -97,15 +104,33 @@ export async function checkpointPortStepAndResolveSuccessors(
|
|
|
97
104
|
* - `not-claimed`: another handler already moved the step out of `waiting`,
|
|
98
105
|
* or the failed task is no longer the one bound to the step (it was
|
|
99
106
|
* already retried and a fresh task now owns it); the caller stops.
|
|
100
|
-
* - `not-eligible`: no retry applies (no `node.retry
|
|
101
|
-
* or retries exhausted); the caller applies
|
|
107
|
+
* - `not-eligible`: no retry applies (no `node.retry` and not a supersede
|
|
108
|
+
* cut, not an agent-task node, or retries exhausted); the caller applies
|
|
109
|
+
* `onNodeFailure` as before.
|
|
102
110
|
*/
|
|
103
111
|
export type TaskStepRetryOutcome = "scheduled" | "not-claimed" | "not-eligible";
|
|
104
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Retry applied to an agent-task step whose task was cut by a supersede
|
|
115
|
+
* (deploy drain, heartbeat remediation) when the node declares no `retry`.
|
|
116
|
+
* The cut is not the task's fault, so the step gets one fresh attempt after
|
|
117
|
+
* the restart settles. An explicit `node.retry` always wins; `retry:
|
|
118
|
+
* { maxRetries: 0 }` opts a node with external side effects out.
|
|
119
|
+
*/
|
|
120
|
+
export const SUPERSEDE_RETRY_POLICY: RetryPolicy = {
|
|
121
|
+
maxRetries: 1,
|
|
122
|
+
strategy: "static",
|
|
123
|
+
baseDelayMs: 30_000,
|
|
124
|
+
maxDelayMs: 30_000,
|
|
125
|
+
};
|
|
126
|
+
|
|
105
127
|
/**
|
|
106
128
|
* Apply a node's `retry` policy to a FAILED agent-task step task. The sync
|
|
107
129
|
* executor path does this in `executeStep`; an async agent-task step only
|
|
108
130
|
* learns about the failure here, from `task.failed` or the recovery sweep.
|
|
131
|
+
* A node without `retry` gets `SUPERSEDE_RETRY_POLICY` when the task was
|
|
132
|
+
* failed with `SUPERSEDED_WORKFLOW_TASK_REASON`. The task row is checked as
|
|
133
|
+
* well as `reason`, because the recovery sweep passes its own reason text.
|
|
109
134
|
*
|
|
110
135
|
* In one transaction: re-claim the step while it is still `waiting` AND still
|
|
111
136
|
* bound to the failed task, detach that task (the executor reuses any task
|
|
@@ -114,6 +139,9 @@ export type TaskStepRetryOutcome = "scheduled" | "not-claimed" | "not-eligible";
|
|
|
114
139
|
* task. Bounded by `maxRetries` via the step's persisted `retryCount`, read
|
|
115
140
|
* only after ownership is confirmed: a stale caller's step snapshot may carry
|
|
116
141
|
* the counter of a newer attempt.
|
|
142
|
+
*
|
|
143
|
+
* Foreach children (`<foreachId>#<key>`) are not matched here: the retry
|
|
144
|
+
* poller resolves nodes by exact id and cannot re-dispatch a child yet.
|
|
117
145
|
*/
|
|
118
146
|
export async function scheduleTaskStepRetry(
|
|
119
147
|
def: WorkflowDefinition,
|
|
@@ -123,10 +151,16 @@ export async function scheduleTaskStepRetry(
|
|
|
123
151
|
reason: string,
|
|
124
152
|
): Promise<TaskStepRetryOutcome> {
|
|
125
153
|
const node = def.nodes.find((n) => n.id === step.nodeId);
|
|
126
|
-
if (!node || node.type !== "agent-task"
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
154
|
+
if (!node || node.type !== "agent-task") return "not-eligible";
|
|
155
|
+
let policy: RetryPolicy;
|
|
156
|
+
if (node.retry) {
|
|
157
|
+
const parsed = RetryPolicySchema.safeParse(node.retry);
|
|
158
|
+
if (!parsed.success) return "not-eligible";
|
|
159
|
+
policy = parsed.data;
|
|
160
|
+
} else {
|
|
161
|
+
if (!(await isSupersedeCut(taskId, reason))) return "not-eligible";
|
|
162
|
+
policy = SUPERSEDE_RETRY_POLICY;
|
|
163
|
+
}
|
|
130
164
|
|
|
131
165
|
return await getDbClient().transaction(async (): Promise<TaskStepRetryOutcome> => {
|
|
132
166
|
const current = await getWorkflowRunStep(step.id);
|
|
@@ -139,6 +173,12 @@ export async function scheduleTaskStepRetry(
|
|
|
139
173
|
});
|
|
140
174
|
}
|
|
141
175
|
|
|
176
|
+
async function isSupersedeCut(taskId: string, reason: string): Promise<boolean> {
|
|
177
|
+
if (reason === SUPERSEDED_WORKFLOW_TASK_REASON) return true;
|
|
178
|
+
const task = await getTaskById(taskId);
|
|
179
|
+
return task?.failureReason === SUPERSEDED_WORKFLOW_TASK_REASON;
|
|
180
|
+
}
|
|
181
|
+
|
|
142
182
|
export interface TaskStepRoutingResult {
|
|
143
183
|
/**
|
|
144
184
|
* False when another handler already moved the step out of `waiting` — the
|