agentlas 1.0.65 → 1.0.67

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 (163) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +12 -4
  3. package/bin/agentlas.cjs +354 -16
  4. package/engine/acp/server.cjs +34 -3
  5. package/engine/agentlas-api-agent.cjs +151 -120
  6. package/engine/agentlas-banner.cjs +6 -2
  7. package/engine/agentlas-capabilities.cjs +17 -10
  8. package/engine/agentlas-cloud-runtime.cjs +4 -5
  9. package/engine/agentlas-config.cjs +19 -54
  10. package/engine/agentlas-core-harness.cjs +59 -5
  11. package/engine/agentlas-desktop-loadout.cjs +17 -3
  12. package/engine/agentlas-evolution.cjs +75 -18
  13. package/engine/agentlas-experience-exchange.cjs +145 -14
  14. package/engine/agentlas-experience-intake.cjs +2 -2
  15. package/engine/agentlas-i18n.cjs +0 -2
  16. package/engine/agentlas-input.cjs +273 -20
  17. package/engine/agentlas-judgment.cjs +0 -0
  18. package/engine/agentlas-mcp-env.cjs +84 -23
  19. package/engine/agentlas-mcp-wrapper.cjs +36 -4
  20. package/engine/agentlas-memory-governance.cjs +2 -1
  21. package/engine/agentlas-memory-import.cjs +49 -12
  22. package/engine/agentlas-native-host.cjs +279 -44
  23. package/engine/agentlas-onboard.cjs +23 -1
  24. package/engine/agentlas-permissions.cjs +5 -1
  25. package/engine/agentlas-sqlite-policy.cjs +40 -2
  26. package/engine/agentlas-tasks.cjs +7 -0
  27. package/engine/agentlas-tools.cjs +37 -10
  28. package/engine/agentlas-ui.cjs +26 -4
  29. package/engine/agentlas-workforce.cjs +30 -5
  30. package/engine/agentlas-workload-routing.cjs +83 -10
  31. package/engine/agentlas.cjs +21 -4
  32. package/engine/agents/builder.cjs +35 -10
  33. package/engine/agents/files.cjs +81 -10
  34. package/engine/agents/import-local.cjs +10 -2
  35. package/engine/agents/registry.cjs +10 -3
  36. package/engine/agents/router.cjs +7 -7
  37. package/engine/agents/routes.cjs +31 -9
  38. package/engine/automation/daemon.cjs +154 -40
  39. package/engine/automation/launchd.cjs +395 -16
  40. package/engine/automation/schedule.cjs +90 -3
  41. package/engine/automation/store.cjs +163 -59
  42. package/engine/browser/cdp.cjs +190 -17
  43. package/engine/browser/vault.cjs +1 -0
  44. package/engine/cli-output.cjs +96 -30
  45. package/engine/cloud/auth.cjs +131 -13
  46. package/engine/cloud/hub-client.cjs +41 -16
  47. package/engine/cloud-assets/cas.cjs +18 -9
  48. package/engine/cloud-assets/commands.cjs +108 -30
  49. package/engine/cloud-assets/package.cjs +66 -3
  50. package/engine/cloud-assets/restore.cjs +12 -9
  51. package/engine/cloud-assets/state.cjs +609 -116
  52. package/engine/commands/acp.cjs +13 -2
  53. package/engine/commands/automation.cjs +75 -24
  54. package/engine/commands/billing.cjs +6 -1
  55. package/engine/commands/browser.cjs +61 -29
  56. package/engine/commands/build.cjs +72 -14
  57. package/engine/commands/call.cjs +1 -1
  58. package/engine/commands/cd.cjs +1 -1
  59. package/engine/commands/cloud.cjs +2 -1
  60. package/engine/commands/connect.cjs +38 -13
  61. package/engine/commands/creds.cjs +74 -29
  62. package/engine/commands/doctor.cjs +17 -1
  63. package/engine/commands/document.cjs +38 -10
  64. package/engine/commands/env.cjs +6 -1
  65. package/engine/commands/firm.cjs +42 -12
  66. package/engine/commands/graph.cjs +36 -32
  67. package/engine/commands/help.cjs +11 -1
  68. package/engine/commands/hep.cjs +1 -1
  69. package/engine/commands/import.cjs +5 -4
  70. package/engine/commands/index.cjs +1 -1
  71. package/engine/commands/install.cjs +6 -4
  72. package/engine/commands/list.cjs +56 -16
  73. package/engine/commands/login.cjs +5 -0
  74. package/engine/commands/logout.cjs +6 -1
  75. package/engine/commands/mcp.cjs +150 -37
  76. package/engine/commands/memory.cjs +3 -2
  77. package/engine/commands/multimodal.cjs +12 -0
  78. package/engine/commands/native.cjs +5 -1
  79. package/engine/commands/one.cjs +78 -33
  80. package/engine/commands/plugin.cjs +13 -2
  81. package/engine/commands/project.cjs +15 -12
  82. package/engine/commands/research.cjs +18 -7
  83. package/engine/commands/roles.cjs +104 -28
  84. package/engine/commands/route.cjs +2 -2
  85. package/engine/commands/run.cjs +42 -9
  86. package/engine/commands/search.cjs +23 -2
  87. package/engine/commands/setup.cjs +6 -1
  88. package/engine/commands/storm.cjs +44 -6
  89. package/engine/commands/swarm.cjs +47 -9
  90. package/engine/commands/telegram.cjs +6 -1
  91. package/engine/commands/uninstall.cjs +12 -6
  92. package/engine/commands/update.cjs +5 -0
  93. package/engine/commands/upload.cjs +2 -1
  94. package/engine/commands/usage.cjs +6 -1
  95. package/engine/commands/version.cjs +6 -1
  96. package/engine/commands/whoami.cjs +6 -1
  97. package/engine/commands/workforce.cjs +60 -16
  98. package/engine/core/capability-grants.cjs +7 -2
  99. package/engine/core/daemon-client.cjs +36 -7
  100. package/engine/core/db.cjs +10 -2
  101. package/engine/core/desktop-core-fetch.cjs +477 -77
  102. package/engine/core/schema-ensure.cjs +23 -11
  103. package/engine/experience/build.cjs +5 -1
  104. package/engine/experience/intents.cjs +35 -82
  105. package/engine/experience/runtime.cjs +7 -1
  106. package/engine/experience/variant.cjs +58 -4
  107. package/engine/firms/orchestrate.cjs +9 -6
  108. package/engine/graph/ask-model.cjs +22 -11
  109. package/engine/graph/interview.cjs +74 -23
  110. package/engine/graph/package.cjs +63 -8
  111. package/engine/hephaestus/local-core.cjs +18 -5
  112. package/engine/hephaestus/runtime.cjs +48 -43
  113. package/engine/hub/install.cjs +484 -63
  114. package/engine/hub/plugins.cjs +391 -29
  115. package/engine/mcp/consent.cjs +25 -17
  116. package/engine/mcp/contract.cjs +229 -24
  117. package/engine/mcp/inventory.cjs +46 -10
  118. package/engine/mcp/plan.cjs +10 -1
  119. package/engine/mcp/probe.cjs +24 -9
  120. package/engine/memory-cli/curate.cjs +74 -16
  121. package/engine/oberon/common.cjs +37 -12
  122. package/engine/oberon/manifest.cjs +25 -2
  123. package/engine/oberon/outputs.cjs +49 -19
  124. package/engine/oberon/render.cjs +665 -40
  125. package/engine/project/career-graph.cjs +15 -2
  126. package/engine/project/controller.cjs +133 -15
  127. package/engine/project/credentials.cjs +382 -34
  128. package/engine/project/env-file.cjs +93 -8
  129. package/engine/project/memory-context.cjs +11 -57
  130. package/engine/project/ontology.cjs +66 -25
  131. package/engine/project/paths.cjs +42 -8
  132. package/engine/project/seed.cjs +88 -51
  133. package/engine/project/state.cjs +41 -16
  134. package/engine/project/team.cjs +26 -1
  135. package/engine/runtime-refusal.cjs +4 -1
  136. package/engine/runtimes/acp-driver.cjs +56 -9
  137. package/engine/runtimes/auth-evidence.cjs +21 -2
  138. package/engine/runtimes/detect.cjs +28 -8
  139. package/engine/runtimes/kinds.cjs +12 -4
  140. package/engine/runtimes/overrides.cjs +4 -3
  141. package/engine/runtimes/resolve.cjs +8 -7
  142. package/engine/runtimes/roles.cjs +7 -5
  143. package/engine/sessions/apply-fences.cjs +396 -56
  144. package/engine/sessions/fences.cjs +111 -27
  145. package/engine/sessions/memory-turn.cjs +62 -3
  146. package/engine/sessions/orchestrator.cjs +11 -1
  147. package/engine/sessions/prompt.cjs +66 -9
  148. package/engine/sessions/session.cjs +70 -10
  149. package/engine/sessions/sink.cjs +78 -12
  150. package/engine/sessions/store.cjs +29 -7
  151. package/engine/storm/storm.cjs +26 -3
  152. package/engine/storm/swarm.cjs +31 -3
  153. package/engine/telegram/connect.cjs +185 -22
  154. package/engine/ui/commands-catalog.cjs +1 -1
  155. package/engine/ui/palette.cjs +12 -2
  156. package/engine/ui/repl.cjs +57 -9
  157. package/engine/ui/screens.cjs +2 -4
  158. package/engine/vendor/desktop-core.manifest.json +5 -5
  159. package/engine/workforce/capture.cjs +9 -27
  160. package/engine/workforce/deps.cjs +18 -6
  161. package/engine/workforce/local-core-transport.cjs +68 -5
  162. package/package.json +1 -1
  163. package/engine/commands/career-graph.cjs +0 -51
