@desplega.ai/agent-swarm 2.6.0 → 2.7.0
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 +2 -0
- package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-ma1js5n7.js} +6 -6
- package/dist/{actions-fbqma8aa.js → actions-1vhmtvg9.js} +18 -18
- package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-sxqqvmdk.js} +3 -3
- package/dist/{app-k8a5cgpp.js → app-mwx1d5a9.js} +3 -3
- package/dist/{assistant-a16433gr.js → assistant-yx1p6g82.js} +20 -20
- package/dist/{boot-reembed-f4nn561n.js → boot-reembed-5h37a2c2.js} +1 -1
- package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-7h78vmvv.js} +2 -1
- package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-8mawd4gw.js} +3 -3
- package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-ca84dhnn.js} +4 -4
- package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-wqt4hfkq.js} +7 -7
- package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
- package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-rqv98sea.js} +2 -2
- package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-f29f3b2x.js} +7 -7
- package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
- package/dist/{cli-k21gx5ht.js → cli-1hhbzkbb.js} +6 -6
- package/dist/{cli-v49fkghp.js → cli-1m9x8dr5.js} +1 -1
- package/dist/{cli-24j1jeeb.js → cli-2pvzmas1.js} +3 -3
- package/dist/{cli-w2656ka1.js → cli-2y7v133v.js} +7 -7
- package/dist/{cli-g8ka5j4j.js → cli-48gxjmcz.js} +94 -36
- package/dist/{cli-2sjhpsns.js → cli-54sj64fc.js} +1 -1
- package/dist/{cli-aj8djyqt.js → cli-60sty27p.js} +2 -2
- package/dist/{cli-4hxrf0p4.js → cli-6a9qfa8b.js} +308 -47
- package/dist/{cli-n7asmce6.js → cli-6mgdba42.js} +3 -3
- package/dist/{cli-gf95q3bm.js → cli-6srv653c.js} +3 -3
- package/dist/{cli-9abdgn5t.js → cli-86ehn4s5.js} +3 -3
- package/dist/{cli-bqztfyve.js → cli-8mbc9za9.js} +1 -1
- package/dist/{cli-a2rr0fmg.js → cli-8wmj18cz.js} +1 -1
- package/dist/{cli-c2habvsf.js → cli-9gsm2rzp.js} +8 -8
- package/dist/{cli-4rf0f5vg.js → cli-9pshx6rd.js} +4 -2
- package/dist/{cli-abkm71vr.js → cli-a6ds2epe.js} +1 -1
- package/dist/{cli-esakngh8.js → cli-a89zdqz9.js} +1 -1
- package/dist/{cli-3ndmpxxv.js → cli-ay40dnj3.js} +2 -2
- package/dist/{cli-kf4jree9.js → cli-aypd09nf.js} +1 -1
- package/dist/{cli-6y51ryva.js → cli-b3yzyc57.js} +1 -1
- package/dist/{cli-ag2yrnt5.js → cli-c668vvcf.js} +2 -2
- package/dist/{cli-5sckpqcx.js → cli-d20qvpr2.js} +1 -1
- package/dist/{cli-a34pr3w1.js → cli-e1595bgx.js} +2 -2
- package/dist/{cli-m70bajzr.js → cli-f12mr20z.js} +1 -1
- package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
- package/dist/{cli-mvjz5an3.js → cli-f4dbh7a5.js} +7 -7
- package/dist/{cli-az25fn5f.js → cli-gsfk58nw.js} +2 -2
- package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
- package/dist/{cli-cz37jqss.js → cli-h5jeg46p.js} +2 -2
- package/dist/{cli-emhjcj2y.js → cli-he9gbdx0.js} +60 -10
- package/dist/{cli-3fwqy71j.js → cli-j0ppywdr.js} +38 -31
- package/dist/{cli-5rj83bam.js → cli-j3y0x2n4.js} +34 -33
- package/dist/{cli-3gfvmzht.js → cli-j9bpmykh.js} +73 -25
- package/dist/{cli-m5a9m0ak.js → cli-kv7sbc2c.js} +3 -3
- package/dist/{cli-g20jjwt4.js → cli-m30k9777.js} +12 -12
- package/dist/{cli-683y0b3a.js → cli-m5e802r7.js} +2 -2
- package/dist/{cli-nn6vx2mq.js → cli-ptfq202x.js} +5 -3
- package/dist/{cli-d2dpf95n.js → cli-qj442fws.js} +1 -1
- package/dist/{cli-03gg9pd1.js → cli-rd5m70gj.js} +4 -4
- package/dist/{cli-gnsb1zxa.js → cli-s09j9vp5.js} +1 -1
- package/dist/{cli-zqne1qcj.js → cli-s0mgx214.js} +2 -2
- package/dist/{cli-sqma6vw1.js → cli-sgqm2582.js} +5 -5
- package/dist/{cli-nq677csy.js → cli-sjctjr48.js} +1 -1
- package/dist/{cli-3aqd4jaa.js → cli-twctnkng.js} +3 -3
- package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
- package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-art7c1zh.js → cli-wqr7cea4.js} +4 -4
- package/dist/{cli-hfeb68z4.js → cli-x37r7qwf.js} +11 -3
- package/dist/{cli-ffvsj2qw.js → cli-yd2nreqw.js} +2 -2
- package/dist/{cli-ca6banwz.js → cli-ymjpkxgn.js} +6 -6
- package/dist/{cli-mtnj4fwa.js → cli-yxbsmx56.js} +5 -5
- package/dist/cli.js +18 -16
- package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-zk59x2zh.js} +5 -5
- package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
- package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-kh6s4j3n.js} +5 -5
- package/dist/{commands-jqmgk54b.js → commands-68et1qv2.js} +2 -2
- package/dist/{controller-z3jq589f.js → controller-fyrbnzg4.js} +2 -2
- package/dist/{controllers-connect-r2za39ef.js → controllers-connect-nep9h2xv.js} +19 -19
- package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-yss3pg7t.js} +1 -1
- package/dist/{db-w36wwb5g.js → db-28gjyvxj.js} +2 -2
- package/dist/{db-ppv2tze7.js → db-8ftwvzba.js} +3 -3
- package/dist/{dispatcher-pv5h77vf.js → dispatcher-hayexmkr.js} +17 -17
- package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-46wawff9.js} +1 -1
- package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-8d1e0h4m.js} +1 -1
- package/dist/{execute-jev839y1.js → execute-cjrfxfnm.js} +1 -1
- package/dist/{external-channel-y6nkzhnn.js → external-channel-xbmxcg09.js} +2 -2
- package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-ag4wks2a.js} +7 -7
- package/dist/handlers-tzmevqb5.js +41 -0
- package/dist/{heartbeat-vn31xwdk.js → heartbeat-psfvcgwp.js} +20 -20
- package/dist/{hook-6d0zbvvt.js → hook-8pz04697.js} +5 -5
- package/dist/{hook-cpjs8q51.js → hook-cyq4mryn.js} +6 -6
- package/dist/{http-9c6medda.js → http-haj1nd6k.js} +109 -114
- package/dist/{index-qhjf2htm.js → index-jwt2bj6f.js} +261 -96
- package/dist/{index-g6k9tctj.js → index-q52f47pe.js} +1 -1
- package/dist/{index-pkma1yf8.js → index-syvhnvtw.js} +1 -1
- package/dist/{index-yqgbfgaj.js → index-y1ajx04a.js} +20 -20
- package/dist/{index-k0yt1tvq.js → index-z5h22pes.js} +17 -17
- package/dist/{index-38t35h0n.js → index-zyq8ct52.js} +17 -17
- package/dist/{keepalive-em0117bp.js → keepalive-xdvsamvj.js} +3 -3
- package/dist/{lead-23e7z59t.js → lead-hqg57kdp.js} +35 -35
- package/dist/{maintenance-kbme3qy9.js → maintenance-4y2xzh3a.js} +1 -1
- package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-jd4r7zbs.js} +8 -3
- package/dist/{onboard-p6q0vz97.js → onboard-18447q9q.js} +5 -5
- package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-26b2frdf.js} +5 -5
- package/dist/{otel-impl-54qhqh1z.js → otel-impl-f9tgr8yw.js} +1 -1
- package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-qn6qbga1.js} +33 -12
- package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-dw2tgb53.js} +3 -3
- package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-aygm2760.js} +2 -2
- package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-s4nwj2m3.js} +1 -1
- package/dist/{render-v2-tjay5hhf.js → render-v2-yfvdfr0x.js} +3 -3
- package/dist/{resume-8ayf37g5.js → resume-1bmvjbf3.js} +7 -7
- package/dist/{runtime-hqe5cqcd.js → runtime-f7tpzedy.js} +1 -1
- package/dist/{session-access-fdhp987s.js → session-access-r5garej6.js} +4 -4
- package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-424wzv0r.js} +2 -2
- package/dist/{setup-4rrmqcq3.js → setup-8tygvxkq.js} +1 -1
- package/dist/{virtual-modules-52km00d6.js → virtual-modules-xsw323b3.js} +2 -2
- package/dist/{work-objects-p28fgd70.js → work-objects-ggnghpgf.js} +20 -20
- package/dist/{worker-e3w7vye1.js → worker-sqjd1nqf.js} +35 -35
- package/openapi.json +69 -1
- package/package.json +5 -3
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/db/api-keys.ts +9 -0
- package/src/be/db/workflow-runs.ts +36 -5
- package/src/be/db.ts +80 -22
- package/src/be/memory/task-learning.ts +155 -0
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +2 -0
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +9 -3
- package/src/be/seed-skills/index.ts +7 -0
- package/src/commands/onboard/compose-generator.ts +5 -3
- package/src/commands/provider-credentials.ts +2 -1
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/api-keys.ts +24 -9
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/index.ts +3 -3
- package/src/http/pages-public.ts +38 -5
- package/src/http/scripts.ts +27 -0
- package/src/http/workflows.ts +10 -0
- package/src/http/x.ts +5 -0
- package/src/prompts/base-prompt.ts +21 -1
- package/src/prompts/session-templates.ts +17 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +41 -11
- package/src/scheduler/scheduler.ts +2 -1
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +9 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/base-prompt.test.ts +70 -2
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/credential-check.test.ts +4 -2
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +20 -0
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +3 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/store-progress-learning.test.ts +288 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +67 -1
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +12 -1
- package/src/utils/credentials.ts +5 -2
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/dist/handlers-eqsnmv95.js +0 -41
package/src/be/db.ts
CHANGED
|
@@ -227,8 +227,10 @@ import { configureTaskWriteDependencies, failTask } from "./db/tasks/write";
|
|
|
227
227
|
import { tombstoneWorkflowIdempotencyKeys } from "./db/workflow-idempotency";
|
|
228
228
|
import {
|
|
229
229
|
emitChildRunFinished,
|
|
230
|
+
emitRunFinished,
|
|
230
231
|
getRunLineageBlock,
|
|
231
232
|
getStepExecutionBlock,
|
|
233
|
+
isTerminalWorkflowRunStatus,
|
|
232
234
|
rowToWorkflowRun,
|
|
233
235
|
rowToWorkflowRunStep,
|
|
234
236
|
WORKFLOW_RUN_SUMMARY_COLUMNS,
|
|
@@ -7994,16 +7996,33 @@ export async function updateWorkflowRun(
|
|
|
7994
7996
|
updates.push("lastUpdatedAt = ?");
|
|
7995
7997
|
params.push(new Date().toISOString());
|
|
7996
7998
|
params.push(id);
|
|
7997
|
-
const
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
7999
|
+
const update = () =>
|
|
8000
|
+
getDbClient().get<WorkflowRunRow>(
|
|
8001
|
+
`UPDATE workflow_runs SET ${updates.join(", ")} WHERE id = ? RETURNING *`,
|
|
8002
|
+
params,
|
|
8003
|
+
);
|
|
8004
|
+
const endsRun = data.status !== undefined && isTerminalWorkflowRunStatus(data.status);
|
|
8005
|
+
// A run that ends reads the status it replaces in the same transaction, so
|
|
8006
|
+
// only a change out of a live status announces the end: a later terminal
|
|
8007
|
+
// write, same status or not, does not announce it again.
|
|
8008
|
+
const [previousStatus, row] = endsRun
|
|
8009
|
+
? await getDbClient().transaction(async () => {
|
|
8010
|
+
const previous = await getDbClient().get<{ status: WorkflowRunStatus }>(
|
|
8011
|
+
"SELECT status FROM workflow_runs WHERE id = ?",
|
|
8012
|
+
[id],
|
|
8013
|
+
);
|
|
8014
|
+
return [previous?.status, await update()] as const;
|
|
8015
|
+
})
|
|
8016
|
+
: [undefined, await update()];
|
|
8001
8017
|
if (!row) return null;
|
|
8002
8018
|
const run = rowToWorkflowRun(row);
|
|
8003
8019
|
if (data.status === "completed" || data.status === "failed") {
|
|
8004
8020
|
emitWorkflowTerminalTelemetry(run);
|
|
8005
8021
|
}
|
|
8006
8022
|
if (row.parentStepId) emitChildRunFinished(id, row.parentStepId, run.status);
|
|
8023
|
+
if (endsRun && previousStatus && !isTerminalWorkflowRunStatus(previousStatus)) {
|
|
8024
|
+
emitRunFinished(id, run.workflowId, run.status);
|
|
8025
|
+
}
|
|
8007
8026
|
return run;
|
|
8008
8027
|
}
|
|
8009
8028
|
|
|
@@ -11721,6 +11740,14 @@ export interface AvailableKeyIndicesResult {
|
|
|
11721
11740
|
* whose `plan` cannot run the family (`planAllowsModelFamily`) is excluded
|
|
11722
11741
|
* and reported in `seatBlockedIndices`, also when it is key-wide or
|
|
11723
11742
|
* model-window blocked; a seat block has no reset time.
|
|
11743
|
+
*
|
|
11744
|
+
* With `keySuffixes` (the caller's own pool, in order), rows are matched by
|
|
11745
|
+
* key suffix instead of the stored `keyIndex`, across every scope: a key has
|
|
11746
|
+
* one state wherever it is pooled, and a key at the same index in another
|
|
11747
|
+
* agent's pool is never blocked by it. A suffix listed more than once is read
|
|
11748
|
+
* by `keyIndex` in `scope`/`scopeId`, as without the list. Returned indices
|
|
11749
|
+
* are positions in `keySuffixes`. Without it, rows of `scope`/`scopeId` are
|
|
11750
|
+
* read by `keyIndex`.
|
|
11724
11751
|
*/
|
|
11725
11752
|
export async function getAvailableKeyIndices(
|
|
11726
11753
|
keyType: string,
|
|
@@ -11728,41 +11755,58 @@ export async function getAvailableKeyIndices(
|
|
|
11728
11755
|
scope = "global",
|
|
11729
11756
|
scopeId: string | null = null,
|
|
11730
11757
|
modelFamily?: ModelFamily,
|
|
11758
|
+
keySuffixes?: string[],
|
|
11731
11759
|
): Promise<AvailableKeyIndicesResult> {
|
|
11732
11760
|
const now = new Date().toISOString();
|
|
11733
11761
|
const nowMs = Date.now();
|
|
11734
11762
|
const client = getDbClient();
|
|
11735
11763
|
const effectiveScopeId = scopeId ?? "";
|
|
11764
|
+
const suffixes = keySuffixes?.length ? keySuffixes : undefined;
|
|
11765
|
+
const rowFilter = suffixes
|
|
11766
|
+
? `keyType = ? AND keySuffix IN (${suffixes.map(() => "?").join(", ")})`
|
|
11767
|
+
: "keyType = ? AND scope = ? AND scopeId = ?";
|
|
11768
|
+
const rowParams = suffixes ? [keyType, ...suffixes] : [keyType, scope, effectiveScopeId];
|
|
11736
11769
|
|
|
11737
11770
|
// Auto-clear expired rate limits. Never an auth bench: only `clearAuthBench` lifts it.
|
|
11738
11771
|
await client.run(
|
|
11739
11772
|
`UPDATE api_key_status
|
|
11740
11773
|
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
11741
|
-
WHERE
|
|
11774
|
+
WHERE ${rowFilter}
|
|
11742
11775
|
AND status = 'rate_limited' AND rateLimitedUntil <= ? AND consecutiveAuthFailures < ?`,
|
|
11743
|
-
[now,
|
|
11776
|
+
[now, ...rowParams, now, CODEX_AUTH_FAILURE_BENCH_THRESHOLD],
|
|
11744
11777
|
);
|
|
11745
11778
|
|
|
11746
11779
|
const rows = await client.query<{
|
|
11747
11780
|
keyIndex: number;
|
|
11781
|
+
keySuffix: string;
|
|
11782
|
+
scope: string;
|
|
11783
|
+
scopeId: string;
|
|
11748
11784
|
status: string;
|
|
11749
11785
|
rateLimitWindows: string | null;
|
|
11750
11786
|
plan: string | null;
|
|
11751
11787
|
authFailureSeq: number;
|
|
11752
11788
|
lastValidationFailedAt: string | null;
|
|
11753
11789
|
}>(
|
|
11754
|
-
`SELECT keyIndex, status, rateLimitWindows, plan, authFailureSeq, lastValidationFailedAt FROM api_key_status
|
|
11755
|
-
WHERE
|
|
11756
|
-
|
|
11757
|
-
);
|
|
11790
|
+
`SELECT keyIndex, keySuffix, scope, scopeId, status, rateLimitWindows, plan, authFailureSeq, lastValidationFailedAt FROM api_key_status
|
|
11791
|
+
WHERE ${rowFilter}`,
|
|
11792
|
+
rowParams,
|
|
11793
|
+
);
|
|
11794
|
+
// Pool positions a row applies to: the position holding its suffix, or its stored index.
|
|
11795
|
+
// A suffix held by several pool members cannot tell them apart, so it falls back to
|
|
11796
|
+
// the index read: only a row of the requested scope, at its stored index.
|
|
11797
|
+
const positionsOf = (row: (typeof rows)[number]): number[] => {
|
|
11798
|
+
if (!suffixes) return [row.keyIndex];
|
|
11799
|
+
const positions = suffixes.flatMap((suffix, i) => (suffix === row.keySuffix ? [i] : []));
|
|
11800
|
+
if (positions.length < 2) return positions;
|
|
11801
|
+
const sameScope = row.scope === scope && row.scopeId === effectiveScopeId;
|
|
11802
|
+
return sameScope && positions.includes(row.keyIndex) ? [row.keyIndex] : [];
|
|
11803
|
+
};
|
|
11758
11804
|
|
|
11759
11805
|
const blockedIndices = new Set(
|
|
11760
|
-
rows.filter((r) => r.status === "rate_limited").
|
|
11806
|
+
rows.filter((r) => r.status === "rate_limited").flatMap(positionsOf),
|
|
11761
11807
|
);
|
|
11762
11808
|
const rejectedIndices = [
|
|
11763
|
-
...new Set(
|
|
11764
|
-
rows.filter((row) => row.lastValidationFailedAt !== null).map((row) => row.keyIndex),
|
|
11765
|
-
),
|
|
11809
|
+
...new Set(rows.filter((row) => row.lastValidationFailedAt !== null).flatMap(positionsOf)),
|
|
11766
11810
|
];
|
|
11767
11811
|
for (const index of rejectedIndices) blockedIndices.add(index);
|
|
11768
11812
|
|
|
@@ -11773,14 +11817,17 @@ export async function getAvailableKeyIndices(
|
|
|
11773
11817
|
// A key already blocked key-wide doesn't need a separate model-block
|
|
11774
11818
|
// entry — it's excluded from availableIndices either way, and
|
|
11775
11819
|
// modelBlockedIndices means "excluded only by a model-scoped block".
|
|
11776
|
-
|
|
11820
|
+
const positions = positionsOf(row).filter(
|
|
11821
|
+
(i) => !blockedIndices.has(i) && !modelBlockedIndices.includes(i),
|
|
11822
|
+
);
|
|
11823
|
+
if (positions.length === 0) continue;
|
|
11777
11824
|
const block = activeModelBlock(
|
|
11778
11825
|
parseRateLimitWindowsJson(row.rateLimitWindows),
|
|
11779
11826
|
modelFamily,
|
|
11780
11827
|
nowMs,
|
|
11781
11828
|
);
|
|
11782
11829
|
if (!block) continue;
|
|
11783
|
-
modelBlockedIndices.push(
|
|
11830
|
+
modelBlockedIndices.push(...positions);
|
|
11784
11831
|
if (earliestModelResetsAtSec === undefined || block.resetsAt < earliestModelResetsAtSec) {
|
|
11785
11832
|
earliestModelResetsAtSec = block.resetsAt;
|
|
11786
11833
|
}
|
|
@@ -11794,7 +11841,10 @@ export async function getAvailableKeyIndices(
|
|
|
11794
11841
|
const seatBlockedIndices: number[] = [];
|
|
11795
11842
|
if (modelFamily) {
|
|
11796
11843
|
for (const row of rows) {
|
|
11797
|
-
if (
|
|
11844
|
+
if (planAllowsModelFamily(row.plan, modelFamily)) continue;
|
|
11845
|
+
for (const i of positionsOf(row)) {
|
|
11846
|
+
if (!seatBlockedIndices.includes(i)) seatBlockedIndices.push(i);
|
|
11847
|
+
}
|
|
11798
11848
|
}
|
|
11799
11849
|
}
|
|
11800
11850
|
const seatBlockedSet = new Set(seatBlockedIndices);
|
|
@@ -12143,22 +12193,30 @@ export async function clearKeyRateLimit(
|
|
|
12143
12193
|
const effectiveScopeId = scopeId ?? "";
|
|
12144
12194
|
const { keyIndex = -1, authFence = null } = opts;
|
|
12145
12195
|
const clearAuthBench = opts.clearAuthBench === true && authFence !== null;
|
|
12146
|
-
const key = [keyType, keySuffix, scope, effectiveScopeId];
|
|
12147
12196
|
return await getDbClient().transaction(async (tx) => {
|
|
12197
|
+
// The key's rows in every scope: `getAvailableKeyIndices` reads them all by suffix.
|
|
12148
12198
|
const result = await tx.run(
|
|
12149
12199
|
`UPDATE api_key_status
|
|
12150
12200
|
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
12151
|
-
WHERE keyType = ? AND keySuffix = ?
|
|
12201
|
+
WHERE keyType = ? AND keySuffix = ?
|
|
12152
12202
|
AND status = 'rate_limited' AND ((? AND authFailureSeq <= ?) OR consecutiveAuthFailures < ?)`,
|
|
12153
|
-
[
|
|
12203
|
+
[
|
|
12204
|
+
now,
|
|
12205
|
+
keyType,
|
|
12206
|
+
keySuffix,
|
|
12207
|
+
clearAuthBench ? 1 : 0,
|
|
12208
|
+
authFence,
|
|
12209
|
+
CODEX_AUTH_FAILURE_BENCH_THRESHOLD,
|
|
12210
|
+
],
|
|
12154
12211
|
);
|
|
12155
12212
|
const cleared = result.changes > 0;
|
|
12156
12213
|
if (!clearAuthBench) return cleared;
|
|
12157
12214
|
const lifted = await tx.query<{ keySuffix: string }>(
|
|
12158
12215
|
`UPDATE api_key_status SET consecutiveAuthFailures = 0, updatedAt = ?1,
|
|
12159
12216
|
status = CASE WHEN keySuffix = ?2 THEN status ELSE 'available' END, rateLimitedUntil = CASE WHEN keySuffix = ?2 THEN rateLimitedUntil END
|
|
12160
|
-
WHERE keyType = ?3 AND
|
|
12161
|
-
AND ((keySuffix = ?2 AND authFailureSeq <= ?7)
|
|
12217
|
+
WHERE keyType = ?3 AND (consecutiveAuthFailures > 0 OR status = 'rate_limited')
|
|
12218
|
+
AND ((keySuffix = ?2 AND authFailureSeq <= ?7)
|
|
12219
|
+
OR (keySuffix != ?2 AND keyIndex = ?6 AND scope = ?4 AND scopeId = ?5)) RETURNING keySuffix`,
|
|
12162
12220
|
[now, keySuffix, keyType, scope, effectiveScopeId, keyIndex, authFence],
|
|
12163
12221
|
);
|
|
12164
12222
|
const retired = lifted.filter((row) => row.keySuffix !== keySuffix);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learnings captured through `store-progress`: one swarm-scope `manual` memory
|
|
3
|
+
* per learning, keyed `/learnings/<workstream>/<slug>` and tied to its task by
|
|
4
|
+
* `sourceTaskId`, so "what did we learn doing X" is one `keyPrefix` search.
|
|
5
|
+
*
|
|
6
|
+
* Source stays `manual` so nightly curation (memtax B1 reads only manual and
|
|
7
|
+
* file_index rows) picks learnings up without a new source value.
|
|
8
|
+
*/
|
|
9
|
+
import { getDbClient } from "@/be/db";
|
|
10
|
+
import { indexMemoryContent } from "@/be/memory/index-content";
|
|
11
|
+
import { contentSha256 } from "@/commands/profile-sync";
|
|
12
|
+
import type { AgentTask } from "@/types";
|
|
13
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
14
|
+
|
|
15
|
+
/** ~500 tokens at ceil(chars / 4); also keeps a learning to one memory chunk. */
|
|
16
|
+
export const LEARNING_MAX_CHARS = 2000;
|
|
17
|
+
export const LEARNING_MAX_TAGS = 5;
|
|
18
|
+
const LEARNINGS_ROOT = "/learnings";
|
|
19
|
+
const SEGMENT_MAX_CHARS = 60;
|
|
20
|
+
const KEY_ATTEMPTS = 4;
|
|
21
|
+
|
|
22
|
+
export type TaskLearningInput = string | { text: string; workstream?: string; tags?: string[] };
|
|
23
|
+
|
|
24
|
+
export type TaskLearningResult =
|
|
25
|
+
| { status: "stored"; memoryId: string; key: string; workstream: string }
|
|
26
|
+
| { status: "replayed"; memoryId: string; key: string }
|
|
27
|
+
| { status: "failed"; error: string };
|
|
28
|
+
|
|
29
|
+
/** Lowercase kebab slug: `Agent Swarm!` -> `agent-swarm`. Empty when nothing usable is left. */
|
|
30
|
+
export function toKebab(value: string, maxChars = SEGMENT_MAX_CHARS): string {
|
|
31
|
+
return value
|
|
32
|
+
.normalize("NFKD")
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
35
|
+
.replace(/^-+/, "")
|
|
36
|
+
.slice(0, maxChars)
|
|
37
|
+
.replace(/-+$/, "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Explicit workstream, else the repo name from `vcsRepo` (`owner/name` or a URL), else `general`. */
|
|
41
|
+
export function resolveLearningWorkstream(
|
|
42
|
+
explicit: string | undefined,
|
|
43
|
+
vcsRepo: string | undefined,
|
|
44
|
+
): string {
|
|
45
|
+
const fromExplicit = explicit ? toKebab(explicit) : "";
|
|
46
|
+
if (fromExplicit) return fromExplicit;
|
|
47
|
+
const repoName = vcsRepo
|
|
48
|
+
?.trim()
|
|
49
|
+
.replace(/\/+$/, "")
|
|
50
|
+
.split("/")
|
|
51
|
+
.pop()
|
|
52
|
+
?.replace(/\.git$/, "");
|
|
53
|
+
const fromRepo = repoName ? toKebab(repoName) : "";
|
|
54
|
+
return fromRepo || "general";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function storeTaskLearning(params: {
|
|
58
|
+
agentId: string;
|
|
59
|
+
isLead: boolean;
|
|
60
|
+
task: Pick<AgentTask, "id" | "vcsRepo">;
|
|
61
|
+
learning: TaskLearningInput;
|
|
62
|
+
}): Promise<TaskLearningResult> {
|
|
63
|
+
const { agentId, isLead, task } = params;
|
|
64
|
+
const input = typeof params.learning === "string" ? { text: params.learning } : params.learning;
|
|
65
|
+
// Scrub before anything is derived: a token in the title, workstream or a tag
|
|
66
|
+
// would otherwise survive (kebab-cased) in the key and tags, and the replay
|
|
67
|
+
// hash must match the hash of the scrubbed content the store persists.
|
|
68
|
+
const text = scrubSecrets(input.text).trim();
|
|
69
|
+
if (!text) return { status: "failed", error: "learning text is empty" };
|
|
70
|
+
if (text.length > LEARNING_MAX_CHARS) {
|
|
71
|
+
return {
|
|
72
|
+
status: "failed",
|
|
73
|
+
error: `learning text is over ~500 tokens (${LEARNING_MAX_CHARS} characters); shorten it`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const tags = [
|
|
77
|
+
...new Set((input.tags ?? []).map((tag) => toKebab(scrubSecrets(tag))).filter(Boolean)),
|
|
78
|
+
].filter((tag) => tag !== "learning");
|
|
79
|
+
if (tags.length > LEARNING_MAX_TAGS) {
|
|
80
|
+
return { status: "failed", error: `learning takes at most ${LEARNING_MAX_TAGS} tags` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const workstream = resolveLearningWorkstream(
|
|
84
|
+
input.workstream === undefined ? undefined : scrubSecrets(input.workstream),
|
|
85
|
+
task.vcsRepo === undefined ? undefined : scrubSecrets(task.vcsRepo),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// One write transaction for the replay check and the insert, so two
|
|
90
|
+
// overlapping calls with the same learning store it once.
|
|
91
|
+
return await getDbClient().transaction(() =>
|
|
92
|
+
insertLearning({ agentId, isLead, taskId: task.id, text, tags, workstream }),
|
|
93
|
+
);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
status: "failed",
|
|
97
|
+
error: scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function insertLearning(params: {
|
|
103
|
+
agentId: string;
|
|
104
|
+
isLead: boolean;
|
|
105
|
+
taskId: string;
|
|
106
|
+
text: string;
|
|
107
|
+
tags: string[];
|
|
108
|
+
workstream: string;
|
|
109
|
+
}): Promise<TaskLearningResult> {
|
|
110
|
+
const { agentId, isLead, taskId, text, tags, workstream } = params;
|
|
111
|
+
// indexMemoryContent stores text this short as one trimmed chunk, so this
|
|
112
|
+
// hash is the row's contentHash: a replayed call finds its own earlier write.
|
|
113
|
+
const contentHash = contentSha256(text);
|
|
114
|
+
const replay = await getDbClient().get<{ id: string; key: string }>(
|
|
115
|
+
`SELECT id, key FROM agent_memory
|
|
116
|
+
WHERE sourceTaskId = ? AND contentHash = ? AND key LIKE '${LEARNINGS_ROOT}/%'
|
|
117
|
+
LIMIT 1`,
|
|
118
|
+
[taskId, contentHash],
|
|
119
|
+
);
|
|
120
|
+
if (replay) return { status: "replayed", memoryId: replay.id, key: replay.key };
|
|
121
|
+
|
|
122
|
+
const title = text.split(/\r?\n/, 1)[0]!.trim().slice(0, 200);
|
|
123
|
+
const baseKey = `${LEARNINGS_ROOT}/${workstream}/${toKebab(title) || "learning"}`;
|
|
124
|
+
|
|
125
|
+
// Keys are unique per owner and a reused key fails rather than upserts.
|
|
126
|
+
// Keep the path unique across the swarm: on a taken key, add a short suffix.
|
|
127
|
+
for (let attempt = 0; attempt < KEY_ATTEMPTS; attempt++) {
|
|
128
|
+
const key = attempt === 0 ? baseKey : `${baseKey}-${crypto.randomUUID().slice(0, 6)}`;
|
|
129
|
+
const taken = await getDbClient().get<{ id: string }>(
|
|
130
|
+
"SELECT id FROM agent_memory WHERE scope = 'swarm' AND key = ? LIMIT 1",
|
|
131
|
+
[key],
|
|
132
|
+
);
|
|
133
|
+
if (taken) continue;
|
|
134
|
+
try {
|
|
135
|
+
const result = await indexMemoryContent({
|
|
136
|
+
agentId,
|
|
137
|
+
content: text,
|
|
138
|
+
name: title,
|
|
139
|
+
scope: "swarm",
|
|
140
|
+
source: "manual",
|
|
141
|
+
sourceTaskId: taskId,
|
|
142
|
+
tags: ["learning", ...tags],
|
|
143
|
+
intent: "store-progress learning",
|
|
144
|
+
key,
|
|
145
|
+
writer: { principal: { kind: "agent", agentId, isLead }, source: "mcp" },
|
|
146
|
+
});
|
|
147
|
+
return { status: "stored", memoryId: result.memoryIds[0]!, key, workstream };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// The key is taken after all: try a suffixed one.
|
|
150
|
+
if (/UNIQUE constraint failed/i.test((err as Error).message)) continue;
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { status: "failed", error: `no free key under ${baseKey}` };
|
|
155
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* same per-authorization locks in ensure-token, so overlap is safe.
|
|
21
21
|
*/
|
|
22
22
|
import { forceRefreshAuthorizationOrThrow } from "../oauth/ensure-token";
|
|
23
|
+
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
23
24
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
24
25
|
import {
|
|
25
26
|
type AuthorizationSweepRow,
|
|
@@ -138,7 +139,7 @@ async function runSweepTick(trigger: "startup" | "interval"): Promise<void> {
|
|
|
138
139
|
* already running.
|
|
139
140
|
*/
|
|
140
141
|
export function startOAuthRefreshSweep(): void {
|
|
141
|
-
if (
|
|
142
|
+
if (isEnvFlagEnabled("OAUTH_REFRESH_SWEEP_DISABLE", false)) {
|
|
142
143
|
console.log("[oauth-refresh-sweep] Disabled via OAUTH_REFRESH_SWEEP_DISABLE");
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
@@ -26,6 +26,7 @@ type ScriptRow = {
|
|
|
26
26
|
isScratch: number;
|
|
27
27
|
typeChecked: number;
|
|
28
28
|
fsMode: "none" | "workspace-rw";
|
|
29
|
+
executor: "native" | "quickjs" | null;
|
|
29
30
|
createdByAgentId: string | null;
|
|
30
31
|
createdAt: string;
|
|
31
32
|
updatedAt: string;
|
|
@@ -37,6 +38,7 @@ function toScriptRecord(row: ScriptRow) {
|
|
|
37
38
|
scopeId: row.scopeId ?? null,
|
|
38
39
|
isScratch: row.isScratch === 1,
|
|
39
40
|
typeChecked: row.typeChecked === 1,
|
|
41
|
+
executor: row.executor ?? null,
|
|
40
42
|
createdByAgentId: row.createdByAgentId ?? null,
|
|
41
43
|
};
|
|
42
44
|
}
|
package/src/be/scripts/db.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ScriptApiAuthMode,
|
|
4
4
|
ScriptApiRecord,
|
|
5
5
|
ScriptApiWithSecret,
|
|
6
|
+
ScriptExecutorName,
|
|
6
7
|
ScriptFsMode,
|
|
7
8
|
ScriptRecord,
|
|
8
9
|
ScriptScope,
|
|
@@ -36,6 +37,7 @@ type ScriptWriteArgs = ScriptIdentity & {
|
|
|
36
37
|
isScratch?: boolean;
|
|
37
38
|
typeChecked?: boolean;
|
|
38
39
|
fsMode?: ScriptFsMode;
|
|
40
|
+
executor?: ScriptExecutorName | null;
|
|
39
41
|
agentId?: string | null;
|
|
40
42
|
changeReason?: string | null;
|
|
41
43
|
embeddingMode?: "sync" | "skip";
|
|
@@ -62,6 +64,7 @@ function rowToScript(row: ScriptRow): ScriptRecord {
|
|
|
62
64
|
scopeId: row.scopeId ?? null,
|
|
63
65
|
isScratch: row.isScratch === 1,
|
|
64
66
|
typeChecked: row.typeChecked === 1,
|
|
67
|
+
executor: row.executor ?? null,
|
|
65
68
|
createdByAgentId: row.createdByAgentId ?? null,
|
|
66
69
|
};
|
|
67
70
|
}
|
|
@@ -113,6 +116,7 @@ export async function insertScript(args: ScriptWriteArgs): Promise<ScriptRecord>
|
|
|
113
116
|
const scopeId = normalizeScopeId(args.scope, args.scopeId);
|
|
114
117
|
const contentHash = computeContentHash(args.source);
|
|
115
118
|
const fsMode = args.fsMode ?? "none";
|
|
119
|
+
const executor = args.executor ?? null;
|
|
116
120
|
const isScratch = args.isScratch ? 1 : 0;
|
|
117
121
|
const typeChecked = args.typeChecked ? 1 : 0;
|
|
118
122
|
|
|
@@ -120,10 +124,10 @@ export async function insertScript(args: ScriptWriteArgs): Promise<ScriptRecord>
|
|
|
120
124
|
const row = await tx.get<ScriptRow>(
|
|
121
125
|
`INSERT INTO scripts (
|
|
122
126
|
id, "key", name, scope, scopeId, source, description, intent, signatureJson,
|
|
123
|
-
argsJsonSchema, contentHash, isScratch, typeChecked, fsMode, createdByAgentId, createdAt, updatedAt,
|
|
127
|
+
argsJsonSchema, contentHash, isScratch, typeChecked, fsMode, executor, createdByAgentId, createdAt, updatedAt,
|
|
124
128
|
created_by, updated_by
|
|
125
129
|
)
|
|
126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
130
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
127
131
|
RETURNING *`,
|
|
128
132
|
[
|
|
129
133
|
id,
|
|
@@ -140,6 +144,7 @@ export async function insertScript(args: ScriptWriteArgs): Promise<ScriptRecord>
|
|
|
140
144
|
isScratch,
|
|
141
145
|
typeChecked,
|
|
142
146
|
fsMode,
|
|
147
|
+
executor,
|
|
143
148
|
args.agentId ?? null,
|
|
144
149
|
now,
|
|
145
150
|
now,
|
|
@@ -189,6 +194,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
189
194
|
const contentHash = computeContentHash(args.source);
|
|
190
195
|
if (existing.contentHash === contentHash) {
|
|
191
196
|
const fsMode = args.fsMode ?? existing.fsMode;
|
|
197
|
+
const executor = args.executor !== undefined ? args.executor : existing.executor;
|
|
192
198
|
const isScratch = args.isScratch ?? existing.isScratch;
|
|
193
199
|
const typeChecked = args.typeChecked ?? existing.typeChecked;
|
|
194
200
|
const argsJsonSchema =
|
|
@@ -206,6 +212,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
206
212
|
isScratch;
|
|
207
213
|
if (
|
|
208
214
|
fsMode !== existing.fsMode ||
|
|
215
|
+
executor !== existing.executor ||
|
|
209
216
|
isScratch !== existing.isScratch ||
|
|
210
217
|
typeChecked !== existing.typeChecked ||
|
|
211
218
|
trackedMetadataChanged ||
|
|
@@ -214,7 +221,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
214
221
|
const row = await getDbClient().get<ScriptRow>(
|
|
215
222
|
`UPDATE scripts
|
|
216
223
|
SET description = ?, intent = ?, signatureJson = ?, argsJsonSchema = ?,
|
|
217
|
-
isScratch = ?, typeChecked = ?, fsMode = ?, updatedAt = ?, updated_by = ?
|
|
224
|
+
isScratch = ?, typeChecked = ?, fsMode = ?, executor = ?, updatedAt = ?, updated_by = ?
|
|
218
225
|
WHERE id = ?
|
|
219
226
|
RETURNING *`,
|
|
220
227
|
[
|
|
@@ -225,6 +232,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
225
232
|
isScratch ? 1 : 0,
|
|
226
233
|
typeChecked ? 1 : 0,
|
|
227
234
|
fsMode,
|
|
235
|
+
executor ?? null,
|
|
228
236
|
new Date().toISOString(),
|
|
229
237
|
args.createdBy ?? null,
|
|
230
238
|
existing.id,
|
|
@@ -253,6 +261,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
253
261
|
const now = new Date().toISOString();
|
|
254
262
|
const newVersion = existing.version + 1;
|
|
255
263
|
const fsMode = args.fsMode ?? existing.fsMode;
|
|
264
|
+
const executor = args.executor !== undefined ? args.executor : existing.executor;
|
|
256
265
|
const isScratch = args.isScratch ?? existing.isScratch;
|
|
257
266
|
const typeChecked = args.typeChecked ?? existing.typeChecked;
|
|
258
267
|
const argsJsonSchema =
|
|
@@ -262,7 +271,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
262
271
|
const row = await tx.get<ScriptRow>(
|
|
263
272
|
`UPDATE scripts
|
|
264
273
|
SET source = ?, description = ?, intent = ?, signatureJson = ?, argsJsonSchema = ?,
|
|
265
|
-
contentHash = ?, version = ?, isScratch = ?, typeChecked = ?, fsMode = ?, updatedAt = ?, updated_by = ?
|
|
274
|
+
contentHash = ?, version = ?, isScratch = ?, typeChecked = ?, fsMode = ?, executor = ?, updatedAt = ?, updated_by = ?
|
|
266
275
|
WHERE id = ?
|
|
267
276
|
RETURNING *`,
|
|
268
277
|
[
|
|
@@ -276,6 +285,7 @@ export async function upsertScriptByName(args: ScriptWriteArgs): Promise<UpsertS
|
|
|
276
285
|
isScratch ? 1 : 0,
|
|
277
286
|
typeChecked ? 1 : 0,
|
|
278
287
|
fsMode,
|
|
288
|
+
executor ?? null,
|
|
279
289
|
now,
|
|
280
290
|
args.createdBy ?? null,
|
|
281
291
|
existing.id,
|
|
@@ -28,6 +28,7 @@ type ScriptEmbeddingCandidateRow = ScriptEmbeddingRow & {
|
|
|
28
28
|
isScratch: number;
|
|
29
29
|
typeChecked: number;
|
|
30
30
|
fsMode: "none" | "workspace-rw";
|
|
31
|
+
executor: "native" | "quickjs" | null;
|
|
31
32
|
createdByAgentId: string | null;
|
|
32
33
|
createdAt: string;
|
|
33
34
|
updatedAt: string;
|
|
@@ -70,6 +71,7 @@ function rowToScript(row: ScriptEmbeddingCandidateRow): ScriptRecord {
|
|
|
70
71
|
isScratch: row.isScratch === 1,
|
|
71
72
|
typeChecked: row.typeChecked === 1,
|
|
72
73
|
fsMode: row.fsMode,
|
|
74
|
+
executor: row.executor ?? null,
|
|
73
75
|
createdByAgentId: row.createdByAgentId ?? null,
|
|
74
76
|
createdAt: row.createdAt,
|
|
75
77
|
updatedAt: row.updatedAt,
|
|
@@ -50,6 +50,7 @@ export async function runSavedScriptAsAgent(
|
|
|
50
50
|
source: args.script.source,
|
|
51
51
|
args: args.input,
|
|
52
52
|
fsMode: args.script.fsMode,
|
|
53
|
+
executor: args.script.executor,
|
|
53
54
|
agentId: args.agentId,
|
|
54
55
|
egressSecrets: credentials.egressSecrets,
|
|
55
56
|
failedBindings: credentials.failedBindings,
|
|
@@ -33,6 +33,7 @@ export const SCRIPT_SDK_TYPES = `
|
|
|
33
33
|
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
34
34
|
export type ScriptScope = "agent" | "global";
|
|
35
35
|
export type ScriptFsMode = "none" | "workspace-rw";
|
|
36
|
+
export type ScriptExecutorName = "native" | "quickjs";
|
|
36
37
|
export type ScriptApiRawOptions = { raw: true };
|
|
37
38
|
export type ScriptApiDefaultOptions = { raw?: false };
|
|
38
39
|
export type RoutingReason = "skill" | "continuity" | "overflow" | "human_pinned" | "reroute_fault";
|
|
@@ -224,7 +225,7 @@ export interface SwarmSdk {
|
|
|
224
225
|
schedule_list(args?: { enabled?: boolean; name?: string; scheduleType?: "recurring" | "one_time"; targetType?: "agent-task" | "workflow" | "script"; workflowId?: string; scriptName?: string; hideCompleted?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "failed" | "succeeded"; includeFull?: boolean }): Promise<unknown>;
|
|
225
226
|
// --- scripts ---
|
|
226
227
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
227
|
-
script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; idempotencyKey?: string }): Promise<unknown>;
|
|
228
|
+
script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; executor?: ScriptExecutorName | null; idempotencyKey?: string }): Promise<unknown>;
|
|
228
229
|
// --- swarm / agent ---
|
|
229
230
|
swarm_get(args?: { includeFull?: boolean }): Promise<unknown>;
|
|
230
231
|
agent_info(args?: Record<string, unknown>): Promise<unknown>;
|
|
@@ -341,7 +342,12 @@ export interface SwarmSdk {
|
|
|
341
342
|
workflow_patch(args: Record<string, unknown>): Promise<unknown>;
|
|
342
343
|
workflow_patchNode(args: Record<string, unknown>): Promise<unknown>;
|
|
343
344
|
workflow_delete(args: { id: string }): Promise<unknown>;
|
|
344
|
-
workflow_trigger(args: {
|
|
345
|
+
workflow_trigger(args: {
|
|
346
|
+
id: string;
|
|
347
|
+
triggerData?: Record<string, unknown>;
|
|
348
|
+
wait?: boolean;
|
|
349
|
+
idempotencyKey?: string;
|
|
350
|
+
}): Promise<unknown>;
|
|
345
351
|
workflow_retryRun(args: { id: string }): Promise<unknown>;
|
|
346
352
|
workflow_cancelRun(args: { id: string }): Promise<unknown>;
|
|
347
353
|
|
|
@@ -351,7 +357,7 @@ export interface SwarmSdk {
|
|
|
351
357
|
prompt_preview(args: Record<string, unknown>): Promise<unknown>;
|
|
352
358
|
|
|
353
359
|
// --- write: scripts ---
|
|
354
|
-
script_upsert(args: { name: string; source: string; description?: string; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode }): Promise<unknown>;
|
|
360
|
+
script_upsert(args: { name: string; source: string; description?: string; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; executor?: ScriptExecutorName | null }): Promise<unknown>;
|
|
355
361
|
script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
|
|
356
362
|
script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
|
|
357
363
|
script_launchRun(args: { source: string; args?: unknown; idempotencyKey?: string; scriptName?: string; requestedByUserId?: string }): Promise<unknown>;
|
|
@@ -208,6 +208,12 @@ import stepRunningConfig from "../../../templates/skills/step-running/config.jso
|
|
|
208
208
|
import stepRunningContent from "../../../templates/skills/step-running/content.md" with {
|
|
209
209
|
type: "text",
|
|
210
210
|
};
|
|
211
|
+
import swarmDocsConfig from "../../../templates/skills/swarm-docs/config.json" with {
|
|
212
|
+
type: "text",
|
|
213
|
+
};
|
|
214
|
+
import swarmDocsContent from "../../../templates/skills/swarm-docs/content.md" with {
|
|
215
|
+
type: "text",
|
|
216
|
+
};
|
|
211
217
|
import swarmExtensionsConfig from "../../../templates/skills/swarm-extensions/config.json" with {
|
|
212
218
|
type: "text",
|
|
213
219
|
};
|
|
@@ -367,6 +373,7 @@ const BUILT_IN_SKILL_SOURCES = [
|
|
|
367
373
|
{ config: reviewingConfig, body: reviewingContent },
|
|
368
374
|
{ config: scheduledTaskResilienceConfig, body: scheduledTaskResilienceContent },
|
|
369
375
|
{ config: schedulingConfig, body: schedulingContent },
|
|
376
|
+
{ config: swarmDocsConfig, body: swarmDocsContent },
|
|
370
377
|
{ config: swarmExtensionsConfig, body: swarmExtensionsContent },
|
|
371
378
|
{ config: scriptBuilderConfig, body: scriptBuilderContent },
|
|
372
379
|
{ config: scriptWorkflowsConfig, body: scriptWorkflowsContent },
|
|
@@ -243,9 +243,11 @@ export function generateCompose(state: OnboardState): string {
|
|
|
243
243
|
lines.push(` - "${port}:3000"`);
|
|
244
244
|
lines.push("");
|
|
245
245
|
lines.push(" volumes:");
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
lines.push(
|
|
246
|
+
// Same targets as docker-compose.example.yml: the worker logs to /logs and
|
|
247
|
+
// keeps its clones and files under /workspace.
|
|
248
|
+
lines.push(" - swarm_logs:/logs");
|
|
249
|
+
lines.push(" - swarm_shared:/workspace/shared");
|
|
250
|
+
lines.push(` - swarm_${svc.sanitizedName}:/workspace/personal`);
|
|
249
251
|
if (state.provider === "bedrock" && state.awsProfile) {
|
|
250
252
|
lines.push(engine === "podman" ? AWS_PROFILE_VOLUME_SELINUX : AWS_PROFILE_VOLUME);
|
|
251
253
|
}
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
CREDENTIAL_POOL_VARS,
|
|
50
50
|
splitCredentialPool,
|
|
51
51
|
} from "../utils/credentials";
|
|
52
|
+
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
52
53
|
import {
|
|
53
54
|
checkAnthropicApiKey,
|
|
54
55
|
checkDeepSeekApiKey,
|
|
@@ -664,7 +665,7 @@ export async function validateProviderCredentials(
|
|
|
664
665
|
* the dashboard surfaces "unreported".
|
|
665
666
|
*/
|
|
666
667
|
export function isCredCheckDisabled(env: NodeJS.ProcessEnv): boolean {
|
|
667
|
-
return
|
|
668
|
+
return isEnvFlagEnabled("CRED_CHECK_DISABLE", false, env);
|
|
668
669
|
}
|
|
669
670
|
|
|
670
671
|
/**
|
|
@@ -83,6 +83,7 @@ import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../utils/open
|
|
|
83
83
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
84
84
|
import { getExecutorRegistry } from "../workflows";
|
|
85
85
|
import { recoverIncompleteRuns } from "../workflows/recovery";
|
|
86
|
+
import { probeScriptRunner } from "./script-runner-probe";
|
|
86
87
|
// Side-effect import: registers heartbeat event templates in the in-memory registry
|
|
87
88
|
import "./templates";
|
|
88
89
|
|
|
@@ -1703,8 +1704,20 @@ export async function defaultChecklistGateRunner(
|
|
|
1703
1704
|
scriptName: string,
|
|
1704
1705
|
leadAgentId: string,
|
|
1705
1706
|
signal: AbortSignal,
|
|
1706
|
-
deps: Parameters<typeof runSavedScriptAsAgent>[1]
|
|
1707
|
+
deps: Parameters<typeof runSavedScriptAsAgent>[1] & {
|
|
1708
|
+
probe?: typeof probeScriptRunner;
|
|
1709
|
+
} = {},
|
|
1707
1710
|
): Promise<unknown> {
|
|
1711
|
+
try {
|
|
1712
|
+
const unhealthy = await (deps.probe ?? probeScriptRunner)(leadAgentId);
|
|
1713
|
+
if (unhealthy) return unhealthy;
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1716
|
+
return {
|
|
1717
|
+
quiet: false,
|
|
1718
|
+
reason: `script-runner-unhealthy: ${scrubSecrets(message)}`.slice(0, 300),
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1708
1721
|
const script = await getScript({ name: scriptName, scope: "global" });
|
|
1709
1722
|
if (!script) throw new Error(`global script "${scriptName}" not found`);
|
|
1710
1723
|
const output = await runSavedScriptAsAgent(
|