@desplega.ai/agent-swarm 1.123.0 → 1.124.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 -1
- package/dist/{actions-e4c9f0xy.js → actions-z3d0fk7z.js} +6 -6
- package/dist/{app-vmvmj2v3.js → app-q2rc9tdr.js} +4 -4
- package/dist/{assistant-yn7a792a.js → assistant-3stjq7fz.js} +9 -9
- package/dist/{boot-reembed-xg1r86h2.js → boot-reembed-5867whnb.js} +3 -3
- package/dist/{boot-reembed-chxty50a.js → boot-reembed-v6r1df32.js} +4 -4
- package/dist/{boot-scrub-logs-exwddvp5.js → boot-scrub-logs-8b8jegk3.js} +2 -2
- package/dist/{cli-1592nfd6.js → cli-0925phzv.js} +1 -1
- package/dist/{cli-hbbxmhec.js → cli-2yr6edr7.js} +4 -4
- package/dist/{cli-8cq4z4x7.js → cli-3z4q09tn.js} +182 -19
- package/dist/{cli-vz9drmce.js → cli-5jn6wnd9.js} +17 -8
- package/dist/{cli-5arfywtm.js → cli-5ncdb7ff.js} +19 -9
- package/dist/{cli-3tv9z4wx.js → cli-92bqgbgy.js} +1 -1
- package/dist/{cli-fjf04hpa.js → cli-dq2ssyf0.js} +48 -56
- package/dist/{cli-mngsj9fx.js → cli-gkvf8d83.js} +1 -1
- package/dist/{cli-mepk1j0t.js → cli-gzjsamwc.js} +2 -2
- package/dist/{cli-c9m59f83.js → cli-h89g0qrd.js} +1 -1
- package/dist/{cli-n8mf7x4b.js → cli-jp4mdj9d.js} +2 -2
- package/dist/{cli-822d4bjk.js → cli-kk58tg33.js} +7 -7
- package/dist/{cli-9hj1adgp.js → cli-mcez7z4p.js} +1 -1
- package/dist/{cli-byv3720g.js → cli-mmk5vagg.js} +2 -2
- package/dist/{cli-zdp11t8m.js → cli-p0ec4vv6.js} +1 -1
- package/dist/{cli-h2say6xk.js → cli-p0zgv8pj.js} +3 -1
- package/dist/{cli-8ef5ky75.js → cli-perb20ma.js} +1 -1
- package/dist/{cli-zkj6f2sk.js → cli-qf7acvyr.js} +90 -4
- package/dist/{cli-kmm1rwmp.js → cli-qwsagsmg.js} +1 -1
- package/dist/{cli-we91ywvy.js → cli-sqv6c9b8.js} +6 -6
- package/dist/{cli-np0yhmmk.js → cli-vm20sh8d.js} +3 -3
- package/dist/{cli-p11rf59d.js → cli-wn3m6q54.js} +3 -3
- package/dist/{cli-mkpf4ndf.js → cli-x268say8.js} +2976 -5621
- package/dist/{cli-ngbsk4yx.js → cli-y43n987v.js} +1 -1
- package/dist/{cli-4509mwm5.js → cli-ykthjw29.js} +8 -8
- package/dist/{cli-9eka6jwv.js → cli-zxb72ctk.js} +2 -2
- package/dist/cli.js +12 -10
- package/dist/{commands-sjk3zfyr.js → commands-sda76yn8.js} +2 -2
- package/dist/{db-2je5agjb.js → db-vn9e4rqj.js} +6 -2
- package/dist/{e2b-2s4psnbf.js → e2b-q345y8s4.js} +0 -4
- package/dist/{handlers-yzv369hf.js → handlers-xfja18ac.js} +9 -9
- package/dist/{hook-rfsj2r8x.js → hook-2medrp47.js} +1 -40
- package/dist/{http-v9rr144m.js → http-937pyjfc.js} +99 -44
- package/dist/{index-nr2199m6.js → index-2bqd2q9v.js} +11 -11
- package/dist/{index-3xtk777g.js → index-gw4en7jg.js} +9 -9
- package/dist/{index-c3tan44f.js → index-knqczmnm.js} +10 -10
- package/dist/{index-smjdc3h9.js → index-vah60vxp.js} +8 -8
- package/dist/{keepalive-5ds53bjd.js → keepalive-20rd6hxh.js} +5 -5
- package/dist/{lead-5xhez25b.js → lead-7w2k3a49.js} +21 -21
- package/dist/{maintenance-b43d24q6.js → maintenance-p3x918yw.js} +4 -4
- package/dist/{oauth-refresh-sweep-qe286vn2.js → oauth-refresh-sweep-shm0nzm4.js} +4 -4
- package/dist/{onboard-c4r3ds3z.js → onboard-py9dnf54.js} +6 -2
- package/dist/{otel-impl-nv3rjw61.js → otel-impl-7yjkfg68.js} +1 -1
- package/dist/{pi-mono-adapter-2syx1x4d.js → pi-mono-adapter-z6h98fva.js} +4 -38
- package/dist/{pricing-refresh-5ct2wp4k.js → pricing-refresh-3snb6370.js} +4 -4
- package/dist/{rbac-roles-m502w0qv.js → rbac-roles-5e72bbpk.js} +3 -3
- package/dist/{rbac-roles-b82cxpxn.js → rbac-roles-gqxdp8st.js} +4 -4
- package/dist/{seed-pricing-jhjphbf0.js → seed-pricing-bg42d7h2.js} +3 -3
- package/dist/{setup-zbaspydq.js → setup-nzk3kbbq.js} +2 -2
- package/dist/{worker-snvhpax5.js → worker-0p1jdzkm.js} +21 -21
- package/openapi.json +146 -1
- package/package.json +3 -1
- package/src/be/db.ts +81 -4
- package/src/be/models-catalog.ts +156 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-refresh.ts +2 -0
- package/src/be/scripts/typecheck.ts +17 -4
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +1 -1
- package/src/commands/onboard/env-generator.ts +12 -0
- package/src/e2b/env.ts +0 -4
- package/src/hooks/hook.ts +0 -67
- package/src/http/all-routes.ts +1 -0
- package/src/http/index.ts +2 -0
- package/src/http/models-catalog.ts +65 -0
- package/src/http/workflows.ts +16 -4
- package/src/prompts/base-prompt.ts +8 -7
- package/src/prompts/session-templates.ts +106 -12
- package/src/providers/pi-mono-adapter.ts +12 -2
- package/src/providers/pi-mono-extension.ts +0 -64
- package/src/providers/pricing-sources.md +16 -1
- package/src/scripts-runtime/eval-harness.ts +18 -1
- package/src/scripts-runtime/swarm-sdk.ts +10 -0
- package/src/scripts-runtime/types/stdlib.d.ts +13 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -0
- package/src/server-user.ts +4 -7
- package/src/slack/blocks.ts +21 -8
- package/src/slack/responses.ts +27 -51
- package/src/slack/watcher.ts +22 -5
- package/src/telemetry.ts +169 -3
- package/src/tests/base-prompt.test.ts +79 -0
- package/src/tests/mcp-tools-user.test.ts +62 -16
- package/src/tests/mcp-tools.test.ts +6 -3
- package/src/tests/memory-edit.test.ts +21 -4
- package/src/tests/model-groups-live-catalog.test.ts +65 -0
- package/src/tests/models-catalog.test.ts +119 -0
- package/src/tests/oauth-access-token-tool.test.ts +71 -1
- package/src/tests/pricing-refresh.test.ts +25 -0
- package/src/tests/prompt-template-session.test.ts +142 -5
- package/src/tests/rbac-charact-skills.test.ts +81 -14
- package/src/tests/script-apis-mcp.test.ts +56 -8
- package/src/tests/scripts-mcp-e2e.test.ts +157 -8
- package/src/tests/scripts-typecheck.test.ts +17 -0
- package/src/tests/sdk-allowlist.test.ts +34 -1
- package/src/tests/seed-scripts.test.ts +43 -0
- package/src/tests/slack-blocks.test.ts +2 -1
- package/src/tests/slack-inline-output.test.ts +24 -30
- package/src/tests/slack-watcher.test.ts +42 -0
- package/src/tests/steer-task-tool.test.ts +37 -8
- package/src/tests/steering-transport.test.ts +18 -3
- package/src/tests/swarm-tool-result-gate.test.ts +399 -0
- package/src/tests/task-tools-ctx.test.ts +49 -17
- package/src/tests/task-tools-ownership.test.ts +41 -13
- package/src/tests/telemetry-init.test.ts +376 -2
- package/src/tests/tool-output-agent-id.test.ts +14 -2
- package/src/tests/workflow-http-v2.test.ts +116 -0
- package/src/tools/accept-steer.ts +45 -53
- package/src/tools/cancel-task.ts +31 -41
- package/src/tools/context-diff.ts +19 -63
- package/src/tools/context-history.ts +22 -51
- package/src/tools/create-channel.ts +24 -39
- package/src/tools/create-metric.ts +19 -82
- package/src/tools/create-page.ts +20 -81
- package/src/tools/credential-bindings/tool.ts +125 -246
- package/src/tools/db-query.ts +13 -24
- package/src/tools/delete-channel.ts +25 -71
- package/src/tools/delete-page.ts +27 -75
- package/src/tools/get-metrics.ts +29 -22
- package/src/tools/get-swarm.ts +44 -15
- package/src/tools/get-task-details.ts +150 -32
- package/src/tools/get-tasks.ts +63 -29
- package/src/tools/inject-learning.ts +8 -38
- package/src/tools/join-swarm.ts +45 -47
- package/src/tools/kv/kv-delete.ts +13 -37
- package/src/tools/kv/kv-get.ts +38 -29
- package/src/tools/kv/kv-incr.ts +28 -56
- package/src/tools/kv/kv-list.ts +40 -31
- package/src/tools/kv/kv-set.ts +34 -76
- package/src/tools/list-channels.ts +24 -17
- package/src/tools/list-services.ts +37 -45
- package/src/tools/manage-user.ts +53 -66
- package/src/tools/mcp-servers/mcp-server-create.ts +19 -55
- package/src/tools/mcp-servers/mcp-server-delete.ts +9 -39
- package/src/tools/mcp-servers/mcp-server-get.ts +9 -35
- package/src/tools/mcp-servers/mcp-server-install.ts +13 -60
- package/src/tools/mcp-servers/mcp-server-list.ts +28 -26
- package/src/tools/mcp-servers/mcp-server-uninstall.ts +10 -36
- package/src/tools/mcp-servers/mcp-server-update.ts +13 -57
- package/src/tools/memory-delete.ts +16 -46
- package/src/tools/memory-edit.ts +48 -49
- package/src/tools/memory-get.ts +40 -36
- package/src/tools/memory-rate.ts +14 -44
- package/src/tools/memory-search.ts +46 -52
- package/src/tools/my-agent-info.ts +39 -33
- package/src/tools/oauth-access-token.ts +9 -24
- package/src/tools/poll-task.ts +50 -77
- package/src/tools/post-message.ts +24 -38
- package/src/tools/prompt-templates/delete.ts +16 -48
- package/src/tools/prompt-templates/get.ts +45 -42
- package/src/tools/prompt-templates/list.ts +29 -44
- package/src/tools/prompt-templates/preview.ts +14 -40
- package/src/tools/prompt-templates/set.ts +30 -52
- package/src/tools/read-messages.ts +45 -54
- package/src/tools/register-agentmail-inbox.ts +28 -84
- package/src/tools/register-kapso-number.ts +20 -50
- package/src/tools/register-service.ts +36 -44
- package/src/tools/repos/get-repos.ts +29 -21
- package/src/tools/repos/update-repo.ts +30 -27
- package/src/tools/request-human-input.ts +10 -33
- package/src/tools/resolve-user.ts +59 -16
- package/src/tools/schedules/create-schedule.ts +58 -173
- package/src/tools/schedules/delete-schedule.ts +16 -56
- package/src/tools/schedules/list-schedules.ts +41 -67
- package/src/tools/schedules/patch-schedule.ts +57 -153
- package/src/tools/schedules/run-schedule-now.ts +18 -59
- package/src/tools/schedules/update-schedule.ts +57 -153
- package/src/tools/script-apis.ts +46 -28
- package/src/tools/script-common.ts +129 -34
- package/src/tools/script-connections/tool.ts +59 -154
- package/src/tools/script-query-types.ts +43 -3
- package/src/tools/script-run.ts +41 -2
- package/src/tools/script-runs.ts +69 -1
- package/src/tools/script-search.ts +24 -1
- package/src/tools/script-upsert.ts +18 -3
- package/src/tools/send-task.ts +32 -81
- package/src/tools/skills/skill-create.ts +14 -39
- package/src/tools/skills/skill-delete.ts +14 -42
- package/src/tools/skills/skill-get-file.ts +10 -43
- package/src/tools/skills/skill-get.ts +9 -32
- package/src/tools/skills/skill-install-remote.ts +16 -52
- package/src/tools/skills/skill-install.ts +14 -52
- package/src/tools/skills/skill-list.ts +24 -26
- package/src/tools/skills/skill-publish.ts +18 -54
- package/src/tools/skills/skill-search.ts +21 -18
- package/src/tools/skills/skill-sync-remote.ts +12 -24
- package/src/tools/skills/skill-uninstall.ts +10 -26
- package/src/tools/skills/skill-update.ts +24 -81
- package/src/tools/slack-delete.ts +8 -32
- package/src/tools/slack-download-file.ts +17 -57
- package/src/tools/slack-list-channels.ts +16 -43
- package/src/tools/slack-post.ts +11 -40
- package/src/tools/slack-read.ts +33 -103
- package/src/tools/slack-reply.ts +16 -53
- package/src/tools/slack-start-thread.ts +14 -55
- package/src/tools/slack-update.ts +8 -35
- package/src/tools/slack-upload-file.ts +22 -122
- package/src/tools/steer-task.ts +22 -35
- package/src/tools/store-progress.ts +11 -26
- package/src/tools/swarm-config/delete-config.ts +19 -57
- package/src/tools/swarm-config/get-config.ts +36 -44
- package/src/tools/swarm-config/list-config.ts +36 -44
- package/src/tools/swarm-config/set-config.ts +35 -73
- package/src/tools/swarm-x.ts +20 -26
- package/src/tools/task-action.ts +26 -46
- package/src/tools/task-tool-ctx.ts +5 -13
- package/src/tools/tracker/tracker-link-task.ts +8 -22
- package/src/tools/tracker/tracker-map-agent.ts +8 -22
- package/src/tools/tracker/tracker-status.ts +27 -19
- package/src/tools/tracker/tracker-sync-status.ts +8 -18
- package/src/tools/tracker/tracker-unlink.ts +4 -16
- package/src/tools/unregister-service.ts +19 -59
- package/src/tools/update-profile.ts +60 -100
- package/src/tools/update-service-status.ts +42 -67
- package/src/tools/utils.ts +189 -8
- package/src/tools/whatsapp-message.ts +19 -32
- package/src/tools/workflows/cancel-workflow-run.ts +4 -16
- package/src/tools/workflows/create-workflow.ts +9 -38
- package/src/tools/workflows/delete-workflow.ts +5 -17
- package/src/tools/workflows/get-workflow-run.ts +41 -31
- package/src/tools/workflows/get-workflow.ts +10 -21
- package/src/tools/workflows/list-workflow-runs.ts +107 -34
- package/src/tools/workflows/list-workflows.ts +5 -17
- package/src/tools/workflows/patch-workflow-node.ts +11 -39
- package/src/tools/workflows/patch-workflow.ts +11 -36
- package/src/tools/workflows/retry-workflow-run.ts +4 -16
- package/src/tools/workflows/trigger-workflow.ts +22 -58
- package/src/tools/workflows/update-workflow.ts +10 -42
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { loadModelsDevCache, type ModelsDevCache, type ModelsDevModel } from "./modelsdev-cache";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Live model catalog for the UI model picker.
|
|
5
|
+
*
|
|
6
|
+
* The pricing-refresh loop (`src/be/pricing-refresh.ts`) already fetches the
|
|
7
|
+
* full models.dev payload at boot and every 12h. This module keeps a slim
|
|
8
|
+
* in-memory projection of that payload — only the providers the four local
|
|
9
|
+
* harnesses' pickers can reach, and only the fields the picker reads — and
|
|
10
|
+
* serves it through `GET /api/models-catalog` (`src/http/models-catalog.ts`).
|
|
11
|
+
*
|
|
12
|
+
* Until the first successful fetch (or if models.dev is unreachable), the
|
|
13
|
+
* vendored `src/be/modelsdev-cache.json` snapshot is slimmed and served
|
|
14
|
+
* instead, so the endpoint is never empty. The UI additionally bundles the
|
|
15
|
+
* same snapshot as its own fallback while the request is in flight.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Mirrors `SNAPSHOT_ORDER` + `BEDROCK_SNAPSHOT_ID` in `apps/ui/src/lib/agent-runtime-models.ts`. */
|
|
19
|
+
export const CATALOG_PROVIDER_IDS = [
|
|
20
|
+
"openrouter",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"openai",
|
|
23
|
+
"amazon-bedrock",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export type CatalogProviderId = (typeof CATALOG_PROVIDER_IDS)[number];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Limited-availability models that are intentionally vendored even when
|
|
30
|
+
* models.dev does not list them yet, as "provider/model-id". Shared with
|
|
31
|
+
* `scripts/refresh-modelsdev-pricing.ts` (which pins them into the snapshot)
|
|
32
|
+
* — the live catalog re-merges them from the snapshot so they never drop out
|
|
33
|
+
* of the picker between models.dev listing gaps.
|
|
34
|
+
*/
|
|
35
|
+
export const PINNED_MODELSDEV_ENTRIES = [
|
|
36
|
+
"anthropic/claude-mythos-5",
|
|
37
|
+
"anthropic/claude-sonnet-5",
|
|
38
|
+
"amazon-bedrock/anthropic.claude-sonnet-5",
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
export interface CatalogReasoningOption {
|
|
42
|
+
type: string;
|
|
43
|
+
values?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Field names deliberately match raw models.dev (`reasoning_options`, not camelCase) so the UI can treat live and bundled-snapshot data identically. */
|
|
47
|
+
export interface CatalogModel {
|
|
48
|
+
id: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
cost?: { input?: number; output?: number };
|
|
51
|
+
limit?: { context?: number };
|
|
52
|
+
reasoning?: boolean;
|
|
53
|
+
reasoning_options?: CatalogReasoningOption[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CatalogProvider {
|
|
57
|
+
id: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
models: Record<string, CatalogModel>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ModelsCatalog = Partial<Record<CatalogProviderId, CatalogProvider>>;
|
|
63
|
+
|
|
64
|
+
export interface ModelsCatalogResult {
|
|
65
|
+
source: "live" | "snapshot";
|
|
66
|
+
/** Epoch ms of the last successful models.dev fetch; null for snapshot data. */
|
|
67
|
+
updatedAt: number | null;
|
|
68
|
+
providers: ModelsCatalog;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function slimModel(modelKey: string, model: ModelsDevModel): CatalogModel {
|
|
72
|
+
const reasoningOptions = Array.isArray(model.reasoning_options)
|
|
73
|
+
? model.reasoning_options
|
|
74
|
+
.filter((o) => typeof o.type === "string")
|
|
75
|
+
.map((o) => ({
|
|
76
|
+
type: o.type as string,
|
|
77
|
+
...(Array.isArray(o.values) ? { values: o.values } : {}),
|
|
78
|
+
}))
|
|
79
|
+
: [];
|
|
80
|
+
return {
|
|
81
|
+
id: model.id ?? modelKey,
|
|
82
|
+
...(model.name !== undefined ? { name: model.name } : {}),
|
|
83
|
+
...(model.cost !== undefined
|
|
84
|
+
? { cost: { input: model.cost.input, output: model.cost.output } }
|
|
85
|
+
: {}),
|
|
86
|
+
...(model.limit?.context !== undefined ? { limit: { context: model.limit.context } } : {}),
|
|
87
|
+
...(model.reasoning !== undefined ? { reasoning: model.reasoning } : {}),
|
|
88
|
+
...(reasoningOptions.length > 0 ? { reasoning_options: reasoningOptions } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Slim a full models.dev payload down to the picker-reachable providers/fields. */
|
|
93
|
+
export function buildModelsCatalog(cache: ModelsDevCache): ModelsCatalog {
|
|
94
|
+
const catalog: ModelsCatalog = {};
|
|
95
|
+
for (const providerId of CATALOG_PROVIDER_IDS) {
|
|
96
|
+
const provider = cache[providerId];
|
|
97
|
+
if (!provider?.models) continue;
|
|
98
|
+
const models: Record<string, CatalogModel> = {};
|
|
99
|
+
for (const [modelKey, model] of Object.entries(provider.models)) {
|
|
100
|
+
models[modelKey] = slimModel(modelKey, model);
|
|
101
|
+
}
|
|
102
|
+
catalog[providerId] = {
|
|
103
|
+
id: provider.id ?? providerId,
|
|
104
|
+
...(provider.name !== undefined ? { name: provider.name } : {}),
|
|
105
|
+
models,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return catalog;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let liveCatalog: ModelsCatalog | null = null;
|
|
112
|
+
let liveUpdatedAt: number | null = null;
|
|
113
|
+
/** Memoized slim of the vendored snapshot; `null` = load attempted and failed. */
|
|
114
|
+
let snapshotCatalog: ModelsCatalog | null | undefined;
|
|
115
|
+
|
|
116
|
+
function bundledSnapshotCatalog(): ModelsCatalog {
|
|
117
|
+
if (snapshotCatalog === undefined) {
|
|
118
|
+
const raw = loadModelsDevCache();
|
|
119
|
+
snapshotCatalog = raw ? buildModelsCatalog(raw) : null;
|
|
120
|
+
}
|
|
121
|
+
return snapshotCatalog ?? {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function mergePinnedEntries(catalog: ModelsCatalog): void {
|
|
125
|
+
const snapshot = bundledSnapshotCatalog();
|
|
126
|
+
for (const entryPath of PINNED_MODELSDEV_ENTRIES) {
|
|
127
|
+
const slashIndex = entryPath.indexOf("/");
|
|
128
|
+
const providerId = entryPath.slice(0, slashIndex) as CatalogProviderId;
|
|
129
|
+
const modelId = entryPath.slice(slashIndex + 1);
|
|
130
|
+
if (catalog[providerId]?.models[modelId]) continue;
|
|
131
|
+
const pinned = snapshot[providerId]?.models[modelId];
|
|
132
|
+
if (!pinned) continue;
|
|
133
|
+
catalog[providerId] ??= { id: providerId, models: {} };
|
|
134
|
+
catalog[providerId].models[modelId] = pinned;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Called by the pricing-refresh loop after every successful full models.dev fetch. */
|
|
139
|
+
export function updateLiveModelsCatalog(cache: ModelsDevCache, now = Date.now()): void {
|
|
140
|
+
const catalog = buildModelsCatalog(cache);
|
|
141
|
+
mergePinnedEntries(catalog);
|
|
142
|
+
liveCatalog = catalog;
|
|
143
|
+
liveUpdatedAt = now;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getModelsCatalog(): ModelsCatalogResult {
|
|
147
|
+
if (liveCatalog) {
|
|
148
|
+
return { source: "live", updatedAt: liveUpdatedAt, providers: liveCatalog };
|
|
149
|
+
}
|
|
150
|
+
return { source: "snapshot", updatedAt: null, providers: bundledSnapshotCatalog() };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function resetModelsCatalogForTests(): void {
|
|
154
|
+
liveCatalog = null;
|
|
155
|
+
liveUpdatedAt = null;
|
|
156
|
+
}
|
|
@@ -8,12 +8,23 @@ export interface ModelsDevCostBlock {
|
|
|
8
8
|
cache_write?: number;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface ModelsDevReasoningOption {
|
|
12
|
+
type?: string;
|
|
13
|
+
values?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export interface ModelsDevModel {
|
|
12
17
|
id?: string;
|
|
18
|
+
name?: string;
|
|
13
19
|
cost?: ModelsDevCostBlock;
|
|
20
|
+
limit?: { context?: number };
|
|
21
|
+
reasoning?: boolean;
|
|
22
|
+
reasoning_options?: ModelsDevReasoningOption[];
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export interface ModelsDevProvider {
|
|
26
|
+
id?: string;
|
|
27
|
+
name?: string;
|
|
17
28
|
models?: Record<string, ModelsDevModel>;
|
|
18
29
|
}
|
|
19
30
|
|
|
@@ -28,7 +39,9 @@ export const MODELSDEV_CACHE_PATH = path.join("src", "be", "modelsdev-cache.json
|
|
|
28
39
|
* This file is now fallback-only for pricing freshness: boot seeding uses it
|
|
29
40
|
* when the DB is empty or models.dev is unavailable, while
|
|
30
41
|
* `src/be/pricing-refresh.ts` owns live price updates. The UI model picker
|
|
31
|
-
*
|
|
42
|
+
* fetches the live catalog from `GET /api/models-catalog`
|
|
43
|
+
* (`src/be/models-catalog.ts`) and only falls back to its bundled copy of
|
|
44
|
+
* this snapshot when that request hasn't resolved.
|
|
32
45
|
*/
|
|
33
46
|
export function loadModelsDevCache(): ModelsDevCache | null {
|
|
34
47
|
const explicitPath = process.env.MODELSDEV_CACHE_PATH;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type InsertPricingRowInput,
|
|
7
7
|
insertPricingRow,
|
|
8
8
|
} from "./db";
|
|
9
|
+
import { updateLiveModelsCatalog } from "./models-catalog";
|
|
9
10
|
import type { ModelsDevCache } from "./modelsdev-cache";
|
|
10
11
|
import { buildModelsDevSeedRows, type PricingSeedRow } from "./seed-pricing";
|
|
11
12
|
|
|
@@ -148,6 +149,7 @@ export async function refreshPricingFromModelsDev(
|
|
|
148
149
|
|
|
149
150
|
const cache = (await response.json()) as ModelsDevCache;
|
|
150
151
|
const etag = response.headers.get("etag");
|
|
152
|
+
updateLiveModelsCatalog(cache, now);
|
|
151
153
|
const rows = buildModelsDevSeedRows(cache);
|
|
152
154
|
const { inserted, unchanged } = insertChangedPricingRows(rows, now);
|
|
153
155
|
const pruned = prunePricingHistory(2);
|
|
@@ -106,7 +106,7 @@ export interface SwarmSdk {
|
|
|
106
106
|
// --- workflows ---
|
|
107
107
|
workflow_list(args?: { enabled?: boolean; includeFull?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled" }): Promise<unknown>;
|
|
108
108
|
workflow_get(args: { id: string }): Promise<unknown>;
|
|
109
|
-
workflow_listRuns(args: { workflowId: string; status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled" }): Promise<unknown>;
|
|
109
|
+
workflow_listRuns(args: { workflowId: string; status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled"; limit?: number; offset?: number }): Promise<unknown>;
|
|
110
110
|
workflow_getRun(args: { id: string }): Promise<unknown>;
|
|
111
111
|
// --- prompt templates ---
|
|
112
112
|
prompt_list(args?: { eventType?: string; scope?: "global" | "agent" | "repo"; scopeId?: string; isDefault?: boolean }): Promise<unknown>;
|
|
@@ -295,6 +295,17 @@ export interface ScriptContext {
|
|
|
295
295
|
logger: ScriptLogger;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/**
|
|
299
|
+
* A swarm script's default export. \`args\` comes FIRST, \`ctx\` second — never swap them.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* import type { ScriptContext } from "swarm-sdk";
|
|
303
|
+
*
|
|
304
|
+
* export default async function (args: { name: string }, ctx: ScriptContext) {
|
|
305
|
+
* await ctx.logger.log(\`hello \${args.name}\`);
|
|
306
|
+
* return { ok: true };
|
|
307
|
+
* }
|
|
308
|
+
*/
|
|
298
309
|
// biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
|
|
299
310
|
export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
|
|
300
311
|
`;
|
|
@@ -878,15 +889,17 @@ void _scriptMain;
|
|
|
878
889
|
|
|
879
890
|
if (diagnostics.length === 0) return { ok: true };
|
|
880
891
|
|
|
881
|
-
const
|
|
892
|
+
const formatHost: ts.FormatDiagnosticsHost = {
|
|
882
893
|
getCanonicalFileName: (fileName) => fileName,
|
|
883
894
|
getCurrentDirectory: () => "/virtual",
|
|
884
895
|
getNewLine: () => "\n",
|
|
885
|
-
}
|
|
896
|
+
};
|
|
886
897
|
|
|
887
898
|
return {
|
|
888
899
|
ok: false,
|
|
889
|
-
diagnostics:
|
|
900
|
+
diagnostics: diagnostics.map((diagnostic) =>
|
|
901
|
+
ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost).trimEnd(),
|
|
902
|
+
),
|
|
890
903
|
structured: diagnostics.map(toStructured),
|
|
891
904
|
};
|
|
892
905
|
}
|
|
@@ -81,7 +81,7 @@ export default async function taskContextGathering(args: any, ctx: any) {
|
|
|
81
81
|
memories.sort((a: any, b: any) => b.compositeScore - a.compositeScore);
|
|
82
82
|
|
|
83
83
|
return {
|
|
84
|
-
task: slimTask(taskPayload
|
|
84
|
+
task: slimTask(taskPayload),
|
|
85
85
|
requestedBy: taskPayload?.requestedBy,
|
|
86
86
|
attachments: taskPayload?.attachments ?? [],
|
|
87
87
|
queriesRun: queries.length,
|
|
@@ -16,6 +16,18 @@ export function generateEnv(state: OnboardState): string {
|
|
|
16
16
|
lines.push(`MCP_BASE_URL=http://localhost:${port}`);
|
|
17
17
|
lines.push("APP_URL=https://app.agent-swarm.dev");
|
|
18
18
|
|
|
19
|
+
// Install path — read by src/telemetry.ts at boot to attribute the
|
|
20
|
+
// "which entry point produced this install" cohort. Boolean/enum only,
|
|
21
|
+
// never an identifier. Installs that never ran the wizard (hand-written
|
|
22
|
+
// docker-compose, manual `bun run start:http`) simply won't have this
|
|
23
|
+
// var set, and telemetry falls back to "manual".
|
|
24
|
+
lines.push(
|
|
25
|
+
`INSTALL_METHOD=${state.nonInteractive ? "onboard_noninteractive" : "onboard_interactive"}`,
|
|
26
|
+
);
|
|
27
|
+
if (state.presetId) {
|
|
28
|
+
lines.push(`INSTALL_PRESET=${state.presetId}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
// ── Authentication ──
|
|
20
32
|
lines.push("");
|
|
21
33
|
lines.push("# === Authentication ===");
|
package/src/e2b/env.ts
CHANGED
|
@@ -36,10 +36,6 @@ export const DEFAULT_E2B_FORWARD_KEYS = [
|
|
|
36
36
|
"MANAGED_ENVIRONMENT_ID",
|
|
37
37
|
"MANAGED_AGENT_MODEL",
|
|
38
38
|
"MANAGED_GITHUB_TOKEN",
|
|
39
|
-
"ARCHIL_MOUNT_TOKEN",
|
|
40
|
-
"ARCHIL_REGION",
|
|
41
|
-
"ARCHIL_SHARED_DISK_NAME",
|
|
42
|
-
"ARCHIL_PERSONAL_DISK_NAME",
|
|
43
39
|
"HARNESS_PROVIDER",
|
|
44
40
|
"MODEL_OVERRIDE",
|
|
45
41
|
"STARTUP_SCRIPT_STRICT",
|
package/src/hooks/hook.ts
CHANGED
|
@@ -105,31 +105,6 @@ export function shouldShowRegistrationNudge(opts: {
|
|
|
105
105
|
return true;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/**
|
|
109
|
-
* Check if a path is under the agent's own subdirectory on the shared disk.
|
|
110
|
-
* Shared disk categories: thoughts, memory, downloads, misc.
|
|
111
|
-
* Each agent can only write to /workspace/shared/{category}/{agentId}/
|
|
112
|
-
*/
|
|
113
|
-
function isOwnedSharedPath(path: string, agentId: string): boolean {
|
|
114
|
-
const sharedCategories = ["thoughts", "memory", "downloads", "misc"];
|
|
115
|
-
return sharedCategories.some((cat) => path.startsWith(`/workspace/shared/${cat}/${agentId}/`));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Build the shared disk write warning message for a given agent ID.
|
|
120
|
-
*/
|
|
121
|
-
function sharedDiskWriteWarning(agentId: string): string {
|
|
122
|
-
return (
|
|
123
|
-
`⚠️ This write will fail: You don't have write access to this directory.\n\n` +
|
|
124
|
-
`On shared workspaces, each agent can only write to their own directories:\n` +
|
|
125
|
-
`- /workspace/shared/thoughts/${agentId}/\n` +
|
|
126
|
-
`- /workspace/shared/memory/${agentId}/\n` +
|
|
127
|
-
`- /workspace/shared/downloads/${agentId}/\n` +
|
|
128
|
-
`- /workspace/shared/misc/${agentId}/\n\n` +
|
|
129
|
-
`You CAN read any file on the shared disk. For writes, use your own subdirectory.`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
108
|
/**
|
|
134
109
|
* Hook response for blocking actions
|
|
135
110
|
* See: https://code.claude.com/docs/en/hooks
|
|
@@ -1089,20 +1064,6 @@ export async function handleHook(): Promise<void> {
|
|
|
1089
1064
|
}
|
|
1090
1065
|
}
|
|
1091
1066
|
|
|
1092
|
-
// Shared disk write prevention (Archil only — skip in local dev)
|
|
1093
|
-
if (process.env.ARCHIL_MOUNT_TOKEN) {
|
|
1094
|
-
const preAgentId = process.env.AGENT_ID;
|
|
1095
|
-
if (preAgentId && (msg.tool_name === "Write" || msg.tool_name === "Edit")) {
|
|
1096
|
-
const targetPath =
|
|
1097
|
-
(msg.tool_input as { file_path?: string } | undefined)?.file_path ?? "";
|
|
1098
|
-
if (
|
|
1099
|
-
targetPath.startsWith("/workspace/shared/") &&
|
|
1100
|
-
!isOwnedSharedPath(targetPath, preAgentId)
|
|
1101
|
-
) {
|
|
1102
|
-
console.log(sharedDiskWriteWarning(preAgentId));
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
1067
|
break;
|
|
1107
1068
|
}
|
|
1108
1069
|
|
|
@@ -1128,34 +1089,6 @@ export async function handleHook(): Promise<void> {
|
|
|
1128
1089
|
});
|
|
1129
1090
|
}
|
|
1130
1091
|
|
|
1131
|
-
// Shared disk write failure detection (Archil only — safety net)
|
|
1132
|
-
if (process.env.ARCHIL_MOUNT_TOKEN) {
|
|
1133
|
-
const postAgentId = process.env.AGENT_ID;
|
|
1134
|
-
if (
|
|
1135
|
-
postAgentId &&
|
|
1136
|
-
(msg.tool_name === "Write" || msg.tool_name === "Edit" || msg.tool_name === "Bash")
|
|
1137
|
-
) {
|
|
1138
|
-
const toolResponse = msg.tool_response as
|
|
1139
|
-
| { output?: string; stderr?: string }
|
|
1140
|
-
| undefined;
|
|
1141
|
-
const responseStr = JSON.stringify(toolResponse ?? "");
|
|
1142
|
-
if (responseStr.includes("Read-only file system")) {
|
|
1143
|
-
const postTargetPath =
|
|
1144
|
-
(msg.tool_input as { file_path?: string; command?: string } | undefined)?.file_path ??
|
|
1145
|
-
"";
|
|
1146
|
-
if (
|
|
1147
|
-
postTargetPath.startsWith("/workspace/shared/") &&
|
|
1148
|
-
!isOwnedSharedPath(postTargetPath, postAgentId)
|
|
1149
|
-
) {
|
|
1150
|
-
console.log(sharedDiskWriteWarning(postAgentId));
|
|
1151
|
-
} else if (!postTargetPath) {
|
|
1152
|
-
// Bash tool — no file_path, just warn generically
|
|
1153
|
-
console.log(sharedDiskWriteWarning(postAgentId));
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
1092
|
if (agentInfo) {
|
|
1160
1093
|
// Sync workspace file edits back to DB
|
|
1161
1094
|
const toolName = msg.tool_name;
|
package/src/http/all-routes.ts
CHANGED
package/src/http/index.ts
CHANGED
|
@@ -72,6 +72,7 @@ import { handleMcpServers } from "./mcp-servers";
|
|
|
72
72
|
import { closeIdleMcpUserTransports, handleMcpUser } from "./mcp-user";
|
|
73
73
|
import { handleMemory, startMemoryGc, stopMemoryGc } from "./memory";
|
|
74
74
|
import { handleMetrics } from "./metrics";
|
|
75
|
+
import { handleModelsCatalog } from "./models-catalog";
|
|
75
76
|
import { handleOAuthCallback, startOAuthPendingGc, stopOAuthPendingGc } from "./oauth-callback";
|
|
76
77
|
import { handleGenericOAuth } from "./oauth-generic";
|
|
77
78
|
import { handleOAuthLocks } from "./oauth-locks";
|
|
@@ -329,6 +330,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
329
330
|
() => handlePromptTemplates(req, res, pathSegments, queryParams),
|
|
330
331
|
() => handleDbQuery(req, res, pathSegments, queryParams),
|
|
331
332
|
() => handleMetrics(req, res, pathSegments, queryParams, myAgentId),
|
|
333
|
+
() => handleModelsCatalog(req, res, pathSegments, queryParams),
|
|
332
334
|
() => handleRepos(req, res, pathSegments, queryParams),
|
|
333
335
|
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
334
336
|
() => handleScriptConnections(req, res, pathSegments, queryParams, myAgentId),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Live model catalog for the UI model picker.
|
|
2
|
+
//
|
|
3
|
+
// Serves the slim in-memory projection of the models.dev payload maintained
|
|
4
|
+
// by the pricing-refresh loop (`src/be/models-catalog.ts`). Read-only: the
|
|
5
|
+
// catalog is refreshed server-side (boot + every 12h); the UI polls this
|
|
6
|
+
// endpoint instead of relying on the build-time snapshot bundled into the SPA.
|
|
7
|
+
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { getModelsCatalog } from "../be/models-catalog";
|
|
11
|
+
import { route } from "./route-def";
|
|
12
|
+
import { json } from "./utils";
|
|
13
|
+
|
|
14
|
+
const CatalogModelSchema = z.object({
|
|
15
|
+
id: z.string(),
|
|
16
|
+
name: z.string().optional(),
|
|
17
|
+
cost: z.object({ input: z.number().optional(), output: z.number().optional() }).optional(),
|
|
18
|
+
limit: z.object({ context: z.number().optional() }).optional(),
|
|
19
|
+
reasoning: z.boolean().optional(),
|
|
20
|
+
reasoning_options: z
|
|
21
|
+
.array(z.object({ type: z.string(), values: z.array(z.string()).optional() }))
|
|
22
|
+
.optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const CatalogProviderSchema = z.object({
|
|
26
|
+
id: z.string(),
|
|
27
|
+
name: z.string().optional(),
|
|
28
|
+
models: z.record(z.string(), CatalogModelSchema),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const getCatalog = route({
|
|
32
|
+
method: "get",
|
|
33
|
+
path: "/api/models-catalog",
|
|
34
|
+
pattern: ["api", "models-catalog"],
|
|
35
|
+
summary: "Get the live model catalog for the picker-reachable providers",
|
|
36
|
+
description:
|
|
37
|
+
"Slim projection of the models.dev payload (openrouter / anthropic / openai / amazon-bedrock only), refreshed server-side at boot and every 12h by the pricing-refresh loop. `source` is 'snapshot' with `updatedAt: null` until the first successful fetch (or when models.dev is unreachable), in which case the vendored snapshot is served instead.",
|
|
38
|
+
tags: ["Pricing"],
|
|
39
|
+
responses: {
|
|
40
|
+
200: {
|
|
41
|
+
description: "Model catalog",
|
|
42
|
+
schema: z.object({
|
|
43
|
+
source: z.enum(["live", "snapshot"]),
|
|
44
|
+
updatedAt: z.number().nullable(),
|
|
45
|
+
providers: z.record(z.string(), CatalogProviderSchema),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export async function handleModelsCatalog(
|
|
52
|
+
req: IncomingMessage,
|
|
53
|
+
res: ServerResponse,
|
|
54
|
+
pathSegments: string[],
|
|
55
|
+
queryParams: URLSearchParams,
|
|
56
|
+
): Promise<boolean> {
|
|
57
|
+
if (getCatalog.match(req.method, pathSegments)) {
|
|
58
|
+
const parsed = await getCatalog.parse(req, res, pathSegments, queryParams);
|
|
59
|
+
if (!parsed) return true;
|
|
60
|
+
json(res, getModelsCatalog());
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
}
|
package/src/http/workflows.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getWorkflowVersion,
|
|
12
12
|
getWorkflowVersions,
|
|
13
13
|
listWorkflowRuns,
|
|
14
|
+
listWorkflowRunsPage,
|
|
14
15
|
listWorkflows,
|
|
15
16
|
updateWorkflow,
|
|
16
17
|
withFavoriteFlags,
|
|
@@ -209,6 +210,8 @@ const listWorkflowRunsRoute = route({
|
|
|
209
210
|
params: z.object({ id: z.string() }),
|
|
210
211
|
query: z.object({
|
|
211
212
|
status: WorkflowRunStatusSchema.optional(),
|
|
213
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
214
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
212
215
|
}),
|
|
213
216
|
responses: {
|
|
214
217
|
200: { description: "Workflow run list" },
|
|
@@ -754,11 +757,20 @@ export async function handleWorkflows(
|
|
|
754
757
|
if (listWorkflowRunsRoute.match(req.method, pathSegments)) {
|
|
755
758
|
const parsed = await listWorkflowRunsRoute.parse(req, res, pathSegments, queryParams);
|
|
756
759
|
if (!parsed) return true;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (
|
|
760
|
-
|
|
760
|
+
const paginationRequested =
|
|
761
|
+
parsed.query?.limit !== undefined || parsed.query?.offset !== undefined;
|
|
762
|
+
if (paginationRequested) {
|
|
763
|
+
const page = listWorkflowRunsPage(parsed.params.id, {
|
|
764
|
+
status: parsed.query?.status,
|
|
765
|
+
limit: parsed.query?.limit ?? 20,
|
|
766
|
+
offset: parsed.query?.offset ?? 0,
|
|
767
|
+
});
|
|
768
|
+
json(res, page);
|
|
769
|
+
return true;
|
|
761
770
|
}
|
|
771
|
+
// Preserve the pre-pagination response for the UI when limit/offset are
|
|
772
|
+
// omitted: a bare array containing every matching run.
|
|
773
|
+
const runs = listWorkflowRuns(parsed.params.id, { status: parsed.query?.status });
|
|
762
774
|
json(res, runs);
|
|
763
775
|
return true;
|
|
764
776
|
}
|
|
@@ -116,15 +116,16 @@ export const getBasePrompt = async (args: BasePromptArgs): Promise<string> => {
|
|
|
116
116
|
if (!hasMcp) {
|
|
117
117
|
// If no MCP, role cannot be lead
|
|
118
118
|
compositeEventType = "system.session.worker.remote";
|
|
119
|
+
} else if (args.provider === "pi") {
|
|
120
|
+
// Pi has no context-mode MCP wiring yet, so it uses composites that omit
|
|
121
|
+
// the context-mode tool list while still including the shared script
|
|
122
|
+
// rubric (and its authoring contract) plus seed-script guidance. Checked
|
|
123
|
+
// before the role branch so pi *leads* aren't told about `ctx_*` tools that
|
|
124
|
+
// don't exist for them either. All other local providers (claude, codex,
|
|
125
|
+
// opencode) keep the full context block via the standard composites.
|
|
126
|
+
compositeEventType = role === "lead" ? "system.session.lead.pi" : "system.session.worker.pi";
|
|
119
127
|
} else if (role === "lead") {
|
|
120
128
|
compositeEventType = "system.session.lead";
|
|
121
|
-
} else if (args.provider === "pi") {
|
|
122
|
-
// Pi has no context-mode MCP wiring yet, so it uses a worker composite that
|
|
123
|
-
// omits the context-mode tool list while still including the shared script
|
|
124
|
-
// rubric and seed-script guidance. All other local providers (claude,
|
|
125
|
-
// codex, opencode) keep the full context block via the standard worker
|
|
126
|
-
// composite.
|
|
127
|
-
compositeEventType = "system.session.worker.pi";
|
|
128
129
|
} else {
|
|
129
130
|
compositeEventType = "system.session.worker";
|
|
130
131
|
}
|