@@ -182,6 +182,7 @@ async function runCareerGraphCli(args, opts) {
182
182
  const sub = normalizedArgs[0] || "status";
183
183
  const passivePaths = careerGraphPathsForCli(projectPath);
184
184
  if (sub === "status" || sub === "list") {
185
+ if (normalizedArgs.length > 1) throw new Error(`usage: career-graph ${sub}`);
185
186
  if (!initializedAgentlasProjectPathCli(projectPath)) {
186
187
  return [
187
188
  ko ? "커리어 그래프: 초기화되지 않음" : "Career Graph: not initialized",
@@ -192,7 +193,10 @@ async function runCareerGraphCli(args, opts) {
192
193
  }
193
194
  return formatCareerGraphStatusCli(passivePaths, opts.lang);
194
195
  }
195
- if (sub === "help" || sub === "--help" || sub === "-h") return careerGraphUsageLinesCli(opts.lang);
196
+ if (sub === "help" || sub === "--help" || sub === "-h") {
197
+ if (normalizedArgs.length > 1) throw new Error("usage: career-graph help");
198
+ return careerGraphUsageLinesCli(opts.lang);
199
+ }
196
200
  if (["ingest", "query", "verify", "trace"].includes(String(sub))) {
197
201
  return [
198
202
  ko
@@ -207,6 +211,15 @@ async function runCareerGraphCli(args, opts) {
207
211
  const parsed = await parseOntologyNaturalArgsCli(normalizedArgs.join(" "), cwd);
208
212
  return runCareerGraphCli(parsed, opts);
209
213
  }
214
+ let directFlags = null;
215
+ if (sub === "open") {
216
+ if (normalizedArgs.length !== 1) throw new Error("usage: career-graph open");
217
+ } else if (sub === "add") {
218
+ directFlags = parseFlagsCli(normalizedArgs.slice(1));
219
+ if (directFlags._.length < 1 || directFlags._.length > 3) throw new Error("usage: career-graph add <path> [kind] [scope] [--kind <kind>] [--scope <scope>]");
220
+ if (directFlags.kind !== undefined && directFlags._[1] !== undefined) throw new Error("kind was provided twice; use either the positional value or --kind");
221
+ if (directFlags.scope !== undefined && directFlags._[2] !== undefined) throw new Error("scope was provided twice; use either the positional value or --scope");
222
+ }
210
223
  const paths = ensureCareerGraphCli(projectPath, opts.lang);
211
224
  if (sub === "open") {
212
225
  const opened = opts.noOpen ? true : openLocalPathCli(paths.inboxPath, opts.notify);
@@ -215,7 +228,7 @@ async function runCareerGraphCli(args, opts) {
215
228
  : (ko ? "커리어 그래프 수신함을 자동으로 열지 못했습니다. 직접 여세요" : "Could not open Career Graph inbox automatically; open it manually")}: ${paths.inboxPath}`];
216
229
  }
217
230
  if (sub === "add") {
218
- const flags = parseFlagsCli(normalizedArgs.slice(1));
231
+ const flags = directFlags;
219
232
  const source = flags._[0];
220
233
  const kind = inferOntologyKindCli(flags.kind || flags._[1], normalizedArgs.join(" "));
221
234
  const scope = inferOntologyScopeCli(flags.scope || flags._[2], normalizedArgs.join(" "), kind);
@@ -4,6 +4,30 @@ const fs = require("node:fs");
4
4
  const path = require("node:path");
5
5
  const { listRoutableAgents } = require("../agents/registry.cjs");
6
6
 
7
+ const MAX_AGENT_POOL_MEMBERS = 64;
8
+ const MAX_AGENT_POOL_BYTES = 256 * 1024;
9
+ const MAX_AGENT_POOL_MEMBER_BYTES = 8 * 1024;
10
+ const MAX_AGENT_ID_BYTES = 512;
11
+ const MAX_RELEASE_ID_BYTES = 512;
12
+ const MAX_MEMBER_NAME_BYTES = 4 * 1024;
13
+ const MAX_PROJECT_NAME_BYTES = 4 * 1024;
14
+ const MAX_SYSTEM_PROMPT_BYTES = 256 * 1024;
15
+ const MAX_COMBINED_SYSTEM_PROMPT_BYTES = 512 * 1024;
16
+
17
+ function projectError(code, message) {
18
+ return Object.assign(new Error(message), { code, honestStop: true });
19
+ }
20
+
21
+ function boundedText(value, maxBytes, label, { allowEmpty = false } = {}) {
22
+ if (typeof value !== "string") throw projectError("project_team_unreadable", `This project's ${label} is invalid.`);
23
+ const text = value.trim();
24
+ if (!allowEmpty && !text) throw projectError("project_team_unreadable", `This project's ${label} is empty.`);
25
+ if (Buffer.byteLength(text, "utf8") > maxBytes || /[\u0000\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(text)) {
26
+ throw projectError("project_team_unreadable", `This project's ${label} is invalid or too large.`);
27
+ }
28
+ return text;
29
+ }
30
+
7
31
  function canonicalPath(value) {
8
32
  const resolved = path.resolve(String(value || ""));
9
33
  try {
@@ -27,19 +51,80 @@ function projectColumns(db) {
27
51
  }
28
52
 
29
53
  function parseAgentPool(raw) {
54
+ if (raw !== undefined && raw !== null && typeof raw !== "string") {
55
+ throw projectError("project_team_unreadable", "This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again.");
56
+ }
57
+ const encoded = raw === undefined || raw === null ? "[]" : raw;
58
+ if (Buffer.byteLength(encoded, "utf8") > MAX_AGENT_POOL_BYTES) {
59
+ throw projectError("project_team_unreadable", "This project's agent team is too large. Open the project in Agentlas Desktop and save its team again.");
60
+ }
30
61
  let parsed;
31
62
  try {
32
- parsed = JSON.parse(raw || "[]");
63
+ parsed = JSON.parse(encoded || "[]");
33
64
  } catch {
34
- throw Object.assign(new Error("This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again."), { code: "project_team_unreadable", honestStop: true });
65
+ throw projectError("project_team_unreadable", "This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again.");
35
66
  }
36
67
  if (!Array.isArray(parsed)) {
37
- throw Object.assign(new Error("This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again."), { code: "project_team_unreadable", honestStop: true });
68
+ throw projectError("project_team_unreadable", "This project's agent team cannot be read. Open the project in Agentlas Desktop and save its team again.");
38
69
  }
39
- return parsed.filter((member) => member && typeof member === "object"
40
- && typeof member.agentId === "string" && member.agentId.trim()
41
- && ["local", "cloud", "hub"].includes(member.source)
42
- && typeof member.nameSnapshot === "string");
70
+ if (parsed.length > MAX_AGENT_POOL_MEMBERS) {
71
+ throw projectError("project_team_too_large", "This project's agent team has too many members. Open the project in Agentlas Desktop and save its team again.");
72
+ }
73
+ const seen = new Set();
74
+ return parsed.map((member, index) => {
75
+ if (!member || typeof member !== "object" || Array.isArray(member)) {
76
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} is invalid.`);
77
+ }
78
+ if (Buffer.byteLength(JSON.stringify(member), "utf8") > MAX_AGENT_POOL_MEMBER_BYTES) {
79
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} is too large.`);
80
+ }
81
+ const keys = Object.keys(member).sort();
82
+ const legacyKeys = ["agentId", "nameSnapshot", "releaseId", "source"];
83
+ const canonicalKeys = ["agentId", "controllerAgentId", "entityKind", "firmId", "nameSnapshot", "releaseId", "source", "targetId"];
84
+ const isLegacy = keys.length === legacyKeys.length && keys.every((key, keyIndex) => key === legacyKeys[keyIndex]);
85
+ const isCanonical = keys.length === canonicalKeys.length && keys.every((key, keyIndex) => key === canonicalKeys[keyIndex]);
86
+ if (!isLegacy && !isCanonical) {
87
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} is incomplete.`);
88
+ }
89
+ const nameSnapshot = boundedText(member.nameSnapshot, MAX_MEMBER_NAME_BYTES, `agent team member ${index + 1} name`);
90
+ if (!["local", "cloud", "hub"].includes(member.source)) {
91
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} has an invalid source.`);
92
+ }
93
+ const releaseId = member.releaseId === null
94
+ ? null
95
+ : boundedText(member.releaseId, MAX_RELEASE_ID_BYTES, `agent team member ${index + 1} release`);
96
+ if (isLegacy) {
97
+ const agentId = boundedText(member.agentId, MAX_AGENT_ID_BYTES, `agent team member ${index + 1} agentId`);
98
+ const key = `${member.source}:agent:${agentId}:${releaseId || ""}`;
99
+ if (seen.has(key)) throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} is duplicated.`);
100
+ seen.add(key);
101
+ return {
102
+ entityKind: "agent", targetId: agentId, agentId, firmId: null, controllerAgentId: null,
103
+ source: member.source, releaseId, nameSnapshot,
104
+ };
105
+ }
106
+
107
+ if (member.entityKind !== "agent" && member.entityKind !== "team") {
108
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} has an invalid entity kind.`);
109
+ }
110
+ const optionalId = (value, label) => value === null
111
+ ? null
112
+ : boundedText(value, MAX_AGENT_ID_BYTES, `agent team member ${index + 1} ${label}`);
113
+ const targetId = boundedText(member.targetId, MAX_AGENT_ID_BYTES, `agent team member ${index + 1} targetId`);
114
+ const agentId = optionalId(member.agentId, "agentId");
115
+ const firmId = optionalId(member.firmId, "firmId");
116
+ const controllerAgentId = optionalId(member.controllerAgentId, "controllerAgentId");
117
+ if (member.entityKind === "agent" && (firmId !== null || controllerAgentId !== null || (member.source === "local" && agentId === null))) {
118
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} has inconsistent agent identity fields.`);
119
+ }
120
+ if (member.entityKind === "team" && (agentId !== null || (member.source === "local" && firmId === null))) {
121
+ throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} has inconsistent team identity fields.`);
122
+ }
123
+ const key = `${member.source}:${member.entityKind}:${targetId}:${releaseId || ""}`;
124
+ if (seen.has(key)) throw projectError("project_team_unreadable", `This project's agent team member ${index + 1} is duplicated.`);
125
+ seen.add(key);
126
+ return { entityKind: member.entityKind, targetId, agentId, firmId, controllerAgentId, source: member.source, releaseId, nameSnapshot };
127
+ });
43
128
  }
