@desplega.ai/agent-swarm 1.145.0 → 1.147.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/{acp-adapter-580t41c0.js → acp-adapter-9n319wqc.js} +61 -6
- package/dist/{actions-m662tjng.js → actions-2vxqvpr9.js} +9 -8
- package/dist/{app-zb3rndm8.js → app-w066xfy0.js} +6 -5
- package/dist/assistant-kt31dj2h.js +236 -0
- package/dist/{boot-reembed-072x00vn.js → boot-reembed-az5rassp.js} +7 -6
- package/dist/{boot-reembed-65zz2smt.js → boot-reembed-j3mm3rfz.js} +8 -7
- package/dist/{boot-scrub-logs-yz0ams04.js → boot-scrub-logs-b6h54817.js} +4 -4
- package/dist/{claude-adapter-paj270y2.js → claude-adapter-w1z04ann.js} +8 -6
- package/dist/{claude-managed-setup-w619jncn.js → claude-managed-setup-f5pmsyrp.js} +24 -3
- package/dist/{claude-sdk-session-swt80j9z.js → claude-sdk-session-f0cc1kec.js} +8 -6
- package/dist/{cli-dt553ktb.js → cli-0b6y7kxm.js} +1 -1
- package/dist/{cli-bxdhw615.js → cli-12fz972k.js} +4 -4
- package/dist/{cli-s2f20r5w.js → cli-1th1728g.js} +2 -2
- package/dist/{cli-rs6ec3b9.js → cli-3tgwnf43.js} +3 -3
- package/dist/{cli-gxnzc6wh.js → cli-4nr988h6.js} +1 -1
- package/dist/{cli-axs8e6d4.js → cli-53s590z8.js} +1 -1
- package/dist/{cli-ygbjp3z8.js → cli-54wwve05.js} +5 -4
- package/dist/{cli-v0asr5yv.js → cli-719k9j2c.js} +2 -2
- package/dist/{cli-anktweqr.js → cli-89xfd7f9.js} +21 -47
- package/dist/{cli-q34a0e10.js → cli-8v0g1yc8.js} +1 -1
- package/dist/cli-9d8ntetj.js +42 -0
- package/dist/{cli-z9fprcrf.js → cli-atcve9y8.js} +881 -738
- package/dist/{cli-jmb7g47h.js → cli-b5bq9crk.js} +14 -31
- package/dist/cli-d8brzxjb.js +190 -0
- package/dist/{cli-gzky0gkr.js → cli-de14znh8.js} +2 -2
- package/dist/{cli-xytwff7f.js → cli-dttph1bw.js} +2 -2
- package/dist/cli-ep19k3mb.js +19 -0
- package/dist/{cli-m1xa8be7.js → cli-f146mn5s.js} +7 -50
- package/dist/{cli-1fj0chdk.js → cli-f7dsy61y.js} +5 -3
- package/dist/{cli-f567d5c1.js → cli-ftht3zzj.js} +2782 -2589
- package/dist/{cli-a0g1gbt5.js → cli-hbv0kq6w.js} +2 -2
- package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
- package/dist/{cli-7fnkj1hj.js → cli-kpv03zhj.js} +21 -17
- package/dist/{cli-m4j6trn4.js → cli-m8yrsg97.js} +2 -2
- package/dist/cli-m9sxbkhm.js +779 -0
- package/dist/cli-mmemxxdc.js +823 -0
- package/dist/{cli-tqtvj5pt.js → cli-n8508nre.js} +1 -1
- package/dist/{cli-z96t0thj.js → cli-p1d5b073.js} +37 -2
- package/dist/{cli-gh8y8f36.js → cli-rw51bq3j.js} +13 -10
- package/dist/{cli-59ej1c07.js → cli-smby5m17.js} +7 -6
- package/dist/{cli-65vqzz3v.js → cli-tzvk9haz.js} +94 -372
- package/dist/{cli-dc9k2ba9.js → cli-w9kb3bk1.js} +1 -1
- package/dist/{cli-kpev657q.js → cli-y6b6fb76.js} +25 -3
- package/dist/{cli-123q2get.js → cli-yz3djrm0.js} +9 -9
- package/dist/{cli-wvp6bvy4.js → cli-zhjmqxzh.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{codex-adapter-k98vaw2r.js → codex-adapter-zw4dwsx7.js} +2 -2
- package/dist/{codex-hook-9pf19cks.js → codex-hook-bw8p5nh4.js} +2 -2
- package/dist/{codex-session-runner-tngtphx7.js → codex-session-runner-k981s65v.js} +2 -2
- package/dist/{commands-y8nbr3fv.js → commands-sykrxx7e.js} +4 -4
- package/dist/{db-30p4h2t1.js → db-sfbsc8wt.js} +6 -4
- package/dist/handlers-0aspcejm.js +603 -0
- package/dist/{hook-dcw4jfbj.js → hook-4nvqj2sb.js} +7 -5
- package/dist/{hook-21jexvfx.js → hook-hxccs7m5.js} +24 -49
- package/dist/{http-r4kq5g8d.js → http-y06z517d.js} +230 -94
- package/dist/{index-q3qja6ta.js → index-hssww7kj.js} +15 -14
- package/dist/{index-gay66b9w.js → index-k8znfx0z.js} +13 -12
- package/dist/{index-53zyht4z.js → index-tce1rss4.js} +371 -110
- package/dist/{index-nqvf26za.js → index-x5j894zf.js} +14 -13
- package/dist/{keepalive-xs8fa55y.js → keepalive-w733ax66.js} +6 -6
- package/dist/{lead-d9m2t4s0.js → lead-75bjqa9d.js} +29 -27
- package/dist/{maintenance-3zh82g6z.js → maintenance-w4f1zjck.js} +8 -7
- package/dist/{oauth-refresh-sweep-03snsye5.js → oauth-refresh-sweep-wdetky26.js} +5 -5
- package/dist/{onboard-0cg1925x.js → onboard-bb5net8s.js} +2 -2
- package/dist/{otel-impl-0ab95z27.js → otel-impl-0w7c14ap.js} +1 -1
- package/dist/{pi-mono-adapter-dtkz4e7j.js → pi-mono-adapter-2has4gnp.js} +12 -2
- package/dist/{pricing-refresh-e5jhzz4j.js → pricing-refresh-9nzsrgcq.js} +6 -6
- package/dist/{rbac-roles-p1mf40z3.js → rbac-roles-r3hqjp09.js} +5 -5
- package/dist/{rbac-roles-2w3cmrkc.js → rbac-roles-z1jrffbt.js} +4 -4
- package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
- package/dist/{render-v2-xhawry3k.js → render-v2-vbr89fnb.js} +6 -5
- package/dist/{seed-pricing-t223sw91.js → seed-pricing-g62hy1pk.js} +7 -5
- package/dist/{setup-zcs0rce7.js → setup-gnfqnptp.js} +2 -2
- package/dist/{worker-qnp2ka62.js → worker-kznxp4qm.js} +29 -27
- package/openapi.json +263 -12
- package/package.json +1 -1
- package/plugin/commands/start-worker.md +4 -9
- package/plugin/pi-skills/start-worker/SKILL.md +4 -9
- package/src/be/db/agents.ts +556 -0
- package/src/be/db/context-versions.ts +117 -0
- package/src/be/db/runtime.ts +406 -0
- package/src/be/db/tasks/read.ts +1167 -0
- package/src/be/db/tasks/write.ts +953 -0
- package/src/be/db.ts +584 -3429
- package/src/be/migrations/147_scheduled_tasks_parent_task.sql +7 -0
- package/src/be/migrations/148_pages_svg.sql +123 -0
- package/src/be/migrations/149_session_tokens.sql +24 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +13 -8
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-scripts/catalog/star-history-refresh.ts +101 -0
- package/src/be/seed-scripts/catalog/star-history-renderer.ts +119 -0
- package/src/be/seed-scripts/index.ts +17 -0
- package/src/be/seed-skills/index.ts +14 -0
- package/src/be/swarm-config-guard.ts +5 -2
- package/src/be/task-attachment-store.ts +70 -0
- package/src/be/users.ts +77 -0
- package/src/commands/attachments-section.ts +37 -0
- package/src/commands/claude-managed-setup.ts +45 -3
- package/src/commands/claude-md-session.ts +408 -0
- package/src/commands/context-preamble.ts +15 -3
- package/src/commands/profile-sync.ts +205 -5
- package/src/commands/provider-credentials.ts +60 -27
- package/src/commands/runner.ts +63 -58
- package/src/commands/templates.ts +25 -2
- package/src/hooks/hook.ts +26 -67
- package/src/http/agents.ts +40 -1
- package/src/http/auth.ts +13 -1
- package/src/http/fs.ts +21 -51
- package/src/http/index.ts +2 -0
- package/src/http/mcp.ts +32 -0
- package/src/http/pages-public.ts +20 -4
- package/src/http/pages.ts +11 -3
- package/src/http/poll.ts +4 -0
- package/src/http/repos.ts +3 -3
- package/src/http/sessions.ts +82 -0
- package/src/http/tasks.ts +10 -2
- package/src/http/utils.ts +79 -1
- package/src/prompts/session-templates.ts +12 -8
- package/src/providers/acp-adapter.ts +62 -8
- package/src/providers/pi-mono-adapter.ts +23 -6
- package/src/providers/pricing-sources.md +11 -0
- package/src/scheduler/scheduler.test.ts +49 -2
- package/src/scheduler/scheduler.ts +4 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +8 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +87 -46
- package/src/slack/handlers.test.ts +1 -3
- package/src/slack/handlers.ts +102 -87
- package/src/slack/inbound-files.ts +466 -0
- package/src/slack/templates.ts +14 -0
- package/src/tasks/task-terminal-effects.ts +158 -0
- package/src/tests/acp-adapter.test.ts +369 -117
- package/src/tests/budget-claim-gate.test.ts +1 -0
- package/src/tests/build-pi-skills.test.ts +5 -7
- package/src/tests/claude-managed-setup.test.ts +52 -4
- package/src/tests/claude-md-session.test.ts +603 -0
- package/src/tests/codex-skill-resolver.test.ts +19 -0
- package/src/tests/context-versioning.test.ts +107 -0
- package/src/tests/cors-allowlist.test.ts +309 -0
- package/src/tests/credential-check.test.ts +157 -14
- package/src/tests/db-agents.test.ts +91 -0
- package/src/tests/db-context-versions.test.ts +37 -0
- package/src/tests/db-extraction-contract.test.ts +45 -0
- package/src/tests/db-runtime.test.ts +70 -0
- package/src/tests/db-task-read-contract.test.ts +624 -0
- package/src/tests/db-task-write-contract.test.ts +653 -0
- package/src/tests/db-tasks-read.test.ts +183 -0
- package/src/tests/db-tasks-write.test.ts +230 -0
- package/src/tests/defer-task.test.ts +389 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/e2e-visuals-stage.test.ts +151 -0
- package/src/tests/file-lock.test.ts +144 -0
- package/src/tests/fixtures/file-lock-holder.ts +13 -0
- package/src/tests/fixtures/hold-file-lock.ts +25 -0
- package/src/tests/heartbeat-expiry.test.ts +188 -0
- package/src/tests/http-api-integration.test.ts +78 -1
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-svg-migration.test.ts +42 -0
- package/src/tests/preload.ts +4 -1
- package/src/tests/profile-sync-warning.test.ts +16 -0
- package/src/tests/profile-sync.test.ts +126 -0
- package/src/tests/prompt-template-session.test.ts +20 -4
- package/src/tests/provider-registration.test.ts +93 -0
- package/src/tests/rbac-e2e-helpers.test.ts +53 -0
- package/src/tests/rbac-e2e-helpers.ts +19 -2
- package/src/tests/reload-config.test.ts +26 -0
- package/src/tests/routing-affinity-inventory.test.ts +1 -1
- package/src/tests/runner-attachments-section.test.ts +22 -2
- package/src/tests/runner-context-preamble.test.ts +30 -0
- package/src/tests/runner-deployment-resume.test.ts +107 -0
- package/src/tests/runner-repo-autostash.test.ts +12 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/session-tokens.test.ts +279 -0
- package/src/tests/slack-file-tools-attachments.test.ts +408 -0
- package/src/tests/slack-inbound-files.test.ts +847 -0
- package/src/tests/star-history-refresh.test.ts +102 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +21 -0
- package/src/tests/swarm-repos.test.ts +20 -0
- package/src/tests/tool-annotations.test.ts +4 -3
- package/src/tests/ui-logs-parser.test.ts +176 -0
- package/src/tools/create-page.ts +4 -2
- package/src/tools/defer-task.ts +239 -0
- package/src/tools/repos/update-repo.ts +2 -2
- package/src/tools/schedules/create-schedule.ts +1 -0
- package/src/tools/schedules/list-schedules.ts +1 -0
- package/src/tools/schedules/patch-schedule.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +1 -0
- package/src/tools/slack-download-file.ts +60 -29
- package/src/tools/slack-read.ts +73 -32
- package/src/tools/store-progress.ts +16 -146
- package/src/tools/tool-config.ts +1 -0
- package/src/tools/utils.ts +21 -0
- package/src/types.ts +37 -4
- package/src/utils/acp-session-token.ts +56 -0
- package/src/utils/file-lock.ts +111 -0
- package/src/utils/heartbeat-expiry.ts +78 -0
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/task-attachment-links.ts +22 -0
- package/templates/skills/code-quality/content.md +8 -5
- package/templates/skills/download-task-attachment/content.md +2 -1
- package/{plugin/pi-skills → templates/skills}/review-offered-task/SKILL.md +5 -5
- package/templates/skills/review-offered-task/config.json +14 -0
- package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md} +3 -8
- package/templates/skills/scheduling/content.md +4 -0
- package/templates/skills/slack-interaction/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +16 -0
- package/templates/skills/work-on-task/config.json +14 -0
- package/templates/skills/work-on-task/content.md +11 -0
- package/dist/assistant-0qt42nzg.js +0 -212
- package/dist/cli-fs1kkqgr.js +0 -317
- package/dist/cli-jnf5zghe.js +0 -269
- package/dist/cli-n1q8q14d.js +0 -177
- package/dist/handlers-yfp1qn81.js +0 -592
- package/plugin/commands/work-on-task.md +0 -39
- package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
|
@@ -133,7 +133,7 @@ registerTemplate({
|
|
|
133
133
|
|
|
134
134
|
**RESUMED TASK** - This task was interrupted during a deployment and is being resumed.
|
|
135
135
|
|
|
136
|
-
Task: "{{task_description}}"
|
|
136
|
+
Task: "{{task_description}}"{{attachments_section}}
|
|
137
137
|
|
|
138
138
|
Previous Progress:
|
|
139
139
|
{{progress}}
|
|
@@ -143,6 +143,7 @@ Continue from where you left off. Review the progress above and complete the rem
|
|
|
143
143
|
{ name: "work_on_task_cmd", description: "Formatted /work-on-task command" },
|
|
144
144
|
{ name: "task_id", description: "Task ID" },
|
|
145
145
|
{ name: "task_description", description: "Original task description" },
|
|
146
|
+
{ name: "attachments_section", description: "Task attachment fetch recipes" },
|
|
146
147
|
{ name: "progress", description: "Previous progress text" },
|
|
147
148
|
{
|
|
148
149
|
name: "completion_instructions",
|
|
@@ -159,13 +160,14 @@ registerTemplate({
|
|
|
159
160
|
|
|
160
161
|
**RESUMED TASK** - This task was interrupted during a deployment and is being resumed.
|
|
161
162
|
|
|
162
|
-
Task: "{{task_description}}"
|
|
163
|
+
Task: "{{task_description}}"{{attachments_section}}
|
|
163
164
|
|
|
164
165
|
No progress was saved before the interruption. Start the task fresh but be aware files may have been partially modified.{{completion_instructions}}`,
|
|
165
166
|
variables: [
|
|
166
167
|
{ name: "work_on_task_cmd", description: "Formatted /work-on-task command" },
|
|
167
168
|
{ name: "task_id", description: "Task ID" },
|
|
168
169
|
{ name: "task_description", description: "Original task description" },
|
|
170
|
+
{ name: "attachments_section", description: "Task attachment fetch recipes" },
|
|
169
171
|
{
|
|
170
172
|
name: "completion_instructions",
|
|
171
173
|
description: "Completion instructions (empty for providers without MCP)",
|
|
@@ -173,3 +175,24 @@ No progress was saved before the interruption. Start the task fresh but be aware
|
|
|
173
175
|
],
|
|
174
176
|
category: "task_lifecycle",
|
|
175
177
|
});
|
|
178
|
+
|
|
179
|
+
registerTemplate({
|
|
180
|
+
eventType: "task.output.schema",
|
|
181
|
+
header: "",
|
|
182
|
+
defaultBody: `\n\n**Required Output Format**: When completing this task, you MUST call store-progress with output that is valid JSON conforming to this schema:
|
|
183
|
+
\`\`\`json
|
|
184
|
+
{{schema}}
|
|
185
|
+
\`\`\`
|
|
186
|
+
Call store-progress with status "completed" and your JSON output. If your output doesn't match the schema, the tool call will fail and you should fix and retry.`,
|
|
187
|
+
variables: [{ name: "schema", description: "Required output JSON schema" }],
|
|
188
|
+
category: "task_lifecycle",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
registerTemplate({
|
|
192
|
+
eventType: "task.output.generic",
|
|
193
|
+
header: "",
|
|
194
|
+
defaultBody:
|
|
195
|
+
'\n\nWhen done, use `store-progress` with status: "completed" and include your output.',
|
|
196
|
+
variables: [],
|
|
197
|
+
category: "task_lifecycle",
|
|
198
|
+
});
|
package/src/hooks/hook.ts
CHANGED
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
postRatings,
|
|
10
10
|
type RetrievalRow,
|
|
11
11
|
} from "../be/memory/raters/llm";
|
|
12
|
+
import { materializeClaudeMd, stopClaudeMd } from "../commands/claude-md-session";
|
|
12
13
|
import {
|
|
13
14
|
buildIndependentIdentityPayloads,
|
|
14
15
|
contentSha256,
|
|
16
|
+
fetchWithSyncTimeout,
|
|
17
|
+
type ProfilePayload,
|
|
15
18
|
readIdentityBaselines,
|
|
16
19
|
warnProfileFileTooLarge,
|
|
17
20
|
} from "../commands/profile-sync";
|
|
@@ -24,10 +27,6 @@ import { checkToolLoop, clearToolHistory } from "./tool-loop-detection";
|
|
|
24
27
|
|
|
25
28
|
const SERVER_NAME = pkg.config?.name ?? "agent-swarm";
|
|
26
29
|
|
|
27
|
-
// CLAUDE.md file paths
|
|
28
|
-
const CLAUDE_MD_PATH = `${process.env.HOME}/.claude/CLAUDE.md`;
|
|
29
|
-
const CLAUDE_MD_BACKUP_PATH = `${process.env.HOME}/.claude/CLAUDE.md.bak`;
|
|
30
|
-
|
|
31
30
|
// Identity and workspace file paths
|
|
32
31
|
const SOUL_MD_PATH = "/workspace/SOUL.md";
|
|
33
32
|
const IDENTITY_MD_PATH = "/workspace/IDENTITY.md";
|
|
@@ -120,7 +119,7 @@ export async function postHookProfileUpdate({
|
|
|
120
119
|
}: {
|
|
121
120
|
url: string;
|
|
122
121
|
headers: Record<string, string>;
|
|
123
|
-
body:
|
|
122
|
+
body: ProfilePayload["body"];
|
|
124
123
|
label: string;
|
|
125
124
|
fetchImpl?: typeof fetch;
|
|
126
125
|
}): Promise<void> {
|
|
@@ -204,47 +203,6 @@ async function fetchTaskDetails(
|
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
/**
|
|
208
|
-
* Backup existing CLAUDE.md file if it exists
|
|
209
|
-
*/
|
|
210
|
-
async function backupExistingClaudeMd(): Promise<void> {
|
|
211
|
-
const file = Bun.file(CLAUDE_MD_PATH);
|
|
212
|
-
if (await file.exists()) {
|
|
213
|
-
const content = await file.text();
|
|
214
|
-
await Bun.write(CLAUDE_MD_BACKUP_PATH, content);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Write agent's CLAUDE.md content to ~/.claude/CLAUDE.md
|
|
220
|
-
*/
|
|
221
|
-
async function writeAgentClaudeMd(content: string): Promise<void> {
|
|
222
|
-
// Ensure ~/.claude directory exists
|
|
223
|
-
const dir = `${process.env.HOME}/.claude`;
|
|
224
|
-
try {
|
|
225
|
-
await Bun.$`mkdir -p ${dir}`.quiet();
|
|
226
|
-
} catch {
|
|
227
|
-
// Directory may already exist
|
|
228
|
-
}
|
|
229
|
-
await Bun.write(CLAUDE_MD_PATH, content);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Restore CLAUDE.md from backup or remove it if no backup exists
|
|
234
|
-
*/
|
|
235
|
-
async function restoreClaudeMdBackup(): Promise<void> {
|
|
236
|
-
const backupFile = Bun.file(CLAUDE_MD_BACKUP_PATH);
|
|
237
|
-
if (await backupFile.exists()) {
|
|
238
|
-
const content = await backupFile.text();
|
|
239
|
-
await Bun.write(CLAUDE_MD_PATH, content);
|
|
240
|
-
// Remove backup file
|
|
241
|
-
await Bun.$`rm -f ${CLAUDE_MD_BACKUP_PATH}`.quiet();
|
|
242
|
-
} else {
|
|
243
|
-
// No backup existed, remove the agent's CLAUDE.md
|
|
244
|
-
await Bun.$`rm -f ${CLAUDE_MD_PATH}`.quiet();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
206
|
/**
|
|
249
207
|
* Resolve task context for the Stop-hook session-summary / memory-rater
|
|
250
208
|
* piggyback. Prefers the AGENT_SWARM_TASK_ID env var (set by the harness in
|
|
@@ -663,22 +621,21 @@ export async function handleHook(): Promise<void> {
|
|
|
663
621
|
};
|
|
664
622
|
|
|
665
623
|
/**
|
|
666
|
-
*
|
|
624
|
+
* Stop's CLAUDE.md step: sync an agent's edit (never content a hook wrote, and
|
|
625
|
+
* as a compare-and-set against its base), then restore the `.bak` — all under
|
|
626
|
+
* the CLAUDE.md lock (see `claude-md-session.ts`). The POST is bounded
|
|
627
|
+
* (CLAUDE_MD_SYNC_TIMEOUT_MS) so the lock is never held for long.
|
|
667
628
|
*/
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
url: `${getBaseUrl()}/api/agents/${agentId}/profile`,
|
|
679
|
-
headers: mcpConfig.headers,
|
|
680
|
-
body: { claudeMd: content, changeSource: "session_sync" },
|
|
681
|
-
label: "claudeMd",
|
|
629
|
+
const syncAndRestoreClaudeMd = async (agentId: string): Promise<void> => {
|
|
630
|
+
await stopClaudeMd(async (body) => {
|
|
631
|
+
if (!mcpConfig) return;
|
|
632
|
+
await postHookProfileUpdate({
|
|
633
|
+
url: `${getBaseUrl()}/api/agents/${agentId}/profile`,
|
|
634
|
+
headers: mcpConfig.headers,
|
|
635
|
+
body,
|
|
636
|
+
label: "claudeMd",
|
|
637
|
+
fetchImpl: fetchWithSyncTimeout(),
|
|
638
|
+
});
|
|
682
639
|
});
|
|
683
640
|
};
|
|
684
641
|
|
|
@@ -1055,9 +1012,12 @@ export async function handleHook(): Promise<void> {
|
|
|
1055
1012
|
// Write agent's CLAUDE.md if available
|
|
1056
1013
|
if (agentInfo.claudeMd) {
|
|
1057
1014
|
try {
|
|
1058
|
-
await
|
|
1059
|
-
|
|
1060
|
-
|
|
1015
|
+
const outcome = await materializeClaudeMd(agentInfo.claudeMd);
|
|
1016
|
+
console.log(
|
|
1017
|
+
outcome === "busy" || outcome === "error"
|
|
1018
|
+
? "CLAUDE.md is being updated by another session; keeping the current copy."
|
|
1019
|
+
: "Loaded your personal CLAUDE.md configuration.",
|
|
1020
|
+
);
|
|
1061
1021
|
} catch (error) {
|
|
1062
1022
|
console.log(`Warning: Could not load CLAUDE.md: ${(error as Error).message}`);
|
|
1063
1023
|
}
|
|
@@ -1334,13 +1294,12 @@ export async function handleHook(): Promise<void> {
|
|
|
1334
1294
|
// PM2 not available or no processes - silently ignore
|
|
1335
1295
|
}
|
|
1336
1296
|
|
|
1337
|
-
// Sync CLAUDE.md, identity files, and setup script back to database
|
|
1297
|
+
// Sync CLAUDE.md (then restore its backup), identity files, and setup script back to database
|
|
1338
1298
|
if (agentInfo?.id) {
|
|
1339
1299
|
try {
|
|
1340
|
-
await
|
|
1300
|
+
await syncAndRestoreClaudeMd(agentInfo.id);
|
|
1341
1301
|
await syncIdentityFilesToServer(agentInfo.id);
|
|
1342
1302
|
await syncSetupScriptToServer(agentInfo.id);
|
|
1343
|
-
await restoreClaudeMdBackup();
|
|
1344
1303
|
} catch {
|
|
1345
1304
|
// Silently fail - don't block shutdown
|
|
1346
1305
|
}
|
package/src/http/agents.ts
CHANGED
|
@@ -48,10 +48,12 @@ import {
|
|
|
48
48
|
AgentSchema,
|
|
49
49
|
AgentStatusSchema,
|
|
50
50
|
AgentWithTasksSchema,
|
|
51
|
+
type ProfileSyncConflict,
|
|
51
52
|
type ProviderName,
|
|
52
53
|
ProviderNameSchema,
|
|
53
54
|
ReasoningEffortSchema,
|
|
54
55
|
RuntimeInstanceSchema,
|
|
56
|
+
VersionableFieldSchema,
|
|
55
57
|
} from "../types";
|
|
56
58
|
import {
|
|
57
59
|
type ClaudeTransport,
|
|
@@ -59,6 +61,7 @@ import {
|
|
|
59
61
|
resolveClaudeTransport,
|
|
60
62
|
} from "../utils/claude-transport";
|
|
61
63
|
import { MAX_PROFILE_FILE_LENGTH } from "../utils/constants";
|
|
64
|
+
import { HeartbeatExpiryError } from "../utils/heartbeat-expiry";
|
|
62
65
|
import {
|
|
63
66
|
type BudgetedIdentityField,
|
|
64
67
|
IDENTITY_FIELD_BUDGETS,
|
|
@@ -411,6 +414,8 @@ const listAgentRuntimeInstances = route({
|
|
|
411
414
|
},
|
|
412
415
|
});
|
|
413
416
|
|
|
417
|
+
const ContentHashSchema = z.string().regex(/^[0-9a-f]{64}$/, "sha256 hex");
|
|
418
|
+
|
|
414
419
|
const ProfileSyncRejectionSchema = z.object({
|
|
415
420
|
field: z.enum(["soulMd", "identityMd", "claudeMd", "toolsMd"]),
|
|
416
421
|
diskSize: z.number().int(),
|
|
@@ -442,6 +447,12 @@ const updateAgentProfileRoute = route({
|
|
|
442
447
|
changeSource: z.string().optional(),
|
|
443
448
|
changedByAgentId: z.string().optional(),
|
|
444
449
|
changeReason: z.string().optional(),
|
|
450
|
+
/**
|
|
451
|
+
* Compare-and-set token per field: the sha256 (hex) of the content the edit
|
|
452
|
+
* was based on. A field whose current value hashes differently is dropped
|
|
453
|
+
* (the DB moved since), without failing the rest of the update.
|
|
454
|
+
*/
|
|
455
|
+
expectedHashes: z.partialRecord(VersionableFieldSchema, ContentHashSchema).optional(),
|
|
445
456
|
}),
|
|
446
457
|
responses: {
|
|
447
458
|
200: { description: "Profile updated", schema: AgentWithCapacitySchema },
|
|
@@ -840,6 +851,7 @@ export async function handleAgentsRest(
|
|
|
840
851
|
}
|
|
841
852
|
: undefined;
|
|
842
853
|
|
|
854
|
+
const conflicts: ProfileSyncConflict[] = [];
|
|
843
855
|
let agent: Agent | null;
|
|
844
856
|
try {
|
|
845
857
|
agent = await updateAgentProfile(
|
|
@@ -859,8 +871,16 @@ export async function handleAgentsRest(
|
|
|
859
871
|
...(body.avatar !== undefined ? { avatar: body.avatar } : {}),
|
|
860
872
|
},
|
|
861
873
|
versionMeta,
|
|
874
|
+
{
|
|
875
|
+
expectedHashes: body.expectedHashes,
|
|
876
|
+
onConflict: (conflict) => conflicts.push(conflict),
|
|
877
|
+
},
|
|
862
878
|
);
|
|
863
879
|
} catch (error) {
|
|
880
|
+
if (error instanceof HeartbeatExpiryError) {
|
|
881
|
+
jsonError(res, error.message, 400);
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
864
884
|
if (error instanceof IdentityFieldBudgetError) {
|
|
865
885
|
if (
|
|
866
886
|
versionMeta?.changeSource === "self_edit" ||
|
|
@@ -901,10 +921,29 @@ export async function handleAgentsRest(
|
|
|
901
921
|
return true;
|
|
902
922
|
}
|
|
903
923
|
|
|
924
|
+
// A dropped field still answers 200 (the rest of the update landed), so the
|
|
925
|
+
// drop must be visible somewhere: one event per field, queryable later.
|
|
926
|
+
for (const conflict of conflicts) {
|
|
927
|
+
try {
|
|
928
|
+
await createEvent({
|
|
929
|
+
category: "system",
|
|
930
|
+
event: "system.profile_sync_conflict",
|
|
931
|
+
status: "skipped",
|
|
932
|
+
source: "api",
|
|
933
|
+
agentId: parsed.params.id,
|
|
934
|
+
data: { ...conflict, changeSource: versionMeta?.changeSource ?? null },
|
|
935
|
+
});
|
|
936
|
+
} catch (eventError) {
|
|
937
|
+
const message = eventError instanceof Error ? eventError.message : String(eventError);
|
|
938
|
+
console.error(scrubSecrets(`[profile-sync] Failed to persist conflict event: ${message}`));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
904
942
|
if (versionMeta?.changeSource === "self_edit" || versionMeta?.changeSource === "session_sync") {
|
|
905
943
|
try {
|
|
906
944
|
for (const field of Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]) {
|
|
907
|
-
|
|
945
|
+
// A field dropped by the compare-and-set was not written: nothing reconciled.
|
|
946
|
+
if (body[field] === undefined || conflicts.some((c) => c.field === field)) continue;
|
|
908
947
|
await createEvent({
|
|
909
948
|
category: "system",
|
|
910
949
|
event: "system.profile_sync_reconciled",
|
package/src/http/auth.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
2
|
import { getPage } from "../be/db";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
findUserById,
|
|
5
|
+
fingerprintApiKey,
|
|
6
|
+
resolveBySessionToken,
|
|
7
|
+
resolveUserByToken,
|
|
8
|
+
} from "../be/users";
|
|
4
9
|
import type { User } from "../types";
|
|
5
10
|
import { verifyPageSession } from "../utils/page-session";
|
|
6
11
|
import type { HttpRequestAuth } from "../utils/request-auth-context";
|
|
@@ -52,6 +57,13 @@ export async function resolveHttpRequestAuth(
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
if (bearer.startsWith("aseph_")) {
|
|
61
|
+
const principal = await resolveBySessionToken(bearer);
|
|
62
|
+
if (principal) {
|
|
63
|
+
return { kind: "agent", agentId: principal.agentId, taskId: principal.taskId };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
return null;
|
|
56
68
|
}
|
|
57
69
|
|
package/src/http/fs.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
2
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
deleteTaskAttachment,
|
|
6
|
-
getAgentById,
|
|
7
|
-
getTaskAttachments,
|
|
8
|
-
getTaskById,
|
|
9
|
-
insertTaskAttachment,
|
|
10
|
-
} from "../be/db";
|
|
3
|
+
import { deleteTaskAttachment, getAgentById, getTaskAttachments, getTaskById } from "../be/db";
|
|
11
4
|
import {
|
|
12
5
|
ensureAgentFsCredentialsForAgent,
|
|
13
6
|
inviteEmailToSharedOrg,
|
|
14
7
|
} from "../be/seed/agent-fs-provision";
|
|
8
|
+
import { MAX_TASK_ATTACHMENT_BYTES, recordTaskAttachmentUpload } from "../be/task-attachment-store";
|
|
15
9
|
import { type FileObject, type FileScope, FilesError, normalizeFilesError } from "../fs/provider";
|
|
16
10
|
import { getFileStorageProvider } from "../fs/registry";
|
|
17
11
|
import { can, type RbacPrincipal, type RbacResource } from "../rbac";
|
|
@@ -22,8 +16,6 @@ import { scrubSecrets } from "../utils/secret-scrubber";
|
|
|
22
16
|
import { route } from "./route-def";
|
|
23
17
|
import { BODY_TOO_LARGE, enforceContentLengthCap, jsonError } from "./utils";
|
|
24
18
|
|
|
25
|
-
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
26
|
-
|
|
27
19
|
// Upload wall-clock past which the provider round-trip is worth a log line.
|
|
28
20
|
// Attachment stalls were invisible before: this path had no timing at all.
|
|
29
21
|
const SLOW_UPLOAD_LOG_MS = 3_000;
|
|
@@ -306,7 +298,8 @@ export async function handleFs(
|
|
|
306
298
|
}
|
|
307
299
|
|
|
308
300
|
if (uploadTaskFileRoute.match(req.method, pathSegments)) {
|
|
309
|
-
if (enforceContentLengthCap(req, res,
|
|
301
|
+
if (enforceContentLengthCap(req, res, MAX_TASK_ATTACHMENT_BYTES) === BODY_TOO_LARGE)
|
|
302
|
+
return true;
|
|
310
303
|
const parsed = await uploadTaskFileRoute.parse(req, res, pathSegments, queryParams);
|
|
311
304
|
if (!parsed) return true;
|
|
312
305
|
const task = await getTaskById(parsed.params.taskId);
|
|
@@ -344,9 +337,9 @@ async function sendUpload(
|
|
|
344
337
|
query: z.infer<typeof uploadQuery>,
|
|
345
338
|
agentId: string | null,
|
|
346
339
|
): Promise<boolean> {
|
|
347
|
-
const body = await readRawBody(req,
|
|
340
|
+
const body = await readRawBody(req, MAX_TASK_ATTACHMENT_BYTES);
|
|
348
341
|
if (body === BODY_TOO_LARGE) {
|
|
349
|
-
jsonError(res, `Payload too large (max ${
|
|
342
|
+
jsonError(res, `Payload too large (max ${MAX_TASK_ATTACHMENT_BYTES} bytes)`, 413);
|
|
350
343
|
return true;
|
|
351
344
|
}
|
|
352
345
|
|
|
@@ -380,44 +373,21 @@ async function sendUpload(
|
|
|
380
373
|
return sendProviderError(res, error);
|
|
381
374
|
}
|
|
382
375
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
mimeType: uploaded.contentType ?? contentType,
|
|
399
|
-
sizeBytes: uploaded.sizeBytes ?? body.byteLength,
|
|
400
|
-
sha256: uploaded.sha256 ?? createHash("sha256").update(body).digest("hex"),
|
|
401
|
-
intent: query.intent,
|
|
402
|
-
description: query.description,
|
|
403
|
-
isPrimary: query.isPrimary === "true",
|
|
404
|
-
createdBy: auth?.kind === "user" ? auth.userId : undefined,
|
|
405
|
-
});
|
|
406
|
-
uploadTaskFileRoute.respond(res, 201, attachment);
|
|
407
|
-
} catch (error) {
|
|
408
|
-
try {
|
|
409
|
-
await provider.delete(scope);
|
|
410
|
-
} catch (cleanupError) {
|
|
411
|
-
console.warn(
|
|
412
|
-
scrubSecrets(
|
|
413
|
-
`[fs] upload metadata insert failed and blob cleanup failed: ${
|
|
414
|
-
cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
415
|
-
}`,
|
|
416
|
-
),
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
throw error;
|
|
420
|
-
}
|
|
376
|
+
const auth = getCurrentRequestAuth();
|
|
377
|
+
const attachment = await recordTaskAttachmentUpload({
|
|
378
|
+
provider,
|
|
379
|
+
scope,
|
|
380
|
+
uploaded,
|
|
381
|
+
sizeBytes: body.byteLength,
|
|
382
|
+
sha256: uploaded.sha256 ?? new Bun.CryptoHasher("sha256").update(body).digest("hex"),
|
|
383
|
+
contentType,
|
|
384
|
+
agentId,
|
|
385
|
+
intent: query.intent,
|
|
386
|
+
description: query.description,
|
|
387
|
+
isPrimary: query.isPrimary === "true",
|
|
388
|
+
createdBy: auth?.kind === "user" ? auth.userId : undefined,
|
|
389
|
+
});
|
|
390
|
+
uploadTaskFileRoute.respond(res, 201, attachment);
|
|
421
391
|
return true;
|
|
422
392
|
}
|
|
423
393
|
|
package/src/http/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ import {
|
|
|
118
118
|
parseQueryParams,
|
|
119
119
|
safeRequestUrlForLog,
|
|
120
120
|
setCorsHeaders,
|
|
121
|
+
warnIfCorsAllowsAnyOrigin,
|
|
121
122
|
wireHttpSpanLifecycle,
|
|
122
123
|
} from "./utils";
|
|
123
124
|
import { handleWebhooks } from "./webhooks";
|
|
@@ -556,6 +557,7 @@ try {
|
|
|
556
557
|
process.exitCode = 1;
|
|
557
558
|
throw err;
|
|
558
559
|
}
|
|
560
|
+
warnIfCorsAllowsAnyOrigin();
|
|
559
561
|
|
|
560
562
|
// Upgrade seed: explicit CAPABILITIES env values that predate capability
|
|
561
563
|
// gating get the previously always-registered groups backfilled into a
|
package/src/http/mcp.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
4
4
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { getAgentById, getResolvedConfig } from "@/be/db";
|
|
6
6
|
import { createServer } from "@/server";
|
|
7
|
+
import { getRequestAuth } from "@/utils/request-auth-context";
|
|
7
8
|
import { resolveScriptsOnlyMode } from "@/utils/scripts-only-mode";
|
|
8
9
|
|
|
9
10
|
export type McpTransportActivity = Record<string, number>;
|
|
@@ -64,6 +65,34 @@ function unauthorized(res: ServerResponse, message = "Unauthorized"): true {
|
|
|
64
65
|
return true;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function forbidden(res: ServerResponse, message: string): true {
|
|
69
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
70
|
+
res.end(JSON.stringify({ error: message }));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* When a request is authenticated via an aseph_ session token (kind: "agent"),
|
|
76
|
+
* the X-Agent-ID and X-Source-Task-Id headers MUST match the token's bound
|
|
77
|
+
* agentId and taskId. A valid token cannot be used on behalf of a different
|
|
78
|
+
* agent or task.
|
|
79
|
+
*/
|
|
80
|
+
function validateAgentTokenIdentity(req: IncomingMessage, res: ServerResponse): true | undefined {
|
|
81
|
+
const auth = getRequestAuth(req);
|
|
82
|
+
if (!auth || auth.kind !== "agent") return undefined;
|
|
83
|
+
|
|
84
|
+
const agentId = headerValue(req.headers["x-agent-id"]);
|
|
85
|
+
const taskId = headerValue(req.headers["x-source-task-id"]);
|
|
86
|
+
|
|
87
|
+
if (!agentId || agentId !== auth.agentId) {
|
|
88
|
+
return forbidden(res, "X-Agent-ID does not match session token");
|
|
89
|
+
}
|
|
90
|
+
if (!taskId || taskId !== auth.taskId) {
|
|
91
|
+
return forbidden(res, "X-Source-Task-Id does not match session token");
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
67
96
|
function headerValue(value: string | string[] | undefined): string | undefined {
|
|
68
97
|
return Array.isArray(value) ? value[0] : value;
|
|
69
98
|
}
|
|
@@ -111,6 +140,9 @@ export async function handleMcp(
|
|
|
111
140
|
return false;
|
|
112
141
|
}
|
|
113
142
|
|
|
143
|
+
const tokenIdentityMismatch = validateAgentTokenIdentity(req, res);
|
|
144
|
+
if (tokenIdentityMismatch) return true;
|
|
145
|
+
|
|
114
146
|
const agentMismatch = validateBoundAgent(req, res, sessionId, sessionAgents);
|
|
115
147
|
if (agentMismatch) return true;
|
|
116
148
|
|
package/src/http/pages-public.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Scope of THIS module (step-3):
|
|
9
9
|
* - `auth_mode === 'public'`: ungated. HTML responses inline-inject the
|
|
10
10
|
* `BROWSER_SDK_JS` constant from `src/artifact-sdk/browser-sdk.ts` (reused
|
|
11
|
-
* verbatim — no token-injection hook on the client).
|
|
11
|
+
* verbatim — no token-injection hook on the client). SVG responses serve
|
|
12
|
+
* raw bytes with a restrictive CSP and no injected scripts. JSON responses
|
|
12
13
|
* 302-redirect to the SPA `/pages/:id` route (the JSON renderer lives
|
|
13
14
|
* in the SPA, not the API — step-6/7).
|
|
14
15
|
* - `auth_mode === 'authed'`: returns 401. step-4 narrows this to also
|
|
@@ -52,14 +53,14 @@ const publicPageRoute = route({
|
|
|
52
53
|
method: "get",
|
|
53
54
|
path: "/p/{id}",
|
|
54
55
|
pattern: ["p", null],
|
|
55
|
-
summary: "Render a page (HTML inline; JSON redirects to SPA)",
|
|
56
|
+
summary: "Render a page (HTML/SVG inline; JSON redirects to SPA)",
|
|
56
57
|
tags: ["Pages"],
|
|
57
58
|
params: z.object({ id: z.string() }),
|
|
58
59
|
responses: {
|
|
59
60
|
200: {
|
|
60
|
-
description: "Rendered HTML page",
|
|
61
|
+
description: "Rendered HTML page or raw SVG image",
|
|
61
62
|
unstructured:
|
|
62
|
-
"Serves the page's
|
|
63
|
+
"Serves raw image/svg+xml bytes, the page's agent-authored HTML body (SDK-injected) or, for ?print=1 on a JSON-content page, a standalone printable HTML document — not a JSON shape",
|
|
63
64
|
},
|
|
64
65
|
302: { description: "Redirect to SPA for JSON content" },
|
|
65
66
|
401: { description: "Page requires an authenticated session" },
|
|
@@ -593,6 +594,21 @@ export async function handlePagesPublic(
|
|
|
593
594
|
return true;
|
|
594
595
|
}
|
|
595
596
|
|
|
597
|
+
// SVG stays byte-for-byte intact, including when ?print=1 is supplied.
|
|
598
|
+
if (page.contentType === "image/svg+xml") {
|
|
599
|
+
const headers: Record<string, string> = {
|
|
600
|
+
"Content-Type": "image/svg+xml",
|
|
601
|
+
"Cache-Control": page.authMode === "public" ? "public, max-age=1800" : "private, no-store",
|
|
602
|
+
"X-Content-Type-Options": "nosniff",
|
|
603
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
604
|
+
};
|
|
605
|
+
if (inlineSetCookie) headers["Set-Cookie"] = inlineSetCookie;
|
|
606
|
+
res.writeHead(200, headers);
|
|
607
|
+
res.end(page.body);
|
|
608
|
+
await bumpViewCount(page.id);
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
|
|
596
612
|
// `?print=1` — the SPA's "Export PDF" button opens the page in a new tab
|
|
597
613
|
// with this flag so the page self-prints in the user's own browser (no
|
|
598
614
|
// server-side headless browser). HTML pages get the auto-print snippet
|
package/src/http/pages.ts
CHANGED
|
@@ -33,7 +33,12 @@ import { issuePageSessionCookie } from "../utils/page-session";
|
|
|
33
33
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
34
34
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
35
35
|
import { route } from "./route-def";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
BODY_TOO_LARGE,
|
|
38
|
+
enforceContentLengthCap,
|
|
39
|
+
isOriginAllowedForCredentials,
|
|
40
|
+
jsonError,
|
|
41
|
+
} from "./utils";
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* Per-page body-size cap. Page bodies are stored as a TEXT column with no
|
|
@@ -370,9 +375,12 @@ const getPageVersionRoute = route({
|
|
|
370
375
|
function applyLaunchCors(req: IncomingMessage, res: ServerResponse): void {
|
|
371
376
|
const origin = (req.headers.origin as string | undefined) ?? "";
|
|
372
377
|
if (origin) {
|
|
373
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
374
378
|
res.setHeader("Vary", "Origin");
|
|
375
|
-
|
|
379
|
+
// Cookie issuance always requires the hosted/dev or configured allowlist.
|
|
380
|
+
if (isOriginAllowedForCredentials(origin)) {
|
|
381
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
382
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
383
|
+
}
|
|
376
384
|
}
|
|
377
385
|
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
378
386
|
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
|
package/src/http/poll.ts
CHANGED
|
@@ -94,11 +94,14 @@ const PollTriggerAttachmentSchema = TaskAttachmentSchema.pick({
|
|
|
94
94
|
// user's identity fields, or (when no `requestedByUserId` is recorded) just a
|
|
95
95
|
// rendered `name` for the UNKNOWN-identity sentinel.
|
|
96
96
|
const PollRequestedBySchema = UserSchema.pick({
|
|
97
|
+
id: true,
|
|
97
98
|
name: true,
|
|
98
99
|
email: true,
|
|
99
100
|
role: true,
|
|
100
101
|
notes: true,
|
|
101
102
|
}).extend({
|
|
103
|
+
// Absent for the UNKNOWN-identity sentinel (Slack-only requester, no users row).
|
|
104
|
+
id: z.string().optional(),
|
|
102
105
|
// Structured communication preferences from `users.metadata.comms`.
|
|
103
106
|
comms: UserCommsPrefsSchema.optional(),
|
|
104
107
|
});
|
|
@@ -195,6 +198,7 @@ async function buildTriggerRequestedBy(task: {
|
|
|
195
198
|
const user = task.requestedByUserId ? await getUserById(task.requestedByUserId) : undefined;
|
|
196
199
|
if (user) {
|
|
197
200
|
return {
|
|
201
|
+
id: user.id,
|
|
198
202
|
name: user.name,
|
|
199
203
|
email: user.email,
|
|
200
204
|
role: user.role,
|
package/src/http/repos.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
updateSwarmRepo,
|
|
9
9
|
} from "../be/db";
|
|
10
10
|
import { emitIntegrationConnected } from "../telemetry";
|
|
11
|
-
import {
|
|
11
|
+
import { RepoGuidelinesInputSchema, RepoHooksSchema, SwarmRepoSchema } from "../types";
|
|
12
12
|
import { route } from "./route-def";
|
|
13
13
|
import { json, jsonError } from "./utils";
|
|
14
14
|
|
|
@@ -58,7 +58,7 @@ const createRepo = route({
|
|
|
58
58
|
defaultBranch: z.string().optional(),
|
|
59
59
|
autoClone: z.boolean().optional(),
|
|
60
60
|
hooks: RepoHooksSchema.optional(),
|
|
61
|
-
guidelines:
|
|
61
|
+
guidelines: RepoGuidelinesInputSchema.nullable().optional(),
|
|
62
62
|
}),
|
|
63
63
|
responses: {
|
|
64
64
|
201: { description: "Repo created", schema: SwarmRepoSchema },
|
|
@@ -81,7 +81,7 @@ const updateRepo = route({
|
|
|
81
81
|
defaultBranch: z.string().optional(),
|
|
82
82
|
autoClone: z.boolean().optional(),
|
|
83
83
|
hooks: RepoHooksSchema.nullable().optional(),
|
|
84
|
-
guidelines:
|
|
84
|
+
guidelines: RepoGuidelinesInputSchema.nullable().optional(),
|
|
85
85
|
}),
|
|
86
86
|
responses: {
|
|
87
87
|
200: { description: "Repo updated", schema: SwarmRepoSchema },
|