@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.
Files changed (221) hide show
  1. package/README.md +2 -1
  2. package/dist/{acp-adapter-580t41c0.js → acp-adapter-9n319wqc.js} +61 -6
  3. package/dist/{actions-m662tjng.js → actions-2vxqvpr9.js} +9 -8
  4. package/dist/{app-zb3rndm8.js → app-w066xfy0.js} +6 -5
  5. package/dist/assistant-kt31dj2h.js +236 -0
  6. package/dist/{boot-reembed-072x00vn.js → boot-reembed-az5rassp.js} +7 -6
  7. package/dist/{boot-reembed-65zz2smt.js → boot-reembed-j3mm3rfz.js} +8 -7
  8. package/dist/{boot-scrub-logs-yz0ams04.js → boot-scrub-logs-b6h54817.js} +4 -4
  9. package/dist/{claude-adapter-paj270y2.js → claude-adapter-w1z04ann.js} +8 -6
  10. package/dist/{claude-managed-setup-w619jncn.js → claude-managed-setup-f5pmsyrp.js} +24 -3
  11. package/dist/{claude-sdk-session-swt80j9z.js → claude-sdk-session-f0cc1kec.js} +8 -6
  12. package/dist/{cli-dt553ktb.js → cli-0b6y7kxm.js} +1 -1
  13. package/dist/{cli-bxdhw615.js → cli-12fz972k.js} +4 -4
  14. package/dist/{cli-s2f20r5w.js → cli-1th1728g.js} +2 -2
  15. package/dist/{cli-rs6ec3b9.js → cli-3tgwnf43.js} +3 -3
  16. package/dist/{cli-gxnzc6wh.js → cli-4nr988h6.js} +1 -1
  17. package/dist/{cli-axs8e6d4.js → cli-53s590z8.js} +1 -1
  18. package/dist/{cli-ygbjp3z8.js → cli-54wwve05.js} +5 -4
  19. package/dist/{cli-v0asr5yv.js → cli-719k9j2c.js} +2 -2
  20. package/dist/{cli-anktweqr.js → cli-89xfd7f9.js} +21 -47
  21. package/dist/{cli-q34a0e10.js → cli-8v0g1yc8.js} +1 -1
  22. package/dist/cli-9d8ntetj.js +42 -0
  23. package/dist/{cli-z9fprcrf.js → cli-atcve9y8.js} +881 -738
  24. package/dist/{cli-jmb7g47h.js → cli-b5bq9crk.js} +14 -31
  25. package/dist/cli-d8brzxjb.js +190 -0
  26. package/dist/{cli-gzky0gkr.js → cli-de14znh8.js} +2 -2
  27. package/dist/{cli-xytwff7f.js → cli-dttph1bw.js} +2 -2
  28. package/dist/cli-ep19k3mb.js +19 -0
  29. package/dist/{cli-m1xa8be7.js → cli-f146mn5s.js} +7 -50
  30. package/dist/{cli-1fj0chdk.js → cli-f7dsy61y.js} +5 -3
  31. package/dist/{cli-f567d5c1.js → cli-ftht3zzj.js} +2782 -2589
  32. package/dist/{cli-a0g1gbt5.js → cli-hbv0kq6w.js} +2 -2
  33. package/dist/{cli-fhsz3twr.js → cli-jsp14b6c.js} +171 -1
  34. package/dist/{cli-7fnkj1hj.js → cli-kpv03zhj.js} +21 -17
  35. package/dist/{cli-m4j6trn4.js → cli-m8yrsg97.js} +2 -2
  36. package/dist/cli-m9sxbkhm.js +779 -0
  37. package/dist/cli-mmemxxdc.js +823 -0
  38. package/dist/{cli-tqtvj5pt.js → cli-n8508nre.js} +1 -1
  39. package/dist/{cli-z96t0thj.js → cli-p1d5b073.js} +37 -2
  40. package/dist/{cli-gh8y8f36.js → cli-rw51bq3j.js} +13 -10
  41. package/dist/{cli-59ej1c07.js → cli-smby5m17.js} +7 -6
  42. package/dist/{cli-65vqzz3v.js → cli-tzvk9haz.js} +94 -372
  43. package/dist/{cli-dc9k2ba9.js → cli-w9kb3bk1.js} +1 -1
  44. package/dist/{cli-kpev657q.js → cli-y6b6fb76.js} +25 -3
  45. package/dist/{cli-123q2get.js → cli-yz3djrm0.js} +9 -9
  46. package/dist/{cli-wvp6bvy4.js → cli-zhjmqxzh.js} +1 -1
  47. package/dist/cli.js +13 -13
  48. package/dist/{codex-adapter-k98vaw2r.js → codex-adapter-zw4dwsx7.js} +2 -2
  49. package/dist/{codex-hook-9pf19cks.js → codex-hook-bw8p5nh4.js} +2 -2
  50. package/dist/{codex-session-runner-tngtphx7.js → codex-session-runner-k981s65v.js} +2 -2
  51. package/dist/{commands-y8nbr3fv.js → commands-sykrxx7e.js} +4 -4
  52. package/dist/{db-30p4h2t1.js → db-sfbsc8wt.js} +6 -4
  53. package/dist/handlers-0aspcejm.js +603 -0
  54. package/dist/{hook-dcw4jfbj.js → hook-4nvqj2sb.js} +7 -5
  55. package/dist/{hook-21jexvfx.js → hook-hxccs7m5.js} +24 -49
  56. package/dist/{http-r4kq5g8d.js → http-y06z517d.js} +230 -94
  57. package/dist/{index-q3qja6ta.js → index-hssww7kj.js} +15 -14
  58. package/dist/{index-gay66b9w.js → index-k8znfx0z.js} +13 -12
  59. package/dist/{index-53zyht4z.js → index-tce1rss4.js} +371 -110
  60. package/dist/{index-nqvf26za.js → index-x5j894zf.js} +14 -13
  61. package/dist/{keepalive-xs8fa55y.js → keepalive-w733ax66.js} +6 -6
  62. package/dist/{lead-d9m2t4s0.js → lead-75bjqa9d.js} +29 -27
  63. package/dist/{maintenance-3zh82g6z.js → maintenance-w4f1zjck.js} +8 -7
  64. package/dist/{oauth-refresh-sweep-03snsye5.js → oauth-refresh-sweep-wdetky26.js} +5 -5
  65. package/dist/{onboard-0cg1925x.js → onboard-bb5net8s.js} +2 -2
  66. package/dist/{otel-impl-0ab95z27.js → otel-impl-0w7c14ap.js} +1 -1
  67. package/dist/{pi-mono-adapter-dtkz4e7j.js → pi-mono-adapter-2has4gnp.js} +12 -2
  68. package/dist/{pricing-refresh-e5jhzz4j.js → pricing-refresh-9nzsrgcq.js} +6 -6
  69. package/dist/{rbac-roles-p1mf40z3.js → rbac-roles-r3hqjp09.js} +5 -5
  70. package/dist/{rbac-roles-2w3cmrkc.js → rbac-roles-z1jrffbt.js} +4 -4
  71. package/dist/{registry-9kdck5ye.js → registry-v5wytg0s.js} +1 -2
  72. package/dist/{render-v2-xhawry3k.js → render-v2-vbr89fnb.js} +6 -5
  73. package/dist/{seed-pricing-t223sw91.js → seed-pricing-g62hy1pk.js} +7 -5
  74. package/dist/{setup-zcs0rce7.js → setup-gnfqnptp.js} +2 -2
  75. package/dist/{worker-qnp2ka62.js → worker-kznxp4qm.js} +29 -27
  76. package/openapi.json +263 -12
  77. package/package.json +1 -1
  78. package/plugin/commands/start-worker.md +4 -9
  79. package/plugin/pi-skills/start-worker/SKILL.md +4 -9
  80. package/src/be/db/agents.ts +556 -0
  81. package/src/be/db/context-versions.ts +117 -0
  82. package/src/be/db/runtime.ts +406 -0
  83. package/src/be/db/tasks/read.ts +1167 -0
  84. package/src/be/db/tasks/write.ts +953 -0
  85. package/src/be/db.ts +584 -3429
  86. package/src/be/migrations/147_scheduled_tasks_parent_task.sql +7 -0
  87. package/src/be/migrations/148_pages_svg.sql +123 -0
  88. package/src/be/migrations/149_session_tokens.sql +24 -0
  89. package/src/be/scripts/typecheck.ts +1 -0
  90. package/src/be/seed-pricing.ts +13 -8
  91. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  92. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  93. package/src/be/seed-scripts/catalog/star-history-refresh.ts +101 -0
  94. package/src/be/seed-scripts/catalog/star-history-renderer.ts +119 -0
  95. package/src/be/seed-scripts/index.ts +17 -0
  96. package/src/be/seed-skills/index.ts +14 -0
  97. package/src/be/swarm-config-guard.ts +5 -2
  98. package/src/be/task-attachment-store.ts +70 -0
  99. package/src/be/users.ts +77 -0
  100. package/src/commands/attachments-section.ts +37 -0
  101. package/src/commands/claude-managed-setup.ts +45 -3
  102. package/src/commands/claude-md-session.ts +408 -0
  103. package/src/commands/context-preamble.ts +15 -3
  104. package/src/commands/profile-sync.ts +205 -5
  105. package/src/commands/provider-credentials.ts +60 -27
  106. package/src/commands/runner.ts +63 -58
  107. package/src/commands/templates.ts +25 -2
  108. package/src/hooks/hook.ts +26 -67
  109. package/src/http/agents.ts +40 -1
  110. package/src/http/auth.ts +13 -1
  111. package/src/http/fs.ts +21 -51
  112. package/src/http/index.ts +2 -0
  113. package/src/http/mcp.ts +32 -0
  114. package/src/http/pages-public.ts +20 -4
  115. package/src/http/pages.ts +11 -3
  116. package/src/http/poll.ts +4 -0
  117. package/src/http/repos.ts +3 -3
  118. package/src/http/sessions.ts +82 -0
  119. package/src/http/tasks.ts +10 -2
  120. package/src/http/utils.ts +79 -1
  121. package/src/prompts/session-templates.ts +12 -8
  122. package/src/providers/acp-adapter.ts +62 -8
  123. package/src/providers/pi-mono-adapter.ts +23 -6
  124. package/src/providers/pricing-sources.md +11 -0
  125. package/src/scheduler/scheduler.test.ts +49 -2
  126. package/src/scheduler/scheduler.ts +4 -0
  127. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  128. package/src/scripts-runtime/types/stdlib.d.ts +8 -0
  129. package/src/scripts-runtime/types/swarm-sdk.d.ts +8 -0
  130. package/src/server.ts +2 -0
  131. package/src/slack/assistant.ts +87 -46
  132. package/src/slack/handlers.test.ts +1 -3
  133. package/src/slack/handlers.ts +102 -87
  134. package/src/slack/inbound-files.ts +466 -0
  135. package/src/slack/templates.ts +14 -0
  136. package/src/tasks/task-terminal-effects.ts +158 -0
  137. package/src/tests/acp-adapter.test.ts +369 -117
  138. package/src/tests/budget-claim-gate.test.ts +1 -0
  139. package/src/tests/build-pi-skills.test.ts +5 -7
  140. package/src/tests/claude-managed-setup.test.ts +52 -4
  141. package/src/tests/claude-md-session.test.ts +603 -0
  142. package/src/tests/codex-skill-resolver.test.ts +19 -0
  143. package/src/tests/context-versioning.test.ts +107 -0
  144. package/src/tests/cors-allowlist.test.ts +309 -0
  145. package/src/tests/credential-check.test.ts +157 -14
  146. package/src/tests/db-agents.test.ts +91 -0
  147. package/src/tests/db-context-versions.test.ts +37 -0
  148. package/src/tests/db-extraction-contract.test.ts +45 -0
  149. package/src/tests/db-runtime.test.ts +70 -0
  150. package/src/tests/db-task-read-contract.test.ts +624 -0
  151. package/src/tests/db-task-write-contract.test.ts +653 -0
  152. package/src/tests/db-tasks-read.test.ts +183 -0
  153. package/src/tests/db-tasks-write.test.ts +230 -0
  154. package/src/tests/defer-task.test.ts +389 -0
  155. package/src/tests/docs-index-footprint.test.js +2 -2
  156. package/src/tests/e2e-visuals-stage.test.ts +151 -0
  157. package/src/tests/file-lock.test.ts +144 -0
  158. package/src/tests/fixtures/file-lock-holder.ts +13 -0
  159. package/src/tests/fixtures/hold-file-lock.ts +25 -0
  160. package/src/tests/heartbeat-expiry.test.ts +188 -0
  161. package/src/tests/http-api-integration.test.ts +78 -1
  162. package/src/tests/pages-public-html.test.ts +43 -0
  163. package/src/tests/pages-svg-migration.test.ts +42 -0
  164. package/src/tests/preload.ts +4 -1
  165. package/src/tests/profile-sync-warning.test.ts +16 -0
  166. package/src/tests/profile-sync.test.ts +126 -0
  167. package/src/tests/prompt-template-session.test.ts +20 -4
  168. package/src/tests/provider-registration.test.ts +93 -0
  169. package/src/tests/rbac-e2e-helpers.test.ts +53 -0
  170. package/src/tests/rbac-e2e-helpers.ts +19 -2
  171. package/src/tests/reload-config.test.ts +26 -0
  172. package/src/tests/routing-affinity-inventory.test.ts +1 -1
  173. package/src/tests/runner-attachments-section.test.ts +22 -2
  174. package/src/tests/runner-context-preamble.test.ts +30 -0
  175. package/src/tests/runner-deployment-resume.test.ts +107 -0
  176. package/src/tests/runner-repo-autostash.test.ts +12 -0
  177. package/src/tests/script-connections.test.ts +4 -0
  178. package/src/tests/seed-scripts.test.ts +2 -2
  179. package/src/tests/session-tokens.test.ts +279 -0
  180. package/src/tests/slack-file-tools-attachments.test.ts +408 -0
  181. package/src/tests/slack-inbound-files.test.ts +847 -0
  182. package/src/tests/star-history-refresh.test.ts +102 -0
  183. package/src/tests/swarm-config-reserved-keys.test.ts +21 -0
  184. package/src/tests/swarm-repos.test.ts +20 -0
  185. package/src/tests/tool-annotations.test.ts +4 -3
  186. package/src/tests/ui-logs-parser.test.ts +176 -0
  187. package/src/tools/create-page.ts +4 -2
  188. package/src/tools/defer-task.ts +239 -0
  189. package/src/tools/repos/update-repo.ts +2 -2
  190. package/src/tools/schedules/create-schedule.ts +1 -0
  191. package/src/tools/schedules/list-schedules.ts +1 -0
  192. package/src/tools/schedules/patch-schedule.ts +1 -0
  193. package/src/tools/schedules/update-schedule.ts +1 -0
  194. package/src/tools/slack-download-file.ts +60 -29
  195. package/src/tools/slack-read.ts +73 -32
  196. package/src/tools/store-progress.ts +16 -146
  197. package/src/tools/tool-config.ts +1 -0
  198. package/src/tools/utils.ts +21 -0
  199. package/src/types.ts +37 -4
  200. package/src/utils/acp-session-token.ts +56 -0
  201. package/src/utils/file-lock.ts +111 -0
  202. package/src/utils/heartbeat-expiry.ts +78 -0
  203. package/src/utils/request-auth-context.ts +2 -0
  204. package/src/utils/task-attachment-links.ts +22 -0
  205. package/templates/skills/code-quality/content.md +8 -5
  206. package/templates/skills/download-task-attachment/content.md +2 -1
  207. package/{plugin/pi-skills → templates/skills}/review-offered-task/SKILL.md +5 -5
  208. package/templates/skills/review-offered-task/config.json +14 -0
  209. package/{plugin/commands/review-offered-task.md → templates/skills/review-offered-task/content.md} +3 -8
  210. package/templates/skills/scheduling/content.md +4 -0
  211. package/templates/skills/slack-interaction/content.md +2 -2
  212. package/templates/skills/work-on-task/SKILL.md +16 -0
  213. package/templates/skills/work-on-task/config.json +14 -0
  214. package/templates/skills/work-on-task/content.md +11 -0
  215. package/dist/assistant-0qt42nzg.js +0 -212
  216. package/dist/cli-fs1kkqgr.js +0 -317
  217. package/dist/cli-jnf5zghe.js +0 -269
  218. package/dist/cli-n1q8q14d.js +0 -177
  219. package/dist/handlers-yfp1qn81.js +0 -592
  220. package/plugin/commands/work-on-task.md +0 -39
  221. package/plugin/pi-skills/work-on-task/SKILL.md +0 -39