44
129
 
45
130
  function resolveProjectForCwd(db, cwd) {
@@ -64,7 +149,23 @@ function resolveProjectForCwd(db, cwd) {
64
149
  if (best.length !== 1) {
65
150
  throw Object.assign(new Error("More than one Agentlas project is connected to this folder. Keep one source connection, then retry."), { code: "project_ambiguous", honestStop: true });
66
151
  }
67
- return { ...best[0].row, rootPath: best[0].root, agentPool: parseAgentPool(best[0].row.agent_pool_json) };
152
+ const row = best[0].row;
153
+ boundedText(row.name, MAX_PROJECT_NAME_BYTES, "name");
154
+ if (row.system_prompt !== null && row.system_prompt !== undefined) {
155
+ boundedText(row.system_prompt, MAX_SYSTEM_PROMPT_BYTES, "system prompt", { allowEmpty: true });
156
+ }
157
+ return { ...row, rootPath: best[0].root, agentPool: parseAgentPool(row.agent_pool_json) };
158
+ }
159
+
160
+ function exactInstalledRelease(db, agentId) {
161
+ try {
162
+ const row = db.prepare(
163
+ "SELECT agent_release_id FROM installed_agent_hub_bindings WHERE installed_agent_id=?",
164
+ ).get(agentId);
165
+ return row && typeof row.agent_release_id === "string" ? row.agent_release_id : null;
166
+ } catch {
167
+ return null;
168
+ }
68
169
  }
69
170
 
70
171
  function resolveProjectController(db, cwd) {
@@ -73,10 +174,19 @@ function resolveProjectController(db, cwd) {
73
174
  throw Object.assign(new Error("This project has no agent team. Drag agents into the project in Desktop Work, then retry."), { code: "project_team_empty", honestStop: true });
74
175
  }
75
176
  const controllerRef = project.agentPool[0];
76
- if (controllerRef.source !== "local") {
177
+ const controllerAgentId = controllerRef.entityKind === "team"
178
+ ? controllerRef.controllerAgentId
179
+ : controllerRef.agentId;
180
+ if (!controllerAgentId) {
77
181
  throw Object.assign(new Error(`The project controller “${controllerRef.nameSnapshot}” is not installed locally for Terminal execution. Install that exact release locally or reorder the project team.`), { code: "controller_not_installed", honestStop: true });
78
182
  }
79
- const controller = listRoutableAgents(db).find((agent) => agent.id === controllerRef.agentId) || null;
183
+ if (controllerRef.entityKind === "team" && controllerRef.source !== "local" && controllerRef.releaseId !== null) {
184
+ throw projectError("controller_release_unavailable", `The project controller team “${controllerRef.nameSnapshot}” needs its exact team release restored before Terminal can execute it.`);
185
+ }
186
+ if (controllerRef.entityKind === "agent" && controllerRef.releaseId !== null && exactInstalledRelease(db, controllerAgentId) !== controllerRef.releaseId) {
187
+ throw projectError("controller_release_unavailable", `The project controller “${controllerRef.nameSnapshot}” is pinned to an unavailable exact release. Restore that release or explicitly choose a new first agent in Desktop Work.`);
188
+ }
189
+ const controller = listRoutableAgents(db).find((agent) => agent.id === controllerAgentId) || null;
80
190
  if (!controller) {
81
191
  throw Object.assign(new Error(`The project controller “${controllerRef.nameSnapshot}” is unavailable. Restore that agent or explicitly choose a new first agent in Desktop Work.`), { code: "controller_unavailable", honestStop: true });
82
192
  }
@@ -84,22 +194,30 @@ function resolveProjectController(db, cwd) {
84
194
  }
85
195
 
86
196
  function withProjectControllerContext(controller, project) {
197
+ const projectName = boundedText(project.name, MAX_PROJECT_NAME_BYTES, "name");
87
198
  const team = project.agentPool.map((member, index) =>
88
199
  `${index + 1}. ${member.nameSnapshot} (${member.source}${member.releaseId ? `, exact release ${member.releaseId}` : ""})`,
89
200
  ).join("\n");
90
- const projectInstruction = String(project.system_prompt || "").trim();
201
+ const projectInstruction = project.system_prompt === null || project.system_prompt === undefined
202
+ ? ""
203
+ : boundedText(project.system_prompt, MAX_SYSTEM_PROMPT_BYTES, "system prompt", { allowEmpty: true });
204
+ const controllerPrompt = boundedText(controller.systemPrompt || "", MAX_SYSTEM_PROMPT_BYTES, "controller system prompt", { allowEmpty: true });
91
205
  const ownership = [
92
- `You are the controller for the Agentlas Work project “${project.name}”.`,
206
+ `You are the controller for the Agentlas Work project “${projectName}”.`,
93
207
  "You own this task. The remaining ordered project members are eligible sub-agents for task-scoped WorkOrders only.",
94
208
  "Do not transfer task ownership, silently substitute an unavailable member, or claim a sub-agent ran without an assignment and execution receipt.",
95
209
  "Project team:",
96
210
  team,
97
211
  ].join("\n");
212
+ const systemPrompt = [controllerPrompt, projectInstruction && `Project system prompt:\n${projectInstruction}`, ownership]
213
+ .filter(Boolean)
214
+ .join("\n\n");
215
+ if (Buffer.byteLength(systemPrompt, "utf8") > MAX_COMBINED_SYSTEM_PROMPT_BYTES) {
216
+ throw projectError("project_context_too_large", "This project's controller context is too large to execute safely.");
217
+ }
98
218
  return {
99
219
  ...controller,
100
- systemPrompt: [controller.systemPrompt, projectInstruction && `Project system prompt:\n${projectInstruction}`, ownership]
101
- .filter(Boolean)
102
- .join("\n\n"),
220
+ systemPrompt,
103
221
  };
104
222
  }
105
223