@desplega.ai/agent-swarm 1.129.0 → 1.131.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 -2
- package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
- package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
- package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
- package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
- package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
- package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
- package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
- package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
- package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
- package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
- package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
- package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
- package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
- package/dist/cli-3fj60etf.js +153 -0
- package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
- package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
- package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
- package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
- package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
- package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
- package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
- package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
- package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
- package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
- package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
- package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
- package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
- package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
- package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
- package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
- package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
- package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
- package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
- package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
- package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
- package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
- package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
- package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
- package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
- package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
- package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
- package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
- package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
- package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
- package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
- package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
- package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
- package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
- package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
- package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
- package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
- package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
- package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
- package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
- package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
- package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
- package/dist/cli-zsspvvm2.js +490 -0
- package/dist/cli.js +27 -25
- package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
- package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
- package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
- package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
- package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
- package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
- package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
- package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
- package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
- package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
- package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
- package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
- package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
- package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
- package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
- package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
- package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
- package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
- package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
- package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
- package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
- package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
- package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
- package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
- package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
- package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
- package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
- package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
- package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
- package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
- package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
- package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
- package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
- package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
- package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
- package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
- package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
- package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
- package/openapi.json +34039 -11036
- package/package.json +8 -6
- package/src/apps/definition.ts +368 -26
- package/src/apps/row-store.ts +66 -10
- package/src/apps/schema-migrate.ts +95 -1
- package/src/apps/store.ts +13 -4
- package/src/apps/sync-run-as.ts +18 -0
- package/src/apps/sync.ts +934 -0
- package/src/apps/version.ts +16 -2
- package/src/assets/key.ts +8 -1
- package/src/be/asset-key-audit.ts +3 -1
- package/src/be/db-queries/mcp-oauth.ts +297 -24
- package/src/be/db.ts +42 -1
- package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
- package/src/be/scripts/db.ts +5 -2
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
- package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
- package/src/be/seed-scripts/index.ts +18 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/http/active-sessions.ts +26 -18
- package/src/http/agents.ts +94 -27
- package/src/http/api-keys.ts +91 -17
- package/src/http/approval-requests.ts +87 -10
- package/src/http/apps.ts +361 -53
- package/src/http/assets.ts +165 -31
- package/src/http/budgets.ts +7 -5
- package/src/http/codex-oauth-keep-warm.ts +21 -3
- package/src/http/config.ts +46 -16
- package/src/http/context.ts +33 -6
- package/src/http/ecosystem.ts +20 -6
- package/src/http/events.ts +24 -9
- package/src/http/favorites.ts +20 -6
- package/src/http/fs.ts +60 -17
- package/src/http/heartbeat.ts +18 -4
- package/src/http/inbox-state.ts +12 -6
- package/src/http/integrations.ts +25 -5
- package/src/http/kv.ts +36 -15
- package/src/http/mcp-bridge.ts +5 -1
- package/src/http/mcp-oauth.ts +332 -17
- package/src/http/mcp-servers.ts +56 -18
- package/src/http/memory.ts +282 -29
- package/src/http/metrics.ts +80 -17
- package/src/http/oauth-callback.ts +10 -4
- package/src/http/oauth-generic.ts +5 -1
- package/src/http/oauth-locks.ts +6 -3
- package/src/http/openapi.ts +57 -9
- package/src/http/page-proxy.ts +25 -5
- package/src/http/pages-public.ts +37 -20
- package/src/http/pages.ts +93 -28
- package/src/http/poll.ts +89 -5
- package/src/http/pricing.ts +9 -6
- package/src/http/prompt-templates.ts +85 -21
- package/src/http/route-def.ts +67 -4
- package/src/http/schedules.ts +63 -14
- package/src/http/script-connection-proxy.ts +30 -4
- package/src/http/script-connections.ts +393 -43
- package/src/http/script-runs.ts +73 -19
- package/src/http/scripts.ts +257 -39
- package/src/http/session-data.ts +87 -14
- package/src/http/sessions.ts +80 -5
- package/src/http/skills.ts +115 -35
- package/src/http/stats.ts +94 -13
- package/src/http/task-templates.ts +6 -4
- package/src/http/tasks.ts +230 -61
- package/src/http/trackers/jira.ts +90 -30
- package/src/http/trackers/linear.ts +46 -12
- package/src/http/users.ts +162 -25
- package/src/http/utils.ts +12 -2
- package/src/http/webhooks.ts +30 -16
- package/src/http/workflow-events.ts +18 -5
- package/src/http/workflows.ts +128 -39
- package/src/http/x.ts +37 -6
- package/src/oauth/ensure-mcp-token.ts +16 -1
- package/src/oauth/mcp-wrapper.ts +281 -26
- package/src/providers/pi-mono-adapter.ts +1 -1
- package/src/scripts-runtime/credential-broker/README.md +1 -1
- package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/stdlib/fetch.ts +6 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tests/apps-rbac.test.ts +110 -0
- package/src/tests/apps-spike2.test.ts +4 -1
- package/src/tests/apps-sync-engine.test.ts +2540 -0
- package/src/tests/apps-sync.test.ts +1627 -0
- package/src/tests/asset-key-api.test.ts +48 -2
- package/src/tests/asset-key-invariants.test.ts +92 -0
- package/src/tests/asset-key-migration.test.ts +174 -8
- package/src/tests/asset-key-normalization.test.ts +2 -0
- package/src/tests/credential-broker.test.ts +27 -2
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
- package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
- package/src/tests/mcp-oauth-queries.test.ts +153 -0
- package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
- package/src/tests/memory-http-recall-gating.test.ts +2 -0
- package/src/tests/openapi-response-contract.test.ts +138 -0
- package/src/tests/rbac-admission-e2e.test.ts +68 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/scripts-runtime-fetch.test.ts +121 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/swarm-tool-result-gate.test.ts +32 -0
- package/src/tests/task-working-dir.test.ts +8 -4
- package/src/tests/whoami-route.test.ts +127 -0
- package/src/tests/workflow-engine-v2.test.ts +80 -7
- package/src/tests/workflow-swarm-script.test.ts +104 -0
- package/src/tools/app-get.ts +10 -2
- package/src/tools/app-rollback.ts +1 -0
- package/src/tools/app-sync.ts +112 -0
- package/src/tools/script-common.ts +20 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +11 -0
- package/src/tools/workflows/create-workflow.ts +4 -1
- package/src/types.ts +1394 -1206
- package/src/utils/template.ts +8 -1
- package/src/utils/zod-openapi.ts +11 -0
- package/src/workflows/engine.ts +27 -4
- package/src/workflows/executors/swarm-script.ts +28 -0
- package/src/workflows/retry-poller.ts +19 -2
- package/templates/skills/apps/content.md +94 -5
- package/templates/skills/asset-namespaces/content.md +2 -2
- package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
- package/templates/skills/scheduled-task-resilience/config.json +14 -0
- package/templates/skills/scheduled-task-resilience/content.md +97 -0
- package/dist/cli-7wmybrt5.js +0 -115
- package/dist/cli-d91pvmq1.js +0 -14
package/src/http/apps.ts
CHANGED
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
type AppQueryDef,
|
|
7
7
|
type AppValidationIssue,
|
|
8
8
|
applyAppDefinitionPatch,
|
|
9
|
+
ColumnKindSchema,
|
|
9
10
|
isIso8601Date,
|
|
10
11
|
type ModelDef,
|
|
11
12
|
parseAppDefinition,
|
|
13
|
+
SORTABLE_SYSTEM_DATE_COLUMNS,
|
|
12
14
|
SYSTEM_COLUMN_KINDS,
|
|
13
15
|
} from "../apps/definition";
|
|
14
16
|
import {
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
listApps,
|
|
42
44
|
updateApp,
|
|
43
45
|
} from "../apps/store";
|
|
46
|
+
import { collectAppSyncStatus, runAppSync } from "../apps/sync";
|
|
44
47
|
import {
|
|
45
48
|
getAppUserConfigValues,
|
|
46
49
|
isReservedUserConfigKey,
|
|
@@ -56,6 +59,7 @@ import {
|
|
|
56
59
|
rollbackApp,
|
|
57
60
|
snapshotApp,
|
|
58
61
|
} from "../apps/version";
|
|
62
|
+
import { normalizeAssetKey } from "../assets/key";
|
|
59
63
|
import { getAgentById, getAppVersion, getAppVersions, getLeadAgent } from "../be/db";
|
|
60
64
|
import { getScriptById } from "../be/scripts/db";
|
|
61
65
|
import { getSavedScriptOwnerAgentId, runSavedScriptAsAgent } from "../be/scripts/run-saved";
|
|
@@ -63,6 +67,7 @@ import { resolveTemplate } from "../prompts/resolver";
|
|
|
63
67
|
import type { RbacPrincipal, RbacResource } from "../rbac";
|
|
64
68
|
import { can } from "../rbac";
|
|
65
69
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
70
|
+
import { AgentTaskStatusSchema } from "../types";
|
|
66
71
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
67
72
|
import { scrubObject } from "../utils/secret-scrubber";
|
|
68
73
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
@@ -88,8 +93,142 @@ const rowParamsSchema = z.object({
|
|
|
88
93
|
rowId: z.string().min(1),
|
|
89
94
|
});
|
|
90
95
|
const valuesSchema = z.record(z.string(), z.unknown());
|
|
96
|
+
|
|
97
|
+
const AppValidationIssueSchema = z.object({ path: z.string(), message: z.string() });
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Any JSON value. Used for the genuinely free-form-but-ALWAYS-PRESENT payloads
|
|
101
|
+
* below. `z.unknown()` would be wrong for those: it accepts `undefined`, so the
|
|
102
|
+
* generator drops the property from the spec's `required` list even though
|
|
103
|
+
* every write path emits it (same reasoning as `argsJsonSchemaValueSchema` in
|
|
104
|
+
* src/http/scripts.ts).
|
|
105
|
+
*/
|
|
106
|
+
const JsonValueSchema = z.union([
|
|
107
|
+
z.record(z.string(), z.unknown()),
|
|
108
|
+
z.array(z.unknown()),
|
|
109
|
+
z.string(),
|
|
110
|
+
z.number(),
|
|
111
|
+
z.boolean(),
|
|
112
|
+
z.null(),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Mirrors `AppRecord` (src/apps/store.ts). `definition` is free-form JSON: it's
|
|
117
|
+
* either a validated `AppDefinition & { schemaVersion }` or — when
|
|
118
|
+
* `definitionError` is set — the raw, possibly malformed, stored value (which
|
|
119
|
+
* `decodeApp` leaves as the un-parsed JSON *string* when the column itself is
|
|
120
|
+
* corrupt).
|
|
121
|
+
*/
|
|
122
|
+
const AppRecordSchema = z.object({
|
|
123
|
+
id: z.string(),
|
|
124
|
+
name: z.string(),
|
|
125
|
+
description: z.string().optional(),
|
|
126
|
+
definition: JsonValueSchema,
|
|
127
|
+
definitionError: z.array(AppValidationIssueSchema).optional(),
|
|
128
|
+
createdAt: z.string(),
|
|
129
|
+
updatedAt: z.string(),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Mirrors what `listApps()` (src/apps/store.ts) actually builds. Its declared
|
|
134
|
+
* return type is `Omit<AppRecord, "definition">`, but the row mapper also never
|
|
135
|
+
* emits `definitionError` — so this is deliberately not derived from
|
|
136
|
+
* `AppRecordSchema`.
|
|
137
|
+
*/
|
|
138
|
+
const AppSummarySchema = z.object({
|
|
139
|
+
id: z.string(),
|
|
140
|
+
name: z.string(),
|
|
141
|
+
description: z.string().optional(),
|
|
142
|
+
createdAt: z.string(),
|
|
143
|
+
updatedAt: z.string(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Mirrors `AppVersion` (src/be/db.ts). `snapshot` is a free-form JSON blob —
|
|
148
|
+
* normally an `AppSnapshot`, but `rollbackSnapshot()` proves historical rows can
|
|
149
|
+
* hold anything. Unlike `AppRecord.definition` it stays `z.unknown()` rather
|
|
150
|
+
* than `JsonValueSchema`: `AppVersion.snapshot` is declared `unknown` in db.ts,
|
|
151
|
+
* so narrowing it would need an unsafe cast at both call sites. The cost is
|
|
152
|
+
* that the generated spec lists `snapshot` as optional even though
|
|
153
|
+
* `rowToAppVersion` always emits it.
|
|
154
|
+
*/
|
|
155
|
+
const AppVersionSchema = z.object({
|
|
156
|
+
id: z.string(),
|
|
157
|
+
appId: z.string(),
|
|
158
|
+
version: z.number(),
|
|
159
|
+
snapshot: z.unknown(),
|
|
160
|
+
changedByAgentId: z.string().optional(),
|
|
161
|
+
createdAt: z.string(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** Mirrors `AppRow` (src/apps/row-store.ts): fixed system columns plus arbitrary model-defined columns. */
|
|
165
|
+
const AppRowSchema = z.looseObject({
|
|
166
|
+
id: z.string(),
|
|
167
|
+
createdAt: z.string(),
|
|
168
|
+
updatedAt: z.string(),
|
|
169
|
+
createdBy: z.string().optional(),
|
|
170
|
+
updatedBy: z.string().optional(),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/** Mirrors `SyncPassResult` (src/apps/sync.ts): one (model x source) sync pass outcome. */
|
|
174
|
+
const SyncPassResultSchema = z.object({
|
|
175
|
+
model: z.string(),
|
|
176
|
+
source: z.string(),
|
|
177
|
+
connector: z.enum(["script", "swarm-tasks"]),
|
|
178
|
+
pulled: z.number(),
|
|
179
|
+
created: z.number(),
|
|
180
|
+
updated: z.number(),
|
|
181
|
+
refreshed: z.number(),
|
|
182
|
+
unchanged: z.number(),
|
|
183
|
+
markedStale: z.number(),
|
|
184
|
+
staleSweepSkipped: z.boolean().optional(),
|
|
185
|
+
warnings: z.array(z.string()),
|
|
186
|
+
durationMs: z.number(),
|
|
187
|
+
invokedBy: z.string().optional(),
|
|
188
|
+
error: z.string().optional(),
|
|
189
|
+
skipped: z.literal(true).optional(),
|
|
190
|
+
alreadyRunning: z.literal(true).optional(),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/** Mirrors `AppSyncStatus` (src/apps/sync.ts): per-(model:source) pass freshness. */
|
|
194
|
+
const AppSyncStatusSchema = z.object({
|
|
195
|
+
lastStartedAt: z.string(),
|
|
196
|
+
lastFinishedAt: z.string(),
|
|
197
|
+
ok: z.boolean(),
|
|
198
|
+
created: z.number(),
|
|
199
|
+
updated: z.number(),
|
|
200
|
+
refreshed: z.number(),
|
|
201
|
+
markedStale: z.number(),
|
|
202
|
+
error: z.string().optional(),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/** Mirrors `UserConfigValues` (src/apps/user-config.ts). */
|
|
206
|
+
const UserConfigValuesSchema = z.record(
|
|
207
|
+
z.string(),
|
|
208
|
+
z.union([z.string(), z.number(), z.boolean(), z.null()]),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Wire mirror of one `userConfig` field declaration (`UserConfigFieldSchema`,
|
|
213
|
+
* src/apps/definition.ts). The definition module's own `UserConfigSchema` is
|
|
214
|
+
* deliberately NOT reused here: its `required: z.never().optional()` guard has
|
|
215
|
+
* no OpenAPI representation and makes the spec generator throw
|
|
216
|
+
* (`Unknown zod object type … {type: "never"}`), which breaks both
|
|
217
|
+
* `bun run docs:openapi` and `GET /openapi.json`. `required` is unobservable
|
|
218
|
+
* on the wire anyway — the key can only ever be absent.
|
|
219
|
+
*/
|
|
220
|
+
const AppUserConfigFieldSchema = z.object({
|
|
221
|
+
kind: ColumnKindSchema,
|
|
222
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
223
|
+
enum: z.array(z.string()).optional(),
|
|
224
|
+
label: z.string().optional(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/** Mirrors an app definition's `userConfig` declaration map. */
|
|
228
|
+
const AppUserConfigSchema = z.record(AppNameSchema, AppUserConfigFieldSchema);
|
|
229
|
+
|
|
91
230
|
const appWriteResponseSchema = z.object({
|
|
92
|
-
app:
|
|
231
|
+
app: AppRecordSchema,
|
|
93
232
|
migration: AppMigrationReportSchema,
|
|
94
233
|
});
|
|
95
234
|
const userConfigBodySchema = z.object({ values: z.record(z.string(), z.unknown()) }).strict();
|
|
@@ -104,7 +243,10 @@ const getUserConfigRoute = route({
|
|
|
104
243
|
tags: ["Apps"],
|
|
105
244
|
params: appParamsSchema,
|
|
106
245
|
responses: {
|
|
107
|
-
200: {
|
|
246
|
+
200: {
|
|
247
|
+
description: "Merged user configuration",
|
|
248
|
+
schema: z.object({ values: UserConfigValuesSchema, schema: AppUserConfigSchema }),
|
|
249
|
+
},
|
|
108
250
|
403: { description: "Permission denied" },
|
|
109
251
|
404: { description: "App not found" },
|
|
110
252
|
409: { description: "App definition needs repair" },
|
|
@@ -123,7 +265,10 @@ const putUserConfigRoute = route({
|
|
|
123
265
|
params: appParamsSchema,
|
|
124
266
|
body: userConfigBodySchema,
|
|
125
267
|
responses: {
|
|
126
|
-
200: {
|
|
268
|
+
200: {
|
|
269
|
+
description: "Stored user configuration",
|
|
270
|
+
schema: z.object({ values: UserConfigValuesSchema, schema: AppUserConfigSchema }),
|
|
271
|
+
},
|
|
127
272
|
400: { description: "Invalid user configuration" },
|
|
128
273
|
403: { description: "Permission denied" },
|
|
129
274
|
413: { description: "Request exceeds 64 KB or serialized values exceed 16 KB" },
|
|
@@ -139,7 +284,12 @@ const listAppsRoute = route({
|
|
|
139
284
|
pattern: ["api", "apps"],
|
|
140
285
|
summary: "List apps",
|
|
141
286
|
tags: ["Apps"],
|
|
142
|
-
responses: {
|
|
287
|
+
responses: {
|
|
288
|
+
200: {
|
|
289
|
+
description: "App summaries without definitions",
|
|
290
|
+
schema: z.object({ apps: z.array(AppSummarySchema) }),
|
|
291
|
+
},
|
|
292
|
+
},
|
|
143
293
|
// App summaries remain list-level until a future policy can filter them per app.
|
|
144
294
|
rbac: { ungated: "app summaries are list-level; per-app filtering is future work" },
|
|
145
295
|
});
|
|
@@ -157,7 +307,7 @@ const createAppRoute = route({
|
|
|
157
307
|
forceElementBreak: ForceElementBreakSchema.optional(),
|
|
158
308
|
}),
|
|
159
309
|
responses: {
|
|
160
|
-
201: { description: "Created app" },
|
|
310
|
+
201: { description: "Created app", schema: z.object({ app: AppRecordSchema }) },
|
|
161
311
|
400: { description: "Invalid app definition" },
|
|
162
312
|
403: { description: "Permission denied" },
|
|
163
313
|
},
|
|
@@ -172,7 +322,10 @@ const listAppVersionsRoute = route({
|
|
|
172
322
|
tags: ["Apps"],
|
|
173
323
|
params: appParamsSchema,
|
|
174
324
|
responses: {
|
|
175
|
-
200: {
|
|
325
|
+
200: {
|
|
326
|
+
description: "App definition versions",
|
|
327
|
+
schema: z.object({ versions: z.array(AppVersionSchema) }),
|
|
328
|
+
},
|
|
176
329
|
404: { description: "App not found" },
|
|
177
330
|
},
|
|
178
331
|
rbac: { permission: "app.manage" },
|
|
@@ -186,7 +339,7 @@ const getAppVersionRoute = route({
|
|
|
186
339
|
tags: ["Apps"],
|
|
187
340
|
params: appVersionParamsSchema,
|
|
188
341
|
responses: {
|
|
189
|
-
200: { description: "App definition version" },
|
|
342
|
+
200: { description: "App definition version", schema: z.object({ version: AppVersionSchema }) },
|
|
190
343
|
404: { description: "App or version not found" },
|
|
191
344
|
},
|
|
192
345
|
rbac: { permission: "app.manage" },
|
|
@@ -223,7 +376,14 @@ const getAppRoute = route({
|
|
|
223
376
|
tags: ["Apps"],
|
|
224
377
|
params: appParamsSchema,
|
|
225
378
|
responses: {
|
|
226
|
-
200: {
|
|
379
|
+
200: {
|
|
380
|
+
description: "App including its definition",
|
|
381
|
+
schema: z.object({
|
|
382
|
+
app: AppRecordSchema,
|
|
383
|
+
// Present only once at least one declared source has completed a pass.
|
|
384
|
+
syncStatus: z.record(z.string(), AppSyncStatusSchema).optional(),
|
|
385
|
+
}),
|
|
386
|
+
},
|
|
227
387
|
403: { description: "Permission denied" },
|
|
228
388
|
404: { description: "App not found" },
|
|
229
389
|
},
|
|
@@ -286,7 +446,7 @@ const deleteAppRoute = route({
|
|
|
286
446
|
tags: ["Apps"],
|
|
287
447
|
params: appParamsSchema,
|
|
288
448
|
responses: {
|
|
289
|
-
200: { description: "App deleted" },
|
|
449
|
+
200: { description: "App deleted", schema: z.object({ ok: z.literal(true) }) },
|
|
290
450
|
403: { description: "Permission denied" },
|
|
291
451
|
404: { description: "App not found" },
|
|
292
452
|
},
|
|
@@ -302,7 +462,7 @@ const createRowRoute = route({
|
|
|
302
462
|
params: modelParamsSchema,
|
|
303
463
|
body: z.object({ values: valuesSchema }),
|
|
304
464
|
responses: {
|
|
305
|
-
201: { description: "Created row" },
|
|
465
|
+
201: { description: "Created row", schema: z.object({ row: AppRowSchema }) },
|
|
306
466
|
400: { description: "Invalid row values" },
|
|
307
467
|
403: { description: "Permission denied" },
|
|
308
468
|
404: { description: "App or model not found" },
|
|
@@ -319,7 +479,7 @@ const bulkCreateRowsRoute = route({
|
|
|
319
479
|
params: modelParamsSchema,
|
|
320
480
|
body: z.object({ rows: z.array(z.object({ values: valuesSchema })).max(500) }),
|
|
321
481
|
responses: {
|
|
322
|
-
200: { description: "Created rows" },
|
|
482
|
+
200: { description: "Created rows", schema: z.object({ rows: z.array(AppRowSchema) }) },
|
|
323
483
|
400: { description: "Invalid row values" },
|
|
324
484
|
403: { description: "Permission denied" },
|
|
325
485
|
404: { description: "App or model not found" },
|
|
@@ -339,7 +499,10 @@ const listRowsRoute = route({
|
|
|
339
499
|
limit: z.coerce.number().int().positive().max(1000).optional(),
|
|
340
500
|
}),
|
|
341
501
|
responses: {
|
|
342
|
-
200: {
|
|
502
|
+
200: {
|
|
503
|
+
description: "Filtered app model rows",
|
|
504
|
+
schema: z.object({ rows: z.array(AppRowSchema), total: z.number() }),
|
|
505
|
+
},
|
|
343
506
|
400: { description: "Invalid filter or sort" },
|
|
344
507
|
403: { description: "Permission denied" },
|
|
345
508
|
404: { description: "App or model not found" },
|
|
@@ -355,7 +518,7 @@ const getRowRoute = route({
|
|
|
355
518
|
tags: ["Apps"],
|
|
356
519
|
params: rowParamsSchema,
|
|
357
520
|
responses: {
|
|
358
|
-
200: { description: "App model row" },
|
|
521
|
+
200: { description: "App model row", schema: z.object({ row: AppRowSchema }) },
|
|
359
522
|
403: { description: "Permission denied" },
|
|
360
523
|
404: { description: "App, model, or row not found" },
|
|
361
524
|
},
|
|
@@ -371,7 +534,7 @@ const patchRowRoute = route({
|
|
|
371
534
|
params: rowParamsSchema,
|
|
372
535
|
body: z.object({ values: valuesSchema }),
|
|
373
536
|
responses: {
|
|
374
|
-
200: { description: "Updated row" },
|
|
537
|
+
200: { description: "Updated row", schema: z.object({ row: AppRowSchema }) },
|
|
375
538
|
400: { description: "Invalid row values" },
|
|
376
539
|
403: { description: "Permission denied" },
|
|
377
540
|
404: { description: "App, model, or row not found" },
|
|
@@ -387,7 +550,7 @@ const deleteRowRoute = route({
|
|
|
387
550
|
tags: ["Apps"],
|
|
388
551
|
params: rowParamsSchema,
|
|
389
552
|
responses: {
|
|
390
|
-
200: { description: "Row deleted" },
|
|
553
|
+
200: { description: "Row deleted", schema: z.object({ ok: z.literal(true) }) },
|
|
391
554
|
403: { description: "Permission denied" },
|
|
392
555
|
404: { description: "App, model, or row not found" },
|
|
393
556
|
},
|
|
@@ -402,7 +565,7 @@ const runNamedQueryRoute = route({
|
|
|
402
565
|
tags: ["Apps"],
|
|
403
566
|
params: z.object({ id: z.string().min(1), name: AppNameSchema }),
|
|
404
567
|
responses: {
|
|
405
|
-
200: { description: "Named query rows" },
|
|
568
|
+
200: { description: "Named query rows", schema: z.object({ rows: z.array(AppRowSchema) }) },
|
|
406
569
|
400: { description: "Missing or invalid named query parameters" },
|
|
407
570
|
403: { description: "Permission denied" },
|
|
408
571
|
409: { description: "App definition needs repair" },
|
|
@@ -411,6 +574,40 @@ const runNamedQueryRoute = route({
|
|
|
411
574
|
rbac: { permission: "app.use" },
|
|
412
575
|
});
|
|
413
576
|
|
|
577
|
+
/**
|
|
578
|
+
* `runActionRoute`'s 200 shape branches on the action kind: script actions
|
|
579
|
+
* echo the script's exit result, task actions echo the created task's id and
|
|
580
|
+
* status.
|
|
581
|
+
*/
|
|
582
|
+
const runActionScriptResultSchema = z.object({
|
|
583
|
+
ok: z.boolean(),
|
|
584
|
+
result: z.unknown(),
|
|
585
|
+
stdout: z.string(),
|
|
586
|
+
error: z.string().optional(),
|
|
587
|
+
durationMs: z.number(),
|
|
588
|
+
});
|
|
589
|
+
const runActionTaskResultSchema = z.object({
|
|
590
|
+
ok: z.literal(true),
|
|
591
|
+
taskId: z.string(),
|
|
592
|
+
status: AgentTaskStatusSchema,
|
|
593
|
+
});
|
|
594
|
+
/**
|
|
595
|
+
* Sync actions deliberately reuse the SCRIPT-action shape minus `stdout`/
|
|
596
|
+
* `taskId` (see the handler comment): `result` carries the pass list instead
|
|
597
|
+
* of a script's return value.
|
|
598
|
+
*/
|
|
599
|
+
const runActionSyncResultSchema = z.object({
|
|
600
|
+
ok: z.boolean(),
|
|
601
|
+
result: z.object({ passes: z.array(SyncPassResultSchema) }),
|
|
602
|
+
error: z.string().optional(),
|
|
603
|
+
durationMs: z.number(),
|
|
604
|
+
});
|
|
605
|
+
const runActionResultSchema = z.union([
|
|
606
|
+
runActionScriptResultSchema,
|
|
607
|
+
runActionTaskResultSchema,
|
|
608
|
+
runActionSyncResultSchema,
|
|
609
|
+
]);
|
|
610
|
+
|
|
414
611
|
const runActionRoute = route({
|
|
415
612
|
method: "post",
|
|
416
613
|
path: "/api/apps/{id}/actions/{name}",
|
|
@@ -421,7 +618,7 @@ const runActionRoute = route({
|
|
|
421
618
|
params: z.object({ id: z.string().min(1), name: AppNameSchema }),
|
|
422
619
|
body: z.object({ input: z.record(z.string(), z.unknown()).optional() }),
|
|
423
620
|
responses: {
|
|
424
|
-
200: { description: "Action invoked" },
|
|
621
|
+
200: { description: "Action invoked", schema: runActionResultSchema },
|
|
425
622
|
400: { description: "Invalid action input or stale script reference" },
|
|
426
623
|
403: { description: "Permission denied" },
|
|
427
624
|
409: { description: "App definition needs repair" },
|
|
@@ -430,6 +627,29 @@ const runActionRoute = route({
|
|
|
430
627
|
rbac: { permission: "app.use" },
|
|
431
628
|
});
|
|
432
629
|
|
|
630
|
+
const syncAppRoute = route({
|
|
631
|
+
method: "post",
|
|
632
|
+
path: "/api/apps/{id}/sync",
|
|
633
|
+
pattern: ["api", "apps", null, "sync"],
|
|
634
|
+
summary: "Sync an app's declared sources",
|
|
635
|
+
description:
|
|
636
|
+
"Runs every (model x source) pair the body selects. Each pass pulls outside the row mutation lock and reconciles inside it.",
|
|
637
|
+
tags: ["Apps"],
|
|
638
|
+
params: appParamsSchema,
|
|
639
|
+
body: z.object({ model: AppNameSchema.optional(), source: AppNameSchema.optional() }),
|
|
640
|
+
responses: {
|
|
641
|
+
200: {
|
|
642
|
+
description: "Sync passes",
|
|
643
|
+
schema: z.object({ ok: z.boolean(), passes: z.array(SyncPassResultSchema) }),
|
|
644
|
+
},
|
|
645
|
+
400: { description: "Unknown model or source, or no model declares a source" },
|
|
646
|
+
403: { description: "Permission denied" },
|
|
647
|
+
409: { description: "App definition needs repair" },
|
|
648
|
+
404: { description: "App not found" },
|
|
649
|
+
},
|
|
650
|
+
rbac: { permission: "app.use" },
|
|
651
|
+
});
|
|
652
|
+
|
|
433
653
|
/**
|
|
434
654
|
* RBAC-gate an app operation and resolve the acting principal's stable actor id
|
|
435
655
|
* (`user:<id>`, `agent:<id>`, or `operator`) for row provenance. Returns null
|
|
@@ -501,6 +721,17 @@ function definitionNeedsRepair(res: ServerResponse, app: ReturnType<typeof getAp
|
|
|
501
721
|
return true;
|
|
502
722
|
}
|
|
503
723
|
|
|
724
|
+
/**
|
|
725
|
+
* One `error` string for a sync result, or undefined when every pass succeeded.
|
|
726
|
+
* Each failed pair is named so a multi-pair sync says which one broke; the pass
|
|
727
|
+
* list in `result.passes` carries the rest.
|
|
728
|
+
*/
|
|
729
|
+
function syncPassError(sync: Awaited<ReturnType<typeof runAppSync>>): string | undefined {
|
|
730
|
+
const failed = sync.passes.filter((pass) => pass.error !== undefined);
|
|
731
|
+
if (failed.length === 0) return undefined;
|
|
732
|
+
return failed.map((pass) => `${pass.model}.${pass.source}: ${pass.error}`).join("; ");
|
|
733
|
+
}
|
|
734
|
+
|
|
504
735
|
function snapshotFailure(res: ServerResponse): void {
|
|
505
736
|
jsonError(res, "failed to snapshot app", 500);
|
|
506
737
|
}
|
|
@@ -670,6 +901,12 @@ interface RowFilter {
|
|
|
670
901
|
value: unknown;
|
|
671
902
|
}
|
|
672
903
|
|
|
904
|
+
/**
|
|
905
|
+
* Ad-hoc REST `filter.<col>` stays model-columns-only on purpose: system fields
|
|
906
|
+
* (including the sync envelope) are filterable through named queries, which
|
|
907
|
+
* validate kinds at definition-write time. Widening this loosely-typed
|
|
908
|
+
* query-string path would give two filter vocabularies with different rules.
|
|
909
|
+
*/
|
|
673
910
|
function filtersFromQuery(
|
|
674
911
|
queryParams: URLSearchParams,
|
|
675
912
|
model: ModelDef,
|
|
@@ -737,8 +974,7 @@ export function applyQuery(
|
|
|
737
974
|
rowValue(a, sort.column),
|
|
738
975
|
rowValue(b, sort.column),
|
|
739
976
|
sort.dir,
|
|
740
|
-
sort.column
|
|
741
|
-
sort.column === "updatedAt" ||
|
|
977
|
+
SORTABLE_SYSTEM_DATE_COLUMNS.has(sort.column) ||
|
|
742
978
|
(Object.hasOwn(model.columns, sort.column) &&
|
|
743
979
|
model.columns[sort.column]!.kind === "date"),
|
|
744
980
|
);
|
|
@@ -772,27 +1008,24 @@ export async function handleApps(
|
|
|
772
1008
|
const definition = parseAppDefinition(parsed.body.definition, {
|
|
773
1009
|
resolveApp: getApp,
|
|
774
1010
|
writerAgentId: myAgentId ?? null,
|
|
1011
|
+
writerIsUser: getRequestAuth(req)?.kind === "user",
|
|
775
1012
|
});
|
|
776
1013
|
if (!definition.success) {
|
|
777
1014
|
invalidDefinition(res, definition.issues);
|
|
778
1015
|
return true;
|
|
779
1016
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}),
|
|
788
|
-
},
|
|
789
|
-
201,
|
|
790
|
-
);
|
|
1017
|
+
createAppRoute.respond(res, 201, {
|
|
1018
|
+
app: createApp({
|
|
1019
|
+
name: parsed.body.name,
|
|
1020
|
+
description: parsed.body.description,
|
|
1021
|
+
definition: definition.definition,
|
|
1022
|
+
}),
|
|
1023
|
+
});
|
|
791
1024
|
return true;
|
|
792
1025
|
}
|
|
793
1026
|
|
|
794
1027
|
if (listAppsRoute.match(req.method, pathSegments)) {
|
|
795
|
-
|
|
1028
|
+
listAppsRoute.respond(res, 200, { apps: listApps() });
|
|
796
1029
|
return true;
|
|
797
1030
|
}
|
|
798
1031
|
|
|
@@ -804,7 +1037,9 @@ export async function handleApps(
|
|
|
804
1037
|
jsonError(res, "app not found", 404);
|
|
805
1038
|
return true;
|
|
806
1039
|
}
|
|
807
|
-
|
|
1040
|
+
listAppVersionsRoute.respond(res, 200, {
|
|
1041
|
+
versions: getAppVersions(parsed.params.id).map(decodeAppVersion),
|
|
1042
|
+
});
|
|
808
1043
|
return true;
|
|
809
1044
|
}
|
|
810
1045
|
|
|
@@ -821,7 +1056,7 @@ export async function handleApps(
|
|
|
821
1056
|
jsonError(res, "app version not found", 404);
|
|
822
1057
|
return true;
|
|
823
1058
|
}
|
|
824
|
-
|
|
1059
|
+
getAppVersionRoute.respond(res, 200, { version: decodeAppVersion(version) });
|
|
825
1060
|
return true;
|
|
826
1061
|
}
|
|
827
1062
|
|
|
@@ -837,8 +1072,10 @@ export async function handleApps(
|
|
|
837
1072
|
migration: parsed.body.migration,
|
|
838
1073
|
forceElementBreak: parsed.body.forceElementBreak,
|
|
839
1074
|
changedByAgentId: myAgentId,
|
|
1075
|
+
writerAgentId: myAgentId ?? null,
|
|
1076
|
+
writerIsUser: getRequestAuth(req)?.kind === "user",
|
|
840
1077
|
});
|
|
841
|
-
|
|
1078
|
+
rollbackAppRoute.respond(res, 200, { app: rolledBack.app, migration: rolledBack.migration });
|
|
842
1079
|
} catch (error) {
|
|
843
1080
|
if (error instanceof AppRollbackAppNotFoundError) {
|
|
844
1081
|
jsonError(res, "app not found", 404);
|
|
@@ -874,7 +1111,7 @@ export async function handleApps(
|
|
|
874
1111
|
parsed.body.rows.map((row) => row.values),
|
|
875
1112
|
{ actor },
|
|
876
1113
|
);
|
|
877
|
-
|
|
1114
|
+
bulkCreateRowsRoute.respond(res, 200, { rows });
|
|
878
1115
|
} catch (error) {
|
|
879
1116
|
if (!invalidRows(res, error)) throw error;
|
|
880
1117
|
}
|
|
@@ -897,7 +1134,7 @@ export async function handleApps(
|
|
|
897
1134
|
parsed.body.values,
|
|
898
1135
|
{ actor },
|
|
899
1136
|
);
|
|
900
|
-
|
|
1137
|
+
createRowRoute.respond(res, 201, { row });
|
|
901
1138
|
} catch (error) {
|
|
902
1139
|
if (!invalidRows(res, error)) throw error;
|
|
903
1140
|
}
|
|
@@ -925,8 +1162,7 @@ export async function handleApps(
|
|
|
925
1162
|
extra !== undefined ||
|
|
926
1163
|
!column ||
|
|
927
1164
|
(dir !== "asc" && dir !== "desc") ||
|
|
928
|
-
(column
|
|
929
|
-
column !== "updatedAt" &&
|
|
1165
|
+
(!SORTABLE_SYSTEM_DATE_COLUMNS.has(column) &&
|
|
930
1166
|
(!Object.hasOwn(resolved.model.columns, column) ||
|
|
931
1167
|
resolved.model.columns[column]!.hidden === true))
|
|
932
1168
|
) {
|
|
@@ -945,8 +1181,7 @@ export async function handleApps(
|
|
|
945
1181
|
rowValue(a, column),
|
|
946
1182
|
rowValue(b, column),
|
|
947
1183
|
dir,
|
|
948
|
-
column
|
|
949
|
-
column === "updatedAt" ||
|
|
1184
|
+
SORTABLE_SYSTEM_DATE_COLUMNS.has(column) ||
|
|
950
1185
|
(Object.hasOwn(resolved.model.columns, column) &&
|
|
951
1186
|
resolved.model.columns[column]!.kind === "date"),
|
|
952
1187
|
);
|
|
@@ -954,7 +1189,7 @@ export async function handleApps(
|
|
|
954
1189
|
});
|
|
955
1190
|
}
|
|
956
1191
|
const total = rows.length;
|
|
957
|
-
|
|
1192
|
+
listRowsRoute.respond(res, 200, { rows: rows.slice(0, parsed.query.limit ?? 200), total });
|
|
958
1193
|
return true;
|
|
959
1194
|
}
|
|
960
1195
|
|
|
@@ -968,7 +1203,7 @@ export async function handleApps(
|
|
|
968
1203
|
jsonError(res, "row not found", 404);
|
|
969
1204
|
return true;
|
|
970
1205
|
}
|
|
971
|
-
|
|
1206
|
+
getRowRoute.respond(res, 200, { row });
|
|
972
1207
|
return true;
|
|
973
1208
|
}
|
|
974
1209
|
|
|
@@ -993,7 +1228,7 @@ export async function handleApps(
|
|
|
993
1228
|
jsonError(res, "row not found", 404);
|
|
994
1229
|
return true;
|
|
995
1230
|
}
|
|
996
|
-
|
|
1231
|
+
patchRowRoute.respond(res, 200, { row });
|
|
997
1232
|
} catch (error) {
|
|
998
1233
|
if (!invalidRows(res, error)) throw error;
|
|
999
1234
|
}
|
|
@@ -1017,7 +1252,7 @@ export async function handleApps(
|
|
|
1017
1252
|
jsonError(res, "row not found", 404);
|
|
1018
1253
|
return true;
|
|
1019
1254
|
}
|
|
1020
|
-
|
|
1255
|
+
deleteRowRoute.respond(res, 200, { ok: true });
|
|
1021
1256
|
return true;
|
|
1022
1257
|
}
|
|
1023
1258
|
|
|
@@ -1039,7 +1274,7 @@ export async function handleApps(
|
|
|
1039
1274
|
}
|
|
1040
1275
|
const model = app.definition.models[query.model]!;
|
|
1041
1276
|
try {
|
|
1042
|
-
|
|
1277
|
+
runNamedQueryRoute.respond(res, 200, {
|
|
1043
1278
|
rows: applyQuery(
|
|
1044
1279
|
listAppRows(app.id, query.model),
|
|
1045
1280
|
query,
|
|
@@ -1121,8 +1356,9 @@ export async function handleApps(
|
|
|
1121
1356
|
: output.runtimeError
|
|
1122
1357
|
? `${output.runtimeError.name}: ${output.runtimeError.message}`
|
|
1123
1358
|
: (output.error ?? `Script exited with code ${output.exitCode}`);
|
|
1124
|
-
|
|
1359
|
+
runActionRoute.respond(
|
|
1125
1360
|
res,
|
|
1361
|
+
200,
|
|
1126
1362
|
scrubObject({
|
|
1127
1363
|
ok,
|
|
1128
1364
|
result: output.result,
|
|
@@ -1134,6 +1370,36 @@ export async function handleApps(
|
|
|
1134
1370
|
return true;
|
|
1135
1371
|
}
|
|
1136
1372
|
|
|
1373
|
+
if (action.kind === "sync") {
|
|
1374
|
+
const startedAt = Date.now();
|
|
1375
|
+
const sync = await runAppSync({
|
|
1376
|
+
appId: app.id,
|
|
1377
|
+
...(action.model === undefined ? {} : { model: action.model }),
|
|
1378
|
+
...(action.source === undefined ? {} : { source: action.source }),
|
|
1379
|
+
invokedBy: actor,
|
|
1380
|
+
});
|
|
1381
|
+
const durationMs = Date.now() - startedAt;
|
|
1382
|
+
if (sync.issues && sync.issues.length > 0) {
|
|
1383
|
+
json(res, { error: "invalid sync action", issues: sync.issues }, 400);
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
// Deliberately the SCRIPT-action response shape, minus `taskId`:
|
|
1387
|
+
// app-surface.tsx branches on `taskId` first, so omitting it gives the
|
|
1388
|
+
// dashboard running -> ok/error + refetchAll() with zero UI changes.
|
|
1389
|
+
const error = syncPassError(sync);
|
|
1390
|
+
runActionRoute.respond(
|
|
1391
|
+
res,
|
|
1392
|
+
200,
|
|
1393
|
+
scrubObject({
|
|
1394
|
+
ok: sync.ok,
|
|
1395
|
+
result: { passes: sync.passes },
|
|
1396
|
+
...(error === undefined ? {} : { error }),
|
|
1397
|
+
durationMs,
|
|
1398
|
+
}),
|
|
1399
|
+
);
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1137
1403
|
const lead = action.agentId ? null : getLeadAgent();
|
|
1138
1404
|
const taskPrompt = resolveTemplate("task.app.action", {
|
|
1139
1405
|
prompt: action.prompt,
|
|
@@ -1144,9 +1410,40 @@ export async function handleApps(
|
|
|
1144
1410
|
const task = createTaskWithSiblingAwareness(taskPrompt.text, {
|
|
1145
1411
|
source: "api",
|
|
1146
1412
|
agentId: action.agentId ?? lead?.id,
|
|
1413
|
+
// App-spawned tasks group under the app's asset namespace so a
|
|
1414
|
+
// swarm-tasks source can pull them back via config.assetKey.
|
|
1415
|
+
key: normalizeAssetKey(`shared/app:${app.id}/action:${parsed.params.name}/`),
|
|
1147
1416
|
...(actor.startsWith("user:") ? { requestedByUserId: actor.slice("user:".length) } : {}),
|
|
1148
1417
|
});
|
|
1149
|
-
|
|
1418
|
+
runActionRoute.respond(res, 200, { ok: true, taskId: task.id, status: task.status });
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (syncAppRoute.match(req.method, pathSegments)) {
|
|
1423
|
+
if (enforceContentLengthCap(req, res, MAX_APP_ROW_BODY_BYTES) === BODY_TOO_LARGE) return true;
|
|
1424
|
+
const parsed = await syncAppRoute.parse(req, res, pathSegments, queryParams);
|
|
1425
|
+
if (!parsed) return true;
|
|
1426
|
+
const actor = authorizeAppUse(req, res, myAgentId, parsed.params.id);
|
|
1427
|
+
if (!actor) return true;
|
|
1428
|
+
|
|
1429
|
+
const app = getApp(parsed.params.id);
|
|
1430
|
+
if (!app) {
|
|
1431
|
+
jsonError(res, "app not found", 404);
|
|
1432
|
+
return true;
|
|
1433
|
+
}
|
|
1434
|
+
if (definitionNeedsRepair(res, app)) return true;
|
|
1435
|
+
|
|
1436
|
+
const sync = await runAppSync({
|
|
1437
|
+
appId: app.id,
|
|
1438
|
+
...(parsed.body.model === undefined ? {} : { model: parsed.body.model }),
|
|
1439
|
+
...(parsed.body.source === undefined ? {} : { source: parsed.body.source }),
|
|
1440
|
+
invokedBy: actor,
|
|
1441
|
+
});
|
|
1442
|
+
if (sync.issues && sync.issues.length > 0) {
|
|
1443
|
+
json(res, { error: "nothing to sync", issues: sync.issues }, 400);
|
|
1444
|
+
return true;
|
|
1445
|
+
}
|
|
1446
|
+
syncAppRoute.respond(res, 200, scrubObject({ ok: sync.ok, passes: sync.passes }));
|
|
1150
1447
|
return true;
|
|
1151
1448
|
}
|
|
1152
1449
|
|
|
@@ -1185,6 +1482,7 @@ export async function handleApps(
|
|
|
1185
1482
|
currentAppId: parsed.params.id,
|
|
1186
1483
|
resolveApp: getApp,
|
|
1187
1484
|
writerAgentId: myAgentId ?? null,
|
|
1485
|
+
writerIsUser: getRequestAuth(req)?.kind === "user",
|
|
1188
1486
|
existingDefinition: existing.definition,
|
|
1189
1487
|
});
|
|
1190
1488
|
if (!definition.success) {
|
|
@@ -1232,7 +1530,7 @@ export async function handleApps(
|
|
|
1232
1530
|
jsonError(res, "app not found", 404);
|
|
1233
1531
|
return true;
|
|
1234
1532
|
}
|
|
1235
|
-
|
|
1533
|
+
patchAppRoute.respond(res, 200, { app, migration });
|
|
1236
1534
|
return true;
|
|
1237
1535
|
}
|
|
1238
1536
|
|
|
@@ -1262,6 +1560,7 @@ export async function handleApps(
|
|
|
1262
1560
|
currentAppId: parsed.params.id,
|
|
1263
1561
|
resolveApp: getApp,
|
|
1264
1562
|
writerAgentId: myAgentId ?? null,
|
|
1563
|
+
writerIsUser: getRequestAuth(req)?.kind === "user",
|
|
1265
1564
|
existingDefinition: existing.definition,
|
|
1266
1565
|
});
|
|
1267
1566
|
if (!parsedDefinition.success) {
|
|
@@ -1315,7 +1614,7 @@ export async function handleApps(
|
|
|
1315
1614
|
jsonError(res, "app not found", 404);
|
|
1316
1615
|
return true;
|
|
1317
1616
|
}
|
|
1318
|
-
|
|
1617
|
+
updateAppRoute.respond(res, 200, { app, migration });
|
|
1319
1618
|
return true;
|
|
1320
1619
|
}
|
|
1321
1620
|
|
|
@@ -1342,7 +1641,7 @@ export async function handleApps(
|
|
|
1342
1641
|
jsonError(res, "app not found", 404);
|
|
1343
1642
|
return true;
|
|
1344
1643
|
}
|
|
1345
|
-
|
|
1644
|
+
deleteAppRoute.respond(res, 200, { ok: true });
|
|
1346
1645
|
return true;
|
|
1347
1646
|
}
|
|
1348
1647
|
|
|
@@ -1355,7 +1654,13 @@ export async function handleApps(
|
|
|
1355
1654
|
jsonError(res, "app not found", 404);
|
|
1356
1655
|
return true;
|
|
1357
1656
|
}
|
|
1358
|
-
|
|
1657
|
+
// Per-source freshness for the runtime payload — present only once a
|
|
1658
|
+
// declared source has completed at least one pass.
|
|
1659
|
+
const syncStatus = collectAppSyncStatus(app.id);
|
|
1660
|
+
getAppRoute.respond(res, 200, {
|
|
1661
|
+
app,
|
|
1662
|
+
...(Object.keys(syncStatus).length > 0 ? { syncStatus } : {}),
|
|
1663
|
+
});
|
|
1359
1664
|
return true;
|
|
1360
1665
|
}
|
|
1361
1666
|
|
|
@@ -1372,7 +1677,7 @@ export async function handleApps(
|
|
|
1372
1677
|
const schema = app.definition.userConfig ?? {};
|
|
1373
1678
|
const owner = resolveHttpFavoriteOwner(req, myAgentId);
|
|
1374
1679
|
const stored = owner ? getAppUserConfigValues(app.id, owner.scope) : {};
|
|
1375
|
-
|
|
1680
|
+
getUserConfigRoute.respond(res, 200, { values: mergeUserConfigValues(schema, stored), schema });
|
|
1376
1681
|
return true;
|
|
1377
1682
|
}
|
|
1378
1683
|
|
|
@@ -1418,7 +1723,10 @@ export async function handleApps(
|
|
|
1418
1723
|
return true;
|
|
1419
1724
|
}
|
|
1420
1725
|
upsertAppUserConfigValues(app.id, owner.scope, parsed.body.values);
|
|
1421
|
-
|
|
1726
|
+
putUserConfigRoute.respond(res, 200, {
|
|
1727
|
+
values: mergeUserConfigValues(schema, parsed.body.values),
|
|
1728
|
+
schema,
|
|
1729
|
+
});
|
|
1422
1730
|
return true;
|
|
1423
1731
|
}
|
|
1424
1732
|
|