@@ -0,0 +1,779 @@
1
+ import {
2
+ init_task_attachment_links,
3
+ taskAttachmentDisplayUrl
4
+ } from "./cli-9d8ntetj.js";
5
+ import {
6
+ IDENTITY_FIELD_BUDGETS,
7
+ init_identity_field_budget,
8
+ init_templates
9
+ } from "./cli-y6b6fb76.js";
10
+ import {
11
+ init_resolver,
12
+ init_steering_enabled,
13
+ isSteeringEnabled,
14
+ resolveTemplateAsync
15
+ } from "./cli-kpv03zhj.js";
16
+ import {
17
+ MAX_PROFILE_FILE_LENGTH,
18
+ init_constants
19
+ } from "./cli-e29gmdmw.js";
20
+ import {
21
+ init_secret_scrubber,
22
+ scrubSecrets
23
+ } from "./cli-xqcq9y5e.js";
24
+ import {
25
+ __esm
26
+ } from "./cli-bc05qbss.js";
27
+
28
+ // src/utils/file-lock.ts
29
+ import { dlopen, FFIType } from "bun:ffi";
30
+ import { mkdir, open } from "node:fs/promises";
31
+ import { dirname } from "node:path";
32
+ function loadFlock() {
33
+ if (flockImpl !== undefined)
34
+ return flockImpl;
35
+ flockImpl = null;
36
+ for (const name of LIBC_CANDIDATES) {
37
+ try {
38
+ const { symbols } = dlopen(name, {
39
+ flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 }
40
+ });
41
+ flockImpl = (fd, operation) => symbols.flock(fd, operation);
42
+ break;
43
+ } catch {}
44
+ }
45
+ return flockImpl;
46
+ }
47
+ async function withFileLock(lockPath, fn, options = {}) {
48
+ const flock = loadFlock();
49
+ if (!flock)
50
+ return { acquired: false, reason: "unsupported" };
51
+ const waitMs = options.waitMs ?? DEFAULTS.waitMs;
52
+ const pollMs = options.pollMs ?? DEFAULTS.pollMs;
53
+ let handle;
54
+ try {
55
+ await mkdir(dirname(lockPath), { recursive: true });
56
+ handle = await open(lockPath, "a");
57
+ } catch (error) {
58
+ return { acquired: false, reason: "error", error };
59
+ }
60
+ try {
61
+ const deadline = Date.now() + waitMs;
62
+ while (flock(handle.fd, LOCK_EX | LOCK_NB) !== 0) {
63
+ if (Date.now() >= deadline)
64
+ return { acquired: false, reason: "busy" };
65
+ await Bun.sleep(pollMs);
66
+ }
67
+ return { acquired: true, value: await fn() };
68
+ } finally {
69
+ await handle.close();
70
+ }
71
+ }
72
+ var LOCK_EX = 2, LOCK_NB = 4, LIBC_CANDIDATES, flockImpl, DEFAULTS;
73
+ var init_file_lock = __esm(() => {
74
+ LIBC_CANDIDATES = process.platform === "darwin" ? ["libc.dylib", "/usr/lib/libSystem.B.dylib"] : ["libc.so.6", "libc.so", "libc.musl-x86_64.so.1", "libc.musl-aarch64.so.1"];
75
+ DEFAULTS = { waitMs: 15000, pollMs: 25 };
76
+ });
77
+
78
+ // src/commands/context-preamble.ts
79
+ function prependContextPreamble(prompt, preamble) {
80
+ if (!preamble)
81
+ return prompt;
82
+ const newline = prompt.indexOf(`
83
+ `);
84
+ const firstLine = newline === -1 ? prompt : prompt.slice(0, newline);
85
+ if (!/^\/[a-z0-9:_-]+(?:[ \t].*)?$/.test(firstLine.trim())) {
86
+ return preamble + prompt;
87
+ }
88
+ const rest = newline === -1 ? "" : prompt.slice(newline + 1);
89
+ return `${firstLine}
90
+ ${preamble}${rest}`;
91
+ }
92
+ async function fetchTaskContextForPreamble(apiUrl, apiKey, taskId) {
93
+ const headers = {};
94
+ if (apiKey)
95
+ headers.Authorization = `Bearer ${apiKey}`;
96
+ try {
97
+ const response = await fetch(`${apiUrl}/api/tasks/${taskId}`, { headers });
98
+ if (!response.ok)
99
+ return null;
100
+ const data = await response.json();
101
+ return {
102
+ id: data.id,
103
+ task: data.task,
104
+ output: data.output,
105
+ progress: data.progress,
106
+ status: data.status,
107
+ taskType: data.taskType,
108
+ parentTaskId: data.parentTaskId,
109
+ attachments: data.attachments
110
+ };
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ function formatAttachmentPointer(att) {
116
+ const pointer = taskAttachmentDisplayUrl(att);
117
+ return pointer || "(no pointer)";
118
+ }
119
+ async function buildContextPreamble(apiUrl, apiKey, parentTaskId) {
120
+ const ancestors = [];
121
+ let currentId = parentTaskId;
122
+ while (currentId && ancestors.length < CONTEXT_PREAMBLE_MAX_ANCESTORS) {
123
+ const ctx = await fetchTaskContextForPreamble(apiUrl, apiKey, currentId);
124
+ if (!ctx)
125
+ break;
126
+ ancestors.push(ctx);
127
+ currentId = ctx.parentTaskId;
128
+ }
129
+ if (ancestors.length === 0)
130
+ return null;
131
+ const parent = ancestors[0];
132
+ if (!parent)
133
+ return null;
134
+ const lines = [
135
+ `
136
+ ---`,
137
+ "## Prior Conversation Context",
138
+ "",
139
+ "This task is a follow-up in an ongoing thread. Here is a summary of prior work to maintain continuity.",
140
+ ""
141
+ ];
142
+ const subjectPreview = parent.task.slice(0, 600).replace(/\n/g, " ");
143
+ lines.push(`### Immediate Prior Task (ID: \`${parent.id}\`)`);
144
+ lines.push(`**Task:** ${subjectPreview}`);
145
+ lines.push("");
146
+ const rawResult = parent.output || parent.progress;
147
+ if (rawResult) {
148
+ const outputBudget = Math.floor(CONTEXT_PREAMBLE_MAX_CHARS * 0.55);
149
+ const truncated = rawResult.length > outputBudget ? `${rawResult.slice(0, outputBudget)}
150
+
151
+ [output truncated — full history via \`get-task-details\` with taskId \`${parent.id}\`]` : rawResult;
152
+ lines.push("**Outcome:**");
153
+ lines.push(truncated);
154
+ lines.push("");
155
+ } else {
156
+ lines.push("**Outcome:** (no output recorded yet — task may still be in progress)");
157
+ lines.push("");
158
+ }
159
+ const atts = parent.attachments?.filter((a) => a.name && (a.url || a.path || a.pageId));
160
+ if (atts && atts.length > 0) {
161
+ lines.push("**Artifacts from prior task:**");
162
+ for (const att of atts.slice(0, 10)) {
163
+ const pointer = formatAttachmentPointer(att);
164
+ const note = att.description || att.intent || "";
165
+ lines.push(` - **${att.name}**: \`${pointer}\`${note ? ` — ${note}` : ""}`);
166
+ }
167
+ lines.push("");
168
+ }
169
+ lines.push(`To review the full prior conversation call \`get-task-details\` with taskId \`${parent.id}\`.`);
170
+ if (ancestors.length > 1) {
171
+ lines.push("");
172
+ lines.push("### Older Ancestor Tasks (pointers only — call `get-task-details` for full details)");
173
+ for (const ancestor of ancestors.slice(1)) {
174
+ const brief = ancestor.task.slice(0, 200).replace(/\n/g, " ");
175
+ lines.push(`- \`${ancestor.id}\` — ${brief}`);
176
+ }
177
+ }
178
+ lines.push("", "---", "");
179
+ let preamble = lines.join(`
180
+ `);
181
+ if (preamble.length > CONTEXT_PREAMBLE_MAX_CHARS) {
182
+ preamble = `${preamble.slice(0, CONTEXT_PREAMBLE_MAX_CHARS)}
183
+
184
+ [context preamble truncated to ${CONTEXT_PREAMBLE_MAX_TOKENS}-token budget]
185
+
186
+ ---
187
+ `;
188
+ }
189
+ return preamble;
190
+ }
191
+ async function fetchSessionLogsForResume(apiUrl, apiKey, taskId) {
192
+ const headers = {};
193
+ if (apiKey)
194
+ headers.Authorization = `Bearer ${apiKey}`;
195
+ try {
196
+ const url = `${apiUrl}/api/tasks/${taskId}/session-logs?limit=${CONTEXT_PREAMBLE_RESUME_SESSION_LOG_LIMIT}`;
197
+ const response = await fetch(url, { headers });
198
+ if (!response.ok)
199
+ return [];
200
+ const data = await response.json();
201
+ return Array.isArray(data.logs) ? data.logs : [];
202
+ } catch {
203
+ return [];
204
+ }
205
+ }
206
+ async function fetchSteeringMessagesForResume(apiUrl, apiKey, taskId) {
207
+ const headers = {};
208
+ if (apiKey)
209
+ headers.Authorization = `Bearer ${apiKey}`;
210
+ try {
211
+ const response = await fetch(`${apiUrl}/api/tasks/${taskId}/steering-messages?status=pending,promoted`, { headers });
212
+ if (!response.ok)
213
+ return [];
214
+ const data = await response.json();
215
+ return Array.isArray(data.messages) ? data.messages.filter((message) => (message.status === "pending" || message.status === "promoted") && typeof message.body === "string" && typeof message.createdAt === "string") : [];
216
+ } catch {
217
+ return [];
218
+ }
219
+ }
220
+ function summarizeSessionLogLine(line) {
221
+ const ts = line.createdAt.slice(11, 19);
222
+ let parsed;
223
+ try {
224
+ parsed = JSON.parse(line.content);
225
+ } catch {
226
+ const snippet2 = line.content.replace(/\s+/g, " ").slice(0, 120);
227
+ return snippet2 ? `[${ts}] ${snippet2}` : null;
228
+ }
229
+ if (!parsed || typeof parsed !== "object")
230
+ return null;
231
+ const obj = parsed;
232
+ const message = obj.message;
233
+ const content = message?.content;
234
+ if (Array.isArray(content)) {
235
+ for (const block of content) {
236
+ if (!block || typeof block !== "object")
237
+ continue;
238
+ const b = block;
239
+ if (b.type === "tool_use" && typeof b.name === "string") {
240
+ const input = b.input;
241
+ const file = input?.file_path ?? input?.path ?? input?.command;
242
+ const fileStr = typeof file === "string" ? ` ${file}` : "";
243
+ return `[${ts}] ${b.name}${fileStr}`;
244
+ }
245
+ }
246
+ }
247
+ if (obj.type === "tool_use" && typeof obj.name === "string") {
248
+ const input = obj.input;
249
+ const file = input?.file_path ?? input?.path ?? input?.command;
250
+ const fileStr = typeof file === "string" ? ` ${file}` : "";
251
+ return `[${ts}] ${obj.name}${fileStr}`;
252
+ }
253
+ const snippet = JSON.stringify(parsed).replace(/\s+/g, " ").slice(0, 120);
254
+ return snippet ? `[${ts}] ${snippet}` : null;
255
+ }
256
+ async function walkResumeChain(apiUrl, apiKey, immediateParentId) {
257
+ const chain = [];
258
+ let currentId = immediateParentId;
259
+ for (let depth = 0;depth < MAX_RESUME_CHAIN_DEPTH && currentId; depth++) {
260
+ const ctx = await fetchTaskContextForPreamble(apiUrl, apiKey, currentId);
261
+ if (!ctx)
262
+ break;
263
+ chain.push(ctx);
264
+ if (ctx.taskType !== "resume")
265
+ break;
266
+ currentId = ctx.parentTaskId;
267
+ }
268
+ return chain;
269
+ }
270
+ async function buildResumeContextPreamble(apiUrl, apiKey, parentTaskId) {
271
+ const chain = await walkResumeChain(apiUrl, apiKey, parentTaskId);
272
+ if (chain.length === 0)
273
+ return null;
274
+ const original = chain[chain.length - 1] ?? chain[0];
275
+ if (!original)
276
+ return null;
277
+ const parent = chain[0] ?? original;
278
+ const steeringEnabled = isSteeringEnabled();
279
+ const [logsBatches, steeringBatches] = await Promise.all([
280
+ Promise.all(chain.map((c) => fetchSessionLogsForResume(apiUrl, apiKey, c.id))),
281
+ steeringEnabled ? Promise.all(chain.map((c) => fetchSteeringMessagesForResume(apiUrl, apiKey, c.id))) : Promise.resolve([])
282
+ ]);
283
+ const merged = logsBatches.flat();
284
+ merged.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
285
+ const recentLogs = merged.slice(-CONTEXT_PREAMBLE_RESUME_SESSION_LOG_LIMIT);
286
+ const steeringMessages = steeringBatches.flat();
287
+ steeringMessages.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
288
+ const descBudget = Math.floor(CONTEXT_PREAMBLE_RESUME_MAX_CHARS * 0.4);
289
+ let logsBudget = Math.floor(CONTEXT_PREAMBLE_RESUME_MAX_CHARS * 0.3);
290
+ let artBudget = Math.floor(CONTEXT_PREAMBLE_RESUME_MAX_CHARS * 0.1);
291
+ const steeringBudget = Math.floor(CONTEXT_PREAMBLE_RESUME_MAX_CHARS * 0.1);
292
+ const header = [
293
+ `
294
+ ---`,
295
+ "## Resuming Interrupted Task",
296
+ "",
297
+ "This task is a fresh-session continuation of an interrupted task (graceful",
298
+ "shutdown / context-limit / operator action). The block below summarizes the",
299
+ "original task, what was done so far, and the artifacts in flight.",
300
+ "",
301
+ "**Do not redo work already completed below — extend it.**",
302
+ "",
303
+ `Original task ID: \`${original.id}\``,
304
+ chain.length > 1 ? `Resume chain depth: ${chain.length} (this is at least the ${chain.length === 2 ? "2nd" : chain.length === 3 ? "3rd" : `${chain.length}th`} resume attempt).` : "",
305
+ "",
306
+ "---",
307
+ "",
308
+ "### Original Task Description",
309
+ ""
310
+ ].filter((s) => s !== "").join(`
311
+ `);
312
+ const descSection = original.task;
313
+ const summaryLines = [];
314
+ for (const line of recentLogs) {
315
+ const summary = summarizeSessionLogLine(line);
316
+ if (!summary)
317
+ continue;
318
+ summaryLines.push(summary);
319
+ }
320
+ const scrubbedSummary = summaryLines.map((s) => scrubSecrets(s));
321
+ let logsSection = scrubbedSummary.join(`
322
+ `);
323
+ if (descSection.length > descBudget) {
324
+ const overflow = descSection.length - descBudget;
325
+ logsBudget = Math.max(0, logsBudget - Math.ceil(overflow / 2));
326
+ artBudget = Math.max(0, artBudget - Math.floor(overflow / 2));
327
+ }
328
+ while (logsSection.length > logsBudget && scrubbedSummary.length > 0) {
329
+ scrubbedSummary.shift();
330
+ logsSection = scrubbedSummary.join(`
331
+ `);
332
+ }
333
+ const steeringLines = steeringEnabled ? steeringMessages.map((message) => {
334
+ const ts = message.createdAt.slice(11, 19);
335
+ return `[${ts}] ${scrubSecrets(message.body)}`;
336
+ }) : [];
337
+ let steeringSection = steeringLines.join(`
338
+ `);
339
+ while (steeringSection.length > steeringBudget && steeringLines.length > 0) {
340
+ steeringLines.shift();
341
+ steeringSection = steeringLines.join(`
342
+ `);
343
+ }
344
+ const atts = parent.attachments?.filter((a) => a.name && (a.url || a.path || a.pageId)) ?? [];
345
+ const artLines = [];
346
+ for (const att of atts) {
347
+ const pointer = formatAttachmentPointer(att);
348
+ artLines.push(` - **${att.name}**: \`${pointer}\``);
349
+ }
350
+ let artSection = artLines.join(`
351
+ `);
352
+ while (artSection.length > artBudget && artLines.length > 0) {
353
+ artLines.pop();
354
+ artSection = artLines.join(`
355
+ `);
356
+ }
357
+ const sections = [header, descSection, ""];
358
+ if (logsSection) {
359
+ sections.push("### Recent Tool Calls", "", logsSection, "");
360
+ }
361
+ if (artSection) {
362
+ sections.push("### Artifacts In Flight", "", artSection, "");
363
+ }
364
+ if (steeringEnabled && steeringSection) {
365
+ sections.push("### Undelivered Steering Messages", "", steeringSection, "");
366
+ }
367
+ sections.push("---", "", `To review the full prior session call \`get-task-details\` with taskId \`${original.id}\`.`, "", "---", "");
368
+ let preamble = sections.join(`
369
+ `);
370
+ if (preamble.length > CONTEXT_PREAMBLE_RESUME_MAX_CHARS) {
371
+ preamble = `${preamble.slice(0, CONTEXT_PREAMBLE_RESUME_MAX_CHARS)}
372
+
373
+ [resume preamble truncated to ${CONTEXT_PREAMBLE_RESUME_MAX_TOKENS}-token budget]
374
+
375
+ ---
376
+ `;
377
+ }
378
+ return preamble;
379
+ }
380
+ var CONTEXT_PREAMBLE_MAX_TOKENS, CONTEXT_PREAMBLE_MAX_CHARS, CONTEXT_PREAMBLE_MAX_ANCESTORS = 5, CONTEXT_PREAMBLE_RESUME_MAX_TOKENS, CONTEXT_PREAMBLE_RESUME_MAX_CHARS, CONTEXT_PREAMBLE_RESUME_SESSION_LOG_LIMIT = 50, MAX_RESUME_CHAIN_DEPTH = 10;
381
+ var init_context_preamble = __esm(() => {
382
+ init_secret_scrubber();
383
+ init_steering_enabled();
384
+ init_task_attachment_links();
385
+ CONTEXT_PREAMBLE_MAX_TOKENS = Number(process.env.CONTEXT_PREAMBLE_MAX_TOKENS || "2000");
386
+ CONTEXT_PREAMBLE_MAX_CHARS = CONTEXT_PREAMBLE_MAX_TOKENS * 4;
387
+ CONTEXT_PREAMBLE_RESUME_MAX_TOKENS = Number(process.env.CONTEXT_PREAMBLE_RESUME_MAX_TOKENS || "4000");
388
+ CONTEXT_PREAMBLE_RESUME_MAX_CHARS = CONTEXT_PREAMBLE_RESUME_MAX_TOKENS * 4;
389
+ });
390
+
391
+ // src/commands/profile-sync.ts
392
+ function contentSha256(content) {
393
+ return new Bun.CryptoHasher("sha256").update(content).digest("hex");
394
+ }
395
+ async function renderProfileSyncRejectionBanner(event) {
396
+ const data = event.data;
397
+ const field = data?.field;
398
+ const diskSize = data?.diskSize;
399
+ const dbSize = data?.dbSize;
400
+ const budget = data?.budget;
401
+ const delta = data?.delta;
402
+ if (typeof field !== "string" || !(field in PROFILE_FIELD_DETAILS) || typeof diskSize !== "number" || typeof dbSize !== "number" || typeof budget !== "number" || typeof delta !== "number") {
403
+ return null;
404
+ }
405
+ const details = PROFILE_FIELD_DETAILS[field];
406
+ const result = await resolveTemplateAsync("task.profile_sync_rejection", {
407
+ timestamp: event.createdAt,
408
+ field_label: details.label,
409
+ disk_size: diskSize,
410
+ db_size: dbSize,
411
+ budget,
412
+ delta: delta >= 0 ? `+${delta}` : String(delta),
413
+ event_id: event.id,
414
+ recovery_guidance: details.recoveryGuidance
415
+ });
416
+ return result.skipped ? null : result.text;
417
+ }
418
+ async function fetchProfileSyncRejectionBanner(config, fetchImpl = fetch, readFile = readFileIfExists) {
419
+ try {
420
+ const responses = await Promise.all(Object.keys(IDENTITY_FIELD_BUDGETS).map(async (field) => {
421
+ const fetchLatestEvent = async (event) => {
422
+ const query = new URLSearchParams({
423
+ event,
424
+ agentId: config.agentId,
425
+ dataField: field,
426
+ limit: "1"
427
+ });
428
+ const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
429
+ headers: {
430
+ Authorization: `Bearer ${config.apiKey}`,
431
+ "X-Agent-ID": config.agentId
432
+ }
433
+ });
434
+ if (!response.ok)
435
+ return null;
436
+ const payload = await response.json();
437
+ return payload.events?.[0] ?? null;
438
+ };
439
+ const [rejection, reconciliation] = await Promise.all([
440
+ fetchLatestEvent("system.profile_sync_rejected"),
441
+ fetchLatestEvent("system.profile_sync_reconciled")
442
+ ]);
443
+ return { rejection, reconciliation };
444
+ }));
445
+ const latestEvents = responses.flatMap(({ rejection, reconciliation }) => rejection ? [{ rejection, reconciliation }] : []);
446
+ if (latestEvents.length === 0)
447
+ return "";
448
+ let profile = null;
449
+ const profileResponse = await fetchImpl(`${config.apiUrl}/me`, {
450
+ headers: {
451
+ Authorization: `Bearer ${config.apiKey}`,
452
+ "X-Agent-ID": config.agentId
453
+ }
454
+ });
455
+ if (profileResponse.ok) {
456
+ profile = await profileResponse.json();
457
+ }
458
+ let disk = null;
459
+ if (config.claudeMdPath) {
460
+ disk = {
461
+ soulMd: await readFile(SOUL_MD_PATH),
462
+ identityMd: await readFile(IDENTITY_MD_PATH),
463
+ claudeMd: await readFile(config.claudeMdPath),
464
+ toolsMd: await readFile(TOOLS_MD_PATH)
465
+ };
466
+ }
467
+ const unresolved = latestEvents.flatMap(({ rejection, reconciliation }) => {
468
+ if (reconciliation && reconciliation.createdAt > rejection.createdAt)
469
+ return [];
470
+ const event = rejection;
471
+ const field = event.data?.field;
472
+ const rejectedDbHash = event.data?.dbHash;
473
+ const currentValue = typeof field === "string" ? profile?.[field] : undefined;
474
+ const diskValue = typeof field === "string" ? disk?.[field] : undefined;
475
+ if (typeof currentValue === "string" && diskValue === currentValue)
476
+ return [];
477
+ return !(typeof rejectedDbHash === "string" && typeof currentValue === "string" && contentSha256(currentValue) !== rejectedDbHash) ? [event] : [];
478
+ });
479
+ const banners = await Promise.all(unresolved.map(renderProfileSyncRejectionBanner));
480
+ return banners.filter((banner) => banner !== null).join("");
481
+ } catch {
482
+ return "";
483
+ }
484
+ }
485
+ async function prependProfileSyncRejectionBanner(prompt, config, fetchImpl = fetch, readFile = readFileIfExists) {
486
+ const banner = await fetchProfileSyncRejectionBanner(config, fetchImpl, readFile);
487
+ return banner ? { prompt: prependContextPreamble(prompt, banner), injected: true } : { prompt, injected: false };
488
+ }
489
+ async function writeIdentityBaselines(baselines) {
490
+ await Bun.write(IDENTITY_BASELINES_PATH, JSON.stringify(baselines));
491
+ }
492
+ async function readIdentityBaselines(readFile = readFileIfExists) {
493
+ try {
494
+ const raw = await readFile(IDENTITY_BASELINES_PATH);
495
+ if (!raw)
496
+ return null;
497
+ return JSON.parse(raw);
498
+ } catch {
499
+ return null;
500
+ }
501
+ }
502
+ function fetchWithSyncTimeout(fetchImpl = fetch) {
503
+ return (input, init) => fetchImpl(input, {
504
+ ...init,
505
+ signal: AbortSignal.timeout(CLAUDE_MD_SYNC_TIMEOUT_MS)
506
+ });
507
+ }
508
+ function syncsHookOwnedClaudeMd(claudeMdPath, changeSource) {
509
+ return claudeMdPath === CLAUDE_MD_PATH && changeSource === "session_sync";
510
+ }
511
+ function parseClaudeMdLineage(raw) {
512
+ if (!raw)
513
+ return null;
514
+ let value;
515
+ try {
516
+ value = JSON.parse(raw);
517
+ } catch {
518
+ return null;
519
+ }
520
+ if (typeof value !== "object" || value === null || Array.isArray(value))
521
+ return null;
522
+ const { written, base } = value;
523
+ const hashOrNull = (v) => v === null || typeof v === "string" && SHA256_HEX.test(v);
524
+ if (!("written" in value) || !("base" in value) || !hashOrNull(written) || !hashOrNull(base)) {
525
+ return null;
526
+ }
527
+ return { written, base };
528
+ }
529
+ function claudeMdPendingRecord(written) {
530
+ return JSON.stringify({ pending: true, written });
531
+ }
532
+ function parseClaudeMdPendingRecord(raw) {
533
+ if (!raw)
534
+ return null;
535
+ try {
536
+ const value = JSON.parse(raw);
537
+ const written = value?.pending === true ? value.written : undefined;
538
+ return typeof written === "string" && SHA256_HEX.test(written) ? written : null;
539
+ } catch {
540
+ return null;
541
+ }
542
+ }
543
+ function hookWrittenLineage(content) {
544
+ return { written: contentSha256(content), base: null };
545
+ }
546
+ function effectiveClaudeMdLineage(raw, content) {
547
+ if (raw === undefined)
548
+ return null;
549
+ return parseClaudeMdLineage(raw) ?? hookWrittenLineage(content);
550
+ }
551
+ function claudeMdLineageOf(content, record) {
552
+ if (record && record.written === contentSha256(content))
553
+ return record;
554
+ return { written: null, base: record?.base ?? null };
555
+ }
556
+ function planClaudeMdSync(state) {
557
+ const { content, record } = state;
558
+ if (!content.trim())
559
+ return null;
560
+ if (record === null)
561
+ return { claudeMd: content, changeSource: "session_sync" };
562
+ const { written, base } = claudeMdLineageOf(content, record);
563
+ if (written !== null || contentSha256(content) === base)
564
+ return null;
565
+ if (base === null)
566
+ return { claudeMd: content, changeSource: "session_sync" };
567
+ return { claudeMd: content, changeSource: "session_sync", expectedHashes: { claudeMd: base } };
568
+ }
569
+ function warnProfileFileTooLarge(agentId, field, actualLength, source = "profile-sync") {
570
+ console.warn(`[${source}] Skipping profile sync for agent ${agentId}, field ${field}: ${actualLength} characters exceeds the ${MAX_PROFILE_FILE_LENGTH}-character cap.`);
571
+ }
572
+ async function writeProfileFileFromDb(filePath, dbContent, now = () => new Date) {
573
+ const file = Bun.file(filePath);
574
+ let backupPath = null;
575
+ if (await file.exists()) {
576
+ const localContent = await file.text();
577
+ if (localContent !== dbContent) {
578
+ backupPath = `${filePath}.pre-boot-${now().toISOString()}.bak`;
579
+ await Bun.write(backupPath, localContent);
580
+ }
581
+ }
582
+ await Bun.write(filePath, dbContent);
583
+ return backupPath;
584
+ }
585
+ function resolveClaudeMdPath(completedProviders) {
586
+ const anyNonClaude = completedProviders.some((p) => p !== "claude");
587
+ return anyNonClaude ? WORKSPACE_CLAUDE_MD_PATH : CLAUDE_MD_PATH;
588
+ }
589
+ function buildIndependentIdentityPayloads(updates, changeSource) {
590
+ return Object.entries(updates).map(([field, value]) => ({
591
+ label: `identity.${field}`,
592
+ body: { [field]: value, changeSource }
593
+ }));
594
+ }
595
+ function extractSetupScriptContent(raw, agentId = "unknown") {
596
+ if (!raw.trim())
597
+ return null;
598
+ const startIdx = raw.indexOf(SETUP_MARKER_START);
599
+ const endIdx = raw.indexOf(SETUP_MARKER_END);
600
+ let content;
601
+ if (startIdx !== -1 && endIdx !== -1) {
602
+ content = raw.substring(startIdx + SETUP_MARKER_START.length, endIdx).trim();
603
+ } else {
604
+ content = raw.replace(/^#!\/bin\/bash\n/, "").trim();
605
+ }
606
+ if (!content)
607
+ return null;
608
+ if (content.length > MAX_PROFILE_FILE_LENGTH) {
609
+ warnProfileFileTooLarge(agentId, "setupScript", content.length);
610
+ return null;
611
+ }
612
+ return content;
613
+ }
614
+ function buildIdentityPayload(files, baselines, agentId = "unknown") {
615
+ const updates = {};
616
+ if (files.soulMd !== undefined) {
617
+ const content = files.soulMd;
618
+ if (baselines?.soulMd && contentSha256(content) === baselines.soulMd) {} else if (content.trim()) {
619
+ if (content.length < IDENTITY_FILE_MIN_LENGTH) {
620
+ console.error(`[profile-sync] Skipping SOUL.md sync: content too short (${content.length} chars, minimum ${IDENTITY_FILE_MIN_LENGTH}). This prevents accidental profile corruption.`);
621
+ } else {
622
+ updates.soulMd = content;
623
+ }
624
+ }
625
+ }
626
+ if (files.identityMd !== undefined) {
627
+ const content = files.identityMd;
628
+ if (baselines?.identityMd && contentSha256(content) === baselines.identityMd) {} else if (content.trim()) {
629
+ if (content.length < IDENTITY_FILE_MIN_LENGTH) {
630
+ console.error(`[profile-sync] Skipping IDENTITY.md sync: content too short (${content.length} chars, minimum ${IDENTITY_FILE_MIN_LENGTH}). This prevents accidental profile corruption.`);
631
+ } else {
632
+ updates.identityMd = content;
633
+ }
634
+ }
635
+ }
636
+ if (files.toolsMd !== undefined) {
637
+ const content = files.toolsMd;
638
+ if (baselines?.toolsMd && contentSha256(content) === baselines.toolsMd) {} else if (content.trim()) {
639
+ updates.toolsMd = content;
640
+ }
641
+ }
642
+ if (files.heartbeatMd !== undefined) {
643
+ const content = files.heartbeatMd;
644
+ if (baselines?.heartbeatMd && contentSha256(content) === baselines.heartbeatMd) {} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
645
+ warnProfileFileTooLarge(agentId, "heartbeatMd", content.length);
646
+ } else {
647
+ updates.heartbeatMd = content;
648
+ }
649
+ }
650
+ return updates;
651
+ }
652
+ async function readFileIfExists(path) {
653
+ try {
654
+ const file = Bun.file(path);
655
+ if (!await file.exists())
656
+ return;
657
+ return await file.text();
658
+ } catch {
659
+ return;
660
+ }
661
+ }
662
+ async function collectProfilePayloads(fields, changeSource, readFile = readFileIfExists, claudeMdPath = CLAUDE_MD_PATH, agentId = "unknown") {
663
+ const payloads = [];
664
+ const baselines = changeSource === "session_sync" ? await readIdentityBaselines(readFile) : null;
665
+ if (fields.includes("identity")) {
666
+ const updates = buildIdentityPayload({
667
+ soulMd: await readFile(SOUL_MD_PATH),
668
+ identityMd: await readFile(IDENTITY_MD_PATH),
669
+ toolsMd: await readFile(TOOLS_MD_PATH),
670
+ heartbeatMd: await readFile(HEARTBEAT_MD_PATH)
671
+ }, baselines, agentId);
672
+ payloads.push(...buildIndependentIdentityPayloads(updates, changeSource));
673
+ }
674
+ if (fields.includes("claude")) {
675
+ const raw = await readFile(claudeMdPath);
676
+ if (raw?.trim()) {
677
+ if (baselines?.claudeMd && contentSha256(raw) === baselines.claudeMd) {} else if (syncsHookOwnedClaudeMd(claudeMdPath, changeSource)) {
678
+ const record = effectiveClaudeMdLineage(await readFile(CLAUDE_MD_LINEAGE_PATH), raw) ?? (baselines?.claudeMd ? { written: null, base: baselines.claudeMd } : null);
679
+ const body = planClaudeMdSync({ content: raw, record });
680
+ if (body)
681
+ payloads.push({ label: "claude", body });
682
+ } else {
683
+ payloads.push({ label: "claude", body: { claudeMd: raw, changeSource } });
684
+ }
685
+ }
686
+ }
687
+ if (fields.includes("setup")) {
688
+ const raw = await readFile(SETUP_SCRIPT_PATH);
689
+ if (raw !== undefined) {
690
+ const content = extractSetupScriptContent(raw, agentId);
691
+ if (content !== null) {
692
+ payloads.push({ label: "setup", body: { setupScript: content, changeSource } });
693
+ }
694
+ }
695
+ }
696
+ return payloads;
697
+ }
698
+ async function postProfileUpdate(opts, payload) {
699
+ const doFetch = opts.fetchImpl ?? fetch;
700
+ try {
701
+ const resp = await doFetch(`${opts.apiUrl}/api/agents/${opts.agentId}/profile`, {
702
+ method: "PUT",
703
+ headers: {
704
+ "Content-Type": "application/json",
705
+ Authorization: `Bearer ${opts.apiKey}`,
706
+ "X-Agent-ID": opts.agentId
707
+ },
708
+ body: JSON.stringify(payload.body)
709
+ });
710
+ if (!resp.ok) {
711
+ let detail = "";
712
+ try {
713
+ detail = (await resp.text()).slice(0, 500);
714
+ } catch {}
715
+ console.warn(scrubSecrets(`[profile-sync] ${payload.label} sync failed: HTTP ${resp.status}${detail ? ` — ${detail}` : ""}`));
716
+ }
717
+ } catch (err) {
718
+ const msg = err instanceof Error ? err.message : String(err);
719
+ console.warn(scrubSecrets(`[profile-sync] ${payload.label} sync errored: ${msg}`));
720
+ }
721
+ }
722
+ async function syncProfileFilesToServer(opts) {
723
+ const changeSource = opts.changeSource ?? "session_sync";
724
+ const fields = opts.fields ?? ["identity", "claude", "setup"];
725
+ const claudeMdPath = opts.claudeMdPath ?? CLAUDE_MD_PATH;
726
+ const claudeUnderLock = fields.includes("claude") && syncsHookOwnedClaudeMd(claudeMdPath, changeSource);
727
+ const unlockedFields = claudeUnderLock ? fields.filter((f) => f !== "claude") : fields;
728
+ const payloads = await collectProfilePayloads(unlockedFields, changeSource, readFileIfExists, claudeMdPath, opts.agentId);
729
+ for (const payload of payloads) {
730
+ await postProfileUpdate(opts, payload);
731
+ }
732
+ if (claudeUnderLock) {
733
+ const { path: lockPath = CLAUDE_MD_LOCK_PATH, waitMs = RUNNER_LOCK_WAIT_MS } = opts.claudeMdLock ?? {};
734
+ const bounded = { ...opts, fetchImpl: fetchWithSyncTimeout(opts.fetchImpl) };
735
+ const locked = await withFileLock(lockPath, async () => {
736
+ const claude = await collectProfilePayloads(["claude"], changeSource, readFileIfExists, claudeMdPath, opts.agentId);
737
+ for (const payload of claude)
738
+ await postProfileUpdate(bounded, payload);
739
+ }, { waitMs });
740
+ if (!locked.acquired) {
741
+ const detail = locked.error === undefined ? "" : `: ${String(locked.error)}`;
742
+ console.warn(scrubSecrets(`[profile-sync] no CLAUDE.md lock (${locked.reason})${detail} — backstop sync skipped`));
743
+ }
744
+ }
745
+ }
746
+ var SOUL_MD_PATH = "/workspace/SOUL.md", IDENTITY_MD_PATH = "/workspace/IDENTITY.md", TOOLS_MD_PATH = "/workspace/TOOLS.md", HEARTBEAT_MD_PATH = "/workspace/HEARTBEAT.md", SETUP_SCRIPT_PATH = "/workspace/start-up.sh", IDENTITY_BASELINES_PATH = "/tmp/identity-baselines.json", PROFILE_FIELD_DETAILS, CLAUDE_MD_PATH, CLAUDE_MD_LINEAGE_PATH, CLAUDE_MD_LOCK_PATH, CLAUDE_MD_SYNC_TIMEOUT_MS = 1e4, RUNNER_LOCK_WAIT_MS = 15000, SHA256_HEX, WORKSPACE_CLAUDE_MD_PATH = "/workspace/CLAUDE.md", IDENTITY_FILE_MIN_LENGTH = 500, SETUP_MARKER_START = "# === Agent-managed setup (from DB) ===", SETUP_MARKER_END = "# === End agent-managed setup ===";
747
+ var init_profile_sync = __esm(() => {
748
+ init_resolver();
749
+ init_constants();
750
+ init_file_lock();
751
+ init_identity_field_budget();
752
+ init_secret_scrubber();
753
+ init_context_preamble();
754
+ init_templates();
755
+ PROFILE_FIELD_DETAILS = {
756
+ soulMd: {
757
+ label: `soulMd (${SOUL_MD_PATH})`,
758
+ recoveryGuidance: `Inspect and shrink the live ${SOUL_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${SOUL_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`
759
+ },
760
+ identityMd: {
761
+ label: `identityMd (${IDENTITY_MD_PATH})`,
762
+ recoveryGuidance: `Inspect and shrink the live ${IDENTITY_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${IDENTITY_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`
763
+ },
764
+ claudeMd: {
765
+ label: "claudeMd (/workspace/CLAUDE.md for non-Claude harnesses; ~/.claude/CLAUDE.md for Claude)",
766
+ recoveryGuidance: "Inspect and shrink the live CLAUDE.md source first, moving durable tail content into memory. After a worker restart, non-Claude harnesses can use /workspace/CLAUDE.md.pre-boot-<timestamp>.bak as the fallback copy; Claude's native ~/.claude/CLAUDE.md source is not archived by that boot step."
767
+ },
768
+ toolsMd: {
769
+ label: `toolsMd (${TOOLS_MD_PATH})`,
770
+ recoveryGuidance: `Inspect and shrink the live ${TOOLS_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${TOOLS_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`
771
+ }
772
+ };
773
+ CLAUDE_MD_PATH = `${process.env.HOME}/.claude/CLAUDE.md`;
774
+ CLAUDE_MD_LINEAGE_PATH = `${CLAUDE_MD_PATH}.lineage.json`;
775
+ CLAUDE_MD_LOCK_PATH = `${CLAUDE_MD_PATH}.lock`;
776
+ SHA256_HEX = /^[0-9a-f]{64}$/;
777
+ });
778
+
779
+ export { withFileLock, init_file_lock, prependContextPreamble, buildContextPreamble, buildResumeContextPreamble, init_context_preamble, contentSha256, prependProfileSyncRejectionBanner, writeIdentityBaselines, readIdentityBaselines, CLAUDE_MD_PATH, CLAUDE_MD_LINEAGE_PATH, CLAUDE_MD_LOCK_PATH, fetchWithSyncTimeout, parseClaudeMdLineage, claudeMdPendingRecord, parseClaudeMdPendingRecord, hookWrittenLineage, effectiveClaudeMdLineage, claudeMdLineageOf, planClaudeMdSync, warnProfileFileTooLarge, writeProfileFileFromDb, resolveClaudeMdPath, buildIndependentIdentityPayloads, syncProfileFilesToServer, init_profile_sync };